1/* @internal */ 2namespace ts { 3 export function getOriginalNodeId(node: Node) { 4 node = getOriginalNode(node); 5 return node ? getNodeId(node) : 0; 6 } 7 8 export interface ExternalModuleInfo { 9 externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[]; // imports of other external modules 10 externalHelpersImportDeclaration: ImportDeclaration | undefined; // import of external helpers 11 exportSpecifiers: ESMap<string, ExportSpecifier[]>; // file-local export specifiers by name (no reexports) 12 exportedBindings: Identifier[][]; // exported names of local declarations 13 exportedNames: Identifier[] | undefined; // all exported names in the module, both local and reexported 14 exportEquals: ExportAssignment | undefined; // an export= declaration if one was present 15 hasExportStarsToExportValues: boolean; // whether this module contains export* 16 } 17 18 function containsDefaultReference(node: NamedImportBindings | undefined) { 19 if (!node) return false; 20 if (!isNamedImports(node)) return false; 21 return some(node.elements, isNamedDefaultReference); 22 } 23 24 function isNamedDefaultReference(e: ImportSpecifier): boolean { 25 return e.propertyName !== undefined && e.propertyName.escapedText === InternalSymbolName.Default; 26 } 27 28 export function chainBundle(context: CoreTransformationContext, transformSourceFile: (x: SourceFile) => SourceFile): (x: SourceFile | Bundle) => SourceFile | Bundle { 29 return transformSourceFileOrBundle; 30 31 function transformSourceFileOrBundle(node: SourceFile | Bundle) { 32 return node.kind === SyntaxKind.SourceFile ? transformSourceFile(node) : transformBundle(node); 33 } 34 35 function transformBundle(node: Bundle) { 36 return context.factory.createBundle(map(node.sourceFiles, transformSourceFile), node.prepends); 37 } 38 } 39 40 export function getExportNeedsImportStarHelper(node: ExportDeclaration): boolean { 41 return !!getNamespaceDeclarationNode(node); 42 } 43 44 export function getImportNeedsImportStarHelper(node: ImportDeclaration): boolean { 45 if (!!getNamespaceDeclarationNode(node)) { 46 return true; 47 } 48 const bindings = node.importClause && node.importClause.namedBindings; 49 if (!bindings) { 50 return false; 51 } 52 if (!isNamedImports(bindings)) return false; 53 let defaultRefCount = 0; 54 for (const binding of bindings.elements) { 55 if (isNamedDefaultReference(binding)) { 56 defaultRefCount++; 57 } 58 } 59 // Import star is required if there's default named refs mixed with non-default refs, or if theres non-default refs and it has a default import 60 return (defaultRefCount > 0 && defaultRefCount !== bindings.elements.length) || (!!(bindings.elements.length - defaultRefCount) && isDefaultImport(node)); 61 } 62 63 export function getImportNeedsImportDefaultHelper(node: ImportDeclaration): boolean { 64 // Import default is needed if there's a default import or a default ref and no other refs (meaning an import star helper wasn't requested) 65 return !getImportNeedsImportStarHelper(node) && (isDefaultImport(node) || (!!node.importClause && isNamedImports(node.importClause.namedBindings!) && containsDefaultReference(node.importClause.namedBindings))); // TODO: GH#18217 66 } 67 68 export function collectExternalModuleInfo(context: TransformationContext, sourceFile: SourceFile, resolver: EmitResolver, compilerOptions: CompilerOptions): ExternalModuleInfo { 69 const externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[] = []; 70 const exportSpecifiers = createMultiMap<ExportSpecifier>(); 71 const exportedBindings: Identifier[][] = []; 72 const uniqueExports = new Map<string, boolean>(); 73 let exportedNames: Identifier[] | undefined; 74 let hasExportDefault = false; 75 let exportEquals: ExportAssignment | undefined; 76 let hasExportStarsToExportValues = false; 77 let hasImportStar = false; 78 let hasImportDefault = false; 79 80 for (const node of sourceFile.statements) { 81 switch (node.kind) { 82 case SyntaxKind.ImportDeclaration: 83 // import "mod" 84 // import x from "mod" 85 // import * as x from "mod" 86 // import { x, y } from "mod" 87 externalImports.push(<ImportDeclaration>node); 88 if (!hasImportStar && getImportNeedsImportStarHelper(<ImportDeclaration>node)) { 89 hasImportStar = true; 90 } 91 if (!hasImportDefault && getImportNeedsImportDefaultHelper(<ImportDeclaration>node)) { 92 hasImportDefault = true; 93 } 94 break; 95 96 case SyntaxKind.ImportEqualsDeclaration: 97 if ((<ImportEqualsDeclaration>node).moduleReference.kind === SyntaxKind.ExternalModuleReference) { 98 // import x = require("mod") 99 externalImports.push(<ImportEqualsDeclaration>node); 100 } 101 102 break; 103 104 case SyntaxKind.ExportDeclaration: 105 if ((<ExportDeclaration>node).moduleSpecifier) { 106 if (!(<ExportDeclaration>node).exportClause) { 107 // export * from "mod" 108 externalImports.push(<ExportDeclaration>node); 109 hasExportStarsToExportValues = true; 110 } 111 else { 112 // export * as ns from "mod" 113 // export { x, y } from "mod" 114 externalImports.push(<ExportDeclaration>node); 115 if (isNamedExports((node as ExportDeclaration).exportClause!)) { 116 addExportedNamesForExportDeclaration(node as ExportDeclaration); 117 } 118 else { 119 const name = ((node as ExportDeclaration).exportClause as NamespaceExport).name; 120 if (!uniqueExports.get(idText(name))) { 121 multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), name); 122 uniqueExports.set(idText(name), true); 123 exportedNames = append(exportedNames, name); 124 } 125 // we use the same helpers for `export * as ns` as we do for `import * as ns` 126 hasImportStar = true; 127 } 128 } 129 } 130 else { 131 // export { x, y } 132 addExportedNamesForExportDeclaration(node as ExportDeclaration); 133 } 134 break; 135 136 case SyntaxKind.ExportAssignment: 137 if ((<ExportAssignment>node).isExportEquals && !exportEquals) { 138 // export = x 139 exportEquals = <ExportAssignment>node; 140 } 141 break; 142 143 case SyntaxKind.VariableStatement: 144 if (hasSyntacticModifier(node, ModifierFlags.Export)) { 145 for (const decl of (<VariableStatement>node).declarationList.declarations) { 146 exportedNames = collectExportedVariableInfo(decl, uniqueExports, exportedNames); 147 } 148 } 149 break; 150 151 case SyntaxKind.FunctionDeclaration: 152 if (hasSyntacticModifier(node, ModifierFlags.Export)) { 153 if (hasSyntacticModifier(node, ModifierFlags.Default)) { 154 // export default function() { } 155 if (!hasExportDefault) { 156 multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), context.factory.getDeclarationName(<FunctionDeclaration>node)); 157 hasExportDefault = true; 158 } 159 } 160 else { 161 // export function x() { } 162 const name = (<FunctionDeclaration>node).name!; 163 if (!uniqueExports.get(idText(name))) { 164 multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), name); 165 uniqueExports.set(idText(name), true); 166 exportedNames = append(exportedNames, name); 167 } 168 } 169 } 170 break; 171 172 case SyntaxKind.ClassDeclaration: 173 if (hasSyntacticModifier(node, ModifierFlags.Export)) { 174 if (hasSyntacticModifier(node, ModifierFlags.Default)) { 175 // export default class { } 176 if (!hasExportDefault) { 177 multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), context.factory.getDeclarationName(<ClassDeclaration>node)); 178 hasExportDefault = true; 179 } 180 } 181 else { 182 // export class x { } 183 const name = (<ClassDeclaration>node).name; 184 if (name && !uniqueExports.get(idText(name))) { 185 multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), name); 186 uniqueExports.set(idText(name), true); 187 exportedNames = append(exportedNames, name); 188 } 189 } 190 } 191 break; 192 } 193 } 194 195 const externalHelpersImportDeclaration = createExternalHelpersImportDeclarationIfNeeded(context.factory, context.getEmitHelperFactory(), sourceFile, compilerOptions, hasExportStarsToExportValues, hasImportStar, hasImportDefault); 196 if (externalHelpersImportDeclaration) { 197 externalImports.unshift(externalHelpersImportDeclaration); 198 } 199 200 return { externalImports, exportSpecifiers, exportEquals, hasExportStarsToExportValues, exportedBindings, exportedNames, externalHelpersImportDeclaration }; 201 202 function addExportedNamesForExportDeclaration(node: ExportDeclaration) { 203 for (const specifier of cast(node.exportClause, isNamedExports).elements) { 204 if (!uniqueExports.get(idText(specifier.name))) { 205 const name = specifier.propertyName || specifier.name; 206 if (!node.moduleSpecifier) { 207 exportSpecifiers.add(idText(name), specifier); 208 } 209 210 const decl = resolver.getReferencedImportDeclaration(name) 211 || resolver.getReferencedValueDeclaration(name); 212 213 if (decl) { 214 multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(decl), specifier.name); 215 } 216 217 uniqueExports.set(idText(specifier.name), true); 218 exportedNames = append(exportedNames, specifier.name); 219 } 220 } 221 } 222 } 223 224 function collectExportedVariableInfo(decl: VariableDeclaration | BindingElement, uniqueExports: ESMap<string, boolean>, exportedNames: Identifier[] | undefined) { 225 if (isBindingPattern(decl.name)) { 226 for (const element of decl.name.elements) { 227 if (!isOmittedExpression(element)) { 228 exportedNames = collectExportedVariableInfo(element, uniqueExports, exportedNames); 229 } 230 } 231 } 232 else if (!isGeneratedIdentifier(decl.name)) { 233 const text = idText(decl.name); 234 if (!uniqueExports.get(text)) { 235 uniqueExports.set(text, true); 236 exportedNames = append(exportedNames, decl.name); 237 } 238 } 239 return exportedNames; 240 } 241 242 /** Use a sparse array as a multi-map. */ 243 function multiMapSparseArrayAdd<V>(map: V[][], key: number, value: V): V[] { 244 let values = map[key]; 245 if (values) { 246 values.push(value); 247 } 248 else { 249 map[key] = values = [value]; 250 } 251 return values; 252 } 253 254 /** 255 * Used in the module transformer to check if an expression is reasonably without sideeffect, 256 * and thus better to copy into multiple places rather than to cache in a temporary variable 257 * - this is mostly subjective beyond the requirement that the expression not be sideeffecting 258 */ 259 export function isSimpleCopiableExpression(expression: Expression) { 260 return isStringLiteralLike(expression) || 261 expression.kind === SyntaxKind.NumericLiteral || 262 isKeyword(expression.kind) || 263 isIdentifier(expression); 264 } 265 266 /** 267 * A simple inlinable expression is an expression which can be copied into multiple locations 268 * without risk of repeating any sideeffects and whose value could not possibly change between 269 * any such locations 270 */ 271 export function isSimpleInlineableExpression(expression: Expression) { 272 return !isIdentifier(expression) && isSimpleCopiableExpression(expression) || 273 isWellKnownSymbolSyntactically(expression); 274 } 275 276 export function isCompoundAssignment(kind: BinaryOperator): kind is CompoundAssignmentOperator { 277 return kind >= SyntaxKind.FirstCompoundAssignment 278 && kind <= SyntaxKind.LastCompoundAssignment; 279 } 280 281 export function getNonAssignmentOperatorForCompoundAssignment(kind: CompoundAssignmentOperator): LogicalOperatorOrHigher | SyntaxKind.QuestionQuestionToken { 282 switch (kind) { 283 case SyntaxKind.PlusEqualsToken: return SyntaxKind.PlusToken; 284 case SyntaxKind.MinusEqualsToken: return SyntaxKind.MinusToken; 285 case SyntaxKind.AsteriskEqualsToken: return SyntaxKind.AsteriskToken; 286 case SyntaxKind.AsteriskAsteriskEqualsToken: return SyntaxKind.AsteriskAsteriskToken; 287 case SyntaxKind.SlashEqualsToken: return SyntaxKind.SlashToken; 288 case SyntaxKind.PercentEqualsToken: return SyntaxKind.PercentToken; 289 case SyntaxKind.LessThanLessThanEqualsToken: return SyntaxKind.LessThanLessThanToken; 290 case SyntaxKind.GreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanToken; 291 case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanGreaterThanToken; 292 case SyntaxKind.AmpersandEqualsToken: return SyntaxKind.AmpersandToken; 293 case SyntaxKind.BarEqualsToken: return SyntaxKind.BarToken; 294 case SyntaxKind.CaretEqualsToken: return SyntaxKind.CaretToken; 295 case SyntaxKind.BarBarEqualsToken: return SyntaxKind.BarBarToken; 296 case SyntaxKind.AmpersandAmpersandEqualsToken: return SyntaxKind.AmpersandAmpersandToken; 297 case SyntaxKind.QuestionQuestionEqualsToken: return SyntaxKind.QuestionQuestionToken; 298 299 } 300 } 301 302 /** 303 * Adds super call and preceding prologue directives into the list of statements. 304 * 305 * @param ctor The constructor node. 306 * @param result The list of statements. 307 * @param visitor The visitor to apply to each node added to the result array. 308 * @returns index of the statement that follows super call 309 */ 310 export function addPrologueDirectivesAndInitialSuperCall(factory: NodeFactory, ctor: ConstructorDeclaration, result: Statement[], visitor: Visitor): number { 311 if (ctor.body) { 312 const statements = ctor.body.statements; 313 // add prologue directives to the list (if any) 314 const index = factory.copyPrologue(statements, result, /*ensureUseStrict*/ false, visitor); 315 if (index === statements.length) { 316 // list contains nothing but prologue directives (or empty) - exit 317 return index; 318 } 319 320 const superIndex = findIndex(statements, s => isExpressionStatement(s) && isSuperCall(s.expression), index); 321 if (superIndex > -1) { 322 for (let i = index; i <= superIndex; i++) { 323 result.push(visitNode(statements[i], visitor, isStatement)); 324 } 325 return superIndex + 1; 326 } 327 328 return index; 329 } 330 331 return 0; 332 } 333 334 /** 335 * Gets all the static or all the instance property declarations of a class 336 * 337 * @param node The class node. 338 * @param isStatic A value indicating whether to get properties from the static or instance side of the class. 339 */ 340 export function getProperties(node: ClassExpression | ClassDeclaration, requireInitializer: true, isStatic: boolean): readonly InitializedPropertyDeclaration[]; 341 export function getProperties(node: ClassExpression | ClassDeclaration, requireInitializer: boolean, isStatic: boolean): readonly PropertyDeclaration[]; 342 export function getProperties(node: ClassExpression | ClassDeclaration, requireInitializer: boolean, isStatic: boolean): readonly PropertyDeclaration[] { 343 return filter(node.members, m => isInitializedOrStaticProperty(m, requireInitializer, isStatic)) as PropertyDeclaration[]; 344 } 345 346 /** 347 * Is a class element either a static or an instance property declaration with an initializer? 348 * 349 * @param member The class element node. 350 * @param isStatic A value indicating whether the member should be a static or instance member. 351 */ 352 function isInitializedOrStaticProperty(member: ClassElement, requireInitializer: boolean, isStatic: boolean) { 353 return isPropertyDeclaration(member) 354 && (!!member.initializer || !requireInitializer) 355 && hasStaticModifier(member) === isStatic; 356 } 357 358 /** 359 * Gets a value indicating whether a class element is either a static or an instance property declaration with an initializer. 360 * 361 * @param member The class element node. 362 * @param isStatic A value indicating whether the member should be a static or instance member. 363 */ 364 export function isInitializedProperty(member: ClassElement): member is PropertyDeclaration & { initializer: Expression; } { 365 return member.kind === SyntaxKind.PropertyDeclaration 366 && (<PropertyDeclaration>member).initializer !== undefined; 367 } 368} 369