|
9 | 9 | import {WrappedNodeExpr} from '@angular/compiler'; |
10 | 10 | import * as ts from 'typescript'; |
11 | 11 |
|
| 12 | +import {Decorator, ReflectionHost} from '../../host'; |
| 13 | +import {relativePathBetween} from '../../util/src/path'; |
12 | 14 | import {VisitListEntryResult, Visitor, visit} from '../../util/src/visitor'; |
13 | 15 |
|
14 | 16 | import {CompileResult} from './api'; |
15 | 17 | import {IvyCompilation} from './compilation'; |
16 | 18 | import {ImportManager, translateExpression, translateStatement} from './translator'; |
17 | 19 |
|
| 20 | +const NO_DECORATORS = new Set<ts.Decorator>(); |
| 21 | + |
18 | 22 | export function ivyTransformFactory( |
19 | | - compilation: IvyCompilation, |
| 23 | + compilation: IvyCompilation, reflector: ReflectionHost, |
20 | 24 | coreImportsFrom: ts.SourceFile | null): ts.TransformerFactory<ts.SourceFile> { |
21 | 25 | return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => { |
22 | 26 | return (file: ts.SourceFile): ts.SourceFile => { |
23 | | - return transformIvySourceFile(compilation, context, coreImportsFrom, file); |
| 27 | + return transformIvySourceFile(compilation, context, reflector, coreImportsFrom, file); |
24 | 28 | }; |
25 | 29 | }; |
26 | 30 | } |
27 | 31 |
|
28 | 32 | 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) { |
30 | 36 | super(); |
31 | 37 | } |
32 | 38 |
|
@@ -62,24 +68,133 @@ class IvyVisitor extends Visitor { |
62 | 68 | // Remove the decorator which triggered this compilation, leaving the others alone. |
63 | 69 | maybeFilterDecorator( |
64 | 70 | 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))); |
66 | 74 | return {node, before: statements}; |
67 | 75 | } |
68 | 76 |
|
69 | 77 | return {node}; |
70 | 78 | } |
| 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 | + } |
71 | 184 | } |
72 | 185 |
|
73 | 186 | /** |
74 | 187 | * A transformer which operates on ts.SourceFiles and applies changes from an `IvyCompilation`. |
75 | 188 | */ |
76 | 189 | function transformIvySourceFile( |
77 | | - compilation: IvyCompilation, context: ts.TransformationContext, |
| 190 | + compilation: IvyCompilation, context: ts.TransformationContext, reflector: ReflectionHost, |
78 | 191 | coreImportsFrom: ts.SourceFile | null, file: ts.SourceFile): ts.SourceFile { |
79 | 192 | const importManager = new ImportManager(coreImportsFrom !== null); |
80 | 193 |
|
81 | 194 | // 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); |
83 | 198 |
|
84 | 199 | // Generate the import statements to prepend. |
85 | 200 | const imports = importManager.getAllImports(file.fileName, coreImportsFrom).map(i => { |
@@ -108,3 +223,7 @@ function maybeFilterDecorator( |
108 | 223 | } |
109 | 224 | return ts.createNodeArray(filtered); |
110 | 225 | } |
| 226 | + |
| 227 | +function isFromAngularCore(decorator: Decorator): boolean { |
| 228 | + return decorator.import !== null && decorator.import.from === '@angular/core'; |
| 229 | +} |
0 commit comments