UNPKG

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