Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): Provide a diagnostic for missing Signal invocation in tem…
…plate interpolation. (#49660) To improve DX for beginners, this commit adds an extended diagnostic for Signals in template interpolations. PR Close #49660
- Loading branch information
Showing
10 changed files
with
515 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
@name Signals must be invoked in template interpolations. | ||
|
||
@description | ||
|
||
Angular Signals are zero-argument functions (`() => T`). When executed, they return the current value of the signal. | ||
This means they are meant to be invoked when used in template interpolations to render its value. | ||
|
||
## What should I do instead? | ||
|
||
When you use a signal within a template interpolation, you need to invoke it to render its value. | ||
|
||
<code-example format="typescript" language="typescript"> | ||
|
||
import {Component, signal, Signal} from '@angular/core'; | ||
|
||
@Component({ | ||
// … | ||
}) | ||
class MyComponent { | ||
mySignal: Signal<number> = signal(0) | ||
} | ||
</code-example> | ||
|
||
<code-example format="html" language="html"> | ||
<div>{{ mySignal() }}/div> | ||
</code-example> | ||
|
||
<!-- links --> | ||
|
||
<!-- external links --> | ||
|
||
<!-- end links --> | ||
|
||
@reviewed 2023-04-02 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
...piler-cli/src/ngtsc/typecheck/extended/checks/interpolated_signal_not_invoked/BUILD.bazel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
load("//tools:defaults.bzl", "ts_library") | ||
|
||
ts_library( | ||
name = "interpolated_signal_not_invoked", | ||
srcs = ["index.ts"], | ||
visibility = [ | ||
"//packages/compiler-cli/src/ngtsc:__subpackages__", | ||
"//packages/compiler-cli/test/ngtsc:__pkg__", | ||
], | ||
deps = [ | ||
"//packages/compiler", | ||
"//packages/compiler-cli/src/ngtsc/diagnostics", | ||
"//packages/compiler-cli/src/ngtsc/typecheck/api", | ||
"//packages/compiler-cli/src/ngtsc/typecheck/extended/api", | ||
"@npm//typescript", | ||
], | ||
) |
67 changes: 67 additions & 0 deletions
67
...compiler-cli/src/ngtsc/typecheck/extended/checks/interpolated_signal_not_invoked/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import {AST, Interpolation, PropertyRead, TmplAstNode} from '@angular/compiler'; | ||
import ts from 'typescript'; | ||
|
||
import {ErrorCode, ExtendedTemplateDiagnosticName} from '../../../../diagnostics'; | ||
import {NgTemplateDiagnostic, SymbolKind} from '../../../api'; | ||
import {TemplateCheckFactory, TemplateCheckWithVisitor, TemplateContext} from '../../api'; | ||
|
||
/** | ||
* Ensures Signals are invoked when used in template interpolations. | ||
*/ | ||
class InterpolatedSignalCheck extends | ||
TemplateCheckWithVisitor<ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED> { | ||
override code = ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED as const; | ||
|
||
override visitNode( | ||
ctx: TemplateContext<ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED>, | ||
component: ts.ClassDeclaration, | ||
node: TmplAstNode|AST): NgTemplateDiagnostic<ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED>[] { | ||
if (node instanceof Interpolation) { | ||
return node.expressions.filter((item): item is PropertyRead => item instanceof PropertyRead) | ||
.flatMap((item) => { | ||
if (item instanceof PropertyRead) { | ||
return buildDiagnosticForSignal(ctx, item, component); | ||
} | ||
return []; | ||
}); | ||
} | ||
return []; | ||
} | ||
} | ||
|
||
function buildDiagnosticForSignal( | ||
ctx: TemplateContext<ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED>, node: PropertyRead, | ||
component: ts.ClassDeclaration): | ||
Array<NgTemplateDiagnostic<ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED>> { | ||
const symbol = ctx.templateTypeChecker.getSymbolOfNode(node, component); | ||
if (symbol?.kind === SymbolKind.Expression && | ||
/* can this condition be improved ? */ | ||
(symbol.tsType.symbol?.escapedName === 'WritableSignal' || | ||
symbol.tsType.symbol?.escapedName === 'Signal') && | ||
(symbol.tsType.symbol as any).parent.escapedName.includes('@angular/core')) { | ||
const templateMapping = | ||
ctx.templateTypeChecker.getTemplateMappingAtTcbLocation(symbol.tcbLocation)!; | ||
|
||
const errorString = `${node.name} is a function and should be invoked : ${node.name}()`; | ||
const diagnostic = ctx.makeTemplateDiagnostic(templateMapping.span, errorString); | ||
return [diagnostic]; | ||
} | ||
|
||
return []; | ||
} | ||
|
||
export const factory: TemplateCheckFactory< | ||
ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED, | ||
ExtendedTemplateDiagnosticName.INTERPOLATED_SIGNAL_NOT_INVOKED> = { | ||
code: ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED, | ||
name: ExtendedTemplateDiagnosticName.INTERPOLATED_SIGNAL_NOT_INVOKED, | ||
create: () => new InterpolatedSignalCheck(), | ||
}; |
27 changes: 27 additions & 0 deletions
27
...-cli/src/ngtsc/typecheck/extended/test/checks/interpolated_signal_not_invoked/BUILD.bazel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
load("//tools:defaults.bzl", "jasmine_node_test", "ts_library") | ||
|
||
ts_library( | ||
name = "test_lib", | ||
testonly = True, | ||
srcs = ["interpolated_signal_not_invoked_spec.ts"], | ||
deps = [ | ||
"//packages/compiler", | ||
"//packages/compiler-cli/src/ngtsc/core:api", | ||
"//packages/compiler-cli/src/ngtsc/diagnostics", | ||
"//packages/compiler-cli/src/ngtsc/file_system", | ||
"//packages/compiler-cli/src/ngtsc/file_system/testing", | ||
"//packages/compiler-cli/src/ngtsc/testing", | ||
"//packages/compiler-cli/src/ngtsc/typecheck/extended", | ||
"//packages/compiler-cli/src/ngtsc/typecheck/extended/checks/interpolated_signal_not_invoked", | ||
"//packages/compiler-cli/src/ngtsc/typecheck/testing", | ||
"@npm//typescript", | ||
], | ||
) | ||
|
||
jasmine_node_test( | ||
name = "test", | ||
bootstrap = ["//tools/testing:node_no_angular"], | ||
deps = [ | ||
":test_lib", | ||
], | ||
) |
Oops, something went wrong.