Skip to content

Commit a904d9f

Browse files
yjaaidithePunderWoman
authored andcommitted
fix(compiler-cli): support nested component declaration
```ts @component(...) class Outer { constructor() { @component(...) class Inner {} } } ``` previous behavior was that IVy transformation was only applied to `Inner`, thus breaking `Outer` transformation.
1 parent e673eb6 commit a904d9f

File tree

5 files changed

+105
-97
lines changed

5 files changed

+105
-97
lines changed

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,14 @@ class IvyTransformationVisitor extends Visitor {
136136
override visitClassDeclaration(
137137
node: ts.ClassDeclaration,
138138
): VisitListEntryResult<ts.Statement, ts.ClassDeclaration> {
139+
// Node might be modified since results were collected in the compilation phase.
140+
const original = ts.getOriginalNode(node, ts.isClassDeclaration);
141+
139142
// If this class is not registered in the map, it means that it doesn't have Angular decorators,
140143
// thus no further processing is required.
141-
if (!this.classCompilationMap.has(node)) {
144+
const compileResults =
145+
this.classCompilationMap.get(node) ?? this.classCompilationMap.get(original);
146+
if (!compileResults) {
142147
return {node};
143148
}
144149

@@ -153,9 +158,9 @@ class IvyTransformationVisitor extends Visitor {
153158

154159
// Note: Class may be already transformed by e.g. Tsickle and
155160
// not have a direct reference to the source file.
156-
const sourceFile = ts.getOriginalNode(node).getSourceFile();
161+
const sourceFile = original.getSourceFile();
157162

158-
for (const field of this.classCompilationMap.get(node)!) {
163+
for (const field of compileResults) {
159164
// Type-only member.
160165
if (field.initializer === null) {
161166
continue;

packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/GOLDEN_PARTIAL.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,3 +700,36 @@ export declare class MainStandalone {
700700
static ɵcmp: i0.ɵɵComponentDeclaration<MainStandalone, "ng-component", never, {}, {}, never, never, true, never>;
701701
}
702702

703+
/****************************************************************************************************
704+
* PARTIAL FILE: nested_component_definition.js
705+
****************************************************************************************************/
706+
import { Component } from '@angular/core';
707+
import * as i0 from "@angular/core";
708+
class Outer {
709+
constructor() {
710+
class Inner {
711+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Inner, deps: [], target: i0.ɵɵFactoryTarget.Component });
712+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: Inner, isStandalone: true, selector: "ng-component", ngImport: i0, template: 'inner', isInline: true });
713+
}
714+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Inner, decorators: [{
715+
type: Component,
716+
args: [{
717+
template: 'inner',
718+
}]
719+
}] });
720+
}
721+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Outer, deps: [], target: i0.ɵɵFactoryTarget.Component });
722+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: Outer, isStandalone: true, selector: "ng-component", ngImport: i0, template: 'outer', isInline: true });
723+
}
724+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Outer, decorators: [{
725+
type: Component,
726+
args: [{
727+
template: 'outer',
728+
}]
729+
}], ctorParameters: () => [] });
730+
731+
/****************************************************************************************************
732+
* PARTIAL FILE: nested_component_definition.d.ts
733+
****************************************************************************************************/
734+
export {};
735+

packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/TEST_CASES.json

Lines changed: 41 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -3,202 +3,143 @@
33
"cases": [
44
{
55
"description": "should instantiate directives in a closure when they are forward referenced",
6-
"inputFiles": [
7-
"forward_referenced_directive.ts"
8-
],
6+
"inputFiles": ["forward_referenced_directive.ts"],
97
"expectations": [
108
{
119
"failureMessage": "Invalid component definition",
12-
"files": [
13-
"forward_referenced_directive.js"
14-
]
10+
"files": ["forward_referenced_directive.js"]
1511
}
1612
]
1713
},
1814
{
1915
"description": "should instantiate pipes in a closure when they are forward referenced",
20-
"inputFiles": [
21-
"forward_referenced_pipe.ts"
22-
],
16+
"inputFiles": ["forward_referenced_pipe.ts"],
2317
"expectations": [
2418
{
2519
"failureMessage": "Invalid component definition",
26-
"files": [
27-
"forward_referenced_pipe.js"
28-
]
20+
"files": ["forward_referenced_pipe.js"]
2921
}
3022
]
3123
},
3224
{
3325
"description": "should split multiple `exportAs` values into an array",
34-
"inputFiles": [
35-
"export_as.ts"
36-
],
26+
"inputFiles": ["export_as.ts"],
3727
"expectations": [
3828
{
3929
"failureMessage": "Incorrect SomeDirective.ɵdir",
40-
"files": [
41-
"export_as.js"
42-
]
30+
"files": ["export_as.js"]
4331
}
4432
]
4533
},
4634
{
4735
"description": "should not generate a selectors array if the directive does not have a selector",
48-
"inputFiles": [
49-
"no_selector.ts"
50-
],
36+
"inputFiles": ["no_selector.ts"],
5137
"expectations": [
5238
{
5339
"failureMessage": "Invalid directive definition",
54-
"files": [
55-
"no_selector.js"
56-
]
40+
"files": ["no_selector.js"]
5741
}
5842
]
5943
},
6044
{
6145
"description": "should generate a pure function for constant object literals",
62-
"inputFiles": [
63-
"constant_object_literals.ts"
64-
],
46+
"inputFiles": ["constant_object_literals.ts"],
6547
"expectations": [
6648
{
6749
"failureMessage": "Invalid component definition",
68-
"files": [
69-
"constant_object_literals.js"
70-
]
50+
"files": ["constant_object_literals.js"]
7151
}
7252
]
7353
},
7454
{
7555
"description": "should generate a pure function for constant array literals",
76-
"inputFiles": [
77-
"constant_array_literals.ts"
78-
],
56+
"inputFiles": ["constant_array_literals.ts"],
7957
"expectations": [
8058
{
8159
"failureMessage": "Invalid component definition",
82-
"files": [
83-
"constant_array_literals.js"
84-
]
60+
"files": ["constant_array_literals.js"]
8561
}
8662
]
8763
},
8864
{
8965
"description": "should not share pure functions between null and object literals",
90-
"inputFiles": [
91-
"object_literals_null_vs_empty.ts"
92-
],
66+
"inputFiles": ["object_literals_null_vs_empty.ts"],
9367
"expectations": [
9468
{
9569
"failureMessage": "Invalid component definition",
96-
"files": [
97-
"object_literals_null_vs_empty.js"
98-
]
70+
"files": ["object_literals_null_vs_empty.js"]
9971
}
10072
]
10173
},
10274
{
10375
"description": "should not share pure functions between null and array literals",
104-
"inputFiles": [
105-
"array_literals_null_vs_empty.ts"
106-
],
76+
"inputFiles": ["array_literals_null_vs_empty.ts"],
10777
"expectations": [
10878
{
10979
"failureMessage": "Invalid component definition",
110-
"files": [
111-
"array_literals_null_vs_empty.js"
112-
]
80+
"files": ["array_literals_null_vs_empty.js"]
11381
}
11482
]
11583
},
11684
{
11785
"description": "should not share pure functions between null and function calls",
118-
"inputFiles": [
119-
"object_literals_null_vs_function.ts"
120-
],
86+
"inputFiles": ["object_literals_null_vs_function.ts"],
12187
"expectations": [
12288
{
12389
"failureMessage": "Invalid component definition",
124-
"files": [
125-
"object_literals_null_vs_function.js"
126-
]
90+
"files": ["object_literals_null_vs_function.js"]
12791
}
12892
]
12993
},
13094
{
13195
"description": "should emit a valid setClassMetadata call in ES5 if a class with a custom decorator is referencing itself inside its own metadata",
132-
"inputFiles": [
133-
"custom_decorator_es5.ts"
134-
],
96+
"inputFiles": ["custom_decorator_es5.ts"],
13597
"compilerOptions": {
13698
"target": "ES5"
13799
},
138-
"compilationModeFilter": [
139-
"full compile"
140-
],
100+
"compilationModeFilter": ["full compile"],
141101
"expectations": [
142102
{
143103
"failureMessage": "Incorrect setClassMetadata call",
144-
"files": [
145-
"custom_decorator_es5.js"
146-
]
104+
"files": ["custom_decorator_es5.js"]
147105
}
148106
]
149107
},
150108
{
151109
"description": "should support empty property bindings on ng-template",
152-
"inputFiles": [
153-
"ng_template_empty_binding.ts"
154-
],
110+
"inputFiles": ["ng_template_empty_binding.ts"],
155111
"expectations": [
156112
{
157113
"failureMessage": "Incorrect template",
158-
"files": [
159-
"ng_template_empty_binding.js"
160-
]
114+
"files": ["ng_template_empty_binding.js"]
161115
}
162116
]
163117
},
164118
{
165119
"description": "should support inline non-literal templates",
166-
"inputFiles": [
167-
"non_literal_template.ts"
168-
]
120+
"inputFiles": ["non_literal_template.ts"]
169121
},
170122
{
171123
"description": "should support inline non-literal templates using substitution",
172-
"inputFiles": [
173-
"non_literal_template_with_substitution.ts"
174-
]
124+
"inputFiles": ["non_literal_template_with_substitution.ts"]
175125
},
176126
{
177127
"description": "should support inline non-literal templates using string concatenation",
178-
"inputFiles": [
179-
"non_literal_template_with_concatenation.ts"
180-
]
128+
"inputFiles": ["non_literal_template_with_concatenation.ts"]
181129
},
182130
{
183131
"description": "should not use reexport names inside declarations when a direct export is available",
184-
"inputFiles": [
185-
"external_library.d.ts",
186-
"library_exports.ts"
187-
],
132+
"inputFiles": ["external_library.d.ts", "library_exports.ts"],
188133
"expectations": [
189134
{
190135
"failureMessage": "Invalid list of directives",
191-
"files": [
192-
"library_exports.js"
193-
]
136+
"files": ["library_exports.js"]
194137
}
195138
],
196139
"compilerOptions": {
197140
"baseUrl": ".",
198141
"paths": {
199-
"external_library": [
200-
"./external_library"
201-
]
142+
"external_library": ["./external_library"]
202143
}
203144
}
204145
},
@@ -209,13 +150,19 @@
209150
{
210151
"failureMessage": "Incorrect generated debug info statement",
211152
"files": ["debug_info.js"]
212-
}
153+
}
213154
],
214-
"compilationModeFilter": [
215-
"full compile",
216-
"local compile",
217-
"declaration-only emit"
155+
"compilationModeFilter": ["full compile", "local compile", "declaration-only emit"]
156+
},
157+
{
158+
"description": "should support nested component declarations",
159+
"inputFiles": ["nested_component_definition.ts"],
160+
"expectations": [
161+
{
162+
"failureMessage": "Invalid component definition",
163+
"files": ["nested_component_definition.js"]
164+
}
218165
]
219166
}
220167
]
221-
}
168+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class Outer {
2+
constructor() {
3+
class Inner {
4+
static ɵfac =
5+
static ɵcmp =
6+
}
7+
}
8+
9+
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {Component} from '@angular/core';
2+
3+
@Component({
4+
template: 'outer',
5+
})
6+
class Outer {
7+
constructor() {
8+
@Component({
9+
template: 'inner',
10+
})
11+
class Inner {}
12+
}
13+
}

0 commit comments

Comments
 (0)