| "use strict"; |
| |
| const atRuleParamIndex = require("../../utils/atRuleParamIndex"); |
| const declarationValueIndex = require("../../utils/declarationValueIndex"); |
| const report = require("../../utils/report"); |
| const ruleMessages = require("../../utils/ruleMessages"); |
| const validateOptions = require("../../utils/validateOptions"); |
| const valueParser = require("postcss-value-parser"); |
| |
| const ruleName = "number-no-trailing-zeros"; |
| |
| const messages = ruleMessages(ruleName, { |
| rejected: "Unexpected trailing zero(s)" |
| }); |
| |
| const rule = function(actual, secondary, context) { |
| return (root, result) => { |
| const validOptions = validateOptions(result, ruleName, { actual }); |
| |
| if (!validOptions) { |
| return; |
| } |
| |
| root.walkAtRules(atRule => { |
| if (atRule.name.toLowerCase() === "import") { |
| return; |
| } |
| |
| check(atRule, atRule.params, atRuleParamIndex); |
| }); |
| |
| root.walkDecls(decl => check(decl, decl.value, declarationValueIndex)); |
| |
| function check(node, value, getIndex) { |
| const fixPositions = []; |
| |
| // Get out quickly if there are no periods |
| if (value.indexOf(".") === -1) { |
| return; |
| } |
| |
| valueParser(value).walk(valueNode => { |
| // Ignore `url` function |
| if ( |
| valueNode.type === "function" && |
| valueNode.value.toLowerCase() === "url" |
| ) { |
| return false; |
| } |
| |
| // Ignore strings, comments, etc |
| if (valueNode.type !== "word") { |
| return; |
| } |
| |
| const match = /\.(\d*?)(0+)(?:\D|$)/.exec(valueNode.value); |
| |
| // match[1] is any numbers between the decimal and our trailing zero, could be empty |
| // match[2] is our trailing zero(s) |
| if (match === null) { |
| return; |
| } |
| |
| // our index is: |
| // the index of our valueNode + |
| // the index of our match + |
| // 1 for our decimal + |
| // the length of our potential non-zero number match (match[1]) |
| const index = valueNode.sourceIndex + match.index + 1 + match[1].length; |
| |
| // our startIndex is identical to our index except when we have only |
| // trailing zeros after our decimal. in that case we don't need the decimal |
| // either so we move our index back by 1. |
| const startIndex = match[1].length > 0 ? index : index - 1; |
| |
| // our end index is our original index + the length of our trailing zeros |
| const endIndex = index + match[2].length; |
| |
| if (context.fix) { |
| fixPositions.unshift({ |
| startIndex, |
| endIndex |
| }); |
| |
| return; |
| } else { |
| report({ |
| message: messages.rejected, |
| node, |
| // this is the index of the _first_ trailing zero |
| index: getIndex(node) + index, |
| result, |
| ruleName |
| }); |
| } |
| }); |
| |
| if (fixPositions.length) { |
| fixPositions.forEach(function(fixPosition) { |
| const startIndex = fixPosition.startIndex; |
| const endIndex = fixPosition.endIndex; |
| |
| if (node.type === "atrule") { |
| node.params = removeTrailingZeros( |
| node.params, |
| startIndex, |
| endIndex |
| ); |
| } else { |
| node.value = removeTrailingZeros(node.value, startIndex, endIndex); |
| } |
| }); |
| } |
| } |
| }; |
| }; |
| |
| function removeTrailingZeros(input, startIndex, endIndex) { |
| return input.slice(0, startIndex) + input.slice(endIndex); |
| } |
| |
| rule.ruleName = ruleName; |
| rule.messages = messages; |
| module.exports = rule; |