fix(linter): generate flat config for new projects correctly (#26328)
- Change generated import for `FlatCompat`:
```diff
- const FlatCompat = require('@eslint/eslintrc');
+ const { FlatCompat } = require('@eslint/eslintrc');
```
- Fix replacing overrides to be reflected in the end result (the updated
content with the replacements was not being assigned)
- Add extended plugins/configs to the start (matches behavior of the old
config)
<!-- 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` -->
## 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 #22350
Fixes #26151
This commit is contained in:
parent
0594debfef
commit
e95204b037
@ -1,5 +1,6 @@
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import {
|
import {
|
||||||
|
checkFilesDoNotExist,
|
||||||
checkFilesExist,
|
checkFilesExist,
|
||||||
cleanupProject,
|
cleanupProject,
|
||||||
createFile,
|
createFile,
|
||||||
@ -521,6 +522,43 @@ describe('Linter', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('flat config', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
runCLI(`generate @nx/eslint:convert-to-flat-config`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate new projects using flat config', () => {
|
||||||
|
const reactLib = uniq('react-lib');
|
||||||
|
const jsLib = uniq('js-lib');
|
||||||
|
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/react:lib ${reactLib} --directory=${reactLib} --projectNameAndRootFormat=as-provided`
|
||||||
|
);
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/js:lib ${jsLib} --directory=${jsLib} --projectNameAndRootFormat=as-provided`
|
||||||
|
);
|
||||||
|
|
||||||
|
checkFilesExist(
|
||||||
|
`${reactLib}/eslint.config.js`,
|
||||||
|
`${jsLib}/eslint.config.js`
|
||||||
|
);
|
||||||
|
checkFilesDoNotExist(
|
||||||
|
`${reactLib}/.eslintrc.json`,
|
||||||
|
`${jsLib}/.eslintrc.json`
|
||||||
|
);
|
||||||
|
|
||||||
|
// validate that the new projects are linted successfully
|
||||||
|
let output = runCLI(`lint ${reactLib}`);
|
||||||
|
expect(output).toContain(
|
||||||
|
`Successfully ran target lint for project ${reactLib}`
|
||||||
|
);
|
||||||
|
output = runCLI(`lint ${jsLib}`);
|
||||||
|
expect(output).toContain(
|
||||||
|
`Successfully ran target lint for project ${jsLib}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Root projects migration', () => {
|
describe('Root projects migration', () => {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import {
|
|||||||
addExtendsToLintConfig,
|
addExtendsToLintConfig,
|
||||||
findEslintFile,
|
findEslintFile,
|
||||||
lintConfigHasOverride,
|
lintConfigHasOverride,
|
||||||
|
replaceOverridesInLintConfig,
|
||||||
} from './eslint-file';
|
} from './eslint-file';
|
||||||
|
|
||||||
import { Tree, readJson } from '@nx/devkit';
|
import { Tree, readJson } from '@nx/devkit';
|
||||||
@ -120,4 +121,124 @@ describe('@nx/eslint:lint-file', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('replaceOverridesInLintConfig', () => {
|
||||||
|
it('should replace overrides when using flat config', () => {
|
||||||
|
tree.write('eslint.config.js', 'module.exports = {};');
|
||||||
|
tree.write(
|
||||||
|
'apps/demo/eslint.config.js',
|
||||||
|
`const baseConfig = require("../../eslint.config.js");
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
...baseConfig,
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
"**/*.js",
|
||||||
|
"**/*.jsx"
|
||||||
|
],
|
||||||
|
rules: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx"
|
||||||
|
],
|
||||||
|
rules: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
"**/*.js",
|
||||||
|
"**/*.jsx"
|
||||||
|
],
|
||||||
|
rules: {}
|
||||||
|
}
|
||||||
|
];`
|
||||||
|
);
|
||||||
|
|
||||||
|
replaceOverridesInLintConfig(tree, 'apps/demo', [
|
||||||
|
{
|
||||||
|
files: ['*.ts'],
|
||||||
|
extends: [
|
||||||
|
'plugin:@nx/angular',
|
||||||
|
'plugin:@angular-eslint/template/process-inline-templates',
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
'@angular-eslint/directive-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'attribute',
|
||||||
|
prefix: 'myOrg',
|
||||||
|
style: 'camelCase',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@angular-eslint/component-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
prefix: 'my-org',
|
||||||
|
style: 'kebab-case',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['*.html'],
|
||||||
|
extends: ['plugin:@nx/angular-template'],
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(tree.read('apps/demo/eslint.config.js', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||||
|
const js = require("@eslint/js");
|
||||||
|
const baseConfig = require("../../eslint.config.js");
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
...baseConfig,
|
||||||
|
...compat.config({ extends: [
|
||||||
|
"plugin:@nx/angular",
|
||||||
|
"plugin:@angular-eslint/template/process-inline-templates"
|
||||||
|
] }).map(config => ({
|
||||||
|
...config,
|
||||||
|
files: ["**/*.ts"],
|
||||||
|
rules: {
|
||||||
|
...config.rules,
|
||||||
|
"@angular-eslint/directive-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
type: "attribute",
|
||||||
|
prefix: "myOrg",
|
||||||
|
style: "camelCase"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@angular-eslint/component-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
prefix: "my-org",
|
||||||
|
style: "kebab-case"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
...compat.config({ extends: ["plugin:@nx/angular-template"] }).map(config => ({
|
||||||
|
...config,
|
||||||
|
files: ["**/*.html"],
|
||||||
|
rules: {
|
||||||
|
...config.rules
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
];"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -291,7 +291,7 @@ export function replaceOverridesInLintConfig(
|
|||||||
content = removeOverridesFromLintConfig(content);
|
content = removeOverridesFromLintConfig(content);
|
||||||
overrides.forEach((override) => {
|
overrides.forEach((override) => {
|
||||||
const flatOverride = generateFlatOverride(override);
|
const flatOverride = generateFlatOverride(override);
|
||||||
addBlockToFlatConfigExport(content, flatOverride);
|
content = addBlockToFlatConfigExport(content, flatOverride);
|
||||||
});
|
});
|
||||||
|
|
||||||
tree.write(fileName, content);
|
tree.write(fileName, content);
|
||||||
@ -315,7 +315,12 @@ export function addExtendsToLintConfig(
|
|||||||
const pluginExtends = generatePluginExtendsElement(plugins);
|
const pluginExtends = generatePluginExtendsElement(plugins);
|
||||||
let content = tree.read(fileName, 'utf8');
|
let content = tree.read(fileName, 'utf8');
|
||||||
content = addCompatToFlatConfig(content);
|
content = addCompatToFlatConfig(content);
|
||||||
tree.write(fileName, addBlockToFlatConfigExport(content, pluginExtends));
|
tree.write(
|
||||||
|
fileName,
|
||||||
|
addBlockToFlatConfigExport(content, pluginExtends, {
|
||||||
|
insertAtTheEnd: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const fileName = joinPathFragments(root, '.eslintrc.json');
|
const fileName = joinPathFragments(root, '.eslintrc.json');
|
||||||
updateJson(tree, fileName, (json) => {
|
updateJson(tree, fileName, (json) => {
|
||||||
|
|||||||
@ -223,7 +223,7 @@ describe('ast-utils', () => {
|
|||||||
];`;
|
];`;
|
||||||
const result = addCompatToFlatConfig(content);
|
const result = addCompatToFlatConfig(content);
|
||||||
expect(result).toMatchInlineSnapshot(`
|
expect(result).toMatchInlineSnapshot(`
|
||||||
"const FlatCompat = require("@eslint/eslintrc");
|
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||||
const js = require("@eslint/js");
|
const js = require("@eslint/js");
|
||||||
const baseConfig = require("../../eslint.config.js");
|
const baseConfig = require("../../eslint.config.js");
|
||||||
|
|
||||||
@ -262,7 +262,7 @@ describe('ast-utils', () => {
|
|||||||
];`;
|
];`;
|
||||||
const result = addCompatToFlatConfig(content);
|
const result = addCompatToFlatConfig(content);
|
||||||
expect(result).toMatchInlineSnapshot(`
|
expect(result).toMatchInlineSnapshot(`
|
||||||
"const FlatCompat = require("@eslint/eslintrc");
|
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||||
const baseConfig = require("../../eslint.config.js");
|
const baseConfig = require("../../eslint.config.js");
|
||||||
const js = require("@eslint/js");
|
const js = require("@eslint/js");
|
||||||
|
|
||||||
|
|||||||
@ -602,7 +602,7 @@ export function addCompatToFlatConfig(content: string) {
|
|||||||
if (result.includes('const compat = new FlatCompat')) {
|
if (result.includes('const compat = new FlatCompat')) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
result = addImportToFlatConfig(result, 'FlatCompat', '@eslint/eslintrc');
|
result = addImportToFlatConfig(result, ['FlatCompat'], '@eslint/eslintrc');
|
||||||
const index = result.indexOf('module.exports');
|
const index = result.indexOf('module.exports');
|
||||||
return applyChangesToString(result, [
|
return applyChangesToString(result, [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -116,7 +116,7 @@ describe('updateEslint', () => {
|
|||||||
|
|
||||||
expect(tree.read(`${schema.appProjectRoot}/eslint.config.js`, 'utf-8'))
|
expect(tree.read(`${schema.appProjectRoot}/eslint.config.js`, 'utf-8'))
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
"const FlatCompat = require("@eslint/eslintrc");
|
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||||
const js = require("@eslint/js");
|
const js = require("@eslint/js");
|
||||||
const baseConfig = require("../eslint.config.js");
|
const baseConfig = require("../eslint.config.js");
|
||||||
|
|
||||||
@ -127,6 +127,7 @@ describe('updateEslint', () => {
|
|||||||
|
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
|
...compat.extends("plugin:@nx/react-typescript", "next", "next/core-web-vitals"),
|
||||||
...baseConfig,
|
...baseConfig,
|
||||||
{
|
{
|
||||||
"files": [
|
"files": [
|
||||||
@ -156,7 +157,6 @@ describe('updateEslint', () => {
|
|||||||
],
|
],
|
||||||
rules: {}
|
rules: {}
|
||||||
},
|
},
|
||||||
...compat.extends("plugin:@nx/react-typescript", "next", "next/core-web-vitals"),
|
|
||||||
...compat.config({ env: { jest: true } }).map(config => ({
|
...compat.config({ env: { jest: true } }).map(config => ({
|
||||||
...config,
|
...config,
|
||||||
files: [
|
files: [
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user