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

Add no-unsafe-any rule #2047

Merged
merged 4 commits into from
Jan 19, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 15 additions & 2 deletions docs/_data/rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,18 @@
"type": "maintainability",
"typescriptOnly": false
},
{
"ruleName": "strict-boolean-expressions",
"description": "\nWarns when using an expression of type 'any' in an unsafe way.\nType casts and tests are allowed.\nExpressions that work on all values (such as '\"\" + x') are allowed.",
"optionsDescription": "Not configurable.",
"options": null,
"optionExamples": [
"true"
],
"type": "functionality",
"typescriptOnly": true,
"requiresTypeInfo": true
},
{
"ruleName": "no-unsafe-finally",
"description": "\nDisallows control flow statements, such as `return`, `continue`,\n`break` and `throws` in finally blocks.",
Expand Down Expand Up @@ -1720,7 +1732,7 @@
"ruleName": "whitespace",
"description": "Enforces whitespace style conventions.",
"rationale": "Helps maintain a readable, consistent style in your codebase.",
"optionsDescription": "\nSeven arguments may be optionally provided:\n\n* `\"check-branch\"` checks branching statements (`if`/`else`/`for`/`while`) are followed by whitespace.\n* `\"check-decl\"`checks that variable declarations have whitespace around the equals token.\n* `\"check-operator\"` checks for whitespace around operator tokens.\n* `\"check-module\"` checks for whitespace in import & export statements.\n* `\"check-separator\"` checks for whitespace after separator tokens (`,`/`;`).\n* `\"check-type\"` checks for whitespace before a variable type specification.\n* `\"check-typecast\"` checks for whitespace between a typecast and its target.",
"optionsDescription": "\nSeven arguments may be optionally provided:\n\n* `\"check-branch\"` checks branching statements (`if`/`else`/`for`/`while`) are followed by whitespace.\n* `\"check-decl\"`checks that variable declarations have whitespace around the equals token.\n* `\"check-operator\"` checks for whitespace around operator tokens.\n* `\"check-module\"` checks for whitespace in import & export statements.\n* `\"check-separator\"` checks for whitespace after separator tokens (`,`/`;`).\n* `\"check-type\"` checks for whitespace before a variable type specification.\n* `\"check-typecast\"` checks for whitespace between a typecast and its target.\n* `\"check-preblock\"` checks for whitespace before the opening brace of a block",
"options": {
"type": "array",
"items": {
Expand All @@ -1732,7 +1744,8 @@
"check-module",
"check-separator",
"check-type",
"check-typecast"
"check-typecast",
"check-preblock"
]
},
"minLength": 0,
Expand Down
5 changes: 4 additions & 1 deletion docs/rules/whitespace/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* `"check-separator"` checks for whitespace after separator tokens (`,`/`;`).
* `"check-type"` checks for whitespace before a variable type specification.
* `"check-typecast"` checks for whitespace between a typecast and its target.
* `"check-preblock"` checks for whitespace before the opening brace of a block
options:
type: array
items:
Expand All @@ -25,6 +26,7 @@
- check-separator
- check-type
- check-typecast
- check-preblock
minLength: 0
maxLength: 7
optionExamples:
Expand All @@ -45,7 +47,8 @@
"check-module",
"check-separator",
"check-type",
"check-typecast"
"check-typecast",
"check-preblock"
]
},
"minLength": 0,
Expand Down
119 changes: 119 additions & 0 deletions src/rules/noUnsafeAnyRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* @license
* Copyright 2017 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 ts from "typescript";
import * as Lint from "../index";

export class Rule extends Lint.Rules.TypedRule {
/* tslint:disable:object-literal-sort-keys */
public static metadata: Lint.IRuleMetadata = {
ruleName: "strict-boolean-expressions",
Copy link
Contributor

Choose a reason for hiding this comment

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

Rule name is misspelled. (Not a code review; I'm just here to read the description.)

description: Lint.Utils.dedent`
Warns when using an expression of type 'any' in an unsafe way.
Type casts and tests are allowed.
Expressions that work on all values (such as '"" + x') are allowed.`,
optionsDescription: "Not configurable.",
options: null,
optionExamples: ["true"],
type: "functionality",
typescriptOnly: true,
requiresTypeInfo: true,
};
/* tslint:enable:object-literal-sort-keys */

public static FAILURE_STRING = "Unsafe use of expression of type 'any'.";

public applyWithProgram(srcFile: ts.SourceFile, langSvc: ts.LanguageService): Lint.RuleFailure[] {
return this.applyWithWalker(new Walker(srcFile, this.getOptions(), langSvc.getProgram()));
}
}

class Walker extends Lint.ProgramAwareRuleWalker {
public visitNode(node: ts.Node) {
if (ts.isExpression(node) && isAny(this.getType(node)) && !this.isAllowedLocation(node)) {
this.addFailureAtNode(node, Rule.FAILURE_STRING);
} else {
super.visitNode(node);
}
}

private isAllowedLocation(node: ts.Expression): boolean {
const parent = node.parent!;
switch (parent.kind) {
case ts.SyntaxKind.ExpressionStatement: // Allow unused expression
case ts.SyntaxKind.Parameter: // Allow to declare a parameter of type 'any'
case ts.SyntaxKind.TypeOfExpression: // Allow test
case ts.SyntaxKind.TemplateSpan: // Allow stringification (works on all values)
// Allow casts
case ts.SyntaxKind.TypeAssertionExpression:
case ts.SyntaxKind.AsExpression:
return true;

// OK to pass 'any' to a function that takes 'any' as its argument
case ts.SyntaxKind.CallExpression:
case ts.SyntaxKind.NewExpression:
return isAny(this.getTypeChecker().getContextualType(node));

case ts.SyntaxKind.BinaryExpression:
const { left, right, operatorToken } = parent as ts.BinaryExpression;
switch (operatorToken.kind) {
case ts.SyntaxKind.InstanceOfKeyword: // Allow test
// Allow equality since all values support equality.
case ts.SyntaxKind.EqualsEqualsToken:
case ts.SyntaxKind.EqualsEqualsEqualsToken:
case ts.SyntaxKind.ExclamationEqualsToken:
case ts.SyntaxKind.ExclamationEqualsEqualsToken:
return true;
case ts.SyntaxKind.PlusToken: // Allow stringification
return node === left ? this.isStringLike(right) : this.isStringLike(left);
case ts.SyntaxKind.PlusEqualsToken: // Allow stringification in `str += x;`, but not `x += str;`.
return node === right && this.isStringLike(left);
default:
return false;
}

// Allow `const x = foo;`, but not `const x: Foo = foo;`.
case ts.SyntaxKind.VariableDeclaration:
return Lint.hasModifier(parent.parent!.parent!.modifiers, ts.SyntaxKind.DeclareKeyword) ||
(parent as ts.VariableDeclaration).type === undefined;

case ts.SyntaxKind.PropertyAccessExpression:
// Don't warn for right hand side; this is redundant if we warn for the left-hand side.
return (parent as ts.PropertyAccessExpression).name === node;

default:
return false;
}
}

private isStringLike(node: ts.Expression) {
return Lint.isTypeFlagSet(this.getType(node), ts.TypeFlags.StringLike);
}

private getType(node: ts.Expression): ts.Type {
return this.getTypeChecker().getTypeAtLocation(node);
}
}

function isAny(type: ts.Type | undefined): boolean {
return type !== undefined && Lint.isTypeFlagSet(type, ts.TypeFlags.Any);
}

// This is marked @internal, but we need it!
declare module "typescript" {
export function isExpression(node: ts.Node): node is ts.Expression;
}
54 changes: 54 additions & 0 deletions test/rules/no-unsafe-any/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
declare const x: any;

function f(x: any) {
x.foo;
~ [0]
x(0);
~ [0]
x``;
~ [0]
x + 3;
~ [0]

// OK to pass it to a function that takes `any`
g(x);
// Not OK to pass to any other function.
[].map(x);
~ [0]

// Same for constructors
new X(x);
new Y(x);
~ [0]
}

class X { constructor(x: any) {} }
class Y { constructor(x: number) {} }

function g(x: any): string {
if (x === undefined) {
return "undefined";
}
if (typeof x === "string") {
return `string: ${x}`;
}
if (x instanceof Array) {
// Once it's been tested, OK to use it as an array.
return `Array, length ${x.length}`;
}
if (Math.random() > 0.5) {
// Allow explicit cast
return (<string> x).toLowerCase() + (x as string).toUpperCase();
}

"" + x;
x + "";
let s = "";
s += x;
x += s;
~ [0]

return `other: ${x}`;
}

[0]: Unsafe use of expression of type 'any'.
8 changes: 8 additions & 0 deletions test/rules/no-unsafe-any/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"linterOptions": {
"typeCheck": true
},
"rules": {
"no-unsafe-any": true
}
}