Skip to content
This repository has been archived by the owner on Mar 25, 2021. It is now read-only.

curly: Add "as-needed" option #2842

Merged
merged 3 commits into from May 31, 2017
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
46 changes: 43 additions & 3 deletions src/rules/curlyRule.ts
Expand Up @@ -15,11 +15,12 @@
* limitations under the License.
*/

import { isIfStatement, isIterationStatement, isSameLine } from "tsutils";
import { isBlock, isIfStatement, isIterationStatement, isSameLine } from "tsutils";
import * as ts from "typescript";

import * as Lint from "../index";

const OPTION_NEVER = "never";
const OPTION_IGNORE_SAME_LINE = "ignore-same-line";

interface Options {
Expand All @@ -42,8 +43,9 @@ export class Rule extends Lint.Rules.AbstractRule {
to be executed only if \`foo === bar\`. However, he forgot braces and \`bar++\` will be executed
no matter what. This rule could prevent such a mistake.`,
optionsDescription: Lint.Utils.dedent`
The rule may be set to \`true\`, or to the following:
One of the following options may be provided:

* \`"${OPTION_NEVER}"\` forbids any unnecessary curly braces.
* \`"${OPTION_IGNORE_SAME_LINE}"\` skips checking braces for control-flow statements
that are on one line and start on the same line as their control-flow keyword
`,
Expand All @@ -52,27 +54,65 @@ export class Rule extends Lint.Rules.AbstractRule {
items: {
type: "string",
enum: [
OPTION_NEVER,
OPTION_IGNORE_SAME_LINE,
],
},
},
optionExamples: [true, [true, "ignore-same-line"]],
optionExamples: [
true,
[true, "ignore-same-line"],
[true, "never"],
],
type: "functionality",
typescriptOnly: false,
};
/* tslint:enable:object-literal-sort-keys */

public static FAILURE_STRING_NEVER = "Block contains only one statement; remove the curly braces.";
public static FAILURE_STRING_FACTORY(kind: string) {
return `${kind} statements must be braced`;
}

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
if (this.ruleArguments.indexOf(OPTION_NEVER) !== -1) {
return this.applyWithFunction(sourceFile, walkNever);
}

return this.applyWithWalker(new CurlyWalker(sourceFile, this.ruleName, {
ignoreSameLine: this.ruleArguments.indexOf(OPTION_IGNORE_SAME_LINE) !== -1,
}));
}
}

function walkNever(ctx: Lint.WalkContext<void>): void {
ts.forEachChild(ctx.sourceFile, function cb(node) {
if (isBlock(node) && isBlockUnnecessary(node)) {
ctx.addFailureAtNode(Lint.childOfKind(node, ts.SyntaxKind.OpenBraceToken)!, Rule.FAILURE_STRING_NEVER);
}
ts.forEachChild(node, cb);
});
}

function isBlockUnnecessary(node: ts.Block): boolean {
const parent = node.parent!;
if (node.statements.length !== 1) { return false; }
const statement = node.statements[0];
if (isIterationStatement(parent)) { return true; }
/*
Watch out for this case:
if (so) {
if (also)
foo();
} else
bar();
*/
return isIfStatement(parent) && !(isIfStatement(statement)
&& statement.elseStatement === undefined
&& parent.thenStatement === node
&& parent.elseStatement !== undefined);
}

class CurlyWalker extends Lint.AbstractWalker<Options> {
public walk(sourceFile: ts.SourceFile) {
const cb = (node: ts.Node): void => {
Expand Down
53 changes: 53 additions & 0 deletions test/rules/curly/never/test.ts.lint
@@ -0,0 +1,53 @@
if (so) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's one edge case that needs to be handled. You can't remove the curly braces in the following example

if (foo) {
    if (bar)
        foo(bar);
} else
    bar();

~ [0]
foo();
} else {
~ [0]
foo();
}

while (true) {
~ [0]
foo();
}

if (so) {
~ [0]
if (also)
foo();
}

if (so) {
~ [0]
if (also)
foo();
else
foo();
} else
foo();

if (so)
bar();
else {
~ [0]
if (also)
foo();
}

// Some blocks are necessary.

if (so) {
if (also)
foo();
} else
bar();

function f() {
foo();
}

() => { foo(); };

try { foo(); } catch (e) { foo(); } finally { foo(); }

[0]: Block contains only one statement; remove the curly braces.
5 changes: 5 additions & 0 deletions test/rules/curly/never/tslint.json
@@ -0,0 +1,5 @@
{
"rules": {
"curly": [true, "never"]
}
}