1/* 2 * Copyright (c) 2022 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import * as ts from "typescript"; 17import * as astutils from "./astutils"; 18import { 19 hasDefaultKeywordModifier, 20 hasExportKeywordModifier, 21 isAnonymousFunctionDefinition 22} from "./base/util"; 23import { CmdOptions } from "./cmdOptions"; 24import { CompilerDriver } from "./compilerDriver"; 25import { DiagnosticCode, DiagnosticError } from "./diagnostic"; 26import { findOuterNodeOfParenthesis } from "./expression/parenthesizedExpression"; 27import * as jshelpers from "./jshelpers"; 28import { LOGD } from "./log"; 29import { ModuleStmt } from "./modules"; // [delete it when type system adapts for ESM] 30import { 31 CatchParameter, 32 ClassDecl, 33 ConstDecl, 34 Decl, 35 FuncDecl, 36 FunctionParameter, 37 FunctionScope, 38 GlobalScope, 39 LetDecl, 40 LocalScope, 41 LoopScope, 42 ModuleScope, 43 ModuleVarKind, 44 Scope, 45 VarDecl, 46 VariableScope 47} from "./scope"; 48import { 49 AddCtor2Class, 50 getClassNameForConstructor, 51 extractCtorOfClass 52} from "./statement/classStatement"; 53import { checkSyntaxError } from "./syntaxChecker"; 54import { isGlobalIdentifier, isFunctionLikeDeclaration } from "./syntaxCheckHelper"; 55import { TypeChecker } from "./typeChecker"; 56import { MandatoryArguments, VarDeclarationKind } from "./variable"; 57 58export class Recorder { 59 node: ts.Node; 60 scope: Scope; 61 compilerDriver: CompilerDriver; 62 recordType: boolean; 63 private scopeMap: Map<ts.Node, Scope> = new Map<ts.Node, Scope>(); 64 private hoistMap: Map<Scope, Decl[]> = new Map<Scope, Decl[]>(); 65 private parametersMap: Map<ts.FunctionLikeDeclaration, FunctionParameter[]> = new Map<ts.FunctionLikeDeclaration, FunctionParameter[]>(); 66 private funcNameMap: Map<string, number>; 67 private class2Ctor: Map<ts.ClassLikeDeclaration, ts.ConstructorDeclaration> = new Map<ts.ClassLikeDeclaration, ts.ConstructorDeclaration>(); 68 private isTsFile: boolean; 69 // [delete it when type system adapts for ESM] 70 private importStmts: Array<ModuleStmt> = []; 71 private exportStmts: Array<ModuleStmt> = []; 72 private syntaxCheckStatus: boolean; 73 74 constructor(node: ts.Node, scope: Scope, compilerDriver: CompilerDriver, recordType: boolean, isTsFile: boolean, syntaxCheckStatus: boolean) { 75 this.node = node; 76 this.scope = scope; 77 this.compilerDriver = compilerDriver; 78 this.recordType = recordType; 79 this.funcNameMap = new Map<string, number>(); 80 this.funcNameMap.set("main", 1); 81 this.isTsFile = isTsFile; 82 this.syntaxCheckStatus = syntaxCheckStatus; 83 } 84 85 record() { 86 this.setParent(this.node); 87 this.setScopeMap(this.node, this.scope); 88 this.recordInfo(this.node, this.scope); 89 return this.node; 90 } 91 92 getCtorOfClass(node: ts.ClassLikeDeclaration) { 93 return this.class2Ctor.get(node); 94 } 95 96 setCtorOfClass(node: ts.ClassLikeDeclaration, ctor: ts.ConstructorDeclaration) { 97 if (!this.class2Ctor.has(node)) { 98 this.class2Ctor.set(node, ctor); 99 } 100 } 101 102 private setParent(node: ts.Node) { 103 node.forEachChild(childNode => { 104 if (!this.isTsFile || childNode!.parent == undefined || childNode.parent.kind != node.kind) { 105 childNode = jshelpers.setParent(childNode, node)!; 106 let originNode = ts.getOriginalNode(childNode); 107 childNode = ts.setTextRange(childNode, originNode); 108 } 109 this.setParent(childNode); 110 }); 111 } 112 113 private recordInfo(node: ts.Node, scope: Scope) { 114 node.forEachChild(childNode => { 115 if (this.syntaxCheckStatus) { 116 checkSyntaxError(childNode, scope); 117 } 118 switch (childNode.kind) { 119 case ts.SyntaxKind.FunctionExpression: 120 case ts.SyntaxKind.MethodDeclaration: 121 case ts.SyntaxKind.Constructor: 122 case ts.SyntaxKind.GetAccessor: 123 case ts.SyntaxKind.SetAccessor: 124 case ts.SyntaxKind.ArrowFunction: { 125 let functionScope = this.buildVariableScope(scope, <ts.FunctionLikeDeclaration>childNode); 126 this.recordOtherFunc(<ts.FunctionLikeDeclaration>childNode, functionScope); 127 this.recordInfo(childNode, functionScope); 128 break; 129 } 130 case ts.SyntaxKind.FunctionDeclaration: { 131 let functionScope = this.buildVariableScope(scope, <ts.FunctionLikeDeclaration>childNode); 132 let isExport: boolean = false; 133 if (hasExportKeywordModifier(childNode)) { 134 if (!CmdOptions.isModules()) { 135 throw new DiagnosticError(childNode, DiagnosticCode.Cannot_use_imports_exports_or_module_augmentations_when_module_is_none, jshelpers.getSourceFileOfNode(childNode)); 136 } 137 this.recordEcmaExportInfo(<ts.FunctionDeclaration>childNode, scope); 138 isExport = true; 139 } 140 // recordFuncDecl must behind recordEcmaExportInfo() cause function without name 141 // should be SyntaxChecked in recordEcmaExportInfo 142 this.recordFuncDecl(<ts.FunctionDeclaration>childNode, scope, isExport); 143 if (this.recordType) { 144 TypeChecker.getInstance().formatNodeType(childNode); 145 } 146 this.recordInfo(childNode, functionScope); 147 break; 148 } 149 case ts.SyntaxKind.Block: 150 case ts.SyntaxKind.IfStatement: 151 case ts.SyntaxKind.SwitchStatement: 152 case ts.SyntaxKind.LabeledStatement: 153 case ts.SyntaxKind.ThrowStatement: 154 case ts.SyntaxKind.TryStatement: 155 case ts.SyntaxKind.CatchClause: { 156 let localScope = new LocalScope(scope); 157 this.setScopeMap(childNode, localScope); 158 this.recordInfo(childNode, localScope); 159 break; 160 } 161 case ts.SyntaxKind.DoStatement: 162 case ts.SyntaxKind.WhileStatement: 163 case ts.SyntaxKind.ForStatement: 164 case ts.SyntaxKind.ForInStatement: 165 case ts.SyntaxKind.ForOfStatement: { 166 let loopScope: LoopScope = new LoopScope(scope);; 167 this.setScopeMap(childNode, loopScope); 168 this.recordInfo(childNode, loopScope); 169 break; 170 } 171 case ts.SyntaxKind.ClassDeclaration: { 172 let isExport: boolean = false; 173 if (hasExportKeywordModifier(childNode)) { 174 if (!CmdOptions.isModules()) { 175 throw new DiagnosticError(childNode, DiagnosticCode.Cannot_use_imports_exports_or_module_augmentations_when_module_is_none, jshelpers.getSourceFileOfNode(childNode)); 176 } 177 this.recordEcmaExportInfo(<ts.ClassDeclaration>childNode, scope); 178 isExport = true; 179 } 180 this.recordClassInfo(<ts.ClassLikeDeclaration>childNode, scope, isExport); 181 if (this.recordType) { 182 TypeChecker.getInstance().formatNodeType(childNode); 183 } 184 break; 185 } 186 case ts.SyntaxKind.ClassExpression: { 187 this.recordClassInfo(<ts.ClassLikeDeclaration>childNode, scope, false); 188 if (this.recordType) { 189 TypeChecker.getInstance().formatNodeType(childNode); 190 } 191 break; 192 } 193 case ts.SyntaxKind.InterfaceDeclaration: { 194 if (this.recordType) { 195 TypeChecker.getInstance().formatNodeType(childNode); 196 } 197 break; 198 } 199 case ts.SyntaxKind.Identifier: { 200 this.recordVariableDecl(<ts.Identifier>childNode, scope); 201 break; 202 } 203 case ts.SyntaxKind.ImportDeclaration: { 204 if (!CmdOptions.isModules()) { 205 throw new DiagnosticError(childNode, DiagnosticCode.Cannot_use_imports_exports_or_module_augmentations_when_module_is_none, jshelpers.getSourceFileOfNode(childNode)); 206 } 207 this.recordEcmaImportInfo(<ts.ImportDeclaration>childNode, scope); 208 209 let importStmt = this.recordImportInfo(<ts.ImportDeclaration>childNode); // [delete it when type system adapts for ESM] 210 if (this.recordType) { 211 TypeChecker.getInstance().formatNodeType(childNode, importStmt); 212 } 213 break; 214 } 215 case ts.SyntaxKind.ExportDeclaration: { 216 if (!CmdOptions.isModules()) { 217 throw new DiagnosticError(childNode, DiagnosticCode.Cannot_use_imports_exports_or_module_augmentations_when_module_is_none, jshelpers.getSourceFileOfNode(childNode)); 218 } 219 this.recordEcmaExportInfo(<ts.ExportDeclaration>childNode, scope); 220 221 let exportStmt = this.recordExportInfo(<ts.ExportDeclaration>childNode); // [delete it when type system adapts for ESM] 222 if (this.recordType) { 223 TypeChecker.getInstance().formatNodeType(childNode, exportStmt); 224 } 225 break; 226 } 227 case ts.SyntaxKind.ExportAssignment: { 228 if (!CmdOptions.isModules()) { 229 throw new DiagnosticError(childNode, DiagnosticCode.Cannot_use_imports_exports_or_module_augmentations_when_module_is_none, jshelpers.getSourceFileOfNode(childNode)); 230 } 231 this.recordEcmaExportInfo(<ts.ExportAssignment>childNode, scope); 232 233 this.recordInfo(childNode, scope); 234 if (this.recordType) { 235 TypeChecker.getInstance().formatNodeType(childNode); 236 } 237 break; 238 } 239 case ts.SyntaxKind.VariableStatement: { 240 if (hasExportKeywordModifier(childNode)) { 241 if (!CmdOptions.isModules()) { 242 throw new DiagnosticError(childNode, DiagnosticCode.Cannot_use_imports_exports_or_module_augmentations_when_module_is_none, jshelpers.getSourceFileOfNode(childNode)); 243 } 244 this.recordEcmaExportInfo(<ts.VariableStatement>childNode, scope); 245 } 246 if (this.recordType) { 247 TypeChecker.getInstance().formatNodeType(childNode); 248 } 249 this.recordInfo(childNode, scope); 250 break; 251 } 252 default: 253 this.recordInfo(childNode, scope); 254 } 255 }); 256 } 257 258 private recordClassInfo(childNode: ts.ClassLikeDeclaration, scope: Scope, isExport: boolean) { 259 let localScope = new LocalScope(scope); 260 this.setScopeMap(childNode, localScope); 261 let ctor = extractCtorOfClass(childNode); 262 if (!ctor) { 263 AddCtor2Class(this, childNode, localScope); 264 } else { 265 this.setCtorOfClass(childNode, ctor); 266 } 267 268 let name: string = childNode.name ? jshelpers.getTextOfIdentifierOrLiteral(childNode.name) : "*default*"; 269 270 let moduleKind = isExport ? ModuleVarKind.EXPORTED : ModuleVarKind.NOT; 271 let classDecl = new ClassDecl(name, childNode, moduleKind); 272 scope.setDecls(classDecl); 273 this.recordInfo(childNode, localScope); 274 } 275 276 buildVariableScope(curScope: Scope, node: ts.FunctionLikeDeclaration) { 277 let functionScope = new FunctionScope(curScope, <ts.FunctionLikeDeclaration>node); 278 let parentVariableScope = <VariableScope>curScope.getNearestVariableScope(); 279 functionScope.setParentVariableScope(parentVariableScope); 280 parentVariableScope.addChildVariableScope(functionScope); 281 this.setScopeMap(node, functionScope); 282 return functionScope; 283 } 284 285 private recordVariableDecl(id: ts.Identifier, scope: Scope) { 286 let name = jshelpers.getTextOfIdentifierOrLiteral(id); 287 let parent = this.getDeclarationNodeOfId(id); 288 289 if (parent) { 290 let declKind = astutils.getVarDeclarationKind(<ts.VariableDeclaration>parent); 291 let isExportDecl: boolean = false; 292 if ((<ts.VariableDeclaration>parent).parent.parent.kind == ts.SyntaxKind.VariableStatement) { 293 isExportDecl = hasExportKeywordModifier((<ts.VariableDeclaration>parent).parent.parent); 294 } 295 296 // collect declaration information to corresponding scope 297 let decl = this.addVariableDeclToScope(scope, id, parent, name, declKind, isExportDecl); 298 if (declKind == VarDeclarationKind.VAR) { 299 let variableScopeParent = <VariableScope>scope.getNearestVariableScope(); 300 this.collectHoistDecls(id, variableScopeParent, decl); 301 } 302 } else { 303 let declScope = scope.findDeclPos(name); 304 if (declScope) { 305 let decl = <Decl>declScope.getDecl(name); 306 307 if ((decl instanceof LetDecl || decl instanceof ConstDecl)) { 308 let nearestRefVariableScope = <VariableScope>scope.getNearestVariableScope(); 309 let nearestDefLexicalScope = <VariableScope | LoopScope>declScope.getNearestLexicalScope(); 310 311 let tmp: Scope | undefined = nearestRefVariableScope.getNearestLexicalScope(); 312 let needCreateLoopEnv: boolean = false; 313 if (nearestDefLexicalScope instanceof LoopScope) { 314 while (tmp) { 315 if (tmp == nearestDefLexicalScope) { 316 needCreateLoopEnv = true; 317 break; 318 } 319 320 tmp = tmp.getParent(); 321 } 322 323 if (needCreateLoopEnv) { 324 nearestDefLexicalScope.pendingCreateEnv(); 325 } 326 } 327 } 328 } 329 } 330 331 if (name == MandatoryArguments) { 332 let varialbeScope = scope.getNearestVariableScope(); 333 varialbeScope?.setUseArgs(true); 334 } 335 } 336 337 private addVariableDeclToScope(scope: Scope, node: ts.Node, parent: ts.Node, name: string, declKind: VarDeclarationKind, isExportDecl: boolean): Decl { 338 let moduleKind = isExportDecl ? ModuleVarKind.EXPORTED : ModuleVarKind.NOT; 339 let decl = new VarDecl(name, node, moduleKind); 340 341 switch (declKind) { 342 case VarDeclarationKind.VAR: 343 break; 344 case VarDeclarationKind.LET: 345 if (parent.parent.kind == ts.SyntaxKind.CatchClause) { 346 decl = new CatchParameter(name, node); 347 } else { 348 decl = new LetDecl(name, node, moduleKind); 349 } 350 break; 351 case VarDeclarationKind.CONST: 352 decl = new ConstDecl(name, node, moduleKind); 353 break; 354 default: 355 throw new Error("Wrong type of declaration"); 356 } 357 scope.setDecls(decl); 358 return decl; 359 } 360 361 private getDeclarationNodeOfId(id: ts.Identifier): ts.VariableDeclaration | undefined { 362 let parent = id.parent; 363 if (ts.isVariableDeclaration(parent) && 364 parent.name == id) { 365 return <ts.VariableDeclaration>parent; 366 } else if (ts.isBindingElement(parent) && 367 parent.name == id) { 368 while (parent && !ts.isVariableDeclaration(parent)) { 369 parent = parent.parent; 370 } 371 372 return parent ? <ts.VariableDeclaration>parent : undefined; 373 } else { 374 return undefined; 375 } 376 } 377 378 // [delete it when type system adapts for ESM] 379 private recordImportInfo(node: ts.ImportDeclaration): ModuleStmt { 380 if (!ts.isStringLiteral(node.moduleSpecifier)) { 381 throw new Error("moduleSpecifier must be a stringLiteral"); 382 } 383 let importStmt: ModuleStmt; 384 if (node.moduleSpecifier) { 385 let moduleRequest = jshelpers.getTextOfIdentifierOrLiteral(node.moduleSpecifier); 386 importStmt = new ModuleStmt(node, moduleRequest); 387 } else { 388 importStmt = new ModuleStmt(node); 389 } 390 if (node.importClause) { 391 let importClause: ts.ImportClause = node.importClause; 392 393 // import defaultExport from "a.js" 394 if (importClause.name) { 395 let name = jshelpers.getTextOfIdentifierOrLiteral(importClause.name); 396 importStmt.addLocalName(name, "default"); 397 importStmt.addNodeMap(importClause.name, importClause.name); 398 } 399 400 // import { ... } from "a.js" 401 // import * as a from "a.js" 402 // import defaultExport, * as a from "a.js" 403 if (importClause.namedBindings) { 404 let namedBindings = importClause.namedBindings; 405 // import * as a from "a.js" 406 if (ts.isNamespaceImport(namedBindings)) { 407 let nameSpace = jshelpers.getTextOfIdentifierOrLiteral((<ts.NamespaceImport>namedBindings).name); 408 importStmt.setNameSpace(nameSpace); 409 } 410 411 // import { ... } from "a.js" 412 if (ts.isNamedImports(namedBindings)) { 413 namedBindings.elements.forEach((element) => { 414 let name: string = jshelpers.getTextOfIdentifierOrLiteral(element.name); 415 let exoticName: string = element.propertyName ? jshelpers.getTextOfIdentifierOrLiteral(element.propertyName) : name; 416 importStmt.addLocalName(name, exoticName); 417 importStmt.addNodeMap(element.name, element.propertyName ? element.propertyName : element.name); 418 }); 419 } 420 } 421 } 422 423 this.importStmts.push(importStmt); 424 return importStmt; 425 } 426 427 // [delete it when type system adapts for ESM] 428 private recordExportInfo(node: ts.ExportDeclaration): ModuleStmt { 429 let origNode = <ts.ExportDeclaration>ts.getOriginalNode(node); 430 let exportStmt: ModuleStmt; 431 if (origNode.moduleSpecifier) { 432 if (!ts.isStringLiteral(origNode.moduleSpecifier)) { 433 throw new Error("moduleSpecifier must be a stringLiteral"); 434 } 435 exportStmt = new ModuleStmt(origNode, jshelpers.getTextOfIdentifierOrLiteral(origNode.moduleSpecifier)); 436 } else { 437 exportStmt = new ModuleStmt(origNode); 438 } 439 440 if (origNode.exportClause) { 441 exportStmt.setCopyFlag(false); 442 let namedBindings: ts.NamedExportBindings = origNode.exportClause; 443 if (ts.isNamespaceExport(namedBindings)) { 444 exportStmt.setNameSpace(jshelpers.getTextOfIdentifierOrLiteral((<ts.NamespaceExport>namedBindings).name)); 445 } 446 447 if (ts.isNamedExports(namedBindings)) { 448 namedBindings.elements.forEach((element) => { 449 let name: string = jshelpers.getTextOfIdentifierOrLiteral(element.name); 450 let exoticName: string = element.propertyName ? jshelpers.getTextOfIdentifierOrLiteral(element.propertyName) : name; 451 exportStmt.addLocalName(name, exoticName); 452 exportStmt.addNodeMap(element.name, element.propertyName ? element.propertyName : element.name); 453 }); 454 } 455 } 456 this.exportStmts.push(exportStmt); 457 return exportStmt; 458 } 459 460 private getModuleSpecifier(moduleSpecifier: ts.Expression): string { 461 if (!ts.isStringLiteral(moduleSpecifier)) { 462 throw new Error("moduleSpecifier must be a stringLiteral"); 463 } 464 return jshelpers.getTextOfIdentifierOrLiteral(moduleSpecifier); 465 } 466 467 private recordEcmaNamedBindings(namedBindings: ts.NamedImportBindings, scope: ModuleScope, moduleRequest: string) { 468 // import * as a from "a.js" 469 if (ts.isNamespaceImport(namedBindings)) { 470 let nameSpace = jshelpers.getTextOfIdentifierOrLiteral((<ts.NamespaceImport>namedBindings).name); 471 scope.setDecls(new ConstDecl(nameSpace, namedBindings, ModuleVarKind.NOT)); 472 scope.module().addStarImportEntry(namedBindings, nameSpace, moduleRequest); 473 } else if (ts.isNamedImports(namedBindings)) { 474 if (namedBindings.elements.length == 0) { 475 // import {} from "a.js" 476 scope.module().addEmptyImportEntry(moduleRequest); 477 } 478 // import { ... } from "a.js" 479 namedBindings.elements.forEach((element: any) => { 480 let localName: string = jshelpers.getTextOfIdentifierOrLiteral(element.name); 481 let importName: string = element.propertyName ? jshelpers.getTextOfIdentifierOrLiteral(element.propertyName) : localName; 482 scope.setDecls(new ConstDecl(localName, element, ModuleVarKind.IMPORTED)); 483 scope.module().addImportEntry(element, importName, localName, moduleRequest); 484 }); 485 } else { 486 throw new Error("Unreachable kind for namedBindings"); 487 } 488 } 489 490 private recordEcmaImportClause(importClause: ts.ImportClause, scope: ModuleScope, moduleRequest: string) { 491 // import defaultExport from "a.js" 492 if (importClause.name) { 493 let localName = jshelpers.getTextOfIdentifierOrLiteral(importClause.name); 494 scope.setDecls(new ConstDecl(localName, importClause.name, ModuleVarKind.IMPORTED)); 495 scope.module().addImportEntry(importClause, "default", localName, moduleRequest); 496 } 497 if (importClause.namedBindings) { 498 let namedBindings = importClause.namedBindings; 499 this.recordEcmaNamedBindings(namedBindings, scope, moduleRequest); 500 } 501 } 502 503 private recordEcmaImportInfo(node: ts.ImportDeclaration, scope: Scope) { 504 if (!(scope instanceof ModuleScope)) { 505 return; 506 } 507 508 let moduleRequest: string = this.getModuleSpecifier(node.moduleSpecifier); 509 510 if (node.importClause) { 511 let importClause: ts.ImportClause = node.importClause; 512 this.recordEcmaImportClause(importClause, scope, moduleRequest); 513 } else { 514 // import "a.js" 515 scope.module().addEmptyImportEntry(moduleRequest); 516 } 517 } 518 519 private recordEcmaExportDecl(node: ts.ExportDeclaration, scope: ModuleScope) { 520 if (node.moduleSpecifier) { 521 let moduleRequest: string = this.getModuleSpecifier(node.moduleSpecifier); 522 523 if (node.exportClause) { 524 let namedBindings: ts.NamedExportBindings = node.exportClause; 525 if (ts.isNamespaceExport(namedBindings)) { 526 // export * as m from "mod"; 527 // `export namespace` is not the ECMA2018's feature 528 } else if (ts.isNamedExports(namedBindings)) { 529 if (namedBindings.elements.length == 0) { 530 // export {} from "mod"; 531 scope.module().addEmptyImportEntry(moduleRequest); 532 } 533 // export {x} from "mod"; 534 // export {v as x} from "mod"; 535 namedBindings.elements.forEach((element: any) => { 536 let exportName: string = jshelpers.getTextOfIdentifierOrLiteral(element.name); 537 let importName: string = element.propertyName ? jshelpers.getTextOfIdentifierOrLiteral(element.propertyName) : exportName; 538 scope.module().addIndirectExportEntry(element, importName, exportName, moduleRequest); 539 }); 540 } 541 } else { 542 // export * from "mod"; 543 scope.module().addStarExportEntry(node, moduleRequest); 544 } 545 } else if (node.exportClause && ts.isNamedExports(node.exportClause)) { 546 // export {x}; 547 // export {v as x}; 548 node.exportClause.elements.forEach((element: any) => { 549 let exportName: string = jshelpers.getTextOfIdentifierOrLiteral(element.name); 550 let localName: string = element.propertyName ? jshelpers.getTextOfIdentifierOrLiteral(element.propertyName) : exportName; 551 scope.module().addLocalExportEntry(element, exportName, localName); 552 }); 553 } else { 554 throw new Error("Unreachable node kind for Export Declaration"); 555 } 556 } 557 558 private recordEcmaExportInfo(node: ts.ExportDeclaration | ts.ExportAssignment | ts.VariableStatement | ts.FunctionDeclaration | ts.ClassDeclaration, scope: Scope) { 559 if (!(scope instanceof ModuleScope)) { 560 return; 561 } 562 563 switch (node.kind) { 564 case ts.SyntaxKind.ExportDeclaration: { 565 this.recordEcmaExportDecl(<ts.ExportDeclaration>node, scope); 566 break; 567 } 568 case ts.SyntaxKind.ExportAssignment: { 569 // export default 42; 570 // export default v; 571 // "*default*" is used within this specification as a synthetic name for anonymous default export values. 572 scope.module().addLocalExportEntry(node, "default", "*default*"); 573 scope.setDecls(new LetDecl("*default*", node, ModuleVarKind.EXPORTED)); 574 break; 575 } 576 case ts.SyntaxKind.VariableStatement: { 577 // export var a,b; 578 node.declarationList.declarations.forEach(decl => { 579 let name = jshelpers.getTextOfIdentifierOrLiteral(decl.name); 580 scope.module().addLocalExportEntry(decl, name, name); 581 }); 582 break; 583 } 584 case ts.SyntaxKind.FunctionDeclaration: 585 case ts.SyntaxKind.ClassDeclaration: { 586 if (hasDefaultKeywordModifier(node)) { 587 // HoistableDeclaration : FunctionDecl/GeneratorDecl/AsyncFunctionDecl/AsyncGeneratorDecl 588 // export default function f(){} 589 // export default function(){} 590 // export default class{} 591 let localName = node.name ? jshelpers.getTextOfIdentifierOrLiteral(node.name) : "*default*"; 592 scope.module().addLocalExportEntry(node, "default", localName); 593 } else { 594 // export function f(){} 595 // export class c{} 596 if (!node.name) { 597 throw new DiagnosticError(node, DiagnosticCode.A_class_or_function_declaration_without_the_default_modifier_must_have_a_name, jshelpers.getSourceFileOfNode(node)); 598 } 599 let name = jshelpers.getTextOfIdentifierOrLiteral(node.name!); 600 scope.module().addLocalExportEntry(node, name, name); 601 } 602 break; 603 } 604 default: 605 throw new Error("Unreachable syntax kind for static exporting"); 606 } 607 } 608 609 private recordFuncDecl(node: ts.FunctionDeclaration, scope: Scope, isExport: boolean) { 610 this.recordFuncInfo(node); 611 612 let funcId = <ts.Identifier>(node).name; 613 if (!funcId && !isExport) { 614 // unexported function declaration without name doesn't need to record hoisting. 615 return; 616 } 617 // if function without name must has modifiers of 'export' & 'default' 618 let funcName = funcId ? jshelpers.getTextOfIdentifierOrLiteral(funcId) : '*default*'; 619 let moduleKind = isExport ? ModuleVarKind.EXPORTED : ModuleVarKind.NOT; 620 let funcDecl = new FuncDecl(funcName, node, moduleKind); 621 let hoistScope = scope; 622 let need2AddDecls: boolean = true; 623 if (scope instanceof GlobalScope || scope instanceof ModuleScope) { 624 this.collectHoistDecls(node, <GlobalScope | ModuleScope>hoistScope, funcDecl); 625 } else if (scope instanceof LocalScope) { 626 hoistScope = <Scope>scope.getNearestVariableScope(); 627 if ((hoistScope instanceof FunctionScope) && isFunctionLikeDeclaration(node.parent.parent)) { 628 need2AddDecls = this.collectHoistDecls(node, hoistScope, funcDecl); 629 } 630 } else { 631 LOGD("Function declaration", " in function is collected in its body block"); 632 } 633 if (need2AddDecls) { 634 scope.setDecls(funcDecl); 635 } 636 } 637 638 private recordOtherFunc(node: ts.FunctionLikeDeclaration, scope: Scope) { // functionlikedecalration except function declaration 639 this.recordFuncInfo(node); 640 if (!ts.isFunctionExpression(node) && !ts.isMethodDeclaration(node)) { 641 return; 642 } 643 644 if (node.name && ts.isIdentifier(node.name)) { 645 let funcName = jshelpers.getTextOfIdentifierOrLiteral(node.name); 646 let funcDecl = new FuncDecl(funcName, node, ModuleVarKind.NOT); 647 scope.setDecls(funcDecl); 648 } 649 } 650 651 private recordFuncInfo(node: ts.FunctionLikeDeclaration) { 652 this.recordFunctionParameters(node); 653 this.recordFuncName(node); 654 } 655 656 recordFuncName(node: ts.FunctionLikeDeclaration) { 657 let name: string = ''; 658 if (ts.isConstructorDeclaration(node)) { 659 let classNode = node.parent; 660 name = getClassNameForConstructor(classNode); 661 } else { 662 if (isAnonymousFunctionDefinition(node)) { 663 let outerNode = findOuterNodeOfParenthesis(node); 664 665 if (ts.isVariableDeclaration(outerNode)) { 666 // @ts-ignore 667 let id = outerNode.name; 668 if (ts.isIdentifier(id)) { 669 name = jshelpers.getTextOfIdentifierOrLiteral(id); 670 } 671 } else if (ts.isBinaryExpression(outerNode)) { 672 // @ts-ignore 673 if (outerNode.operatorToken.kind == ts.SyntaxKind.EqualsToken && ts.isIdentifier(outerNode.left)) { 674 // @ts-ignore 675 name = jshelpers.getTextOfIdentifierOrLiteral(outerNode.left); 676 } 677 } else if (ts.isPropertyAssignment(outerNode)) { 678 // @ts-ignore 679 let propName = outerNode.name; 680 if (ts.isIdentifier(propName) || ts.isStringLiteral(propName) || ts.isNumericLiteral(propName)) { 681 name = jshelpers.getTextOfIdentifierOrLiteral(propName); 682 if (name == "__proto__") { 683 name = ''; 684 } 685 } 686 } 687 } else { 688 if (ts.isIdentifier(node.name!)) { 689 name = jshelpers.getTextOfIdentifierOrLiteral(node.name); 690 } 691 } 692 } 693 694 (<FunctionScope>this.getScopeOfNode(node)).setFuncName(name); 695 696 if (name != '') { 697 let funcNameMap = this.funcNameMap; 698 if (funcNameMap.has(name)) { 699 let nums = <number>funcNameMap.get(name); 700 funcNameMap.set(name, ++nums); 701 } else { 702 funcNameMap.set(name, 1); 703 } 704 } 705 } 706 707 recordFunctionParameters(node: ts.FunctionLikeDeclaration) { 708 let parameters = node.parameters; 709 let funcParams: FunctionParameter[] = []; 710 let length = 0; 711 let lengthFlag = true; 712 713 if (parameters) { 714 parameters.forEach(parameter => { 715 // record function.length 716 if (parameter.initializer || this.isRestParameter(parameter)) { 717 lengthFlag = false; 718 } 719 if (lengthFlag) { 720 length++; 721 } 722 723 if (ts.isIdentifier(parameter.name)) { 724 let name = jshelpers.getTextOfIdentifierOrLiteral(<ts.Identifier>parameter.name); 725 funcParams.push(new FunctionParameter(name, parameter.name)); 726 } else { // parameter is binding pattern 727 this.recordPatternParameter(<ts.BindingPattern>parameter.name, funcParams); 728 } 729 }); 730 } 731 (<FunctionScope>this.getScopeOfNode(node)).setParameterLength(length); 732 this.setParametersMap(node, funcParams); 733 } 734 735 recordPatternParameter(pattern: ts.BindingPattern, funcParams: Array<FunctionParameter>) { 736 let name: string = ''; 737 pattern.elements.forEach(bindingElement => { 738 if (ts.isOmittedExpression(bindingElement)) { 739 return; 740 } 741 742 bindingElement = <ts.BindingElement>bindingElement; 743 if (ts.isIdentifier(bindingElement.name)) { 744 name = jshelpers.getTextOfIdentifierOrLiteral(bindingElement.name); 745 funcParams.push(new FunctionParameter(name, bindingElement.name)); 746 } else { // case of binding pattern 747 let innerPattern = <ts.BindingPattern>bindingElement.name; 748 this.recordPatternParameter(innerPattern, funcParams); 749 } 750 }); 751 } 752 753 754 isRestParameter(parameter: ts.ParameterDeclaration) { 755 return parameter.dotDotDotToken ? true : false; 756 } 757 758 private collectHoistDecls(node: ts.Node, scope: VariableScope, decl: Decl): boolean { 759 let declName = decl.name; 760 761 // if variable share a same name with the parameter of its contained function, it should not be hoisted 762 if (scope instanceof FunctionScope) { 763 let nearestFunc = jshelpers.getContainingFunctionDeclaration(node); 764 let functionParameters = this.getParametersOfFunction(<ts.FunctionLikeDeclaration>nearestFunc); 765 if (functionParameters) { 766 for (let i = 0; i < functionParameters.length; i++) { 767 if (functionParameters[i].name == declName) { 768 return false; 769 } 770 } 771 } 772 } 773 774 // Variable named of global identifier should not be hoisted. 775 if (isGlobalIdentifier(declName) && (scope instanceof GlobalScope)) { 776 return true; 777 } 778 779 this.setHoistMap(scope, decl); 780 return false; 781 } 782 783 setScopeMap(node: ts.Node, scope: Scope) { 784 this.scopeMap.set(node, scope); 785 } 786 787 getScopeMap() { 788 return this.scopeMap; 789 } 790 791 getScopeOfNode(node: ts.Node) { 792 return this.scopeMap.get(node); 793 } 794 795 setHoistMap(scope: VariableScope, decl: Decl) { 796 if (!this.hoistMap.has(scope)) { 797 this.hoistMap.set(scope, [decl]); 798 return; 799 } 800 801 let hoistDecls = <Decl[]>this.hoistMap.get(scope); 802 for (let i = 0; i < hoistDecls.length; i++) { 803 if (decl.name == hoistDecls[i].name) { 804 if (decl instanceof FuncDecl) { 805 hoistDecls[i] = decl; 806 } 807 return; 808 } 809 } 810 hoistDecls.push(decl); 811 } 812 813 getHoistMap() { 814 return this.hoistMap; 815 } 816 817 getHoistDeclsOfScope(scope: VariableScope) { 818 return this.hoistMap.get(scope); 819 } 820 821 setParametersMap(node: ts.FunctionLikeDeclaration, parameters: FunctionParameter[]) { 822 this.parametersMap.set(node, parameters); 823 } 824 825 getParametersOfFunction(node: ts.FunctionLikeDeclaration) { 826 return this.parametersMap.get(node); 827 } 828 829 getFuncNameMap() { 830 return this.funcNameMap; 831 } 832} 833