• 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() {
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