• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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