UNPKG

4.25 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-leading-zero';
11
12const messages = ruleMessages(ruleName, {
13 expected: 'Expected a leading zero',
14 rejected: 'Unexpected leading zero',
15});
16
17function rule(expectation, secondary, context) {
18 return (root, result) => {
19 const validOptions = validateOptions(result, ruleName, {
20 actual: expectation,
21 possible: ['always', 'never'],
22 });
23
24 if (!validOptions) {
25 return;
26 }
27
28 root.walkAtRules((atRule) => {
29 if (atRule.name.toLowerCase() === 'import') {
30 return;
31 }
32
33 check(atRule, atRule.params, atRuleParamIndex);
34 });
35
36 root.walkDecls((decl) => check(decl, decl.value, declarationValueIndex));
37
38 function check(node, value, getIndex) {
39 const neverFixPositions = [];
40 const alwaysFixPositions = [];
41
42 // Get out quickly if there are no periods
43 if (!value.includes('.')) {
44 return;
45 }
46
47 valueParser(value).walk((valueNode) => {
48 // Ignore `url` function
49 if (valueNode.type === 'function' && valueNode.value.toLowerCase() === 'url') {
50 return false;
51 }
52
53 // Ignore strings, comments, etc
54 if (valueNode.type !== 'word') {
55 return;
56 }
57
58 // Check leading zero
59 if (expectation === 'always') {
60 const match = /(?:\D|^)(\.\d+)/.exec(valueNode.value);
61
62 if (match === null) {
63 return;
64 }
65
66 // The regexp above consists of 2 capturing groups (or capturing parentheses).
67 // We need the index of the second group. This makes sanse when we have "-.5" as an input
68 // for regex. And we need the index of ".5".
69 const capturingGroupIndex = match[0].length - match[1].length;
70
71 const index = valueNode.sourceIndex + match.index + capturingGroupIndex;
72
73 if (context.fix) {
74 alwaysFixPositions.unshift({
75 index,
76 });
77
78 return;
79 }
80
81 complain(messages.expected, node, getIndex(node) + index);
82 }
83
84 if (expectation === 'never') {
85 const match = /(?:\D|^)(0+)(\.\d+)/.exec(valueNode.value);
86
87 if (match === null) {
88 return;
89 }
90
91 // The regexp above consists of 3 capturing groups (or capturing parentheses).
92 // We need the index of the second group. This makes sanse when we have "-00.5"
93 // as an input for regex. And we need the index of "00".
94 const capturingGroupIndex = match[0].length - (match[1].length + match[2].length);
95
96 const index = valueNode.sourceIndex + match.index + capturingGroupIndex;
97
98 if (context.fix) {
99 neverFixPositions.unshift({
100 startIndex: index,
101 // match[1].length is the length of our matched zero(s)
102 endIndex: index + match[1].length,
103 });
104
105 return;
106 }
107
108 complain(messages.rejected, node, getIndex(node) + index);
109 }
110 });
111
112 if (alwaysFixPositions.length) {
113 alwaysFixPositions.forEach((fixPosition) => {
114 const index = fixPosition.index;
115
116 if (node.type === 'atrule') {
117 node.params = addLeadingZero(node.params, index);
118 } else {
119 node.value = addLeadingZero(node.value, index);
120 }
121 });
122 }
123
124 if (neverFixPositions.length) {
125 neverFixPositions.forEach((fixPosition) => {
126 const startIndex = fixPosition.startIndex;
127 const endIndex = fixPosition.endIndex;
128
129 if (node.type === 'atrule') {
130 node.params = removeLeadingZeros(node.params, startIndex, endIndex);
131 } else {
132 node.value = removeLeadingZeros(node.value, startIndex, endIndex);
133 }
134 });
135 }
136 }
137
138 function complain(message, node, index) {
139 report({
140 result,
141 ruleName,
142 message,
143 node,
144 index,
145 });
146 }
147 };
148}
149
150function addLeadingZero(input, index) {
151 // eslint-disable-next-line prefer-template
152 return input.slice(0, index) + '0' + input.slice(index);
153}
154
155function removeLeadingZeros(input, startIndex, endIndex) {
156 return input.slice(0, startIndex) + input.slice(endIndex);
157}
158
159rule.ruleName = ruleName;
160rule.messages = messages;
161module.exports = rule;