123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- // @ts-nocheck
-
- 'use strict';
-
- const declarationValueIndex = require('../../utils/declarationValueIndex');
- const isStandardSyntaxMathFunction = require('../../utils/isStandardSyntaxMathFunction');
- const parseCalcExpression = require('../../utils/parseCalcExpression');
- const report = require('../../utils/report');
- const ruleMessages = require('../../utils/ruleMessages');
- const validateOptions = require('../../utils/validateOptions');
- const valueParser = require('postcss-value-parser');
-
- const ruleName = 'function-calc-no-invalid';
-
- const messages = ruleMessages(ruleName, {
- expectedExpression: () => 'Expected a valid expression',
- expectedSpaceBeforeOperator: (operator) => `Expected space before "${operator}" operator`,
- expectedSpaceAfterOperator: (operator) => `Expected space after "${operator}" operator`,
- rejectedDivisionByZero: () => 'Unexpected division by zero',
- expectedValidResolvedType: (operator) =>
- `Expected to be compatible with the left and right argument types of "${operator}" operation.`,
- });
-
- function rule(actual) {
- return (root, result) => {
- const validOptions = validateOptions(result, ruleName, { actual });
-
- if (!validOptions) {
- return;
- }
-
- root.walkDecls((decl) => {
- const checked = [];
-
- valueParser(decl.value).walk((node) => {
- if (node.type !== 'function' || node.value.toLowerCase() !== 'calc') {
- return;
- }
-
- const mathFunction = valueParser.stringify(node);
-
- if (!isStandardSyntaxMathFunction(mathFunction)) {
- return;
- }
-
- if (checked.includes(node)) {
- return;
- }
-
- checked.push(...getCalcNodes(node));
-
- checked.push(...node.nodes);
-
- let ast;
-
- try {
- ast = parseCalcExpression(mathFunction);
- } catch (e) {
- if (e.hash && e.hash.loc) {
- complain(messages.expectedExpression(), node.sourceIndex + e.hash.loc.range[0]);
-
- return;
- }
-
- throw e;
- }
-
- verifyMathExpressions(ast, node);
- });
-
- function complain(message, valueIndex) {
- report({
- message,
- node: decl,
- index: declarationValueIndex(decl) + valueIndex,
- result,
- ruleName,
- });
- }
-
- /**
- * Verify that each operation expression is valid.
- * Reports when a invalid operation expression is found.
- * @param {object} expression expression node.
- * @param {object} node calc function node.
- * @returns {void}
- */
- function verifyMathExpressions(expression, node) {
- if (expression.type === 'MathExpression') {
- const { operator, left, right } = expression;
-
- if (operator === '+' || operator === '-') {
- if (expression.source.operator.end.index === right.source.start.index) {
- complain(
- messages.expectedSpaceAfterOperator(operator),
- node.sourceIndex + expression.source.operator.end.index,
- );
- }
-
- if (expression.source.operator.start.index === left.source.end.index) {
- complain(
- messages.expectedSpaceBeforeOperator(operator),
- node.sourceIndex + expression.source.operator.start.index,
- );
- }
- } else if (operator === '/') {
- if (
- (right.type === 'Value' && right.value === 0) ||
- (right.type === 'MathExpression' && getNumber(right) === 0)
- ) {
- complain(
- messages.rejectedDivisionByZero(),
- node.sourceIndex + expression.source.operator.end.index,
- );
- }
- }
-
- if (getResolvedType(expression) === 'invalid') {
- complain(
- messages.expectedValidResolvedType(operator),
- node.sourceIndex + expression.source.operator.start.index,
- );
- }
-
- verifyMathExpressions(expression.left, node);
- verifyMathExpressions(expression.right, node);
- }
- }
- });
- };
- }
-
- function getCalcNodes(node) {
- if (node.type !== 'function') {
- return [];
- }
-
- const functionName = node.value.toLowerCase();
- const result = [];
-
- if (functionName === 'calc') {
- result.push(node);
- }
-
- if (!functionName || functionName === 'calc') {
- // find nested calc
- for (const c of node.nodes) {
- result.push(...getCalcNodes(c));
- }
- }
-
- return result;
- }
-
- function getNumber(mathExpression) {
- const { left, right } = mathExpression;
-
- const leftValue =
- left.type === 'Value' ? left.value : left.type === 'MathExpression' ? getNumber(left) : null;
- const rightValue =
- right.type === 'Value'
- ? right.value
- : right.type === 'MathExpression'
- ? getNumber(right)
- : null;
-
- if (leftValue == null || rightValue == null) {
- return null;
- }
-
- switch (mathExpression.operator) {
- case '+':
- return leftValue + rightValue;
- case '-':
- return leftValue - rightValue;
- case '*':
- return leftValue * rightValue;
- case '/':
- return leftValue / rightValue;
- }
-
- return null;
- }
-
- function getResolvedType(mathExpression) {
- const { left: leftExpression, operator, right: rightExpression } = mathExpression;
- let left =
- leftExpression.type === 'MathExpression'
- ? getResolvedType(leftExpression)
- : leftExpression.type;
- let right =
- rightExpression.type === 'MathExpression'
- ? getResolvedType(rightExpression)
- : rightExpression.type;
-
- if (left === 'Function' || left === 'invalid') {
- left = 'UnknownValue';
- }
-
- if (right === 'Function' || right === 'invalid') {
- right = 'UnknownValue';
- }
-
- switch (operator) {
- case '+':
- case '-':
- if (left === 'UnknownValue' || right === 'UnknownValue') {
- return 'UnknownValue';
- }
-
- if (left === right) {
- return left;
- }
-
- if (left === 'Value' || right === 'Value') {
- return 'invalid';
- }
-
- if (left === 'PercentageValue') {
- return right;
- }
-
- if (right === 'PercentageValue') {
- return left;
- }
-
- return 'invalid';
- case '*':
- if (left === 'UnknownValue' || right === 'UnknownValue') {
- return 'UnknownValue';
- }
-
- if (left === 'Value') {
- return right;
- }
-
- if (right === 'Value') {
- return left;
- }
-
- return 'invalid';
- case '/':
- if (right === 'UnknownValue') {
- return 'UnknownValue';
- }
-
- if (right === 'Value') {
- return left;
- }
-
- return 'invalid';
- }
-
- return 'UnknownValue';
- }
-
- rule.ruleName = ruleName;
- rule.messages = messages;
- module.exports = rule;
|