From e080fc4ceb58e907d48bf5419ceb96e6899dd5d5 Mon Sep 17 00:00:00 2001 From: Karol Majewski <20233319+karol-majewski@users.noreply.github.com> Date: Tue, 12 Mar 2019 19:31:59 +0100 Subject: [PATCH] [no-object-literal-type-assertion] New option: "allow-arguments" (#4521) --- .../noObjectLiteralTypeAssertion.examples.ts | 50 ++++++++++++++++++ src/rules/noObjectLiteralTypeAssertionRule.ts | 52 ++++++++++++++++--- .../allow-arguments/test.ts.lint | 23 ++++++++ .../allow-arguments/tslint.json | 5 ++ .../{ => default}/test.ts.lint | 6 +++ .../{ => default}/tslint.json | 0 6 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 src/rules/code-examples/noObjectLiteralTypeAssertion.examples.ts create mode 100644 test/rules/no-object-literal-type-assertion/allow-arguments/test.ts.lint create mode 100644 test/rules/no-object-literal-type-assertion/allow-arguments/tslint.json rename test/rules/no-object-literal-type-assertion/{ => default}/test.ts.lint (81%) rename test/rules/no-object-literal-type-assertion/{ => default}/tslint.json (100%) diff --git a/src/rules/code-examples/noObjectLiteralTypeAssertion.examples.ts b/src/rules/code-examples/noObjectLiteralTypeAssertion.examples.ts new file mode 100644 index 00000000000..19e7ccf4ab2 --- /dev/null +++ b/src/rules/code-examples/noObjectLiteralTypeAssertion.examples.ts @@ -0,0 +1,50 @@ +/** + * @license + * Copyright 2019 Palantir Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as Lint from "../../index"; +import { ICodeExample } from "../../language/rule/rule"; + +// tslint:disable: object-literal-sort-keys +export const codeExamples: ICodeExample[] = [ + { + description: + "Disallow object literals to appear in type assertion expressions (default). Casing to `any` and `unknown` is allowed.", + config: Lint.Utils.dedent` + "rules": { "no-object-literal-type-assertion": true } + `, + pass: Lint.Utils.dedent` + let foo = {} as any; + let foo = {} as unknown; + + let foo = {} as any as Foo; + let foo = {} as unknown as Foo; + `, + fail: Lint.Utils.dedent` + let foo = {} as Foo; + let foo = {}; + `, + }, + { + description: "Allow using a type assertion when the object literal is used as an argument.", + config: Lint.Utils.dedent` + "rules": { "no-object-literal-type-assertion": [true, { "allow-arguments": true }] } + `, + pass: Lint.Utils.dedent` + bar({} as Foo) + `, + }, +]; diff --git a/src/rules/noObjectLiteralTypeAssertionRule.ts b/src/rules/noObjectLiteralTypeAssertionRule.ts index 2a845406a59..2db26eb09da 100644 --- a/src/rules/noObjectLiteralTypeAssertionRule.ts +++ b/src/rules/noObjectLiteralTypeAssertionRule.ts @@ -24,6 +24,18 @@ import * as ts from "typescript"; import * as Lint from "../index"; +import { codeExamples } from "./code-examples/noObjectLiteralTypeAssertion.examples"; + +const OPTION_ALLOW_ARGUMENTS = "allow-arguments"; + +const DEFAULT_OPTIONS: Options = { + [OPTION_ALLOW_ARGUMENTS]: false, +}; + +interface Options { + [OPTION_ALLOW_ARGUMENTS]: boolean; +} + export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ public static metadata: Lint.IRuleMetadata = { @@ -37,11 +49,23 @@ export class Rule extends Lint.Rules.AbstractRule { The compiler will warn for excess properties with this syntax, but not missing required fields. For example: \`const x: { foo: number } = {}\` will fail to compile, but \`const x = {} as { foo: number }\` will succeed.`, - optionsDescription: "Not configurable.", - options: null, - optionExamples: [true], + optionsDescription: Lint.Utils.dedent` + One option may be configured: + + * \`${OPTION_ALLOW_ARGUMENTS}\` allows type assertions to be used on object literals inside call expressions.`, + options: { + type: "object", + properties: { + [OPTION_ALLOW_ARGUMENTS]: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + optionExamples: [true, [true, { [OPTION_ALLOW_ARGUMENTS]: true }]], type: "functionality", typescriptOnly: true, + codeExamples, }; /* tslint:enable:object-literal-sort-keys */ @@ -49,11 +73,15 @@ export class Rule extends Lint.Rules.AbstractRule { "Type assertion on object literals is forbidden, use a type annotation instead."; public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { - return this.applyWithFunction(sourceFile, walk); + return this.applyWithFunction( + sourceFile, + walk, + parseOptions(this.ruleArguments[0] as Partial | undefined), + ); } } -function walk(ctx: Lint.WalkContext): void { +function walk(ctx: Lint.WalkContext): void { return ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void { if ( isAssertionExpression(node) && @@ -66,10 +94,22 @@ function walk(ctx: Lint.WalkContext): void { isParenthesizedExpression(node.expression) ? node.expression.expression : node.expression, - ) + ) && + !(ctx.options[OPTION_ALLOW_ARGUMENTS] && isArgument(node)) ) { ctx.addFailureAtNode(node, Rule.FAILURE_STRING); } return ts.forEachChild(node, cb); }); } + +function isArgument(node: ts.Node): boolean { + return ts.isCallLikeExpression(node.parent); +} + +function parseOptions(options: Partial | undefined): Options { + return { + ...DEFAULT_OPTIONS, + ...options, + }; +} diff --git a/test/rules/no-object-literal-type-assertion/allow-arguments/test.ts.lint b/test/rules/no-object-literal-type-assertion/allow-arguments/test.ts.lint new file mode 100644 index 00000000000..ba5f37ee442 --- /dev/null +++ b/test/rules/no-object-literal-type-assertion/allow-arguments/test.ts.lint @@ -0,0 +1,23 @@ + ({}); +~~~~~~~~ [0] + +({}) as T; +~~~~~~~~~ [0] + + x; + +x as T; + +// Allow cast to 'any' +{} as any; + {}; + +// Allow cast to 'unknown' +{} as unknown; + {}; + +// Allow object literals to be asserted when used as arguments. +foo({} as T); +foo({}); + +[0]: Type assertion on object literals is forbidden, use a type annotation instead. diff --git a/test/rules/no-object-literal-type-assertion/allow-arguments/tslint.json b/test/rules/no-object-literal-type-assertion/allow-arguments/tslint.json new file mode 100644 index 00000000000..6dad916f47d --- /dev/null +++ b/test/rules/no-object-literal-type-assertion/allow-arguments/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-object-literal-type-assertion": [true, { "allow-arguments": true }] + } +} diff --git a/test/rules/no-object-literal-type-assertion/test.ts.lint b/test/rules/no-object-literal-type-assertion/default/test.ts.lint similarity index 81% rename from test/rules/no-object-literal-type-assertion/test.ts.lint rename to test/rules/no-object-literal-type-assertion/default/test.ts.lint index 1ccea8ea1bf..eaebb211673 100644 --- a/test/rules/no-object-literal-type-assertion/test.ts.lint +++ b/test/rules/no-object-literal-type-assertion/default/test.ts.lint @@ -16,4 +16,10 @@ x as T; {} as unknown; {}; +foo({} as T); + ~~~~~~~ [0] + +foo({}); + ~~~~~ [0] + [0]: Type assertion on object literals is forbidden, use a type annotation instead. diff --git a/test/rules/no-object-literal-type-assertion/tslint.json b/test/rules/no-object-literal-type-assertion/default/tslint.json similarity index 100% rename from test/rules/no-object-literal-type-assertion/tslint.json rename to test/rules/no-object-literal-type-assertion/default/tslint.json