1import { 2 AccessorDeclaration, AllDecorators, append, BinaryOperator, BindingElement, Bundle, cast, ClassDeclaration, 3 ClassElement, ClassExpression, ClassLikeDeclaration, ClassStaticBlockDeclaration, CompilerOptions, 4 CompoundAssignmentOperator, CoreTransformationContext, createExternalHelpersImportDeclarationIfNeeded, 5 createMultiMap, Decorator, EmitResolver, ESMap, ExportAssignment, ExportDeclaration, ExportSpecifier, Expression, 6 filter, FunctionDeclaration, FunctionLikeDeclaration, getAllAccessorDeclarations, getDecorators, 7 getFirstConstructorWithBody, getNamespaceDeclarationNode, getNodeId, getOriginalNode, hasDecorators, 8 hasStaticModifier, hasSyntacticModifier, Identifier, idText, ImportDeclaration, ImportEqualsDeclaration, 9 ImportSpecifier, InitializedPropertyDeclaration, InternalSymbolName, isAutoAccessorPropertyDeclaration, 10 isBindingPattern, isClassStaticBlockDeclaration, isDefaultImport, isExpressionStatement, isGeneratedIdentifier, 11 isIdentifier, isKeyword, isMethodOrAccessor, isNamedExports, isNamedImports, isOmittedExpression, 12 isPrivateIdentifier, isPropertyDeclaration, isStatic, isStringLiteralLike, isSuperCall, LogicalOperatorOrHigher, 13 map, Map, MethodDeclaration, ModifierFlags, NamedImportBindings, NamespaceExport, Node, NodeArray, 14 parameterIsThisKeyword, PrivateIdentifierAccessorDeclaration, PrivateIdentifierAutoAccessorPropertyDeclaration, 15 PrivateIdentifierMethodDeclaration, PropertyDeclaration, skipParentheses, some, SourceFile, Statement, 16 StructDeclaration, SuperCall, SyntaxKind, TransformationContext, VariableDeclaration, VariableStatement, 17} from "../_namespaces/ts"; 18 19/** @internal */ 20export function getOriginalNodeId(node: Node) { 21 node = getOriginalNode(node); 22 return node ? getNodeId(node) : 0; 23} 24 25/** @internal */ 26export interface ExternalModuleInfo { 27 externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[]; // imports of other external modules 28 externalHelpersImportDeclaration: ImportDeclaration | undefined; // import of external helpers 29 exportSpecifiers: ESMap<string, ExportSpecifier[]>; // file-local export specifiers by name (no reexports) 30 exportedBindings: Identifier[][]; // exported names of local declarations 31 exportedNames: Identifier[] | undefined; // all exported names in the module, both local and reexported 32 exportEquals: ExportAssignment | undefined; // an export= declaration if one was present 33 hasExportStarsToExportValues: boolean; // whether this module contains export* 34} 35 36function containsDefaultReference(node: NamedImportBindings | undefined) { 37 if (!node) return false; 38 if (!isNamedImports(node)) return false; 39 return some(node.elements, isNamedDefaultReference); 40} 41 42function isNamedDefaultReference(e: ImportSpecifier): boolean { 43 return e.propertyName !== undefined && e.propertyName.escapedText === InternalSymbolName.Default; 44} 45 46/** @internal */ 47export function chainBundle(context: CoreTransformationContext, transformSourceFile: (x: SourceFile) => SourceFile): (x: SourceFile | Bundle) => SourceFile | Bundle { 48 return transformSourceFileOrBundle; 49 50 function transformSourceFileOrBundle(node: SourceFile | Bundle) { 51 return node.kind === SyntaxKind.SourceFile ? transformSourceFile(node) : transformBundle(node); 52 } 53 54 function transformBundle(node: Bundle) { 55 return context.factory.createBundle(map(node.sourceFiles, transformSourceFile), node.prepends); 56 } 57} 58 59/** @internal */ 60export function getExportNeedsImportStarHelper(node: ExportDeclaration): boolean { 61 return !!getNamespaceDeclarationNode(node); 62} 63 64/** @internal */ 65export function getImportNeedsImportStarHelper(node: ImportDeclaration): boolean { 66 if (!!getNamespaceDeclarationNode(node)) { 67 return true; 68 } 69 const bindings = node.importClause && node.importClause.namedBindings; 70 if (!bindings) { 71 return false; 72 } 73 if (!isNamedImports(bindings)) return false; 74 let defaultRefCount = 0; 75 for (const binding of bindings.elements) { 76 if (isNamedDefaultReference(binding)) { 77 defaultRefCount++; 78 } 79 } 80 // 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 81 return (defaultRefCount > 0 && defaultRefCount !== bindings.elements.length) || (!!(bindings.elements.length - defaultRefCount) && isDefaultImport(node)); 82} 83 84/** @internal */ 85export function getImportNeedsImportDefaultHelper(node: ImportDeclaration): boolean { 86 // 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) 87 return !getImportNeedsImportStarHelper(node) && (isDefaultImport(node) || (!!node.importClause && isNamedImports(node.importClause.namedBindings!) && containsDefaultReference(node.importClause.namedBindings))); // TODO: GH#18217 88} 89 90/** @internal */ 91export function collectExternalModuleInfo(context: TransformationContext, sourceFile: SourceFile, resolver: EmitResolver, compilerOptions: CompilerOptions): ExternalModuleInfo { 92 const externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[] = []; 93 const exportSpecifiers = createMultiMap<ExportSpecifier>(); 94 const exportedBindings: Identifier[][] = []; 95 const uniqueExports = new Map<string, boolean>(); 96 let exportedNames: Identifier[] | undefined; 97 let hasExportDefault = false; 98 let exportEquals: ExportAssignment | undefined; 99 let hasExportStarsToExportValues = false; 100 let hasImportStar = false; 101 let hasImportDefault = false; 102 103 for (const node of sourceFile.statements) { 104 switch (node.kind) { 105 case SyntaxKind.ImportDeclaration: 106 // import "mod" 107 // import x from "mod" 108 // import * as x from "mod" 109 // import { x, y } from "mod" 110 externalImports.push(node as ImportDeclaration); 111 if (!hasImportStar && getImportNeedsImportStarHelper(node as ImportDeclaration)) { 112 hasImportStar = true; 113 } 114 if (!hasImportDefault && getImportNeedsImportDefaultHelper(node as ImportDeclaration)) { 115 hasImportDefault = true; 116 } 117 break; 118 119 case SyntaxKind.ImportEqualsDeclaration: 120 if ((node as ImportEqualsDeclaration).moduleReference.kind === SyntaxKind.ExternalModuleReference) { 121 // import x = require("mod") 122 externalImports.push(node as ImportEqualsDeclaration); 123 } 124 125 break; 126 127 case SyntaxKind.ExportDeclaration: 128 if ((node as ExportDeclaration).moduleSpecifier) { 129 if (!(node as ExportDeclaration).exportClause) { 130 // export * from "mod" 131 externalImports.push(node as ExportDeclaration); 132 hasExportStarsToExportValues = true; 133 } 134 else { 135 // export * as ns from "mod" 136 // export { x, y } from "mod" 137 externalImports.push(node as ExportDeclaration); 138 if (isNamedExports((node as ExportDeclaration).exportClause!)) { 139 addExportedNamesForExportDeclaration(node as ExportDeclaration); 140 } 141 else { 142 const name = ((node as ExportDeclaration).exportClause as NamespaceExport).name; 143 if (!uniqueExports.get(idText(name))) { 144 multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), name); 145 uniqueExports.set(idText(name), true); 146 exportedNames = append(exportedNames, name); 147 } 148 // we use the same helpers for `export * as ns` as we do for `import * as ns` 149 hasImportStar = true; 150 } 151 } 152 } 153 else { 154 // export { x, y } 155 addExportedNamesForExportDeclaration(node as ExportDeclaration); 156 } 157 break; 158 159 case SyntaxKind.ExportAssignment: 160 if ((node as ExportAssignment).isExportEquals && !exportEquals) { 161 // export = x 162 exportEquals = node as ExportAssignment; 163 } 164 break; 165 166 case SyntaxKind.VariableStatement: 167 if (hasSyntacticModifier(node, ModifierFlags.Export)) { 168 for (const decl of (node as VariableStatement).declarationList.declarations) { 169 exportedNames = collectExportedVariableInfo(decl, uniqueExports, exportedNames); 170 } 171 } 172 break; 173 174 case SyntaxKind.FunctionDeclaration: 175 if (hasSyntacticModifier(node, ModifierFlags.Export)) { 176 if (hasSyntacticModifier(node, ModifierFlags.Default)) { 177 // export default function() { } 178 if (!hasExportDefault) { 179 multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), context.factory.getDeclarationName(node as FunctionDeclaration)); 180 hasExportDefault = true; 181 } 182 } 183 else { 184 // export function x() { } 185 const name = (node as FunctionDeclaration).name!; 186 if (!uniqueExports.get(idText(name))) { 187 multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), name); 188 uniqueExports.set(idText(name), true); 189 exportedNames = append(exportedNames, name); 190 } 191 } 192 } 193 break; 194 195 case SyntaxKind.ClassDeclaration: 196 if (hasSyntacticModifier(node, ModifierFlags.Export)) { 197 if (hasSyntacticModifier(node, ModifierFlags.Default)) { 198 // export default class { } 199 if (!hasExportDefault) { 200 multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), context.factory.getDeclarationName(node as ClassDeclaration)); 201 hasExportDefault = true; 202 } 203 } 204 else { 205 // export class x { } 206 const name = (node as ClassDeclaration).name; 207 if (name && !uniqueExports.get(idText(name))) { 208 multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), name); 209 uniqueExports.set(idText(name), true); 210 exportedNames = append(exportedNames, name); 211 } 212 } 213 } 214 break; 215 } 216 } 217 218 const externalHelpersImportDeclaration = createExternalHelpersImportDeclarationIfNeeded(context.factory, context.getEmitHelperFactory(), sourceFile, compilerOptions, hasExportStarsToExportValues, hasImportStar, hasImportDefault); 219 if (externalHelpersImportDeclaration) { 220 externalImports.unshift(externalHelpersImportDeclaration); 221 } 222 223 return { externalImports, exportSpecifiers, exportEquals, hasExportStarsToExportValues, exportedBindings, exportedNames, externalHelpersImportDeclaration }; 224 225 function addExportedNamesForExportDeclaration(node: ExportDeclaration) { 226 for (const specifier of cast(node.exportClause, isNamedExports).elements) { 227 if (resolver.isReferredToAnnotation(specifier) === true) { 228 continue; 229 } 230 if (!uniqueExports.get(idText(specifier.name))) { 231 const name = specifier.propertyName || specifier.name; 232 if (!node.moduleSpecifier) { 233 exportSpecifiers.add(idText(name), specifier); 234 } 235 236 const decl = resolver.getReferencedImportDeclaration(name) 237 || resolver.getReferencedValueDeclaration(name); 238 239 if (decl) { 240 multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(decl), specifier.name); 241 } 242 243 uniqueExports.set(idText(specifier.name), true); 244 exportedNames = append(exportedNames, specifier.name); 245 } 246 } 247 } 248} 249 250function collectExportedVariableInfo(decl: VariableDeclaration | BindingElement, uniqueExports: ESMap<string, boolean>, exportedNames: Identifier[] | undefined) { 251 if (isBindingPattern(decl.name)) { 252 for (const element of decl.name.elements) { 253 if (!isOmittedExpression(element)) { 254 exportedNames = collectExportedVariableInfo(element, uniqueExports, exportedNames); 255 } 256 } 257 } 258 else if (!isGeneratedIdentifier(decl.name)) { 259 const text = idText(decl.name); 260 if (!uniqueExports.get(text)) { 261 uniqueExports.set(text, true); 262 exportedNames = append(exportedNames, decl.name); 263 } 264 } 265 return exportedNames; 266} 267 268/** Use a sparse array as a multi-map. */ 269function multiMapSparseArrayAdd<V>(map: V[][], key: number, value: V): V[] { 270 let values = map[key]; 271 if (values) { 272 values.push(value); 273 } 274 else { 275 map[key] = values = [value]; 276 } 277 return values; 278} 279 280/** 281 * Used in the module transformer to check if an expression is reasonably without sideeffect, 282 * and thus better to copy into multiple places rather than to cache in a temporary variable 283 * - this is mostly subjective beyond the requirement that the expression not be sideeffecting 284 * 285 * @internal 286 */ 287export function isSimpleCopiableExpression(expression: Expression) { 288 return isStringLiteralLike(expression) || 289 expression.kind === SyntaxKind.NumericLiteral || 290 isKeyword(expression.kind) || 291 isIdentifier(expression); 292} 293 294/** 295 * A simple inlinable expression is an expression which can be copied into multiple locations 296 * without risk of repeating any sideeffects and whose value could not possibly change between 297 * any such locations 298 * 299 * @internal 300 */ 301export function isSimpleInlineableExpression(expression: Expression) { 302 return !isIdentifier(expression) && isSimpleCopiableExpression(expression); 303} 304 305/** @internal */ 306export function isCompoundAssignment(kind: BinaryOperator): kind is CompoundAssignmentOperator { 307 return kind >= SyntaxKind.FirstCompoundAssignment 308 && kind <= SyntaxKind.LastCompoundAssignment; 309} 310 311/** @internal */ 312export function getNonAssignmentOperatorForCompoundAssignment(kind: CompoundAssignmentOperator): LogicalOperatorOrHigher | SyntaxKind.QuestionQuestionToken { 313 switch (kind) { 314 case SyntaxKind.PlusEqualsToken: return SyntaxKind.PlusToken; 315 case SyntaxKind.MinusEqualsToken: return SyntaxKind.MinusToken; 316 case SyntaxKind.AsteriskEqualsToken: return SyntaxKind.AsteriskToken; 317 case SyntaxKind.AsteriskAsteriskEqualsToken: return SyntaxKind.AsteriskAsteriskToken; 318 case SyntaxKind.SlashEqualsToken: return SyntaxKind.SlashToken; 319 case SyntaxKind.PercentEqualsToken: return SyntaxKind.PercentToken; 320 case SyntaxKind.LessThanLessThanEqualsToken: return SyntaxKind.LessThanLessThanToken; 321 case SyntaxKind.GreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanToken; 322 case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanGreaterThanToken; 323 case SyntaxKind.AmpersandEqualsToken: return SyntaxKind.AmpersandToken; 324 case SyntaxKind.BarEqualsToken: return SyntaxKind.BarToken; 325 case SyntaxKind.CaretEqualsToken: return SyntaxKind.CaretToken; 326 case SyntaxKind.BarBarEqualsToken: return SyntaxKind.BarBarToken; 327 case SyntaxKind.AmpersandAmpersandEqualsToken: return SyntaxKind.AmpersandAmpersandToken; 328 case SyntaxKind.QuestionQuestionEqualsToken: return SyntaxKind.QuestionQuestionToken; 329 330 } 331} 332 333/** 334 * @returns Contained super() call from descending into the statement ignoring parentheses, if that call exists. 335 * 336 * @internal 337 */ 338export function getSuperCallFromStatement(statement: Statement): SuperCall | undefined { 339 if (!isExpressionStatement(statement)) { 340 return undefined; 341 } 342 343 const expression = skipParentheses(statement.expression); 344 return isSuperCall(expression) 345 ? expression 346 : undefined; 347} 348 349/** 350 * @returns The index (after prologue statements) of a super call, or -1 if not found. 351 * 352 * @internal 353 */ 354export function findSuperStatementIndex(statements: NodeArray<Statement>, indexAfterLastPrologueStatement: number) { 355 for (let i = indexAfterLastPrologueStatement; i < statements.length; i += 1) { 356 const statement = statements[i]; 357 358 if (getSuperCallFromStatement(statement)) { 359 return i; 360 } 361 } 362 363 return -1; 364} 365 366/** 367 * Gets all the static or all the instance property declarations of a class 368 * 369 * @param node The class node. 370 * @param isStatic A value indicating whether to get properties from the static or instance side of the class. 371 * 372 * @internal 373 */ 374export function getProperties(node: ClassExpression | ClassDeclaration | StructDeclaration, requireInitializer: true, isStatic: boolean): readonly InitializedPropertyDeclaration[]; 375/** @internal */ 376export function getProperties(node: ClassExpression | ClassDeclaration | StructDeclaration, requireInitializer: boolean, isStatic: boolean): readonly PropertyDeclaration[]; 377/** @internal */ 378export function getProperties(node: ClassExpression | ClassDeclaration | StructDeclaration, requireInitializer: boolean, isStatic: boolean): readonly PropertyDeclaration[] { 379 return filter(node.members, m => isInitializedOrStaticProperty(m, requireInitializer, isStatic)) as PropertyDeclaration[]; 380} 381 382function isStaticPropertyDeclarationOrClassStaticBlockDeclaration(element: ClassElement): element is PropertyDeclaration | ClassStaticBlockDeclaration { 383 return isStaticPropertyDeclaration(element) || isClassStaticBlockDeclaration(element); 384} 385 386/** @internal */ 387export function getStaticPropertiesAndClassStaticBlock(node: ClassExpression | ClassDeclaration): readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[]; 388/** @internal */ 389export function getStaticPropertiesAndClassStaticBlock(node: ClassExpression | ClassDeclaration): readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[]; 390/** @internal */ 391export function getStaticPropertiesAndClassStaticBlock(node: ClassExpression | ClassDeclaration): readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[] { 392 return filter(node.members, isStaticPropertyDeclarationOrClassStaticBlockDeclaration); 393} 394 395/** 396 * Is a class element either a static or an instance property declaration with an initializer? 397 * 398 * @param member The class element node. 399 * @param isStatic A value indicating whether the member should be a static or instance member. 400 */ 401function isInitializedOrStaticProperty(member: ClassElement, requireInitializer: boolean, isStatic: boolean) { 402 return isPropertyDeclaration(member) 403 && (!!member.initializer || !requireInitializer) 404 && hasStaticModifier(member) === isStatic; 405} 406 407function isStaticPropertyDeclaration(member: ClassElement) { 408 return isPropertyDeclaration(member) && hasStaticModifier(member); 409} 410 411/** 412 * Gets a value indicating whether a class element is either a static or an instance property declaration with an initializer. 413 * 414 * @param member The class element node. 415 * @param isStatic A value indicating whether the member should be a static or instance member. 416 * 417 * @internal 418 */ 419export function isInitializedProperty(member: ClassElement): member is PropertyDeclaration & { initializer: Expression; } { 420 return member.kind === SyntaxKind.PropertyDeclaration 421 && (member as PropertyDeclaration).initializer !== undefined; 422} 423 424/** 425 * Gets a value indicating whether a class element is a private instance method or accessor. 426 * 427 * @param member The class element node. 428 * 429 * @internal 430 */ 431export function isNonStaticMethodOrAccessorWithPrivateName(member: ClassElement): member is PrivateIdentifierMethodDeclaration | PrivateIdentifierAccessorDeclaration | PrivateIdentifierAutoAccessorPropertyDeclaration { 432 return !isStatic(member) && (isMethodOrAccessor(member) || isAutoAccessorPropertyDeclaration(member)) && isPrivateIdentifier(member.name); 433} 434 435/** 436 * Gets an array of arrays of decorators for the parameters of a function-like node. 437 * The offset into the result array should correspond to the offset of the parameter. 438 * 439 * @param node The function-like node. 440 */ 441function getDecoratorsOfParameters(node: FunctionLikeDeclaration | undefined) { 442 let decorators: (readonly Decorator[] | undefined)[] | undefined; 443 if (node) { 444 const parameters = node.parameters; 445 const firstParameterIsThis = parameters.length > 0 && parameterIsThisKeyword(parameters[0]); 446 const firstParameterOffset = firstParameterIsThis ? 1 : 0; 447 const numParameters = firstParameterIsThis ? parameters.length - 1 : parameters.length; 448 for (let i = 0; i < numParameters; i++) { 449 const parameter = parameters[i + firstParameterOffset]; 450 if (decorators || hasDecorators(parameter)) { 451 if (!decorators) { 452 decorators = new Array(numParameters); 453 } 454 455 decorators[i] = getDecorators(parameter); 456 } 457 } 458 } 459 460 return decorators; 461} 462 463/** 464 * Gets an AllDecorators object containing the decorators for the class and the decorators for the 465 * parameters of the constructor of the class. 466 * 467 * @param node The class node. 468 * 469 * @internal 470 */ 471export function getAllDecoratorsOfClass(node: ClassLikeDeclaration): AllDecorators | undefined { 472 const decorators = getDecorators(node); 473 const parameters = getDecoratorsOfParameters(getFirstConstructorWithBody(node)); 474 if (!some(decorators) && !some(parameters)) { 475 return undefined; 476 } 477 478 return { 479 decorators, 480 parameters 481 }; 482} 483 484/** 485 * Gets an AllDecorators object containing the decorators for the member and its parameters. 486 * 487 * @param parent The class node that contains the member. 488 * @param member The class member. 489 * 490 * @internal 491 */ 492export function getAllDecoratorsOfClassElement(member: ClassElement, parent: ClassLikeDeclaration): AllDecorators | undefined { 493 switch (member.kind) { 494 case SyntaxKind.GetAccessor: 495 case SyntaxKind.SetAccessor: 496 return getAllDecoratorsOfAccessors(member as AccessorDeclaration, parent); 497 498 case SyntaxKind.MethodDeclaration: 499 return getAllDecoratorsOfMethod(member as MethodDeclaration); 500 501 case SyntaxKind.PropertyDeclaration: 502 return getAllDecoratorsOfProperty(member as PropertyDeclaration); 503 504 default: 505 return undefined; 506 } 507} 508 509/** 510 * Gets an AllDecorators object containing the decorators for the accessor and its parameters. 511 * 512 * @param parent The class node that contains the accessor. 513 * @param accessor The class accessor member. 514 */ 515function getAllDecoratorsOfAccessors(accessor: AccessorDeclaration, parent: ClassExpression | ClassDeclaration | StructDeclaration): AllDecorators | undefined { 516 if (!accessor.body) { 517 return undefined; 518 } 519 520 const { firstAccessor, secondAccessor, getAccessor, setAccessor } = getAllAccessorDeclarations(parent.members, accessor); 521 const firstAccessorWithDecorators = 522 hasDecorators(firstAccessor) ? firstAccessor : 523 secondAccessor && hasDecorators(secondAccessor) ? secondAccessor : 524 undefined; 525 526 if (!firstAccessorWithDecorators || accessor !== firstAccessorWithDecorators) { 527 return undefined; 528 } 529 530 const decorators = getDecorators(firstAccessorWithDecorators); 531 const parameters = getDecoratorsOfParameters(setAccessor); 532 if (!some(decorators) && !some(parameters)) { 533 return undefined; 534 } 535 536 return { 537 decorators, 538 parameters, 539 getDecorators: getAccessor && getDecorators(getAccessor), 540 setDecorators: setAccessor && getDecorators(setAccessor) 541 }; 542} 543 544/** 545 * Gets an AllDecorators object containing the decorators for the method and its parameters. 546 * 547 * @param method The class method member. 548 */ 549function getAllDecoratorsOfMethod(method: MethodDeclaration): AllDecorators | undefined { 550 if (!method.body) { 551 return undefined; 552 } 553 554 const decorators = getDecorators(method); 555 const parameters = getDecoratorsOfParameters(method); 556 if (!some(decorators) && !some(parameters)) { 557 return undefined; 558 } 559 560 return { decorators, parameters }; 561} 562 563/** 564 * Gets an AllDecorators object containing the decorators for the property. 565 * 566 * @param property The class property member. 567 */ 568function getAllDecoratorsOfProperty(property: PropertyDeclaration): AllDecorators | undefined { 569 const decorators = getDecorators(property); 570 if (!some(decorators)) { 571 return undefined; 572 573 } 574 575 return { decorators }; 576} 577