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}