/* * Copyright (c) 2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import * as ts from "typescript"; import { PandaGen } from "./pandagen"; import { DiagnosticCode, DiagnosticError } from "./diagnostic"; import { ModuleScope, Scope } from "./scope"; import { getSourceFileOfNode } from "./jshelpers"; import { LReference } from "./base/lreference"; import { Compiler } from "./compiler"; import { ModuleVariable } from "./variable"; class Entry { node: ts.Node; exportName: string | undefined; localName: string | undefined; importName: string | undefined; moduleRequest: number = -1; constructor(node: ts.Node, exportName: string | undefined, localName: string | undefined, importName: string | undefined, moduleRequest?: number) { this.node = node; this.exportName = exportName; this.localName = localName; this.importName = importName; if (moduleRequest !== undefined) { this.moduleRequest = moduleRequest; } } } export class SourceTextModuleRecord { private moduleName: string; private moduleRequests: Array = []; private moduleRequestIdxMap: Map = new Map(); private regularImportEntries: Map = new Map(); private namespaceImportEntries: Array = []; private localExportEntries: Map> = new Map>(); private starExportEntries: Array = []; private indirectExportEntries: Array = []; constructor(moduleName: string) { this.moduleName = moduleName; } addModuleRequest(moduleRequest: string): number { if (this.moduleRequestIdxMap.has(moduleRequest)) { return this.moduleRequestIdxMap.get(moduleRequest)!; } let index = this.moduleRequests.length; this.moduleRequests.push(moduleRequest); this.moduleRequestIdxMap.set(moduleRequest, index); return index; } // Targetcase 1: import x from 'test.js'; // Targetcase 2: import {x} from 'test.js'; // Targetcase 3: import {x as y} from 'test.js'; // Targetcase 4: import defaultExport from 'test.js' addImportEntry(node: ts.Node, importName: string, localName: string, moduleRequest: string): void { let importEntry: Entry = new Entry(node, undefined, localName, importName, this.addModuleRequest(moduleRequest)); // We don't care if there's already an entry for this local name, as in that // case we will report an error when declaring the variable. this.regularImportEntries.set(localName, importEntry); } // Targetcase 1: import 'test.js' // Targetcase 2: import {} from 'test.js' // Targetcase 3: export {} from 'test.js' addEmptyImportEntry(moduleRequest: string) { this.addModuleRequest(moduleRequest); } // Targetcase 1: import * as x from 'test.js'; addStarImportEntry(node: ts.Node, localName: string, moduleRequest: string): void { let starImportEntry: Entry = new Entry(node, undefined, localName, undefined, this.addModuleRequest(moduleRequest)); this.namespaceImportEntries.push(starImportEntry); } // Targetcase 1: export {x}; // Targetcase 2: export {x as y}; // Targetcase 3: export VariableStatement // Targetcase 4: export Declaration // Targetcase 5: export default ... addLocalExportEntry(node: ts.Node, exportName: string, localName: string): void { let localExportEntry: Entry = new Entry(node, exportName, localName, undefined); if (this.localExportEntries.has(localName)) { this.localExportEntries.get(localName)!.push(localExportEntry); } else { this.localExportEntries.set(localName, [localExportEntry]); } } // Targetcase 1: export {x} from 'test.js'; // Targetcase 2: export {x as y} from 'test.js'; // Targetcase 3: import { x } from 'test.js'; export { x } addIndirectExportEntry(node: ts.Node, importName: string, exportName: string, moduleRequest: string): void { let indirectExportEntry: Entry = new Entry(node, exportName, undefined, importName, this.addModuleRequest(moduleRequest)); this.indirectExportEntries.push(indirectExportEntry); } // Targetcase 1: export * from 'test.js'; addStarExportEntry(node: ts.Node, moduleRequest: string): void { let starExportEntry: Entry = new Entry(node, undefined, undefined, undefined, this.addModuleRequest(moduleRequest)); this.starExportEntries.push(starExportEntry); } getModuleName(): string { return this.moduleName; } getModuleRequests(): string[] { return this.moduleRequests; } getRegularImportEntries(): Map { return this.regularImportEntries; } getNamespaceImportEntries(): Entry[] { return this.namespaceImportEntries; } getLocalExportEntries(): Map { return this.localExportEntries; } getStarExportEntries(): Entry[] { return this.starExportEntries; } getIndirectExportEntries(): Entry[] { return this.indirectExportEntries; } makeIndirectExportsExplicit(): void { // @ts-ignore this.localExportEntries.forEach((entries: Array, localName: string) => { let importEntry: Entry | undefined = this.regularImportEntries.get(localName); if (importEntry) { // get indirect export entries entries.forEach(e => { e.importName = importEntry.importName; e.moduleRequest = importEntry.moduleRequest; e.localName = undefined; this.indirectExportEntries.push(e); }); this.localExportEntries.delete(localName); } }); } nextDuplicateExportEntry(candidate: Entry, exportNameEntry: Map, currentCandidate: Entry | undefined): Entry { if (!exportNameEntry.has(candidate.exportName!)) { exportNameEntry.set(candidate.exportName!, candidate); return currentCandidate; } if (currentCandidate === undefined) { currentCandidate = candidate; } return candidate.node.pos > currentCandidate.node.pos ? candidate : currentCandidate; } searchDuplicateExport(): Entry | undefined { let duplicateEntry: Entry | undefined; let exportNameEntry: Map = new Map(); // @ts-ignore this.localExportEntries.forEach((entries: Array, localName: string) => { entries.forEach((e: Entry) => { duplicateEntry = this.nextDuplicateExportEntry(e, exportNameEntry, duplicateEntry); }); }); this.indirectExportEntries.forEach((e: Entry) => { duplicateEntry = this.nextDuplicateExportEntry(e, exportNameEntry, duplicateEntry); }); return duplicateEntry; } validateModuleRecordEntries(moduleScope: ModuleScope): void { // check module is well-formed and report errors if not { let dupExportEntry: Entry | undefined = this.searchDuplicateExport(); if (dupExportEntry !== undefined) { throw new DiagnosticError(dupExportEntry.node, DiagnosticCode.Module_0_has_already_exported_a_member_named_1, getSourceFileOfNode(dupExportEntry.node), [getSourceFileOfNode(dupExportEntry.node).fileName, dupExportEntry.exportName]); } } this.localExportEntries.forEach((entry: Array, localName: string) => { if (!moduleScope.hasDecl(localName) && localName != '*default*') { throw new DiagnosticError(entry[0].node, DiagnosticCode.Module_0_has_no_exported_member_1, getSourceFileOfNode(entry[0].node), [getSourceFileOfNode(entry[0].node).fileName, localName]); } }); this.makeIndirectExportsExplicit(); } setExportedDecls(moduleScope: ModuleScope): void { // @ts-ignore this.localExportEntries.forEach((entry: Array, localName: string) => { moduleScope.setExportDecl(localName); }) } setModuleEnvironment(moduleScope: ModuleScope): void { this.validateModuleRecordEntries(moduleScope); this.setExportedDecls(moduleScope); } } export function setModuleNamespaceImports(compiler: Compiler, moduleScope: Scope, pandagen: PandaGen): void { if (!(moduleScope instanceof ModuleScope)) { return; } moduleScope.module().getNamespaceImportEntries().forEach(entry => { let namespaceLref = LReference.generateLReference(compiler, (entry.node).name, true); pandagen.getModuleNamespace(entry.node, entry.moduleRequest); namespaceLref.setValue(); }); } export function assignIndexToModuleVariable(moduleScope: Scope): void { if (!(moduleScope instanceof ModuleScope)) { return; } let index: number = 0; // @ts-ignore moduleScope.module().getLocalExportEntries().forEach((entries: Array, localName: string) => { (moduleScope.findLocal(localName)!).assignIndex(index++); }); index = 0; // @ts-ignore moduleScope.module().getRegularImportEntries().forEach((entry: Entry, localName: string) => { (moduleScope.findLocal(localName)!).assignIndex(index++); }); }