.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / stylelint / lib / rules / function-calc-no-unspaced-operator / index.js
1 "use strict";
2
3 const balancedMatch = require("balanced-match");
4 const isWhitespace = require("../../utils/isWhitespace");
5 const report = require("../../utils/report");
6 const ruleMessages = require("../../utils/ruleMessages");
7 const styleSearch = require("style-search");
8 const validateOptions = require("../../utils/validateOptions");
9 const valueParser = require("postcss-value-parser");
10
11 const ruleName = "function-calc-no-unspaced-operator";
12
13 const messages = ruleMessages(ruleName, {
14   expectedBefore: operator =>
15     `Expected single space before "${operator}" operator`,
16   expectedAfter: operator =>
17     `Expected single space after "${operator}" operator`,
18   expectedOperatorBeforeSign: operator =>
19     `Expected an operator before sign "${operator}"`
20 });
21
22 const rule = function(actual) {
23   return (root, result) => {
24     const validOptions = validateOptions(result, ruleName, { actual });
25     if (!validOptions) {
26       return;
27     }
28
29     function complain(message, node, index) {
30       report({ message, node, index, result, ruleName });
31     }
32
33     root.walkDecls(decl => {
34       valueParser(decl.value).walk(node => {
35         if (node.type !== "function" || node.value.toLowerCase() !== "calc") {
36           return;
37         }
38
39         const parensMatch = balancedMatch(
40           "(",
41           ")",
42           valueParser.stringify(node)
43         );
44         const rawExpression = parensMatch.body;
45         const expressionIndex =
46           decl.source.start.column +
47           decl.prop.length +
48           (decl.raws.between || "").length +
49           node.sourceIndex;
50         const expression = blurVariables(rawExpression);
51
52         checkSymbol("+");
53         checkSymbol("-");
54         checkSymbol("*");
55         checkSymbol("/");
56
57         function checkSymbol(symbol) {
58           const styleSearchOptions = {
59             source: expression,
60             target: symbol,
61             functionArguments: "skip"
62           };
63
64           styleSearch(styleSearchOptions, match => {
65             const index = match.startIndex;
66
67             // Deal with signs.
68             // (@ and $ are considered "digits" here to allow for variable syntaxes
69             // that permit signs in front of variables, e.g. `-$number`)
70             // As is "." to deal with fractional numbers without a leading zero
71             if (
72               (symbol === "+" || symbol === "-") &&
73               /[\d@$.]/.test(expression[index + 1])
74             ) {
75               const expressionBeforeSign = expression.substr(0, index);
76
77               // Ignore signs that directly follow a opening bracket
78               if (
79                 expressionBeforeSign[expressionBeforeSign.length - 1] === "("
80               ) {
81                 return;
82               }
83
84               // Ignore signs at the beginning of the expression
85               if (/^\s*$/.test(expressionBeforeSign)) {
86                 return;
87               }
88
89               // Otherwise, ensure that there is a real operator preceeding them
90               if (/[*/+-]\s*$/.test(expressionBeforeSign)) {
91                 return;
92               }
93
94               // And if not, complain
95               complain(
96                 messages.expectedOperatorBeforeSign(symbol),
97                 decl,
98                 expressionIndex + index
99               );
100               return;
101             }
102
103             const beforeOk =
104               (expression[index - 1] === " " &&
105                 !isWhitespace(expression[index - 2])) ||
106               newlineBefore(expression, index - 1);
107             if (!beforeOk) {
108               complain(
109                 messages.expectedBefore(symbol),
110                 decl,
111                 expressionIndex + index
112               );
113             }
114
115             const afterOk =
116               (expression[index + 1] === " " &&
117                 !isWhitespace(expression[index + 2])) ||
118               expression[index + 1] === "\n" ||
119               expression.substr(index + 1, 2) === "\r\n";
120
121             if (!afterOk) {
122               complain(
123                 messages.expectedAfter(symbol),
124                 decl,
125                 expressionIndex + index
126               );
127             }
128           });
129         }
130       });
131     });
132   };
133 };
134
135 function blurVariables(source) {
136   return source.replace(/[$@][^)\s]+|#{.+?}/g, "0");
137 }
138
139 function newlineBefore(str, startIndex) {
140   let index = startIndex;
141   while (index && isWhitespace(str[index])) {
142     if (str[index] === "\n") return true;
143     index--;
144   }
145   return false;
146 }
147
148 rule.ruleName = ruleName;
149 rule.messages = messages;
150 module.exports = rule;