--- /dev/null
+"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;