1111import { Decorator , ReflectionHost , TypeScriptReflectionHost } from '@angular/compiler-cli/src/ngtsc/reflection' ;
1212import ts from 'typescript' ;
1313
14- /**
15- * Describes a TypeScript transformation context with the internal emit
16- * resolver exposed. There are requests upstream in TypeScript to expose
17- * that as public API: https://github.com/microsoft/TypeScript/issues/17516..
18- */
19- interface TransformationContextWithResolver extends ts . TransformationContext {
20- getEmitResolver : ( ) => EmitResolver ;
21- }
22-
23- /** Describes a subset of the TypeScript internal emit resolver. */
24- interface EmitResolver {
25- isReferencedAliasDeclaration ?( node : ts . Node , checkChildren ?: boolean ) : void ;
26- }
27-
28- /**
29- * Patches the alias declaration reference resolution for a given transformation context
30- * so that TypeScript knows about the specified alias declarations being referenced.
31- *
32- * This exists because TypeScript performs analysis of import usage before transformers
33- * run and doesn't refresh its state after transformations. This means that imports
34- * for symbols used as constructor types are elided due to their original type-only usage.
35- *
36- * In reality though, since we downlevel decorators and constructor parameters, we want
37- * these symbols to be retained in the JavaScript output as they will be used as values
38- * at runtime. We can instruct TypeScript to preserve imports for such identifiers by
39- * creating a mutable clone of a given import specifier/clause or namespace, but that
40- * has the downside of preserving the full import in the JS output. See:
41- * https://github.com/microsoft/TypeScript/blob/3eaa7c65f6f076a08a5f7f1946fd0df7c7430259/src/compiler/transformers/ts.ts#L242-L250.
42- *
43- * This is a trick the CLI used in the past for constructor parameter downleveling in JIT:
44- * https://github.com/angular/angular-cli/blob/b3f84cc5184337666ce61c07b7b9df418030106f/packages/ngtools/webpack/src/transformers/ctor-parameters.ts#L323-L325
45- * The trick is not ideal though as it preserves the full import (as outlined before), and it
46- * results in a slow-down due to the type checker being involved multiple times. The CLI
47- * worked around this import preserving issue by having another complex post-process step that
48- * detects and elides unused imports. Note that these unused imports could cause unused chunks
49- * being generated by Webpack if the application or library is not marked as side-effect free.
50- *
51- * This is not ideal though, as we basically re-implement the complex import usage resolution
52- * from TypeScript. We can do better by letting TypeScript do the import eliding, but providing
53- * information about the alias declarations (e.g. import specifiers) that should not be elided
54- * because they are actually referenced (as they will now appear in static properties).
55- *
56- * More information about these limitations with transformers can be found in:
57- * 1. https://github.com/Microsoft/TypeScript/issues/17552.
58- * 2. https://github.com/microsoft/TypeScript/issues/17516.
59- * 3. https://github.com/angular/tsickle/issues/635.
60- *
61- * The patch we apply to tell TypeScript about actual referenced aliases (i.e. imported symbols),
62- * matches conceptually with the logic that runs internally in TypeScript when the
63- * `emitDecoratorMetadata` flag is enabled. TypeScript basically surfaces the same problem and
64- * solves it conceptually the same way, but obviously doesn't need to access an `@internal` API.
65- *
66- * See below. Note that this uses sourcegraph as the TypeScript checker file doesn't display on
67- * Github.
68- * https://sourcegraph.com/github.com/microsoft/TypeScript@3eaa7c65f6f076a08a5f7f1946fd0df7c7430259/-/blob/src/compiler/checker.ts#L31219-31257
69- */
70- function patchAliasReferenceResolutionOrDie (
71- context : ts . TransformationContext ,
72- referencedAliases : Set < ts . Declaration > ,
73- ) : void {
74- // If the `getEmitResolver` method is not available, TS most likely changed the
75- // internal structure of the transformation context. We will abort gracefully.
76- if ( ! isTransformationContextWithEmitResolver ( context ) ) {
77- throwIncompatibleTransformationContextError ( ) ;
78-
79- return ;
80- }
81- const emitResolver = context . getEmitResolver ( ) ;
82- // eslint-disable-next-line @typescript-eslint/unbound-method
83- const originalReferenceResolution = emitResolver . isReferencedAliasDeclaration ;
84- // If the emit resolver does not have a function called `isReferencedAliasDeclaration`, then
85- // we abort gracefully as most likely TS changed the internal structure of the emit resolver.
86- if ( originalReferenceResolution === undefined ) {
87- throwIncompatibleTransformationContextError ( ) ;
88-
89- return ;
90- }
91- emitResolver . isReferencedAliasDeclaration = function ( node , ...args ) {
92- if ( isAliasImportDeclaration ( node ) && referencedAliases . has ( node ) ) {
93- return true ;
94- }
95-
96- return originalReferenceResolution . call ( emitResolver , node , ...args ) ;
97- } ;
98- }
99-
100- /** Whether the transformation context exposes its emit resolver. */
101- function isTransformationContextWithEmitResolver (
102- context : ts . TransformationContext ,
103- ) : context is TransformationContextWithResolver {
104- return ( context as Partial < TransformationContextWithResolver > ) . getEmitResolver !== undefined ;
105- }
106-
107- /**
108- * Gets whether a given node corresponds to an import alias declaration. Alias
109- * declarations can be import specifiers, namespace imports or import clauses
110- * as these do not declare an actual symbol but just point to a target declaration.
111- */
112- function isAliasImportDeclaration ( node : ts . Node ) : node is ts . ImportSpecifier | ts . NamespaceImport | ts . ImportClause {
113- return ts . isImportSpecifier ( node ) || ts . isNamespaceImport ( node ) || ts . isImportClause ( node ) ;
114- }
14+ import { isAliasImportDeclaration , loadIsReferencedAliasDeclarationPatch } from './patch-alias-reference-resolution' ;
11515
11616/**
11717 * Whether a given decorator should be treated as an Angular decorator.
@@ -532,7 +432,12 @@ function getDownlevelDecoratorsTransform(
532432 skipClassDecorators : boolean ,
533433) : ts . TransformerFactory < ts . SourceFile > {
534434 return ( context : ts . TransformationContext ) => {
535- const referencedParameterTypes = new Set < ts . Declaration > ( ) ;
435+ // Ensure that referenced type symbols are not elided by TypeScript. Imports for
436+ // such parameter type symbols previously could be type-only, but now might be also
437+ // used in the `ctorParameters` static property as a value. We want to make sure
438+ // that TypeScript does not elide imports for such type references. Read more
439+ // about this in the description for `loadIsReferencedAliasDeclarationPatch`.
440+ const referencedParameterTypes = loadIsReferencedAliasDeclarationPatch ( context ) ;
536441
537442 /**
538443 * Converts an EntityName (from a type annotation) to an expression (accessing a value).
@@ -627,7 +532,7 @@ function getDownlevelDecoratorsTransform(
627532 return [ undefined , element , [ ] ] ;
628533 }
629534
630- const name = element . name . text ;
535+ const name = ( element . name as ts . Identifier ) . text ;
631536 const mutable = ts . getMutableClone ( element ) ;
632537 // eslint-disable-next-line @typescript-eslint/no-explicit-any
633538 ( mutable as any ) . decorators = decoratorsToKeep . length
@@ -663,14 +568,16 @@ function getDownlevelDecoratorsTransform(
663568 decoratorsToKeep . push ( decoratorNode ) ;
664569 continue ;
665570 }
666- paramInfo . decorators . push ( decoratorNode ) ;
571+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
572+ paramInfo ! . decorators . push ( decoratorNode ) ;
667573 }
668574 if ( param . type ) {
669575 // param has a type provided, e.g. "foo: Bar".
670576 // The type will be emitted as a value expression in entityNameToExpression, which takes
671577 // care not to emit anything for types that cannot be expressed as a value (e.g.
672578 // interfaces).
673- paramInfo . type = param . type ;
579+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
580+ paramInfo ! . type = param . type ;
674581 }
675582 parametersInfo . push ( paramInfo ) ;
676583 const newParam = ts . updateParameter (
@@ -815,13 +722,6 @@ function getDownlevelDecoratorsTransform(
815722 }
816723
817724 return ( sf : ts . SourceFile ) => {
818- // Ensure that referenced type symbols are not elided by TypeScript. Imports for
819- // such parameter type symbols previously could be type-only, but now might be also
820- // used in the `ctorParameters` static property as a value. We want to make sure
821- // that TypeScript does not elide imports for such type references. Read more
822- // about this in the description for `patchAliasReferenceResolution`.
823- patchAliasReferenceResolutionOrDie ( context , referencedParameterTypes ) ;
824-
825725 // Downlevel decorators and constructor parameter types. We will keep track of all
826726 // referenced constructor parameter types so that we can instruct TypeScript to
827727 // not elide their imports if they previously were only type-only.
@@ -830,21 +730,6 @@ function getDownlevelDecoratorsTransform(
830730 } ;
831731}
832732
833- /**
834- * Throws an error about an incompatible TypeScript version for which the alias
835- * declaration reference resolution could not be monkey-patched. The error will
836- * also propose potential solutions that can be applied by developers.
837- */
838- function throwIncompatibleTransformationContextError ( ) {
839- throw Error (
840- 'Unable to downlevel Angular decorators due to an incompatible TypeScript ' +
841- 'version.\nIf you recently updated TypeScript and this issue surfaces now, consider ' +
842- 'downgrading.\n\n' +
843- 'Please report an issue on the Angular repositories when this issue ' +
844- 'surfaces and you are using a supposedly compatible TypeScript version.' ,
845- ) ;
846- }
847-
848733/**
849734 * Transform for downleveling Angular decorators and Angular-decorated class constructor
850735 * parameters for dependency injection. This transform can be used by the CLI for JIT-mode
0 commit comments