diff --git a/src/configs/all.ts b/src/configs/all.ts index 63b14cae706..31d6a37375f 100644 --- a/src/configs/all.ts +++ b/src/configs/all.ts @@ -231,6 +231,7 @@ export const rules = { "prefer-object-spread": true, "prefer-switch": true, "prefer-template": true, + "prefer-while": true, "quotemark": [ true, "double", diff --git a/src/rules/code-examples/preferWhile.examples.ts b/src/rules/code-examples/preferWhile.examples.ts new file mode 100644 index 00000000000..30cf27c9ebb --- /dev/null +++ b/src/rules/code-examples/preferWhile.examples.ts @@ -0,0 +1,50 @@ +/** + * @license + * Copyright 2018 Palantir Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as Lint from "../../index"; + +// tslint:disable: object-literal-sort-keys +export const codeExamples = [ + { + description: "Prefer `while` loops instead of `for` loops without an initializer and incrementor.", + config: Lint.Utils.dedent` + "rules": { "prefer-while": true } + `, + pass: Lint.Utils.dedent` + for(let x = 1; x < 10; x++) { + console.log(x); + } + + for (let i = 0; i < 10; x+=1) { + console.log(x); + } + + for (let i = 0; i < 10;) { + i += 1; + } + `, + fail: Lint.Utils.dedent` + for(;;) { + console.log(x); + } + + for(;true===true;) { + console.log(x); + } + `, + }, +]; diff --git a/src/rules/preferWhileRule.ts b/src/rules/preferWhileRule.ts new file mode 100644 index 00000000000..240d69ce018 --- /dev/null +++ b/src/rules/preferWhileRule.ts @@ -0,0 +1,74 @@ +/** + * @license + * Copyright 2018 Palantir Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { isForStatement } from "tsutils"; +import * as ts from "typescript"; +import * as Lint from "../index"; +import { codeExamples } from "./code-examples/preferWhile.examples"; + +export class Rule extends Lint.Rules.AbstractRule { + /* tslint:disable:object-literal-sort-keys */ + public static metadata: Lint.IRuleMetadata = { + ruleName: "prefer-while", + description: "Prefer `while` loops instead of `for` loops without an initializer and incrementor.", + rationale: "Simplifies the readability of the loop statement, while maintaining the same functionality.", + optionsDescription: "Not configurable.", + options: null, + optionExamples: [true], + hasFix: true, + type: "style", + typescriptOnly: false, + codeExamples, + }; + /* tslint:enable:object-literal-sort-keys */ + + public static FAILURE_STRING = "Prefer `while` loops instead of `for` loops without an initializer and incrementor."; + + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + const failures: Lint.RuleFailure[] = []; + + const cb = (node: ts.Node): void => { + if (isForStatement(node) && this.doesNodeViolateRule(node)) { + failures.push(this.createFailure(sourceFile, node)); + } + return ts.forEachChild(node, cb); + }; + + ts.forEachChild(sourceFile, cb); + return failures; + } + + private doesNodeViolateRule(node: ts.ForStatement) { + return (node.initializer === undefined && node.incrementor === undefined); + } + + private createFailure(sourceFile: ts.SourceFile, node: ts.ForStatement): Lint.RuleFailure { + const start = node.getStart(sourceFile); + const end = node.statement.pos; + + let fix: Lint.Fix; + if (node.condition === undefined) { + fix = Lint.Replacement.replaceFromTo(start, end, "while (true)"); + } else { + fix = [ + Lint.Replacement.replaceFromTo(start, node.condition.getStart(sourceFile), "while ("), + Lint.Replacement.deleteFromTo(node.condition.end, end - 1), + ]; + } + return new Lint.RuleFailure(sourceFile, start, end, Rule.FAILURE_STRING, this.ruleName, fix); + } +} diff --git a/test/rules/prefer-while/test.ts.fix b/test/rules/prefer-while/test.ts.fix new file mode 100644 index 00000000000..43d120d1491 --- /dev/null +++ b/test/rules/prefer-while/test.ts.fix @@ -0,0 +1,24 @@ +// for loops without an initializer, termination condition, and incrementor should be updated +while (true) { + console.log(x); +} + +// for loops without an initializer and incrementor should be updated +while (true===true) { + console.log(x); +} + +// for loops with an initializer, termination condition, and incrementor using '++' should remain untouched +for(let x = 1; x < 10; x++) { + console.log(x); +} + +// for loops with an initializer, termination condition, and incrementor using '+=' should remain untouched +for (let i = 0; i < 10; x+=1) { + console.log(x); +} + +// for loops with an initializer and termination condition should remain untouched +for (let i = 0; i < 10;) { + i += 1; +} diff --git a/test/rules/prefer-while/test.ts.lint b/test/rules/prefer-while/test.ts.lint new file mode 100644 index 00000000000..cc6c417132f --- /dev/null +++ b/test/rules/prefer-while/test.ts.lint @@ -0,0 +1,26 @@ +// for loops without an initializer, termination condition, and incrementor should be updated +for(;;) { +~~~~~~~ [Prefer `while` loops instead of `for` loops without an initializer and incrementor.] + console.log(x); +} + +// for loops without an initializer and incrementor should be updated +for(;true===true;) { +~~~~~~~~~~~~~~~~~~ [Prefer `while` loops instead of `for` loops without an initializer and incrementor.] + console.log(x); +} + +// for loops with an initializer, termination condition, and incrementor using '++' should remain untouched +for(let x = 1; x < 10; x++) { + console.log(x); +} + +// for loops with an initializer, termination condition, and incrementor using '+=' should remain untouched +for (let i = 0; i < 10; x+=1) { + console.log(x); +} + +// for loops with an initializer and termination condition should remain untouched +for (let i = 0; i < 10;) { + i += 1; +} diff --git a/test/rules/prefer-while/tslint.json b/test/rules/prefer-while/tslint.json new file mode 100644 index 00000000000..87f765de49f --- /dev/null +++ b/test/rules/prefer-while/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "prefer-while": true + } +}