From 624d12b6e4184ae1410caa45b52acac74a3c7eea Mon Sep 17 00:00:00 2001 From: Noah Chen Date: Sat, 17 Dec 2016 12:34:40 -0800 Subject: [PATCH] Adds option to `arrow-parens` to disallow parentheses for single arg (#1893) --- src/rules/arrowParensRule.ts | 47 +++++++++++++++---- .../avoid-on-single-parameter/test.js.lint | 12 +++++ .../avoid-on-single-parameter/test.ts.fix | 25 ++++++++++ .../avoid-on-single-parameter/test.ts.lint | 26 ++++++++++ .../avoid-on-single-parameter/tslint.json | 14 ++++++ .../arrow-parens/{ => default}/test.js.lint | 0 .../arrow-parens/{ => default}/test.ts.fix | 0 .../arrow-parens/{ => default}/test.ts.lint | 0 .../arrow-parens/{ => default}/tslint.json | 0 9 files changed, 115 insertions(+), 9 deletions(-) create mode 100644 test/rules/arrow-parens/avoid-on-single-parameter/test.js.lint create mode 100644 test/rules/arrow-parens/avoid-on-single-parameter/test.ts.fix create mode 100644 test/rules/arrow-parens/avoid-on-single-parameter/test.ts.lint create mode 100644 test/rules/arrow-parens/avoid-on-single-parameter/tslint.json rename test/rules/arrow-parens/{ => default}/test.js.lint (100%) rename test/rules/arrow-parens/{ => default}/test.ts.fix (100%) rename test/rules/arrow-parens/{ => default}/test.ts.lint (100%) rename test/rules/arrow-parens/{ => default}/tslint.json (100%) diff --git a/src/rules/arrowParensRule.ts b/src/rules/arrowParensRule.ts index bc89008ee82..fabe078915e 100644 --- a/src/rules/arrowParensRule.ts +++ b/src/rules/arrowParensRule.ts @@ -20,21 +20,29 @@ import * as ts from "typescript"; import * as Lint from "../index"; import { hasModifier } from "../language/utils"; +const AVOID_ON_SINGLE_PARAMETER = "avoid-on-single-parameter"; + export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ public static metadata: Lint.IRuleMetadata = { ruleName: "arrow-parens", description: "Requires parentheses around the parameters of arrow function definitions.", rationale: "Maintains stylistic consistency with other arrow function definitions.", - optionsDescription: "Not configurable.", - options: null, - optionExamples: ["true"], + optionsDescription: Lint.Utils.dedent` + if \`${AVOID_ON_SINGLE_PARAMETER}\` is specified, then arrow functions with one parameter + must not have parentheses if removing them is allowed by TypeScript.`, + options: { + type: "string", + enum: [AVOID_ON_SINGLE_PARAMETER], + }, + optionExamples: [`true`, `[true, ${AVOID_ON_SINGLE_PARAMETER}]`], type: "style", typescriptOnly: false, }; /* tslint:enable:object-literal-sort-keys */ - public static FAILURE_STRING = "Parentheses are required around the parameters of an arrow function definition"; + public static FAILURE_STRING_MISSING = "Parentheses are required around the parameters of an arrow function definition"; + public static FAILURE_STRING_EXISTS = "Parentheses are prohibited around the parameter in this single parameter arrow function"; public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { const newParensWalker = new ArrowParensWalker(sourceFile, this.getOptions()); @@ -43,6 +51,13 @@ export class Rule extends Lint.Rules.AbstractRule { } class ArrowParensWalker extends Lint.RuleWalker { + private avoidOnSingleParameter: boolean; + + constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) { + super(sourceFile, options); + this.avoidOnSingleParameter = this.hasOption(AVOID_ON_SINGLE_PARAMETER); + } + public visitArrowFunction(node: ts.FunctionLikeDeclaration) { if (node.parameters.length === 1) { const parameter = node.parameters[0]; @@ -58,11 +73,25 @@ class ArrowParensWalker extends Lint.RuleWalker { isGenerics = true; } - if ((firstToken.kind !== ts.SyntaxKind.OpenParenToken || lastToken.kind !== ts.SyntaxKind.CloseParenToken) - && !isGenerics && !hasModifier(node.modifiers, ts.SyntaxKind.AsyncKeyword)) { - - const fix = new Lint.Fix(Rule.metadata.ruleName, [new Lint.Replacement(position, width, `(${parameter.getText()})`)]); - this.addFailureAt(position, width, Rule.FAILURE_STRING, fix); + if (!isGenerics && !hasModifier(node.modifiers, ts.SyntaxKind.AsyncKeyword)) { + const hasParens = firstToken.kind === ts.SyntaxKind.OpenParenToken && lastToken.kind === ts.SyntaxKind.CloseParenToken; + if (!hasParens && !this.avoidOnSingleParameter) { + const fix = new Lint.Fix(Rule.metadata.ruleName, [new Lint.Replacement(position, width, `(${parameter.getText()})`)]); + this.addFailureAt(position, width, Rule.FAILURE_STRING_MISSING, fix); + } else if (hasParens + && this.avoidOnSingleParameter + && parameter.decorators == null + && parameter.dotDotDotToken == null + && parameter.initializer == null + && parameter.modifiers == null + && parameter.questionToken == null + && parameter.type == null) { + const fix = new Lint.Fix(Rule.metadata.ruleName, [ + new Lint.Replacement(lastToken.getStart(), 1, ``), + new Lint.Replacement(firstToken.getStart(), 1, ``), + ]); + this.addFailureAt(position, width, Rule.FAILURE_STRING_EXISTS, fix); + } } } super.visitArrowFunction(node); diff --git a/test/rules/arrow-parens/avoid-on-single-parameter/test.js.lint b/test/rules/arrow-parens/avoid-on-single-parameter/test.js.lint new file mode 100644 index 00000000000..6fee683ef3e --- /dev/null +++ b/test/rules/arrow-parens/avoid-on-single-parameter/test.js.lint @@ -0,0 +1,12 @@ +// valid case +var b = (a: number) => {}; +var c = (a, b) => {}; +var f = (...rest) => {}; +class Foo { + a: (a) =>{} +} +var e = (a => {})(1); +var f = a => {}; +// invalid case +var a = (a) => {}; + ~ [Parentheses are prohibited around the parameter in this single parameter arrow function] diff --git a/test/rules/arrow-parens/avoid-on-single-parameter/test.ts.fix b/test/rules/arrow-parens/avoid-on-single-parameter/test.ts.fix new file mode 100644 index 00000000000..d15557fe7d3 --- /dev/null +++ b/test/rules/arrow-parens/avoid-on-single-parameter/test.ts.fix @@ -0,0 +1,25 @@ +// valid case +var b = (a: number) => {}; +var c = (a, b) => {}; +var f = (...rest) => {}; +var f = a: number => {}; // TSLint don't warn. But syntax is wrong. +var bar = (method: () => T) => { + method(); +}; +var barbar = (method: (a: any) => T) => { + method(""); +}; +var barbarbar = (method: (a) => T) => { + method(""); +}; +var piyo = (method: () => T) => { + method(); +}; +const validAsync = async (param: any) => {}; +const validAsync = async (param) => {}; + +var e = (a => {})(1); +var f = ab => {}; + +// invalid case +var a = a => {}; diff --git a/test/rules/arrow-parens/avoid-on-single-parameter/test.ts.lint b/test/rules/arrow-parens/avoid-on-single-parameter/test.ts.lint new file mode 100644 index 00000000000..23b233bb4fc --- /dev/null +++ b/test/rules/arrow-parens/avoid-on-single-parameter/test.ts.lint @@ -0,0 +1,26 @@ +// valid case +var b = (a: number) => {}; +var c = (a, b) => {}; +var f = (...rest) => {}; +var f = a: number => {}; // TSLint don't warn. But syntax is wrong. +var bar = (method: () => T) => { + method(); +}; +var barbar = (method: (a: any) => T) => { + method(""); +}; +var barbarbar = (method: (a) => T) => { + method(""); +}; +var piyo = (method: () => T) => { + method(); +}; +const validAsync = async (param: any) => {}; +const validAsync = async (param) => {}; + +var e = (a => {})(1); +var f = ab => {}; + +// invalid case +var a = (a) => {}; + ~ [Parentheses are prohibited around the parameter in this single parameter arrow function] diff --git a/test/rules/arrow-parens/avoid-on-single-parameter/tslint.json b/test/rules/arrow-parens/avoid-on-single-parameter/tslint.json new file mode 100644 index 00000000000..c4aff29932b --- /dev/null +++ b/test/rules/arrow-parens/avoid-on-single-parameter/tslint.json @@ -0,0 +1,14 @@ +{ + "rules": { + "arrow-parens": [ + true, + "avoid-on-single-parameter" + ] + }, + "jsRules": { + "arrow-parens": [ + true, + "avoid-on-single-parameter" + ] + } +} \ No newline at end of file diff --git a/test/rules/arrow-parens/test.js.lint b/test/rules/arrow-parens/default/test.js.lint similarity index 100% rename from test/rules/arrow-parens/test.js.lint rename to test/rules/arrow-parens/default/test.js.lint diff --git a/test/rules/arrow-parens/test.ts.fix b/test/rules/arrow-parens/default/test.ts.fix similarity index 100% rename from test/rules/arrow-parens/test.ts.fix rename to test/rules/arrow-parens/default/test.ts.fix diff --git a/test/rules/arrow-parens/test.ts.lint b/test/rules/arrow-parens/default/test.ts.lint similarity index 100% rename from test/rules/arrow-parens/test.ts.lint rename to test/rules/arrow-parens/default/test.ts.lint diff --git a/test/rules/arrow-parens/tslint.json b/test/rules/arrow-parens/default/tslint.json similarity index 100% rename from test/rules/arrow-parens/tslint.json rename to test/rules/arrow-parens/default/tslint.json