diff --git a/docs/rules/prefer-switch.md b/docs/rules/prefer-switch.md new file mode 100644 index 0000000000..9c391cc8d1 --- /dev/null +++ b/docs/rules/prefer-switch.md @@ -0,0 +1,171 @@ +# Prefer `switch` over multiple `else-if` + +A switch statement is easier to read than multiple if statements with simple equality comparisons. + +This rule is partly fixable. + +## Fail + +```js +if (foo === 1) { + // 1 +} else if (foo === 2) { + // 2 +} else if (foo === 3) { + // 3 +} else { + // default +} +``` + +## Pass + +```js +if (foo === 1) { + // 1 +} else if (foo === 2) { + // 2 +} +``` + +```js +switch (foo) { + case 1: { + // 1 + break; + } + case 2: { + // 2 + break; + } + case 3: { + // 3 + break; + } + default: { + // default + } +} +``` + +### `options` + +Type: `object` + +#### `minimumCases` + +Type: `integer`\ +Minimum: `2`\ +Default: `3` + +The minimum number of cases before reporting. + +The `default` branch doesn't count. Multiple comparisons on the same `if` block is considered one case. + +Examples: + +```js +// eslint unicorn/prefer-switch: ["error", {"minimumCases": 4}] +if (foo === 1) {} +else (foo === 2) {} +else (foo === 3) {} + +// Passes, only 3 cases. +``` + +```js +// eslint unicorn/prefer-switch: ["error", {"minimumCases": 4}] +if (foo === 1) {} +else (foo === 2) {} +else (foo === 3) {} +else {} + +// Passes, only 3 cases. +``` + +```js +// eslint unicorn/prefer-switch: ["error", {"minimumCases": 4}] +if (foo === 1) {} +else if (foo === 2 || foo === 3) {} +else if (foo === 4) {} + +// Passes, only 3 cases. +``` + +```js +// eslint unicorn/prefer-switch: ["error", {"minimumCases": 2}] +if (foo === 1) {} +else if (foo === 2) {} + +// Fails +``` + +#### `emptyDefaultCase` + +Type: `string`\ +Default: `'no-default-comment'` + +To avoid conflict with the [`default-case`](https://eslint.org/docs/rules/default-case) rule, choose the fix style you prefer: + +- `'no-default-comment'` (default) + Insert `// No default` comment after last case. +- `'do-nothing-comment'` + Insert `default` case and add `// Do nothing` comment. +- `'no-default-case'` + Don't insert default case or comment. + +```js +if (foo === 1) {} +else (foo === 2) {} +else (foo === 3) {} +``` + +Fixed + +```js +/* eslint unicorn/prefer-switch: ["error", { "emptyDefaultCase": "no-default-comment" }] */ +switch (foo) { + case 1: { + break; + } + case 2: { + break; + } + case 3: { + break; + } + // No default +} +``` + +```js +/* eslint unicorn/prefer-switch: ["error", { "emptyDefaultCase": "do-nothing-comment" }] */ +switch (foo) { + case 1: { + break; + } + case 2: { + break; + } + case 3: { + break; + } + default: + // Do nothing +} +``` + +```js +/* eslint unicorn/prefer-switch: ["error", { "emptyDefaultCase": "no-default-case" }] */ +switch (foo) { + case 1: { + break; + } + case 2: { + break; + } + case 3: { + break; + } +} +``` diff --git a/index.js b/index.js index c873a76bd5..88140a9e64 100644 --- a/index.js +++ b/index.js @@ -112,6 +112,7 @@ module.exports = { 'unicorn/prefer-string-slice': 'error', 'unicorn/prefer-string-starts-ends-with': 'error', 'unicorn/prefer-string-trim-start-end': 'error', + 'unicorn/prefer-switch': 'error', 'unicorn/prefer-ternary': 'error', 'unicorn/prefer-type-error': 'error', 'unicorn/prevent-abbreviations': 'error', diff --git a/readme.md b/readme.md index c569eca7c1..9a1d6e36e3 100644 --- a/readme.md +++ b/readme.md @@ -102,6 +102,7 @@ Configure it in `package.json`. "unicorn/prefer-string-slice": "error", "unicorn/prefer-string-starts-ends-with": "error", "unicorn/prefer-string-trim-start-end": "error", + "unicorn/prefer-switch": "error", "unicorn/prefer-ternary": "off", "unicorn/prefer-type-error": "error", "unicorn/prevent-abbreviations": "error", @@ -192,6 +193,7 @@ Each rule has emojis denoting: | [prefer-string-slice](docs/rules/prefer-string-slice.md) | Prefer `String#slice()` over `String#substr()` and `String#substring()`. | βœ… | πŸ”§ | | [prefer-string-starts-ends-with](docs/rules/prefer-string-starts-ends-with.md) | Prefer `String#startsWith()` & `String#endsWith()` over `RegExp#test()`. | βœ… | πŸ”§ | | [prefer-string-trim-start-end](docs/rules/prefer-string-trim-start-end.md) | Prefer `String#trimStart()` / `String#trimEnd()` over `String#trimLeft()` / `String#trimRight()`. | βœ… | πŸ”§ | +| [prefer-switch](docs/rules/prefer-switch.md) | Prefer `switch` over multiple `else-if`. | βœ… | πŸ”§ | | [prefer-ternary](docs/rules/prefer-ternary.md) | Prefer ternary expressions over simple `if-else` statements. | βœ… | πŸ”§ | | [prefer-type-error](docs/rules/prefer-type-error.md) | Enforce throwing `TypeError` in type checking conditions. | βœ… | πŸ”§ | | [prevent-abbreviations](docs/rules/prevent-abbreviations.md) | Prevent abbreviations. | βœ… | πŸ”§ | diff --git a/rules/prefer-string-slice.js b/rules/prefer-string-slice.js index f0cef2cc90..40fa63ce40 100644 --- a/rules/prefer-string-slice.js +++ b/rules/prefer-string-slice.js @@ -72,34 +72,45 @@ const create = context => { let sliceArguments; - if (argumentNodes.length === 0) { - sliceArguments = []; - } else if (argumentNodes.length === 1) { - sliceArguments = [firstArgument]; - } else if (argumentNodes.length === 2) { - if (firstArgument === '0') { + switch (argumentNodes.length) { + case 0: { + sliceArguments = []; + break; + } + + case 1: { sliceArguments = [firstArgument]; - if (isLiteralNumber(secondArgument) || isLengthProperty(argumentNodes[1])) { - sliceArguments.push(secondArgument); - } else if (typeof getNumericValue(argumentNodes[1]) === 'number') { - sliceArguments.push(Math.max(0, getNumericValue(argumentNodes[1]))); - } else { - sliceArguments.push(`Math.max(0, ${secondArgument})`); - } - } else if ( - isLiteralNumber(argumentNodes[0]) && - isLiteralNumber(argumentNodes[1]) - ) { - sliceArguments = [ - firstArgument, - argumentNodes[0].value + argumentNodes[1].value - ]; - } else if ( - isLikelyNumeric(argumentNodes[0]) && + break; + } + + case 2: { + if (firstArgument === '0') { + sliceArguments = [firstArgument]; + if (isLiteralNumber(secondArgument) || isLengthProperty(argumentNodes[1])) { + sliceArguments.push(secondArgument); + } else if (typeof getNumericValue(argumentNodes[1]) === 'number') { + sliceArguments.push(Math.max(0, getNumericValue(argumentNodes[1]))); + } else { + sliceArguments.push(`Math.max(0, ${secondArgument})`); + } + } else if ( + isLiteralNumber(argumentNodes[0]) && + isLiteralNumber(argumentNodes[1]) + ) { + sliceArguments = [ + firstArgument, + argumentNodes[0].value + argumentNodes[1].value + ]; + } else if ( + isLikelyNumeric(argumentNodes[0]) && isLikelyNumeric(argumentNodes[1]) - ) { - sliceArguments = [firstArgument, firstArgument + ' + ' + secondArgument]; + ) { + sliceArguments = [firstArgument, firstArgument + ' + ' + secondArgument]; + } + + break; } + // No default } if (sliceArguments) { @@ -129,33 +140,45 @@ const create = context => { let sliceArguments; - if (argumentNodes.length === 0) { - sliceArguments = []; - } else if (argumentNodes.length === 1) { - if (firstNumber !== undefined) { - sliceArguments = [Math.max(0, firstNumber)]; - } else if (isLengthProperty(argumentNodes[0])) { - sliceArguments = [firstArgument]; - } else { - sliceArguments = [`Math.max(0, ${firstArgument})`]; + switch (argumentNodes.length) { + case 0: { + sliceArguments = []; + break; } - } else if (argumentNodes.length === 2) { - const secondNumber = getNumericValue(argumentNodes[1]); - - if (firstNumber !== undefined && secondNumber !== undefined) { - sliceArguments = firstNumber > secondNumber ? - [Math.max(0, secondNumber), Math.max(0, firstNumber)] : - [Math.max(0, firstNumber), Math.max(0, secondNumber)]; - } else if (firstNumber === 0 || secondNumber === 0) { - sliceArguments = [0, `Math.max(0, ${firstNumber === 0 ? secondArgument : firstArgument})`]; - } else { + + case 1: { + if (firstNumber !== undefined) { + sliceArguments = [Math.max(0, firstNumber)]; + } else if (isLengthProperty(argumentNodes[0])) { + sliceArguments = [firstArgument]; + } else { + sliceArguments = [`Math.max(0, ${firstArgument})`]; + } + + break; + } + + case 2: { + const secondNumber = getNumericValue(argumentNodes[1]); + + if (firstNumber !== undefined && secondNumber !== undefined) { + sliceArguments = firstNumber > secondNumber ? + [Math.max(0, secondNumber), Math.max(0, firstNumber)] : + [Math.max(0, firstNumber), Math.max(0, secondNumber)]; + } else if (firstNumber === 0 || secondNumber === 0) { + sliceArguments = [0, `Math.max(0, ${firstNumber === 0 ? secondArgument : firstArgument})`]; + } else { // As values aren't Literal, we can not know whether secondArgument will become smaller than the first or not, causing an issue: // .substring(0, 2) and .substring(2, 0) returns the same result // .slice(0, 2) and .slice(2, 0) doesn't return the same result // There's also an issue with us now knowing whether the value will be negative or not, due to: // .substring() treats a negative number the same as it treats a zero. // The latter issue could be solved by wrapping all dynamic numbers in Math.max(0, ), but the resulting code would not be nice + } + + break; } + // No default } if (sliceArguments) { diff --git a/rules/prefer-switch.js b/rules/prefer-switch.js new file mode 100644 index 0000000000..126aeb66de --- /dev/null +++ b/rules/prefer-switch.js @@ -0,0 +1,283 @@ +'use strict'; +const {hasSideEffect} = require('eslint-utils'); +const getDocumentationUrl = require('./utils/get-documentation-url'); +const isSameReference = require('./utils/is-same-reference'); +const getIndentString = require('./utils/get-indent-string'); + +const MESSAGE_ID = 'prefer-switch'; +const messages = { + [MESSAGE_ID]: 'Use `switch` instead of multiple `else-if`.' +}; + +const isSame = (nodeA, nodeB) => nodeA === nodeB || isSameReference(nodeA, nodeB); + +function getEqualityComparisons(node) { + const nodes = [node]; + const compareExpressions = []; + while (nodes.length > 0) { + node = nodes.pop(); + + if (node.type === 'LogicalExpression' && node.operator === '||') { + nodes.push(node.right, node.left); + continue; + } + + if (node.type !== 'BinaryExpression' || node.operator !== '===') { + return []; + } + + compareExpressions.push(node); + } + + return compareExpressions; +} + +function getCommonReferences(expressions, candidates) { + for (const {left, right} of expressions) { + candidates = candidates.filter(node => isSame(node, left) || isSame(node, right)); + + if (candidates.length === 0) { + break; + } + } + + return candidates; +} + +function getStatements(statement) { + let discriminantCandidates; + const ifStatements = []; + for (; statement && statement.type === 'IfStatement'; statement = statement.alternate) { + const {test} = statement; + const compareExpressions = getEqualityComparisons(test); + + if (compareExpressions.length === 0) { + break; + } + + if (!discriminantCandidates) { + const [{left, right}] = compareExpressions; + discriminantCandidates = [left, right]; + } + + const candidates = getCommonReferences( + compareExpressions, + discriminantCandidates + ); + + if (candidates.length === 0) { + break; + } + + discriminantCandidates = candidates; + + ifStatements.push({ + statement, + compareExpressions + }); + } + + return { + ifStatements, + discriminant: discriminantCandidates && discriminantCandidates[0] + }; +} + +const breakAbleNodeTypes = new Set([ + 'WhileStatement', + 'DoWhileStatement', + 'ForStatement', + 'ForOfStatement', + 'ForInStatement', + 'SwitchStatement' +]); +const getBreakTarget = node => { + for (;node.parent; node = node.parent) { + if (breakAbleNodeTypes.has(node.type)) { + return node; + } + } +}; + +const isNodeInsideNode = (inner, outer) => + inner.range[0] >= outer.range[0] && inner.range[1] <= outer.range[1]; +function hasBreakInside(breakStatements, node) { + for (const breakStatement of breakStatements) { + if (!isNodeInsideNode(breakStatement, node)) { + continue; + } + + const breakTarget = getBreakTarget(breakStatement); + + if (!breakTarget) { + return true; + } + + if (isNodeInsideNode(node, breakTarget)) { + return true; + } + } + + return false; +} + +function * insertBracesIfNotBlockStatement(node, fixer, indent) { + if (!node || node.type === 'BlockStatement') { + return; + } + + yield fixer.insertTextBefore(node, `{\n${indent}`); + yield fixer.insertTextAfter(node, `\n${indent}}`); +} + +function * insertBreakStatement(node, fixer, sourceCode, indent) { + if (node.type === 'BlockStatement') { + const lastToken = sourceCode.getLastToken(node); + yield fixer.insertTextBefore(lastToken, `\n${indent}break;\n${indent}`); + } else { + yield fixer.insertTextAfter(node, `\n${indent}break;`); + } +} + +function fix({discriminant, ifStatements}, sourceCode, options) { + const discriminantText = sourceCode.getText(discriminant); + + return function * (fixer) { + const firstStatement = ifStatements[0].statement; + const indent = getIndentString(firstStatement, sourceCode); + yield fixer.insertTextBefore(firstStatement, `switch (${discriminantText}) {`); + + const lastStatement = ifStatements[ifStatements.length - 1].statement; + if (lastStatement.alternate) { + const {alternate} = lastStatement; + yield fixer.insertTextBefore(alternate, `\n${indent}default: `); + yield * insertBracesIfNotBlockStatement(alternate, fixer, indent); + } else { + switch (options.emptyDefaultCase) { + case 'no-default-comment': + yield fixer.insertTextAfter(firstStatement, `\n${indent}// No default`); + break; + case 'do-nothing-comment': { + yield fixer.insertTextAfter(firstStatement, `\n${indent}default:\n${indent}// Do nothing`); + break; + } + // No default + } + } + + yield fixer.insertTextAfter(firstStatement, `\n${indent}}`); + + for (const {statement, compareExpressions} of ifStatements) { + const {consequent, alternate, range} = statement; + const headRange = [range[0], consequent.range[0]]; + + if (alternate) { + const [, start] = consequent.range; + const [end] = alternate.range; + yield fixer.replaceTextRange([start, end], ''); + } + + yield fixer.replaceTextRange(headRange, ''); + for (const {left, right} of compareExpressions) { + const node = isSame(left, discriminant) ? right : left; + const text = sourceCode.getText(node); + yield fixer.insertTextBefore(consequent, `\n${indent}case ${text}: `); + } + + yield * insertBreakStatement(consequent, fixer, sourceCode, indent); + yield * insertBracesIfNotBlockStatement(consequent, fixer, indent); + } + }; +} + +const create = context => { + const options = { + minimumCases: 3, + emptyDefaultCase: 'no-default-comment', + insertBreakInDefaultCase: false, + ...context.options[0] + }; + const sourceCode = context.getSourceCode(); + const ifStatements = new Set(); + const breakStatements = []; + const checked = new Set(); + + return { + 'IfStatement'(node) { + ifStatements.add(node); + }, + 'BreakStatement:not([label])'(node) { + breakStatements.push(node); + }, + 'Program:exit'() { + for (const node of ifStatements) { + if (checked.has(node)) { + continue; + } + + const {discriminant, ifStatements} = getStatements(node); + + if (!discriminant || ifStatements.length < options.minimumCases) { + continue; + } + + for (const {statement} of ifStatements) { + checked.add(statement); + } + + const problem = { + loc: { + start: node.loc.start, + end: node.consequent.loc.start + }, + messageId: MESSAGE_ID + }; + + if ( + !hasSideEffect(discriminant, sourceCode) && + !ifStatements.some(({statement}) => hasBreakInside(breakStatements, statement)) + ) { + problem.fix = fix({discriminant, ifStatements}, sourceCode, options); + } + + context.report(problem); + } + } + }; +}; + +const schema = [ + { + type: 'object', + properties: { + minimumCases: { + type: 'integer', + minimum: 2, + default: 3 + }, + emptyDefaultCase: { + enum: [ + 'no-default-comment', + 'do-nothing-comment', + 'no-default-case' + ], + default: 'no-default-comment' + } + }, + additionalProperties: false + } +]; + +module.exports = { + create, + meta: { + type: 'suggestion', + docs: { + description: 'Prefer `switch` over multiple `else-if`.', + url: getDocumentationUrl(__filename) + }, + fixable: 'code', + schema, + messages + } +}; diff --git a/rules/prefer-ternary.js b/rules/prefer-ternary.js index cc9a7645f6..50c01cb34e 100644 --- a/rules/prefer-ternary.js +++ b/rules/prefer-ternary.js @@ -6,6 +6,7 @@ const avoidCapture = require('./utils/avoid-capture'); const extendFixRange = require('./utils/extend-fix-range'); const needsSemicolon = require('./utils/needs-semicolon'); const isSameReference = require('./utils/is-same-reference'); +const getIndentString = require('./utils/get-indent-string'); const messageId = 'prefer-ternary'; @@ -39,14 +40,6 @@ function getNodeBody(node) { return node; } -const getIndentString = (node, sourceCode) => { - const {line, column} = sourceCode.getLocFromIndex(node.range[0]); - const lines = sourceCode.getLines(); - const before = lines[line - 1].slice(0, column); - - return before.match(/\s*$/)[0]; -}; - const getScopes = scope => [ scope, ...flatten(scope.childScopes.map(scope => getScopes(scope))) diff --git a/rules/utils/get-indent-string.js b/rules/utils/get-indent-string.js new file mode 100644 index 0000000000..7f7deb4490 --- /dev/null +++ b/rules/utils/get-indent-string.js @@ -0,0 +1,11 @@ +'use strict'; + +function getIndentString(node, sourceCode) { + const {line, column} = sourceCode.getLocFromIndex(node.range[0]); + const lines = sourceCode.getLines(); + const before = lines[line - 1].slice(0, column); + + return before.match(/\s*$/)[0]; +} + +module.exports = getIndentString; diff --git a/test/prefer-switch.mjs b/test/prefer-switch.mjs new file mode 100644 index 0000000000..fbda4e071c --- /dev/null +++ b/test/prefer-switch.mjs @@ -0,0 +1,337 @@ +import outdent from 'outdent'; +import {getTester} from './utils/test.mjs'; + +const {test} = getTester(import.meta); + +test.snapshot({ + valid: [ + // Less cases + outdent` + if (foo === 1) {} + else if (foo === 2) {} + `, + // Less cases + outdent` + if (foo === 1) { + if (foo === 2) {} + } + else if (foo === 2) {} + `, + outdent` + if (foo === 1 || foo === 2) {} + else if (foo === 3 || foo === 4) {} + `, + // No fixable branches + outdent` + if (foo === 1) {} + else if (foo === 2) {} + else if (bar === 1) {} + else if (foo === 3) {} + else if (foo === 4) {} + else if (bar === 1) {} + else if (foo === 5) {} + else if (foo === 6) {} + `, + // Not considered same + outdent` + if (foo() === 1) {} + else if (foo() === 2) {} + else if (foo() === 3) {} + `, + // Ternary + 'foo === 1 ? 1 : foo === 2 ? 2 : foo === 3 ? 3 : 0', + // Not comparison + outdent` + if (foo === 1) {} + else if (foo !== 2) {} + else if (foo === 3) {} + `, + outdent` + if (foo === 1) {} + else if (foo === 2 && foo === 4) {} + else if (foo === 3) {} + `, + outdent` + if (foo === 1) {} + else if (foo === 2 || foo !== 4) {} + else if (foo === 3) {} + ` + ], + invalid: [ + outdent` + if (foo === 1) {} + else if (foo === 2) {} + else if (foo === 3) {} + `, + outdent` + if (foo === 1) {} + else if (foo === 2) {} + else if (foo === 3) {} + else { + // default + } + `, + outdent` + if (foo === 1) (notBlock()) + else if (foo === 2) (notBlock()) + else if (foo === 3) (notBlock()) + else (notBlock()) + `, + // First condition is not fixable + outdent` + if (bar = 1) {} + else if (foo === 1) {} + else if (foo === 2) {} + else if (foo === 3) {} + `, + // Last condition is not fixable + outdent` + if (foo === 1) {} + else if (foo === 2) {} + else if (foo === 3) {} + else if (bar === 3) {} + `, + // Inside if + outdent` + if (foo === 0) { + if (foo === 1) {} + else if (foo === 2) {} + else if (foo === 3) {} + } + `, + // Variable is on right side + outdent` + if (1 === foo) {} + else if (foo === 2) {} + else if (3 === foo) {} + `, + // Compare to constant + outdent` + if (true === foo) {} + else if (bar.bar === true) {} + else if (true === baz()) {} + `, + // No need add parentheses + outdent` + if (foo === ((0, 1))) {} + else if (foo === (bar + 2)) {} + else if (foo === (baz = 2)) {} + `, + // All conditions are same + outdent` + // Should use "foo" as discriminant + if (foo === bar) {} + else if (foo === bar) {} + else if (foo === bar) {} + + // Should use "bar" as discriminant + if (bar === foo) {} + else if (bar === foo) {} + else if (foo === bar) {} + `, + // Many cases + outdent` + if (foo === 1) {} + ${Array.from({length: 49}, (_, index) => `else if (foo === ${index + 2}) {}`).join('\n')} + else {} + `, + // `OR` + outdent` + if (foo === 1) {} + else if ((foo === 2 || foo === 3) || (foo === 4)) {} + else if (foo === 5) {} + `, + // Indention + outdent` + function foo() { + for (const a of b) { + if (foo === 1) { + return 1; + } else if (foo === 2) { + throw new Error(); + } else if (foo === 3) { + alert(foo); + } else { + console.log('wow'); + } + } + } + `, + outdent` + function foo() { + return bar.map(foo => { + if (foo === 1) return foo; + else if (foo === 2) throw new Error(); + else if (foo === 3) foo++ + else console.log('wow'); + }) + } + `, + // Multiple + outdent` + if (one) {} + else if (foo === 1) {} + else if (foo === 2) {} + else if (foo === 3) {} + else if (two) {} + else if (bar === 1) {} + else if (bar === 2) {} + else if (bar === 3) {} + else if (foo === 1) {} + else if (foo === 2) {} + else if (foo === 3) {} + `, + // Same reference + outdent` + if (foo.baz === 1) {} + else if (foo['baz'] === 2) {} + else if (foo["ba" + 'z'] === 3) {} + `, + // Still fixable even there are `break` + outdent` + while (bar) { + if (foo === 1) { + for (const foo of bar) { + break; + } + } else if (foo === 2) { + } else if (foo === 3) { + } + } + `, + // Not fixable + outdent` + while (bar) { + if (foo === 1) { + break; + } else if (foo === 2) { + } else if (foo === 3) { + } + } + `, + outdent` + while (bar) { + if (foo === 1) { + } else if (foo === 2) { + break; + } else if (foo === 3) { + } + } + `, + outdent` + while (bar) { + if (foo === 1) { + } else if (foo === 2) { + } else if (foo === 3) { + if (a) { + if (b) { + if (c) { + break; + } + } + } + } + } + `, + outdent` + switch (bar) { + case 'bar': + if (foo === 1) { + } else if (foo === 2) { + } else if (foo === 3) { + break; + } + } + ` + ] +}); + +// `options` +test.snapshot({ + valid: [ + { + code: outdent` + if (foo === 1) {} + else if (foo === 2) {} + else if (foo === 3) {} + `, + options: [{minimumCases: 4}] + }, + { + code: outdent` + if (foo === 1) {} + else if (foo === 2) {} + else if (foo === 3) {} + else {} + `, + options: [{minimumCases: 4}] + } + ], + invalid: [ + { + code: outdent` + if (foo === 1) {} + else if (foo === 2) {} + `, + options: [{minimumCases: 2}] + }, + { + code: outdent` + if (foo === 1) {} + else if (foo === 2) {} + else {} + `, + options: [{minimumCases: 2}] + }, + { + code: outdent` + function foo() { + if (foo === 1) {} + else if (foo === 2) {} + else if (foo === 3) {} + } + `, + options: [{emptyDefaultCase: 'no-default-comment'}] + }, + { + code: outdent` + function foo() { + if (foo === 1) {} + else if (foo === 2) {} + else if (foo === 3) {} + } + `, + options: [{emptyDefaultCase: 'do-nothing-comment'}] + }, + { + code: outdent` + function foo() { + if (foo === 1) {} + else if (foo === 2) {} + else if (foo === 3) {} + } + `, + options: [{emptyDefaultCase: 'no-default-case'}] + } + ] +}); + +test.typescript({ + valid: [], + invalid: [ + // TypeScript allow `break` here + { + code: outdent` + if (foo === 1) {} + else if (foo === 2) {} + else if (foo === 3) {break;} + `, + output: outdent` + if (foo === 1) {} + else if (foo === 2) {} + else if (foo === 3) {break;} + `, + errors: 1 + } + ] +}); diff --git a/test/snapshots/prefer-switch.mjs.md b/test/snapshots/prefer-switch.mjs.md new file mode 100644 index 0000000000..4a7bfb2705 --- /dev/null +++ b/test/snapshots/prefer-switch.mjs.md @@ -0,0 +1,1307 @@ +# Snapshot report for `test/prefer-switch.mjs` + +The actual snapshot is saved in `prefer-switch.mjs.snap`. + +Generated by [AVA](https://avajs.dev). + +## Invalid #1 + 1 | if (foo === 1) {} + 2 | else if (foo === 2) {} + 3 | else if (foo === 3) {} + +> Output + + `␊ + 1 | switch (foo) {␊ + 2 | case 1: {␊ + 3 | break;␊ + 4 | }␊ + 5 | case 2: {␊ + 6 | break;␊ + 7 | }␊ + 8 | case 3: {␊ + 9 | break;␊ + 10 | }␊ + 11 | // No default␊ + 12 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | if (foo === 1) {}␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 2 | else if (foo === 2) {}␊ + 3 | else if (foo === 3) {}␊ + ` + +## Invalid #2 + 1 | if (foo === 1) {} + 2 | else if (foo === 2) {} + 3 | else if (foo === 3) {} + 4 | else { + 5 | // default + 6 | } + +> Output + + `␊ + 1 | switch (foo) {␊ + 2 | case 1: {␊ + 3 | break;␊ + 4 | }␊ + 5 | case 2: {␊ + 6 | break;␊ + 7 | }␊ + 8 | case 3: {␊ + 9 | break;␊ + 10 | }␊ + 11 | default: {␊ + 12 | // default␊ + 13 | }␊ + 14 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | if (foo === 1) {}␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 2 | else if (foo === 2) {}␊ + 3 | else if (foo === 3) {}␊ + 4 | else {␊ + 5 | // default␊ + 6 | }␊ + ` + +## Invalid #3 + 1 | if (foo === 1) (notBlock()) + 2 | else if (foo === 2) (notBlock()) + 3 | else if (foo === 3) (notBlock()) + 4 | else (notBlock()) + +> Output + + `␊ + 1 | switch (foo) {␊ + 2 | case 1: {␊ + 3 | (notBlock())␊ + 4 | break;␊ + 5 | }␊ + 6 | case 2: {␊ + 7 | (notBlock())␊ + 8 | break;␊ + 9 | }␊ + 10 | case 3: {␊ + 11 | (notBlock())␊ + 12 | break;␊ + 13 | }␊ + 14 | default: {␊ + 15 | (notBlock())␊ + 16 | }␊ + 17 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | if (foo === 1) (notBlock())␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 2 | else if (foo === 2) (notBlock())␊ + 3 | else if (foo === 3) (notBlock())␊ + 4 | else (notBlock())␊ + ` + +## Invalid #4 + 1 | if (bar = 1) {} + 2 | else if (foo === 1) {} + 3 | else if (foo === 2) {} + 4 | else if (foo === 3) {} + +> Output + + `␊ + 1 | if (bar = 1) {}␊ + 2 | else switch (foo) {␊ + 3 | case 1: {␊ + 4 | break;␊ + 5 | }␊ + 6 | case 2: {␊ + 7 | break;␊ + 8 | }␊ + 9 | case 3: {␊ + 10 | break;␊ + 11 | }␊ + 12 | // No default␊ + 13 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | if (bar = 1) {}␊ + > 2 | else if (foo === 1) {}␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 3 | else if (foo === 2) {}␊ + 4 | else if (foo === 3) {}␊ + ` + +## Invalid #5 + 1 | if (foo === 1) {} + 2 | else if (foo === 2) {} + 3 | else if (foo === 3) {} + 4 | else if (bar === 3) {} + +> Output + + `␊ + 1 | switch (foo) {␊ + 2 | case 1: {␊ + 3 | break;␊ + 4 | }␊ + 5 | case 2: {␊ + 6 | break;␊ + 7 | }␊ + 8 | case 3: {␊ + 9 | break;␊ + 10 | }␊ + 11 | default: {␊ + 12 | if (bar === 3) {}␊ + 13 | }␊ + 14 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | if (foo === 1) {}␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 2 | else if (foo === 2) {}␊ + 3 | else if (foo === 3) {}␊ + 4 | else if (bar === 3) {}␊ + ` + +## Invalid #6 + 1 | if (foo === 0) { + 2 | if (foo === 1) {} + 3 | else if (foo === 2) {} + 4 | else if (foo === 3) {} + 5 | } + +> Output + + `␊ + 1 | if (foo === 0) {␊ + 2 | switch (foo) {␊ + 3 | case 1: {␊ + 4 | break;␊ + 5 | }␊ + 6 | case 2: {␊ + 7 | break;␊ + 8 | }␊ + 9 | case 3: {␊ + 10 | break;␊ + 11 | }␊ + 12 | // No default␊ + 13 | }␊ + 14 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | if (foo === 0) {␊ + > 2 | if (foo === 1) {}␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 3 | else if (foo === 2) {}␊ + 4 | else if (foo === 3) {}␊ + 5 | }␊ + ` + +## Invalid #7 + 1 | if (1 === foo) {} + 2 | else if (foo === 2) {} + 3 | else if (3 === foo) {} + +> Output + + `␊ + 1 | switch (foo) {␊ + 2 | case 1: {␊ + 3 | break;␊ + 4 | }␊ + 5 | case 2: {␊ + 6 | break;␊ + 7 | }␊ + 8 | case 3: {␊ + 9 | break;␊ + 10 | }␊ + 11 | // No default␊ + 12 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | if (1 === foo) {}␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 2 | else if (foo === 2) {}␊ + 3 | else if (3 === foo) {}␊ + ` + +## Invalid #8 + 1 | if (true === foo) {} + 2 | else if (bar.bar === true) {} + 3 | else if (true === baz()) {} + +> Output + + `␊ + 1 | switch (true) {␊ + 2 | case foo: {␊ + 3 | break;␊ + 4 | }␊ + 5 | case bar.bar: {␊ + 6 | break;␊ + 7 | }␊ + 8 | case baz(): {␊ + 9 | break;␊ + 10 | }␊ + 11 | // No default␊ + 12 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | if (true === foo) {}␊ + | ^^^^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 2 | else if (bar.bar === true) {}␊ + 3 | else if (true === baz()) {}␊ + ` + +## Invalid #9 + 1 | if (foo === ((0, 1))) {} + 2 | else if (foo === (bar + 2)) {} + 3 | else if (foo === (baz = 2)) {} + +> Output + + `␊ + 1 | switch (foo) {␊ + 2 | case 0, 1: {␊ + 3 | break;␊ + 4 | }␊ + 5 | case bar + 2: {␊ + 6 | break;␊ + 7 | }␊ + 8 | case baz = 2: {␊ + 9 | break;␊ + 10 | }␊ + 11 | // No default␊ + 12 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | if (foo === ((0, 1))) {}␊ + | ^^^^^^^^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 2 | else if (foo === (bar + 2)) {}␊ + 3 | else if (foo === (baz = 2)) {}␊ + ` + +## Invalid #10 + 1 | // Should use "foo" as discriminant + 2 | if (foo === bar) {} + 3 | else if (foo === bar) {} + 4 | else if (foo === bar) {} + 5 | + 6 | // Should use "bar" as discriminant + 7 | if (bar === foo) {} + 8 | else if (bar === foo) {} + 9 | else if (foo === bar) {} + +> Output + + `␊ + 1 | // Should use "foo" as discriminant␊ + 2 | switch (foo) {␊ + 3 | case bar: {␊ + 4 | break;␊ + 5 | }␊ + 6 | case bar: {␊ + 7 | break;␊ + 8 | }␊ + 9 | case bar: {␊ + 10 | break;␊ + 11 | }␊ + 12 | // No default␊ + 13 | }␊ + 14 |␊ + 15 | // Should use "bar" as discriminant␊ + 16 | switch (bar) {␊ + 17 | case foo: {␊ + 18 | break;␊ + 19 | }␊ + 20 | case foo: {␊ + 21 | break;␊ + 22 | }␊ + 23 | case foo: {␊ + 24 | break;␊ + 25 | }␊ + 26 | // No default␊ + 27 | }␊ + ` + +> Error 1/2 + + `␊ + 1 | // Should use "foo" as discriminant␊ + > 2 | if (foo === bar) {}␊ + | ^^^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 3 | else if (foo === bar) {}␊ + 4 | else if (foo === bar) {}␊ + 5 |␊ + 6 | // Should use "bar" as discriminant␊ + 7 | if (bar === foo) {}␊ + 8 | else if (bar === foo) {}␊ + 9 | else if (foo === bar) {}␊ + ` + +> Error 2/2 + + `␊ + 1 | // Should use "foo" as discriminant␊ + 2 | if (foo === bar) {}␊ + 3 | else if (foo === bar) {}␊ + 4 | else if (foo === bar) {}␊ + 5 |␊ + 6 | // Should use "bar" as discriminant␊ + > 7 | if (bar === foo) {}␊ + | ^^^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 8 | else if (bar === foo) {}␊ + 9 | else if (foo === bar) {}␊ + ` + +## Invalid #11 + 1 | if (foo === 1) {} + 2 | else if (foo === 2) {} + 3 | else if (foo === 3) {} + 4 | else if (foo === 4) {} + 5 | else if (foo === 5) {} + 6 | else if (foo === 6) {} + 7 | else if (foo === 7) {} + 8 | else if (foo === 8) {} + 9 | else if (foo === 9) {} + 10 | else if (foo === 10) {} + 11 | else if (foo === 11) {} + 12 | else if (foo === 12) {} + 13 | else if (foo === 13) {} + 14 | else if (foo === 14) {} + 15 | else if (foo === 15) {} + 16 | else if (foo === 16) {} + 17 | else if (foo === 17) {} + 18 | else if (foo === 18) {} + 19 | else if (foo === 19) {} + 20 | else if (foo === 20) {} + 21 | else if (foo === 21) {} + 22 | else if (foo === 22) {} + 23 | else if (foo === 23) {} + 24 | else if (foo === 24) {} + 25 | else if (foo === 25) {} + 26 | else if (foo === 26) {} + 27 | else if (foo === 27) {} + 28 | else if (foo === 28) {} + 29 | else if (foo === 29) {} + 30 | else if (foo === 30) {} + 31 | else if (foo === 31) {} + 32 | else if (foo === 32) {} + 33 | else if (foo === 33) {} + 34 | else if (foo === 34) {} + 35 | else if (foo === 35) {} + 36 | else if (foo === 36) {} + 37 | else if (foo === 37) {} + 38 | else if (foo === 38) {} + 39 | else if (foo === 39) {} + 40 | else if (foo === 40) {} + 41 | else if (foo === 41) {} + 42 | else if (foo === 42) {} + 43 | else if (foo === 43) {} + 44 | else if (foo === 44) {} + 45 | else if (foo === 45) {} + 46 | else if (foo === 46) {} + 47 | else if (foo === 47) {} + 48 | else if (foo === 48) {} + 49 | else if (foo === 49) {} + 50 | else if (foo === 50) {} + 51 | else {} + +> Output + + `␊ + 1 | switch (foo) {␊ + 2 | case 1: {␊ + 3 | break;␊ + 4 | }␊ + 5 | case 2: {␊ + 6 | break;␊ + 7 | }␊ + 8 | case 3: {␊ + 9 | break;␊ + 10 | }␊ + 11 | case 4: {␊ + 12 | break;␊ + 13 | }␊ + 14 | case 5: {␊ + 15 | break;␊ + 16 | }␊ + 17 | case 6: {␊ + 18 | break;␊ + 19 | }␊ + 20 | case 7: {␊ + 21 | break;␊ + 22 | }␊ + 23 | case 8: {␊ + 24 | break;␊ + 25 | }␊ + 26 | case 9: {␊ + 27 | break;␊ + 28 | }␊ + 29 | case 10: {␊ + 30 | break;␊ + 31 | }␊ + 32 | case 11: {␊ + 33 | break;␊ + 34 | }␊ + 35 | case 12: {␊ + 36 | break;␊ + 37 | }␊ + 38 | case 13: {␊ + 39 | break;␊ + 40 | }␊ + 41 | case 14: {␊ + 42 | break;␊ + 43 | }␊ + 44 | case 15: {␊ + 45 | break;␊ + 46 | }␊ + 47 | case 16: {␊ + 48 | break;␊ + 49 | }␊ + 50 | case 17: {␊ + 51 | break;␊ + 52 | }␊ + 53 | case 18: {␊ + 54 | break;␊ + 55 | }␊ + 56 | case 19: {␊ + 57 | break;␊ + 58 | }␊ + 59 | case 20: {␊ + 60 | break;␊ + 61 | }␊ + 62 | case 21: {␊ + 63 | break;␊ + 64 | }␊ + 65 | case 22: {␊ + 66 | break;␊ + 67 | }␊ + 68 | case 23: {␊ + 69 | break;␊ + 70 | }␊ + 71 | case 24: {␊ + 72 | break;␊ + 73 | }␊ + 74 | case 25: {␊ + 75 | break;␊ + 76 | }␊ + 77 | case 26: {␊ + 78 | break;␊ + 79 | }␊ + 80 | case 27: {␊ + 81 | break;␊ + 82 | }␊ + 83 | case 28: {␊ + 84 | break;␊ + 85 | }␊ + 86 | case 29: {␊ + 87 | break;␊ + 88 | }␊ + 89 | case 30: {␊ + 90 | break;␊ + 91 | }␊ + 92 | case 31: {␊ + 93 | break;␊ + 94 | }␊ + 95 | case 32: {␊ + 96 | break;␊ + 97 | }␊ + 98 | case 33: {␊ + 99 | break;␊ + 100 | }␊ + 101 | case 34: {␊ + 102 | break;␊ + 103 | }␊ + 104 | case 35: {␊ + 105 | break;␊ + 106 | }␊ + 107 | case 36: {␊ + 108 | break;␊ + 109 | }␊ + 110 | case 37: {␊ + 111 | break;␊ + 112 | }␊ + 113 | case 38: {␊ + 114 | break;␊ + 115 | }␊ + 116 | case 39: {␊ + 117 | break;␊ + 118 | }␊ + 119 | case 40: {␊ + 120 | break;␊ + 121 | }␊ + 122 | case 41: {␊ + 123 | break;␊ + 124 | }␊ + 125 | case 42: {␊ + 126 | break;␊ + 127 | }␊ + 128 | case 43: {␊ + 129 | break;␊ + 130 | }␊ + 131 | case 44: {␊ + 132 | break;␊ + 133 | }␊ + 134 | case 45: {␊ + 135 | break;␊ + 136 | }␊ + 137 | case 46: {␊ + 138 | break;␊ + 139 | }␊ + 140 | case 47: {␊ + 141 | break;␊ + 142 | }␊ + 143 | case 48: {␊ + 144 | break;␊ + 145 | }␊ + 146 | case 49: {␊ + 147 | break;␊ + 148 | }␊ + 149 | case 50: {␊ + 150 | break;␊ + 151 | }␊ + 152 | default: {}␊ + 153 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | if (foo === 1) {}␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 2 | else if (foo === 2) {}␊ + 3 | else if (foo === 3) {}␊ + 4 | else if (foo === 4) {}␊ + 5 | else if (foo === 5) {}␊ + 6 | else if (foo === 6) {}␊ + 7 | else if (foo === 7) {}␊ + 8 | else if (foo === 8) {}␊ + 9 | else if (foo === 9) {}␊ + 10 | else if (foo === 10) {}␊ + 11 | else if (foo === 11) {}␊ + 12 | else if (foo === 12) {}␊ + 13 | else if (foo === 13) {}␊ + 14 | else if (foo === 14) {}␊ + 15 | else if (foo === 15) {}␊ + 16 | else if (foo === 16) {}␊ + 17 | else if (foo === 17) {}␊ + 18 | else if (foo === 18) {}␊ + 19 | else if (foo === 19) {}␊ + 20 | else if (foo === 20) {}␊ + 21 | else if (foo === 21) {}␊ + 22 | else if (foo === 22) {}␊ + 23 | else if (foo === 23) {}␊ + 24 | else if (foo === 24) {}␊ + 25 | else if (foo === 25) {}␊ + 26 | else if (foo === 26) {}␊ + 27 | else if (foo === 27) {}␊ + 28 | else if (foo === 28) {}␊ + 29 | else if (foo === 29) {}␊ + 30 | else if (foo === 30) {}␊ + 31 | else if (foo === 31) {}␊ + 32 | else if (foo === 32) {}␊ + 33 | else if (foo === 33) {}␊ + 34 | else if (foo === 34) {}␊ + 35 | else if (foo === 35) {}␊ + 36 | else if (foo === 36) {}␊ + 37 | else if (foo === 37) {}␊ + 38 | else if (foo === 38) {}␊ + 39 | else if (foo === 39) {}␊ + 40 | else if (foo === 40) {}␊ + 41 | else if (foo === 41) {}␊ + 42 | else if (foo === 42) {}␊ + 43 | else if (foo === 43) {}␊ + 44 | else if (foo === 44) {}␊ + 45 | else if (foo === 45) {}␊ + 46 | else if (foo === 46) {}␊ + 47 | else if (foo === 47) {}␊ + 48 | else if (foo === 48) {}␊ + 49 | else if (foo === 49) {}␊ + 50 | else if (foo === 50) {}␊ + 51 | else {}␊ + ` + +## Invalid #12 + 1 | if (foo === 1) {} + 2 | else if ((foo === 2 || foo === 3) || (foo === 4)) {} + 3 | else if (foo === 5) {} + +> Output + + `␊ + 1 | switch (foo) {␊ + 2 | case 1: {␊ + 3 | break;␊ + 4 | }␊ + 5 | case 2: ␊ + 6 | case 3: ␊ + 7 | case 4: {␊ + 8 | break;␊ + 9 | }␊ + 10 | case 5: {␊ + 11 | break;␊ + 12 | }␊ + 13 | // No default␊ + 14 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | if (foo === 1) {}␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 2 | else if ((foo === 2 || foo === 3) || (foo === 4)) {}␊ + 3 | else if (foo === 5) {}␊ + ` + +## Invalid #13 + 1 | function foo() { + 2 | for (const a of b) { + 3 | if (foo === 1) { + 4 | return 1; + 5 | } else if (foo === 2) { + 6 | throw new Error(); + 7 | } else if (foo === 3) { + 8 | alert(foo); + 9 | } else { + 10 | console.log('wow'); + 11 | } + 12 | } + 13 | } + +> Output + + `␊ + 1 | function foo() {␊ + 2 | for (const a of b) {␊ + 3 | switch (foo) {␊ + 4 | case 1: {␊ + 5 | return 1;␊ + 6 | ␊ + 7 | break;␊ + 8 | }␊ + 9 | case 2: {␊ + 10 | throw new Error();␊ + 11 | ␊ + 12 | break;␊ + 13 | }␊ + 14 | case 3: {␊ + 15 | alert(foo);␊ + 16 | ␊ + 17 | break;␊ + 18 | }␊ + 19 | default: {␊ + 20 | console.log('wow');␊ + 21 | }␊ + 22 | }␊ + 23 | }␊ + 24 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function foo() {␊ + 2 | for (const a of b) {␊ + > 3 | if (foo === 1) {␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 4 | return 1;␊ + 5 | } else if (foo === 2) {␊ + 6 | throw new Error();␊ + 7 | } else if (foo === 3) {␊ + 8 | alert(foo);␊ + 9 | } else {␊ + 10 | console.log('wow');␊ + 11 | }␊ + 12 | }␊ + 13 | }␊ + ` + +## Invalid #14 + 1 | function foo() { + 2 | return bar.map(foo => { + 3 | if (foo === 1) return foo; + 4 | else if (foo === 2) throw new Error(); + 5 | else if (foo === 3) foo++ + 6 | else console.log('wow'); + 7 | }) + 8 | } + +> Output + + `␊ + 1 | function foo() {␊ + 2 | return bar.map(foo => {␊ + 3 | switch (foo) {␊ + 4 | case 1: {␊ + 5 | return foo;␊ + 6 | break;␊ + 7 | }␊ + 8 | case 2: {␊ + 9 | throw new Error();␊ + 10 | break;␊ + 11 | }␊ + 12 | case 3: {␊ + 13 | foo++␊ + 14 | break;␊ + 15 | }␊ + 16 | default: {␊ + 17 | console.log('wow');␊ + 18 | }␊ + 19 | }␊ + 20 | })␊ + 21 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function foo() {␊ + 2 | return bar.map(foo => {␊ + > 3 | if (foo === 1) return foo;␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 4 | else if (foo === 2) throw new Error();␊ + 5 | else if (foo === 3) foo++␊ + 6 | else console.log('wow');␊ + 7 | })␊ + 8 | }␊ + ` + +## Invalid #15 + 1 | if (one) {} + 2 | else if (foo === 1) {} + 3 | else if (foo === 2) {} + 4 | else if (foo === 3) {} + 5 | else if (two) {} + 6 | else if (bar === 1) {} + 7 | else if (bar === 2) {} + 8 | else if (bar === 3) {} + 9 | else if (foo === 1) {} + 10 | else if (foo === 2) {} + 11 | else if (foo === 3) {} + +> Output + + `␊ + 1 | if (one) {}␊ + 2 | else switch (foo) {␊ + 3 | case 1: {␊ + 4 | break;␊ + 5 | }␊ + 6 | case 2: {␊ + 7 | break;␊ + 8 | }␊ + 9 | case 3: {␊ + 10 | break;␊ + 11 | }␊ + 12 | default: {␊ + 13 | if (two) {}␊ + 14 | else switch (bar) {␊ + 15 | case 1: {␊ + 16 | break;␊ + 17 | }␊ + 18 | case 2: {␊ + 19 | break;␊ + 20 | }␊ + 21 | case 3: {␊ + 22 | break;␊ + 23 | }␊ + 24 | default: {␊ + 25 | switch (foo) {␊ + 26 | case 1: {␊ + 27 | break;␊ + 28 | }␊ + 29 | case 2: {␊ + 30 | break;␊ + 31 | }␊ + 32 | case 3: {␊ + 33 | break;␊ + 34 | }␊ + 35 | // No default␊ + 36 | }␊ + 37 | }␊ + 38 | }␊ + 39 | }␊ + 40 | }␊ + ` + +> Error 1/3 + + `␊ + 1 | if (one) {}␊ + > 2 | else if (foo === 1) {}␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 3 | else if (foo === 2) {}␊ + 4 | else if (foo === 3) {}␊ + 5 | else if (two) {}␊ + 6 | else if (bar === 1) {}␊ + 7 | else if (bar === 2) {}␊ + 8 | else if (bar === 3) {}␊ + 9 | else if (foo === 1) {}␊ + 10 | else if (foo === 2) {}␊ + 11 | else if (foo === 3) {}␊ + ` + +> Error 2/3 + + `␊ + 1 | if (one) {}␊ + 2 | else if (foo === 1) {}␊ + 3 | else if (foo === 2) {}␊ + 4 | else if (foo === 3) {}␊ + 5 | else if (two) {}␊ + > 6 | else if (bar === 1) {}␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 7 | else if (bar === 2) {}␊ + 8 | else if (bar === 3) {}␊ + 9 | else if (foo === 1) {}␊ + 10 | else if (foo === 2) {}␊ + 11 | else if (foo === 3) {}␊ + ` + +> Error 3/3 + + `␊ + 1 | if (one) {}␊ + 2 | else if (foo === 1) {}␊ + 3 | else if (foo === 2) {}␊ + 4 | else if (foo === 3) {}␊ + 5 | else if (two) {}␊ + 6 | else if (bar === 1) {}␊ + 7 | else if (bar === 2) {}␊ + 8 | else if (bar === 3) {}␊ + > 9 | else if (foo === 1) {}␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 10 | else if (foo === 2) {}␊ + 11 | else if (foo === 3) {}␊ + ` + +## Invalid #16 + 1 | if (foo.baz === 1) {} + 2 | else if (foo['baz'] === 2) {} + 3 | else if (foo["ba" + 'z'] === 3) {} + +> Output + + `␊ + 1 | switch (foo.baz) {␊ + 2 | case 1: {␊ + 3 | break;␊ + 4 | }␊ + 5 | case 2: {␊ + 6 | break;␊ + 7 | }␊ + 8 | case 3: {␊ + 9 | break;␊ + 10 | }␊ + 11 | // No default␊ + 12 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | if (foo.baz === 1) {}␊ + | ^^^^^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 2 | else if (foo['baz'] === 2) {}␊ + 3 | else if (foo["ba" + 'z'] === 3) {}␊ + ` + +## Invalid #17 + 1 | while (bar) { + 2 | if (foo === 1) { + 3 | for (const foo of bar) { + 4 | break; + 5 | } + 6 | } else if (foo === 2) { + 7 | } else if (foo === 3) { + 8 | } + 9 | } + +> Output + + `␊ + 1 | while (bar) {␊ + 2 | switch (foo) {␊ + 3 | case 1: {␊ + 4 | for (const foo of bar) {␊ + 5 | break;␊ + 6 | }␊ + 7 | ␊ + 8 | break;␊ + 9 | }␊ + 10 | case 2: {␊ + 11 | ␊ + 12 | break;␊ + 13 | }␊ + 14 | case 3: {␊ + 15 | ␊ + 16 | break;␊ + 17 | }␊ + 18 | // No default␊ + 19 | }␊ + 20 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | while (bar) {␊ + > 2 | if (foo === 1) {␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 3 | for (const foo of bar) {␊ + 4 | break;␊ + 5 | }␊ + 6 | } else if (foo === 2) {␊ + 7 | } else if (foo === 3) {␊ + 8 | }␊ + 9 | }␊ + ` + +## Invalid #18 + 1 | while (bar) { + 2 | if (foo === 1) { + 3 | break; + 4 | } else if (foo === 2) { + 5 | } else if (foo === 3) { + 6 | } + 7 | } + +> Error 1/1 + + `␊ + 1 | while (bar) {␊ + > 2 | if (foo === 1) {␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 3 | break;␊ + 4 | } else if (foo === 2) {␊ + 5 | } else if (foo === 3) {␊ + 6 | }␊ + 7 | }␊ + ` + +## Invalid #19 + 1 | while (bar) { + 2 | if (foo === 1) { + 3 | } else if (foo === 2) { + 4 | break; + 5 | } else if (foo === 3) { + 6 | } + 7 | } + +> Error 1/1 + + `␊ + 1 | while (bar) {␊ + > 2 | if (foo === 1) {␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 3 | } else if (foo === 2) {␊ + 4 | break;␊ + 5 | } else if (foo === 3) {␊ + 6 | }␊ + 7 | }␊ + ` + +## Invalid #20 + 1 | while (bar) { + 2 | if (foo === 1) { + 3 | } else if (foo === 2) { + 4 | } else if (foo === 3) { + 5 | if (a) { + 6 | if (b) { + 7 | if (c) { + 8 | break; + 9 | } + 10 | } + 11 | } + 12 | } + 13 | } + +> Error 1/1 + + `␊ + 1 | while (bar) {␊ + > 2 | if (foo === 1) {␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 3 | } else if (foo === 2) {␊ + 4 | } else if (foo === 3) {␊ + 5 | if (a) {␊ + 6 | if (b) {␊ + 7 | if (c) {␊ + 8 | break;␊ + 9 | }␊ + 10 | }␊ + 11 | }␊ + 12 | }␊ + 13 | }␊ + ` + +## Invalid #21 + 1 | switch (bar) { + 2 | case 'bar': + 3 | if (foo === 1) { + 4 | } else if (foo === 2) { + 5 | } else if (foo === 3) { + 6 | break; + 7 | } + 8 | } + +> Error 1/1 + + `␊ + 1 | switch (bar) {␊ + 2 | case 'bar':␊ + > 3 | if (foo === 1) {␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 4 | } else if (foo === 2) {␊ + 5 | } else if (foo === 3) {␊ + 6 | break;␊ + 7 | }␊ + 8 | }␊ + ` + +## Invalid #1 + 1 | if (foo === 1) {} + 2 | else if (foo === 2) {} + +> Options + + `␊ + [␊ + {␊ + "minimumCases": 2␊ + }␊ + ]␊ + ` + +> Output + + `␊ + 1 | switch (foo) {␊ + 2 | case 1: {␊ + 3 | break;␊ + 4 | }␊ + 5 | case 2: {␊ + 6 | break;␊ + 7 | }␊ + 8 | // No default␊ + 9 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | if (foo === 1) {}␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 2 | else if (foo === 2) {}␊ + ` + +## Invalid #2 + 1 | if (foo === 1) {} + 2 | else if (foo === 2) {} + 3 | else {} + +> Options + + `␊ + [␊ + {␊ + "minimumCases": 2␊ + }␊ + ]␊ + ` + +> Output + + `␊ + 1 | switch (foo) {␊ + 2 | case 1: {␊ + 3 | break;␊ + 4 | }␊ + 5 | case 2: {␊ + 6 | break;␊ + 7 | }␊ + 8 | default: {}␊ + 9 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | if (foo === 1) {}␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 2 | else if (foo === 2) {}␊ + 3 | else {}␊ + ` + +## Invalid #3 + 1 | function foo() { + 2 | if (foo === 1) {} + 3 | else if (foo === 2) {} + 4 | else if (foo === 3) {} + 5 | } + +> Options + + `␊ + [␊ + {␊ + "emptyDefaultCase": "no-default-comment"␊ + }␊ + ]␊ + ` + +> Output + + `␊ + 1 | function foo() {␊ + 2 | switch (foo) {␊ + 3 | case 1: {␊ + 4 | break;␊ + 5 | }␊ + 6 | case 2: {␊ + 7 | break;␊ + 8 | }␊ + 9 | case 3: {␊ + 10 | break;␊ + 11 | }␊ + 12 | // No default␊ + 13 | }␊ + 14 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function foo() {␊ + > 2 | if (foo === 1) {}␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 3 | else if (foo === 2) {}␊ + 4 | else if (foo === 3) {}␊ + 5 | }␊ + ` + +## Invalid #4 + 1 | function foo() { + 2 | if (foo === 1) {} + 3 | else if (foo === 2) {} + 4 | else if (foo === 3) {} + 5 | } + +> Options + + `␊ + [␊ + {␊ + "emptyDefaultCase": "do-nothing-comment"␊ + }␊ + ]␊ + ` + +> Output + + `␊ + 1 | function foo() {␊ + 2 | switch (foo) {␊ + 3 | case 1: {␊ + 4 | break;␊ + 5 | }␊ + 6 | case 2: {␊ + 7 | break;␊ + 8 | }␊ + 9 | case 3: {␊ + 10 | break;␊ + 11 | }␊ + 12 | default:␊ + 13 | // Do nothing␊ + 14 | }␊ + 15 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function foo() {␊ + > 2 | if (foo === 1) {}␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 3 | else if (foo === 2) {}␊ + 4 | else if (foo === 3) {}␊ + 5 | }␊ + ` + +## Invalid #5 + 1 | function foo() { + 2 | if (foo === 1) {} + 3 | else if (foo === 2) {} + 4 | else if (foo === 3) {} + 5 | } + +> Options + + `␊ + [␊ + {␊ + "emptyDefaultCase": "no-default-case"␊ + }␊ + ]␊ + ` + +> Output + + `␊ + 1 | function foo() {␊ + 2 | switch (foo) {␊ + 3 | case 1: {␊ + 4 | break;␊ + 5 | }␊ + 6 | case 2: {␊ + 7 | break;␊ + 8 | }␊ + 9 | case 3: {␊ + 10 | break;␊ + 11 | }␊ + 12 | }␊ + 13 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function foo() {␊ + > 2 | if (foo === 1) {}␊ + | ^^^^^^^^^^^^^^^ Use \`switch\` instead of multiple \`else-if\`.␊ + 3 | else if (foo === 2) {}␊ + 4 | else if (foo === 3) {}␊ + 5 | }␊ + ` diff --git a/test/snapshots/prefer-switch.mjs.snap b/test/snapshots/prefer-switch.mjs.snap new file mode 100644 index 0000000000..045b67ab6d Binary files /dev/null and b/test/snapshots/prefer-switch.mjs.snap differ