Skip to content

Commit

Permalink
Refactor function-calc-no-unspaced-operator for coverage improvemen…
Browse files Browse the repository at this point in the history
…ts (#7646)

In addition to coverage, this renames variables/functions for more readability.
  • Loading branch information
ybiquitous committed Apr 25, 2024
1 parent c4c1816 commit 48eb848
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 337 deletions.
209 changes: 41 additions & 168 deletions lib/rules/function-calc-no-unspaced-operator/index.cjs
Expand Up @@ -63,131 +63,55 @@ const rule = (primary, _secondaryOptions, context) => {

let needsFix = false;
const valueIndex = declarationValueIndex(decl);
const parsedValue = valueParser(value);

/**
* @param {import('postcss-value-parser').Node} operatorNode
* @param {import('postcss-value-parser').Node} currentNode
* @param {boolean} isBeforeOp
* @param {import('postcss-value-parser').WordNode} operatorNode
* @param {import('postcss-value-parser').SpaceNode} spaceNode
* @param {'before' | 'after'} position
* @returns {void}
*/
function checkAroundOperator(operatorNode, currentNode, isBeforeOp) {
const operator = operatorNode.value;
const operatorSourceIndex = operatorNode.sourceIndex;

if (currentNode && !isSingleSpace(currentNode)) {
if (typeGuards.isValueWord(currentNode)) {
if (isBeforeOp) {
const lastChar = currentNode.value.slice(-1);

if (OPERATORS.has(lastChar)) {
if (context.fix) {
currentNode.value = `${currentNode.value.slice(0, -1)} ${lastChar}`;

return true;
}

complain(
messages.expectedOperatorBeforeSign(operator),
decl,
operatorSourceIndex,
operator,
);

return true;
}
} else {
const firstChar = currentNode.value.slice(0, 1);

if (OPERATORS.has(firstChar)) {
if (context.fix) {
currentNode.value = `${firstChar} ${currentNode.value.slice(1)}`;

return true;
}

complain(messages.expectedAfter(operator), decl, operatorSourceIndex, operator);

return true;
}
}

if (context.fix) {
needsFix = true;
currentNode.value = isBeforeOp ? `${currentNode.value} ` : ` ${currentNode.value}`;

return true;
}

complain(
isBeforeOp ? messages.expectedBefore(operator) : messages.expectedAfter(operator),
decl,
valueIndex + operatorSourceIndex,
operator,
);

return true;
}

if (currentNode.type === 'space') {
const indexOfFirstNewLine = currentNode.value.search(/(\n|\r\n)/);

if (indexOfFirstNewLine === 0) return;
function checkSpaceAroundOperator(operatorNode, spaceNode, position) {
const indexOfFirstNewLine = spaceNode.value.search(/(\n|\r\n)/);

if (context.fix) {
needsFix = true;
currentNode.value =
indexOfFirstNewLine === -1 ? ' ' : currentNode.value.slice(indexOfFirstNewLine);
if (indexOfFirstNewLine === 0) return;

return true;
}

const message = isBeforeOp
? messages.expectedBefore(operator)
: messages.expectedAfter(operator);

complain(message, decl, valueIndex + operatorSourceIndex, operator);

return true;
}

if (typeGuards.isValueFunction(currentNode)) {
if (context.fix) {
needsFix = true;
currentNode.value = isBeforeOp ? `${currentNode.value} ` : ` ${currentNode.value}`;

return true;
}
if (context.fix) {
needsFix = true;
spaceNode.value =
indexOfFirstNewLine === -1 ? ' ' : spaceNode.value.slice(indexOfFirstNewLine);

const message = isBeforeOp
? messages.expectedBefore(operator)
: messages.expectedAfter(operator);
return;
}

complain(message, decl, valueIndex + operatorSourceIndex, operator);
const operator = operatorNode.value;
const operatorSourceIndex = operatorNode.sourceIndex;

return true;
}
}
const message =
position === 'before'
? messages.expectedBefore(operator)
: messages.expectedAfter(operator);

return false;
complain(message, decl, valueIndex + operatorSourceIndex, operator);
}

/**
* @param {import('postcss-value-parser').Node[]} nodes
* @returns {boolean}
*/
function checkForOperatorInFirstNode(nodes) {
const firstNode = nodes[0];
const [firstNode] = nodes;

validateTypes.assert(firstNode);

if (firstNode.type !== 'word') return false;
if (!typeGuards.isValueWord(firstNode)) return false;

if (!isStandardSyntaxValue(firstNode.value)) return false;

const operatorIndex = firstNode.value.search(OPERATOR_REGEX);
const operator = firstNode.value.slice(operatorIndex, operatorIndex + 1);

if (operatorIndex <= 0) return false;

const operator = firstNode.value.charAt(operatorIndex);
const charBefore = firstNode.value.charAt(operatorIndex - 1);
const charAfter = firstNode.value.charAt(operatorIndex + 1);

Expand Down Expand Up @@ -222,46 +146,30 @@ const rule = (primary, _secondaryOptions, context) => {
operator,
);
}
} else if (charAfter && charAfter !== ' ') {
if (context.fix) {
needsFix = true;
firstNode.value = insertCharAtIndex(firstNode.value, operatorIndex, ' ');
} else {
complain(
messages.expectedAfter(operator),
decl,
valueIndex + firstNode.sourceIndex + operatorIndex + 1,
operator,
);
}
}

return true;
}

/**
* @param {import('postcss-value-parser').Node[]} nodes
* @returns {boolean}
*/
function checkForOperatorInLastNode(nodes) {
if (nodes.length === 1) return false;

const lastNode = nodes[nodes.length - 1];
const lastNode = nodes.at(-1);

validateTypes.assert(lastNode);

if (lastNode.type !== 'word') return false;
if (!typeGuards.isValueWord(lastNode)) return false;

const operatorIndex = lastNode.value.search(OPERATOR_REGEX);

if (operatorIndex === -1) return false;

if (lastNode.value.charAt(operatorIndex - 1) === ' ') return false;

// E.g. "10px * -2" when the last node is "-2"
if (
isOperator(nodes[nodes.length - 3], ALL_OPERATORS) &&
isSingleSpace(nodes[nodes.length - 2])
) {
if (isOperator(nodes.at(-3), ALL_OPERATORS) && isSingleSpace(nodes.at(-2))) {
return false;
}

Expand All @@ -285,69 +193,34 @@ const rule = (primary, _secondaryOptions, context) => {
return true;
}

/**
* @param {import('postcss-value-parser').Node[]} nodes
*/
function checkWords(nodes) {
if (checkForOperatorInFirstNode(nodes) || checkForOperatorInLastNode(nodes)) return;

for (const [index, node] of nodes.entries()) {
const lastChar = node.value.slice(-1);
const firstChar = node.value.slice(0, 1);

if (typeGuards.isValueWord(node)) {
if (index === 0 && OPERATORS.has(lastChar)) {
if (context.fix) {
node.value = `${node.value.slice(0, -1)} ${lastChar}`;

continue;
}

complain(messages.expectedBefore(lastChar), decl, node.sourceIndex, lastChar);
} else if (index === nodes.length && OPERATORS.has(firstChar)) {
if (context.fix) {
node.value = `${firstChar} ${node.value.slice(1)}`;

continue;
}

complain(
messages.expectedOperatorBeforeSign(firstChar),
decl,
node.sourceIndex,
firstChar,
);
}
}
}
}
const parsedValue = valueParser(value);

parsedValue.walk((node) => {
if (!typeGuards.isValueFunction(node) || !FUNC_NAMES_REGEX.test(node.value)) return;

const { nodes } = node;

if (!nodes.length) return;

let foundOperatorNode = false;

for (const [nodeIndex, currNode] of nodes.entries()) {
if (!isOperator(currNode)) continue;
for (const [nodeIndex, operatorNode] of nodes.entries()) {
if (!isOperator(operatorNode)) continue;

foundOperatorNode = true;

const nodeBefore = nodes[nodeIndex - 1];
const nodeAfter = nodes[nodeIndex + 1];

if (isSingleSpace(nodeBefore) && isSingleSpace(nodeAfter)) continue;

if (nodeAfter && checkAroundOperator(currNode, nodeAfter, false)) continue;
if (nodeBefore && typeGuards.isValueSpace(nodeBefore) && nodeBefore.value !== ' ') {
checkSpaceAroundOperator(operatorNode, nodeBefore, 'before');
}

nodeBefore && checkAroundOperator(currNode, nodeBefore, true);
if (nodeAfter && typeGuards.isValueSpace(nodeAfter) && nodeAfter.value !== ' ') {
checkSpaceAroundOperator(operatorNode, nodeAfter, 'after');
}
}

if (!foundOperatorNode) {
checkWords(nodes);
checkForOperatorInFirstNode(nodes) || checkForOperatorInLastNode(nodes);
}
});

Expand All @@ -369,10 +242,10 @@ function insertCharAtIndex(str, index, char) {

/**
* @param {import('postcss-value-parser').Node | undefined} node
* @returns {node is import('postcss-value-parser').SpaceNode & { value: ' ' } }
* @returns {node is import('postcss-value-parser').SpaceNode}
*/
function isSingleSpace(node) {
return node != null && node.type === 'space' && node.value === ' ';
return node != null && typeGuards.isValueSpace(node) && node.value === ' ';
}

/**
Expand All @@ -381,7 +254,7 @@ function isSingleSpace(node) {
* @returns {node is import('postcss-value-parser').WordNode}
*/
function isOperator(node, operators = OPERATORS) {
return node != null && node.type === 'word' && operators.has(node.value);
return node != null && typeGuards.isValueWord(node) && operators.has(node.value);
}

rule.ruleName = ruleName;
Expand Down

0 comments on commit 48eb848

Please sign in to comment.