Skip to content

Commit 2fa3ce2

Browse files
authored
feat(angular): add migration to remove angular eslint rules removed in v19 (#29214)
Add migration to remove Angular ESLint rules that were removed in v19: - `@angular-eslint/no-host-metadata-property` - `@angular-eslint/sort-ngmodule-metadata-arrays` - `@angular-eslint/prefer-standalone-component` See Angular ESLint v19 changelog for reference: https://github.com/angular-eslint/angular-eslint/blob/main/CHANGELOG.md#1900-2024-11-29 <!-- 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 --> ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
1 parent 15060e3 commit 2fa3ce2

File tree

4 files changed

+210
-1
lines changed

4 files changed

+210
-1
lines changed

packages/angular/migrations.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,15 @@
314314
},
315315
"description": "Disable the Angular ESLint prefer-standalone rule if not set.",
316316
"factory": "./src/migrations/update-20-2-0/disable-angular-eslint-prefer-standalone"
317+
},
318+
"remove-angular-eslint-rules": {
319+
"cli": "nx",
320+
"version": "20.2.0-beta.8",
321+
"requires": {
322+
"@angular/core": ">=19.0.0"
323+
},
324+
"description": "Remove Angular ESLint rules that were removed in v19.0.0.",
325+
"factory": "./src/migrations/update-20-2-0/remove-angular-eslint-rules"
317326
}
318327
},
319328
"packageJsonUpdates": {
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import {
2+
addProjectConfiguration,
3+
writeJson,
4+
type ProjectConfiguration,
5+
type ProjectGraph,
6+
type Tree,
7+
} from '@nx/devkit';
8+
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
9+
import migration from './remove-angular-eslint-rules';
10+
11+
let projectGraph: ProjectGraph;
12+
jest.mock('@nx/devkit', () => ({
13+
...jest.requireActual('@nx/devkit'),
14+
createProjectGraphAsync: () => Promise.resolve(projectGraph),
15+
}));
16+
17+
describe('remove-angular-eslint-rules', () => {
18+
let tree: Tree;
19+
20+
beforeEach(() => {
21+
tree = createTreeWithEmptyWorkspace();
22+
23+
const projectConfig: ProjectConfiguration = {
24+
name: 'app1',
25+
root: 'apps/app1',
26+
};
27+
projectGraph = {
28+
dependencies: {
29+
app1: [
30+
{
31+
source: 'app1',
32+
target: 'npm:@angular/core',
33+
type: 'static',
34+
},
35+
],
36+
},
37+
nodes: {
38+
app1: {
39+
data: projectConfig,
40+
name: 'app1',
41+
type: 'app',
42+
},
43+
},
44+
};
45+
addProjectConfiguration(tree, projectConfig.name, projectConfig);
46+
});
47+
48+
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) => {
54+
writeJson(tree, 'apps/app1/.eslintrc.json', {
55+
overrides: [
56+
{
57+
files: ['*.ts'],
58+
rules: { [rule]: ['error'] },
59+
},
60+
],
61+
});
62+
63+
await migration(tree);
64+
65+
expect(tree.read('apps/app1/.eslintrc.json', 'utf8')).not.toContain(rule);
66+
});
67+
68+
it('should remove multiple rules', async () => {
69+
writeJson(tree, 'apps/app1/.eslintrc.json', {
70+
overrides: [
71+
{
72+
files: ['*.ts'],
73+
rules: {
74+
'@angular-eslint/no-host-metadata-property': ['error'],
75+
'@angular-eslint/sort-ngmodule-metadata-arrays': ['error'],
76+
'@angular-eslint/prefer-standalone-component': ['error'],
77+
},
78+
},
79+
],
80+
});
81+
82+
await migration(tree);
83+
84+
expect(tree.read('apps/app1/.eslintrc.json', 'utf8'))
85+
.toMatchInlineSnapshot(`
86+
"{
87+
"overrides": [
88+
{
89+
"files": ["*.ts"],
90+
"rules": {}
91+
}
92+
]
93+
}
94+
"
95+
`);
96+
});
97+
});
98+
99+
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) => {
105+
tree.write('eslint.config.js', 'module.exports = [];');
106+
tree.write(
107+
'apps/app1/eslint.config.js',
108+
`module.exports = [
109+
{
110+
files: ['*.ts'],
111+
rules: { '${rule}': ['error'] },
112+
},
113+
];
114+
`
115+
);
116+
117+
await migration(tree);
118+
119+
expect(tree.read('apps/app1/eslint.config.js', 'utf8')).not.toContain(
120+
rule
121+
);
122+
});
123+
124+
it('should remove multiple rules', async () => {
125+
tree.write('eslint.config.js', 'module.exports = [];');
126+
tree.write(
127+
'apps/app1/eslint.config.js',
128+
`module.exports = [
129+
{
130+
files: ['*.ts'],
131+
rules: {
132+
'@angular-eslint/no-host-metadata-property': ['error'],
133+
'@angular-eslint/sort-ngmodule-metadata-arrays': ['error'],
134+
'@angular-eslint/prefer-standalone-component': ['error'],
135+
},
136+
},
137+
];
138+
`
139+
);
140+
141+
await migration(tree);
142+
143+
expect(tree.read('apps/app1/eslint.config.js', 'utf8'))
144+
.toMatchInlineSnapshot(`
145+
"module.exports = [
146+
{
147+
files: ['**/*.ts'],
148+
rules: {},
149+
},
150+
];
151+
"
152+
`);
153+
});
154+
});
155+
});
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { formatFiles, type Tree } from '@nx/devkit';
2+
import {
3+
isEslintConfigSupported,
4+
lintConfigHasOverride,
5+
updateOverrideInLintConfig,
6+
} from '@nx/eslint/src/generators/utils/eslint-file';
7+
import { getProjectsFilteredByDependencies } from '../utils/projects';
8+
9+
export default async function (tree: Tree) {
10+
const projects = await getProjectsFilteredByDependencies(tree, [
11+
'npm:@angular/core',
12+
]);
13+
14+
for (const {
15+
project: { root },
16+
} of projects) {
17+
if (!isEslintConfigSupported(tree, root)) {
18+
// ESLint config is not supported, skip
19+
continue;
20+
}
21+
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');
25+
}
26+
27+
await formatFiles(tree);
28+
}
29+
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+
}
37+
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+
});
43+
}

packages/eslint/src/generators/utils/flat-config/ast-utils.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ function findAllBlocks(source: ts.SourceFile): ts.NodeArray<ts.Node> {
6363
function isOverride(node: ts.Node): boolean {
6464
return (
6565
(ts.isObjectLiteralExpression(node) &&
66-
node.properties.some((p) => p.name.getText() === 'files')) ||
66+
node.properties.some(
67+
(p) => p.name.getText() === 'files' || p.name.getText() === '"files"'
68+
)) ||
6769
// detect ...compat.config(...).map(...)
6870
(ts.isSpreadElement(node) &&
6971
ts.isCallExpression(node.expression) &&

0 commit comments

Comments
 (0)