Skip to content
This repository has been archived by the owner on Mar 25, 2021. It is now read-only.

[no-object-literal-type-assertion] New option: "allow-arguments" #4521

50 changes: 50 additions & 0 deletions 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 = <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"] }
`,
pass: Lint.Utils.dedent`
bar({} as Foo)
`,
},
];
37 changes: 31 additions & 6 deletions src/rules/noObjectLiteralTypeAssertionRule.ts
Expand Up @@ -24,6 +24,14 @@ import * as ts from "typescript";

import * as Lint from "../index";

import { codeExamples } from "./code-examples/noObjectLiteralTypeAssertion.examples";

const OPTION_ALLOW_ARGUMENTS = "allow-arguments";

interface Options {
allowArguments: boolean;
}

export class Rule extends Lint.Rules.AbstractRule {
/* tslint:disable:object-literal-sort-keys */
public static metadata: Lint.IRuleMetadata = {
Expand All @@ -37,23 +45,35 @@ 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 argument may be optionally provided:

* \`${OPTION_ALLOW_ARGUMENTS}\` allows type assertions to be used on object literals inside call expressions.`,
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
options: {
type: "array",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need to wrap the object in an array, you can just bring the object out as the top level options value

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! Thank you.

items: {
type: "string",
enum: [OPTION_ALLOW_ARGUMENTS],
},
},
optionExamples: [true, [true, OPTION_ALLOW_ARGUMENTS]],
type: "functionality",
typescriptOnly: true,
codeExamples,
};
/* tslint:enable:object-literal-sort-keys */

public static FAILURE_STRING =
"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, {
allowArguments: this.ruleArguments.indexOf(OPTION_ALLOW_ARGUMENTS) !== -1,
});
}
}

function walk(ctx: Lint.WalkContext<void>): void {
function walk(ctx: Lint.WalkContext<Options>): void {
return ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void {
if (
isAssertionExpression(node) &&
Expand All @@ -66,10 +86,15 @@ function walk(ctx: Lint.WalkContext<void>): void {
isParenthesizedExpression(node.expression)
? node.expression.expression
: node.expression,
)
) &&
!(ctx.options.allowArguments && isArgument(node))
) {
ctx.addFailureAtNode(node, Rule.FAILURE_STRING);
}
return ts.forEachChild(node, cb);
});
}

function isArgument(node: ts.Node): boolean {
return ts.isCallLikeExpression(node.parent);
}
@@ -0,0 +1,12 @@
// Allow object literals to be asserted when used as arguments.
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
const pick = <T, K extends keyof T>(source: T, configuration: Partial<Record<keyof T, boolean>>): Pick<T, K> =>
(Object.keys(source) as K[])
.reduce(
(cumulus, property) =>
(configuration[property] === true)
? Object.assign(cumulus, {
[property]: source[property],
})
: cumulus,
{} as Pick<T, K>,
);
@@ -0,0 +1,5 @@
{
"rules": {
"no-object-literal-type-assertion": [true, "allow-arguments"]
}
}
32 changes: 32 additions & 0 deletions test/rules/no-object-literal-type-assertion/default/test.ts.lint
@@ -0,0 +1,32 @@
<T> ({});
~~~~~~~~ [0]

({}) as T;
~~~~~~~~~ [0]

<T> x;

x as T;

// Allow cast to 'any'
{} as any;
<any> {};

// Allow cast to 'unknown'
{} as unknown;
<unknown> {};

const pick = <T, K extends keyof T>(source: T, configuration: Partial<Record<keyof T, boolean>>): Pick<T, K> =>
(Object.keys(source) as K[])
.reduce(
(cumulus, property) =>
(configuration[property] === true)
? Object.assign(cumulus, {
[property]: source[property],
})
: cumulus,
{} as Pick<T, K>,
~~~~~~~~~~~~~~~~ [0]
);

[0]: Type assertion on object literals is forbidden, use a type annotation instead.
19 changes: 0 additions & 19 deletions test/rules/no-object-literal-type-assertion/test.ts.lint

This file was deleted.