This repository has been archived on 2020-11-02. You can view files and clone it, but cannot push or open issues or pull requests.
2020-11-01 22:46:04 +00:00

139 lines
3.8 KiB
JavaScript

// @ts-nocheck
'use strict';
const balancedMatch = require('balanced-match');
const isWhitespace = require('../../utils/isWhitespace');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const styleSearch = require('style-search');
const validateOptions = require('../../utils/validateOptions');
const valueParser = require('postcss-value-parser');
const ruleName = 'function-calc-no-unspaced-operator';
const messages = ruleMessages(ruleName, {
expectedBefore: (operator) => `Expected single space before "${operator}" operator`,
expectedAfter: (operator) => `Expected single space after "${operator}" operator`,
expectedOperatorBeforeSign: (operator) => `Expected an operator before sign "${operator}"`,
});
function rule(actual) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, { actual });
if (!validOptions) {
return;
}
function complain(message, node, index) {
report({ message, node, index, result, ruleName });
}
root.walkDecls((decl) => {
valueParser(decl.value).walk((node) => {
if (node.type !== 'function' || node.value.toLowerCase() !== 'calc') {
return;
}
const nodeText = valueParser.stringify(node);
const parensMatch = balancedMatch('(', ')', nodeText);
if (!parensMatch) {
throw new Error(`No parens match: "${nodeText}"`);
}
const rawExpression = parensMatch.body;
const expressionIndex =
decl.source.start.column +
decl.prop.length +
(decl.raws.between || '').length +
node.sourceIndex;
const expression = blurVariables(rawExpression);
checkSymbol('+');
checkSymbol('-');
checkSymbol('*');
checkSymbol('/');
function checkSymbol(symbol) {
const styleSearchOptions = {
source: expression,
target: symbol,
functionArguments: 'skip',
};
styleSearch(styleSearchOptions, (match) => {
const index = match.startIndex;
// Deal with signs.
// (@ and $ are considered "digits" here to allow for variable syntaxes
// that permit signs in front of variables, e.g. `-$number`)
// As is "." to deal with fractional numbers without a leading zero
if ((symbol === '+' || symbol === '-') && /[\d@$.]/.test(expression[index + 1])) {
const expressionBeforeSign = expression.substr(0, index);
// Ignore signs that directly follow a opening bracket
if (expressionBeforeSign[expressionBeforeSign.length - 1] === '(') {
return;
}
// Ignore signs at the beginning of the expression
if (/^\s*$/.test(expressionBeforeSign)) {
return;
}
// Otherwise, ensure that there is a real operator preceding them
if (/[*/+-]\s*$/.test(expressionBeforeSign)) {
return;
}
// And if not, complain
complain(messages.expectedOperatorBeforeSign(symbol), decl, expressionIndex + index);
return;
}
const beforeOk =
(expression[index - 1] === ' ' && !isWhitespace(expression[index - 2])) ||
newlineBefore(expression, index - 1);
if (!beforeOk) {
complain(messages.expectedBefore(symbol), decl, expressionIndex + index);
}
const afterOk =
(expression[index + 1] === ' ' && !isWhitespace(expression[index + 2])) ||
expression[index + 1] === '\n' ||
expression.substr(index + 1, 2) === '\r\n';
if (!afterOk) {
complain(messages.expectedAfter(symbol), decl, expressionIndex + index);
}
});
}
});
});
};
}
function blurVariables(source) {
return source.replace(/[$@][^)\s]+|#{.+?}/g, '0');
}
function newlineBefore(str, startIndex) {
let index = startIndex;
while (index && isWhitespace(str[index])) {
if (str[index] === '\n') return true;
index--;
}
return false;
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;