Skip to content

Commit b30f699

Browse files
committed
feat(migrations): Disabling nullishCoalescingNotNullable & optionalChainNotNullable on ng update
Related to angular#67959 disabling two diagnostics errors by `ng update`: - nullishCoalescingNotNullable - optionalChainNotNullable
1 parent e95d846 commit b30f699

5 files changed

Lines changed: 268 additions & 0 deletions

File tree

packages/core/schematics/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ bundle_entrypoints = [
133133
"can-match-snapshot-required",
134134
"packages/core/schematics/migrations/can-match-snapshot-required/index.js",
135135
],
136+
[
137+
"strict-safe-navigation-narrow",
138+
"packages/core/schematics/migrations/strict-safe-navigation-narrow/index.js",
139+
],
136140
]
137141

138142
rollup.rollup(
@@ -147,6 +151,7 @@ rollup.rollup(
147151
"//packages/core/schematics/migrations/can-match-snapshot-required",
148152
"//packages/core/schematics/migrations/change-detection-eager",
149153
"//packages/core/schematics/migrations/http-xhr-backend",
154+
"//packages/core/schematics/migrations/strict-safe-navigation-narrow",
150155
"//packages/core/schematics/migrations/strict-template",
151156
"//packages/core/schematics/ng-generate/cleanup-unused-imports",
152157
"//packages/core/schematics/ng-generate/common-to-standalone-migration",

packages/core/schematics/migrations.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
"version": "22.0.0",
2020
"description": "Adds the required third argument to canMatch callsites.",
2121
"factory": "./bundles/can-match-snapshot-required.cjs#migrate"
22+
},
23+
"strict-safe-navigation-narrow": {
24+
"version": "22.0.0",
25+
"description": "Adds 'strictTemplates: true' in tsconfig.json.",
26+
"factory": "./bundles/strict-safe-navigation-narrow.cjs#migrate"
2227
}
2328
}
2429
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
load("//tools:defaults.bzl", "jasmine_test", "ts_project")
2+
3+
package(
4+
default_visibility = [
5+
"//packages/core/schematics:__pkg__",
6+
"//packages/core/schematics/test:__pkg__",
7+
],
8+
)
9+
10+
ts_project(
11+
name = "strict-safe-navigation-narrow",
12+
srcs = glob(
13+
["**/*.ts"],
14+
exclude = ["*.spec.ts"],
15+
),
16+
deps = [
17+
"//:node_modules/@angular-devkit/schematics",
18+
"//packages/core/schematics/utils",
19+
],
20+
)
21+
22+
ts_project(
23+
name = "test_lib",
24+
testonly = True,
25+
srcs = glob(["*.spec.ts"]),
26+
deps = [
27+
":strict-safe-navigation-narrow",
28+
"//:node_modules/@angular-devkit/core",
29+
"//:node_modules/@angular-devkit/schematics",
30+
"//packages/core/schematics/utils",
31+
],
32+
)
33+
34+
jasmine_test(
35+
name = "test",
36+
data = [":test_lib"],
37+
)
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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 {Rule} from '@angular-devkit/schematics';
10+
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
11+
12+
const NULLISH_COALESCING_NOT_NULLABLE = 'nullishCoalescingNotNullable';
13+
const OPTIONAL_CHAIN_NOT_NULLABLE = 'optionalChainNotNullable';
14+
15+
/**
16+
* Migration that adds `strictTemplates: false` to `tsconfig.json` files.
17+
*/
18+
export function migrate(): Rule {
19+
return async (tree) => {
20+
const {buildPaths, testPaths} = await getProjectTsConfigPaths(tree);
21+
const allPaths = [...new Set([...buildPaths, ...testPaths])];
22+
23+
for (const path of allPaths) {
24+
const content = tree.read(path);
25+
if (!content) continue;
26+
27+
const contentStr = content.toString('utf-8');
28+
29+
// Check if it's already there to avoid parsing if not needed.
30+
if (
31+
contentStr.includes(NULLISH_COALESCING_NOT_NULLABLE) &&
32+
contentStr.includes(OPTIONAL_CHAIN_NOT_NULLABLE)
33+
) {
34+
continue;
35+
}
36+
37+
try {
38+
// Use a simple JSON.parse for now. In a real world scenario we might want to use
39+
// a parser that supports comments (JSONC), but for this migration it's likely
40+
// that tsconfig files are standard enough or that overwriting them is acceptable
41+
// in the context of an ng update.
42+
const json = JSON.parse(contentStr);
43+
44+
if (!json.compilerOptions || Object.keys(json.compilerOptions).length === 0) {
45+
continue;
46+
}
47+
48+
if (!json.angularCompilerOptions) {
49+
json.angularCompilerOptions = {};
50+
}
51+
52+
for (const key of [NULLISH_COALESCING_NOT_NULLABLE, OPTIONAL_CHAIN_NOT_NULLABLE]) {
53+
if (json.angularCompilerOptions[key] === undefined) {
54+
json.angularCompilerOptions[key] = false;
55+
}
56+
}
57+
58+
tree.overwrite(path, JSON.stringify(json, null, 2));
59+
} catch (e) {
60+
// If parsing fails, skip the file to avoid corrupting it.
61+
continue;
62+
}
63+
}
64+
};
65+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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 {HostTree} from '@angular-devkit/schematics';
10+
import {UnitTestTree} from '@angular-devkit/schematics/testing/index.js';
11+
import {migrate} from './index';
12+
13+
describe('strict-safe-navigation-narrow migration', () => {
14+
let tree: UnitTestTree;
15+
16+
beforeEach(() => {
17+
tree = new UnitTestTree(new HostTree());
18+
tree.create(
19+
'/angular.json',
20+
JSON.stringify({
21+
version: 1,
22+
projects: {
23+
t: {
24+
root: '',
25+
architect: {
26+
build: {
27+
options: {
28+
tsConfig: './tsconfig.json',
29+
},
30+
},
31+
},
32+
},
33+
},
34+
}),
35+
);
36+
});
37+
38+
it('should not add options if compilerOptions is empty', async () => {
39+
tree.create(
40+
'/tsconfig.json',
41+
JSON.stringify({
42+
compilerOptions: {},
43+
}),
44+
);
45+
46+
await migrate()(tree, {} as any);
47+
48+
const tsconfig = JSON.parse(tree.readContent('/tsconfig.json'));
49+
expect(tsconfig.angularCompilerOptions).toBeUndefined();
50+
});
51+
52+
it('should not add options if compilerOptions is missing', async () => {
53+
tree.create('/tsconfig.json', JSON.stringify({}));
54+
55+
await migrate()(tree, {} as any);
56+
57+
const tsconfig = JSON.parse(tree.readContent('/tsconfig.json'));
58+
expect(tsconfig.angularCompilerOptions).toBeUndefined();
59+
});
60+
61+
it('should add options to false if compilerOptions is not empty', async () => {
62+
tree.create(
63+
'/tsconfig.json',
64+
JSON.stringify({
65+
compilerOptions: {
66+
target: 'es2020',
67+
},
68+
}),
69+
);
70+
71+
await migrate()(tree, {} as any);
72+
73+
const tsconfig = JSON.parse(tree.readContent('/tsconfig.json'));
74+
expect(tsconfig.angularCompilerOptions.nullishCoalescingNotNullable).toBe(false);
75+
expect(tsconfig.angularCompilerOptions.optionalChainNotNullable).toBe(false);
76+
});
77+
78+
it('should add options to false if angularCompilerOptions is empty but compilerOptions is not', async () => {
79+
tree.create(
80+
'/tsconfig.json',
81+
JSON.stringify({
82+
compilerOptions: {
83+
target: 'es2020',
84+
},
85+
angularCompilerOptions: {},
86+
}),
87+
);
88+
89+
await migrate()(tree, {} as any);
90+
91+
const tsconfig = JSON.parse(tree.readContent('/tsconfig.json'));
92+
expect(tsconfig.angularCompilerOptions.nullishCoalescingNotNullable).toBe(false);
93+
expect(tsconfig.angularCompilerOptions.optionalChainNotNullable).toBe(false);
94+
});
95+
96+
it('should not change options if already present', async () => {
97+
tree.create(
98+
'/tsconfig.json',
99+
JSON.stringify({
100+
compilerOptions: {
101+
target: 'es2020',
102+
},
103+
angularCompilerOptions: {
104+
nullishCoalescingNotNullable: true,
105+
optionalChainNotNullable: true,
106+
},
107+
}),
108+
);
109+
110+
await migrate()(tree, {} as any);
111+
112+
const tsconfig = JSON.parse(tree.readContent('/tsconfig.json'));
113+
expect(tsconfig.angularCompilerOptions.nullishCoalescingNotNullable).toBe(true);
114+
expect(tsconfig.angularCompilerOptions.optionalChainNotNullable).toBe(true);
115+
});
116+
117+
it('should not change nullishCoalescingNotNullable option but add optionalChainNotNullable because is not present', async () => {
118+
tree.create(
119+
'/tsconfig.json',
120+
JSON.stringify({
121+
compilerOptions: {
122+
target: 'es2020',
123+
},
124+
angularCompilerOptions: {
125+
nullishCoalescingNotNullable: true,
126+
},
127+
}),
128+
);
129+
130+
await migrate()(tree, {} as any);
131+
132+
const tsconfig = JSON.parse(tree.readContent('/tsconfig.json'));
133+
expect(tsconfig.angularCompilerOptions.nullishCoalescingNotNullable).toBe(true);
134+
expect(tsconfig.angularCompilerOptions.optionalChainNotNullable).toBe(false);
135+
});
136+
137+
it('should not change optionalChainNotNullable option but add nullishCoalescingNotNullable because is not present', async () => {
138+
tree.create(
139+
'/tsconfig.json',
140+
JSON.stringify({
141+
compilerOptions: {
142+
target: 'es2020',
143+
},
144+
angularCompilerOptions: {
145+
optionalChainNotNullable: true,
146+
},
147+
}),
148+
);
149+
150+
await migrate()(tree, {} as any);
151+
152+
const tsconfig = JSON.parse(tree.readContent('/tsconfig.json'));
153+
expect(tsconfig.angularCompilerOptions.nullishCoalescingNotNullable).toBe(false);
154+
expect(tsconfig.angularCompilerOptions.optionalChainNotNullable).toBe(true);
155+
});
156+
});

0 commit comments

Comments
 (0)