Skip to content

Commit 0802b59

Browse files
danieltre23thePunderWoman
authored andcommitted
refactor(compiler-cli): add NullishCoalescingNotNullableCheck (angular#43232)
Add a template check that returns diagnostics if the left side of a nullish coalescing operation is not nullable. Refs angular#42966 PR Close angular#43232
1 parent fc85fa8 commit 0802b59

File tree

6 files changed

+83
-2
lines changed

6 files changed

+83
-2
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export enum ErrorCode {
4545
NGMODULE_MODULE_WITH_PROVIDERS_MISSING_GENERIC = 6005,
4646
NGMODULE_REEXPORT_NAME_COLLISION = 6006,
4747
NGMODULE_VE_DEPENDENCY_ON_IVY_LIB = 6999,
48+
NULLISH_COALESCING_NOT_NULLABLE = 8102,
4849
// (undocumented)
4950
PARAM_MISSING_TOKEN = 2003,
5051
// (undocumented)

packages/compiler-cli/src/ngtsc/core/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ ts_library(
4040
"//packages/compiler-cli/src/ngtsc/typecheck/extended",
4141
"//packages/compiler-cli/src/ngtsc/typecheck/extended/api",
4242
"//packages/compiler-cli/src/ngtsc/typecheck/extended/checks/invalid_banana_in_box",
43+
"//packages/compiler-cli/src/ngtsc/typecheck/extended/checks/nullish_coalescing_not_nullable",
4344
"//packages/compiler-cli/src/ngtsc/util",
4445
"//packages/compiler-cli/src/ngtsc/xi18n",
4546
"@npm//typescript",

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ import {aliasTransformFactory, CompilationMode, declarationTransformFactory, Dec
3131
import {TemplateTypeCheckerImpl} from '../../typecheck';
3232
import {OptimizeFor, TemplateTypeChecker, TypeCheckingConfig} from '../../typecheck/api';
3333
import {ExtendedTemplateCheckerImpl} from '../../typecheck/extended';
34-
import {ExtendedTemplateChecker} from '../../typecheck/extended/api';
34+
import {ExtendedTemplateChecker, TemplateCheck} from '../../typecheck/extended/api';
3535
import {InvalidBananaInBoxCheck} from '../../typecheck/extended/checks/invalid_banana_in_box';
36+
import {NullishCoalescingNotNullableCheck} from '../../typecheck/extended/checks/nullish_coalescing_not_nullable';
3637
import {getSourceFileOrNull, isDtsPath, resolveModuleName, toUnredirectedSourceFile} from '../../util/src/typescript';
3738
import {Xi18nContext} from '../../xi18n';
3839
import {LazyRoute, NgCompilerAdapter, NgCompilerOptions} from '../api';
@@ -1109,7 +1110,10 @@ export class NgCompiler {
11091110
reflector, this.adapter, this.incrementalCompilation, scopeRegistry, typeCheckScopeRegistry,
11101111
this.delegatingPerfRecorder);
11111112

1112-
const templateChecks = [new InvalidBananaInBoxCheck()];
1113+
const templateChecks: TemplateCheck<ErrorCode>[] = [new InvalidBananaInBoxCheck()];
1114+
if (this.options.strictNullChecks) {
1115+
templateChecks.push(new NullishCoalescingNotNullableCheck());
1116+
}
11131117
const extendedTemplateChecker =
11141118
new ExtendedTemplateCheckerImpl(templateTypeChecker, checker, templateChecks);
11151119

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

+10
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,16 @@ export enum ErrorCode {
182182
*/
183183
INVALID_BANANA_IN_BOX = 8101,
184184

185+
/**
186+
* The left side of a nullish coalescing operation is not nullable.
187+
*
188+
* ```
189+
* {{ foo ?? bar }}
190+
* ```
191+
* When the type of foo doesn't include `null` or `undefined`.
192+
*/
193+
NULLISH_COALESCING_NOT_NULLABLE = 8102,
194+
185195
/**
186196
* The template type-checking engine would need to generate an inline type check block for a
187197
* component, but the current type-checking environment doesn't support it.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
load("//tools:defaults.bzl", "ts_library")
2+
3+
ts_library(
4+
name = "nullish_coalescing_not_nullable",
5+
srcs = ["index.ts"],
6+
visibility = ["//packages/compiler-cli/src/ngtsc:__subpackages__"],
7+
deps = [
8+
"//packages/compiler",
9+
"//packages/compiler-cli/src/ngtsc/diagnostics",
10+
"//packages/compiler-cli/src/ngtsc/typecheck/api",
11+
"//packages/compiler-cli/src/ngtsc/typecheck/extended/api",
12+
"@npm//typescript",
13+
],
14+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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.io/license
7+
*/
8+
9+
import {AST, Binary, TmplAstNode} from '@angular/compiler';
10+
import * as ts from 'typescript';
11+
12+
import {ErrorCode} from '../../../../diagnostics';
13+
import {NgTemplateDiagnostic, SymbolKind} from '../../../api';
14+
import {TemplateCheckWithVisitor, TemplateContext} from '../../api';
15+
16+
/**
17+
* Ensures the left side of a nullish coalescing operation is nullable.
18+
* Returns diagnostics for the cases where the operator is useless.
19+
* This check should only be use if `strictNullChecks` is enabled,
20+
* otherwise it would produce inaccurate results.
21+
*/
22+
export class NullishCoalescingNotNullableCheck extends
23+
TemplateCheckWithVisitor<ErrorCode.NULLISH_COALESCING_NOT_NULLABLE> {
24+
override code = ErrorCode.NULLISH_COALESCING_NOT_NULLABLE as const;
25+
26+
override visitNode(ctx: TemplateContext, component: ts.ClassDeclaration, node: TmplAstNode|AST):
27+
NgTemplateDiagnostic<ErrorCode.NULLISH_COALESCING_NOT_NULLABLE>[] {
28+
if (!(node instanceof Binary) || node.operation !== '??') return [];
29+
30+
const symbolLeft = ctx.templateTypeChecker.getSymbolOfNode(node.left, component)!;
31+
if (symbolLeft.kind !== SymbolKind.Expression) {
32+
return [];
33+
}
34+
const typeLeft = symbolLeft.tsType;
35+
// If the left operand's type is different from its non-nullable self, then it must
36+
// contain a null or undefined so this nullish coalescing operator is useful. No diagnostic to
37+
// report.
38+
if (typeLeft.getNonNullableType() !== typeLeft) return [];
39+
40+
const symbol = ctx.templateTypeChecker.getSymbolOfNode(node, component)!;
41+
if (symbol.kind !== SymbolKind.Expression) {
42+
return [];
43+
}
44+
const span =
45+
ctx.templateTypeChecker.getTemplateMappingAtShimLocation(symbol.shimLocation)!.span;
46+
const diagnostic = ctx.templateTypeChecker.makeTemplateDiagnostic(
47+
component, span, ts.DiagnosticCategory.Warning, ErrorCode.NULLISH_COALESCING_NOT_NULLABLE,
48+
`The left side of this nullish coalescing operation does not include 'null' or 'undefined' in its type, therefore the '??' operator can be safely removed.`);
49+
return [diagnostic];
50+
}
51+
}

0 commit comments

Comments
 (0)