@maniascript/mslint
Version:
ManiaScript linter
67 lines (66 loc) • 3.45 kB
JavaScript
// Based on the no-dupe-else-if rule from eslint
// https://github.com/eslint/eslint/blob/main/lib/rules/no-dupe-else-if.js
import { sameTokens } from '../linter/ast-utils.js';
import { ConditionalStatement, LogicalExpression, LogicalOperator } from '@maniascript/parser';
function equal(a, b, tokens) {
if (a.kind !== b.kind) {
return false;
}
if (a instanceof LogicalExpression &&
b instanceof LogicalExpression &&
a.operator === b.operator) {
return ((equal(a.left, b.left, tokens) && equal(a.right, b.right, tokens)) ||
(equal(a.left, b.right, tokens) && equal(a.right, b.left, tokens)));
}
return sameTokens(a, b, tokens);
}
function isSubsetByComparator(comparator, arrayA, arrayB, tokens) {
return arrayA.every(a => arrayB.some(b => comparator(a, b, tokens)));
}
const isSubset = isSubsetByComparator.bind(null, equal);
function splitByLogicalOperator(operator, node) {
if (node instanceof LogicalExpression && node.operator === operator) {
return [...splitByLogicalOperator(operator, node.left), ...splitByLogicalOperator(operator, node.right)];
}
return [node];
}
const splitByOr = splitByLogicalOperator.bind(null, LogicalOperator['||']);
const splitByAnd = splitByLogicalOperator.bind(null, LogicalOperator['&&']);
export const noDupeElseIf = {
meta: {
id: 'no-dupe-else-if',
description: 'Forbid duplicate conditions in if-else-if chains',
recommended: true
},
create(context) {
return {
'ConditionalStatement:exit': (node) => {
if (node instanceof ConditionalStatement && node.branches.length > 1) {
const previousBranches = [];
for (const branch of node.branches) {
if (branch.test !== undefined) {
if (previousBranches.length > 0) {
const conditionsToCheck = (branch.test instanceof LogicalExpression &&
branch.test.operator === LogicalOperator['&&'])
? [branch.test, ...splitByAnd(branch.test)]
: [branch.test];
let listToCheck = conditionsToCheck.map(c => splitByOr(c).map(splitByAnd));
for (const previousBranch of previousBranches) {
if (previousBranch.test !== undefined) {
const currentOrOperands = splitByOr(previousBranch.test).map(splitByAnd);
listToCheck = listToCheck.map(orOperands => orOperands.filter(orOperand => !currentOrOperands.some(currentOrOperand => isSubset(currentOrOperand, orOperand, context.tokens))));
if (listToCheck.some(orOperands => orOperands.length === 0)) {
context.report(branch.test, 'This branch will never be executed because its condition is already verified in a previous branch');
break;
}
}
}
}
previousBranches.push(branch);
}
}
}
}
};
}
};