diff --git a/src/rules/code-examples/noAny.examples.ts b/src/rules/code-examples/noAny.examples.ts index 46fc15ec49c..731678ce80f 100644 --- a/src/rules/code-examples/noAny.examples.ts +++ b/src/rules/code-examples/noAny.examples.ts @@ -31,4 +31,16 @@ export const codeExamples = [ let foo: any; `, }, + { + description: + "Disallows usages of `any` as a type declaration except rest spread parameters.", + config: Lint.Utils.dedent` + "rules": { "no-any": [true, { "ignore-rest-args": true }] } + `, + pass: Lint.Utils.dedent` + function foo(a: number, ...rest: any[]): void { + return; + } + `, + }, ]; diff --git a/src/rules/noAnyRule.ts b/src/rules/noAnyRule.ts index 6357fa1b3ab..890ea29adfe 100644 --- a/src/rules/noAnyRule.ts +++ b/src/rules/noAnyRule.ts @@ -15,12 +15,15 @@ * limitations under the License. */ +import { isArrayTypeNode, isParameterDeclaration } from "tsutils"; import * as ts from "typescript"; import * as Lint from "../index"; import { codeExamples } from "./code-examples/noAny.examples"; +const OPTION_IGNORE_REST_ARGS = "ignore-rest-args"; + export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ public static metadata: Lint.IRuleMetadata = { @@ -38,9 +41,16 @@ export class Rule extends Lint.Rules.AbstractRule { Also see the \`no-unsafe-any\` rule. `, - optionsDescription: "Not configurable.", - options: null, - optionExamples: [true], + optionsDescription: Lint.Utils.dedent` + If \`"${OPTION_IGNORE_REST_ARGS}": true\` is provided rest arguments will be ignored. + `, + options: { + type: "object", + properties: { + [OPTION_IGNORE_REST_ARGS]: { type: "boolean" }, + }, + }, + optionExamples: [true, [true, { [OPTION_IGNORE_REST_ARGS]: true }]], type: "typescript", typescriptOnly: true, codeExamples, @@ -51,16 +61,41 @@ export class Rule extends Lint.Rules.AbstractRule { "Type declaration of 'any' loses type-safety. Consider replacing it with a more precise type."; public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { - return this.applyWithFunction(sourceFile, walk); + const options: Options = getOptions(this.ruleArguments[0] as Partial | undefined); + return this.applyWithFunction(sourceFile, walk, options); } } -function walk(ctx: Lint.WalkContext) { +interface Options { + [OPTION_IGNORE_REST_ARGS]: boolean; +} + +function getOptions(options: Partial | undefined): Options { + return { + [OPTION_IGNORE_REST_ARGS]: false, + ...options, + }; +} + +function walk(ctx: Lint.WalkContext) { return ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void { if (node.kind === ts.SyntaxKind.AnyKeyword) { + if (ctx.options[OPTION_IGNORE_REST_ARGS] && isRestParameterArrayType(node)) { + return; + } + const start = node.end - 3; return ctx.addFailure(start, node.end, Rule.FAILURE_STRING); } + return ts.forEachChild(node, cb); }); } + +function isRestParameterArrayType(anyTypeNode: ts.Node) { + return ( + isArrayTypeNode(anyTypeNode.parent) && + isParameterDeclaration(anyTypeNode.parent.parent) && + anyTypeNode.parent.parent.getChildAt(0).kind === ts.SyntaxKind.DotDotDotToken + ); +} diff --git a/test/rules/no-any/test.ts.lint b/test/rules/no-any/default/test.ts.lint similarity index 59% rename from test/rules/no-any/test.ts.lint rename to test/rules/no-any/default/test.ts.lint index 5a1de903ef7..66200e514a1 100644 --- a/test/rules/no-any/test.ts.lint +++ b/test/rules/no-any/default/test.ts.lint @@ -7,9 +7,25 @@ function foo(a: any) : any { // 2 errors return; } +const fooArrow(a: any[]) => { + ~~~ [0] + return; +} + +function bar(...a: any[]) { + ~~~ [0] + return; +} + +const barArrow = (...a: any[]): any => { + ~~~ [0] + ~~~ [0] + return; +} + let a: any = 2, // error ~~~ [0] - b: any = 4; // error + b: any[] = 4; // error ~~~ [0] let {a: c, b: d}: {c: any, d: number} = {c: 99, d: 100}; // error diff --git a/test/rules/no-any/tslint.json b/test/rules/no-any/default/tslint.json similarity index 100% rename from test/rules/no-any/tslint.json rename to test/rules/no-any/default/tslint.json diff --git a/test/rules/no-any/ignore-rest-args/test.ts.lint b/test/rules/no-any/ignore-rest-args/test.ts.lint new file mode 100644 index 00000000000..9cf2ae897f1 --- /dev/null +++ b/test/rules/no-any/ignore-rest-args/test.ts.lint @@ -0,0 +1,38 @@ +var x: any; // error + ~~~ [0] + +function foo(a: any) : any { // 2 errors + ~~~ [0] + ~~~ [0] + return; +} + +const fooArrow(a: any[]) => { + ~~~ [0] + return; +} + +function bar(...a: any[]) { + return; +} + +const barArrow = (...a: any[]): any => { + ~~~ [0] + return; +} + +const function(...a: { [key: any]: any }[]) { + ~~~ [0] + ~~~ [0] + return; +} + +let a: any = 2, // error + ~~~ [0] + b: any[] = 4; // error + ~~~ [0] + +let {a: c, b: d}: {c: any, d: number} = {c: 99, d: 100}; // error + ~~~ [0] + +[0]: Type declaration of 'any' loses type-safety. Consider replacing it with a more precise type. diff --git a/test/rules/no-any/ignore-rest-args/tslint.json b/test/rules/no-any/ignore-rest-args/tslint.json new file mode 100644 index 00000000000..8ec5fb037c2 --- /dev/null +++ b/test/rules/no-any/ignore-rest-args/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-any": [true, { "ignore-rest-args": true }] + } +}