import * as ts from 'typescript' ; import * as Lint from 'tslint' ; import { ErrorTolerantWalker } from './ErrorTolerantWalker' ; import { AstUtils } from './AstUtils' ; import { Scope } from './Scope' ; /**
 * This exists so that you can try to tell the types of variables
 * and identifiers in the current scope. It builds the current scope
 * from the SourceFile then -> Module -> Class -> Function
 */ export class ScopedSymbolTrackingWalker extends ErrorTolerantWalker { private typeChecker ? : ts . TypeChecker ; private scope : Scope ; constructor ( sourceFile , options , program ? ) { super ( sourceFile , options ) ; if ( program ) { this . typeChecker = program . getTypeChecker ( ) ; } } protected isExpressionEvaluatingToFunction ( expression ) { if ( expression . kind === ts . SyntaxKind . ArrowFunction || expression . kind === ts . SyntaxKind . FunctionExpression ) { return true ; } const isString = expression . kind === ts . SyntaxKind . StringLiteral || expression . kind === ts . SyntaxKind . NoSubstitutionTemplateLiteral || expression . kind === ts . SyntaxKind . TemplateExpression || expression . kind === ts . SyntaxKind . TaggedTemplateExpression ; if ( isString || expression . kind === ts . SyntaxKind . BinaryExpression ) { return false ; } if ( this . scope . isFunctionSymbol ( expression . getText ( ) ) ) { return true ; } if ( expression . kind === ts . SyntaxKind . Identifier && this . typeChecker ) { const tsSymbol = this . typeChecker . getSymbolAtLocation ( expression ) ; if ( tsSymbol && tsSymbol . flags === ts . SymbolFlags . Function ) { return true ; } return false ; } if ( expression . kind === ts . SyntaxKind . CallExpression ) { if ( ( < any > expression ) . expression . name && ( < any > expression ) . expression . name . getText ( ) === 'bind' ) { return true ; } try { if ( ! this . typeChecker ) { return true ; } const signature = this . typeChecker . getResolvedSignature ( < ts . CallExpression > expression ) ; const expressionType = this . typeChecker . getReturnTypeOfSignature ( signature ) ; return this . isFunctionType ( expressionType , this . typeChecker ) ; } catch ( error ) { return false ; } } if ( ! this . typeChecker ) { return true ; } return this . isFunctionType ( this . typeChecker . getTypeAtLocation ( expression ) , this . typeChecker ) ; } private isFunctionType ( expressionType , typeChecker ) { const signatures = typeChecker . getSignaturesOfType ( expressionType , ts . SignatureKind . Call ) ; if ( signatures != null && signatures . length > 0 ) { const signatureDeclaration = signatures [ 0 ] . declaration ; if ( signatureDeclaration . kind === ts . SyntaxKind . FunctionType ) { return true ; } } return false ; } protected visitArrowFunction ( node ) { this . scope = new Scope ( this . scope ) ; this . scope . addParameters ( node . parameters ) ; super . visitArrowFunction ( node ) ; this . scope = this . scope . parent ; } protected visitClassDeclaration ( node ) { this . scope = new Scope ( this . scope ) ; node . members . forEach ( ( element ) : void => { const prefix = AstUtils . isStatic ( element ) ? node . name . getText ( ) + '.' : 'this.' ; if ( element . kind === ts . SyntaxKind . MethodDeclaration ) { this . scope . addFunctionSymbol ( prefix + ( < ts . MethodDeclaration > element ) . name . getText ( ) ) ; } else if ( element . kind === ts . SyntaxKind . PropertyDeclaration ) { const prop = < ts . PropertyDeclaration > element ; if ( AstUtils . isDeclarationFunctionType ( prop ) ) { this . scope . addFunctionSymbol ( prefix + ( < ts . MethodDeclaration > element ) . name . getText ( ) ) ; } else { this . scope . addNonFunctionSymbol ( prefix + ( < ts . MethodDeclaration > element ) . name . getText ( ) ) ; } } } ) ; super . visitClassDeclaration ( node ) ; this . scope = this . scope . parent ; } protected visitConstructorDeclaration ( node ) { this . scope = new Scope ( this . scope ) ; this . scope . addParameters ( node . parameters ) ; super . visitConstructorDeclaration ( node ) ; this . scope = this . scope . parent ; } protected visitFunctionDeclaration ( node ) { this . scope = new Scope ( this . scope ) ; this . scope . addParameters ( node . parameters ) ; super . visitFunctionDeclaration ( node ) ; this . scope = this . scope . parent ; } protected visitFunctionExpression ( node ) { this . scope = new Scope ( this . scope ) ; this . scope . addParameters ( node . parameters ) ; super . visitFunctionExpression ( node ) ; this . scope = this . scope . parent ; } protected visitMethodDeclaration ( node ) { this . scope = new Scope ( this . scope ) ; this . scope . addParameters ( node . parameters ) ; super . visitMethodDeclaration ( node ) ; this . scope = this . scope . parent ; } protected visitModuleDeclaration ( node ) { this . scope = new Scope ( this . scope ) ; this . scope . addGlobalScope ( node . body , this . getSourceFile ( ) , this . getOptions ( ) ) ; super . visitModuleDeclaration ( node ) ; this . scope = this . scope . parent ; } protected visitSetAccessor ( node ) { this . scope = new Scope ( this . scope ) ; this . scope . addParameters ( node . parameters ) ; super . visitSetAccessor ( node ) ; this . scope = this . scope . parent ; } protected visitSourceFile ( node ) { this . scope = new Scope ( null ) ; this . scope . addGlobalScope ( node , node , this . getOptions ( ) ) ; super . visitSourceFile ( node ) ; this . scope = null ; } }