UNPKG

3.17 kBJavaScriptView Raw
1'use strict';
2
3const atRuleParamIndex = require('../../utils/atRuleParamIndex');
4const declarationValueIndex = require('../../utils/declarationValueIndex');
5const report = require('../../utils/report');
6const ruleMessages = require('../../utils/ruleMessages');
7const validateOptions = require('../../utils/validateOptions');
8const valueParser = require('postcss-value-parser');
9
10const ruleName = 'number-no-trailing-zeros';
11
12const messages = ruleMessages(ruleName, {
13 rejected: 'Unexpected trailing zero(s)',
14});
15
16function rule(actual, secondary, context) {
17 return (root, result) => {
18 const validOptions = validateOptions(result, ruleName, { actual });
19
20 if (!validOptions) {
21 return;
22 }
23
24 root.walkAtRules((atRule) => {
25 if (atRule.name.toLowerCase() === 'import') {
26 return;
27 }
28
29 check(atRule, atRule.params, atRuleParamIndex);
30 });
31
32 root.walkDecls((decl) => check(decl, decl.value, declarationValueIndex));
33
34 function check(node, value, getIndex) {
35 const fixPositions = [];
36
37 // Get out quickly if there are no periods
38 if (!value.includes('.')) {
39 return;
40 }
41
42 valueParser(value).walk((valueNode) => {
43 // Ignore `url` function
44 if (valueNode.type === 'function' && valueNode.value.toLowerCase() === 'url') {
45 return false;
46 }
47
48 // Ignore strings, comments, etc
49 if (valueNode.type !== 'word') {
50 return;
51 }
52
53 const match = /\.(\d*?)(0+)(?:\D|$)/.exec(valueNode.value);
54
55 // match[1] is any numbers between the decimal and our trailing zero, could be empty
56 // match[2] is our trailing zero(s)
57 if (match === null) {
58 return;
59 }
60
61 // our index is:
62 // the index of our valueNode +
63 // the index of our match +
64 // 1 for our decimal +
65 // the length of our potential non-zero number match (match[1])
66 const index = valueNode.sourceIndex + match.index + 1 + match[1].length;
67
68 // our startIndex is identical to our index except when we have only
69 // trailing zeros after our decimal. in that case we don't need the decimal
70 // either so we move our index back by 1.
71 const startIndex = match[1].length > 0 ? index : index - 1;
72
73 // our end index is our original index + the length of our trailing zeros
74 const endIndex = index + match[2].length;
75
76 if (context.fix) {
77 fixPositions.unshift({
78 startIndex,
79 endIndex,
80 });
81
82 return;
83 }
84
85 report({
86 message: messages.rejected,
87 node,
88 // this is the index of the _first_ trailing zero
89 index: getIndex(node) + index,
90 result,
91 ruleName,
92 });
93 });
94
95 if (fixPositions.length) {
96 fixPositions.forEach((fixPosition) => {
97 const startIndex = fixPosition.startIndex;
98 const endIndex = fixPosition.endIndex;
99
100 if (node.type === 'atrule') {
101 node.params = removeTrailingZeros(node.params, startIndex, endIndex);
102 } else {
103 node.value = removeTrailingZeros(node.value, startIndex, endIndex);
104 }
105 });
106 }
107 }
108 };
109}
110
111function removeTrailingZeros(input, startIndex, endIndex) {
112 return input.slice(0, startIndex) + input.slice(endIndex);
113}
114
115rule.ruleName = ruleName;
116rule.messages = messages;
117module.exports = rule;