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");
11 const ruleName = "function-calc-no-unspaced-operator";
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}"`
22 const rule = function(actual) {
23 return (root, result) => {
24 const validOptions = validateOptions(result, ruleName, { actual });
29 function complain(message, node, index) {
30 report({ message, node, index, result, ruleName });
33 root.walkDecls(decl => {
34 valueParser(decl.value).walk(node => {
35 if (node.type !== "function" || node.value.toLowerCase() !== "calc") {
39 const parensMatch = balancedMatch(
42 valueParser.stringify(node)
44 const rawExpression = parensMatch.body;
45 const expressionIndex =
46 decl.source.start.column +
48 (decl.raws.between || "").length +
50 const expression = blurVariables(rawExpression);
57 function checkSymbol(symbol) {
58 const styleSearchOptions = {
61 functionArguments: "skip"
64 styleSearch(styleSearchOptions, match => {
65 const index = match.startIndex;
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
72 (symbol === "+" || symbol === "-") &&
73 /[\d@$.]/.test(expression[index + 1])
75 const expressionBeforeSign = expression.substr(0, index);
77 // Ignore signs that directly follow a opening bracket
79 expressionBeforeSign[expressionBeforeSign.length - 1] === "("
84 // Ignore signs at the beginning of the expression
85 if (/^\s*$/.test(expressionBeforeSign)) {
89 // Otherwise, ensure that there is a real operator preceeding them
90 if (/[*/+-]\s*$/.test(expressionBeforeSign)) {
94 // And if not, complain
96 messages.expectedOperatorBeforeSign(symbol),
98 expressionIndex + index
104 (expression[index - 1] === " " &&
105 !isWhitespace(expression[index - 2])) ||
106 newlineBefore(expression, index - 1);
109 messages.expectedBefore(symbol),
111 expressionIndex + index
116 (expression[index + 1] === " " &&
117 !isWhitespace(expression[index + 2])) ||
118 expression[index + 1] === "\n" ||
119 expression.substr(index + 1, 2) === "\r\n";
123 messages.expectedAfter(symbol),
125 expressionIndex + index
135 function blurVariables(source) {
136 return source.replace(/[$@][^)\s]+|#{.+?}/g, "0");
139 function newlineBefore(str, startIndex) {
140 let index = startIndex;
141 while (index && isWhitespace(str[index])) {
142 if (str[index] === "\n") return true;
148 rule.ruleName = ruleName;
149 rule.messages = messages;
150 module.exports = rule;