feat(migrations): Disabling nullishCoalescingNotNullable & optionalChainNotNullable on ng update

Related to angular#67959 disabling two diagnostics errors by `ng update`:

- nullishCoalescingNotNullable
- optionalChainNotNullable
This commit is contained in:
aparziale 2026-04-11 23:18:42 +02:00 committed by Kirill Cherkashin
parent 0454d4ced7
commit 6a435658e2
5 changed files with 231 additions and 0 deletions

View file

@ -137,6 +137,10 @@ bundle_entrypoints = [
"incremental-hydration",
"packages/core/schematics/migrations/incremental-hydration/index.js",
],
[
"strict-safe-navigation-narrow",
"packages/core/schematics/migrations/strict-safe-navigation-narrow/index.js",
],
]
rollup.rollup(
@ -152,6 +156,7 @@ rollup.rollup(
"//packages/core/schematics/migrations/change-detection-eager",
"//packages/core/schematics/migrations/http-xhr-backend",
"//packages/core/schematics/migrations/incremental-hydration",
"//packages/core/schematics/migrations/strict-safe-navigation-narrow",
"//packages/core/schematics/migrations/strict-templates-default",
"//packages/core/schematics/ng-generate/cleanup-unused-imports",
"//packages/core/schematics/ng-generate/common-to-standalone-migration",

View file

@ -24,6 +24,11 @@
"version": "22.0.0",
"description": "Adds withNoIncrementalHydration() opt out to provideClientHydration() when incremental hydration is not enabled to retain pre-v22 behavior-.",
"factory": "./bundles/incremental-hydration.cjs#migrate"
},
"strict-safe-navigation-narrow": {
"version": "22.0.0",
"description": "Disables the 'nullishCoalescingNotNullable & optionalChainNotNullable extended diagnostics.",
"factory": "./bundles/strict-safe-navigation-narrow.cjs#migrate"
}
}
}

View file

@ -0,0 +1,40 @@
load("//tools:defaults.bzl", "jasmine_test", "ts_project")
package(
default_visibility = [
"//packages/core/schematics:__pkg__",
"//packages/core/schematics/test:__pkg__",
],
)
ts_project(
name = "strict-safe-navigation-narrow",
srcs = glob(
["**/*.ts"],
exclude = ["*.spec.ts"],
),
deps = [
"//:node_modules/@angular-devkit/core",
"//:node_modules/@angular-devkit/schematics",
"//:node_modules/@schematics/angular",
"//packages/core/schematics/utils",
],
)
ts_project(
name = "test_lib",
testonly = True,
srcs = glob(["*.spec.ts"]),
deps = [
":strict-safe-navigation-narrow",
"//:node_modules/@angular-devkit/core",
"//:node_modules/@angular-devkit/schematics",
"//:node_modules/@schematics/angular",
"//packages/core/schematics/utils",
],
)
jasmine_test(
name = "test",
data = [":test_lib"],
)

View file

@ -0,0 +1,40 @@
/**
* @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.dev/license
*/
import {Rule, Tree} from '@angular-devkit/schematics';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {JSONFile} from '@schematics/angular/utility/json-file';
export function migrate(): Rule {
return async (tree: Tree) => {
const {buildPaths, testPaths} = await getProjectTsConfigPaths(tree);
const allPaths = [...new Set([...buildPaths, ...testPaths])];
for (const tsconfigPath of allPaths) {
const json = new JSONFile(tree, tsconfigPath);
const compilerOptions = json.get(['compilerOptions']);
if (
!compilerOptions ||
typeof compilerOptions !== 'object' ||
Object.keys(compilerOptions).length === 0
) {
continue;
}
json.modify(
['angularCompilerOptions', 'extendedDiagnostics', 'checks', 'nullishCoalescingNotNullable'],
'suppress',
);
json.modify(
['angularCompilerOptions', 'extendedDiagnostics', 'checks', 'optionalChainNotNullable'],
'suppress',
);
}
};
}

View file

@ -0,0 +1,141 @@
/**
* @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.dev/license
*/
import {HostTree} from '@angular-devkit/schematics';
import {UnitTestTree} from '@angular-devkit/schematics/testing/index.js';
import {migrate} from './index';
function parseConfig(tree: UnitTestTree, path: string) {
return JSON.parse(tree.readContent(path).replace(/\/\/.*$/gm, ''));
}
describe('strict-safe-navigation-narrow migration', () => {
let tree: UnitTestTree;
beforeEach(() => {
tree = new UnitTestTree(new HostTree());
tree.create(
'/angular.json',
JSON.stringify({
version: 1,
projects: {
t: {
root: '',
architect: {
build: {
options: {
tsConfig: './tsconfig.json',
},
},
},
},
},
}),
);
});
it('should not add options if compilerOptions is empty', async () => {
tree.create(
'/tsconfig.json',
JSON.stringify({
compilerOptions: {},
}),
);
const runMigration = migrate();
await runMigration(tree, {} as any);
const tsconfig = parseConfig(tree, '/tsconfig.json');
expect(tsconfig.angularCompilerOptions).toBeUndefined();
});
it('should not add options if compilerOptions is missing', async () => {
tree.create('/tsconfig.json', JSON.stringify({}));
const runMigration = migrate();
await runMigration(tree, {} as any);
const tsconfig = parseConfig(tree, '/tsconfig.json');
expect(tsconfig.angularCompilerOptions).toBeUndefined();
});
it('should add suppress options if compilerOptions is not empty', async () => {
tree.create(
'/tsconfig.json',
JSON.stringify({
compilerOptions: {
target: 'es2020',
},
}),
);
const runMigration = migrate();
await runMigration(tree, {} as any);
const tsconfig = parseConfig(tree, '/tsconfig.json');
expect(
tsconfig.angularCompilerOptions.extendedDiagnostics.checks.nullishCoalescingNotNullable,
).toBe('suppress');
expect(
tsconfig.angularCompilerOptions.extendedDiagnostics.checks.optionalChainNotNullable,
).toBe('suppress');
});
it('should preserve existing checks', async () => {
tree.create(
'/tsconfig.json',
JSON.stringify({
compilerOptions: {
target: 'es2020',
},
angularCompilerOptions: {
extendedDiagnostics: {
checks: {
somethingElse: 'warning',
},
},
},
}),
);
const runMigration = migrate();
await runMigration(tree, {} as any);
const tsconfig = parseConfig(tree, '/tsconfig.json');
expect(tsconfig.angularCompilerOptions.extendedDiagnostics.checks.somethingElse).toBe(
'warning',
);
expect(
tsconfig.angularCompilerOptions.extendedDiagnostics.checks.nullishCoalescingNotNullable,
).toBe('suppress');
expect(
tsconfig.angularCompilerOptions.extendedDiagnostics.checks.optionalChainNotNullable,
).toBe('suppress');
});
it('should handle tsconfig with comments', async () => {
tree.create(
'/tsconfig.json',
`{
// This is a comment
"compilerOptions": {
"target": "es2020"
}
}`,
);
const runMigration = migrate();
await runMigration(tree, {} as any);
const tsconfig = parseConfig(tree, '/tsconfig.json');
expect(tsconfig.compilerOptions.target).toBe('es2020');
expect(
tsconfig.angularCompilerOptions.extendedDiagnostics.checks.nullishCoalescingNotNullable,
).toBe('suppress');
});
});