| "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-leading-zero"; |
| |
| const messages = ruleMessages(ruleName, { |
| expected: "Expected a leading zero", |
| rejected: "Unexpected leading zero" |
| }); |
| |
| const rule = function(expectation, secondary, context) { |
| return (root, result) => { |
| const validOptions = validateOptions(result, ruleName, { |
| actual: expectation, |
| possible: ["always", "never"] |
| }); |
| |
| 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 neverFixPositions = []; |
| const alwaysFixPositions = []; |
| |
| // 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; |
| } |
| |
| // Check leading zero |
| if (expectation === "always") { |
| const match = /(?:\D|^)(\.\d+)/.exec(valueNode.value); |
| |
| if (match === null) { |
| return; |
| } |
| |
| // The regexp above consists of 2 capturing groups (or capturing parentheses). |
| // We need the index of the second group. This makes sanse when we have "-.5" as an input |
| // for regex. And we need the index of ".5". |
| const capturingGroupIndex = match[0].length - match[1].length; |
| |
| const index = |
| valueNode.sourceIndex + match.index + capturingGroupIndex; |
| |
| if (context.fix) { |
| alwaysFixPositions.unshift({ |
| index |
| }); |
| |
| return; |
| } else { |
| complain(messages.expected, node, getIndex(node) + index); |
| } |
| } |
| |
| if (expectation === "never") { |
| const match = /(?:\D|^)(0+)(\.\d+)/.exec(valueNode.value); |
| |
| if (match === null) { |
| return; |
| } |
| |
| // The regexp above consists of 3 capturing groups (or capturing parentheses). |
| // We need the index of the second group. This makes sanse when we have "-00.5" |
| // as an input for regex. And we need the index of "00". |
| const capturingGroupIndex = |
| match[0].length - (match[1].length + match[2].length); |
| |
| const index = |
| valueNode.sourceIndex + match.index + capturingGroupIndex; |
| |
| if (context.fix) { |
| neverFixPositions.unshift({ |
| startIndex: index, |
| // match[1].length is the length of our matched zero(s) |
| endIndex: index + match[1].length |
| }); |
| |
| return; |
| } else { |
| complain(messages.rejected, node, getIndex(node) + index); |
| } |
| } |
| }); |
| |
| if (alwaysFixPositions.length) { |
| alwaysFixPositions.forEach(function(fixPosition) { |
| const index = fixPosition.index; |
| |
| if (node.type === "atrule") { |
| node.params = addLeadingZero(node.params, index); |
| } else { |
| node.value = addLeadingZero(node.value, index); |
| } |
| }); |
| } |
| |
| if (neverFixPositions.length) { |
| neverFixPositions.forEach(function(fixPosition) { |
| const startIndex = fixPosition.startIndex; |
| const endIndex = fixPosition.endIndex; |
| |
| if (node.type === "atrule") { |
| node.params = removeLeadingZeros(node.params, startIndex, endIndex); |
| } else { |
| node.value = removeLeadingZeros(node.value, startIndex, endIndex); |
| } |
| }); |
| } |
| } |
| |
| function complain(message, node, index) { |
| report({ |
| result, |
| ruleName, |
| message, |
| node, |
| index |
| }); |
| } |
| }; |
| }; |
| |
| function addLeadingZero(input, index) { |
| return input.slice(0, index) + "0" + input.slice(index); |
| } |
| |
| function removeLeadingZeros(input, startIndex, endIndex) { |
| return input.slice(0, startIndex) + input.slice(endIndex); |
| } |
| |
| rule.ruleName = ruleName; |
| rule.messages = messages; |
| module.exports = rule; |