Skip to content

Commit df77fde

Browse files
authored
fix(angular): handle removed angular-eslint rules in root eslint config files and update package (#29262)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> The migration to remove Angular ESLint rules that were removed in v19 does not handle these rules in the root eslint config files. ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> The migration to remove Angular ESLint rules that were removed in v19 should handle these rules in the root eslint config files. ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
1 parent 7e38824 commit df77fde

File tree

7 files changed

+526
-293
lines changed

7 files changed

+526
-293
lines changed

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@
3131
"@angular-devkit/build-angular": "~19.0.0",
3232
"@angular-devkit/core": "~19.0.0",
3333
"@angular-devkit/schematics": "~19.0.0",
34-
"@angular-eslint/eslint-plugin": "19.0.0",
35-
"@angular-eslint/eslint-plugin-template": "19.0.0",
36-
"@angular-eslint/template-parser": "19.0.0",
34+
"@angular-eslint/eslint-plugin": "19.0.2",
35+
"@angular-eslint/eslint-plugin-template": "19.0.2",
36+
"@angular-eslint/template-parser": "19.0.2",
3737
"@angular/cli": "~19.0.0",
3838
"@angular/common": "~19.0.0",
3939
"@angular/compiler": "~19.0.0",
@@ -154,7 +154,7 @@
154154
"@zkochan/js-yaml": "0.0.7",
155155
"ai": "^2.2.10",
156156
"ajv": "^8.12.0",
157-
"angular-eslint": "19.0.0",
157+
"angular-eslint": "19.0.2",
158158
"autoprefixer": "10.4.13",
159159
"babel-jest": "29.7.0",
160160
"babel-loader": "^9.1.2",

packages/angular/migrations.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,6 +1368,35 @@
13681368
"alwaysAddToPackageJson": false
13691369
}
13701370
}
1371+
},
1372+
"20.2.2-angular-eslint": {
1373+
"version": "20.2.2-beta.0",
1374+
"requires": {
1375+
"eslint": "^8.57.0 || ^9.0.0",
1376+
"@angular/core": ">= 19.0.0 < 20.0.0"
1377+
},
1378+
"packages": {
1379+
"angular-eslint": {
1380+
"version": "^19.0.2",
1381+
"alwaysAddToPackageJson": false
1382+
},
1383+
"@angular-eslint/eslint-plugin": {
1384+
"version": "^19.0.2",
1385+
"alwaysAddToPackageJson": false
1386+
},
1387+
"@angular-eslint/eslint-plugin-template": {
1388+
"version": "^19.0.2",
1389+
"alwaysAddToPackageJson": false
1390+
},
1391+
"@angular-eslint/template-parser": {
1392+
"version": "^19.0.2",
1393+
"alwaysAddToPackageJson": false
1394+
},
1395+
"@angular-eslint/utils": {
1396+
"version": "^19.0.2",
1397+
"alwaysAddToPackageJson": false
1398+
}
1399+
}
13711400
}
13721401
}
13731402
}

packages/angular/src/migrations/update-20-2-0/remove-angular-eslint-rules.spec.ts

Lines changed: 168 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
type Tree,
77
} from '@nx/devkit';
88
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
9-
import migration from './remove-angular-eslint-rules';
9+
import migration, { rulesToRemove } from './remove-angular-eslint-rules';
1010

1111
let projectGraph: ProjectGraph;
1212
jest.mock('@nx/devkit', () => ({
@@ -46,11 +46,7 @@ describe('remove-angular-eslint-rules', () => {
4646
});
4747

4848
describe('.eslintrc.json', () => {
49-
it.each([
50-
['@angular-eslint/no-host-metadata-property'],
51-
['@angular-eslint/sort-ngmodule-metadata-arrays'],
52-
['@angular-eslint/prefer-standalone-component'],
53-
])('should remove %s rule', async (rule) => {
49+
it.each(rulesToRemove)('should remove %s rule', async (rule) => {
5450
writeJson(tree, 'apps/app1/.eslintrc.json', {
5551
overrides: [
5652
{
@@ -94,14 +90,88 @@ describe('remove-angular-eslint-rules', () => {
9490
"
9591
`);
9692
});
93+
94+
it('should handle rules set in the root config', async () => {
95+
writeJson(tree, '.eslintrc.json', {
96+
overrides: [
97+
{
98+
files: ['*.ts'],
99+
rules: {
100+
'@angular-eslint/no-host-metadata-property': ['error'],
101+
'@angular-eslint/sort-ngmodule-metadata-arrays': ['error'],
102+
'@angular-eslint/prefer-standalone-component': ['error'],
103+
},
104+
},
105+
],
106+
});
107+
writeJson(tree, 'apps/app1/.eslintrc.json', {
108+
extends: '../../.eslintrc.json',
109+
});
110+
111+
await migration(tree);
112+
113+
expect(tree.read('.eslintrc.json', 'utf8')).toMatchInlineSnapshot(`
114+
"{
115+
"overrides": [
116+
{
117+
"files": ["*.ts"],
118+
"rules": {}
119+
}
120+
]
121+
}
122+
"
123+
`);
124+
expect(tree.read('apps/app1/.eslintrc.json', 'utf8'))
125+
.toMatchInlineSnapshot(`
126+
"{
127+
"extends": "../../.eslintrc.json"
128+
}
129+
"
130+
`);
131+
});
132+
133+
it('should handle rules set in the root base config', async () => {
134+
writeJson(tree, '.eslintrc.base.json', {
135+
overrides: [
136+
{
137+
files: ['*.ts'],
138+
rules: {
139+
'@angular-eslint/no-host-metadata-property': ['error'],
140+
'@angular-eslint/sort-ngmodule-metadata-arrays': ['error'],
141+
'@angular-eslint/prefer-standalone-component': ['error'],
142+
},
143+
},
144+
],
145+
});
146+
writeJson(tree, 'apps/app1/.eslintrc.json', {
147+
extends: '../../.eslintrc.base.json',
148+
});
149+
150+
await migration(tree);
151+
152+
expect(tree.read('.eslintrc.base.json', 'utf8')).toMatchInlineSnapshot(`
153+
"{
154+
"overrides": [
155+
{
156+
"files": ["*.ts"],
157+
"rules": {}
158+
}
159+
]
160+
}
161+
"
162+
`);
163+
expect(tree.read('apps/app1/.eslintrc.json', 'utf8'))
164+
.toMatchInlineSnapshot(`
165+
"{
166+
"extends": "../../.eslintrc.base.json"
167+
}
168+
"
169+
`);
170+
});
97171
});
98172

99173
describe('flat config', () => {
100-
it.each([
101-
['@angular-eslint/no-host-metadata-property'],
102-
['@angular-eslint/sort-ngmodule-metadata-arrays'],
103-
['@angular-eslint/prefer-standalone-component'],
104-
])('should remove %s rule', async (rule) => {
174+
it.each(rulesToRemove)('should remove %s rule', async (rule) => {
105175
tree.write('eslint.config.js', 'module.exports = [];');
106176
tree.write(
107177
'apps/app1/eslint.config.js',
@@ -151,5 +221,92 @@ describe('remove-angular-eslint-rules', () => {
151221
"
152222
`);
153223
});
224+
225+
it('should handle rules set in the root config', async () => {
226+
tree.write(
227+
'eslint.config.js',
228+
`module.exports = [
229+
{
230+
files: ['*.ts'],
231+
rules: {
232+
'@angular-eslint/no-host-metadata-property': ['error'],
233+
'@angular-eslint/sort-ngmodule-metadata-arrays': ['error'],
234+
'@angular-eslint/prefer-standalone-component': ['error'],
235+
},
236+
},
237+
];
238+
`
239+
);
240+
tree.write(
241+
'apps/app1/eslint.config.js',
242+
`const baseConfig = require('../../eslint.config.js');
243+
244+
module.exports = [...baseConfig];
245+
`
246+
);
247+
248+
await migration(tree);
249+
250+
expect(tree.read('eslint.config.js', 'utf8')).toMatchInlineSnapshot(`
251+
"module.exports = [
252+
{
253+
files: ['**/*.ts'],
254+
rules: {},
255+
},
256+
];
257+
"
258+
`);
259+
expect(tree.read('apps/app1/eslint.config.js', 'utf8'))
260+
.toMatchInlineSnapshot(`
261+
"const baseConfig = require('../../eslint.config.js');
262+
263+
module.exports = [...baseConfig];
264+
"
265+
`);
266+
});
267+
268+
it('should handle rules set in the root base config', async () => {
269+
tree.write(
270+
'eslint.base.config.js',
271+
`module.exports = [
272+
{
273+
files: ['*.ts'],
274+
rules: {
275+
'@angular-eslint/no-host-metadata-property': ['error'],
276+
'@angular-eslint/sort-ngmodule-metadata-arrays': ['error'],
277+
'@angular-eslint/prefer-standalone-component': ['error'],
278+
},
279+
},
280+
];
281+
`
282+
);
283+
tree.write('eslint.config.js', 'module.exports = [];');
284+
tree.write(
285+
'apps/app1/eslint.config.js',
286+
`const baseConfig = require('../../eslint.base.config.js');
287+
288+
module.exports = [...baseConfig];
289+
`
290+
);
291+
292+
await migration(tree);
293+
294+
expect(tree.read('eslint.base.config.js', 'utf8')).toMatchInlineSnapshot(`
295+
"module.exports = [
296+
{
297+
files: ['**/*.ts'],
298+
rules: {},
299+
},
300+
];
301+
"
302+
`);
303+
expect(tree.read('apps/app1/eslint.config.js', 'utf8'))
304+
.toMatchInlineSnapshot(`
305+
"const baseConfig = require('../../eslint.base.config.js');
306+
307+
module.exports = [...baseConfig];
308+
"
309+
`);
310+
});
154311
});
155312
});
Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
import { formatFiles, type Tree } from '@nx/devkit';
22
import {
3+
findEslintFile,
34
isEslintConfigSupported,
45
lintConfigHasOverride,
56
updateOverrideInLintConfig,
67
} from '@nx/eslint/src/generators/utils/eslint-file';
78
import { getProjectsFilteredByDependencies } from '../utils/projects';
89

10+
export const rulesToRemove = [
11+
'@angular-eslint/no-host-metadata-property',
12+
'@angular-eslint/sort-ngmodule-metadata-arrays',
13+
'@angular-eslint/prefer-standalone-component',
14+
];
15+
916
export default async function (tree: Tree) {
1017
const projects = await getProjectsFilteredByDependencies(tree, [
1118
'npm:@angular/core',
1219
]);
1320

21+
let hasRootProject = false;
1422
for (const {
1523
project: { root },
1624
} of projects) {
@@ -19,25 +27,51 @@ export default async function (tree: Tree) {
1927
continue;
2028
}
2129

22-
removeRule(tree, root, '@angular-eslint/no-host-metadata-property');
23-
removeRule(tree, root, '@angular-eslint/sort-ngmodule-metadata-arrays');
24-
removeRule(tree, root, '@angular-eslint/prefer-standalone-component');
30+
if (root === '.') {
31+
hasRootProject = true;
32+
}
33+
34+
removeRules(tree, root);
35+
}
36+
37+
/**
38+
* We need to handle both a root config file (e.g. eslint.config.js) and a
39+
* potential base config file (e.g. eslint.base.config.js). We can't use
40+
* `findEslintFile` because it would return only one or the other depending
41+
* on whether a root is provided and the existence of the files. So, we
42+
* handle each of them separately.
43+
*/
44+
45+
// check root config, provide a root so it doesn't try to lookup a base config
46+
if (!hasRootProject) {
47+
// if there is no root project the root eslint config has not been processed
48+
if (isEslintConfigSupported(tree, '')) {
49+
removeRules(tree, '');
50+
}
51+
}
52+
53+
// handle root base config, not providing a root will prioritize a base config
54+
const baseEslintConfig = findEslintFile(tree);
55+
if (baseEslintConfig && baseEslintConfig.includes('.base.')) {
56+
removeRules(tree, baseEslintConfig);
2557
}
2658

2759
await formatFiles(tree);
2860
}
2961

30-
function removeRule(tree: Tree, root: string, rule: string) {
31-
const lookup: Parameters<typeof lintConfigHasOverride>[2] = (o) =>
32-
!!o.rules?.[rule];
33-
if (!lintConfigHasOverride(tree, root, lookup, true)) {
34-
// it's not using the rule, skip
35-
return;
36-
}
62+
function removeRules(tree: Tree, root: string): void {
63+
for (const rule of rulesToRemove) {
64+
const lookup: Parameters<typeof lintConfigHasOverride>[2] = (o) =>
65+
!!o.rules?.[rule];
66+
if (!lintConfigHasOverride(tree, root, lookup)) {
67+
// it's not using the rule, skip
68+
continue;
69+
}
3770

38-
// there is an override containing the rule, remove the rule
39-
updateOverrideInLintConfig(tree, root, lookup, (o) => {
40-
delete o.rules[rule];
41-
return o;
42-
});
71+
// there is an override containing the rule, remove the rule
72+
updateOverrideInLintConfig(tree, root, lookup, (o) => {
73+
delete o.rules[rule];
74+
return o;
75+
});
76+
}
4377
}

packages/angular/src/utils/versions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const browserSyncVersion = '^3.0.0';
1717
export const moduleFederationNodeVersion = '~2.6.11';
1818
export const moduleFederationEnhancedVersion = '0.7.6';
1919

20-
export const angularEslintVersion = '^19.0.0';
20+
export const angularEslintVersion = '^19.0.2';
2121
export const typescriptEslintVersion = '^7.16.0';
2222
export const tailwindVersion = '^3.0.2';
2323
export const postcssVersion = '^8.4.5';

0 commit comments

Comments
 (0)