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