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-12-10 21:51:33 +01:00
return this . applyWithWalker ( new NoUnsafeAnyWalker ( sourceFile , this . ruleName , 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 ;
2017-12-10 21:51:33 +01:00
var NoUnsafeAnyWalker = /** @class */ ( function ( _super ) {
tslib _1 . _ _extends ( NoUnsafeAnyWalker , _super ) ;
function NoUnsafeAnyWalker ( sourceFile , ruleName , checker ) {
var _this = _super . call ( this , sourceFile , ruleName , undefined ) || this ;
_this . checker = checker ;
/** Wraps `visitNode` with the correct `this` binding and discards the return value to prevent `forEachChild` from returning early */
_this . visitNodeCallback = function ( node ) { return void _this . visitNode ( node ) ; } ;
return _this ;
2017-05-28 00:38:50 +02:00
}
2017-12-10 21:51:33 +01:00
NoUnsafeAnyWalker . prototype . walk = function ( sourceFile ) {
if ( sourceFile . isDeclarationFile ) {
return ; // Not possible in a declaration file.
}
sourceFile . statements . forEach ( this . visitNodeCallback ) ;
} ;
NoUnsafeAnyWalker . prototype . visitNode = function ( node , anyOk ) {
2017-05-28 00:38:50 +02:00
switch ( node . kind ) {
case ts . SyntaxKind . ParenthesizedExpression :
// Don't warn on a parenthesized expression, warn on its contents.
2017-12-10 21:51:33 +01:00
return this . visitNode ( node . expression , anyOk ) ;
2017-05-28 00:38:50 +02:00
case ts . SyntaxKind . LabeledStatement :
// Ignore label
2017-12-10 21:51:33 +01:00
return this . visitNode ( node . statement ) ;
// ignore labels
case ts . SyntaxKind . BreakStatement :
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 :
2017-12-10 21:51:33 +01:00
case ts . SyntaxKind . TypeParameter :
case ts . SyntaxKind . IndexSignature :
2017-05-28 00:38:50 +02:00
// Ignore imports
case ts . SyntaxKind . ImportEqualsDeclaration :
case ts . SyntaxKind . ImportDeclaration :
case ts . SyntaxKind . ExportDeclaration :
2017-12-10 21:51:33 +01:00
case ts . SyntaxKind . ExportAssignment :
return false ;
case ts . SyntaxKind . ThisKeyword :
case ts . SyntaxKind . Identifier :
return anyOk ? false : this . check ( node ) ;
2017-05-28 00:38:50 +02:00
// 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.
2017-12-10 21:51:33 +01:00
case ts . SyntaxKind . ThrowStatement :
case ts . SyntaxKind . TypeOfExpression :
case ts . SyntaxKind . VoidExpression :
return this . visitNode ( node . expression , true ) ;
2017-05-28 00:38:50 +02:00
case ts . SyntaxKind . PropertyAssignment : {
2017-12-10 21:51:33 +01:00
var _a = node , name = _a . name , initializer = _a . initializer ;
this . visitNode ( name , /*anyOk*/ true ) ;
if ( tsutils _1 . isReassignmentTarget ( node . parent ) ) {
return this . visitNode ( initializer , true ) ;
}
return this . checkContextualType ( initializer , true ) ;
}
case ts . SyntaxKind . ShorthandPropertyAssignment : {
var _b = node , name = _b . name , objectAssignmentInitializer = _b . objectAssignmentInitializer ;
if ( objectAssignmentInitializer !== undefined ) {
return this . checkContextualType ( objectAssignmentInitializer ) ;
}
return this . checkContextualType ( name , true ) ;
2017-05-28 00:38:50 +02:00
}
case ts . SyntaxKind . PropertyDeclaration : {
var _c = node , name = _c . name , initializer = _c . initializer ;
2017-12-10 21:51:33 +01:00
this . visitNode ( name , true ) ;
return initializer !== undefined &&
this . visitNode ( initializer , isPropertyAny ( node , this . checker ) ) ;
2017-05-28 00:38:50 +02:00
}
2017-12-10 21:51:33 +01:00
case ts . SyntaxKind . ComputedPropertyName :
return this . visitNode ( node . expression , true ) ;
2017-05-28 00:38:50 +02:00
case ts . SyntaxKind . TaggedTemplateExpression : {
var _d = node , tag = _d . tag , template = _d . template ;
if ( template . kind === ts . SyntaxKind . TemplateExpression ) {
for ( var _i = 0 , _e = template . templateSpans ; _i < _e . length ; _i ++ ) {
var expression = _e [ _i ] . expression ;
2017-12-10 21:51:33 +01:00
this . checkContextualType ( expression ) ;
2017-05-28 00:38:50 +02:00
}
}
// Also check the template expression itself
2017-12-10 21:51:33 +01:00
if ( this . visitNode ( tag ) ) {
return true ;
}
return anyOk ? false : this . check ( node ) ;
2017-05-28 00:38:50 +02:00
}
case ts . SyntaxKind . CallExpression :
case ts . SyntaxKind . NewExpression : {
var _f = node , expression = _f . expression , args = _f . arguments ;
if ( args !== undefined ) {
for ( var _g = 0 , args _1 = args ; _g < args _1 . length ; _g ++ ) {
var arg = args _1 [ _g ] ;
2017-12-10 21:51:33 +01:00
this . checkContextualType ( arg ) ;
2017-05-28 00:38:50 +02:00
}
}
2017-12-10 21:51:33 +01:00
if ( this . visitNode ( expression ) ) {
return true ;
}
2017-05-28 00:38:50 +02:00
// Also check the call expression itself
2017-12-10 21:51:33 +01:00
return anyOk ? false : this . check ( node ) ;
2017-05-28 00:38:50 +02:00
}
case ts . SyntaxKind . PropertyAccessExpression :
// Don't warn for right hand side; this is redundant if we warn for the access itself.
2017-12-10 21:51:33 +01:00
if ( this . visitNode ( node . expression ) ) {
return true ;
}
return anyOk ? false : this . check ( node ) ;
case ts . SyntaxKind . ElementAccessExpression : {
var _h = node , expression = _h . expression , argumentExpression = _h . argumentExpression ;
if ( argumentExpression !== undefined ) {
this . visitNode ( argumentExpression , true ) ;
}
if ( this . visitNode ( expression ) ) {
return true ;
}
return anyOk ? false : this . check ( node ) ;
}
2017-05-28 00:38:50 +02:00
case ts . SyntaxKind . ReturnStatement : {
var expression = node . expression ;
2017-12-10 21:51:33 +01:00
return expression !== undefined && this . checkContextualType ( expression , true ) ;
2017-05-28 00:38:50 +02:00
}
2017-08-14 05:01:11 +02:00
case ts . SyntaxKind . SwitchStatement : {
2017-12-10 21:51:33 +01:00
var _j = node , expression = _j . expression , clauses = _j . caseBlock . clauses ;
2017-08-14 05:01:11 +02:00
// Allow `switch (x) {}` where `x` is any
2017-12-10 21:51:33 +01:00
this . visitNode ( expression , /*anyOk*/ true ) ;
for ( var _k = 0 , clauses _1 = clauses ; _k < clauses _1 . length ; _k ++ ) {
var clause = clauses _1 [ _k ] ;
2017-08-14 05:01:11 +02:00
if ( clause . kind === ts . SyntaxKind . CaseClause ) {
// Allow `case x:` where `x` is any
2017-12-10 21:51:33 +01:00
this . visitNode ( clause . expression , /*anyOk*/ true ) ;
2017-08-14 05:01:11 +02:00
}
2017-12-10 21:51:33 +01:00
for ( var _l = 0 , _m = clause . statements ; _l < _m . length ; _l ++ ) {
var statement = _m [ _l ] ;
this . visitNode ( statement ) ;
2017-08-14 05:01:11 +02:00
}
}
2017-12-10 21:51:33 +01:00
return false ;
2017-08-14 05:01:11 +02:00
}
case ts . SyntaxKind . ModuleDeclaration : {
// In `declare global { ... }`, don't mark `global` as unsafe any.
var body = node . body ;
2017-12-10 21:51:33 +01:00
return body !== undefined && this . visitNode ( body ) ;
2017-08-14 05:01:11 +02:00
}
case ts . SyntaxKind . IfStatement : {
2017-12-10 21:51:33 +01:00
var _o = node , expression = _o . expression , thenStatement = _o . thenStatement , elseStatement = _o . elseStatement ;
this . visitNode ( expression , true ) ; // allow truthyness check
this . visitNode ( thenStatement ) ;
return elseStatement !== undefined && this . visitNode ( elseStatement ) ;
2017-08-14 05:01:11 +02:00
}
case ts . SyntaxKind . PrefixUnaryExpression : {
2017-12-10 21:51:33 +01:00
var _p = node , operator = _p . operator , operand = _p . operand ;
this . visitNode ( operand , operator === ts . SyntaxKind . ExclamationToken ) ; // allow falsyness check
return false ;
2017-08-14 05:01:11 +02:00
}
case ts . SyntaxKind . ForStatement : {
2017-12-10 21:51:33 +01:00
var _q = node , initializer = _q . initializer , condition = _q . condition , incrementor = _q . incrementor , statement = _q . statement ;
2017-08-14 05:01:11 +02:00
if ( initializer !== undefined ) {
2017-12-10 21:51:33 +01:00
this . visitNode ( initializer , true ) ;
2017-08-14 05:01:11 +02:00
}
if ( condition !== undefined ) {
2017-12-10 21:51:33 +01:00
this . visitNode ( condition , true ) ;
2017-08-14 05:01:11 +02:00
} // allow truthyness check
if ( incrementor !== undefined ) {
2017-12-10 21:51:33 +01:00
this . visitNode ( incrementor , true ) ;
2017-08-14 05:01:11 +02:00
}
2017-12-10 21:51:33 +01:00
return this . visitNode ( statement ) ;
2017-08-14 05:01:11 +02:00
}
case ts . SyntaxKind . DoStatement :
case ts . SyntaxKind . WhileStatement :
2017-12-10 21:51:33 +01:00
this . visitNode ( node . expression , true ) ;
return this . visitNode ( node . statement ) ;
case ts . SyntaxKind . ConditionalExpression : {
var _r = node , condition = _r . condition , whenTrue = _r . whenTrue , whenFalse = _r . whenFalse ;
this . visitNode ( condition , true ) ;
var left = this . visitNode ( whenTrue , anyOk ) ;
return this . visitNode ( whenFalse , anyOk ) || left ;
}
case ts . SyntaxKind . VariableDeclaration :
case ts . SyntaxKind . Parameter :
return this . checkVariableOrParameterDeclaration ( node ) ;
case ts . SyntaxKind . BinaryExpression :
return this . checkBinaryExpression ( node , anyOk ) ;
case ts . SyntaxKind . AwaitExpression :
this . visitNode ( node . expression ) ;
return anyOk ? false : this . check ( node ) ;
case ts . SyntaxKind . YieldExpression :
return this . checkYieldExpression ( node , anyOk ) ;
case ts . SyntaxKind . ClassExpression :
case ts . SyntaxKind . ClassDeclaration :
this . checkClassLikeDeclaration ( node ) ;
return false ;
case ts . SyntaxKind . ArrayLiteralExpression : {
for ( var _s = 0 , _t = node . elements ; _s < _t . length ; _s ++ ) {
var element = _t [ _s ] ;
this . checkContextualType ( element , true ) ;
2017-05-28 00:38:50 +02:00
}
2017-12-10 21:51:33 +01:00
return false ;
2017-05-28 00:38:50 +02:00
}
2017-12-10 21:51:33 +01:00
case ts . SyntaxKind . JsxExpression :
return node . expression !== undefined &&
this . checkContextualType ( node . expression ) ;
2017-05-28 00:38:50 +02:00
}
2017-12-10 21:51:33 +01:00
if ( tsutils _1 . isTypeNodeKind ( node . kind ) || tsutils _1 . isTokenKind ( node . kind ) ) {
return false ;
2017-05-28 00:38:50 +02:00
}
2017-12-10 21:51:33 +01:00
return ts . forEachChild ( node , this . visitNodeCallback ) ;
} ;
NoUnsafeAnyWalker . prototype . check = function ( node ) {
if ( ! isNodeAny ( node , this . checker ) ) {
return false ;
2017-05-28 00:38:50 +02:00
}
2017-12-10 21:51:33 +01:00
this . addFailureAtNode ( node , Rule . FAILURE _STRING ) ;
return true ;
} ;
NoUnsafeAnyWalker . prototype . checkContextualType = function ( node , allowIfNoContextualType ) {
var type = this . checker . getContextualType ( node ) ;
return this . visitNode ( node , type === undefined && allowIfNoContextualType || isAny ( type ) ) ;
} ;
// Allow `const x = foo;` and `const x: any = foo`, but not `const x: Foo = foo;`.
NoUnsafeAnyWalker . prototype . checkVariableOrParameterDeclaration = function ( _a ) {
var name = _a . name , type = _a . type , initializer = _a . initializer ;
this . checkBindingName ( name ) ;
// Always allow the LHS to be `any`. Just don't allow RHS to be `any` when LHS isn't.
return initializer !== undefined &&
this . visitNode ( initializer ,
/*anyOk*/
name . kind === ts . SyntaxKind . Identifier && ( type === undefined || type . kind === ts . SyntaxKind . AnyKeyword ) ||
type !== undefined && type . kind === ts . SyntaxKind . AnyKeyword ) ;
} ;
NoUnsafeAnyWalker . prototype . checkBinaryExpression = function ( node , anyOk ) {
var allowAnyLeft = false ;
var allowAnyRight = false ;
switch ( node . operatorToken . kind ) {
case ts . SyntaxKind . ExclamationEqualsEqualsToken :
case ts . SyntaxKind . ExclamationEqualsToken :
case ts . SyntaxKind . EqualsEqualsEqualsToken :
case ts . SyntaxKind . EqualsEqualsToken :
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-12-10 21:51:33 +01:00
allowAnyLeft = allowAnyRight = true ;
break ;
case ts . SyntaxKind . InstanceOfKeyword : // Allow test
allowAnyLeft = true ;
break ;
2017-05-28 00:38:50 +02:00
case ts . SyntaxKind . EqualsToken :
// Allow assignment if the lhs is also *any*.
2017-12-10 21:51:33 +01:00
allowAnyLeft = true ;
allowAnyRight = isNodeAny ( node . left , this . checker ) ;
break ;
2017-05-28 00:38:50 +02:00
case ts . SyntaxKind . PlusToken : // Allow implicit stringification
case ts . SyntaxKind . PlusEqualsToken :
2017-12-10 21:51:33 +01:00
allowAnyLeft = allowAnyRight = isStringLike ( node . left , this . checker )
|| ( isStringLike ( node . right , this . checker ) && node . operatorToken . kind === ts . SyntaxKind . PlusToken ) ;
}
this . visitNode ( node . left , allowAnyLeft ) ;
this . visitNode ( node . right , allowAnyRight ) ;
return anyOk ? false : this . check ( node ) ;
} ;
NoUnsafeAnyWalker . prototype . checkYieldExpression = function ( node , anyOk ) {
if ( node . expression !== undefined ) {
this . checkContextualType ( node . expression , true ) ;
}
if ( anyOk ) {
return false ;
}
this . addFailureAtNode ( node , Rule . FAILURE _STRING ) ;
return true ;
} ;
NoUnsafeAnyWalker . prototype . checkClassLikeDeclaration = function ( node ) {
if ( node . decorators !== undefined ) {
node . decorators . forEach ( this . visitNodeCallback ) ;
}
if ( node . heritageClauses !== undefined ) {
node . heritageClauses . forEach ( this . visitNodeCallback ) ;
}
return node . members . forEach ( this . visitNodeCallback ) ;
} ;
NoUnsafeAnyWalker . prototype . checkBindingName = function ( node ) {
if ( node . kind !== ts . SyntaxKind . Identifier ) {
if ( isNodeAny ( node , this . checker ) ) {
this . addFailureAtNode ( node , Rule . FAILURE _STRING ) ;
}
for ( var _i = 0 , _a = node . elements ; _i < _a . length ; _i ++ ) {
var element = _a [ _i ] ;
if ( element . kind !== ts . SyntaxKind . OmittedExpression ) {
if ( element . propertyName !== undefined && element . propertyName . kind === ts . SyntaxKind . ComputedPropertyName ) {
this . visitNode ( element . propertyName . expression ) ;
}
this . checkBindingName ( element . name ) ;
if ( element . initializer !== undefined ) {
this . checkContextualType ( element . initializer ) ;
}
}
}
}
} ;
return NoUnsafeAnyWalker ;
} ( Lint . AbstractWalker ) ) ;
/** Check if property has no type annotation in this class and the base class */
function isPropertyAny ( node , checker ) {
if ( ! isNodeAny ( node . name , checker ) || node . name . kind === ts . SyntaxKind . ComputedPropertyName ) {
return false ;
}
for ( var _i = 0 , _a = checker . getBaseTypes ( checker . getTypeAtLocation ( node . parent ) ) ; _i < _a . length ; _i ++ ) {
var base = _a [ _i ] ;
var prop = base . getProperty ( node . name . text ) ;
if ( prop !== undefined && prop . declarations !== undefined ) {
return isAny ( checker . getTypeOfSymbolAtLocation ( prop , prop . declarations [ 0 ] ) ) ;
2017-05-28 00:38:50 +02:00
}
}
2017-12-10 21:51:33 +01:00
return true ;
2017-05-28 00:38:50 +02:00
}
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 ;