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,
|
options: this.options,
|
||||||
emitOnlyDtsFiles: false, writeFile,
|
emitOnlyDtsFiles: false, writeFile,
|
||||||
customTransformers: {
|
customTransformers: {
|
||||||
before: [ivyTransformFactory(compilation, coreImportsFrom)],
|
before: [ivyTransformFactory(compilation, reflector, coreImportsFrom)],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return emitResult;
|
return emitResult;
|
||||||
|
|
|
@ -9,24 +9,30 @@
|
||||||
import {WrappedNodeExpr} from '@angular/compiler';
|
import {WrappedNodeExpr} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
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 {VisitListEntryResult, Visitor, visit} from '../../util/src/visitor';
|
||||||
|
|
||||||
import {CompileResult} from './api';
|
import {CompileResult} from './api';
|
||||||
import {IvyCompilation} from './compilation';
|
import {IvyCompilation} from './compilation';
|
||||||
import {ImportManager, translateExpression, translateStatement} from './translator';
|
import {ImportManager, translateExpression, translateStatement} from './translator';
|
||||||
|
|
||||||
|
const NO_DECORATORS = new Set<ts.Decorator>();
|
||||||
|
|
||||||
export function ivyTransformFactory(
|
export function ivyTransformFactory(
|
||||||
compilation: IvyCompilation,
|
compilation: IvyCompilation, reflector: ReflectionHost,
|
||||||
coreImportsFrom: ts.SourceFile | null): ts.TransformerFactory<ts.SourceFile> {
|
coreImportsFrom: ts.SourceFile | null): ts.TransformerFactory<ts.SourceFile> {
|
||||||
return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
|
return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
|
||||||
return (file: ts.SourceFile): 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 {
|
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();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,24 +68,133 @@ class IvyVisitor extends Visitor {
|
||||||
// Remove the decorator which triggered this compilation, leaving the others alone.
|
// Remove the decorator which triggered this compilation, leaving the others alone.
|
||||||
maybeFilterDecorator(
|
maybeFilterDecorator(
|
||||||
node.decorators, this.compilation.ivyDecoratorFor(node) !.node as ts.Decorator),
|
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, before: statements};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {node};
|
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`.
|
* A transformer which operates on ts.SourceFiles and applies changes from an `IvyCompilation`.
|
||||||
*/
|
*/
|
||||||
function transformIvySourceFile(
|
function transformIvySourceFile(
|
||||||
compilation: IvyCompilation, context: ts.TransformationContext,
|
compilation: IvyCompilation, context: ts.TransformationContext, reflector: ReflectionHost,
|
||||||
coreImportsFrom: ts.SourceFile | null, file: ts.SourceFile): ts.SourceFile {
|
coreImportsFrom: ts.SourceFile | null, file: ts.SourceFile): ts.SourceFile {
|
||||||
const importManager = new ImportManager(coreImportsFrom !== null);
|
const importManager = new ImportManager(coreImportsFrom !== null);
|
||||||
|
|
||||||
// Recursively scan through the AST and perform any updates requested by the IvyCompilation.
|
// 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.
|
// Generate the import statements to prepend.
|
||||||
const imports = importManager.getAllImports(file.fileName, coreImportsFrom).map(i => {
|
const imports = importManager.getAllImports(file.fileName, coreImportsFrom).map(i => {
|
||||||
|
@ -108,3 +223,7 @@ function maybeFilterDecorator(
|
||||||
}
|
}
|
||||||
return ts.createNodeArray(filtered);
|
return ts.createNodeArray(filtered);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isFromAngularCore(decorator: Decorator): boolean {
|
||||||
|
return decorator.import !== null && decorator.import.from === '@angular/core';
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue