Skip to content

Commit

Permalink
Add ArrayType rule to enforce 'T[]' or 'Array<T>' (palantir#1498)
Browse files Browse the repository at this point in the history
* Add ArrayType to SyntaxWalker
  • Loading branch information
ScottSWu authored and Nina Hartmann committed Aug 31, 2016
1 parent 22ce5fc commit d3a9b41
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 0 deletions.
8 changes: 8 additions & 0 deletions src/language/walker/syntaxWalker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -332,6 +336,10 @@ export class SyntaxWalker {
this.visitArrayLiteralExpression(<ts.ArrayLiteralExpression> node);
break;

case ts.SyntaxKind.ArrayType:
this.visitArrayType(<ts.ArrayTypeNode> node);
break;

case ts.SyntaxKind.ArrowFunction:
this.visitArrowFunction(<ts.FunctionLikeDeclaration> node);
break;
Expand Down
76 changes: 76 additions & 0 deletions src/rules/arrayTypeRule.ts
Original file line number Diff line number Diff line change
@@ -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<T>' 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<T>'.`,
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<T>' is forbidden. Use 'T[]' instead.";
public static FAILURE_STRING_GENERIC = "Array type using 'T[]' is forbidden. Use 'Array<T>' 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);
}
}
1 change: 1 addition & 0 deletions src/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
29 changes: 29 additions & 0 deletions test/rules/array-type/array/test.ts.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
let x: number[] = [1] as number[];
let y: string[] = <string[]>["2"];
let z: any[] = [3, "4"];

let xx: number[][] = [[1, 2], [3]];
let yy: number[][] = [[4, 5], [6]];

type Arr<T> = T[];

// Ignore user defined aliases
let yyyy: Arr<Arr<string>[][]> = [[[["2"]]]];

interface ArrayClass<T> {
foo: T[];
bar: T[];
baz: Arr<T>;
}

function fooFunction(foo: ArrayClass<string>[]) {
return foo.map(e => e.foo);
}

function barFunction(bar: ArrayClass<String>[]) {
return bar.map(e => e.bar);
}

function bazFunction(baz: Arr<ArrayClass<String>>) {
return baz.map(e => e.baz);
}
38 changes: 38 additions & 0 deletions test/rules/array-type/array/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
let x: Array<number> = [1] as number[];
~~~~~~~~~~~~~ [Array type using 'Array<T>' is forbidden. Use 'T[]' instead.]
let y: string[] = <Array<string>>["2"];
~~~~~~~~~~~~~ [Array type using 'Array<T>' is forbidden. Use 'T[]' instead.]
let z: Array = [3, "4"];
~~~~~ [Array type using 'Array<T>' is forbidden. Use 'T[]' instead.]

let xx: Array<Array<number>> = [[1, 2], [3]];
~~~~~~~~~~~~~~~~~~~~ [Array type using 'Array<T>' is forbidden. Use 'T[]' instead.]
~~~~~~~~~~~~~ [Array type using 'Array<T>' is forbidden. Use 'T[]' instead.]
let yy: number[][] = [[4, 5], [6]];

type Arr<T> = Array<T>;
~~~~~~~~ [Array type using 'Array<T>' is forbidden. Use 'T[]' instead.]

// Ignore user defined aliases
let yyyy: Arr<Array<Arr<string>>[]> = [[[["2"]]]];
~~~~~~~~~~~~~~~~~~ [Array type using 'Array<T>' is forbidden. Use 'T[]' instead.]

interface ArrayClass<T> {
foo: Array<T>;
~~~~~~~~ [Array type using 'Array<T>' is forbidden. Use 'T[]' instead.]
bar: T[];
baz: Arr<T>;
}

function fooFunction(foo: Array<ArrayClass<string>>) {
~~~~~~~~~~~~~~~~~~~~~~~~~ [Array type using 'Array<T>' is forbidden. Use 'T[]' instead.]
return foo.map(e => e.foo);
}

function barFunction(bar: ArrayClass<String>[]) {
return bar.map(e => e.bar);
}

function bazFunction(baz: Arr<ArrayClass<String>>) {
return baz.map(e => e.baz);
}
5 changes: 5 additions & 0 deletions test/rules/array-type/array/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"array-type": [true, "array"]
}
}
29 changes: 29 additions & 0 deletions test/rules/array-type/generic/test.ts.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
let x: Array<number> = [1] as Array<number>;
let y: Array<string> = <Array<string>>["2"];
let z: Array = [3, "4"];

let xx: Array<Array<number>> = [[1, 2], [3]];
let yy: Array<Array<number>> = [[4, 5], [6]];

type Arr<T> = Array<T>;

// Ignore user defined aliases
let yyyy: Arr<Array<Array<Arr<string>>>> = [[[["2"]]]];

interface ArrayClass<T> {
foo: Array<T>;
bar: Array<T>;
baz: Arr<T>;
}

function fooFunction(foo: Array<ArrayClass<string>>) {
return foo.map(e => e.foo);
}

function barFunction(bar: Array<ArrayClass<String>>) {
return bar.map(e => e.bar);
}

function bazFunction(baz: Arr<ArrayClass<String>>) {
return baz.map(e => e.baz);
}
36 changes: 36 additions & 0 deletions test/rules/array-type/generic/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
let x: Array<number> = [1] as number[];
~~~~~~~~ [Array type using 'T[]' is forbidden. Use 'Array<T>' instead.]
let y: string[] = <Array<string>>["2"];
~~~~~~~~ [Array type using 'T[]' is forbidden. Use 'Array<T>' instead.]
let z: Array = [3, "4"];

let xx: Array<Array<number>> = [[1, 2], [3]];
let yy: number[][] = [[4, 5], [6]];
~~~~~~~~~~ [Array type using 'T[]' is forbidden. Use 'Array<T>' instead.]
~~~~~~~~ [Array type using 'T[]' is forbidden. Use 'Array<T>' instead.]

type Arr<T> = Array<T>;

// Ignore user defined aliases
let yyyy: Arr<Array<Arr<string>>[]> = [[[["2"]]]];
~~~~~~~~~~~~~~~~~~~~ [Array type using 'T[]' is forbidden. Use 'Array<T>' instead.]

interface ArrayClass<T> {
foo: Array<T>;
bar: T[];
~~~ [Array type using 'T[]' is forbidden. Use 'Array<T>' instead.]
baz: Arr<T>;
}

function fooFunction(foo: Array<ArrayClass<string>>) {
return foo.map(e => e.foo);
}

function barFunction(bar: ArrayClass<String>[]) {
~~~~~~~~~~~~~~~~~~~~ [Array type using 'T[]' is forbidden. Use 'Array<T>' instead.]
return bar.map(e => e.bar);
}

function bazFunction(baz: Arr<ArrayClass<String>>) {
return baz.map(e => e.baz);
}
5 changes: 5 additions & 0 deletions test/rules/array-type/generic/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"array-type": [true, "generic"]
}
}
1 change: 1 addition & 0 deletions test/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit d3a9b41

Please sign in to comment.