Skip to content

Commit fc4dc35

Browse files
alxhubmhevery
authored andcommitted
feat(ivy): strip all Angular decorators in compiled classes (angular#24677)
Previously ngtsc removed the class-level decorators (@component, etc) but left all the ancillary decorators (@input, @optional, etc). This changes the transform to descend into the members of decorated classes and remove any Angular decorators, not just the class-level ones. PR Close angular#24677
1 parent 104d305 commit fc4dc35

File tree

2 files changed

+126
-7
lines changed

2 files changed

+126
-7
lines changed

packages/compiler-cli/src/ngtsc/program.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ export class NgtscProgram implements api.Program {
132132
options: this.options,
133133
emitOnlyDtsFiles: false, writeFile,
134134
customTransformers: {
135-
before: [ivyTransformFactory(compilation, coreImportsFrom)],
135+
before: [ivyTransformFactory(compilation, reflector, coreImportsFrom)],
136136
},
137137
});
138138
return emitResult;

packages/compiler-cli/src/ngtsc/transform/src/transform.ts

Lines changed: 125 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,30 @@
99
import {WrappedNodeExpr} from '@angular/compiler';
1010
import * as ts from 'typescript';
1111

12+
import {Decorator, ReflectionHost} from '../../host';
13+
import {relativePathBetween} from '../../util/src/path';
1214
import {VisitListEntryResult, Visitor, visit} from '../../util/src/visitor';
1315

1416
import {CompileResult} from './api';
1517
import {IvyCompilation} from './compilation';
1618
import {ImportManager, translateExpression, translateStatement} from './translator';
1719

20+
const NO_DECORATORS = new Set<ts.Decorator>();
21+
1822
export function ivyTransformFactory(
19-
compilation: IvyCompilation,
23+
compilation: IvyCompilation, reflector: ReflectionHost,
2024
coreImportsFrom: ts.SourceFile | null): ts.TransformerFactory<ts.SourceFile> {
2125
return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
2226
return (file: ts.SourceFile): ts.SourceFile => {
23-
return transformIvySourceFile(compilation, context, coreImportsFrom, file);
27+
return transformIvySourceFile(compilation, context, reflector, coreImportsFrom, file);
2428
};
2529
};
2630
}
2731

2832
class IvyVisitor extends Visitor {
29-
constructor(private compilation: IvyCompilation, private importManager: ImportManager) {
33+
constructor(
34+
private compilation: IvyCompilation, private reflector: ReflectionHost,
35+
private importManager: ImportManager, private isCore: boolean) {
3036
super();
3137
}
3238

@@ -62,24 +68,133 @@ class IvyVisitor extends Visitor {
6268
// Remove the decorator which triggered this compilation, leaving the others alone.
6369
maybeFilterDecorator(
6470
node.decorators, this.compilation.ivyDecoratorFor(node) !.node as ts.Decorator),
65-
node.modifiers, node.name, node.typeParameters, node.heritageClauses || [], members);
71+
node.modifiers, node.name, node.typeParameters, node.heritageClauses || [],
72+
// Map over the class members and remove any Angular decorators from them.
73+
members.map(member => this._stripAngularDecorators(member)));
6674
return {node, before: statements};
6775
}
6876

6977
return {node};
7078
}
79+
80+
/**
81+
* Return all decorators on a `Declaration` which are from @angular/core, or an empty set if none
82+
* are.
83+
*/
84+
private _angularCoreDecorators(decl: ts.Declaration): Set<ts.Decorator> {
85+
const decorators = this.reflector.getDecoratorsOfDeclaration(decl);
86+
if (decorators === null) {
87+
return NO_DECORATORS;
88+
}
89+
const coreDecorators = decorators.filter(dec => this.isCore || isFromAngularCore(dec))
90+
.map(dec => dec.node as ts.Decorator);
91+
if (coreDecorators.length > 0) {
92+
return new Set<ts.Decorator>(coreDecorators);
93+
} else {
94+
return NO_DECORATORS;
95+
}
96+
}
97+
98+
/**
99+
* Given a `ts.Node`, filter the decorators array and return a version containing only non-Angular
100+
* decorators.
101+
*
102+
* If all decorators are removed (or none existed in the first place), this method returns
103+
* `undefined`.
104+
*/
105+
private _nonCoreDecoratorsOnly(node: ts.Declaration): ts.NodeArray<ts.Decorator>|undefined {
106+
// Shortcut if the node has no decorators.
107+
if (node.decorators === undefined) {
108+
return undefined;
109+
}
110+
// Build a Set of the decorators on this node from @angular/core.
111+
const coreDecorators = this._angularCoreDecorators(node);
112+
113+
if (coreDecorators.size === node.decorators.length) {
114+
// If all decorators are to be removed, return `undefined`.
115+
return undefined;
116+
} else if (coreDecorators.size === 0) {
117+
// If no decorators need to be removed, return the original decorators array.
118+
return node.decorators;
119+
}
120+
121+
// Filter out the core decorators.
122+
const filtered = node.decorators.filter(dec => !coreDecorators.has(dec));
123+
124+
// If no decorators survive, return `undefined`. This can only happen if a core decorator is
125+
// repeated on the node.
126+
if (filtered.length === 0) {
127+
return undefined;
128+
}
129+
130+
// Create a new `NodeArray` with the filtered decorators that sourcemaps back to the original.
131+
const array = ts.createNodeArray(filtered);
132+
array.pos = node.decorators.pos;
133+
array.end = node.decorators.end;
134+
return array;
135+
}
136+
137+
/**
138+
* Remove Angular decorators from a `ts.Node` in a shallow manner.
139+
*
140+
* This will remove decorators from class elements (getters, setters, properties, methods) as well
141+
* as parameters of constructors.
142+
*/
143+
private _stripAngularDecorators<T extends ts.Node>(node: T): T {
144+
if (ts.isParameter(node)) {
145+
// Strip decorators from parameters (probably of the constructor).
146+
node = ts.updateParameter(
147+
node, this._nonCoreDecoratorsOnly(node), node.modifiers, node.dotDotDotToken,
148+
node.name, node.questionToken, node.type, node.initializer) as T &
149+
ts.ParameterDeclaration;
150+
} else if (ts.isMethodDeclaration(node) && node.decorators !== undefined) {
151+
// Strip decorators of methods.
152+
node = ts.updateMethod(
153+
node, this._nonCoreDecoratorsOnly(node), node.modifiers, node.asteriskToken,
154+
node.name, node.questionToken, node.typeParameters, node.parameters, node.type,
155+
node.body) as T &
156+
ts.MethodDeclaration;
157+
} else if (ts.isPropertyDeclaration(node) && node.decorators !== undefined) {
158+
// Strip decorators of properties.
159+
node = ts.updateProperty(
160+
node, this._nonCoreDecoratorsOnly(node), node.modifiers, node.name,
161+
node.questionToken, node.type, node.initializer) as T &
162+
ts.PropertyDeclaration;
163+
} else if (ts.isGetAccessor(node)) {
164+
// Strip decorators of getters.
165+
node = ts.updateGetAccessor(
166+
node, this._nonCoreDecoratorsOnly(node), node.modifiers, node.name,
167+
node.parameters, node.type, node.body) as T &
168+
ts.GetAccessorDeclaration;
169+
} else if (ts.isSetAccessor(node)) {
170+
// Strip decorators of setters.
171+
node = ts.updateSetAccessor(
172+
node, this._nonCoreDecoratorsOnly(node), node.modifiers, node.name,
173+
node.parameters, node.body) as T &
174+
ts.SetAccessorDeclaration;
175+
} else if (ts.isConstructorDeclaration(node)) {
176+
// For constructors, strip decorators of the parameters.
177+
const parameters = node.parameters.map(param => this._stripAngularDecorators(param));
178+
node =
179+
ts.updateConstructor(node, node.decorators, node.modifiers, parameters, node.body) as T &
180+
ts.ConstructorDeclaration;
181+
}
182+
return node;
183+
}
71184
}
72185

73186
/**
74187
* A transformer which operates on ts.SourceFiles and applies changes from an `IvyCompilation`.
75188
*/
76189
function transformIvySourceFile(
77-
compilation: IvyCompilation, context: ts.TransformationContext,
190+
compilation: IvyCompilation, context: ts.TransformationContext, reflector: ReflectionHost,
78191
coreImportsFrom: ts.SourceFile | null, file: ts.SourceFile): ts.SourceFile {
79192
const importManager = new ImportManager(coreImportsFrom !== null);
80193

81194
// Recursively scan through the AST and perform any updates requested by the IvyCompilation.
82-
const sf = visit(file, new IvyVisitor(compilation, importManager), context);
195+
const sf = visit(
196+
file, new IvyVisitor(compilation, reflector, importManager, coreImportsFrom !== null),
197+
context);
83198

84199
// Generate the import statements to prepend.
85200
const imports = importManager.getAllImports(file.fileName, coreImportsFrom).map(i => {
@@ -108,3 +223,7 @@ function maybeFilterDecorator(
108223
}
109224
return ts.createNodeArray(filtered);
110225
}
226+
227+
function isFromAngularCore(decorator: Decorator): boolean {
228+
return decorator.import !== null && decorator.import.from === '@angular/core';
229+
}

0 commit comments

Comments
 (0)