• 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 { PandaGen } from "./pandagen";
18import { DiagnosticCode, DiagnosticError } from "./diagnostic";
19import { ModuleScope, Scope } from "./scope";
20import { getSourceFileOfNode } from "./jshelpers";
21import { LReference } from "./base/lreference";
22import { Compiler } from "./compiler";
23import { ModuleVariable } from "./variable";
24
25class Entry {
26    node: ts.Node;
27    exportName: string | undefined;
28    localName: string | undefined;
29    importName: string | undefined;
30    moduleRequest: number = -1;
31
32    constructor(node: ts.Node, exportName: string | undefined, localName: string | undefined, importName: string | undefined, moduleRequest?: number) {
33        this.node = node;
34        this.exportName = exportName;
35        this.localName = localName;
36        this.importName = importName;
37        if (moduleRequest !== undefined) {
38            this.moduleRequest = moduleRequest;
39        }
40    }
41}
42
43export class SourceTextModuleRecord {
44    private moduleName: string;
45    private moduleRequests: Array<string> = [];
46    private moduleRequestIdxMap: Map<string, number> = new Map<string, number>();
47
48    private regularImportEntries: Map<string, Entry> = new Map<string, Entry>();
49    private namespaceImportEntries: Array<Entry> = [];
50
51    private localExportEntries: Map<string, Array<Entry>> = new Map<string, Array<Entry>>();
52    private starExportEntries: Array<Entry> = [];
53    private indirectExportEntries: Array<Entry> = [];
54
55    constructor(moduleName: string) {
56        this.moduleName = moduleName;
57    }
58
59    addModuleRequest(moduleRequest: string): number {
60        if (this.moduleRequestIdxMap.has(moduleRequest)) {
61            return this.moduleRequestIdxMap.get(moduleRequest)!;
62        }
63        let index = this.moduleRequests.length;
64        this.moduleRequests.push(moduleRequest);
65        this.moduleRequestIdxMap.set(moduleRequest, index);
66        return index;
67    }
68
69    // Targetcase 1: import x from 'test.js';
70    // Targetcase 2: import {x} from 'test.js';
71    // Targetcase 3: import {x as y} from 'test.js';
72    // Targetcase 4: import defaultExport from 'test.js'
73    addImportEntry(node: ts.Node, importName: string, localName: string, moduleRequest: string): void {
74        let importEntry: Entry = new Entry(node, undefined, localName, importName, this.addModuleRequest(moduleRequest));
75        // We don't care if there's already an entry for this local name, as in that
76        // case we will report an error when declaring the variable.
77        this.regularImportEntries.set(localName, importEntry);
78    }
79
80    // Targetcase 1: import 'test.js'
81    // Targetcase 2: import {} from 'test.js'
82    // Targetcase 3: export {} from 'test.js'
83    addEmptyImportEntry(moduleRequest: string) {
84        this.addModuleRequest(moduleRequest);
85    }
86
87    // Targetcase 1: import * as x from 'test.js';
88    addStarImportEntry(node: ts.Node, localName: string, moduleRequest: string): void {
89        let starImportEntry: Entry = new Entry(node, undefined, localName, undefined, this.addModuleRequest(moduleRequest));
90        this.namespaceImportEntries.push(starImportEntry);
91    }
92
93    // Targetcase 1: export {x};
94    // Targetcase 2: export {x as y};
95    // Targetcase 3: export VariableStatement
96    // Targetcase 4: export Declaration
97    // Targetcase 5: export default ...
98    addLocalExportEntry(node: ts.Node, exportName: string, localName: string): void {
99        let localExportEntry: Entry = new Entry(node, exportName, localName, undefined);
100        if (this.localExportEntries.has(localName)) {
101            this.localExportEntries.get(localName)!.push(localExportEntry);
102        } else {
103            this.localExportEntries.set(localName, [localExportEntry]);
104        }
105    }
106
107    // Targetcase 1: export {x} from 'test.js';
108    // Targetcase 2: export {x as y} from 'test.js';
109    // Targetcase 3: import { x } from 'test.js'; export { x }
110    addIndirectExportEntry(node: ts.Node, importName: string, exportName: string, moduleRequest: string): void {
111        let indirectExportEntry: Entry = new Entry(node, exportName, undefined, importName, this.addModuleRequest(moduleRequest));
112        this.indirectExportEntries.push(indirectExportEntry);
113    }
114
115    // Targetcase 1: export * from 'test.js';
116    addStarExportEntry(node: ts.Node, moduleRequest: string): void {
117        let starExportEntry: Entry = new Entry(node, undefined, undefined, undefined, this.addModuleRequest(moduleRequest));
118        this.starExportEntries.push(starExportEntry);
119    }
120
121    getModuleName(): string {
122        return this.moduleName;
123    }
124
125    getModuleRequests(): string[] {
126        return this.moduleRequests;
127    }
128
129    getRegularImportEntries(): Map<string, Entry> {
130        return this.regularImportEntries;
131    }
132
133    getNamespaceImportEntries(): Entry[] {
134        return this.namespaceImportEntries;
135    }
136
137    getLocalExportEntries(): Map<string, Entry[]> {
138        return this.localExportEntries;
139    }
140
141    getStarExportEntries(): Entry[] {
142        return this.starExportEntries;
143    }
144
145    getIndirectExportEntries(): Entry[] {
146        return this.indirectExportEntries;
147    }
148
149    makeIndirectExportsExplicit(): void {
150        // @ts-ignore
151        this.localExportEntries.forEach((entries: Array<Entry>, localName: string) => {
152            let importEntry: Entry | undefined = this.regularImportEntries.get(localName);
153            if (importEntry) {
154                // get indirect export entries
155                entries.forEach(e => {
156                    e.importName = importEntry.importName;
157                    e.moduleRequest = importEntry.moduleRequest;
158                    e.localName = undefined;
159                    this.indirectExportEntries.push(e);
160                });
161                this.localExportEntries.delete(localName);
162            }
163        });
164    }
165
166    nextDuplicateExportEntry(candidate: Entry, exportNameEntry: Map<string, Entry>, currentCandidate: Entry | undefined): Entry {
167        if (!exportNameEntry.has(candidate.exportName!)) {
168            exportNameEntry.set(candidate.exportName!, candidate);
169            return currentCandidate;
170        }
171
172        if (currentCandidate === undefined) {
173            currentCandidate = candidate;
174        }
175
176        return candidate.node.pos > currentCandidate.node.pos ? candidate : currentCandidate;
177    }
178
179    searchDuplicateExport(): Entry | undefined {
180        let duplicateEntry: Entry | undefined;
181        let exportNameEntry: Map<string, Entry> = new Map<string, Entry>();
182
183        // @ts-ignore
184        this.localExportEntries.forEach((entries: Array<Entry>, localName: string) => {
185            entries.forEach((e: Entry) => {
186                duplicateEntry = this.nextDuplicateExportEntry(e, exportNameEntry, duplicateEntry);
187            });
188        });
189
190        this.indirectExportEntries.forEach((e: Entry) => {
191            duplicateEntry = this.nextDuplicateExportEntry(e, exportNameEntry, duplicateEntry);
192        });
193
194        return duplicateEntry;
195    }
196
197    validateModuleRecordEntries(moduleScope: ModuleScope): void {
198        // check module is well-formed and report errors if not
199        {
200            let dupExportEntry: Entry | undefined = this.searchDuplicateExport();
201            if (dupExportEntry !== undefined) {
202                throw new DiagnosticError(dupExportEntry.node, DiagnosticCode.Module_0_has_already_exported_a_member_named_1,
203                                          getSourceFileOfNode(dupExportEntry.node),
204                                          [getSourceFileOfNode(dupExportEntry.node).fileName, dupExportEntry.exportName]);
205            }
206        }
207
208        this.localExportEntries.forEach((entry: Array<Entry>, localName: string) => {
209            if (!moduleScope.hasDecl(localName) && localName != '*default*') {
210                throw new DiagnosticError(entry[0].node, DiagnosticCode.Module_0_has_no_exported_member_1,
211                                          getSourceFileOfNode(entry[0].node),
212                                          [getSourceFileOfNode(entry[0].node).fileName, localName]);
213            }
214        });
215
216        this.makeIndirectExportsExplicit();
217    }
218
219    setExportedDecls(moduleScope: ModuleScope): void {
220        // @ts-ignore
221        this.localExportEntries.forEach((entry: Array<Entry>, localName: string) => {
222            moduleScope.setExportDecl(localName);
223        })
224    }
225
226    setModuleEnvironment(moduleScope: ModuleScope): void {
227        this.validateModuleRecordEntries(moduleScope);
228        this.setExportedDecls(moduleScope);
229    }
230}
231
232export function setModuleNamespaceImports(compiler: Compiler, moduleScope: Scope, pandagen: PandaGen): void {
233    if (!(moduleScope instanceof ModuleScope)) {
234        return;
235    }
236
237    moduleScope.module().getNamespaceImportEntries().forEach(entry => {
238        let namespaceLref = LReference.generateLReference(compiler, (<ts.NamespaceImport>entry.node).name, true);
239        pandagen.getModuleNamespace(entry.node, entry.moduleRequest);
240        namespaceLref.setValue();
241    });
242}
243
244export function assignIndexToModuleVariable(moduleScope: Scope): void {
245    if (!(moduleScope instanceof ModuleScope)) {
246        return;
247    }
248    let index: number = 0;
249    // @ts-ignore
250    moduleScope.module().getLocalExportEntries().forEach((entries: Array<Entry>, localName: string) => {
251        (<ModuleVariable>moduleScope.findLocal(localName)!).assignIndex(index++);
252    });
253    index = 0;
254    // @ts-ignore
255    moduleScope.module().getRegularImportEntries().forEach((entry: Entry, localName: string) => {
256        (<ModuleVariable>moduleScope.findLocal(localName)!).assignIndex(index++);
257    });
258}