Skip to content

Commit 694ba79

Browse files
crisbetodylhunn
authored andcommitted
fix(compiler-cli): report cases where initializer APIs are used in a non-directive class (#54993)
Expands the check for initializer APIs to also flag when the function is called on a class that isn't a component or directive. PR Close #54993
1 parent 78188e8 commit 694ba79

File tree

2 files changed

+66
-12
lines changed

2 files changed

+66
-12
lines changed

packages/compiler-cli/src/ngtsc/validation/src/rules/initializer_api_usage_rule.ts

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export class InitializerApiUsageRule implements SourceFileValidatorRule {
3838
});
3939
}
4040

41-
checkNode(node: ts.Node): ts.Diagnostic[]|null {
41+
checkNode(node: ts.Node): ts.Diagnostic|null {
4242
// We only care about call expressions.
4343
if (!ts.isCallExpression(node)) {
4444
return null;
@@ -50,9 +50,7 @@ export class InitializerApiUsageRule implements SourceFileValidatorRule {
5050
node = node.parent;
5151
}
5252

53-
// Initializer functions are allowed to be used in the initializer.
54-
if (!node.parent || !ts.isCallExpression(node) ||
55-
(ts.isPropertyDeclaration(node.parent) && node.parent.initializer === node)) {
53+
if (!node.parent || !ts.isCallExpression(node)) {
5654
return null;
5755
}
5856

@@ -62,13 +60,36 @@ export class InitializerApiUsageRule implements SourceFileValidatorRule {
6260
return null;
6361
}
6462

65-
return [
66-
makeDiagnostic(
67-
ErrorCode.UNSUPPORTED_INITIALIZER_API_USAGE, node,
68-
`Unsupported call to the ${identifiedInitializer.api.functionName}${
69-
identifiedInitializer.isRequired ?
70-
'.required' :
71-
''} function. This function can only be called in the initializer of a class member.`)
72-
];
63+
const functionName = identifiedInitializer.api.functionName +
64+
(identifiedInitializer.isRequired ? '.required' : '');
65+
66+
if (ts.isPropertyDeclaration(node.parent) && node.parent.initializer === node) {
67+
let closestClass: ts.Node = node.parent;
68+
69+
while (closestClass && !ts.isClassDeclaration(closestClass)) {
70+
closestClass = closestClass.parent;
71+
}
72+
73+
if (closestClass && ts.isClassDeclaration(closestClass)) {
74+
const decorators = this.reflector.getDecoratorsOfDeclaration(closestClass);
75+
const isComponentOrDirective = decorators !== null && decorators.some(decorator => {
76+
return decorator.import?.from === '@angular/core' &&
77+
(decorator.name === 'Component' || decorator.name === 'Directive');
78+
});
79+
80+
return isComponentOrDirective ?
81+
null :
82+
makeDiagnostic(
83+
ErrorCode.UNSUPPORTED_INITIALIZER_API_USAGE, node,
84+
`Unsupported call to the ${
85+
functionName} function. This function can only be used as the initializer ` +
86+
`of a property on a @Component or @Directive class.`);
87+
}
88+
}
89+
90+
return makeDiagnostic(
91+
ErrorCode.UNSUPPORTED_INITIALIZER_API_USAGE, node,
92+
`Unsupported call to the ${
93+
functionName} function. This function can only be called in the initializer of a class member.`);
7394
}
7495
}

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,5 +209,38 @@ runInEachFileSystem(() => {
209209
const diags = env.driveDiagnostics();
210210
expect(diags.length).toBe(0);
211211
});
212+
213+
it('should report initializer function being used in an undecorated class', () => {
214+
env.write('test.ts', `
215+
import {input} from '@angular/core';
216+
217+
export class Test {
218+
inp = input();
219+
}
220+
`);
221+
222+
const diags = env.driveDiagnostics();
223+
expect(diags.length).toBe(1);
224+
expect(diags[0].messageText)
225+
.toContain(
226+
'Unsupported call to the input function. This function can only be used as the initializer of a property on a @Component or @Directive class.');
227+
});
228+
229+
it('should report initializer function being used in an unsupported Angular class', () => {
230+
env.write('test.ts', `
231+
import {input, Pipe} from '@angular/core';
232+
233+
@Pipe({name: 'test'})
234+
export class Test {
235+
inp = input();
236+
}
237+
`);
238+
239+
const diags = env.driveDiagnostics();
240+
expect(diags.length).toBe(1);
241+
expect(diags[0].messageText)
242+
.toContain(
243+
'Unsupported call to the input function. This function can only be used as the initializer of a property on a @Component or @Directive class.');
244+
});
212245
});
213246
});

0 commit comments

Comments
 (0)