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");
16 const ruleName = "length-zero-no-unit";
18 const messages = ruleMessages(ruleName, {
19 rejected: "Unexpected unit"
22 const rule = function(actual, secondary, context) {
23 return (root, result) => {
24 const validOptions = validateOptions(result, ruleName, { actual });
29 root.walkDecls(decl => {
30 check(blurComments(decl.toString()), decl);
33 root.walkAtRules(atRule => {
34 const source = hasBlock(atRule)
35 ? beforeBlockString(atRule, { noRawBefore: true })
37 check(source, atRule);
40 function check(value, node) {
41 const ignorableIndexes = new Set();
42 const fixPositions = [];
45 optionsMatches(secondary, "ignore", "custom-properties") &&
46 isCustomProperty(value)
51 styleSearch({ source: value, target: "0" }, match => {
52 const index = match.startIndex;
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
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).
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)) {
71 const prevValueBreakIndex = _.findLastIndex(
72 value.substr(0, index),
75 [" ", ",", ")", "(", "#", ":", "\n", "\t"].indexOf(char) !== -1
81 if (value[prevValueBreakIndex] === "#") {
85 // If no prev break was found, this value starts at 0
86 const valueWithZeroStart =
87 prevValueBreakIndex === -1 ? 0 : prevValueBreakIndex + 1;
89 const nextValueBreakIndex = _.findIndex(
90 value.substr(valueWithZeroStart),
92 return [" ", ",", ")"].indexOf(char) !== -1;
96 // If no next break was found, this value ends at the end of the string
97 const valueWithZeroEnd =
98 nextValueBreakIndex === -1
100 : nextValueBreakIndex + valueWithZeroStart;
102 const valueWithZero = value.slice(valueWithZeroStart, valueWithZeroEnd);
103 const parsedValue = valueParser.unit(valueWithZero);
105 if (!parsedValue || (parsedValue && !parsedValue.unit)) {
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)
115 // Only pay attention if the value parses to 0
116 // and units with lengths
118 parseFloat(valueWithZero, 10) !== 0 ||
119 !keywordSets.lengthUnits.has(parsedValue.unit.toLowerCase())
125 fixPositions.unshift({
126 startIndex: valueWithZeroStart,
127 length: valueWithZeroEnd - valueWithZeroStart
133 message: messages.rejected,
135 index: valueWithZeroEnd - parsedValue.unit.length,
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;
147 if (node.type === "atrule") {
148 // atrules have a name and raws.afterName
149 startIndex -= node.name.length;
150 startIndex -= node.raws.afterName.length;
152 node.params = replaceZero(
158 // other nodes have prop
159 startIndex -= node.prop.length;
160 node.value = replaceZero(
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;
178 rule.ruleName = ruleName;
179 rule.messages = messages;
180 module.exports = rule;