From a3d4f3eb3d50481b9f1b2475c37ca21208d776c3 Mon Sep 17 00:00:00 2001 From: ScottSWu Date: Fri, 19 Aug 2016 11:28:25 -0700 Subject: [PATCH] Add ArrayType rule to enforce 'T[]' or 'Array' * Add ArrayType to SyntaxWalker --- src/language/walker/syntaxWalker.ts | 8 +++ src/rules/arrayTypeRule.ts | 76 ++++++++++++++++++++++ src/tsconfig.json | 1 + test/rules/array-type/array/test.ts.fix | 29 +++++++++ test/rules/array-type/array/test.ts.lint | 38 +++++++++++ test/rules/array-type/array/tslint.json | 5 ++ test/rules/array-type/generic/test.ts.fix | 29 +++++++++ test/rules/array-type/generic/test.ts.lint | 36 ++++++++++ test/rules/array-type/generic/tslint.json | 5 ++ test/tsconfig.json | 1 + 10 files changed, 228 insertions(+) create mode 100644 src/rules/arrayTypeRule.ts create mode 100644 test/rules/array-type/array/test.ts.fix create mode 100644 test/rules/array-type/array/test.ts.lint create mode 100644 test/rules/array-type/array/tslint.json create mode 100644 test/rules/array-type/generic/test.ts.fix create mode 100644 test/rules/array-type/generic/test.ts.lint create mode 100644 test/rules/array-type/generic/tslint.json diff --git a/src/language/walker/syntaxWalker.ts b/src/language/walker/syntaxWalker.ts index 245bc0e6ae4..746c49dce8a 100644 --- a/src/language/walker/syntaxWalker.ts +++ b/src/language/walker/syntaxWalker.ts @@ -30,6 +30,10 @@ export class SyntaxWalker { this.walkChildren(node); } + protected visitArrayType(node: ts.ArrayTypeNode) { + this.walkChildren(node); + } + protected visitArrowFunction(node: ts.FunctionLikeDeclaration) { this.walkChildren(node); } @@ -328,6 +332,10 @@ export class SyntaxWalker { this.visitArrayLiteralExpression( node); break; + case ts.SyntaxKind.ArrayType: + this.visitArrayType( node); + break; + case ts.SyntaxKind.ArrowFunction: this.visitArrowFunction( node); break; diff --git a/src/rules/arrayTypeRule.ts b/src/rules/arrayTypeRule.ts new file mode 100644 index 00000000000..9beab5854bc --- /dev/null +++ b/src/rules/arrayTypeRule.ts @@ -0,0 +1,76 @@ +import * as ts from "typescript"; + +import * as Lint from "../lint"; + +const OPTION_ARRAY = "array"; +const OPTION_GENERIC = "generic"; + +export class Rule extends Lint.Rules.AbstractRule { + /* tslint:disable:object-literal-sort-keys */ + public static metadata: Lint.IRuleMetadata = { + ruleName: "array-type", + description: "Requires using either 'T[]' or 'Array' for arrays.", + optionsDescription: Lint.Utils.dedent` + One of the following arguments must be provided: + + * \`"${OPTION_ARRAY}"\` enforces use of 'T[]'. + * \`"${OPTION_GENERIC}"\` enforces use of 'Array'.`, + options: { + type: "string", + enum: [OPTION_ARRAY, OPTION_GENERIC], + }, + optionExamples: ["[true, array]", "[true, generic]"], + type: "style", + }; + /* tslint:enable:object-literal-sort-keys */ + + public static FAILURE_STRING_ARRAY = "Array type using 'Array' is forbidden. Use 'T[]' instead."; + public static FAILURE_STRING_GENERIC = "Array type using 'T[]' is forbidden. Use 'Array' instead."; + + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + const alignWalker = new ArrayTypeWalker(sourceFile, this.getOptions()); + return this.applyWithWalker(alignWalker); + } +} + +class ArrayTypeWalker extends Lint.RuleWalker { + public visitArrayType(node: ts.ArrayTypeNode) { + if (this.hasOption(OPTION_GENERIC)) { + const typeName = node.elementType; + const fix = new Lint.Fix(Rule.metadata.ruleName, [ + this.appendText(typeName.getStart(), "Array<"), + // Delete the square brackets and replace with an angle bracket + this.createReplacement(typeName.getEnd(), node.getEnd() - typeName.getEnd(), ">"), + ]); + this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING_GENERIC, fix)); + } + + super.visitArrayType(node); + } + + public visitTypeReference(node: ts.TypeReferenceNode) { + const typeName = node.typeName.getText(); + if (this.hasOption(OPTION_ARRAY) && typeName === "Array") { + const typeArgs = node.typeArguments; + let fix: Lint.Fix; + if (!typeArgs || typeArgs.length === 0) { + // Create an 'any' array + fix = new Lint.Fix(Rule.metadata.ruleName, [ + this.createReplacement(node.getStart(), node.getWidth(), "any[]"), + ]); + } else if (typeArgs && typeArgs.length === 1) { + const typeStart = typeArgs[0].getStart(); + const typeEnd = typeArgs[0].getEnd(); + fix = new Lint.Fix(Rule.metadata.ruleName, [ + // Delete Array and the first angle bracket + this.deleteText(node.getStart(), typeStart - node.getStart()), + // Delete the last angle bracket and replace with square brackets + this.createReplacement(typeEnd, node.getEnd() - typeEnd, "[]"), + ]); + } + this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING_ARRAY, fix)); + } + + super.visitTypeReference(node); + } +} diff --git a/src/tsconfig.json b/src/tsconfig.json index ccc8f5df16d..cd15813f25a 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -71,6 +71,7 @@ "language/walker/syntaxWalker.ts", "rules/adjacentOverloadSignaturesRule.ts", "rules/alignRule.ts", + "rules/arrayTypeRule.ts", "rules/arrowParensRule.ts", "rules/banRule.ts", "rules/classNameRule.ts", diff --git a/test/rules/array-type/array/test.ts.fix b/test/rules/array-type/array/test.ts.fix new file mode 100644 index 00000000000..fcaf684a099 --- /dev/null +++ b/test/rules/array-type/array/test.ts.fix @@ -0,0 +1,29 @@ +let x: number[] = [1] as number[]; +let y: string[] = ["2"]; +let z: any[] = [3, "4"]; + +let xx: number[][] = [[1, 2], [3]]; +let yy: number[][] = [[4, 5], [6]]; + +type Arr = T[]; + +// Ignore user defined aliases +let yyyy: Arr[][]> = [[[["2"]]]]; + +interface ArrayClass { + foo: T[]; + bar: T[]; + baz: Arr; +} + +function fooFunction(foo: ArrayClass[]) { + return foo.map(e => e.foo); +} + +function barFunction(bar: ArrayClass[]) { + return bar.map(e => e.bar); +} + +function bazFunction(baz: Arr>) { + return baz.map(e => e.baz); +} diff --git a/test/rules/array-type/array/test.ts.lint b/test/rules/array-type/array/test.ts.lint new file mode 100644 index 00000000000..7b2886b21d2 --- /dev/null +++ b/test/rules/array-type/array/test.ts.lint @@ -0,0 +1,38 @@ +let x: Array = [1] as number[]; + ~~~~~~~~~~~~~ [Array type using 'Array' is forbidden. Use 'T[]' instead.] +let y: string[] = >["2"]; + ~~~~~~~~~~~~~ [Array type using 'Array' is forbidden. Use 'T[]' instead.] +let z: Array = [3, "4"]; + ~~~~~ [Array type using 'Array' is forbidden. Use 'T[]' instead.] + +let xx: Array> = [[1, 2], [3]]; + ~~~~~~~~~~~~~~~~~~~~ [Array type using 'Array' is forbidden. Use 'T[]' instead.] + ~~~~~~~~~~~~~ [Array type using 'Array' is forbidden. Use 'T[]' instead.] +let yy: number[][] = [[4, 5], [6]]; + +type Arr = Array; + ~~~~~~~~ [Array type using 'Array' is forbidden. Use 'T[]' instead.] + +// Ignore user defined aliases +let yyyy: Arr>[]> = [[[["2"]]]]; + ~~~~~~~~~~~~~~~~~~ [Array type using 'Array' is forbidden. Use 'T[]' instead.] + +interface ArrayClass { + foo: Array; + ~~~~~~~~ [Array type using 'Array' is forbidden. Use 'T[]' instead.] + bar: T[]; + baz: Arr; +} + +function fooFunction(foo: Array>) { + ~~~~~~~~~~~~~~~~~~~~~~~~~ [Array type using 'Array' is forbidden. Use 'T[]' instead.] + return foo.map(e => e.foo); +} + +function barFunction(bar: ArrayClass[]) { + return bar.map(e => e.bar); +} + +function bazFunction(baz: Arr>) { + return baz.map(e => e.baz); +} diff --git a/test/rules/array-type/array/tslint.json b/test/rules/array-type/array/tslint.json new file mode 100644 index 00000000000..184d4606431 --- /dev/null +++ b/test/rules/array-type/array/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "array-type": [true, "array"] + } +} diff --git a/test/rules/array-type/generic/test.ts.fix b/test/rules/array-type/generic/test.ts.fix new file mode 100644 index 00000000000..fd723976ebb --- /dev/null +++ b/test/rules/array-type/generic/test.ts.fix @@ -0,0 +1,29 @@ +let x: Array = [1] as Array; +let y: Array = >["2"]; +let z: Array = [3, "4"]; + +let xx: Array> = [[1, 2], [3]]; +let yy: Array> = [[4, 5], [6]]; + +type Arr = Array; + +// Ignore user defined aliases +let yyyy: Arr>>> = [[[["2"]]]]; + +interface ArrayClass { + foo: Array; + bar: Array; + baz: Arr; +} + +function fooFunction(foo: Array>) { + return foo.map(e => e.foo); +} + +function barFunction(bar: Array>) { + return bar.map(e => e.bar); +} + +function bazFunction(baz: Arr>) { + return baz.map(e => e.baz); +} diff --git a/test/rules/array-type/generic/test.ts.lint b/test/rules/array-type/generic/test.ts.lint new file mode 100644 index 00000000000..1397ceaedd2 --- /dev/null +++ b/test/rules/array-type/generic/test.ts.lint @@ -0,0 +1,36 @@ +let x: Array = [1] as number[]; + ~~~~~~~~ [Array type using 'T[]' is forbidden. Use 'Array' instead.] +let y: string[] = >["2"]; + ~~~~~~~~ [Array type using 'T[]' is forbidden. Use 'Array' instead.] +let z: Array = [3, "4"]; + +let xx: Array> = [[1, 2], [3]]; +let yy: number[][] = [[4, 5], [6]]; + ~~~~~~~~~~ [Array type using 'T[]' is forbidden. Use 'Array' instead.] + ~~~~~~~~ [Array type using 'T[]' is forbidden. Use 'Array' instead.] + +type Arr = Array; + +// Ignore user defined aliases +let yyyy: Arr>[]> = [[[["2"]]]]; + ~~~~~~~~~~~~~~~~~~~~ [Array type using 'T[]' is forbidden. Use 'Array' instead.] + +interface ArrayClass { + foo: Array; + bar: T[]; + ~~~ [Array type using 'T[]' is forbidden. Use 'Array' instead.] + baz: Arr; +} + +function fooFunction(foo: Array>) { + return foo.map(e => e.foo); +} + +function barFunction(bar: ArrayClass[]) { + ~~~~~~~~~~~~~~~~~~~~ [Array type using 'T[]' is forbidden. Use 'Array' instead.] + return bar.map(e => e.bar); +} + +function bazFunction(baz: Arr>) { + return baz.map(e => e.baz); +} diff --git a/test/rules/array-type/generic/tslint.json b/test/rules/array-type/generic/tslint.json new file mode 100644 index 00000000000..610c20100a8 --- /dev/null +++ b/test/rules/array-type/generic/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "array-type": [true, "generic"] + } +} diff --git a/test/tsconfig.json b/test/tsconfig.json index 5c6e0d82f8b..76a547b2784 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -67,6 +67,7 @@ "../src/rules.ts", "../src/rules/adjacentOverloadSignaturesRule.ts", "../src/rules/alignRule.ts", + "../src/rules/arrayTypeRule.ts", "../src/rules/arrowParensRule.ts", "../src/rules/banRule.ts", "../src/rules/classNameRule.ts",