import { Syntax } from "estraverse" ; import * as esrecurse from "esrecurse" ; import { Reference , ImplicitGlobal } from "./reference" ; import { VariableType } from "./variable" ; import { PatternVisitor , PatternVisitorCallback , AssignmentType , } from "./patternVisitor" ; import { Definition , ParameterDefinition } from "./definition" ; import * as assert from "assert" ; import { ScopeManager } from "./scopeManager" ; import { Scope , ModuleScope } from "./scope" ; import * as ESTree from "estree" ; import { ImportIdentifierInfo , ImportType , } from "./importManager" ; import { ExternalType , ExportVariableType , LocalExportVariable , } from "./exportManager" ; function traverseIdentifierInPattern ( options , rootPattern , referencer , callback ) { const visitor = new PatternVisitor ( options , rootPattern , callback ) ; visitor . visit ( rootPattern ) ; if ( referencer !== null && referencer !== undefined ) { visitor . rightHandNodes . forEach ( referencer . visit , referencer ) ; } } type ImportSpecifierNode = ESTree . ImportSpecifier | ESTree . ImportNamespaceSpecifier | ESTree . ImportDefaultSpecifier ; class Importer extends esrecurse . Visitor { public constructor ( public readonly declaration , public readonly referencer ) { super ( undefined , referencer . options ) ; } get moduleScope ( ) { return this . referencer . currentScope as ModuleScope ; } public visitImport ( id , specifier ) { this . referencer . visitPattern ( id , undefined , pattern => { this . referencer . currentScope ! . __define ( pattern , new Definition ( VariableType . ImportBinding , pattern , specifier , this . declaration ) ) ; } ) ; } public ImportNamespaceSpecifier ( node ) { const local = node . local ; this . visitImport ( local , node ) ; const importId = new ImportIdentifierInfo ( local . name , local . name , this . declaration . source . value as string , ImportType . Namespace ) ; this . moduleScope . importManager . addImportId ( importId ) ; } public ImportDefaultSpecifier ( node ) { const local = node . local ; this . visitImport ( local , node ) ; const importId = new ImportIdentifierInfo ( local . name , "default" , this . declaration . source . value as string , ImportType . Default ) ; this . moduleScope . importManager . addImportId ( importId ) ; } public ImportSpecifier ( node ) { const local = node . local ; this . visitImport ( local , node ) ; const importId = new ImportIdentifierInfo ( local . name , node . imported . name , this . declaration . source . value as string , ImportType . Identifier ) ; this . moduleScope . importManager . addImportId ( importId ) ; } } export class Referencer extends esrecurse . Visitor { public parent : Referencer | null = null ; public isInnerMethodDefinition : boolean = false ; public exportingSource : string | null = null ; public isExportingFromLocal : boolean = false ; public constructor ( public readonly options , public readonly scopeManager ) { super ( undefined , options ) ; } get currentScope ( ) { return this . scopeManager . __currentScope ; } public close ( node ) { while ( this . currentScope && node === this . currentScope . block ) { this . scopeManager . __currentScope = this . currentScope . __close ( this . scopeManager ) ; } } public pushInnerMethodDefinition ( isInnerMethodDefinition ) { const previous = this . isInnerMethodDefinition ; this . isInnerMethodDefinition = isInnerMethodDefinition ; return previous ; } public popInnerMethodDefinition ( isInnerMethodDefinition ) { this . isInnerMethodDefinition = isInnerMethodDefinition ; } public materializeTDZScope ( node , iterationNode ) { this . scopeManager . __nestTDZScope ( node ) ; this . visitVariableDeclaration ( this . currentScope ! , VariableType . TDZ , iterationNode . left as ESTree . VariableDeclaration , 0 , true ) ; } public materializeIterationScope ( node ) { const letOrConstDecl = node . left as ESTree . VariableDeclaration ; this . scopeManager . __nestForScope ( node ) ; this . visitVariableDeclaration ( this . currentScope ! , VariableType . Variable , letOrConstDecl , 0 ) ; this . visitPattern ( letOrConstDecl . declarations [ 0 ] . id , undefined , pattern => { this . currentScope ! . __referencing ( pattern , Reference . WRITE , node . right , undefined , true , true ) ; } ) ; } public referencingDefaultValue ( pattern , assignments , maybeImplicitGlobal ? , init ? ) { const scope = this . currentScope ! ; assignments . forEach ( assignment => { scope . __referencing ( pattern , Reference . WRITE , assignment . right , maybeImplicitGlobal , pattern !== assignment . left , init ) ; } ) ; } public visitPattern ( node , options = { processRightHandNodes : false } , callback ) { traverseIdentifierInPattern ( this . options , node , options . processRightHandNodes ? this : null , callback ) ; } public visitFunction ( node ) { let i , iz ; if ( node . type === Syntax . FunctionDeclaration ) { this . currentScope ! . __define ( node . id ! , new Definition ( VariableType . FunctionName , node . id ! , node ) ) ; } if ( node . type === Syntax . FunctionExpression && node . id ) { this . scopeManager . __nestFunctionExpressionNameScope ( node ) ; } this . scopeManager . __nestFunctionScope ( node , this . isInnerMethodDefinition ) ; const that = this ; param { pattern } pattern param { Object } info returns { void } function visitPatternCallback ( pattern , info ) { that . currentScope ! . __define ( pattern , new ParameterDefinition ( pattern , node , i , info . rest ) ) ; that . referencingDefaultValue ( pattern , info . assignments , undefined , true ) ; } for ( i = 0 , iz = node . params . length ; i < iz ; ++ i ) { this . visitPattern ( node . params [ i ] , { processRightHandNodes : true } , visitPatternCallback ) ; } if ( node . body ) { if ( node . body . type === Syntax . BlockStatement ) { this . visitChildren ( node . body ) ; } else { this . visit ( node . body ) ; } } this . close ( node ) ; } public visitClass ( node ) { if ( node . type === Syntax . ClassDeclaration ) { this . currentScope ! . __define ( node . id ! , new Definition ( VariableType . ClassName , node . id , node ) ) ; } this . visit ( node . superClass ! ) ; this . scopeManager . __nestClassScope ( node ) ; if ( node . id ) { this . currentScope ! . __define ( node . id , new Definition ( VariableType . ClassName , node . id , node ) ) ; } this . visit ( node . body ) ; this . close ( node ) ; } public visitProperty ( node ) { let previous ; if ( node . computed ) { this . visit ( node . key ) ; } const isMethodDefinition = node . type === Syntax . MethodDefinition ; if ( isMethodDefinition ) { previous = this . pushInnerMethodDefinition ( true ) ; } this . visit ( node . value ) ; if ( isMethodDefinition ) { this . popInnerMethodDefinition ( previous ! ) ; } } public visitForIn ( node ) { if ( node . left . type === Syntax . VariableDeclaration && node . left . kind !== "var" ) { this . materializeTDZScope ( node . right , node ) ; this . visit ( node . right ) ; this . close ( node . right ) ; this . materializeIterationScope ( node ) ; this . visit ( node . body ) ; this . close ( node ) ; } else { if ( node . left . type === Syntax . VariableDeclaration ) { this . visit ( node . left ) ; this . visitPattern ( node . left . declarations [ 0 ] . id , undefined , pattern => { this . currentScope ! . __referencing ( pattern , Reference . WRITE , node . right , undefined , true , true ) ; } ) ; } else { this . visitPattern ( node . left as ESTree . Pattern , { processRightHandNodes : true } , ( pattern , info ) => { let maybeImplicitGlobal ; if ( ! this . currentScope ! . isStrict ) { maybeImplicitGlobal = { pattern , node , } ; } this . referencingDefaultValue ( pattern , info . assignments , maybeImplicitGlobal , false ) ; this . currentScope ! . __referencing ( pattern , Reference . WRITE , node . right , maybeImplicitGlobal , true , false ) ; } ) ; } this . visit ( node . right ) ; this . visit ( node . body ) ; } } public visitVariableDeclaration ( variableTargetScope , type , node , index , fromTDZ ? ) { const decl = node . declarations [ index ] ; const init = decl . init ; this . visitPattern ( decl . id , { processRightHandNodes : ! fromTDZ } , ( pattern , info ) => { variableTargetScope . __define ( pattern , new Definition ( type , pattern , decl , node , index , node . kind ) ) ; if ( ! fromTDZ ) { this . referencingDefaultValue ( pattern , info . assignments , undefined , true ) ; } if ( init ) { this . currentScope ! . __referencing ( pattern , Reference . WRITE , init , undefined , ! info . topLevel , true ) ; } } ) ; } public AssignmentExpression ( node ) { if ( PatternVisitor . isPattern ( node . left ) ) { if ( node . operator === "=" ) { this . visitPattern ( node . left , { processRightHandNodes : true } , ( pattern , info ) => { let maybeImplicitGlobal ; if ( ! this . currentScope ! . isStrict ) { maybeImplicitGlobal = { pattern , node , } ; } this . referencingDefaultValue ( pattern , info . assignments , maybeImplicitGlobal , false ) ; this . currentScope ! . __referencing ( pattern , Reference . WRITE , node . right , maybeImplicitGlobal , ! info . topLevel , false ) ; } ) ; } else { this . currentScope ! . __referencing ( node . left , Reference . RW , node . right ) ; } } else { this . visit ( node . left ) ; } this . visit ( node . right ) ; } public CatchClause ( node ) { this . scopeManager . __nestCatchScope ( node ) ; this . visitPattern ( node . param , { processRightHandNodes : true } , ( pattern , info ) => { this . currentScope ! . __define ( pattern , new Definition ( VariableType . CatchClause , node . param , node ) ) ; this . referencingDefaultValue ( pattern , info . assignments , undefined , true ) ; } ) ; this . visit ( node . body ) ; this . close ( node ) ; } public Program ( node ) { this . scopeManager . __nestGlobalScope ( node ) ; if ( this . scopeManager . __isNodejsScope ( ) ) { this . currentScope ! . isStrict = false ; this . scopeManager . __nestFunctionScope ( node , false ) ; } if ( this . scopeManager . __isES6 ( ) && this . scopeManager . isModule ( ) ) { this . scopeManager . __nestModuleScope ( node ) ; } if ( this . scopeManager . isStrictModeSupported ( ) && this . scopeManager . isImpliedStrict ( ) ) { this . currentScope ! . isStrict = true ; } this . visitChildren ( node ) ; this . close ( node ) ; } public Identifier ( node ) { this . currentScope ! . __referencing ( node , undefined , undefined , undefined , undefined , undefined , this . isExportingFromLocal ) ; } public UpdateExpression ( node ) { if ( PatternVisitor . isPattern ( node . argument ) ) { this . currentScope ! . __referencing ( node . argument , Reference . RW ) ; } else { this . visitChildren ( node ) ; } } public MemberExpression ( node ) { this . visit ( node . object ) ; if ( node . computed ) { this . visit ( node . property ) ; } } public Property ( node ) { this . visitProperty ( node ) ; } public MethodDefinition ( node ) { this . visitProperty ( node ) ; } public BreakStatement ( ) { } public ContinueStatement ( ) { } public LabeledStatement ( node ) { this . visit ( node . body ) ; } public ForStatement ( node ) { if ( node . init && node . init . type === Syntax . VariableDeclaration && node . init . kind !== "var" ) { this . scopeManager . __nestForScope ( node ) ; } this . visitChildren ( node ) ; this . close ( node ) ; } public ClassExpression ( node ) { this . visitClass ( node ) ; } public ClassDeclaration ( node ) { this . visitClass ( node ) ; } public CallExpression ( node ) { if ( ! this . scopeManager . __ignoreEval ( ) && node . callee . type === Syntax . Identifier && node . callee . name === "eval" ) { this . currentScope ! . variableScope . __detectEval ( ) ; } this . visitChildren ( node ) ; } public BlockStatement ( node ) { if ( this . scopeManager . __isES6 ( ) ) { this . scopeManager . __nestBlockScope ( node ) ; } this . visitChildren ( node ) ; this . close ( node ) ; } public ThisExpression ( ) { this . currentScope ! . variableScope . __detectThis ( ) ; } public WithStatement ( node ) { this . visit ( node . object ) ; this . scopeManager . __nestWithScope ( node ) ; this . visit ( node . body ) ; this . close ( node ) ; } public VariableDeclaration ( node ) { const variableTargetScope = node . kind === "var" ? this . currentScope ! . variableScope : this . currentScope ! ; for ( let i = 0 , iz = node . declarations . length ; i < iz ; ++ i ) { const decl = node . declarations [ i ] ; this . visitVariableDeclaration ( variableTargetScope , VariableType . Variable , node , i ) ; if ( decl . init ) { this . visit ( decl . init ) ; } } } public SwitchStatement ( node ) { this . visit ( node . discriminant ) ; if ( this . scopeManager . __isES6 ( ) ) { this . scopeManager . __nestSwitchScope ( node ) ; } for ( let i = 0 , iz = node . cases . length ; i < iz ; ++ i ) { this . visit ( node . cases [ i ] ) ; } this . close ( node ) ; } public FunctionDeclaration ( node ) { this . visitFunction ( node ) ; } public FunctionExpression ( node ) { this . visitFunction ( node ) ; } public ForOfStatement ( node ) { this . visitForIn ( node ) ; } public ForInStatement ( node ) { this . visitForIn ( node ) ; } public ArrowFunctionExpression ( node ) { this . visitFunction ( node ) ; } public ImportDeclaration ( node ) { assert ( this . scopeManager . __isES6 ( ) && this . scopeManager . isModule ( ) , "ImportDeclaration should appear when the mode is ES6 and in the module context." ) ; const importer = new Importer ( node , this ) ; importer . visit ( node ) ; } public visitExportDeclaration ( node ) { if ( node . source ) { this . exportingSource = node . source . value as string ; } else { this . exportingSource = null ; } if ( node . declaration ) { this . visit ( node . declaration ) ; return ; } this . visitChildren ( node ) ; } public ExportNamedDeclaration ( node ) { const moduleScope = this . currentScope as ModuleScope ; const previous = moduleScope . isExportingNamedDeclaration ; moduleScope . isExportingNamedDeclaration = true ; this . visitExportDeclaration ( node ) ; moduleScope . isExportingNamedDeclaration = previous ; } public ExportDefaultDeclaration ( node ) { const currentScope = this . currentScope as ModuleScope ; if ( currentScope . type !== "module" ) { throw new Error ( "use export in a non module scope" ) ; } let localExportVar ; if ( node . declaration . type === "Identifier" ) { localExportVar = { type : ExportVariableType . Local , exportName : "default" , localName : node . declaration . name , node : node . declaration , } ; this . isExportingFromLocal = true ; this . visit ( node . declaration ) ; this . isExportingFromLocal = false ; } else { localExportVar = { type : ExportVariableType . Local , exportName : "default" , localName : null , node : node . declaration , } ; this . visit ( node . declaration ) ; } currentScope . exportManager . addLocalExportVariable ( localExportVar ) ; } public ExportAllDeclaration ( node ) { const currentScope = this . currentScope as ModuleScope ; currentScope . exportManager . addExternalVariable ( { type : ExportVariableType . External , moduleName : node . source . value as string , moduleType : ExternalType . All , } ) ; } public ExportSpecifier ( node ) { const local = node . local ; const currentScope = this . currentScope as ModuleScope ; if ( this . exportingSource ) { currentScope . exportManager . addExternalVariable ( { type : ExportVariableType . External , moduleName : this . exportingSource , moduleType : ExternalType . Identifier , names : { exportName : node . exported . name , sourceName : node . local . name , } , } ) ; } else { this . isExportingFromLocal = true ; this . visit ( local ) ; currentScope . exportManager . addLocalExportVariable ( { type : ExportVariableType . Local , exportName : node . exported . name , localName : node . local . name , node : node . local , } ) ; this . isExportingFromLocal = false ; } } public MetaProperty ( ) { } }