--- /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-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;