feat(ivy): strip all Angular decorators in compiled classes (#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 #24677
This commit is contained in:
parent
104d30507a
commit
fc4dc35426
|
@ -132,7 +132,7 @@ export class NgtscProgram implements api.Program {
|
|||
options: this.options,
|
||||
emitOnlyDtsFiles: false, writeFile,
|
||||
customTransformers: {
|
||||
before: [ivyTransformFactory(compilation, coreImportsFrom)],
|
||||
before: [ivyTransformFactory(compilation, reflector, coreImportsFrom)],
|
||||
},
|
||||
});
|
||||
return emitResult;
|
||||
|
|
|
@ -9,24 +9,30 @@
|
|||
import {WrappedNodeExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator, ReflectionHost} from '../../host';
|
||||
import {relativePathBetween} from '../../util/src/path';
|
||||
import {VisitListEntryResult, Visitor, visit} from '../../util/src/visitor';
|
||||
|
||||
import {CompileResult} from './api';
|
||||
import {IvyCompilation} from './compilation';
|
||||
import {ImportManager, translateExpression, translateStatement} from './translator';
|
||||
|
||||
const NO_DECORATORS = new Set<ts.Decorator>();
|
||||
|
||||
export function ivyTransformFactory(
|
||||
compilation: IvyCompilation,
|
||||
compilation: IvyCompilation, reflector: ReflectionHost,
|
||||
coreImportsFrom: ts.SourceFile | null): ts.TransformerFactory<ts.SourceFile> {
|
||||
return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
|
||||
return (file: ts.SourceFile): ts.SourceFile => {
|
||||
return transformIvySourceFile(compilation, context, coreImportsFrom, file);
|
||||
return transformIvySourceFile(compilation, context, reflector, coreImportsFrom, file);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
class IvyVisitor extends Visitor {
|
||||
constructor(private compilation: IvyCompilation, private importManager: ImportManager) {
|
||||
constructor(
|
||||
private compilation: IvyCompilation, private reflector: ReflectionHost,
|
||||
private importManager: ImportManager, private isCore: boolean) {
|
||||
super();
|
||||
}
|
||||
|
||||
|
@ -62,24 +68,133 @@ class IvyVisitor extends Visitor {
|
|||
// Remove the decorator which triggered this compilation, leaving the others alone.
|
||||
maybeFilterDecorator(
|
||||
node.decorators, this.compilation.ivyDecoratorFor(node) !.node as ts.Decorator),
|
||||
node.modifiers, node.name, node.typeParameters, node.heritageClauses || [], members);
|
||||
node.modifiers, node.name, node.typeParameters, node.heritageClauses || [],
|
||||
// Map over the class members and remove any Angular decorators from them.
|
||||
members.map(member => this._stripAngularDecorators(member)));
|
||||
return {node, before: statements};
|
||||
}
|
||||
|
||||
return {node};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all decorators on a `Declaration` which are from @angular/core, or an empty set if none
|
||||
* are.
|
||||
*/
|
||||
private _angularCoreDecorators(decl: ts.Declaration): Set<ts.Decorator> {
|
||||
const decorators = this.reflector.getDecoratorsOfDeclaration(decl);
|
||||
if (decorators === null) {
|
||||
return NO_DECORATORS;
|
||||
}
|
||||
const coreDecorators = decorators.filter(dec => this.isCore || isFromAngularCore(dec))
|
||||
.map(dec => dec.node as ts.Decorator);
|
||||
if (coreDecorators.length > 0) {
|
||||
return new Set<ts.Decorator>(coreDecorators);
|
||||
} else {
|
||||
return NO_DECORATORS;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a `ts.Node`, filter the decorators array and return a version containing only non-Angular
|
||||
* decorators.
|
||||
*
|
||||
* If all decorators are removed (or none existed in the first place), this method returns
|
||||
* `undefined`.
|
||||
*/
|
||||
private _nonCoreDecoratorsOnly(node: ts.Declaration): ts.NodeArray<ts.Decorator>|undefined {
|
||||
// Shortcut if the node has no decorators.
|
||||
if (node.decorators === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
// Build a Set of the decorators on this node from @angular/core.
|
||||
const coreDecorators = this._angularCoreDecorators(node);
|
||||
|
||||
if (coreDecorators.size === node.decorators.length) {
|
||||
// If all decorators are to be removed, return `undefined`.
|
||||
return undefined;
|
||||
} else if (coreDecorators.size === 0) {
|
||||
// If no decorators need to be removed, return the original decorators array.
|
||||
return node.decorators;
|
||||
}
|
||||
|
||||
// Filter out the core decorators.
|
||||
const filtered = node.decorators.filter(dec => !coreDecorators.has(dec));
|
||||
|
||||
// If no decorators survive, return `undefined`. This can only happen if a core decorator is
|
||||
// repeated on the node.
|
||||
if (filtered.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Create a new `NodeArray` with the filtered decorators that sourcemaps back to the original.
|
||||
const array = ts.createNodeArray(filtered);
|
||||
array.pos = node.decorators.pos;
|
||||
array.end = node.decorators.end;
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove Angular decorators from a `ts.Node` in a shallow manner.
|
||||
*
|
||||
* This will remove decorators from class elements (getters, setters, properties, methods) as well
|
||||
* as parameters of constructors.
|
||||
*/
|
||||
private _stripAngularDecorators<T extends ts.Node>(node: T): T {
|
||||
if (ts.isParameter(node)) {
|
||||
// Strip decorators from parameters (probably of the constructor).
|
||||
node = ts.updateParameter(
|
||||
node, this._nonCoreDecoratorsOnly(node), node.modifiers, node.dotDotDotToken,
|
||||
node.name, node.questionToken, node.type, node.initializer) as T &
|
||||
ts.ParameterDeclaration;
|
||||
} else if (ts.isMethodDeclaration(node) && node.decorators !== undefined) {
|
||||
// Strip decorators of methods.
|
||||
node = ts.updateMethod(
|
||||
node, this._nonCoreDecoratorsOnly(node), node.modifiers, node.asteriskToken,
|
||||
node.name, node.questionToken, node.typeParameters, node.parameters, node.type,
|
||||
node.body) as T &
|
||||
ts.MethodDeclaration;
|
||||
} else if (ts.isPropertyDeclaration(node) && node.decorators !== undefined) {
|
||||
// Strip decorators of properties.
|
||||
node = ts.updateProperty(
|
||||
node, this._nonCoreDecoratorsOnly(node), node.modifiers, node.name,
|
||||
node.questionToken, node.type, node.initializer) as T &
|
||||
ts.PropertyDeclaration;
|
||||
} else if (ts.isGetAccessor(node)) {
|
||||
// Strip decorators of getters.
|
||||
node = ts.updateGetAccessor(
|
||||
node, this._nonCoreDecoratorsOnly(node), node.modifiers, node.name,
|
||||
node.parameters, node.type, node.body) as T &
|
||||
ts.GetAccessorDeclaration;
|
||||
} else if (ts.isSetAccessor(node)) {
|
||||
// Strip decorators of setters.
|
||||
node = ts.updateSetAccessor(
|
||||
node, this._nonCoreDecoratorsOnly(node), node.modifiers, node.name,
|
||||
node.parameters, node.body) as T &
|
||||
ts.SetAccessorDeclaration;
|
||||
} else if (ts.isConstructorDeclaration(node)) {
|
||||
// For constructors, strip decorators of the parameters.
|
||||
const parameters = node.parameters.map(param => this._stripAngularDecorators(param));
|
||||
node =
|
||||
ts.updateConstructor(node, node.decorators, node.modifiers, parameters, node.body) as T &
|
||||
ts.ConstructorDeclaration;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A transformer which operates on ts.SourceFiles and applies changes from an `IvyCompilation`.
|
||||
*/
|
||||
function transformIvySourceFile(
|
||||
compilation: IvyCompilation, context: ts.TransformationContext,
|
||||
compilation: IvyCompilation, context: ts.TransformationContext, reflector: ReflectionHost,
|
||||
coreImportsFrom: ts.SourceFile | null, file: ts.SourceFile): ts.SourceFile {
|
||||
const importManager = new ImportManager(coreImportsFrom !== null);
|
||||
|
||||
// Recursively scan through the AST and perform any updates requested by the IvyCompilation.
|
||||
const sf = visit(file, new IvyVisitor(compilation, importManager), context);
|
||||
const sf = visit(
|
||||
file, new IvyVisitor(compilation, reflector, importManager, coreImportsFrom !== null),
|
||||
context);
|
||||
|
||||
// Generate the import statements to prepend.
|
||||
const imports = importManager.getAllImports(file.fileName, coreImportsFrom).map(i => {
|
||||
|
@ -108,3 +223,7 @@ function maybeFilterDecorator(
|
|||
}
|
||||
return ts.createNodeArray(filtered);
|
||||
}
|
||||
|
||||
function isFromAngularCore(decorator: Decorator): boolean {
|
||||
return decorator.import !== null && decorator.import.from === '@angular/core';
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue