"use strict"; /** * @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. */ Object.defineProperty(exports, "__esModule", { value: true }); var tslib_1 = require("tslib"); var tsutils_1 = require("tsutils"); var ts = require("typescript"); var Lint = require("../index"); var Rule = /** @class */ (function (_super) { tslib_1.__extends(Rule, _super); function Rule() { return _super !== null && _super.apply(this, arguments) || this; } Rule.prototype.applyWithProgram = function (sourceFile, program) { return this.applyWithFunction(sourceFile, walk, undefined, program.getTypeChecker()); }; /* tslint:disable:object-literal-sort-keys */ Rule.metadata = { ruleName: "no-unsafe-any", description: (_a = ["\n Warns when using an expression of type 'any' in a dynamic way.\n Uses are only allowed if they would work for `{} | null | undefined`.\n Type casts and tests are allowed.\n Expressions that work on all values (such as `\"\" + x`) are allowed."], _a.raw = ["\n Warns when using an expression of type 'any' in a dynamic way.\n Uses are only allowed if they would work for \\`{} | null | undefined\\`.\n Type casts and tests are allowed.\n Expressions that work on all values (such as \\`\"\" + x\\`) are allowed."], Lint.Utils.dedent(_a)), optionsDescription: "Not configurable.", options: null, optionExamples: [true], type: "functionality", typescriptOnly: true, requiresTypeInfo: true, }; /* tslint:enable:object-literal-sort-keys */ Rule.FAILURE_STRING = "Unsafe use of expression of type 'any'."; return Rule; }(Lint.Rules.TypedRule)); exports.Rule = Rule; function walk(ctx, checker) { if (ctx.sourceFile.isDeclarationFile) { // Not possible in a declaration file. return; } return ts.forEachChild(ctx.sourceFile, cb); /** @param anyOk If true, this node will be allowed to be of type *any*. (But its children might not.) */ function cb(node, anyOk) { switch (node.kind) { case ts.SyntaxKind.ParenthesizedExpression: // Don't warn on a parenthesized expression, warn on its contents. return cb(node.expression, anyOk); case ts.SyntaxKind.Parameter: { var _a = node, type = _a.type, initializer = _a.initializer; // TODO handle destructuring if (initializer !== undefined) { return cb(initializer, /*anyOk*/ type !== undefined && type.kind === ts.SyntaxKind.AnyKeyword); } return; } case ts.SyntaxKind.LabeledStatement: // Ignore label return cb(node.statement); case ts.SyntaxKind.BreakStatement: // Ignore label case ts.SyntaxKind.ContinueStatement: // Ignore types case ts.SyntaxKind.InterfaceDeclaration: case ts.SyntaxKind.TypeAliasDeclaration: case ts.SyntaxKind.QualifiedName: case ts.SyntaxKind.TypePredicate: case ts.SyntaxKind.TypeOfExpression: // Ignore imports case ts.SyntaxKind.ImportEqualsDeclaration: case ts.SyntaxKind.ImportDeclaration: case ts.SyntaxKind.ExportDeclaration: // These show as type "any" if in type position. case ts.SyntaxKind.NumericLiteral: case ts.SyntaxKind.StringLiteral: return; // Recurse through these, but ignore the immediate child because it is allowed to be 'any'. case ts.SyntaxKind.DeleteExpression: case ts.SyntaxKind.ExpressionStatement: case ts.SyntaxKind.TypeAssertionExpression: case ts.SyntaxKind.AsExpression: case ts.SyntaxKind.TemplateSpan: // Allow stringification (works on all values). Note: tagged templates handled differently. case ts.SyntaxKind.ThrowStatement: { var expression = node.expression; return cb(expression, /*anyOk*/ true); } case ts.SyntaxKind.PropertyAssignment: { // Only check RHS. var _b = node, name = _b.name, initializer = _b.initializer; // The LHS will be 'any' if the RHS is, so just handle the RHS. // Still need to check the LHS in case it is a computed key. cb(name, /*anyOk*/ true); cb(initializer); return; } case ts.SyntaxKind.PropertyDeclaration: { var _c = node, name = _c.name, initializer = _c.initializer; if (initializer !== undefined) { return cb(initializer, /*anyOk*/ isNodeAny(name, checker)); } return; } case ts.SyntaxKind.TaggedTemplateExpression: { var _d = node, tag = _d.tag, template = _d.template; cb(tag); if (template.kind === ts.SyntaxKind.TemplateExpression) { for (var _i = 0, _e = template.templateSpans; _i < _e.length; _i++) { var expression = _e[_i].expression; checkContextual(expression); } } // Also check the template expression itself check(); return; } case ts.SyntaxKind.CallExpression: case ts.SyntaxKind.NewExpression: { var _f = node, expression = _f.expression, args = _f.arguments; cb(expression); if (args !== undefined) { for (var _g = 0, args_1 = args; _g < args_1.length; _g++) { var arg = args_1[_g]; checkContextual(arg); } } // Also check the call expression itself check(); return; } case ts.SyntaxKind.PropertyAccessExpression: // Don't warn for right hand side; this is redundant if we warn for the access itself. cb(node.expression); check(); return; case ts.SyntaxKind.VariableDeclaration: return checkVariableDeclaration(node); case ts.SyntaxKind.BinaryExpression: return checkBinaryExpression(node); case ts.SyntaxKind.ReturnStatement: { var expression = node.expression; if (expression !== undefined) { return checkContextual(expression); } return; } case ts.SyntaxKind.SwitchStatement: { var _h = node, expression = _h.expression, clauses = _h.caseBlock.clauses; // Allow `switch (x) {}` where `x` is any cb(expression, /*anyOk*/ true); for (var _j = 0, clauses_1 = clauses; _j < clauses_1.length; _j++) { var clause = clauses_1[_j]; if (clause.kind === ts.SyntaxKind.CaseClause) { // Allow `case x:` where `x` is any cb(clause.expression, /*anyOk*/ true); } for (var _k = 0, _l = clause.statements; _k < _l.length; _k++) { var statement = _l[_k]; cb(statement); } } break; } case ts.SyntaxKind.ModuleDeclaration: { // In `declare global { ... }`, don't mark `global` as unsafe any. var body = node.body; if (body !== undefined) { cb(body); } return; } case ts.SyntaxKind.IfStatement: { var _m = node, expression = _m.expression, thenStatement = _m.thenStatement, elseStatement = _m.elseStatement; cb(expression, true); // allow truthyness check cb(thenStatement); if (elseStatement !== undefined) { cb(elseStatement); } return; } case ts.SyntaxKind.PrefixUnaryExpression: { var _o = node, operator = _o.operator, operand = _o.operand; cb(operand, operator === ts.SyntaxKind.ExclamationToken); // allow falsyness check check(); return; } case ts.SyntaxKind.ForStatement: { var _p = node, initializer = _p.initializer, condition = _p.condition, incrementor = _p.incrementor, statement = _p.statement; if (initializer !== undefined) { cb(initializer); } if (condition !== undefined) { cb(condition, true); } // allow truthyness check if (incrementor !== undefined) { cb(incrementor); } return cb(statement); } case ts.SyntaxKind.DoStatement: case ts.SyntaxKind.WhileStatement: cb(node.statement); return cb(node.expression, true); default: if (!(tsutils_1.isExpression(node) && check())) { return ts.forEachChild(node, cb); } return; } function check() { var isUnsafe = !anyOk && isNodeAny(node, checker); if (isUnsafe) { ctx.addFailureAtNode(node, Rule.FAILURE_STRING); } return isUnsafe; } } /** OK for this value to be 'any' if that's its contextual type. */ function checkContextual(arg) { return cb(arg, /*anyOk*/ isAny(checker.getContextualType(arg))); } // Allow `const x = foo;` and `const x: any = foo`, but not `const x: Foo = foo;`. function checkVariableDeclaration(_a) { var type = _a.type, initializer = _a.initializer; // Always allow the LHS to be `any`. Just don't allow RHS to be `any` when LHS isn't. // TODO: handle destructuring if (initializer !== undefined) { return cb(initializer, /*anyOk*/ type === undefined || type.kind === ts.SyntaxKind.AnyKeyword); } return; } function checkBinaryExpression(node) { var _a = node, left = _a.left, right = _a.right, operatorToken = _a.operatorToken; // Allow equality since all values support equality. if (Lint.getEqualsKind(operatorToken) !== undefined) { return; } switch (operatorToken.kind) { case ts.SyntaxKind.InstanceOfKeyword:// Allow test return cb(right); case ts.SyntaxKind.CommaToken: // Allow `any, any` case ts.SyntaxKind.BarBarToken: // Allow `any || any` case ts.SyntaxKind.AmpersandAmpersandToken:// Allow `any && any` cb(left, /*anyOk*/ true); return cb(right, /*anyOk*/ true); case ts.SyntaxKind.EqualsToken: // Allow assignment if the lhs is also *any*. // TODO: handle destructuring cb(right, /*anyOk*/ isNodeAny(left, checker)); return; case ts.SyntaxKind.PlusToken: // Allow implicit stringification case ts.SyntaxKind.PlusEqualsToken: var anyOk = isStringLike(left, checker) || (isStringLike(right, checker) && operatorToken.kind === ts.SyntaxKind.PlusToken); cb(left, anyOk); return cb(right, anyOk); default: cb(left); return cb(right); } } } function isNodeAny(node, checker) { return isAny(checker.getTypeAtLocation(node)); } function isStringLike(expr, checker) { return Lint.isTypeFlagSet(checker.getTypeAtLocation(expr), ts.TypeFlags.StringLike); } function isAny(type) { return type !== undefined && Lint.isTypeFlagSet(type, ts.TypeFlags.Any); } var _a;