UNPKG

@maniascript/mslint

Version:
69 lines (68 loc) 3.55 kB
// 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 {} from '../linter/rule.js'; import { sameTokens } from '../linter/ast-utils.js'; import { ConditionalBranch, ConditionalStatement, LogicalExpression, LogicalOperator } from '@maniascript/parser'; import { CommonTokenStream } from 'antlr4ng'; 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); } } } } }; } };