From 2d8a96b68447fbc065574c1f9a520ece97c39b52 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Wed, 19 Jun 2024 15:32:02 +0000 Subject: [PATCH] refactor(compiler-cli): support running JIT transforms as part of tsickle emit (#56520) When running the JIT transforms in 1P w/ tsickle, tsickle will transform source files before our custom transforms can run. This is also impacting the Ivy transform and hence we use `ts.getOriginalNode` in various places to inspect the source AST for detecting Angular. For the JIT transform we need to do a similar change so that the transform could run in 1P. PR Close #56520 --- .../initializer_api_transforms/transform.ts | 6 ++- .../test/initializer_api_transforms_spec.ts | 53 ++++++++++++++++++- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/packages/compiler-cli/src/transformers/jit_transforms/initializer_api_transforms/transform.ts b/packages/compiler-cli/src/transformers/jit_transforms/initializer_api_transforms/transform.ts index 50d2aee419a..fecc3fc83f3 100644 --- a/packages/compiler-cli/src/transformers/jit_transforms/initializer_api_transforms/transform.ts +++ b/packages/compiler-cli/src/transformers/jit_transforms/initializer_api_transforms/transform.ts @@ -70,8 +70,12 @@ function createTransformVisitor( ): ts.Visitor { const visitor: ts.Visitor = (node: ts.Node): ts.Node => { if (ts.isClassDeclaration(node) && node.name !== undefined) { + const originalNode = ts.getOriginalNode(node, ts.isClassDeclaration); + // Note: Attempt to detect the `angularDecorator` on the original node of the class. + // That is because e.g. Tsickle or other transforms might have transformed the node + // already to transform decorators. const angularDecorator = host - .getDecoratorsOfDeclaration(node) + .getDecoratorsOfDeclaration(originalNode) ?.find((d) => decoratorsWithInputs.some((name) => isAngularDecorator(d, name, isCore))); if (angularDecorator !== undefined) { diff --git a/packages/compiler-cli/test/initializer_api_transforms_spec.ts b/packages/compiler-cli/test/initializer_api_transforms_spec.ts index c74a2981437..001d2395ba9 100644 --- a/packages/compiler-cli/test/initializer_api_transforms_spec.ts +++ b/packages/compiler-cli/test/initializer_api_transforms_spec.ts @@ -36,7 +36,11 @@ describe('initializer API metadata transform', () => { host = new MockCompilerHost(context); }); - function transform(contents: string, postDownlevelDecoratorsTransform = false) { + function transform( + contents: string, + postDownlevelDecoratorsTransform = false, + customPreTransform?: ts.TransformerFactory, + ) { context.writeFile(TEST_FILE_INPUT, contents); const program = ts.createProgram( @@ -59,7 +63,10 @@ describe('initializer API metadata transform', () => { const reflectionHost = new TypeScriptReflectionHost(typeChecker); const importTracker = new ImportedSymbolsTracker(); const transformers: ts.CustomTransformers = { - before: [getInitializerApiJitTransform(reflectionHost, importTracker, /* isCore */ false)], + before: [ + ...(customPreTransform ? [customPreTransform] : []), + getInitializerApiJitTransform(reflectionHost, importTracker, /* isCore */ false), + ], }; if (postDownlevelDecoratorsTransform) { @@ -269,6 +276,48 @@ describe('initializer API metadata transform', () => { `), ); }); + + it('should migrate if the class decorator is downleveled in advance', () => { + const fakeDownlevelPreTransform = (ctx: ts.TransformationContext) => { + return (sf: ts.SourceFile) => { + const visitor = (node: ts.Node) => { + if (ts.isClassDeclaration(node)) { + return ctx.factory.updateClassDeclaration( + node, + [], + node.name, + node.typeParameters, + node.heritageClauses, + node.members, + ); + } + return node; + }; + return ts.visitEachChild(sf, visitor, ctx); + }; + }; + + const result = transform( + ` + import {input, Directive} from '@angular/core'; + + @Directive({}) + class MyDir { + someInput = input(0); + } + `, + false, + fakeDownlevelPreTransform, + ); + + expect(result).toContain( + omitLeadingWhitespace(` + __decorate([ + i0.Input({ isSignal: true, alias: "someInput", required: false, transform: undefined }) + ], MyDir.prototype, "someInput", void 0); + `), + ); + }); }); describe('model()', () => {