diff --git a/src/rules/unnecessaryElseRule.ts b/src/rules/unnecessaryElseRule.ts index 564cb29f1be..3ff493f8f5d 100644 --- a/src/rules/unnecessaryElseRule.ts +++ b/src/rules/unnecessaryElseRule.ts @@ -27,9 +27,16 @@ export class Rule extends Lint.Rules.AbstractRule { description: Lint.Utils.dedent` Disallows \`else\` blocks following \`if\` blocks ending with a \`break\`, \`continue\`, \`return\`, or \`throw\` statement.`, descriptionDetails: "", - optionExamples: [true], - options: null, - optionsDescription: "Not configurable.", + optionExamples: [true, [true, { allowElseIf: true }]], + options: { + type: "object", + properties: { + allowElseIf: { type: "boolean" }, + }, + }, + optionsDescription: Lint.Utils.dedent` + You can optionally specify the option \`"allowElseIf"\` to allow "else if" statements. + `, rationale: Lint.Utils.dedent` When an \`if\` block is guaranteed to exit control flow when entered, it is unnecessary to add an \`else\` statement. @@ -46,7 +53,11 @@ export class Rule extends Lint.Rules.AbstractRule { } public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { - return this.applyWithFunction(sourceFile, walk); + return this.applyWithFunction( + sourceFile, + walk, + parseOptions(this.ruleArguments[0] as Partial | undefined), + ); } } @@ -55,7 +66,18 @@ interface IJumpAndIfStatement { node: ts.IfStatement; } -function walk(ctx: Lint.WalkContext): void { +interface Options { + allowElseIf: boolean; +} + +function parseOptions(option: Partial | undefined): Options { + return { + allowElseIf: false, + ...option, + }; +} + +function walk(ctx: Lint.WalkContext): void { const ifStatementStack: IJumpAndIfStatement[] = []; function visitIfStatement(node: ts.IfStatement) { @@ -68,7 +90,8 @@ function walk(ctx: Lint.WalkContext): void { if ( jumpStatement !== undefined && node.elseStatement !== undefined && - !recentStackParentMissingJumpStatement() + !recentStackParentMissingJumpStatement() && + (!utils.isIfStatement(node.elseStatement) || !ctx.options.allowElseIf) ) { const elseKeyword = getPositionOfElseKeyword(node, ts.SyntaxKind.ElseKeyword); ctx.addFailureAtNode(elseKeyword, Rule.FAILURE_STRING(jumpStatement)); diff --git a/test/rules/unnecessary-else/allow-else-if/test.ts.lint b/test/rules/unnecessary-else/allow-else-if/test.ts.lint new file mode 100644 index 00000000000..96d1fc50ab6 --- /dev/null +++ b/test/rules/unnecessary-else/allow-else-if/test.ts.lint @@ -0,0 +1,255 @@ +const testReturn = (a) => { + if (a===0) { + return 0; + } else { + ~~~~ [return] + return a; + } +} + +const testReturn = (a) => { + if (a===0) return 0; + else return a; + ~~~~ [return] +} + +const testReturn = (a) => { + if (a===0) + return 0; + else + ~~~~ [return] + return a; +} + +const testReturn = (a) => { + if (a>0) { + if (a%2 ===0) { + return "even" ; + } else { + ~~~~ [return] + return "odd"; + } + } + return "negative"; +} + +const testReturn = (a) => { + if (a===0) { + return 0; + } + return a; +} + +const testReturn = (a) => { + if (a<0) { + return; + } else if (a>0) { + if (a%2 === 0) { + return ; + } else if (a === 0) { + return ; + } + } + return; +} + +const testReturn = (a) => { + if (a<0) { + return; + } + if (a===1) { + return ; + } else { + ~~~~ [return] + return ; + } +} + +const testReturn = (a) => { + if (a>0) { + if (a%3===0) { + return; + } else { + ~~~~ [return] + console.log(a) + } + } + else { + console.log("negative"); + } +} + +const testThrow = (a) => { + if ( a===0 ) { + throw "error"; + } else { + ~~~~ [throw] + return 100/a; + } +} + +const testThrow = (a) => { + if (a===0) + throw "error; + else if (a < 0) + console.log(100/a); +} + +const testThrow = (a) => { + if (a===0) throw "error; + else console.log(100/a); + ~~~~ [throw] +} + +const testThrow = (a) => { + switch (a) { + case 1: + break; + case 2: + if (true) { + throw "error"; + } else { + ~~~~ [throw] + break; + } + default : + break; + } +} + +const testThrow = (a) => { + let i = 1; + do { + if (a-i === 0) { + throw "error; + } else { + ~~~~ [throw] + console.log(i/a-i); + } + ++i; + } +} + +const testThrow = (a) => { + if (a===0) { + throw "error"; + } + return 100/a; +} + +const testThrow = (a) => { + if (a===0) throw "error"; + return 100/a; +} + +const testContinue = () => { + for (let i = 1; i < 10; i++) { + if (i===8) { + continue ; + } else { + ~~~~ [continue] + console.log(i); + } + } +} + +const testContinue = () => { + for (let i = 1; i < 10; i++) { + if (i===8) continue ; + else console.log(i); + ~~~~ [continue] + } +} + +const testContinue = () => { + for (let i = 1; i < 10; i++) { + if (i===8) + continue ; + else + ~~~~ [continue] + console.log(i); + } +} + +const testContinue = () => { + for (let i = 1; i < 10; i++) { + if (i===4) { + continue ; + } + console.log(i); + } +} + +const testContinue = () => { + for (let i = 1; i < 10; i++) { + if (i===4) + continue ; + console.log(i); + } +} + +const testBreak = (a) => { + let i = 0; + while(i < 20) { + if (i === a) { + break ; + } else { + ~~~~ [break] + i++; + } + } + return i-1; +} + +const testBreak = (a) => { + let i = 0; + while(i < 20) { + if (i === a) { + break ; + } + i++; + } + return i-1; +} + +const testBreak = (a) => { + let i = 0; + while(i < 20) { + if (i === a) + break ; + i++; + } + return i-1; +} + +const testBreak = (a) => { + let i = 0; + while(i < 20) { + if (i === a) break ; + i++; + } + return i-1; +} + +const testBreak = (a) => { + let i = 0; + while(i < 20) { + if (i === a) break ; + else i++; + ~~~~ [break] + } + return i-1; +} + +const testNoJump = (a) => { + if (a % 2 === 0) { + console.log(a); + } else { + console.log(a * 2); + } +} + +[return]: The preceding `if` block ends with a `return` statement. This `else` is unnecessary. +[throw]: The preceding `if` block ends with a `throw` statement. This `else` is unnecessary. +[break]: The preceding `if` block ends with a `break` statement. This `else` is unnecessary. +[continue]: The preceding `if` block ends with a `continue` statement. This `else` is unnecessary. diff --git a/test/rules/unnecessary-else/allow-else-if/tslint.json b/test/rules/unnecessary-else/allow-else-if/tslint.json new file mode 100644 index 00000000000..e8a78e5df1b --- /dev/null +++ b/test/rules/unnecessary-else/allow-else-if/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "unnecessary-else": [true, { "allowElseIf": true }] + } +} diff --git a/test/rules/unnecessary-else/test.ts.lint b/test/rules/unnecessary-else/default/test.ts.lint similarity index 100% rename from test/rules/unnecessary-else/test.ts.lint rename to test/rules/unnecessary-else/default/test.ts.lint diff --git a/test/rules/unnecessary-else/tslint.json b/test/rules/unnecessary-else/default/tslint.json similarity index 100% rename from test/rules/unnecessary-else/tslint.json rename to test/rules/unnecessary-else/default/tslint.json