UNPKG

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