4 * Copyright 2017 Palantir Technologies, Inc.
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 Object.defineProperty(exports, "__esModule", { value: true });
19 var tslib_1 = require("tslib");
20 var tsutils_1 = require("tsutils");
21 var ts = require("typescript");
22 var Lint = require("../index");
23 var utils_1 = require("../utils");
24 var Rule = /** @class */ (function (_super) {
25 tslib_1.__extends(Rule, _super);
27 return _super !== null && _super.apply(this, arguments) || this;
29 Rule.prototype.applyWithProgram = function (sourceFile, program) {
30 return this.applyWithWalker(new NoUnsafeAnyWalker(sourceFile, this.ruleName, program.getTypeChecker()));
32 /* tslint:disable:object-literal-sort-keys */
34 ruleName: "no-unsafe-any",
35 description: Lint.Utils.dedent(templateObject_1 || (templateObject_1 = tslib_1.__makeTemplateObject(["\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 Downcasting to unknown is always safe.\n Type casts and tests are allowed.\n Expressions that work on all values (such as `\"\" + x`) are allowed."], ["\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 Downcasting to unknown is always safe.\n Type casts and tests are allowed.\n Expressions that work on all values (such as \\`\"\" + x\\`) are allowed."]))),
36 optionsDescription: "Not configurable.",
38 optionExamples: [true],
39 rationale: Lint.Utils.dedent(templateObject_2 || (templateObject_2 = tslib_1.__makeTemplateObject(["\n If you're dealing with data of unknown or \"any\" types, you shouldn't be accessing members of it.\n Either add type annotations for properties that may exist or change the data type to the empty object type `{}`.\n\n Alternately, if you're creating storage or handling for consistent but unknown types, such as in data structures\n or serialization, use `<T>` template types for generic type handling.\n\n Also see the `no-any` rule.\n "], ["\n If you're dealing with data of unknown or \"any\" types, you shouldn't be accessing members of it.\n Either add type annotations for properties that may exist or change the data type to the empty object type \\`{}\\`.\n\n Alternately, if you're creating storage or handling for consistent but unknown types, such as in data structures\n or serialization, use \\`<T>\\` template types for generic type handling.\n\n Also see the \\`no-any\\` rule.\n "]))),
40 type: "functionality",
42 requiresTypeInfo: true,
44 /* tslint:enable:object-literal-sort-keys */
45 Rule.FAILURE_STRING = "Unsafe use of expression of type 'any'.";
47 }(Lint.Rules.TypedRule));
49 var NoUnsafeAnyWalker = /** @class */ (function (_super) {
50 tslib_1.__extends(NoUnsafeAnyWalker, _super);
51 function NoUnsafeAnyWalker(sourceFile, ruleName, checker) {
52 var _this = _super.call(this, sourceFile, ruleName, undefined) || this;
53 _this.checker = checker;
54 /** Wraps `visitNode` with the correct `this` binding and discards the return value to prevent `forEachChild` from returning early */
55 _this.visitNodeCallback = function (node) { return void _this.visitNode(node); };
58 NoUnsafeAnyWalker.prototype.walk = function (sourceFile) {
59 if (sourceFile.isDeclarationFile) {
60 return; // Not possible in a declaration file.
62 sourceFile.statements.forEach(this.visitNodeCallback);
64 NoUnsafeAnyWalker.prototype.visitNode = function (node, anyOk) {
66 case ts.SyntaxKind.ParenthesizedExpression:
67 // Don't warn on a parenthesized expression, warn on its contents.
68 return this.visitNode(node.expression, anyOk);
69 case ts.SyntaxKind.LabeledStatement:
71 return this.visitNode(node.statement);
73 case ts.SyntaxKind.BreakStatement:
74 case ts.SyntaxKind.ContinueStatement:
76 case ts.SyntaxKind.InterfaceDeclaration:
77 case ts.SyntaxKind.TypeAliasDeclaration:
78 case ts.SyntaxKind.TypeParameter:
79 case ts.SyntaxKind.IndexSignature:
81 case ts.SyntaxKind.ImportEqualsDeclaration:
82 case ts.SyntaxKind.ImportDeclaration:
83 case ts.SyntaxKind.ExportDeclaration:
84 case ts.SyntaxKind.ExportAssignment:
86 case ts.SyntaxKind.ThisKeyword:
87 case ts.SyntaxKind.Identifier:
88 return anyOk ? false : this.check(node);
89 // Recurse through these, but ignore the immediate child because it is allowed to be 'any'.
90 case ts.SyntaxKind.DeleteExpression:
91 case ts.SyntaxKind.ExpressionStatement:
92 case ts.SyntaxKind.TypeAssertionExpression:
93 case ts.SyntaxKind.AsExpression:
94 case ts.SyntaxKind.TemplateSpan: // Allow stringification (works on all values). Note: tagged templates handled differently.
95 case ts.SyntaxKind.TypeOfExpression:
96 case ts.SyntaxKind.VoidExpression:
97 return this.visitNode(node.expression, true);
98 case ts.SyntaxKind.ThrowStatement: {
99 var expression = node.expression;
100 return expression !== undefined ? this.visitNode(expression, true) : false;
102 case ts.SyntaxKind.PropertyAssignment: {
103 var _a = node, name = _a.name, initializer = _a.initializer;
104 this.visitNode(name, /*anyOk*/ true);
105 if (tsutils_1.isReassignmentTarget(node.parent)) {
106 return this.visitNode(initializer, true);
108 return this.checkContextualType(initializer, true);
110 case ts.SyntaxKind.ShorthandPropertyAssignment: {
111 var _b = node, name = _b.name, objectAssignmentInitializer = _b.objectAssignmentInitializer;
112 if (objectAssignmentInitializer !== undefined) {
113 return this.checkContextualType(objectAssignmentInitializer);
115 return this.checkContextualType(name, true);
117 case ts.SyntaxKind.PropertyDeclaration: {
118 var _c = node, name = _c.name, initializer = _c.initializer;
119 this.visitNode(name, true);
120 return (initializer !== undefined &&
121 this.visitNode(initializer, isPropertyAnyOrUnknown(node, this.checker)));
123 case ts.SyntaxKind.SpreadAssignment:
124 return this.visitNode(node.expression,
125 // allow any in object spread, but not in object rest
126 !tsutils_1.isReassignmentTarget(node.parent));
127 case ts.SyntaxKind.ComputedPropertyName:
128 return this.visitNode(node.expression, true);
129 case ts.SyntaxKind.TaggedTemplateExpression: {
130 var _d = node, tag = _d.tag, template = _d.template;
131 if (template.kind === ts.SyntaxKind.TemplateExpression) {
132 for (var _i = 0, _e = template.templateSpans; _i < _e.length; _i++) {
133 var expression = _e[_i].expression;
134 this.checkContextualType(expression);
137 // Also check the template expression itself
138 if (this.visitNode(tag)) {
141 return anyOk ? false : this.check(node);
143 case ts.SyntaxKind.CallExpression:
144 case ts.SyntaxKind.NewExpression: {
145 var _f = node, expression = _f.expression, args = _f.arguments;
146 if (args !== undefined) {
147 for (var _g = 0, args_1 = args; _g < args_1.length; _g++) {
148 var arg = args_1[_g];
149 this.checkContextualType(arg);
152 if (this.visitNode(expression)) {
155 // Also check the call expression itself
156 return anyOk ? false : this.check(node);
158 case ts.SyntaxKind.PropertyAccessExpression:
159 // Don't warn for right hand side; this is redundant if we warn for the access itself.
160 if (this.visitNode(node.expression)) {
163 return anyOk ? false : this.check(node);
164 case ts.SyntaxKind.ElementAccessExpression: {
165 var _h = node, expression = _h.expression, argumentExpression = _h.argumentExpression;
166 if (argumentExpression !== undefined) {
167 this.visitNode(argumentExpression, true);
169 if (this.visitNode(expression)) {
172 return anyOk ? false : this.check(node);
174 case ts.SyntaxKind.ReturnStatement: {
175 var expression = node.expression;
176 return expression !== undefined && this.checkContextualType(expression, true);
178 case ts.SyntaxKind.SwitchStatement: {
179 var _j = node, expression = _j.expression, clauses = _j.caseBlock.clauses;
180 // Allow `switch (x) {}` where `x` is any
181 this.visitNode(expression, /*anyOk*/ true);
182 for (var _k = 0, clauses_1 = clauses; _k < clauses_1.length; _k++) {
183 var clause = clauses_1[_k];
184 if (clause.kind === ts.SyntaxKind.CaseClause) {
185 // Allow `case x:` where `x` is any
186 this.visitNode(clause.expression, /*anyOk*/ true);
188 for (var _l = 0, _m = clause.statements; _l < _m.length; _l++) {
189 var statement = _m[_l];
190 this.visitNode(statement);
195 case ts.SyntaxKind.ModuleDeclaration: {
196 // In `declare global { ... }`, don't mark `global` as unsafe any.
197 var body = node.body;
198 return body !== undefined && this.visitNode(body);
200 case ts.SyntaxKind.IfStatement: {
201 var _o = node, expression = _o.expression, thenStatement = _o.thenStatement, elseStatement = _o.elseStatement;
202 this.visitNode(expression, true); // allow truthyness check
203 this.visitNode(thenStatement);
204 return elseStatement !== undefined && this.visitNode(elseStatement);
206 case ts.SyntaxKind.PrefixUnaryExpression: {
207 var _p = node, operator = _p.operator, operand = _p.operand;
208 this.visitNode(operand, operator === ts.SyntaxKind.ExclamationToken); // allow falsyness check
211 case ts.SyntaxKind.ForStatement: {
212 var _q = node, initializer = _q.initializer, condition = _q.condition, incrementor = _q.incrementor, statement = _q.statement;
213 if (initializer !== undefined) {
214 this.visitNode(initializer, true);
216 if (condition !== undefined) {
217 this.visitNode(condition, true);
218 } // allow truthyness check
219 if (incrementor !== undefined) {
220 this.visitNode(incrementor, true);
222 return this.visitNode(statement);
224 case ts.SyntaxKind.DoStatement:
225 case ts.SyntaxKind.WhileStatement:
226 this.visitNode(node.expression, true);
227 return this.visitNode(node.statement);
228 case ts.SyntaxKind.ConditionalExpression: {
229 var _r = node, condition = _r.condition, whenTrue = _r.whenTrue, whenFalse = _r.whenFalse;
230 this.visitNode(condition, true);
231 var left = this.visitNode(whenTrue, anyOk);
232 return this.visitNode(whenFalse, anyOk) || left;
234 case ts.SyntaxKind.VariableDeclaration:
235 case ts.SyntaxKind.Parameter:
236 return this.checkVariableOrParameterDeclaration(node);
237 case ts.SyntaxKind.BinaryExpression:
238 return this.checkBinaryExpression(node, anyOk);
239 case ts.SyntaxKind.AwaitExpression:
240 this.visitNode(node.expression);
241 return anyOk ? false : this.check(node);
242 case ts.SyntaxKind.YieldExpression:
243 return this.checkYieldExpression(node, anyOk);
244 case ts.SyntaxKind.ClassExpression:
245 case ts.SyntaxKind.ClassDeclaration:
246 this.checkClassLikeDeclaration(node);
248 case ts.SyntaxKind.ArrayLiteralExpression: {
249 for (var _s = 0, _t = node.elements; _s < _t.length; _s++) {
250 var element = _t[_s];
251 this.checkContextualType(element, true);
255 case ts.SyntaxKind.JsxExpression:
256 return (node.expression !== undefined &&
257 this.checkContextualType(node.expression));
259 if (tsutils_1.isTypeNodeKind(node.kind) || tsutils_1.isTokenKind(node.kind)) {
262 return ts.forEachChild(node, this.visitNodeCallback);
264 NoUnsafeAnyWalker.prototype.check = function (node) {
265 if (!isNodeAny(node, this.checker)) {
268 this.addFailureAtNode(node, Rule.FAILURE_STRING);
271 NoUnsafeAnyWalker.prototype.checkContextualType = function (node, allowIfNoContextualType) {
272 var type = this.checker.getContextualType(node);
273 var anyOk = (type === undefined && allowIfNoContextualType) || isAny(type, true);
274 return this.visitNode(node, anyOk);
276 // Allow `const x = foo;` and `const x: any = foo`, but not `const x: Foo = foo;`.
277 NoUnsafeAnyWalker.prototype.checkVariableOrParameterDeclaration = function (_a) {
278 var name = _a.name, type = _a.type, initializer = _a.initializer;
279 this.checkBindingName(name);
280 // Always allow the LHS to be `any`. Just don't allow RHS to be `any` when LHS isn't `any` or `unknown`.
281 var anyOk = (name.kind === ts.SyntaxKind.Identifier &&
282 (type === undefined ||
283 type.kind === ts.SyntaxKind.AnyKeyword ||
284 type.kind === ts.SyntaxKind.UnknownKeyword)) ||
285 (type !== undefined && type.kind === ts.SyntaxKind.AnyKeyword) ||
286 (type !== undefined && type.kind === ts.SyntaxKind.UnknownKeyword);
287 return initializer !== undefined && this.visitNode(initializer, anyOk);
289 NoUnsafeAnyWalker.prototype.checkBinaryExpression = function (node, anyOk) {
290 var allowAnyLeft = false;
291 var allowAnyRight = false;
292 switch (node.operatorToken.kind) {
293 case ts.SyntaxKind.ExclamationEqualsEqualsToken:
294 case ts.SyntaxKind.ExclamationEqualsToken:
295 case ts.SyntaxKind.EqualsEqualsEqualsToken:
296 case ts.SyntaxKind.EqualsEqualsToken:
297 case ts.SyntaxKind.CommaToken: // Allow `any, any`
298 case ts.SyntaxKind.BarBarToken: // Allow `any || any`
299 case ts.SyntaxKind.AmpersandAmpersandToken: // Allow `any && any`
300 allowAnyLeft = allowAnyRight = true;
302 case ts.SyntaxKind.InstanceOfKeyword: // Allow test
305 case ts.SyntaxKind.EqualsToken:
306 // Allow assignment if the lhs is also *any*.
308 allowAnyRight = isNodeAny(node.left, this.checker, true);
310 case ts.SyntaxKind.PlusToken: // Allow implicit stringification
311 case ts.SyntaxKind.PlusEqualsToken:
312 allowAnyLeft = allowAnyRight =
313 isStringLike(node.left, this.checker) ||
314 (isStringLike(node.right, this.checker) &&
315 node.operatorToken.kind === ts.SyntaxKind.PlusToken);
317 this.visitNode(node.left, allowAnyLeft);
318 this.visitNode(node.right, allowAnyRight);
319 return anyOk ? false : this.check(node);
321 NoUnsafeAnyWalker.prototype.checkYieldExpression = function (node, anyOk) {
322 if (node.expression !== undefined) {
323 this.checkContextualType(node.expression, true);
328 this.addFailureAtNode(node, Rule.FAILURE_STRING);
331 NoUnsafeAnyWalker.prototype.checkClassLikeDeclaration = function (node) {
332 if (node.decorators !== undefined) {
333 node.decorators.forEach(this.visitNodeCallback);
335 if (node.heritageClauses !== undefined) {
336 node.heritageClauses.forEach(this.visitNodeCallback);
338 return node.members.forEach(this.visitNodeCallback);
340 NoUnsafeAnyWalker.prototype.checkBindingName = function (node) {
341 if (node.kind !== ts.SyntaxKind.Identifier) {
342 if (isNodeAny(node, this.checker)) {
343 this.addFailureAtNode(node, Rule.FAILURE_STRING);
345 for (var _i = 0, _a = node.elements; _i < _a.length; _i++) {
346 var element = _a[_i];
347 if (element.kind !== ts.SyntaxKind.OmittedExpression) {
348 if (element.propertyName !== undefined &&
349 element.propertyName.kind === ts.SyntaxKind.ComputedPropertyName) {
350 this.visitNode(element.propertyName.expression);
352 this.checkBindingName(element.name);
353 if (element.initializer !== undefined) {
354 this.checkContextualType(element.initializer);
360 return NoUnsafeAnyWalker;
361 }(Lint.AbstractWalker));
362 /** Check if property has no type annotation in this class and the base class */
363 function isPropertyAnyOrUnknown(node, checker) {
364 if (!isNodeAny(node.name, checker, true) ||
365 node.name.kind === ts.SyntaxKind.ComputedPropertyName) {
368 for (var _i = 0, _a = checker.getBaseTypes(checker.getTypeAtLocation(node.parent)); _i < _a.length; _i++) {
370 var prop = base.getProperty(node.name.text);
371 if (prop !== undefined && prop.declarations !== undefined) {
372 return isAny(checker.getTypeOfSymbolAtLocation(prop, prop.declarations[0]), true);
378 * @param orUnknown If true, this function will also return true when the node is unknown.
380 function isNodeAny(node, checker, orUnknown) {
381 if (orUnknown === void 0) { orUnknown = false; }
382 var symbol = checker.getSymbolAtLocation(node);
383 if (symbol !== undefined && tsutils_1.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias)) {
384 symbol = checker.getAliasedSymbol(symbol);
386 if (symbol !== undefined) {
387 // NamespaceModule is a type-only namespace without runtime value, its type is 'any' when used as 'ns.Type' -> avoid error
388 if (tsutils_1.isSymbolFlagSet(symbol, ts.SymbolFlags.NamespaceModule)) {
391 if (tsutils_1.isSymbolFlagSet(symbol, ts.SymbolFlags.Type)) {
392 return isAny(checker.getDeclaredTypeOfSymbol(symbol), orUnknown);
395 // Lowercase JSX elements are assumed to be allowed by design
396 if (isJsxNativeElement(node)) {
399 return isAny(checker.getTypeAtLocation(node), orUnknown);
401 var jsxElementTypes = new Set([
402 ts.SyntaxKind.JsxClosingElement,
403 ts.SyntaxKind.JsxOpeningElement,
404 ts.SyntaxKind.JsxSelfClosingElement,
406 function isJsxNativeElement(node) {
407 if (!tsutils_1.isIdentifier(node) || node.parent === undefined) {
410 // TypeScript <=2.1 incorrectly parses JSX fragments
411 if (node.text === "") {
414 return jsxElementTypes.has(node.parent.kind) && utils_1.isLowerCase(node.text[0]);
416 function isStringLike(expr, checker) {
417 return tsutils_1.isTypeFlagSet(checker.getTypeAtLocation(expr), ts.TypeFlags.StringLike);
419 function isAny(type, orUnknown) {
420 if (orUnknown === void 0) { orUnknown = false; }
421 return (type !== undefined &&
422 (tsutils_1.isTypeFlagSet(type, ts.TypeFlags.Any) ||
423 (orUnknown && tsutils_1.isTypeFlagSet(type, ts.TypeFlags.Unknown))));
425 var templateObject_1, templateObject_2;