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(node as ImportDeclaration); 88 if (!hasImportStar && getImportNeedsImportStarHelper(node as ImportDeclaration)) { 89 hasImportStar = true; 90 } 91 if (!hasImportDefault && getImportNeedsImportDefaultHelper(node as ImportDeclaration)) { 92 hasImportDefault = true; 93 } 94 break; 95 96 case SyntaxKind.ImportEqualsDeclaration: 97 if ((node as ImportEqualsDeclaration).moduleReference.kind === SyntaxKind.ExternalModuleReference) { 98 // import x = require("mod") 99 externalImports.push(node as ImportEqualsDeclaration); 100 } 101 102 break; 103 104 case SyntaxKind.ExportDeclaration: 105 if ((node as ExportDeclaration).moduleSpecifier) { 106 if (!(node as ExportDeclaration).exportClause) { 107 // export * from "mod" 108 externalImports.push(node as ExportDeclaration); 109 hasExportStarsToExportValues = true; 110 } 111 else { 112 // export * as ns from "mod" 113 // export { x, y } from "mod" 114 externalImports.push(node as ExportDeclaration); 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 ((node as ExportAssignment).isExportEquals && !exportEquals) { 138 // export = x 139 exportEquals = node as ExportAssignment; 140 } 141 break; 142 143 case SyntaxKind.VariableStatement: 144 if (hasSyntacticModifier(node, ModifierFlags.Export)) { 145 for (const decl of (node as VariableStatement).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(node as FunctionDeclaration)); 157 hasExportDefault = true; 158 } 159 } 160 else { 161 // export function x() { } 162 const name = (node as FunctionDeclaration).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(node as ClassDeclaration)); 178 hasExportDefault = true; 179 } 180 } 181 else { 182 // export class x { } 183 const name = (node as ClassDeclaration).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 (resolver.isReferredToAnnotation(specifier) === true) { 205 continue; 206 } 207 if (!uniqueExports.get(idText(specifier.name))) { 208 const name = specifier.propertyName || specifier.name; 209 if (!node.moduleSpecifier) { 210 exportSpecifiers.add(idText(name), specifier); 211 } 212 213 const decl = resolver.getReferencedImportDeclaration(name) 214 || resolver.getReferencedValueDeclaration(name); 215 216 if (decl) { 217 multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(decl), specifier.name); 218 } 219 220 uniqueExports.set(idText(specifier.name), true); 221 exportedNames = append(exportedNames, specifier.name); 222 } 223 } 224 } 225 } 226 227 function collectExportedVariableInfo(decl: VariableDeclaration | BindingElement, uniqueExports: ESMap<string, boolean>, exportedNames: Identifier[] | undefined) { 228 if (isBindingPattern(decl.name)) { 229 for (const element of decl.name.elements) { 230 if (!isOmittedExpression(element)) { 231 exportedNames = collectExportedVariableInfo(element, uniqueExports, exportedNames); 232 } 233 } 234 } 235 else if (!isGeneratedIdentifier(decl.name)) { 236 const text = idText(decl.name); 237 if (!uniqueExports.get(text)) { 238 uniqueExports.set(text, true); 239 exportedNames = append(exportedNames, decl.name); 240 } 241 } 242 return exportedNames; 243 } 244 245 /** Use a sparse array as a multi-map. */ 246 function multiMapSparseArrayAdd<V>(map: V[][], key: number, value: V): V[] { 247 let values = map[key]; 248 if (values) { 249 values.push(value); 250 } 251 else { 252 map[key] = values = [value]; 253 } 254 return values; 255 } 256 257 /** 258 * Used in the module transformer to check if an expression is reasonably without sideeffect, 259 * and thus better to copy into multiple places rather than to cache in a temporary variable 260 * - this is mostly subjective beyond the requirement that the expression not be sideeffecting 261 */ 262 export function isSimpleCopiableExpression(expression: Expression) { 263 return isStringLiteralLike(expression) || 264 expression.kind === SyntaxKind.NumericLiteral || 265 isKeyword(expression.kind) || 266 isIdentifier(expression); 267 } 268 269 /** 270 * A simple inlinable expression is an expression which can be copied into multiple locations 271 * without risk of repeating any sideeffects and whose value could not possibly change between 272 * any such locations 273 */ 274 export function isSimpleInlineableExpression(expression: Expression) { 275 return !isIdentifier(expression) && isSimpleCopiableExpression(expression); 276 } 277 278 export function isCompoundAssignment(kind: BinaryOperator): kind is CompoundAssignmentOperator { 279 return kind >= SyntaxKind.FirstCompoundAssignment 280 && kind <= SyntaxKind.LastCompoundAssignment; 281 } 282 283 export function getNonAssignmentOperatorForCompoundAssignment(kind: CompoundAssignmentOperator): LogicalOperatorOrHigher | SyntaxKind.QuestionQuestionToken { 284 switch (kind) { 285 case SyntaxKind.PlusEqualsToken: return SyntaxKind.PlusToken; 286 case SyntaxKind.MinusEqualsToken: return SyntaxKind.MinusToken; 287 case SyntaxKind.AsteriskEqualsToken: return SyntaxKind.AsteriskToken; 288 case SyntaxKind.AsteriskAsteriskEqualsToken: return SyntaxKind.AsteriskAsteriskToken; 289 case SyntaxKind.SlashEqualsToken: return SyntaxKind.SlashToken; 290 case SyntaxKind.PercentEqualsToken: return SyntaxKind.PercentToken; 291 case SyntaxKind.LessThanLessThanEqualsToken: return SyntaxKind.LessThanLessThanToken; 292 case SyntaxKind.GreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanToken; 293 case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanGreaterThanToken; 294 case SyntaxKind.AmpersandEqualsToken: return SyntaxKind.AmpersandToken; 295 case SyntaxKind.BarEqualsToken: return SyntaxKind.BarToken; 296 case SyntaxKind.CaretEqualsToken: return SyntaxKind.CaretToken; 297 case SyntaxKind.BarBarEqualsToken: return SyntaxKind.BarBarToken; 298 case SyntaxKind.AmpersandAmpersandEqualsToken: return SyntaxKind.AmpersandAmpersandToken; 299 case SyntaxKind.QuestionQuestionEqualsToken: return SyntaxKind.QuestionQuestionToken; 300 301 } 302 } 303 304 /** 305 * @returns Contained super() call from descending into the statement ignoring parentheses, if that call exists. 306 */ 307 export function getSuperCallFromStatement(statement: Statement) { 308 if (!isExpressionStatement(statement)) { 309 return undefined; 310 } 311 312 const expression = skipParentheses(statement.expression); 313 return isSuperCall(expression) 314 ? expression 315 : undefined; 316 } 317 318 /** 319 * @returns The index (after prologue statements) of a super call, or -1 if not found. 320 */ 321 export function findSuperStatementIndex(statements: NodeArray<Statement>, indexAfterLastPrologueStatement: number) { 322 for (let i = indexAfterLastPrologueStatement; i < statements.length; i += 1) { 323 const statement = statements[i]; 324 325 if (getSuperCallFromStatement(statement)) { 326 return i; 327 } 328 } 329 330 return -1; 331 } 332 333 /** 334 * Gets all the static or all the instance property declarations of a class 335 * 336 * @param node The class node. 337 * @param isStatic A value indicating whether to get properties from the static or instance side of the class. 338 */ 339 export function getProperties(node: ClassExpression | ClassDeclaration | StructDeclaration, requireInitializer: true, isStatic: boolean): readonly InitializedPropertyDeclaration[]; 340 export function getProperties(node: ClassExpression | ClassDeclaration | StructDeclaration, requireInitializer: boolean, isStatic: boolean): readonly PropertyDeclaration[]; 341 export function getProperties(node: ClassExpression | ClassDeclaration | StructDeclaration, requireInitializer: boolean, isStatic: boolean): readonly PropertyDeclaration[] { 342 return filter(node.members, m => isInitializedOrStaticProperty(m, requireInitializer, isStatic)) as PropertyDeclaration[]; 343 } 344 345 function isStaticPropertyDeclarationOrClassStaticBlockDeclaration(element: ClassElement): element is PropertyDeclaration | ClassStaticBlockDeclaration { 346 return isStaticPropertyDeclaration(element) || isClassStaticBlockDeclaration(element); 347 } 348 349 export function getStaticPropertiesAndClassStaticBlock(node: ClassExpression | ClassDeclaration): readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[]; 350 export function getStaticPropertiesAndClassStaticBlock(node: ClassExpression | ClassDeclaration): readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[]; 351 export function getStaticPropertiesAndClassStaticBlock(node: ClassExpression | ClassDeclaration): readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[] { 352 return filter(node.members, isStaticPropertyDeclarationOrClassStaticBlockDeclaration); 353 } 354 355 /** 356 * Is a class element either a static or an instance property declaration with an initializer? 357 * 358 * @param member The class element node. 359 * @param isStatic A value indicating whether the member should be a static or instance member. 360 */ 361 function isInitializedOrStaticProperty(member: ClassElement, requireInitializer: boolean, isStatic: boolean) { 362 return isPropertyDeclaration(member) 363 && (!!member.initializer || !requireInitializer) 364 && hasStaticModifier(member) === isStatic; 365 } 366 367 function isStaticPropertyDeclaration(member: ClassElement) { 368 return isPropertyDeclaration(member) && hasStaticModifier(member); 369 } 370 371 /** 372 * Gets a value indicating whether a class element is either a static or an instance property declaration with an initializer. 373 * 374 * @param member The class element node. 375 * @param isStatic A value indicating whether the member should be a static or instance member. 376 */ 377 export function isInitializedProperty(member: ClassElement): member is PropertyDeclaration & { initializer: Expression; } { 378 return member.kind === SyntaxKind.PropertyDeclaration 379 && (member as PropertyDeclaration).initializer !== undefined; 380 } 381 382 /** 383 * Gets a value indicating whether a class element is a private instance method or accessor. 384 * 385 * @param member The class element node. 386 */ 387 export function isNonStaticMethodOrAccessorWithPrivateName(member: ClassElement): member is PrivateIdentifierMethodDeclaration | PrivateIdentifierAccessorDeclaration | PrivateIdentifierAutoAccessorPropertyDeclaration { 388 return !isStatic(member) && (isMethodOrAccessor(member) || isAutoAccessorPropertyDeclaration(member)) && isPrivateIdentifier(member.name); 389 } 390 391 /** 392 * Gets an array of arrays of decorators for the parameters of a function-like node. 393 * The offset into the result array should correspond to the offset of the parameter. 394 * 395 * @param node The function-like node. 396 */ 397 function getDecoratorsOfParameters(node: FunctionLikeDeclaration | undefined) { 398 let decorators: (readonly Decorator[] | undefined)[] | undefined; 399 if (node) { 400 const parameters = node.parameters; 401 const firstParameterIsThis = parameters.length > 0 && parameterIsThisKeyword(parameters[0]); 402 const firstParameterOffset = firstParameterIsThis ? 1 : 0; 403 const numParameters = firstParameterIsThis ? parameters.length - 1 : parameters.length; 404 for (let i = 0; i < numParameters; i++) { 405 const parameter = parameters[i + firstParameterOffset]; 406 if (decorators || hasDecorators(parameter)) { 407 if (!decorators) { 408 decorators = new Array(numParameters); 409 } 410 411 decorators[i] = getDecorators(parameter); 412 } 413 } 414 } 415 416 return decorators; 417 } 418 419 /** 420 * Gets an AllDecorators object containing the decorators for the class and the decorators for the 421 * parameters of the constructor of the class. 422 * 423 * @param node The class node. 424 */ 425 export function getAllDecoratorsOfClass(node: ClassLikeDeclaration): AllDecorators | undefined { 426 const decorators = getDecorators(node); 427 const parameters = getDecoratorsOfParameters(getFirstConstructorWithBody(node)); 428 if (!some(decorators) && !some(parameters)) { 429 return undefined; 430 } 431 432 return { 433 decorators, 434 parameters 435 }; 436 } 437 438 /** 439 * Gets an AllDecorators object containing the decorators for the member and its parameters. 440 * 441 * @param parent The class node that contains the member. 442 * @param member The class member. 443 */ 444 export function getAllDecoratorsOfClassElement(member: ClassElement, parent: ClassLikeDeclaration): AllDecorators | undefined { 445 switch (member.kind) { 446 case SyntaxKind.GetAccessor: 447 case SyntaxKind.SetAccessor: 448 return getAllDecoratorsOfAccessors(member as AccessorDeclaration, parent); 449 450 case SyntaxKind.MethodDeclaration: 451 return getAllDecoratorsOfMethod(member as MethodDeclaration); 452 453 case SyntaxKind.PropertyDeclaration: 454 return getAllDecoratorsOfProperty(member as PropertyDeclaration); 455 456 default: 457 return undefined; 458 } 459 } 460 461 /** 462 * Gets an AllDecorators object containing the decorators for the accessor and its parameters. 463 * 464 * @param parent The class node that contains the accessor. 465 * @param accessor The class accessor member. 466 */ 467 function getAllDecoratorsOfAccessors(accessor: AccessorDeclaration, parent: ClassExpression | ClassDeclaration | StructDeclaration): AllDecorators | undefined { 468 if (!accessor.body) { 469 return undefined; 470 } 471 472 const { firstAccessor, secondAccessor, getAccessor, setAccessor } = getAllAccessorDeclarations(parent.members, accessor); 473 const firstAccessorWithDecorators = 474 hasDecorators(firstAccessor) ? firstAccessor : 475 secondAccessor && hasDecorators(secondAccessor) ? secondAccessor : 476 undefined; 477 478 if (!firstAccessorWithDecorators || accessor !== firstAccessorWithDecorators) { 479 return undefined; 480 } 481 482 const decorators = getDecorators(firstAccessorWithDecorators); 483 const parameters = getDecoratorsOfParameters(setAccessor); 484 if (!some(decorators) && !some(parameters)) { 485 return undefined; 486 } 487 488 return { 489 decorators, 490 parameters, 491 getDecorators: getAccessor && getDecorators(getAccessor), 492 setDecorators: setAccessor && getDecorators(setAccessor) 493 }; 494 } 495 496 /** 497 * Gets an AllDecorators object containing the decorators for the method and its parameters. 498 * 499 * @param method The class method member. 500 */ 501 function getAllDecoratorsOfMethod(method: MethodDeclaration): AllDecorators | undefined { 502 if (!method.body) { 503 return undefined; 504 } 505 506 const decorators = getDecorators(method); 507 const parameters = getDecoratorsOfParameters(method); 508 if (!some(decorators) && !some(parameters)) { 509 return undefined; 510 } 511 512 return { decorators, parameters }; 513 } 514 515 /** 516 * Gets an AllDecorators object containing the decorators for the property. 517 * 518 * @param property The class property member. 519 */ 520 function getAllDecoratorsOfProperty(property: PropertyDeclaration): AllDecorators | undefined { 521 const decorators = getDecorators(property); 522 if (!some(decorators)) { 523 return undefined; 524 525 } 526 527 return { decorators }; 528 } 529 530} 531