2017-05-28 00:38:50 +02:00
"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" ) ;
2017-08-14 05:01:11 +02:00
var tsutils _1 = require ( "tsutils" ) ;
2017-05-28 00:38:50 +02:00
var ts = require ( "typescript" ) ;
var Lint = require ( "../index" ) ;
2017-10-14 18:40:54 +02:00
var Rule = /** @class */ ( function ( _super ) {
2017-05-28 00:38:50 +02:00
tslib _1 . _ _extends ( Rule , _super ) ;
function Rule ( ) {
return _super !== null && _super . apply ( this , arguments ) || this ;
}
Rule . prototype . applyWithProgram = function ( sourceFile , program ) {
2017-10-14 18:40:54 +02:00
return this . applyWithFunction ( sourceFile , walk , undefined , program . getTypeChecker ( ) ) ;
2017-05-28 00:38:50 +02:00
} ;
2017-08-14 05:01:11 +02:00
/* 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'." ;
2017-05-28 00:38:50 +02:00
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 ;
2017-08-14 05:01:11 +02:00
// TODO handle destructuring
2017-05-28 00:38:50 +02:00
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
2017-08-14 05:01:11 +02:00
case ts . SyntaxKind . ContinueStatement :
2017-05-28 00:38:50 +02:00
// 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 ;
}
2017-08-14 05:01:11 +02:00
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 ) ;
2017-05-28 00:38:50 +02:00
default :
2017-08-14 05:01:11 +02:00
if ( ! ( tsutils _1 . isExpression ( node ) && check ( ) ) ) {
2017-05-28 00:38:50 +02:00
return ts . forEachChild ( node , cb ) ;
}
return ;
}
function check ( ) {
2017-08-14 05:01:11 +02:00
var isUnsafe = ! anyOk && isNodeAny ( node , checker ) ;
2017-05-28 00:38:50 +02:00
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 ) {
2017-08-14 05:01:11 +02:00
case ts . SyntaxKind . InstanceOfKeyword : // Allow test
2017-05-28 00:38:50 +02:00
return cb ( right ) ;
2017-08-14 05:01:11 +02:00
case ts . SyntaxKind . CommaToken : // Allow `any, any`
case ts . SyntaxKind . BarBarToken : // Allow `any || any`
case ts . SyntaxKind . AmpersandAmpersandToken : // Allow `any && any`
2017-05-28 00:38:50 +02:00
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 ;