.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / stylelint / lib / rules / length-zero-no-unit / index.js
1 "use strict";
2
3 const _ = require("lodash");
4 const beforeBlockString = require("../../utils/beforeBlockString");
5 const blurComments = require("../../utils/blurComments");
6 const hasBlock = require("../../utils/hasBlock");
7 const isCustomProperty = require("../../utils/isCustomProperty");
8 const keywordSets = require("../../reference/keywordSets");
9 const optionsMatches = require("../../utils/optionsMatches");
10 const report = require("../../utils/report");
11 const ruleMessages = require("../../utils/ruleMessages");
12 const styleSearch = require("style-search");
13 const validateOptions = require("../../utils/validateOptions");
14 const valueParser = require("postcss-value-parser");
15
16 const ruleName = "length-zero-no-unit";
17
18 const messages = ruleMessages(ruleName, {
19   rejected: "Unexpected unit"
20 });
21
22 const rule = function(actual, secondary, context) {
23   return (root, result) => {
24     const validOptions = validateOptions(result, ruleName, { actual });
25     if (!validOptions) {
26       return;
27     }
28
29     root.walkDecls(decl => {
30       check(blurComments(decl.toString()), decl);
31     });
32
33     root.walkAtRules(atRule => {
34       const source = hasBlock(atRule)
35         ? beforeBlockString(atRule, { noRawBefore: true })
36         : atRule.toString();
37       check(source, atRule);
38     });
39
40     function check(value, node) {
41       const ignorableIndexes = new Set();
42       const fixPositions = [];
43
44       if (
45         optionsMatches(secondary, "ignore", "custom-properties") &&
46         isCustomProperty(value)
47       ) {
48         return;
49       }
50
51       styleSearch({ source: value, target: "0" }, match => {
52         const index = match.startIndex;
53
54         // Given a 0 somewhere in the full property value (not in a string, thanks
55         // to styleSearch) we need to isolate the value that contains the zero.
56         // To do so, we'll find the last index before the 0 of a character that would
57         // divide one value in a list from another, and the next index of such a
58         // character; then we build a substring from those indexes, which we can
59         // assess.
60
61         // If a single value includes multiple 0's (e.g. 100.01px), we don't want
62         // each 0 to be treated as a separate value, possibly resulting in multiple
63         // warnings for the same value (e.g. 0.00px).
64         //
65         // This check prevents that from happening: we build and check against a
66         // Set containing all the indexes that are part of a value already validated.
67         if (ignorableIndexes.has(index)) {
68           return;
69         }
70
71         const prevValueBreakIndex = _.findLastIndex(
72           value.substr(0, index),
73           char => {
74             return (
75               [" ", ",", ")", "(", "#", ":", "\n", "\t"].indexOf(char) !== -1
76             );
77           }
78         );
79
80         // Ignore hex colors
81         if (value[prevValueBreakIndex] === "#") {
82           return;
83         }
84
85         // If no prev break was found, this value starts at 0
86         const valueWithZeroStart =
87           prevValueBreakIndex === -1 ? 0 : prevValueBreakIndex + 1;
88
89         const nextValueBreakIndex = _.findIndex(
90           value.substr(valueWithZeroStart),
91           char => {
92             return [" ", ",", ")"].indexOf(char) !== -1;
93           }
94         );
95
96         // If no next break was found, this value ends at the end of the string
97         const valueWithZeroEnd =
98           nextValueBreakIndex === -1
99             ? value.length
100             : nextValueBreakIndex + valueWithZeroStart;
101
102         const valueWithZero = value.slice(valueWithZeroStart, valueWithZeroEnd);
103         const parsedValue = valueParser.unit(valueWithZero);
104
105         if (!parsedValue || (parsedValue && !parsedValue.unit)) {
106           return;
107         }
108
109         // Add the indexes to ignorableIndexes so the same value will not
110         // be checked multiple times.
111         _.range(valueWithZeroStart, valueWithZeroEnd).forEach(i =>
112           ignorableIndexes.add(i)
113         );
114
115         // Only pay attention if the value parses to 0
116         // and units with lengths
117         if (
118           parseFloat(valueWithZero, 10) !== 0 ||
119           !keywordSets.lengthUnits.has(parsedValue.unit.toLowerCase())
120         ) {
121           return;
122         }
123
124         if (context.fix) {
125           fixPositions.unshift({
126             startIndex: valueWithZeroStart,
127             length: valueWithZeroEnd - valueWithZeroStart
128           });
129           return;
130         }
131
132         report({
133           message: messages.rejected,
134           node,
135           index: valueWithZeroEnd - parsedValue.unit.length,
136           result,
137           ruleName
138         });
139       });
140
141       if (fixPositions.length) {
142         fixPositions.forEach(function(fixPosition) {
143           let startIndex = fixPosition.startIndex;
144           // all nodes can have this
145           startIndex -= node.raws.between.length;
146
147           if (node.type === "atrule") {
148             // atrules have a name and raws.afterName
149             startIndex -= node.name.length;
150             startIndex -= node.raws.afterName.length;
151
152             node.params = replaceZero(
153               node.params,
154               startIndex,
155               fixPosition.length
156             );
157           } else {
158             // other nodes have prop
159             startIndex -= node.prop.length;
160             node.value = replaceZero(
161               node.value,
162               startIndex,
163               fixPosition.length
164             );
165           }
166         });
167       }
168     }
169   };
170 };
171
172 function replaceZero(input, startIndex, length) {
173   const stringStart = input.slice(0, startIndex);
174   const stringEnd = input.slice(startIndex + length);
175   return stringStart + "0" + stringEnd;
176 }
177
178 rule.ruleName = ruleName;
179 rule.messages = messages;
180 module.exports = rule;