fileoverview import * as ts from 'typescript' ; import { Failure , Fix } from './failure' ; /**
 * A Handler contains a handler function and its corresponding error code so
 * when the handler function is triggered we know which rule is violated.
 */ interface Handler < T extends ts . Node > { handlerFunction ( checker , node ) : void ; code : number ; } /**
 * Tsetse rules use on() and addFailureAtNode() for rule implementations.
 * Rules can get a ts.TypeChecker from checker.typeChecker so typed rules are
 * possible. Compiler uses execute() to run the Tsetse check.
 */ export class Checker { /** Node to handlers mapping for all enabled rules. */ private readonly nodeHandlersMap = new Map < ts . SyntaxKind , Array < Handler < ts . Node > > > ( ) ; /**
     * Mapping from identifier name to handlers for all rules inspecting property
     * names.
     */ private readonly namedIdentifierHandlersMap = new Map < string , Array < Handler < ts . Identifier > > > ( ) ; /**
     * Mapping from property name to handlers for all rules inspecting property
     * accesses expressions.
     */ private readonly namedPropertyAccessHandlersMap = new Map < string , Array < Handler < ts . PropertyAccessExpression > > > ( ) ; /**
     * Mapping from string literal value to handlers for all rules inspecting
     * string literals.
     */ private readonly stringLiteralElementAccessHandlersMap = new Map < string , Array < Handler < ts . ElementAccessExpression > > > ( ) ; private failures : Failure [ ] = [ ] ; private currentSourceFile : ts . SourceFile | undefined ; private currentCode = 0 ; /** Allow typed rules via typeChecker. */ typeChecker : ts . TypeChecker ; constructor ( program ) { this . typeChecker = program . getTypeChecker ( ) ; } /**
     * This doesn't run any checks yet. Instead, it registers `handlerFunction` on
     * `nodeKind` node in `nodeHandlersMap` map. After all rules register their
     * handlers, the source file AST will be traversed.
     */ on < T extends ts . Node > ( nodeKind , handlerFunction , code ) { const newHandler = { handlerFunction , code } ; const registeredHandlers = this . nodeHandlersMap . get ( nodeKind ) ; if ( registeredHandlers === undefined ) { this . nodeHandlersMap . set ( nodeKind , [ newHandler ] ) ; } else { registeredHandlers . push ( newHandler ) ; } } /**
     * Similar to `on`, but registers handlers on more specific node type, i.e.,
     * identifiers.
     */ onNamedIdentifier ( identifierName , handlerFunction , code ) { const newHandler = { handlerFunction , code } ; const registeredHandlers = this . namedIdentifierHandlersMap . get ( identifierName ) ; if ( registeredHandlers === undefined ) { this . namedIdentifierHandlersMap . set ( identifierName , [ newHandler ] ) ; } else { registeredHandlers . push ( newHandler ) ; } } /**
     * Similar to `on`, but registers handlers on more specific node type, i.e.,
     * property access expressions.
     */ onNamedPropertyAccess ( propertyName , handlerFunction , code ) { const newHandler = { handlerFunction , code } ; const registeredHandlers = this . namedPropertyAccessHandlersMap . get ( propertyName ) ; if ( registeredHandlers === undefined ) { this . namedPropertyAccessHandlersMap . set ( propertyName , [ newHandler ] ) ; } else { registeredHandlers . push ( newHandler ) ; } } /**
     * Similar to `on`, but registers handlers on more specific node type, i.e.,
     * element access expressions with string literals as keys.
     */ onStringLiteralElementAccess ( key , handlerFunction , code ) { const newHandler = { handlerFunction , code } ; const registeredHandlers = this . stringLiteralElementAccessHandlersMap . get ( key ) ; if ( registeredHandlers === undefined ) { this . stringLiteralElementAccessHandlersMap . set ( key , [ newHandler ] ) ; } else { registeredHandlers . push ( newHandler ) ; } } /**
     * Add a failure with a span.
     */ addFailure ( start , end , failureText , fix ? ) { if ( ! this . currentSourceFile ) { throw new Error ( 'Source file not defined' ) ; } if ( start >= end || end > this . currentSourceFile . end || start < 0 ) { throw new Error ( `Invalid start and end position: [${ start }, ${ end }]` + ` in file ${ this . currentSourceFile . fileName }.` ) ; } const failure = new Failure ( this . currentSourceFile , start , end , failureText , this . currentCode , fix ) ; this . failures . push ( failure ) ; } addFailureAtNode ( node , failureText , fix ? ) { this . addFailure ( node . getStart ( this . currentSourceFile ) , node . getEnd ( ) , failureText , fix ) ; } /** Dispatch general handlers registered via `on` */ dispatchNodeHandlers ( node ) { const handlers = this . nodeHandlersMap . get ( node . kind ) ; if ( handlers === undefined ) return ; for ( const handler of handlers ) { this . currentCode = handler . code ; handler . handlerFunction ( this , node ) ; } } /** Dispatch identifier handlers registered via `onNamedIdentifier` */ dispatchNamedIdentifierHandlers ( id ) { const handlers = this . namedIdentifierHandlersMap . get ( id . text ) ; if ( handlers === undefined ) return ; for ( const handler of handlers ) { this . currentCode = handler . code ; handler . handlerFunction ( this , id ) ; } } /**
     * Dispatch property access handlers registered via `onNamedPropertyAccess`
     */ dispatchNamedPropertyAccessHandlers ( prop ) { const handlers = this . namedPropertyAccessHandlersMap . get ( prop . name . text ) ; if ( handlers === undefined ) return ; for ( const handler of handlers ) { this . currentCode = handler . code ; handler . handlerFunction ( this , prop ) ; } } /**
     * Dispatch string literal handlers registered via
     * `onStringLiteralElementAccess`.
     */ dispatchStringLiteralElementAccessHandlers ( elem ) { const ty = this . typeChecker . getTypeAtLocation ( elem . argumentExpression ) ; if ( ! ty . isStringLiteral ( ) ) return ; const handlers = this . stringLiteralElementAccessHandlersMap . get ( ty . value ) ; if ( handlers === undefined ) return ; for ( const handler of handlers ) { this . currentCode = handler . code ; handler . handlerFunction ( this , elem ) ; } } /**
     * Walk `sourceFile`, invoking registered handlers with Checker as the first
     * argument and current node as the second argument. Return failures if there
     * are any.
     */ execute ( sourceFile ) { const thisChecker = this ; this . currentSourceFile = sourceFile ; this . failures = [ ] ; run ( sourceFile ) ; return this . failures ; function run ( node ) { thisChecker . dispatchNodeHandlers ( node ) ; if ( ts . isIdentifier ( node ) ) { thisChecker . dispatchNamedIdentifierHandlers ( node ) ; } else if ( ts . isPropertyAccessExpression ( node ) ) { thisChecker . dispatchNamedPropertyAccessHandlers ( node ) ; } else if ( ts . isElementAccessExpression ( node ) ) { thisChecker . dispatchStringLiteralElementAccessHandlers ( node ) ; } ts . forEachChild ( node , run ) ; } } }