8000 feat(compiler-cli): add diagnostic for uninvoked functions in text in… · angular/angular@e0ca7b4 · GitHub
[go: up one dir, main page]

Skip to content

Commit e0ca7b4

Browse files
RafaelJCamaraJeanMeche
authored andcommitted
feat(compiler-cli): add diagnostic for uninvoked functions in text interpolation
This commit adds a new extended diagnostic to detect uninvoked functions in text interpolation.
1 parent 586be0e commit e0ca7b4

File tree

13 files changed

+367
-1
lines changed

13 files changed

+367
-1
lines changed

adev/src/content/reference/extended-diagnostics/NG8114.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,4 @@ class MyComponent {
4949
task = input<Task|undefined>(undefined);
5050
}
5151

52-
</docs-code>
52+
</docs-code>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Functions should be invoked in text interpolation.
2+
3+
This diagnostic detects uninvoked functions in text interpolation.
4+
5+
<docs-code language="typescript">
6+
7+
import {Component} from '@angular/core';
8+
9+
@Component({
10+
template: `{{ getValue }}`,
11+
})
12+
class MyComponent {
13+
getValue() {
14+
return 'value';
15+
}
16+
}
17+
18+
</docs-code>
19+
20+
## What's wrong with that?
21+
22+
Functions in text interpolation should be invoked to return a value.
23+
If the function is not invoked, it will not return any value and the interpolation will not work as expected.
24+
25+
## What should I do instead?
26+
27+
Ensure to invoke the function when you use it in text interpolation to return the value.
28+
29+
<docs-code language="typescript">
30+
31+
import {Component} from '@angular/core';
32+
33+
@Component({
34+
template: `{{ getValue() }}`,
35+
})
36+
class MyComponent {
37+
getValue() {
38+
return 'value';
39+
}
40+
}
41+
42+
</docs-code>
43+
44+
## Configuration requirements
45+
46+
[`strictTemplates`](/tools/cli/template-typecheck#strict-mode) must be enabled for any extended diagnostic to emit.
47+
`uninvokedFunctionInTextInterpolation` has no additional requirements beyond `strictTemplates`.
48+
49+
## What if I can't avoid this?
50+
51+
This diagnostic can be disabled by editing the project's `tsconfig.json` file:
52+
53+
<docs-code language="json">
54+
{
55+
"angularCompilerOptions": {
56+
"extendedDiagnostics": {
57+
"checks": {
58+
"uninvokedFunctionInTextInterpolation": "suppress"
59+
}
60+
}
61+
}
62+
}
63+
</docs-code>
64+
65+
See [extended diagnostic configuration](/extended-diagnostics#configuration) for more info.

adev/src/content/reference/extended-diagnostics/overview.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Currently, Angular supports the following extended diagnostics:
2323
| `NG8113` | [`unusedStandaloneImports`](extended-diagnostics/NG8113) |
2424
| `NG8114` | [`unparenthesizedNullishCoalescing`](extended-diagnostics/NG8114) |
2525
| `NG8116` | [`missingStructuralDirective`](extended-diagnostics/NG8116) |
26+
| `NG8117` | [`uninvokedFunctionInTextInterpolation`](extended-diagnostics/NG8114) |
2627

2728
## Configuration
2829

goldens/public-api/compiler-cli/error_code.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ export enum ErrorCode {
115115
UNINVOKED_FUNCTION_IN_EVENT_BINDING = 8111,
116116
UNINVOKED_TRACK_FUNCTION = 8115,
117117
UNPARENTHESIZED_NULLISH_COALESCING = 8114,
118+
UNINVOKED_FUNCTION_IN_TEXT_INTERPOLATION = 8117,
118119
UNSUPPORTED_INITIALIZER_API_USAGE = 8110,
119120
UNSUPPORTED_SELECTORLESS_COMPONENT_FIELD = 2026,
120121
UNUSED_LET_DECLARATION = 8112,

goldens/public-api/compiler-cli/extended_template_diagnostic_name.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export enum ExtendedTemplateDiagnosticName {
3434
UNINVOKED_TRACK_FUNCTION = "uninvokedTrackFunction",
3535
// (undocumented)
3636
UNPARENTHESIZED_NULLISH_COALESCING = "unparenthesizedNullishCoalescing",
37+
UNINVOKED_FUNCTION_IN_TEXT_INTERPOLATION = "uninvokedFunctionInTextInterpolation",
3738
// (undocumented)
3839
UNUSED_LET_DECLARATION = "unusedLetDeclaration",
3940
// (undocumented)

packages/compiler-cli/src/ngtsc/diagnostics/src/error_code.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,21 @@ export enum ErrorCode {
595595
*/
596596
MISSING_STRUCTURAL_DIRECTIVE = 8116,
597597

598+
/**
599+
* A function in a text interpolation is not invoked.
600+
*
601+
* For example:
602+
* ```html
603+
* <p> {{ firstName }} </p>
604+
* ```
605+
*
606+
* The `firstName` function is not invoked. Instead, it should be:
607+
* ```html
608+
* <p> {{ firstName() }} </p>
609+
* ```
610+
*/
611+
UNINVOKED_FUNCTION_IN_TEXT_INTERPOLATION = 8117,
612+
598613
/**
599614
* The template type-checking engine would need to generate an inline type check block for a
600615
* component, but the current type-checking environment doesn't support it.

packages/compiler-cli/src/ngtsc/diagnostics/src/extended_template_diagnostic_name.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,5 @@ export enum ExtendedTemplateDiagnosticName {
3232
UNINVOKED_TRACK_FUNCTION = 'uninvokedTrackFunction',
3333
UNUSED_STANDALONE_IMPORTS = 'unusedStandaloneImports',
3434
UNPARENTHESIZED_NULLISH_COALESCING = 'unparenthesizedNullishCoalescing',
35+
UNINVOKED_FUNCTION_IN_TEXT_INTERPOLATION = 'uninvokedFunctionInTextInterpolation',
3536
}

packages/compiler-cli/src/ngtsc/typecheck/extended/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ ts_project(
2424
"//packages/compiler-cli/src/ngtsc/typecheck/extended/checks/uninvoked_function_in_event_binding",
2525
"//packages/compiler-cli/src/ngtsc/typecheck/extended/checks/uninvoked_track_function",
2626
"//packages/compiler-cli/src/ngtsc/typecheck/extended/checks/unparenthesized_nullish_coalescing",
27+
"//packages/compiler-cli/src/ngtsc/typecheck/extended/checks/uninvoked_function_in_text_interpolation",
2728
"//packages/compiler-cli/src/ngtsc/typecheck/extended/checks/unused_let_declaration",
2829
],
2930
visibility = ["//packages/compiler-cli/src/ngtsc:__subpackages__"],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
load("//tools:defaults.bzl", "ts_library")
2+
3+
ts_library(
4+
name = "uninvoked_function_in_text_interpolation",
5+
srcs = ["index.ts"],
6+
visibility = [
7+
"//packages/compiler-cli/src/ngtsc:__subpackages__",
8+
"//packages/compiler-cli/test/ngtsc:__pkg__",
9+
],
10+
deps = [
11+
"//packages/compiler",
12+
"//packages/compiler-cli/src/ngtsc/diagnostics",
13+
"//packages/compiler-cli/src/ngtsc/typecheck",
14+
"//packages/compiler-cli/src/ngtsc/typecheck/api",
15+
"//packages/compiler-cli/src/ngtsc/typecheck/extended/api",
16+
"@npm//typescript",
17+
],
18+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import ts from 'typescript';
10+
import {ErrorCode, ExtendedTemplateDiagnosticName} from '../../../../diagnostics';
11+
import {NgTemplateDiagnostic, SymbolKind} from '../../../api';
12+
import {TemplateCheckFactory, TemplateCheckWithVisitor, TemplateContext} from '../../api';
13+
import {
14+
AbsoluteSourceSpan,
15+
AST,
16+
Call,
17+
Interpolation,
18+
PropertyRead,
19+
SafeCall,
20+
SafePropertyRead,
21+
TmplAstNode,
22+
} from '@angular/compiler';
23+
24+
class UninvokedFunctionInTextInterpolation extends TemplateCheckWithVisitor<ErrorCode.UNINVOKED_FUNCTION_IN_TEXT_INTERPOLATION> {
25+
override code = ErrorCode.UNINVOKED_FUNCTION_IN_TEXT_INTERPOLATION as const;
26+
27+
override visitNode(
28+
ctx: TemplateContext<ErrorCode.UNINVOKED_FUNCTION_IN_TEXT_INTERPOLATION>,
29+
component: ts.ClassDeclaration,
30+
node: TmplAstNode | AST,
31+
): NgTemplateDiagnostic<ErrorCode.UNINVOKED_FUNCTION_IN_TEXT_INTERPOLATION>[] {
32+
// interpolations like `{{ myFunction }}`
33+
if (node instanceof Interpolation) {
34+
return node.expressions.flatMap((item) =>
35+
assertExpressionInvoked(item, component, node.sourceSpan, ctx),
36+
);
37+
}
38+
return [];
39+
}
40+
}
41+
42+
function assertExpressionInvoked(
43+
expression: AST,
44+
component: ts.ClassDeclaration,
45+
sourceSpan: AbsoluteSourceSpan,
46+
ctx: TemplateContext<ErrorCode.UNINVOKED_FUNCTION_IN_TEXT_INTERPOLATION>,
47+
): NgTemplateDiagnostic<ErrorCode.UNINVOKED_FUNCTION_IN_TEXT_INTERPOLATION>[] {
48+
if (!(expression instanceof PropertyRead) && !(expression instanceof SafePropertyRead)) {
49+
return []; // If the expression is not a property read, skip it.
50+
}
51+
52+
const symbol = ctx.templateTypeChecker.getSymbolOfNode(expression, component);
53+
54+
if (symbol !== null && symbol.kind === SymbolKind.Expression) {
55+
if (symbol.tsType.getCallSignatures()?.length > 0) {
56+
const fullExpressionText = generateStringFromExpression(expression, sourceSpan.toString());
57+
const errorString = `Function in text interpolation should be invoked: ${fullExpressionText}()`;
58+
const templateMapping = ctx.templateTypeChecker.getTemplateMappingAtTcbLocation(
59+
symbol.tcbLocation,
60+
)!;
61+
return [ctx.makeTemplateDiagnostic(templateMapping.span, errorString)];
62+
}
63+
}
64+
65+
return [];
66+
}
67+
68+
function generateStringFromExpression(expression: AST, source: string): string {
69+
return source.substring(expression.span.start, expression.span.end);
70+
}
71+
72+
export const factory: TemplateCheckFactory<
73+
ErrorCode.UNINVOKED_FUNCTION_IN_TEXT_INTERPOLATION,
74+
ExtendedTemplateDiagnosticName.UNINVOKED_FUNCTION_IN_TEXT_INTERPOLATION
75+
> = {
76+
code: ErrorCode.UNINVOKED_FUNCTION_IN_TEXT_INTERPOLATION,
77+
name: ExtendedTemplateDiagnosticName.UNINVOKED_FUNCTION_IN_TEXT_INTERPOLATION,
78+
create: () => new UninvokedFunctionInTextInterpolation(),
79+
};

0 commit comments

Comments
 (0)
0