Skip to content

Commit 2c141c0

Browse files
surajy93leonsenft
authored andcommitted
refactor(compiler-cli): simplify Angular decorator stripping
Removes redundant decorator-stripping branches and consolidates the transformation flow to reduce complexity and improve readability.
1 parent 271dd4d commit 2c141c0

2 files changed

Lines changed: 50 additions & 94 deletions

File tree

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

Lines changed: 15 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import {ConstantPool} from '@angular/compiler';
1010
import ts from 'typescript';
11-
1211
import {
1312
DefaultImportTracker,
1413
ImportRewriter,
@@ -275,7 +274,7 @@ class IvyTransformationVisitor extends Visitor {
275274
}
276275
}
277276

278-
private _nonCoreDecoratorsOnly(node: ts.HasDecorators): ts.NodeArray<ts.Decorator> | undefined {
277+
private _nonCoreDecoratorsOnly(node: ts.HasDecorators): ts.Decorator[] | undefined {
279278
const decorators = ts.getDecorators(node);
280279

281280
// Shortcut if the node has no decorators.
@@ -285,25 +284,8 @@ class IvyTransformationVisitor extends Visitor {
285284
// Build a Set of the decorators on this node from @angular/core.
286285
const coreDecorators = this._angularCoreDecorators(node);
287286

288-
if (coreDecorators.size === decorators.length) {
289-
// If all decorators are to be removed, return `undefined`.
290-
return undefined;
291-
} else if (coreDecorators.size === 0) {
292-
// If no decorators need to be removed, return the original decorators array.
293-
return nodeArrayFromDecoratorsArray(decorators);
294-
}
295-
296287
// Filter out the core decorators.
297-
const filtered = decorators.filter((dec) => !coreDecorators.has(dec));
298-
299-
// If no decorators survive, return `undefined`. This can only happen if a core decorator is
300-
// repeated on the node.
301-
if (filtered.length === 0) {
302-
return undefined;
303-
}
304-
305-
// Create a new `NodeArray` with the filtered decorators that sourcemaps back to the original.
306-
return nodeArrayFromDecoratorsArray(filtered);
288+
return decorators.filter((dec) => !coreDecorators.has(dec));
307289
}
308290

309291
/**
@@ -314,71 +296,24 @@ class IvyTransformationVisitor extends Visitor {
314296
*/
315297
private _stripAngularDecorators<T extends ts.Node>(node: T): T {
316298
const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
317-
const nonCoreDecorators = ts.canHaveDecorators(node)
318-
? this._nonCoreDecoratorsOnly(node)
319-
: undefined;
320-
const combinedModifiers = [...(nonCoreDecorators || []), ...(modifiers || [])];
321-
322-
if (ts.isParameter(node)) {
323-
// Strip decorators from parameters (probably of the constructor).
324-
node = ts.factory.updateParameterDeclaration(
325-
node,
326-
combinedModifiers,
327-
node.dotDotDotToken,
328-
node.name,
329-
node.questionToken,
330-
node.type,
331-
node.initializer,
332-
) as T & ts.ParameterDeclaration;
333-
} else if (ts.isMethodDeclaration(node)) {
334-
// Strip decorators of methods.
335-
node = ts.factory.updateMethodDeclaration(
336-
node,
337-
combinedModifiers,
338-
node.asteriskToken,
339-
node.name,
340-
node.questionToken,
341-
node.typeParameters,
342-
node.parameters,
343-
node.type,
344-
node.body,
345-
) as T & ts.MethodDeclaration;
346-
} else if (ts.isPropertyDeclaration(node)) {
347-
// Strip decorators of properties.
348-
node = ts.factory.updatePropertyDeclaration(
349-
node,
350-
combinedModifiers,
351-
node.name,
352-
node.questionToken || node.exclamationToken,
353-
node.type,
354-
node.initializer,
355-
) as T & ts.PropertyDeclaration;
356-
} else if (ts.isGetAccessor(node)) {
357-
// Strip decorators of getters.
358-
node = ts.factory.updateGetAccessorDeclaration(
359-
node,
360-
combinedModifiers,
361-
node.name,
362-
node.parameters,
363-
node.type,
364-
node.body,
365-
) as T & ts.GetAccessorDeclaration;
366-
} else if (ts.isSetAccessor(node)) {
367-
// Strip decorators of setters.
368-
node = ts.factory.updateSetAccessorDeclaration(
369-
node,
370-
combinedModifiers,
371-
node.name,
372-
node.parameters,
373-
node.body,
374-
) as T & ts.SetAccessorDeclaration;
375-
} else if (ts.isConstructorDeclaration(node)) {
299+
if (ts.isConstructorDeclaration(node)) {
376300
// For constructors, strip decorators of the parameters.
377301
const parameters = node.parameters.map((param) => this._stripAngularDecorators(param));
378302
node = ts.factory.updateConstructorDeclaration(node, modifiers, parameters, node.body) as T &
379303
ts.ConstructorDeclaration;
380304
}
381-
return node;
305+
306+
if (!ts.canHaveDecorators(node)) {
307+
return node;
308+
}
309+
310+
const nonCoreDecorators = this._nonCoreDecoratorsOnly(node);
311+
if (nonCoreDecorators === undefined) {
312+
return node;
313+
}
314+
315+
const combinedModifiers = [...nonCoreDecorators, ...(modifiers ?? [])];
316+
return ts.factory.replaceDecoratorsAndModifiers(node, combinedModifiers) as T;
382317
}
383318
}
384319

@@ -572,17 +507,3 @@ function createRecorderFn(
572507
}
573508
};
574509
}
575-
576-
/** Creates a `NodeArray` with the correct offsets from an array of decorators. */
577-
function nodeArrayFromDecoratorsArray(
578-
decorators: readonly ts.Decorator[],
579-
): ts.NodeArray<ts.Decorator> {
580-
const array = ts.factory.createNodeArray(decorators);
581-
582-
if (array.length > 0) {
583-
(array.pos as number) = decorators[0].pos;
584-
(array.end as number) = decorators[decorators.length - 1].end;
585-
}
586-
587-
return array;
588-
}

packages/compiler-cli/test/ngtsc/ngtsc_spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9402,6 +9402,41 @@ runInEachFileSystem((os: string) => {
94029402
const diags = env.driveDiagnostics();
94039403
expect(diags.length).toBe(0);
94049404
});
9405+
9406+
it('should emit `declare` fields without runtime initialization in decorated classes', () => {
9407+
env.tsconfig();
9408+
env.write(
9409+
'test.ts',
9410+
`
9411+
import {Directive} from '@angular/core';
9412+
9413+
function Log(target: any, key: string): void {}
9414+
9415+
@Directive({selector: '[child]'})
9416+
export class Child {
9417+
@Log declare value: string;
9418+
}
9419+
`,
9420+
);
9421+
9422+
env.driveMain();
9423+
9424+
const jsContents = trim(env.getContents('test.js'));
9425+
expect(jsContents).toContain(
9426+
trim(`
9427+
import { Directive } from '@angular/core';
9428+
import * as i0 from "@angular/core";
9429+
function Log(target, key) { }
9430+
export class Child {
9431+
}
9432+
Child.ɵfac = function Child_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || Child)(); };
9433+
Child.ɵdir = /*@__PURE__*/ i0.ɵɵdefineDirective({ type: Child, selectors: [["", "child", ""]] });
9434+
__decorate([
9435+
Log
9436+
], Child.prototype, "value", void 0);
9437+
`),
9438+
);
9439+
});
94059440
});
94069441

94079442
describe('SVG animation processing', () => {

0 commit comments

Comments
 (0)