feat(core): Add ESM support for Eslint config file (#29613)
This pull request includes changes to migrate ESLint configuration files
from CommonJS (`.cjs`) to ECMAScript modules (`.mjs`) as the default.
### ESLint Configuration Generation Changes
The changes also ensure consistent generated eslint configs based on the
base eslint config.
- If the workspace root has an `eslint.config.cjs` or `eslint.config.js`
with `module.exports`. When you create a library or application it will
generate an accompanying config at path
`{projectRoot}/eslint.config.cjs` of the same format.
- If the workspace root has an `eslint.config.mjs` or
`eslint.config.mjs` with `export default`. When you create a library or
application it will generate an accompanying config at path
`{projectRoot}/eslint.config.mjs`.
- If no eslint config is found at the workspace root one will be created
`eslint.config.mjs`
This commit is contained in:
parent
a468d72c7f
commit
dec21662b6
@ -39,7 +39,7 @@ describe('Move Angular Project', () => {
|
||||
expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.app.json`);
|
||||
expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.json`);
|
||||
expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.spec.json`);
|
||||
expect(moveOutput).toContain(`CREATE ${newPath}/eslint.config.cjs`);
|
||||
expect(moveOutput).toContain(`CREATE ${newPath}/eslint.config.mjs`);
|
||||
expect(moveOutput).toContain(`CREATE ${newPath}/public/favicon.ico`);
|
||||
expect(moveOutput).toContain(`CREATE ${newPath}/src/index.html`);
|
||||
expect(moveOutput).toContain(`CREATE ${newPath}/src/main.ts`);
|
||||
|
||||
@ -164,13 +164,13 @@ describe('Angular Projects', () => {
|
||||
|
||||
it('should lint correctly with eslint and handle external HTML files and inline templates', async () => {
|
||||
// disable the prefer-standalone rule for app1 which is not standalone
|
||||
let app1EslintConfig = readFile(`${app1}/eslint.config.cjs`);
|
||||
let app1EslintConfig = readFile(`${app1}/eslint.config.mjs`);
|
||||
app1EslintConfig = app1EslintConfig.replace(
|
||||
`'@angular-eslint/directive-selector': [`,
|
||||
`'@angular-eslint/prefer-standalone': 'off',
|
||||
'@angular-eslint/directive-selector': [`
|
||||
);
|
||||
updateFile(`${app1}/eslint.config.cjs`, app1EslintConfig);
|
||||
updateFile(`${app1}/eslint.config.mjs`, app1EslintConfig);
|
||||
|
||||
// check apps and lib pass linting for initial generated code
|
||||
runCLI(`run-many --target lint --projects=${app1},${lib1} --parallel`);
|
||||
|
||||
@ -150,10 +150,10 @@ describe('Linter (legacy)', () => {
|
||||
env: { NX_ADD_PLUGINS: 'false' },
|
||||
});
|
||||
checkFilesExist(
|
||||
'eslint.config.cjs',
|
||||
`apps/${myapp}/eslint.config.cjs`,
|
||||
`libs/${mylib}/eslint.config.cjs`,
|
||||
`libs/${mylib2}/eslint.config.cjs`
|
||||
'eslint.config.mjs',
|
||||
`apps/${myapp}/eslint.config.mjs`,
|
||||
`libs/${mylib}/eslint.config.mjs`,
|
||||
`libs/${mylib2}/eslint.config.mjs`
|
||||
);
|
||||
checkFilesDoNotExist(
|
||||
'.eslintrc.json',
|
||||
@ -164,12 +164,12 @@ describe('Linter (legacy)', () => {
|
||||
|
||||
// move eslint.config one step up
|
||||
// to test the absence of the flat eslint config in the project root folder
|
||||
renameFile(`libs/${mylib2}/eslint.config.cjs`, `libs/eslint.config.cjs`);
|
||||
renameFile(`libs/${mylib2}/eslint.config.mjs`, `libs/eslint.config.mjs`);
|
||||
updateFile(
|
||||
`libs/eslint.config.cjs`,
|
||||
readFile(`libs/eslint.config.cjs`).replace(
|
||||
`../../eslint.config.cjs`,
|
||||
`../eslint.config.cjs`
|
||||
`libs/eslint.config.mjs`,
|
||||
readFile(`libs/eslint.config.mjs`).replace(
|
||||
`../../eslint.config.mjs`,
|
||||
`../eslint.config.mjs`
|
||||
)
|
||||
);
|
||||
|
||||
@ -202,9 +202,9 @@ describe('Linter (legacy)', () => {
|
||||
env: { NX_ADD_PLUGINS: 'false' },
|
||||
});
|
||||
checkFilesExist(
|
||||
'eslint.config.cjs',
|
||||
`${mylib}/eslint.config.cjs`,
|
||||
'eslint.base.config.cjs'
|
||||
'eslint.config.mjs',
|
||||
`${mylib}/eslint.config.mjs`,
|
||||
'eslint.base.config.mjs'
|
||||
);
|
||||
checkFilesDoNotExist(
|
||||
'.eslintrc.json',
|
||||
|
||||
@ -615,8 +615,8 @@ describe('Linter', () => {
|
||||
runCLI(`generate @nx/js:lib ${jsLib} --linter eslint`);
|
||||
|
||||
checkFilesExist(
|
||||
`${reactLib}/eslint.config.cjs`,
|
||||
`${jsLib}/eslint.config.cjs`
|
||||
`${reactLib}/eslint.config.mjs`,
|
||||
`${jsLib}/eslint.config.mjs`
|
||||
);
|
||||
checkFilesDoNotExist(
|
||||
`${reactLib}/.eslintrc.json`,
|
||||
|
||||
@ -26,7 +26,7 @@ exports[`Extra Nx Misc Tests task graph inputs should correctly expand dependent
|
||||
],
|
||||
"lib-base-123": [
|
||||
"libs/lib-base-123/README.md",
|
||||
"libs/lib-base-123/eslint.config.cjs",
|
||||
"libs/lib-base-123/eslint.config.mjs",
|
||||
"libs/lib-base-123/jest.config.ts",
|
||||
"libs/lib-base-123/package.json",
|
||||
"libs/lib-base-123/project.json",
|
||||
@ -39,7 +39,7 @@ exports[`Extra Nx Misc Tests task graph inputs should correctly expand dependent
|
||||
],
|
||||
"lib-dependent-123": [
|
||||
"libs/lib-dependent-123/README.md",
|
||||
"libs/lib-dependent-123/eslint.config.cjs",
|
||||
"libs/lib-dependent-123/eslint.config.mjs",
|
||||
"libs/lib-dependent-123/jest.config.ts",
|
||||
"libs/lib-dependent-123/package.json",
|
||||
"libs/lib-dependent-123/project.json",
|
||||
|
||||
@ -94,6 +94,7 @@ exports[`workspace move to nx layout should create nx.json 1`] = `
|
||||
"!{projectRoot}/karma.conf.js",
|
||||
"!{projectRoot}/.eslintrc.json",
|
||||
"!{projectRoot}/eslint.config.cjs",
|
||||
"!{projectRoot}/eslint.config.mjs",
|
||||
],
|
||||
"sharedGlobals": [],
|
||||
},
|
||||
@ -104,7 +105,7 @@ exports[`workspace move to nx layout should create nx.json 1`] = `
|
||||
"default",
|
||||
"{workspaceRoot}/.eslintrc.json",
|
||||
"{workspaceRoot}/.eslintignore",
|
||||
"{workspaceRoot}/eslint.config.cjs",
|
||||
"{workspaceRoot}/eslint.config.mjs",
|
||||
],
|
||||
},
|
||||
"build": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`convert-to-flat-config generator should add env configuration 1`] = `
|
||||
exports[`convert-to-flat-config generator CJS should add env configuration 1`] = `
|
||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||
const js = require('@eslint/js');
|
||||
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||
@ -61,7 +61,7 @@ module.exports = [
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator should add global and env configuration 1`] = `
|
||||
exports[`convert-to-flat-config generator CJS should add global and env configuration 1`] = `
|
||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||
const js = require('@eslint/js');
|
||||
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||
@ -126,7 +126,7 @@ module.exports = [
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator should add global configuration 1`] = `
|
||||
exports[`convert-to-flat-config generator CJS should add global configuration 1`] = `
|
||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||
const js = require('@eslint/js');
|
||||
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||
@ -186,7 +186,7 @@ module.exports = [
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator should add global eslintignores 1`] = `
|
||||
exports[`convert-to-flat-config generator CJS should add global eslintignores 1`] = `
|
||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||
const js = require('@eslint/js');
|
||||
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||
@ -248,7 +248,7 @@ module.exports = [
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator should add parser 1`] = `
|
||||
exports[`convert-to-flat-config generator CJS should add parser 1`] = `
|
||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||
const js = require('@eslint/js');
|
||||
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||
@ -309,7 +309,7 @@ module.exports = [
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator should add plugins 1`] = `
|
||||
exports[`convert-to-flat-config generator CJS should add plugins 1`] = `
|
||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||
const js = require('@eslint/js');
|
||||
const eslintPluginImport = require('eslint-plugin-import');
|
||||
@ -378,7 +378,7 @@ module.exports = [
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator should add settings 1`] = `
|
||||
exports[`convert-to-flat-config generator CJS should add settings 1`] = `
|
||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||
const js = require('@eslint/js');
|
||||
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||
@ -442,7 +442,7 @@ module.exports = [
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator should convert json successfully 1`] = `
|
||||
exports[`convert-to-flat-config generator CJS should convert json successfully 1`] = `
|
||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||
const js = require('@eslint/js');
|
||||
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||
@ -501,7 +501,7 @@ module.exports = [
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator should convert json successfully 2`] = `
|
||||
exports[`convert-to-flat-config generator CJS should convert json successfully 2`] = `
|
||||
"const baseConfig = require('../../eslint.config.cjs');
|
||||
|
||||
module.exports = [
|
||||
@ -528,7 +528,7 @@ module.exports = [
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator should convert yaml successfully 1`] = `
|
||||
exports[`convert-to-flat-config generator CJS should convert yaml successfully 1`] = `
|
||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||
const js = require('@eslint/js');
|
||||
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||
@ -587,7 +587,7 @@ module.exports = [
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator should convert yaml successfully 2`] = `
|
||||
exports[`convert-to-flat-config generator CJS should convert yaml successfully 2`] = `
|
||||
"const baseConfig = require('../../eslint.config.cjs');
|
||||
|
||||
module.exports = [
|
||||
@ -614,7 +614,7 @@ module.exports = [
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator should convert yml successfully 1`] = `
|
||||
exports[`convert-to-flat-config generator CJS should convert yml successfully 1`] = `
|
||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||
const js = require('@eslint/js');
|
||||
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||
@ -673,7 +673,7 @@ module.exports = [
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator should convert yml successfully 2`] = `
|
||||
exports[`convert-to-flat-config generator CJS should convert yml successfully 2`] = `
|
||||
"const baseConfig = require('../../eslint.config.cjs');
|
||||
|
||||
module.exports = [
|
||||
@ -700,7 +700,7 @@ module.exports = [
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator should handle custom eslintignores 1`] = `
|
||||
exports[`convert-to-flat-config generator CJS should handle custom eslintignores 1`] = `
|
||||
"const baseConfig = require('../../eslint.config.cjs');
|
||||
|
||||
module.exports = [
|
||||
@ -732,3 +732,756 @@ module.exports = [
|
||||
];
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator MJS should add env configuration 1`] = `
|
||||
"import { FlatCompat } from '@eslint/eslintrc';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import js from '@eslint/js';
|
||||
import nxEslintPlugin from '@nx/eslint-plugin';
|
||||
import globals from 'globals';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['**/dist'],
|
||||
},
|
||||
{ plugins: { '@nx': nxEslintPlugin } },
|
||||
{ languageOptions: { globals: { ...globals.browser, ...globals.node } } },
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||
rules: {
|
||||
'@nx/enforce-module-boundaries': [
|
||||
'error',
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: [],
|
||||
depConstraints: [
|
||||
{
|
||||
sourceTag: '*',
|
||||
onlyDependOnLibsWithTags: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
...compat
|
||||
.config({
|
||||
extends: ['plugin:@nx/typescript'],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
...compat
|
||||
.config({
|
||||
extends: ['plugin:@nx/javascript'],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
];
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator MJS should add global and env configuration 1`] = `
|
||||
"import { FlatCompat } from '@eslint/eslintrc';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import js from '@eslint/js';
|
||||
import nxEslintPlugin from '@nx/eslint-plugin';
|
||||
import globals from 'globals';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['**/dist'],
|
||||
},
|
||||
{ plugins: { '@nx': nxEslintPlugin } },
|
||||
{
|
||||
languageOptions: {
|
||||
globals: { ...globals.browser, myCustomGlobal: 'readonly' },
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||
rules: {
|
||||
'@nx/enforce-module-boundaries': [
|
||||
'error',
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: [],
|
||||
depConstraints: [
|
||||
{
|
||||
sourceTag: '*',
|
||||
onlyDependOnLibsWithTags: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
...compat
|
||||
.config({
|
||||
extends: ['plugin:@nx/typescript'],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
...compat
|
||||
.config({
|
||||
extends: ['plugin:@nx/javascript'],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
];
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator MJS should add global configuration 1`] = `
|
||||
"import { FlatCompat } from '@eslint/eslintrc';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import js from '@eslint/js';
|
||||
import nxEslintPlugin from '@nx/eslint-plugin';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['**/dist'],
|
||||
},
|
||||
{ plugins: { '@nx': nxEslintPlugin } },
|
||||
{ languageOptions: { globals: { myCustomGlobal: 'readonly' } } },
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||
rules: {
|
||||
'@nx/enforce-module-boundaries': [
|
||||
'error',
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: [],
|
||||
depConstraints: [
|
||||
{
|
||||
sourceTag: '*',
|
||||
onlyDependOnLibsWithTags: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
...compat
|
||||
.config({
|
||||
extends: ['plugin:@nx/typescript'],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
...compat
|
||||
.config({
|
||||
extends: ['plugin:@nx/javascript'],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
];
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator MJS should add global eslintignores 1`] = `
|
||||
"import { FlatCompat } from '@eslint/eslintrc';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import js from '@eslint/js';
|
||||
import nxEslintPlugin from '@nx/eslint-plugin';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['**/dist'],
|
||||
},
|
||||
{ plugins: { '@nx': nxEslintPlugin } },
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||
rules: {
|
||||
'@nx/enforce-module-boundaries': [
|
||||
'error',
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: [],
|
||||
depConstraints: [
|
||||
{
|
||||
sourceTag: '*',
|
||||
onlyDependOnLibsWithTags: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
...compat
|
||||
.config({
|
||||
extends: ['plugin:@nx/typescript'],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
...compat
|
||||
.config({
|
||||
extends: ['plugin:@nx/javascript'],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
{
|
||||
ignores: ['ignore/me'],
|
||||
},
|
||||
];
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator MJS should add parser 1`] = `
|
||||
"import { FlatCompat } from '@eslint/eslintrc';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import js from '@eslint/js';
|
||||
import nxEslintPlugin from '@nx/eslint-plugin';
|
||||
import typescriptEslintParser from '@typescript-eslint/parser';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['**/dist'],
|
||||
},
|
||||
{ plugins: { '@nx': nxEslintPlugin } },
|
||||
{ languageOptions: { parser: typescriptEslintParser } },
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||
rules: {
|
||||
'@nx/enforce-module-boundaries': [
|
||||
'error',
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: [],
|
||||
depConstraints: [
|
||||
{
|
||||
sourceTag: '*',
|
||||
onlyDependOnLibsWithTags: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
...compat
|
||||
.config({
|
||||
extends: ['plugin:@nx/typescript'],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
...compat
|
||||
.config({
|
||||
extends: ['plugin:@nx/javascript'],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
];
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator MJS should add plugins 1`] = `
|
||||
"import { FlatCompat } from '@eslint/eslintrc';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import js from '@eslint/js';
|
||||
import eslintPluginImport from 'eslint-plugin-import';
|
||||
import eslintPluginSingleName from 'eslint-plugin-single-name';
|
||||
import scopeEslintPluginWithName from '@scope/eslint-plugin-with-name';
|
||||
import justScopeEslintPlugin from '@just-scope/eslint-plugin';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['**/dist'],
|
||||
},
|
||||
{
|
||||
plugins: {
|
||||
'eslint-plugin-import': eslintPluginImport,
|
||||
'single-name': eslintPluginSingleName,
|
||||
'@scope/with-name': scopeEslintPluginWithName,
|
||||
'@just-scope': justScopeEslintPlugin,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||
rules: {
|
||||
'@nx/enforce-module-boundaries': [
|
||||
'error',
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: [],
|
||||
depConstraints: [
|
||||
{
|
||||
sourceTag: '*',
|
||||
onlyDependOnLibsWithTags: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
...compat
|
||||
.config({
|
||||
extends: ['plugin:@nx/typescript'],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
...compat
|
||||
.config({
|
||||
extends: ['plugin:@nx/javascript'],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
];
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator MJS should add settings 1`] = `
|
||||
"import { FlatCompat } from '@eslint/eslintrc';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import js from '@eslint/js';
|
||||
import nxEslintPlugin from '@nx/eslint-plugin';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['**/dist'],
|
||||
},
|
||||
{ plugins: { '@nx': nxEslintPlugin } },
|
||||
{
|
||||
settings: {
|
||||
sharedData: 'Hello',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||
rules: {
|
||||
'@nx/enforce-module-boundaries': [
|
||||
'error',
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: [],
|
||||
depConstraints: [
|
||||
{
|
||||
sourceTag: '*',
|
||||
onlyDependOnLibsWithTags: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
...compat
|
||||
.config({
|
||||
extends: ['plugin:@nx/typescript'],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
...compat
|
||||
.config({
|
||||
extends: ['plugin:@nx/javascript'],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
];
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator MJS should convert json successfully 1`] = `
|
||||
"import { FlatCompat } from '@eslint/eslintrc';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import js from '@eslint/js';
|
||||
import nxEslintPlugin from '@nx/eslint-plugin';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['**/dist'],
|
||||
},
|
||||
{ plugins: { '@nx': nxEslintPlugin } },
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||
rules: {
|
||||
'@nx/enforce-module-boundaries': [
|
||||
'error',
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: [],
|
||||
depConstraints: [
|
||||
{
|
||||
sourceTag: '*',
|
||||
onlyDependOnLibsWithTags: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
...compat
|
||||
.config({
|
||||
extends: ['plugin:@nx/typescript'],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
...compat
|
||||
.config({
|
||||
extends: ['plugin:@nx/javascript'],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
];
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator MJS should convert json successfully 2`] = `
|
||||
"import baseConfig from '../../eslint.config.mjs';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['**/dist'],
|
||||
},
|
||||
...baseConfig,
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
files: ['**/*.js', '**/*.jsx'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
];
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator MJS should convert yaml successfully 1`] = `
|
||||
"import { FlatCompat } from '@eslint/eslintrc';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import js from '@eslint/js';
|
||||
import nxEslintPlugin from '@nx/eslint-plugin';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['**/dist'],
|
||||
},
|
||||
{ plugins: { '@nx': nxEslintPlugin } },
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||
rules: {
|
||||
'@nx/enforce-module-boundaries': [
|
||||
'error',
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: [],
|
||||
depConstraints: [
|
||||
{
|
||||
sourceTag: '*',
|
||||
onlyDependOnLibsWithTags: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
...compat
|
||||
.config({
|
||||
extends: ['plugin:@nx/typescript'],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
...compat
|
||||
.config({
|
||||
extends: ['plugin:@nx/javascript'],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
];
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator MJS should convert yaml successfully 2`] = `
|
||||
"import baseConfig from '../../eslint.config.mjs';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['**/dist'],
|
||||
},
|
||||
...baseConfig,
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
files: ['**/*.js', '**/*.jsx'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
];
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator MJS should convert yml successfully 1`] = `
|
||||
"import { FlatCompat } from '@eslint/eslintrc';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import js from '@eslint/js';
|
||||
import nxEslintPlugin from '@nx/eslint-plugin';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['**/dist'],
|
||||
},
|
||||
{ plugins: { '@nx': nxEslintPlugin } },
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||
rules: {
|
||||
'@nx/enforce-module-boundaries': [
|
||||
'error',
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: [],
|
||||
depConstraints: [
|
||||
{
|
||||
sourceTag: '*',
|
||||
onlyDependOnLibsWithTags: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
...compat
|
||||
.config({
|
||||
extends: ['plugin:@nx/typescript'],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
...compat
|
||||
.config({
|
||||
extends: ['plugin:@nx/javascript'],
|
||||
})
|
||||
.map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
];
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator MJS should convert yml successfully 2`] = `
|
||||
"import baseConfig from '../../eslint.config.mjs';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['**/dist'],
|
||||
},
|
||||
...baseConfig,
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
files: ['**/*.js', '**/*.jsx'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
];
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert-to-flat-config generator MJS should handle custom eslintignores 1`] = `
|
||||
"import baseConfig from '../../eslint.config.mjs';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['**/dist'],
|
||||
},
|
||||
...baseConfig,
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
files: ['**/*.js', '**/*.jsx'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
ignores: ['ignore/me'],
|
||||
},
|
||||
{
|
||||
ignores: ['ignore/me/as/well'],
|
||||
},
|
||||
];
|
||||
"
|
||||
`;
|
||||
|
||||
@ -9,306 +9,623 @@ describe('convertEslintJsonToFlatConfig', () => {
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
});
|
||||
|
||||
it('should convert root configs', async () => {
|
||||
tree.write(
|
||||
'.eslintrc.json',
|
||||
JSON.stringify({
|
||||
root: true,
|
||||
ignorePatterns: ['**/*', 'src/ignore/to/keep.ts'],
|
||||
plugins: ['@nx'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
||||
rules: {
|
||||
'@nx/enforce-module-boundaries': [
|
||||
'error',
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: [],
|
||||
depConstraints: [
|
||||
{
|
||||
sourceTag: '*',
|
||||
onlyDependOnLibsWithTags: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
extends: ['plugin:@nx/typescript'],
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
files: ['*.js', '*.jsx'],
|
||||
extends: ['plugin:@nx/javascript'],
|
||||
rules: {},
|
||||
},
|
||||
|
||||
{
|
||||
files: [
|
||||
'**/*.spec.ts',
|
||||
'**/*.spec.tsx',
|
||||
'**/*.spec.js',
|
||||
'**/*.spec.jsx',
|
||||
],
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
tree.write('.eslintignore', 'node_modules\nsomething/else');
|
||||
|
||||
const { content } = convertEslintJsonToFlatConfig(
|
||||
tree,
|
||||
'',
|
||||
readJson(tree, '.eslintrc.json'),
|
||||
['.eslintignore']
|
||||
);
|
||||
|
||||
expect(content).toMatchInlineSnapshot(`
|
||||
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||
const js = require("@eslint/js");
|
||||
const nxEslintPlugin = require("@nx/eslint-plugin");
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
ignores: [
|
||||
"**/dist"
|
||||
]
|
||||
},
|
||||
{ plugins: { "@nx": nxEslintPlugin } },
|
||||
{
|
||||
files: [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.js",
|
||||
"**/*.jsx"
|
||||
],
|
||||
describe('ESM', () => {
|
||||
it('should convert root configs', async () => {
|
||||
tree.write(
|
||||
'.eslintrc.json',
|
||||
JSON.stringify({
|
||||
root: true,
|
||||
ignorePatterns: ['**/*', 'src/ignore/to/keep.ts'],
|
||||
plugins: ['@nx'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
||||
rules: {
|
||||
"@nx/enforce-module-boundaries": [
|
||||
"error",
|
||||
'@nx/enforce-module-boundaries': [
|
||||
'error',
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: [],
|
||||
depConstraints: [
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: [],
|
||||
depConstraints: [
|
||||
{
|
||||
sourceTag: "*",
|
||||
onlyDependOnLibsWithTags: [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
sourceTag: '*',
|
||||
onlyDependOnLibsWithTags: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
extends: ['plugin:@nx/typescript'],
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
files: ['*.js', '*.jsx'],
|
||||
extends: ['plugin:@nx/javascript'],
|
||||
rules: {},
|
||||
},
|
||||
|
||||
{
|
||||
files: [
|
||||
'**/*.spec.ts',
|
||||
'**/*.spec.tsx',
|
||||
'**/*.spec.js',
|
||||
'**/*.spec.jsx',
|
||||
],
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
tree.write('.eslintignore', 'node_modules\nsomething/else');
|
||||
|
||||
const { content } = convertEslintJsonToFlatConfig(
|
||||
tree,
|
||||
'',
|
||||
readJson(tree, '.eslintrc.json'),
|
||||
['.eslintignore'],
|
||||
'mjs'
|
||||
);
|
||||
|
||||
expect(content).toMatchInlineSnapshot(`
|
||||
"import { FlatCompat } from "@eslint/eslintrc";
|
||||
import { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import js from "@eslint/js";
|
||||
import nxEslintPlugin from "@nx/eslint-plugin";
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: [
|
||||
"**/dist"
|
||||
]
|
||||
},
|
||||
{ plugins: { "@nx": nxEslintPlugin } },
|
||||
{
|
||||
files: [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.js",
|
||||
"**/*.jsx"
|
||||
],
|
||||
rules: {
|
||||
"@nx/enforce-module-boundaries": [
|
||||
"error",
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: [],
|
||||
depConstraints: [
|
||||
{
|
||||
sourceTag: "*",
|
||||
onlyDependOnLibsWithTags: [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
...compat.config({
|
||||
extends: [
|
||||
"plugin:@nx/typescript"
|
||||
]
|
||||
}).map(config => ({
|
||||
...config,
|
||||
files: [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.cts",
|
||||
"**/*.mts"
|
||||
],
|
||||
rules: {
|
||||
...config.rules
|
||||
}
|
||||
})),
|
||||
...compat.config({
|
||||
extends: [
|
||||
"plugin:@nx/javascript"
|
||||
]
|
||||
}).map(config => ({
|
||||
...config,
|
||||
files: [
|
||||
"**/*.js",
|
||||
"**/*.jsx",
|
||||
"**/*.cjs",
|
||||
"**/*.mjs"
|
||||
],
|
||||
rules: {
|
||||
...config.rules
|
||||
}
|
||||
})),
|
||||
...compat.config({
|
||||
env: {
|
||||
jest: true
|
||||
}
|
||||
}).map(config => ({
|
||||
...config,
|
||||
files: [
|
||||
"**/*.spec.ts",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.spec.js",
|
||||
"**/*.spec.jsx"
|
||||
],
|
||||
rules: {
|
||||
...config.rules
|
||||
}
|
||||
})),
|
||||
{
|
||||
ignores: [
|
||||
"src/ignore/to/keep.ts"
|
||||
]
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
"something/else"
|
||||
]
|
||||
}
|
||||
];
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should convert project configs', async () => {
|
||||
tree.write(
|
||||
'mylib/.eslintrc.json',
|
||||
JSON.stringify({
|
||||
extends: [
|
||||
'plugin:@nx/react-typescript',
|
||||
'next',
|
||||
'next/core-web-vitals',
|
||||
'../../.eslintrc.json',
|
||||
],
|
||||
ignorePatterns: ['!**/*', '.next/**/*'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
||||
rules: {
|
||||
'@next/next/no-html-link-for-pages': [
|
||||
'error',
|
||||
'apps/test-next/pages',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
files: ['*.js', '*.jsx'],
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
files: ['*.json'],
|
||||
parser: 'jsonc-eslint-parser',
|
||||
rules: {
|
||||
'@nx/dependency-checks': 'error',
|
||||
},
|
||||
},
|
||||
],
|
||||
rules: {
|
||||
'@next/next/no-html-link-for-pages': 'off',
|
||||
},
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
tree.write('mylib/.eslintignore', 'node_modules\nsomething/else');
|
||||
|
||||
const { content } = convertEslintJsonToFlatConfig(
|
||||
tree,
|
||||
'mylib',
|
||||
readJson(tree, 'mylib/.eslintrc.json'),
|
||||
['mylib/.eslintignore'],
|
||||
'mjs'
|
||||
);
|
||||
|
||||
expect(content).toMatchInlineSnapshot(`
|
||||
"import { FlatCompat } from "@eslint/eslintrc";
|
||||
import { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import js from "@eslint/js";
|
||||
import baseConfig from "../../eslint.config.mjs";
|
||||
import globals from "globals";
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: [
|
||||
"**/dist"
|
||||
]
|
||||
},
|
||||
...baseConfig,
|
||||
...compat.extends("plugin:@nx/react-typescript", "next", "next/core-web-vitals"),
|
||||
{ languageOptions: { globals: { ...globals.jest } } },
|
||||
{
|
||||
rules: {
|
||||
"@next/next/no-html-link-for-pages": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.js",
|
||||
"**/*.jsx"
|
||||
],
|
||||
rules: {
|
||||
"@next/next/no-html-link-for-pages": [
|
||||
"error",
|
||||
"apps/test-next/pages"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
...compat.config({
|
||||
extends: [
|
||||
"plugin:@nx/typescript"
|
||||
]
|
||||
}).map(config => ({
|
||||
...config,
|
||||
files: [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.cts",
|
||||
"**/*.mts"
|
||||
],
|
||||
rules: {
|
||||
...config.rules
|
||||
}
|
||||
})),
|
||||
...compat.config({
|
||||
extends: [
|
||||
"plugin:@nx/javascript"
|
||||
]
|
||||
}).map(config => ({
|
||||
...config,
|
||||
files: [
|
||||
"**/*.js",
|
||||
"**/*.jsx",
|
||||
"**/*.cjs",
|
||||
"**/*.mjs"
|
||||
],
|
||||
rules: {
|
||||
...config.rules
|
||||
}
|
||||
})),
|
||||
...compat.config({
|
||||
env: {
|
||||
jest: true
|
||||
}
|
||||
}).map(config => ({
|
||||
...config,
|
||||
files: [
|
||||
"**/*.spec.ts",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.spec.js",
|
||||
"**/*.spec.jsx"
|
||||
],
|
||||
rules: {
|
||||
...config.rules
|
||||
}
|
||||
})),
|
||||
{
|
||||
ignores: [
|
||||
"src/ignore/to/keep.ts"
|
||||
]
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
"something/else"
|
||||
]
|
||||
}
|
||||
];
|
||||
"
|
||||
`);
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
],
|
||||
// Override or add rules here
|
||||
rules: {}
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"**/*.js",
|
||||
"**/*.jsx"
|
||||
],
|
||||
// Override or add rules here
|
||||
rules: {}
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"**/*.json"
|
||||
],
|
||||
rules: {
|
||||
"@nx/dependency-checks": "error"
|
||||
},
|
||||
languageOptions: {
|
||||
parser: await import("jsonc-eslint-parser")
|
||||
}
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
".next/**/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
"something/else"
|
||||
]
|
||||
}
|
||||
];
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should convert project configs', async () => {
|
||||
tree.write(
|
||||
'mylib/.eslintrc.json',
|
||||
JSON.stringify({
|
||||
extends: [
|
||||
'plugin:@nx/react-typescript',
|
||||
'next',
|
||||
'next/core-web-vitals',
|
||||
'../../.eslintrc.json',
|
||||
],
|
||||
ignorePatterns: ['!**/*', '.next/**/*'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
||||
rules: {
|
||||
'@next/next/no-html-link-for-pages': [
|
||||
'error',
|
||||
'apps/test-next/pages',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
files: ['*.js', '*.jsx'],
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
files: ['*.json'],
|
||||
parser: 'jsonc-eslint-parser',
|
||||
rules: {
|
||||
'@nx/dependency-checks': 'error',
|
||||
},
|
||||
},
|
||||
],
|
||||
rules: {
|
||||
'@next/next/no-html-link-for-pages': 'off',
|
||||
},
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
tree.write('mylib/.eslintignore', 'node_modules\nsomething/else');
|
||||
|
||||
const { content } = convertEslintJsonToFlatConfig(
|
||||
tree,
|
||||
'mylib',
|
||||
readJson(tree, 'mylib/.eslintrc.json'),
|
||||
['mylib/.eslintignore']
|
||||
);
|
||||
|
||||
expect(content).toMatchInlineSnapshot(`
|
||||
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||
const js = require("@eslint/js");
|
||||
const baseConfig = require("../../eslint.config.cjs");
|
||||
const globals = require("globals");
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
ignores: [
|
||||
"**/dist"
|
||||
]
|
||||
},
|
||||
...baseConfig,
|
||||
...compat.extends("plugin:@nx/react-typescript", "next", "next/core-web-vitals"),
|
||||
{ languageOptions: { globals: { ...globals.jest } } },
|
||||
{
|
||||
describe('CJS', () => {
|
||||
it('should convert root configs', async () => {
|
||||
tree.write(
|
||||
'.eslintrc.json',
|
||||
JSON.stringify({
|
||||
root: true,
|
||||
ignorePatterns: ['**/*', 'src/ignore/to/keep.ts'],
|
||||
plugins: ['@nx'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
||||
rules: {
|
||||
"@next/next/no-html-link-for-pages": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.js",
|
||||
"**/*.jsx"
|
||||
],
|
||||
rules: {
|
||||
"@next/next/no-html-link-for-pages": [
|
||||
"error",
|
||||
"apps/test-next/pages"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
],
|
||||
// Override or add rules here
|
||||
rules: {}
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"**/*.js",
|
||||
"**/*.jsx"
|
||||
],
|
||||
// Override or add rules here
|
||||
rules: {}
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"**/*.json"
|
||||
],
|
||||
rules: {
|
||||
"@nx/dependency-checks": "error"
|
||||
'@nx/enforce-module-boundaries': [
|
||||
'error',
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: [],
|
||||
depConstraints: [
|
||||
{
|
||||
sourceTag: '*',
|
||||
onlyDependOnLibsWithTags: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
languageOptions: {
|
||||
parser: require("jsonc-eslint-parser")
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
extends: ['plugin:@nx/typescript'],
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
files: ['*.js', '*.jsx'],
|
||||
extends: ['plugin:@nx/javascript'],
|
||||
rules: {},
|
||||
},
|
||||
|
||||
{
|
||||
files: [
|
||||
'**/*.spec.ts',
|
||||
'**/*.spec.tsx',
|
||||
'**/*.spec.js',
|
||||
'**/*.spec.jsx',
|
||||
],
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
tree.write('.eslintignore', 'node_modules\nsomething/else');
|
||||
|
||||
const { content } = convertEslintJsonToFlatConfig(
|
||||
tree,
|
||||
'',
|
||||
readJson(tree, '.eslintrc.json'),
|
||||
['.eslintignore'],
|
||||
'cjs'
|
||||
);
|
||||
|
||||
expect(content).toMatchInlineSnapshot(`
|
||||
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||
const js = require("@eslint/js");
|
||||
const nxEslintPlugin = require("@nx/eslint-plugin");
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
ignores: [
|
||||
"**/dist"
|
||||
]
|
||||
},
|
||||
{ plugins: { "@nx": nxEslintPlugin } },
|
||||
{
|
||||
files: [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.js",
|
||||
"**/*.jsx"
|
||||
],
|
||||
rules: {
|
||||
"@nx/enforce-module-boundaries": [
|
||||
"error",
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: [],
|
||||
depConstraints: [
|
||||
{
|
||||
sourceTag: "*",
|
||||
onlyDependOnLibsWithTags: [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
...compat.config({
|
||||
extends: [
|
||||
"plugin:@nx/typescript"
|
||||
]
|
||||
}).map(config => ({
|
||||
...config,
|
||||
files: [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.cts",
|
||||
"**/*.mts"
|
||||
],
|
||||
rules: {
|
||||
...config.rules
|
||||
}
|
||||
})),
|
||||
...compat.config({
|
||||
extends: [
|
||||
"plugin:@nx/javascript"
|
||||
]
|
||||
}).map(config => ({
|
||||
...config,
|
||||
files: [
|
||||
"**/*.js",
|
||||
"**/*.jsx",
|
||||
"**/*.cjs",
|
||||
"**/*.mjs"
|
||||
],
|
||||
rules: {
|
||||
...config.rules
|
||||
}
|
||||
})),
|
||||
...compat.config({
|
||||
env: {
|
||||
jest: true
|
||||
}
|
||||
}).map(config => ({
|
||||
...config,
|
||||
files: [
|
||||
"**/*.spec.ts",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.spec.js",
|
||||
"**/*.spec.jsx"
|
||||
],
|
||||
rules: {
|
||||
...config.rules
|
||||
}
|
||||
})),
|
||||
{
|
||||
ignores: [
|
||||
"src/ignore/to/keep.ts"
|
||||
]
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
"something/else"
|
||||
]
|
||||
}
|
||||
];
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should convert project configs', async () => {
|
||||
tree.write(
|
||||
'mylib/.eslintrc.json',
|
||||
JSON.stringify({
|
||||
extends: [
|
||||
'plugin:@nx/react-typescript',
|
||||
'next',
|
||||
'next/core-web-vitals',
|
||||
'../../.eslintrc.json',
|
||||
],
|
||||
ignorePatterns: ['!**/*', '.next/**/*'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
||||
rules: {
|
||||
'@next/next/no-html-link-for-pages': [
|
||||
'error',
|
||||
'apps/test-next/pages',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
files: ['*.js', '*.jsx'],
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
files: ['*.json'],
|
||||
parser: 'jsonc-eslint-parser',
|
||||
rules: {
|
||||
'@nx/dependency-checks': 'error',
|
||||
},
|
||||
},
|
||||
],
|
||||
rules: {
|
||||
'@next/next/no-html-link-for-pages': 'off',
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
".next/**/*"
|
||||
]
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
"something/else"
|
||||
]
|
||||
}
|
||||
];
|
||||
"
|
||||
`);
|
||||
})
|
||||
);
|
||||
|
||||
tree.write('mylib/.eslintignore', 'node_modules\nsomething/else');
|
||||
|
||||
const { content } = convertEslintJsonToFlatConfig(
|
||||
tree,
|
||||
'mylib',
|
||||
readJson(tree, 'mylib/.eslintrc.json'),
|
||||
['mylib/.eslintignore'],
|
||||
'cjs'
|
||||
);
|
||||
|
||||
expect(content).toMatchInlineSnapshot(`
|
||||
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||
const js = require("@eslint/js");
|
||||
const baseConfig = require("../../eslint.config.cjs");
|
||||
const globals = require("globals");
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
ignores: [
|
||||
"**/dist"
|
||||
]
|
||||
},
|
||||
...baseConfig,
|
||||
...compat.extends("plugin:@nx/react-typescript", "next", "next/core-web-vitals"),
|
||||
{ languageOptions: { globals: { ...globals.jest } } },
|
||||
{
|
||||
rules: {
|
||||
"@next/next/no-html-link-for-pages": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.js",
|
||||
"**/*.jsx"
|
||||
],
|
||||
rules: {
|
||||
"@next/next/no-html-link-for-pages": [
|
||||
"error",
|
||||
"apps/test-next/pages"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
],
|
||||
// Override or add rules here
|
||||
rules: {}
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"**/*.js",
|
||||
"**/*.jsx"
|
||||
],
|
||||
// Override or add rules here
|
||||
rules: {}
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"**/*.json"
|
||||
],
|
||||
rules: {
|
||||
"@nx/dependency-checks": "error"
|
||||
},
|
||||
languageOptions: {
|
||||
parser: require("jsonc-eslint-parser")
|
||||
}
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
".next/**/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
"something/else"
|
||||
]
|
||||
}
|
||||
];
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -21,7 +21,8 @@ export function convertEslintJsonToFlatConfig(
|
||||
tree: Tree,
|
||||
root: string,
|
||||
config: ESLint.ConfigData,
|
||||
ignorePaths: string[]
|
||||
ignorePaths: string[],
|
||||
format: 'cjs' | 'mjs'
|
||||
): { content: string; addESLintRC: boolean; addESLintJS: boolean } {
|
||||
const importsMap = new Map<string, string>();
|
||||
const exportElements: ts.Expression[] = [];
|
||||
@ -38,7 +39,12 @@ export function convertEslintJsonToFlatConfig(
|
||||
);
|
||||
|
||||
if (config.extends) {
|
||||
const extendsResult = addExtends(importsMap, exportElements, config);
|
||||
const extendsResult = addExtends(
|
||||
importsMap,
|
||||
exportElements,
|
||||
config,
|
||||
format
|
||||
);
|
||||
isFlatCompatNeeded = extendsResult.isFlatCompatNeeded;
|
||||
isESLintJSNeeded = extendsResult.isESLintJSNeeded;
|
||||
}
|
||||
@ -156,7 +162,7 @@ export function convertEslintJsonToFlatConfig(
|
||||
) {
|
||||
isFlatCompatNeeded = true;
|
||||
}
|
||||
exportElements.push(generateFlatOverride(override));
|
||||
exportElements.push(generateFlatOverride(override, format));
|
||||
});
|
||||
}
|
||||
|
||||
@ -189,7 +195,7 @@ export function convertEslintJsonToFlatConfig(
|
||||
}
|
||||
|
||||
// create the node list and print it to new file
|
||||
const nodeList = createNodeList(importsMap, exportElements);
|
||||
const nodeList = createNodeList(importsMap, exportElements, format);
|
||||
let content = stringifyNodeList(nodeList);
|
||||
if (isFlatCompatNeeded) {
|
||||
content = addFlatCompatToFlatConfig(content);
|
||||
@ -206,7 +212,8 @@ export function convertEslintJsonToFlatConfig(
|
||||
function addExtends(
|
||||
importsMap: Map<string, string | string[]>,
|
||||
configBlocks: ts.Expression[],
|
||||
config: ESLint.ConfigData
|
||||
config: ESLint.ConfigData,
|
||||
format: 'mjs' | 'cjs'
|
||||
): { isFlatCompatNeeded: boolean; isESLintJSNeeded: boolean } {
|
||||
let isFlatCompatNeeded = false;
|
||||
let isESLintJSNeeded = false;
|
||||
@ -225,7 +232,7 @@ function addExtends(
|
||||
configBlocks.push(generateSpreadElement(localName));
|
||||
const newImport = imp.replace(
|
||||
/^(.*)\.eslintrc(.base)?\.json$/,
|
||||
'$1eslint$2.config.cjs'
|
||||
`$1eslint$2.config.${format}`
|
||||
);
|
||||
importsMap.set(newImport, localName);
|
||||
} else {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -40,10 +40,12 @@ export async function convertToFlatConfigGenerator(
|
||||
);
|
||||
}
|
||||
|
||||
options.eslintConfigFormat ??= 'mjs';
|
||||
|
||||
const eslintIgnoreFiles = new Set<string>(['.eslintignore']);
|
||||
|
||||
// convert root eslint config to eslint.config.cjs
|
||||
convertRootToFlatConfig(tree, eslintFile);
|
||||
// convert root eslint config to eslint.config.cjs or eslint.base.config.mjs based on eslintConfigFormat
|
||||
convertRootToFlatConfig(tree, eslintFile, options.eslintConfigFormat);
|
||||
|
||||
// convert project eslint files to eslint.config.cjs
|
||||
const projects = getProjects(tree);
|
||||
@ -53,7 +55,8 @@ export async function convertToFlatConfigGenerator(
|
||||
project,
|
||||
projectConfig,
|
||||
readNxJson(tree),
|
||||
eslintIgnoreFiles
|
||||
eslintIgnoreFiles,
|
||||
options.eslintConfigFormat
|
||||
);
|
||||
}
|
||||
|
||||
@ -63,7 +66,7 @@ export async function convertToFlatConfigGenerator(
|
||||
}
|
||||
|
||||
// replace references in nx.json
|
||||
updateNxJsonConfig(tree);
|
||||
updateNxJsonConfig(tree, options.eslintConfigFormat);
|
||||
// install missing packages
|
||||
|
||||
if (!options.skipFormat) {
|
||||
@ -75,15 +78,26 @@ export async function convertToFlatConfigGenerator(
|
||||
|
||||
export default convertToFlatConfigGenerator;
|
||||
|
||||
function convertRootToFlatConfig(tree: Tree, eslintFile: string) {
|
||||
function convertRootToFlatConfig(
|
||||
tree: Tree,
|
||||
eslintFile: string,
|
||||
format: 'cjs' | 'mjs'
|
||||
) {
|
||||
if (/\.base\.(js|json|yml|yaml)$/.test(eslintFile)) {
|
||||
convertConfigToFlatConfig(tree, '', eslintFile, 'eslint.base.config.cjs');
|
||||
convertConfigToFlatConfig(
|
||||
tree,
|
||||
'',
|
||||
eslintFile,
|
||||
`eslint.base.config.${format}`,
|
||||
format
|
||||
);
|
||||
}
|
||||
convertConfigToFlatConfig(
|
||||
tree,
|
||||
'',
|
||||
eslintFile.replace('.base.', '.'),
|
||||
'eslint.config.cjs'
|
||||
`eslint.config.${format}`,
|
||||
format
|
||||
);
|
||||
}
|
||||
|
||||
@ -92,7 +106,8 @@ function convertProjectToFlatConfig(
|
||||
project: string,
|
||||
projectConfig: ProjectConfiguration,
|
||||
nxJson: NxJsonConfiguration,
|
||||
eslintIgnoreFiles: Set<string>
|
||||
eslintIgnoreFiles: Set<string>,
|
||||
format: 'cjs' | 'mjs'
|
||||
) {
|
||||
const eslintFile = findEslintFile(tree, projectConfig.root);
|
||||
if (eslintFile && !eslintFile.endsWith('.js')) {
|
||||
@ -132,7 +147,8 @@ function convertProjectToFlatConfig(
|
||||
tree,
|
||||
projectConfig.root,
|
||||
eslintFile,
|
||||
'eslint.config.cjs',
|
||||
`eslint.config.${format}`,
|
||||
format,
|
||||
ignorePath
|
||||
);
|
||||
eslintIgnoreFiles.add(`${projectConfig.root}/.eslintignore`);
|
||||
@ -146,22 +162,22 @@ function convertProjectToFlatConfig(
|
||||
|
||||
// update names of eslint files in nx.json
|
||||
// and remove eslintignore
|
||||
function updateNxJsonConfig(tree: Tree) {
|
||||
function updateNxJsonConfig(tree: Tree, format: 'cjs' | 'mjs') {
|
||||
if (tree.exists('nx.json')) {
|
||||
updateJson(tree, 'nx.json', (json: NxJsonConfiguration) => {
|
||||
if (json.targetDefaults?.lint?.inputs) {
|
||||
const inputSet = new Set(json.targetDefaults.lint.inputs);
|
||||
inputSet.add('{workspaceRoot}/eslint.config.cjs');
|
||||
inputSet.add(`{workspaceRoot}/eslint.config.${format}`);
|
||||
json.targetDefaults.lint.inputs = Array.from(inputSet);
|
||||
}
|
||||
if (json.targetDefaults?.['@nx/eslint:lint']?.inputs) {
|
||||
const inputSet = new Set(json.targetDefaults['@nx/eslint:lint'].inputs);
|
||||
inputSet.add('{workspaceRoot}/eslint.config.cjs');
|
||||
inputSet.add(`{workspaceRoot}/eslint.config.${format}`);
|
||||
json.targetDefaults['@nx/eslint:lint'].inputs = Array.from(inputSet);
|
||||
}
|
||||
if (json.namedInputs?.production) {
|
||||
const inputSet = new Set(json.namedInputs.production);
|
||||
inputSet.add('!{projectRoot}/eslint.config.cjs');
|
||||
inputSet.add(`!{projectRoot}/eslint.config.${format}`);
|
||||
json.namedInputs.production = Array.from(inputSet);
|
||||
}
|
||||
return json;
|
||||
@ -174,6 +190,7 @@ function convertConfigToFlatConfig(
|
||||
root: string,
|
||||
source: string,
|
||||
target: string,
|
||||
format: 'cjs' | 'mjs',
|
||||
ignorePath?: string
|
||||
) {
|
||||
const ignorePaths = ignorePath
|
||||
@ -186,7 +203,8 @@ function convertConfigToFlatConfig(
|
||||
tree,
|
||||
root,
|
||||
config,
|
||||
ignorePaths
|
||||
ignorePaths,
|
||||
format
|
||||
);
|
||||
return processConvertedConfig(tree, root, source, target, conversionResult);
|
||||
}
|
||||
@ -201,7 +219,8 @@ function convertConfigToFlatConfig(
|
||||
tree,
|
||||
root,
|
||||
config,
|
||||
ignorePaths
|
||||
ignorePaths,
|
||||
format
|
||||
);
|
||||
return processConvertedConfig(tree, root, source, target, conversionResult);
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
export interface ConvertToFlatConfigGeneratorSchema {
|
||||
skipFormat?: boolean;
|
||||
// Internal option
|
||||
eslintConfigFormat?: 'mjs' | 'cjs';
|
||||
}
|
||||
|
||||
@ -66,6 +66,7 @@ function postTargetTransformer(
|
||||
'{workspaceRoot}/.eslintrc.json',
|
||||
'{workspaceRoot}/.eslintignore',
|
||||
'{workspaceRoot}/eslint.config.cjs',
|
||||
'{workspaceRoot}/eslint.config.mjs',
|
||||
].includes(input)
|
||||
);
|
||||
if (inputs.length === 0) {
|
||||
|
||||
@ -92,9 +92,10 @@ export const getGlobalEsLintConfiguration = (
|
||||
};
|
||||
|
||||
export const getGlobalFlatEslintConfiguration = (
|
||||
format: 'cjs' | 'mjs',
|
||||
rootProject?: boolean
|
||||
): string => {
|
||||
const nodeList = createNodeList(new Map(), []);
|
||||
const nodeList = createNodeList(new Map(), [], format);
|
||||
let content = stringifyNodeList(nodeList);
|
||||
content = addImportToFlatConfig(content, 'nx', '@nx/eslint-plugin');
|
||||
|
||||
@ -114,49 +115,58 @@ export const getGlobalFlatEslintConfiguration = (
|
||||
|
||||
content = addBlockToFlatConfigExport(
|
||||
content,
|
||||
generateFlatOverride({
|
||||
ignores: ['**/dist'],
|
||||
})
|
||||
generateFlatOverride(
|
||||
{
|
||||
ignores: ['**/dist'],
|
||||
},
|
||||
format
|
||||
)
|
||||
);
|
||||
|
||||
if (!rootProject) {
|
||||
content = addBlockToFlatConfigExport(
|
||||
content,
|
||||
generateFlatOverride({
|
||||
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
||||
rules: {
|
||||
'@nx/enforce-module-boundaries': [
|
||||
'error',
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: [
|
||||
// This allows a root project to be present without causing lint errors
|
||||
// since all projects will depend on this base file.
|
||||
'^.*/eslint(\\.base)?\\.config\\.[cm]?js$',
|
||||
],
|
||||
depConstraints: [
|
||||
{ sourceTag: '*', onlyDependOnLibsWithTags: ['*'] },
|
||||
],
|
||||
},
|
||||
],
|
||||
} as Linter.RulesRecord,
|
||||
})
|
||||
generateFlatOverride(
|
||||
{
|
||||
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
||||
rules: {
|
||||
'@nx/enforce-module-boundaries': [
|
||||
'error',
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: [
|
||||
// This allows a root project to be present without causing lint errors
|
||||
// since all projects will depend on this base file.
|
||||
'^.*/eslint(\\.base)?\\.config\\.[cm]?js$',
|
||||
],
|
||||
depConstraints: [
|
||||
{ sourceTag: '*', onlyDependOnLibsWithTags: ['*'] },
|
||||
],
|
||||
},
|
||||
],
|
||||
} as Linter.RulesRecord,
|
||||
},
|
||||
format
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
content = addBlockToFlatConfigExport(
|
||||
content,
|
||||
generateFlatOverride({
|
||||
files: [
|
||||
'**/*.ts',
|
||||
'**/*.tsx',
|
||||
'**/*.js',
|
||||
'**/*.jsx',
|
||||
'**/*.cjs',
|
||||
'**/*.mjs',
|
||||
],
|
||||
rules: {},
|
||||
})
|
||||
generateFlatOverride(
|
||||
{
|
||||
files: [
|
||||
'**/*.ts',
|
||||
'**/*.tsx',
|
||||
'**/*.js',
|
||||
'**/*.jsx',
|
||||
'**/*.cjs',
|
||||
'**/*.mjs',
|
||||
],
|
||||
rules: {},
|
||||
},
|
||||
format
|
||||
)
|
||||
);
|
||||
|
||||
return content;
|
||||
|
||||
@ -9,8 +9,12 @@ import {
|
||||
updateJson,
|
||||
writeJson,
|
||||
} from '@nx/devkit';
|
||||
import { dirname } from 'path';
|
||||
import { findEslintFile, isEslintConfigSupported } from '../utils/eslint-file';
|
||||
import { dirname, extname } from 'path';
|
||||
import {
|
||||
determineEslintConfigFormat,
|
||||
findEslintFile,
|
||||
isEslintConfigSupported,
|
||||
} from '../utils/eslint-file';
|
||||
import {
|
||||
getGlobalEsLintConfiguration,
|
||||
getGlobalFlatEslintConfiguration,
|
||||
@ -32,10 +36,24 @@ export function migrateConfigToMonorepoStyle(
|
||||
projects: ProjectConfiguration[],
|
||||
tree: Tree,
|
||||
unitTestRunner: string,
|
||||
eslintConfigFormat: 'mjs' | 'cjs',
|
||||
keepExistingVersions?: boolean
|
||||
): GeneratorCallback {
|
||||
const rootEslintConfig = findEslintFile(tree);
|
||||
let skipCleanup = false;
|
||||
|
||||
if (rootEslintConfig) {
|
||||
// We do not want to mix the formats
|
||||
const fileExtension = extname(rootEslintConfig);
|
||||
if (fileExtension === '.mjs' || fileExtension === '.cjs') {
|
||||
eslintConfigFormat = fileExtension.slice(1) as 'mjs' | 'cjs';
|
||||
} else {
|
||||
eslintConfigFormat = determineEslintConfigFormat(
|
||||
tree.read(rootEslintConfig, 'utf-8')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
rootEslintConfig?.match(/\.base\./) &&
|
||||
!projects.some((p) => p.root === '.')
|
||||
@ -57,10 +75,10 @@ export function migrateConfigToMonorepoStyle(
|
||||
keepExistingVersions
|
||||
);
|
||||
tree.write(
|
||||
tree.exists('eslint.config.cjs')
|
||||
? 'eslint.base.config.cjs'
|
||||
: 'eslint.config.cjs',
|
||||
getGlobalFlatEslintConfiguration()
|
||||
tree.exists(`eslint.config.${eslintConfigFormat}`)
|
||||
? `eslint.base.config.${eslintConfigFormat}`
|
||||
: `eslint.config.${eslintConfigFormat}`,
|
||||
getGlobalFlatEslintConfiguration(eslintConfigFormat)
|
||||
);
|
||||
} else {
|
||||
const eslintFile = findEslintFile(tree, '.');
|
||||
@ -134,7 +152,9 @@ function migrateEslintFile(projectEslintPath: string, tree: Tree) {
|
||||
let config = tree.read(projectEslintPath, 'utf-8');
|
||||
// remove @nx plugin
|
||||
config = removePlugin(config, '@nx', '@nx/eslint-plugin-nx');
|
||||
// extend eslint.base.config.cjs
|
||||
|
||||
// if base config is cjs, we will need to import it using async import
|
||||
|
||||
config = addImportToFlatConfig(
|
||||
config,
|
||||
'baseConfig',
|
||||
|
||||
@ -102,43 +102,103 @@ describe('@nx/eslint:init', () => {
|
||||
});
|
||||
|
||||
describe('(legacy)', () => {
|
||||
it('should add the root eslint config to the lint targetDefaults for lint', async () => {
|
||||
await lintInitGenerator(tree, { ...options, addPlugin: false });
|
||||
describe('CJS', () => {
|
||||
it('should add the root eslint config to the lint targetDefaults for lint', async () => {
|
||||
await lintInitGenerator(tree, {
|
||||
...options,
|
||||
addPlugin: false,
|
||||
eslintConfigFormat: 'cjs',
|
||||
});
|
||||
|
||||
expect(
|
||||
readJson(tree, 'nx.json').targetDefaults['@nx/eslint:lint']
|
||||
).toEqual({
|
||||
cache: true,
|
||||
inputs: [
|
||||
'default',
|
||||
'{workspaceRoot}/.eslintrc.json',
|
||||
'{workspaceRoot}/.eslintignore',
|
||||
'{workspaceRoot}/eslint.config.cjs',
|
||||
],
|
||||
expect(
|
||||
readJson(tree, 'nx.json').targetDefaults['@nx/eslint:lint']
|
||||
).toEqual({
|
||||
cache: true,
|
||||
inputs: [
|
||||
'default',
|
||||
'{workspaceRoot}/.eslintrc.json',
|
||||
'{workspaceRoot}/.eslintignore',
|
||||
'{workspaceRoot}/eslint.config.cjs',
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should setup lint target defaults', async () => {
|
||||
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
|
||||
json.namedInputs ??= {};
|
||||
json.namedInputs.production = ['default'];
|
||||
return json;
|
||||
});
|
||||
|
||||
await lintInitGenerator(tree, {
|
||||
...options,
|
||||
addPlugin: false,
|
||||
eslintConfigFormat: 'cjs',
|
||||
});
|
||||
|
||||
expect(
|
||||
readJson<NxJsonConfiguration>(tree, 'nx.json').targetDefaults[
|
||||
'@nx/eslint:lint'
|
||||
]
|
||||
).toEqual({
|
||||
cache: true,
|
||||
inputs: [
|
||||
'default',
|
||||
'{workspaceRoot}/.eslintrc.json',
|
||||
'{workspaceRoot}/.eslintignore',
|
||||
'{workspaceRoot}/eslint.config.cjs',
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should setup lint target defaults', async () => {
|
||||
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
|
||||
json.namedInputs ??= {};
|
||||
json.namedInputs.production = ['default'];
|
||||
return json;
|
||||
describe('MJS', () => {
|
||||
it('should add the root eslint config to the lint targetDefaults for lint', async () => {
|
||||
await lintInitGenerator(tree, {
|
||||
...options,
|
||||
addPlugin: false,
|
||||
eslintConfigFormat: 'mjs',
|
||||
});
|
||||
|
||||
expect(
|
||||
readJson(tree, 'nx.json').targetDefaults['@nx/eslint:lint']
|
||||
).toEqual({
|
||||
cache: true,
|
||||
inputs: [
|
||||
'default',
|
||||
'{workspaceRoot}/.eslintrc.json',
|
||||
'{workspaceRoot}/.eslintignore',
|
||||
'{workspaceRoot}/eslint.config.mjs',
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
await lintInitGenerator(tree, { ...options, addPlugin: false });
|
||||
it('should setup lint target defaults', async () => {
|
||||
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
|
||||
json.namedInputs ??= {};
|
||||
json.namedInputs.production = ['default'];
|
||||
return json;
|
||||
});
|
||||
|
||||
expect(
|
||||
readJson<NxJsonConfiguration>(tree, 'nx.json').targetDefaults[
|
||||
'@nx/eslint:lint'
|
||||
]
|
||||
).toEqual({
|
||||
cache: true,
|
||||
inputs: [
|
||||
'default',
|
||||
'{workspaceRoot}/.eslintrc.json',
|
||||
'{workspaceRoot}/.eslintignore',
|
||||
'{workspaceRoot}/eslint.config.cjs',
|
||||
],
|
||||
await lintInitGenerator(tree, {
|
||||
...options,
|
||||
addPlugin: false,
|
||||
eslintConfigFormat: 'mjs',
|
||||
});
|
||||
|
||||
expect(
|
||||
readJson<NxJsonConfiguration>(tree, 'nx.json').targetDefaults[
|
||||
'@nx/eslint:lint'
|
||||
]
|
||||
).toEqual({
|
||||
cache: true,
|
||||
inputs: [
|
||||
'default',
|
||||
'{workspaceRoot}/.eslintrc.json',
|
||||
'{workspaceRoot}/.eslintignore',
|
||||
'{workspaceRoot}/eslint.config.mjs',
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -11,31 +11,37 @@ import {
|
||||
} from '@nx/devkit';
|
||||
import { addPlugin } from '@nx/devkit/src/utils/add-plugin';
|
||||
import { eslintVersion, nxVersion } from '../../utils/versions';
|
||||
import { findEslintFile } from '../utils/eslint-file';
|
||||
import {
|
||||
determineEslintConfigFormat,
|
||||
findEslintFile,
|
||||
} from '../utils/eslint-file';
|
||||
import { createNodesV2 } from '../../plugins/plugin';
|
||||
import { hasEslintPlugin } from '../utils/plugin';
|
||||
import { extname } from 'path';
|
||||
|
||||
export interface LinterInitOptions {
|
||||
skipPackageJson?: boolean;
|
||||
keepExistingVersions?: boolean;
|
||||
updatePackageScripts?: boolean;
|
||||
addPlugin?: boolean;
|
||||
// Internal option
|
||||
eslintConfigFormat?: 'mjs' | 'cjs';
|
||||
}
|
||||
|
||||
function updateProductionFileset(tree: Tree) {
|
||||
function updateProductionFileset(tree: Tree, format: 'mjs' | 'cjs' = 'mjs') {
|
||||
const nxJson = readNxJson(tree);
|
||||
|
||||
const productionFileSet = nxJson.namedInputs?.production;
|
||||
if (productionFileSet) {
|
||||
productionFileSet.push('!{projectRoot}/.eslintrc.json');
|
||||
productionFileSet.push('!{projectRoot}/eslint.config.cjs');
|
||||
productionFileSet.push(`!{projectRoot}/eslint.config.${format}`);
|
||||
// Dedupe and set
|
||||
nxJson.namedInputs.production = Array.from(new Set(productionFileSet));
|
||||
}
|
||||
updateNxJson(tree, nxJson);
|
||||
}
|
||||
|
||||
function addTargetDefaults(tree: Tree) {
|
||||
function addTargetDefaults(tree: Tree, format: 'mjs' | 'cjs') {
|
||||
const nxJson = readNxJson(tree);
|
||||
|
||||
nxJson.targetDefaults ??= {};
|
||||
@ -45,7 +51,7 @@ function addTargetDefaults(tree: Tree) {
|
||||
'default',
|
||||
`{workspaceRoot}/.eslintrc.json`,
|
||||
`{workspaceRoot}/.eslintignore`,
|
||||
`{workspaceRoot}/eslint.config.cjs`,
|
||||
`{workspaceRoot}/eslint.config.${format}`,
|
||||
];
|
||||
updateNxJson(tree, nxJson);
|
||||
}
|
||||
@ -74,9 +80,21 @@ export async function initEsLint(
|
||||
process.env.NX_ADD_PLUGINS !== 'false' &&
|
||||
nxJson.useInferencePlugins !== false;
|
||||
options.addPlugin ??= addPluginDefault;
|
||||
options.eslintConfigFormat ??= 'mjs';
|
||||
const hasPlugin = hasEslintPlugin(tree);
|
||||
const rootEslintFile = findEslintFile(tree);
|
||||
|
||||
if (rootEslintFile) {
|
||||
const fileExtension = extname(rootEslintFile);
|
||||
if (fileExtension === '.mjs' || fileExtension === '.cjs') {
|
||||
options.eslintConfigFormat = fileExtension.slice(1) as 'mjs' | 'cjs';
|
||||
} else {
|
||||
options.eslintConfigFormat = determineEslintConfigFormat(
|
||||
tree.read(rootEslintFile, 'utf-8')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const graph = await createProjectGraphAsync();
|
||||
|
||||
const lintTargetNames = [
|
||||
@ -107,7 +125,7 @@ export async function initEsLint(
|
||||
return () => {};
|
||||
}
|
||||
|
||||
updateProductionFileset(tree);
|
||||
updateProductionFileset(tree, options.eslintConfigFormat);
|
||||
|
||||
updateVsCodeRecommendedExtensions(tree);
|
||||
|
||||
@ -123,7 +141,7 @@ export async function initEsLint(
|
||||
options.updatePackageScripts
|
||||
);
|
||||
} else {
|
||||
addTargetDefaults(tree);
|
||||
addTargetDefaults(tree, options.eslintConfigFormat);
|
||||
}
|
||||
|
||||
const tasks: GeneratorCallback[] = [];
|
||||
|
||||
@ -42,7 +42,237 @@ describe('@nx/eslint:lint-project', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate a flat eslint base config', async () => {
|
||||
describe('Eslint base config named eslint.base.config', () => {
|
||||
it('should generate a flat eslint config format based on base config (JS with CJS export)', async () => {
|
||||
const originalEslintUseFlatConfigVal = process.env.ESLINT_USE_FLAT_CONFIG;
|
||||
process.env.ESLINT_USE_FLAT_CONFIG = 'true';
|
||||
|
||||
// CJS config
|
||||
tree.write('eslint.base.config.js', 'module.exports = {};');
|
||||
|
||||
await lintProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
linter: Linter.EsLint,
|
||||
project: 'test-lib',
|
||||
setParserOptionsProject: false,
|
||||
skipFormat: true,
|
||||
});
|
||||
|
||||
expect(tree.read('libs/test-lib/eslint.config.cjs', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"const baseConfig = require("../../eslint.base.config.js");
|
||||
|
||||
module.exports = [
|
||||
...baseConfig
|
||||
];
|
||||
"
|
||||
`);
|
||||
|
||||
process.env.ESLINT_USE_FLAT_CONFIG = originalEslintUseFlatConfigVal;
|
||||
});
|
||||
|
||||
it('should generate a flat eslint config format based on base config (JS with MJS export)', async () => {
|
||||
const originalEslintUseFlatConfigVal = process.env.ESLINT_USE_FLAT_CONFIG;
|
||||
process.env.ESLINT_USE_FLAT_CONFIG = 'true';
|
||||
|
||||
// MJS config
|
||||
tree.write('eslint.base.config.js', 'export default {};');
|
||||
|
||||
await lintProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
linter: Linter.EsLint,
|
||||
project: 'test-lib',
|
||||
setParserOptionsProject: false,
|
||||
skipFormat: true,
|
||||
});
|
||||
|
||||
expect(tree.read('libs/test-lib/eslint.config.mjs', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import baseConfig from "../../eslint.base.config.js";
|
||||
|
||||
export default [
|
||||
...baseConfig
|
||||
];
|
||||
"
|
||||
`);
|
||||
|
||||
process.env.ESLINT_USE_FLAT_CONFIG = originalEslintUseFlatConfigVal;
|
||||
});
|
||||
|
||||
it('should generate a flat eslint config format based on base config (mjs)', async () => {
|
||||
const originalEslintUseFlatConfigVal = process.env.ESLINT_USE_FLAT_CONFIG;
|
||||
process.env.ESLINT_USE_FLAT_CONFIG = 'true';
|
||||
|
||||
// MJS config
|
||||
tree.write('eslint.base.config.mjs', 'export default {};');
|
||||
|
||||
await lintProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
linter: Linter.EsLint,
|
||||
project: 'test-lib',
|
||||
setParserOptionsProject: false,
|
||||
skipFormat: true,
|
||||
eslintConfigFormat: 'mjs',
|
||||
});
|
||||
|
||||
expect(tree.read('libs/test-lib/eslint.config.mjs', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import baseConfig from "../../eslint.base.config.mjs";
|
||||
|
||||
export default [
|
||||
...baseConfig
|
||||
];
|
||||
"
|
||||
`);
|
||||
|
||||
process.env.ESLINT_USE_FLAT_CONFIG = originalEslintUseFlatConfigVal;
|
||||
});
|
||||
|
||||
it('should generate a flat eslint config format based on base config CJS', async () => {
|
||||
const originalEslintUseFlatConfigVal = process.env.ESLINT_USE_FLAT_CONFIG;
|
||||
process.env.ESLINT_USE_FLAT_CONFIG = 'true';
|
||||
|
||||
// CJS config
|
||||
tree.write('eslint.base.config.cjs', 'module.exports = {};');
|
||||
|
||||
await lintProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
linter: Linter.EsLint,
|
||||
project: 'test-lib',
|
||||
setParserOptionsProject: false,
|
||||
skipFormat: true,
|
||||
});
|
||||
|
||||
expect(tree.read('libs/test-lib/eslint.config.cjs', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"const baseConfig = require("../../eslint.base.config.cjs");
|
||||
|
||||
module.exports = [
|
||||
...baseConfig
|
||||
];
|
||||
"
|
||||
`);
|
||||
|
||||
process.env.ESLINT_USE_FLAT_CONFIG = originalEslintUseFlatConfigVal;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Eslint base config named eslint.config', () => {
|
||||
it('should generate a flat eslint config format based on base config (JS with CJS export)', async () => {
|
||||
const originalEslintUseFlatConfigVal = process.env.ESLINT_USE_FLAT_CONFIG;
|
||||
process.env.ESLINT_USE_FLAT_CONFIG = 'true';
|
||||
|
||||
// CJS config
|
||||
tree.write('eslint.config.js', 'module.exports = {};');
|
||||
|
||||
await lintProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
linter: Linter.EsLint,
|
||||
project: 'test-lib',
|
||||
setParserOptionsProject: false,
|
||||
skipFormat: true,
|
||||
});
|
||||
|
||||
expect(tree.read('libs/test-lib/eslint.config.cjs', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"const baseConfig = require("../../eslint.config.js");
|
||||
|
||||
module.exports = [
|
||||
...baseConfig
|
||||
];
|
||||
"
|
||||
`);
|
||||
|
||||
process.env.ESLINT_USE_FLAT_CONFIG = originalEslintUseFlatConfigVal;
|
||||
});
|
||||
|
||||
it('should generate a flat eslint config format based on base config (JS with MJS export)', async () => {
|
||||
const originalEslintUseFlatConfigVal = process.env.ESLINT_USE_FLAT_CONFIG;
|
||||
process.env.ESLINT_USE_FLAT_CONFIG = 'true';
|
||||
|
||||
// MJS config
|
||||
tree.write('eslint.config.js', 'export default {};');
|
||||
|
||||
await lintProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
linter: Linter.EsLint,
|
||||
project: 'test-lib',
|
||||
setParserOptionsProject: false,
|
||||
skipFormat: true,
|
||||
});
|
||||
|
||||
expect(tree.read('libs/test-lib/eslint.config.mjs', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import baseConfig from "../../eslint.config.js";
|
||||
|
||||
export default [
|
||||
...baseConfig
|
||||
];
|
||||
"
|
||||
`);
|
||||
|
||||
process.env.ESLINT_USE_FLAT_CONFIG = originalEslintUseFlatConfigVal;
|
||||
});
|
||||
|
||||
it('should generate a flat eslint config format based on base config (mjs)', async () => {
|
||||
const originalEslintUseFlatConfigVal = process.env.ESLINT_USE_FLAT_CONFIG;
|
||||
process.env.ESLINT_USE_FLAT_CONFIG = 'true';
|
||||
|
||||
// MJS config
|
||||
tree.write('eslint.config.mjs', 'export default {};');
|
||||
|
||||
await lintProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
linter: Linter.EsLint,
|
||||
project: 'test-lib',
|
||||
setParserOptionsProject: false,
|
||||
skipFormat: true,
|
||||
eslintConfigFormat: 'mjs',
|
||||
});
|
||||
|
||||
expect(tree.read('libs/test-lib/eslint.config.mjs', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import baseConfig from "../../eslint.config.mjs";
|
||||
|
||||
export default [
|
||||
...baseConfig
|
||||
];
|
||||
"
|
||||
`);
|
||||
|
||||
process.env.ESLINT_USE_FLAT_CONFIG = originalEslintUseFlatConfigVal;
|
||||
});
|
||||
|
||||
it('should generate a flat eslint config format based on base config CJS', async () => {
|
||||
const originalEslintUseFlatConfigVal = process.env.ESLINT_USE_FLAT_CONFIG;
|
||||
process.env.ESLINT_USE_FLAT_CONFIG = 'true';
|
||||
|
||||
// CJS config
|
||||
tree.write('eslint.config.cjs', 'module.exports = {};');
|
||||
|
||||
await lintProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
linter: Linter.EsLint,
|
||||
project: 'test-lib',
|
||||
setParserOptionsProject: false,
|
||||
skipFormat: true,
|
||||
});
|
||||
|
||||
expect(tree.read('libs/test-lib/eslint.config.cjs', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"const baseConfig = require("../../eslint.config.cjs");
|
||||
|
||||
module.exports = [
|
||||
...baseConfig
|
||||
];
|
||||
"
|
||||
`);
|
||||
|
||||
process.env.ESLINT_USE_FLAT_CONFIG = originalEslintUseFlatConfigVal;
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate a flat eslint base config ESM', async () => {
|
||||
const originalEslintUseFlatConfigVal = process.env.ESLINT_USE_FLAT_CONFIG;
|
||||
process.env.ESLINT_USE_FLAT_CONFIG = 'true';
|
||||
await lintProjectGenerator(tree, {
|
||||
@ -51,6 +281,76 @@ describe('@nx/eslint:lint-project', () => {
|
||||
project: 'test-lib',
|
||||
setParserOptionsProject: false,
|
||||
skipFormat: true,
|
||||
eslintConfigFormat: 'mjs',
|
||||
});
|
||||
|
||||
expect(tree.read('eslint.config.mjs', 'utf-8')).toMatchInlineSnapshot(`
|
||||
"import nx from "@nx/eslint-plugin";
|
||||
|
||||
export default [
|
||||
...nx.configs["flat/base"],
|
||||
...nx.configs["flat/typescript"],
|
||||
...nx.configs["flat/javascript"],
|
||||
{
|
||||
ignores: [
|
||||
"**/dist"
|
||||
]
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.js",
|
||||
"**/*.jsx"
|
||||
],
|
||||
rules: {
|
||||
"@nx/enforce-module-boundaries": [
|
||||
"error",
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: [
|
||||
"^.*/eslint(\\\\.base)?\\\\.config\\\\.[cm]?js$"
|
||||
],
|
||||
depConstraints: [
|
||||
{
|
||||
sourceTag: "*",
|
||||
onlyDependOnLibsWithTags: [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.js",
|
||||
"**/*.jsx",
|
||||
"**/*.cjs",
|
||||
"**/*.mjs"
|
||||
],
|
||||
// Override or add rules here
|
||||
rules: {}
|
||||
}
|
||||
];
|
||||
"
|
||||
`);
|
||||
process.env.ESLINT_USE_FLAT_CONFIG = originalEslintUseFlatConfigVal;
|
||||
});
|
||||
|
||||
it('should generate a flat eslint base config CJS', async () => {
|
||||
const originalEslintUseFlatConfigVal = process.env.ESLINT_USE_FLAT_CONFIG;
|
||||
process.env.ESLINT_USE_FLAT_CONFIG = 'true';
|
||||
await lintProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
linter: Linter.EsLint,
|
||||
project: 'test-lib',
|
||||
setParserOptionsProject: false,
|
||||
skipFormat: true,
|
||||
eslintConfigFormat: 'cjs',
|
||||
});
|
||||
|
||||
expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchInlineSnapshot(`
|
||||
|
||||
@ -17,8 +17,11 @@ import {
|
||||
} from '@nx/devkit';
|
||||
|
||||
import { Linter as LinterEnum, LinterType } from '../utils/linter';
|
||||
import { findEslintFile } from '../utils/eslint-file';
|
||||
import { join } from 'path';
|
||||
import {
|
||||
determineEslintConfigFormat,
|
||||
findEslintFile,
|
||||
} from '../utils/eslint-file';
|
||||
import { extname, join } from 'path';
|
||||
import { lintInitGenerator } from '../init/init';
|
||||
import type { Linter } from 'eslint';
|
||||
import { migrateConfigToMonorepoStyle } from '../init/init-migration';
|
||||
@ -32,7 +35,7 @@ import {
|
||||
} from '../utils/flat-config/ast-utils';
|
||||
import {
|
||||
baseEsLintConfigFile,
|
||||
baseEsLintFlatConfigFile,
|
||||
BASE_ESLINT_CONFIG_FILENAMES,
|
||||
} from '../../utils/config-file';
|
||||
import { hasEslintPlugin } from '../utils/plugin';
|
||||
import { setupRootEsLint } from './setup-root-eslint';
|
||||
@ -49,6 +52,7 @@ interface LintProjectOptions {
|
||||
rootProject?: boolean;
|
||||
keepExistingVersions?: boolean;
|
||||
addPlugin?: boolean;
|
||||
eslintConfigFormat?: 'mjs' | 'cjs';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -66,6 +70,7 @@ export async function lintProjectGeneratorInternal(
|
||||
options: LintProjectOptions
|
||||
) {
|
||||
const nxJson = readNxJson(tree);
|
||||
options.eslintConfigFormat ??= 'mjs';
|
||||
const addPluginDefault =
|
||||
process.env.NX_ADD_PLUGINS !== 'false' &&
|
||||
nxJson.useInferencePlugins !== false;
|
||||
@ -74,12 +79,14 @@ export async function lintProjectGeneratorInternal(
|
||||
const initTask = await lintInitGenerator(tree, {
|
||||
skipPackageJson: options.skipPackageJson,
|
||||
addPlugin: options.addPlugin,
|
||||
eslintConfigFormat: options.eslintConfigFormat,
|
||||
});
|
||||
tasks.push(initTask);
|
||||
const rootEsLintTask = setupRootEsLint(tree, {
|
||||
unitTestRunner: options.unitTestRunner,
|
||||
skipPackageJson: options.skipPackageJson,
|
||||
rootProject: options.rootProject,
|
||||
eslintConfigFormat: options.eslintConfigFormat,
|
||||
});
|
||||
tasks.push(rootEsLintTask);
|
||||
const projectConfig = readProjectConfiguration(tree, options.project);
|
||||
@ -146,6 +153,7 @@ export async function lintProjectGeneratorInternal(
|
||||
filteredProjects,
|
||||
tree,
|
||||
options.unitTestRunner,
|
||||
options.eslintConfigFormat,
|
||||
options.keepExistingVersions
|
||||
);
|
||||
tasks.push(migrateTask);
|
||||
@ -199,6 +207,22 @@ function createEsLintConfiguration(
|
||||
const pathToRootConfig = extendedRootConfig
|
||||
? `${offsetFromRoot(projectConfig.root)}${extendedRootConfig}`
|
||||
: undefined;
|
||||
|
||||
if (extendedRootConfig) {
|
||||
// We do not want to mix the formats
|
||||
// if the base file extension is `.mjs` we should use `mjs` for the new file
|
||||
// or if base the file extension is `.cjs` then the format should be `cjs`
|
||||
|
||||
const fileExtension = extname(extendedRootConfig);
|
||||
if (fileExtension === '.mjs' || fileExtension === '.cjs') {
|
||||
options.eslintConfigFormat = fileExtension.slice(1) as 'mjs' | 'cjs';
|
||||
} else {
|
||||
options.eslintConfigFormat = determineEslintConfigFormat(
|
||||
tree.read(extendedRootConfig, 'utf-8')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const addDependencyChecks =
|
||||
options.addPackageJsonDependencyChecks ||
|
||||
isBuildableLibraryProject(projectConfig);
|
||||
@ -269,11 +293,18 @@ function createEsLintConfiguration(
|
||||
nodes.push(generateSpreadElement('baseConfig'));
|
||||
}
|
||||
overrides.forEach((override) => {
|
||||
nodes.push(generateFlatOverride(override));
|
||||
nodes.push(generateFlatOverride(override, options.eslintConfigFormat));
|
||||
});
|
||||
const nodeList = createNodeList(importMap, nodes);
|
||||
const nodeList = createNodeList(
|
||||
importMap,
|
||||
nodes,
|
||||
options.eslintConfigFormat
|
||||
);
|
||||
const content = stringifyNodeList(nodeList);
|
||||
tree.write(join(projectConfig.root, `eslint.config.cjs`), content);
|
||||
tree.write(
|
||||
join(projectConfig.root, `eslint.config.${options.eslintConfigFormat}`),
|
||||
content
|
||||
);
|
||||
} else {
|
||||
writeJson(tree, join(projectConfig.root, `.eslintrc.json`), {
|
||||
extends: extendedRootConfig ? [pathToRootConfig] : undefined,
|
||||
@ -313,8 +344,9 @@ function isBuildableLibraryProject(
|
||||
function isMigrationToMonorepoNeeded(tree: Tree, graph: ProjectGraph): boolean {
|
||||
// the base config is already created, migration has been done
|
||||
if (
|
||||
tree.exists(baseEsLintConfigFile) ||
|
||||
tree.exists(baseEsLintFlatConfigFile)
|
||||
[baseEsLintConfigFile, ...BASE_ESLINT_CONFIG_FILENAMES].some((f) =>
|
||||
tree.exists(f)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ export type SetupRootEsLintOptions = {
|
||||
unitTestRunner?: string;
|
||||
skipPackageJson?: boolean;
|
||||
rootProject?: boolean;
|
||||
eslintConfigFormat?: 'mjs' | 'cjs';
|
||||
};
|
||||
|
||||
export function setupRootEsLint(
|
||||
@ -32,6 +33,8 @@ export function setupRootEsLint(
|
||||
if (rootEslintFile) {
|
||||
return () => {};
|
||||
}
|
||||
options.eslintConfigFormat ??= 'mjs';
|
||||
|
||||
if (!useFlatConfig(tree)) {
|
||||
return setUpLegacyRootEslintRc(tree, options);
|
||||
}
|
||||
@ -71,8 +74,11 @@ function setUpLegacyRootEslintRc(tree: Tree, options: SetupRootEsLintOptions) {
|
||||
|
||||
function setUpRootFlatConfig(tree: Tree, options: SetupRootEsLintOptions) {
|
||||
tree.write(
|
||||
'eslint.config.cjs',
|
||||
getGlobalFlatEslintConfiguration(options.rootProject)
|
||||
`eslint.config.${options.eslintConfigFormat}`,
|
||||
getGlobalFlatEslintConfiguration(
|
||||
options.eslintConfigFormat,
|
||||
options.rootProject
|
||||
)
|
||||
);
|
||||
|
||||
return !options.skipPackageJson
|
||||
|
||||
@ -2,8 +2,8 @@ import { readJson, type Tree } from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import * as devkitInternals from 'nx/src/devkit-internals';
|
||||
import {
|
||||
BASE_ESLINT_CONFIG_FILENAMES,
|
||||
ESLINT_CONFIG_FILENAMES,
|
||||
baseEsLintConfigFile,
|
||||
} from '../../utils/config-file';
|
||||
import {
|
||||
addExtendsToLintConfig,
|
||||
@ -32,12 +32,11 @@ describe('@nx/eslint:lint-file', () => {
|
||||
}
|
||||
);
|
||||
|
||||
test.each(ESLINT_CONFIG_FILENAMES)(
|
||||
'should return base file instead %p when calling findEslintFile',
|
||||
test.each(BASE_ESLINT_CONFIG_FILENAMES)(
|
||||
'should return base file %p when calling findEslintFile',
|
||||
(eslintFileName) => {
|
||||
tree.write(baseEsLintConfigFile, '{}');
|
||||
tree.write(eslintFileName, '{}');
|
||||
expect(findEslintFile(tree)).toBe(baseEsLintConfigFile);
|
||||
expect(findEslintFile(tree)).toBe(eslintFileName);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@ -12,9 +12,8 @@ import type { Linter } from 'eslint';
|
||||
import { gte } from 'semver';
|
||||
import {
|
||||
baseEsLintConfigFile,
|
||||
baseEsLintFlatConfigFile,
|
||||
ESLINT_CONFIG_FILENAMES,
|
||||
legacyBaseEsLintFlatConfigFile,
|
||||
BASE_ESLINT_CONFIG_FILENAMES,
|
||||
} from '../../utils/config-file';
|
||||
import {
|
||||
eslintFlatConfigFilenames,
|
||||
@ -45,17 +44,15 @@ export function findEslintFile(
|
||||
tree: Tree,
|
||||
projectRoot?: string
|
||||
): string | null {
|
||||
if (projectRoot === undefined && tree.exists(baseEsLintConfigFile)) {
|
||||
return baseEsLintConfigFile;
|
||||
}
|
||||
if (projectRoot === undefined && tree.exists(baseEsLintFlatConfigFile)) {
|
||||
return baseEsLintFlatConfigFile;
|
||||
}
|
||||
if (
|
||||
projectRoot === undefined &&
|
||||
tree.exists(legacyBaseEsLintFlatConfigFile)
|
||||
) {
|
||||
return legacyBaseEsLintFlatConfigFile;
|
||||
if (projectRoot === undefined) {
|
||||
for (const file of [
|
||||
baseEsLintConfigFile,
|
||||
...BASE_ESLINT_CONFIG_FILENAMES,
|
||||
]) {
|
||||
if (tree.exists(file)) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
projectRoot ??= '';
|
||||
for (const file of ESLINT_CONFIG_FILENAMES) {
|
||||
@ -75,7 +72,8 @@ export function isEslintConfigSupported(tree: Tree, projectRoot = ''): boolean {
|
||||
return (
|
||||
eslintFile.endsWith('.json') ||
|
||||
eslintFile.endsWith('.config.js') ||
|
||||
eslintFile.endsWith('.config.cjs')
|
||||
eslintFile.endsWith('.config.cjs') ||
|
||||
eslintFile.endsWith('.config.mjs')
|
||||
);
|
||||
}
|
||||
|
||||
@ -148,6 +146,19 @@ function replaceFlatConfigPaths(
|
||||
`require('${newPath}')` +
|
||||
newConfig.slice(match.index + match[0].length);
|
||||
}
|
||||
|
||||
// Handle import statements
|
||||
const importRegex = RegExp(/import\s+.*?\s+from\s+['"](.*)['"]/g);
|
||||
while ((match = importRegex.exec(newConfig)) !== null) {
|
||||
const oldPath = match[1];
|
||||
const newPath = offsetFilePath(sourceRoot, oldPath, offset, tree);
|
||||
|
||||
// Replace the old path with the updated path
|
||||
newConfig =
|
||||
newConfig.slice(0, match.index + match[0].indexOf(oldPath)) +
|
||||
newPath +
|
||||
newConfig.slice(match.index + match[0].indexOf(oldPath) + oldPath.length);
|
||||
}
|
||||
// replace projects
|
||||
const projectRegex = RegExp(/project:\s?\[?['"](.*)['"]\]?/g);
|
||||
while ((match = projectRegex.exec(newConfig)) !== null) {
|
||||
@ -184,6 +195,22 @@ function offsetFilePath(
|
||||
return joinPathFragments(offset, projectRoot, pathToFile);
|
||||
}
|
||||
|
||||
export function determineEslintConfigFormat(content: string): 'mjs' | 'cjs' {
|
||||
const sourceFile = ts.createSourceFile(
|
||||
'',
|
||||
content,
|
||||
ts.ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
|
||||
// Check if there's an `export default` in the AST
|
||||
const hasExportDefault = sourceFile.statements.some(
|
||||
(statement) => ts.isExportAssignment(statement) && !statement.isExportEquals
|
||||
);
|
||||
|
||||
return hasExportDefault ? 'mjs' : 'cjs';
|
||||
}
|
||||
|
||||
export function addOverrideToLintConfig(
|
||||
tree: Tree,
|
||||
root: string,
|
||||
@ -197,7 +224,12 @@ export function addOverrideToLintConfig(
|
||||
if (useFlatConfig(tree)) {
|
||||
let fileName: string;
|
||||
if (isBase) {
|
||||
fileName = joinPathFragments(root, baseEsLintFlatConfigFile);
|
||||
for (const file of BASE_ESLINT_CONFIG_FILENAMES) {
|
||||
if (tree.exists(joinPathFragments(root, file))) {
|
||||
fileName = joinPathFragments(root, file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const f of eslintFlatConfigFilenames) {
|
||||
if (tree.exists(joinPathFragments(root, f))) {
|
||||
@ -207,8 +239,10 @@ export function addOverrideToLintConfig(
|
||||
}
|
||||
}
|
||||
|
||||
const flatOverride = generateFlatOverride(override);
|
||||
let content = tree.read(fileName, 'utf8');
|
||||
const format = content.includes('export default') ? 'mjs' : 'cjs';
|
||||
|
||||
const flatOverride = generateFlatOverride(override, format);
|
||||
// Check if the provided override using legacy eslintrc properties or plugins, if so we need to add compat
|
||||
if (overrideNeedsCompat(override)) {
|
||||
content = addFlatCompatToFlatConfig(content);
|
||||
@ -306,7 +340,12 @@ export function lintConfigHasOverride(
|
||||
checkBaseConfig &&
|
||||
findEslintFile(tree, root).includes('.base');
|
||||
if (isBase) {
|
||||
fileName = joinPathFragments(root, baseEsLintFlatConfigFile);
|
||||
for (const file of BASE_ESLINT_CONFIG_FILENAMES) {
|
||||
if (tree.exists(joinPathFragments(root, file))) {
|
||||
fileName = joinPathFragments(root, file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (useFlatConfig(tree)) {
|
||||
if (!fileName) {
|
||||
@ -343,13 +382,14 @@ export function replaceOverridesInLintConfig(
|
||||
}
|
||||
}
|
||||
let content = tree.read(fileName, 'utf8');
|
||||
const format = content.includes('export default') ? 'mjs' : 'cjs';
|
||||
// Check if any of the provided overrides using legacy eslintrc properties or plugins, if so we need to add compat
|
||||
if (overrides.some(overrideNeedsCompat)) {
|
||||
content = addFlatCompatToFlatConfig(content);
|
||||
}
|
||||
content = removeOverridesFromLintConfig(content);
|
||||
overrides.forEach((override) => {
|
||||
const flatOverride = generateFlatOverride(override);
|
||||
const flatOverride = generateFlatOverride(override, format);
|
||||
content = addBlockToFlatConfigExport(content, flatOverride);
|
||||
});
|
||||
|
||||
@ -381,6 +421,14 @@ export function addExtendsToLintConfig(
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Check the file extension to determine the format of the config if it is .js we look for the export
|
||||
const eslintConfigFormat = fileName.endsWith('.mjs')
|
||||
? 'mjs'
|
||||
: fileName.endsWith('.cjs')
|
||||
? 'cjs'
|
||||
: tree.read(fileName, 'utf-8').includes('module.exports')
|
||||
? 'cjs'
|
||||
: 'mjs';
|
||||
|
||||
let shouldImportEslintCompat = false;
|
||||
// assume eslint version is 9 if not found, as it's what we'd be generating by default
|
||||
|
||||
@ -30,7 +30,7 @@ describe('ast-utils', () => {
|
||||
it('should create appropriate ASTs for a flat config entries based on the provided legacy eslintrc JSON override data', () => {
|
||||
// It's easier to review the stringified result of the AST than the AST itself
|
||||
const getOutput = (input: any) => {
|
||||
const ast = generateFlatOverride(input);
|
||||
const ast = generateFlatOverride(input, 'mjs');
|
||||
return printTsNode(ast);
|
||||
};
|
||||
|
||||
@ -87,13 +87,13 @@ describe('ast-utils', () => {
|
||||
|
||||
expect(
|
||||
getOutput({
|
||||
// It should not only nest the parser in languageOptions, but also wrap it in a require call because parsers are passed by reference in flat config
|
||||
// It should not only nest the parser in languageOptions, but also wrap it in an import call because parsers are passed by reference in flat config
|
||||
parser: 'jsonc-eslint-parser',
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
"{
|
||||
languageOptions: {
|
||||
parser: require("jsonc-eslint-parser")
|
||||
parser: await import("jsonc-eslint-parser")
|
||||
}
|
||||
}"
|
||||
`);
|
||||
@ -188,8 +188,8 @@ describe('ast-utils', () => {
|
||||
|
||||
describe('addBlockToFlatConfigExport', () => {
|
||||
it('should inject block to the end of the file', () => {
|
||||
const content = `const baseConfig = require("../../eslint.config.cjs");
|
||||
module.exports = [
|
||||
const content = `import baseConfig from "../../eslint.config.mjs";
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
@ -210,17 +210,18 @@ describe('ast-utils', () => {
|
||||
})
|
||||
);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"const baseConfig = require("../../eslint.config.cjs");
|
||||
module.exports = [
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
"my-lib/**/*.ts",
|
||||
"my-lib/**/*.tsx"
|
||||
],
|
||||
rules: {}
|
||||
},
|
||||
{ ignores: ["my-lib/.cache/**/*"] },
|
||||
"import baseConfig from "../../eslint.config.mjs";
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
"my-lib/**/*.ts",
|
||||
"my-lib/**/*.tsx"
|
||||
],
|
||||
rules: {}
|
||||
},
|
||||
{ ignores: ["my-lib/.cache/**/*"] },
|
||||
{
|
||||
files: [
|
||||
"**/*.svg"
|
||||
@ -228,14 +229,15 @@ describe('ast-utils', () => {
|
||||
rules: {
|
||||
"@nx/do-something-with-svg": "error"
|
||||
}
|
||||
},
|
||||
];"
|
||||
}
|
||||
];
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should inject spread to the beginning of the file', () => {
|
||||
const content = `const baseConfig = require("../../eslint.config.cjs");
|
||||
module.exports = [
|
||||
const content = `import baseConfig from "../../eslint.config.mjs";
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
@ -252,28 +254,29 @@ describe('ast-utils', () => {
|
||||
{ insertAtTheEnd: false }
|
||||
);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"const baseConfig = require("../../eslint.config.cjs");
|
||||
module.exports = [
|
||||
...config,
|
||||
"import baseConfig from "../../eslint.config.mjs";
|
||||
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
"my-lib/**/*.ts",
|
||||
"my-lib/**/*.tsx"
|
||||
],
|
||||
rules: {}
|
||||
},
|
||||
{ ignores: ["my-lib/.cache/**/*"] },
|
||||
];"
|
||||
export default [
|
||||
...config,
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
"my-lib/**/*.ts",
|
||||
"my-lib/**/*.tsx"
|
||||
],
|
||||
rules: {}
|
||||
},
|
||||
{ ignores: ["my-lib/.cache/**/*"] }
|
||||
];
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addImportToFlatConfig', () => {
|
||||
it('should inject import if not found', () => {
|
||||
const content = `const baseConfig = require("../../eslint.config.cjs");
|
||||
module.exports = [
|
||||
const content = `import baseConfig from "../../eslint.config.mjs";
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
@ -286,30 +289,30 @@ describe('ast-utils', () => {
|
||||
];`;
|
||||
const result = addImportToFlatConfig(
|
||||
content,
|
||||
'varName',
|
||||
['varName'],
|
||||
'@myorg/awesome-config'
|
||||
);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"const varName = require("@myorg/awesome-config");
|
||||
const baseConfig = require("../../eslint.config.cjs");
|
||||
module.exports = [
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
"my-lib/**/*.ts",
|
||||
"my-lib/**/*.tsx"
|
||||
],
|
||||
rules: {}
|
||||
},
|
||||
{ ignores: ["my-lib/.cache/**/*"] },
|
||||
];"
|
||||
`);
|
||||
"import { varName } from "@myorg/awesome-config";
|
||||
import baseConfig from "../../eslint.config.mjs";
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
"my-lib/**/*.ts",
|
||||
"my-lib/**/*.tsx"
|
||||
],
|
||||
rules: {}
|
||||
},
|
||||
{ ignores: ["my-lib/.cache/**/*"] },
|
||||
];"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should update import if already found', () => {
|
||||
const content = `const { varName } = require("@myorg/awesome-config");
|
||||
const baseConfig = require("../../eslint.config.cjs");
|
||||
module.exports = [
|
||||
const content = `import { varName } from "@myorg/awesome-config";
|
||||
import baseConfig from "../../eslint.config.mjs";
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
@ -326,26 +329,27 @@ describe('ast-utils', () => {
|
||||
'@myorg/awesome-config'
|
||||
);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"const { varName, otherName, someName } = require("@myorg/awesome-config");
|
||||
const baseConfig = require("../../eslint.config.cjs");
|
||||
module.exports = [
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
"my-lib/**/*.ts",
|
||||
"my-lib/**/*.tsx"
|
||||
],
|
||||
rules: {}
|
||||
},
|
||||
{ ignores: ["my-lib/.cache/**/*"] },
|
||||
];"
|
||||
`);
|
||||
"import { varName, otherName, someName } from "@myorg/awesome-config";
|
||||
import baseConfig from "../../eslint.config.mjs";
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
"my-lib/**/*.ts",
|
||||
"my-lib/**/*.tsx"
|
||||
],
|
||||
rules: {}
|
||||
},
|
||||
{ ignores: ["my-lib/.cache/**/*"] },
|
||||
];"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not inject import if already exists', () => {
|
||||
const content = `const { varName, otherName } = require("@myorg/awesome-config");
|
||||
const baseConfig = require("../../eslint.config.cjs");
|
||||
module.exports = [
|
||||
const content = `import { varName, otherName } from "@myorg/awesome-config";
|
||||
import baseConfig from "../../eslint.config.mjs";
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
@ -365,9 +369,10 @@ describe('ast-utils', () => {
|
||||
});
|
||||
|
||||
it('should not update import if already exists', () => {
|
||||
const content = `const varName = require("@myorg/awesome-config");
|
||||
const baseConfig = require("../../eslint.config.cjs");
|
||||
module.exports = [
|
||||
const content = `import { varName } from "@myorg/awesome-config";
|
||||
import baseConfig from "../../eslint.config.mjs";
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
@ -380,7 +385,7 @@ describe('ast-utils', () => {
|
||||
];`;
|
||||
const result = addImportToFlatConfig(
|
||||
content,
|
||||
'varName',
|
||||
['varName'],
|
||||
'@myorg/awesome-config'
|
||||
);
|
||||
expect(result).toEqual(content);
|
||||
@ -390,10 +395,11 @@ describe('ast-utils', () => {
|
||||
describe('removeImportFromFlatConfig', () => {
|
||||
it('should remove existing import from config if the var name matches', () => {
|
||||
const content = stripIndents`
|
||||
const nx = require("@nx/eslint-plugin");
|
||||
const thisShouldRemain = require("@nx/eslint-plugin");
|
||||
const playwright = require('eslint-plugin-playwright');
|
||||
module.exports = [
|
||||
import nx from "@nx/eslint-plugin";
|
||||
import thisShouldRemain from "@nx/eslint-plugin";
|
||||
import playwright from 'eslint-plugin-playwright';
|
||||
|
||||
export default [
|
||||
playwright.configs['flat/recommended'],
|
||||
];
|
||||
`;
|
||||
@ -404,9 +410,10 @@ describe('ast-utils', () => {
|
||||
);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"
|
||||
const thisShouldRemain = require("@nx/eslint-plugin");
|
||||
const playwright = require('eslint-plugin-playwright');
|
||||
module.exports = [
|
||||
import thisShouldRemain from "@nx/eslint-plugin";
|
||||
import playwright from 'eslint-plugin-playwright';
|
||||
|
||||
export default [
|
||||
playwright.configs['flat/recommended'],
|
||||
];"
|
||||
`);
|
||||
@ -415,8 +422,8 @@ describe('ast-utils', () => {
|
||||
|
||||
describe('addCompatToFlatConfig', () => {
|
||||
it('should add compat to config', () => {
|
||||
const content = `const baseConfig = require("../../eslint.config.cjs");
|
||||
module.exports = [
|
||||
const content = `import baseConfig from "../../eslint.config.mjs";
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
@ -429,15 +436,18 @@ describe('ast-utils', () => {
|
||||
];`;
|
||||
const result = addFlatCompatToFlatConfig(content);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||
const js = require("@eslint/js");
|
||||
const baseConfig = require("../../eslint.config.cjs");
|
||||
"import { FlatCompat } from "@eslint/eslintrc";
|
||||
import { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import js from "@eslint/js";
|
||||
import baseConfig from "../../eslint.config.mjs";
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
module.exports = [
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
@ -452,9 +462,10 @@ describe('ast-utils', () => {
|
||||
});
|
||||
|
||||
it('should add only partially compat to config if parts exist', () => {
|
||||
const content = `const baseConfig = require("../../eslint.config.cjs");
|
||||
const js = require("@eslint/js");
|
||||
module.exports = [
|
||||
const content = `import baseConfig from "../../eslint.config.mjs";
|
||||
import js from "@eslint/js";
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
@ -467,15 +478,19 @@ describe('ast-utils', () => {
|
||||
];`;
|
||||
const result = addFlatCompatToFlatConfig(content);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||
const baseConfig = require("../../eslint.config.cjs");
|
||||
const js = require("@eslint/js");
|
||||
"import { FlatCompat } from "@eslint/eslintrc";
|
||||
import { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import baseConfig from "../../eslint.config.mjs";
|
||||
import js from "@eslint/js";
|
||||
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
module.exports = [
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
@ -490,16 +505,18 @@ describe('ast-utils', () => {
|
||||
});
|
||||
|
||||
it('should not add compat to config if exist', () => {
|
||||
const content = `const FlatCompat = require("@eslint/eslintrc");
|
||||
const baseConfig = require("../../eslint.config.cjs");
|
||||
const js = require("@eslint/js");
|
||||
const content = `import { FlatCompat } from "@eslint/eslintrc";
|
||||
import baseConfig from "../../eslint.config.cjs";
|
||||
import js from "@eslint/js";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from 'path';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
module.exports = [
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
@ -517,16 +534,17 @@ describe('ast-utils', () => {
|
||||
|
||||
describe('removeOverridesFromLintConfig', () => {
|
||||
it('should remove all rules from config', () => {
|
||||
const content = `const FlatCompat = require("@eslint/eslintrc");
|
||||
const baseConfig = require("../../eslint.config.cjs");
|
||||
const js = require("@eslint/js");
|
||||
const content = `import { FlatCompat } from "@eslint/eslintrc";
|
||||
import js from "@eslint/js";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from 'path';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
module.exports = [
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
@ -557,26 +575,27 @@ describe('ast-utils', () => {
|
||||
];`;
|
||||
const result = removeOverridesFromLintConfig(content);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"const FlatCompat = require("@eslint/eslintrc");
|
||||
const baseConfig = require("../../eslint.config.cjs");
|
||||
const js = require("@eslint/js");
|
||||
"import { FlatCompat } from "@eslint/eslintrc";
|
||||
import js from "@eslint/js";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from 'path';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
module.exports = [
|
||||
...baseConfig,
|
||||
{ ignores: ["my-lib/.cache/**/*"] },
|
||||
];"
|
||||
`);
|
||||
export default [
|
||||
...baseConfig,
|
||||
{ ignores: ["my-lib/.cache/**/*"] },
|
||||
];"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should remove all rules from starting with first', () => {
|
||||
const content = `const baseConfig = require("../../eslint.config.cjs");
|
||||
const content = `import baseConfig from "../../eslint.config.mjs";
|
||||
|
||||
module.exports = [
|
||||
export default [
|
||||
{
|
||||
files: [
|
||||
"my-lib/**/*.ts",
|
||||
@ -605,19 +624,19 @@ describe('ast-utils', () => {
|
||||
];`;
|
||||
const result = removeOverridesFromLintConfig(content);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"const baseConfig = require("../../eslint.config.cjs");
|
||||
"import baseConfig from "../../eslint.config.mjs";
|
||||
|
||||
module.exports = [
|
||||
];"
|
||||
`);
|
||||
export default [
|
||||
];"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('replaceOverride', () => {
|
||||
it('should find and replace rules in override', () => {
|
||||
const content = `const baseConfig = require("../../eslint.config.cjs");
|
||||
const content = `import baseConfig from "../../eslint.config.mjs";
|
||||
|
||||
module.exports = [
|
||||
export default [
|
||||
{
|
||||
files: [
|
||||
"my-lib/**/*.ts",
|
||||
@ -657,9 +676,9 @@ module.exports = [
|
||||
})
|
||||
);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"const baseConfig = require("../../eslint.config.cjs");
|
||||
"import baseConfig from "../../eslint.config.mjs";
|
||||
|
||||
module.exports = [
|
||||
export default [
|
||||
{
|
||||
"files": [
|
||||
"my-lib/**/*.ts",
|
||||
@ -692,9 +711,9 @@ module.exports = [
|
||||
});
|
||||
|
||||
it('should append rules in override', () => {
|
||||
const content = `const baseConfig = require("../../eslint.config.cjs");
|
||||
const content = `import baseConfig from "../../eslint.config.mjs";
|
||||
|
||||
module.exports = [
|
||||
export default [
|
||||
{
|
||||
files: [
|
||||
"my-lib/**/*.ts",
|
||||
@ -728,9 +747,9 @@ module.exports = [
|
||||
})
|
||||
);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"const baseConfig = require("../../eslint.config.cjs");
|
||||
"import baseConfig from "../../eslint.config.mjs";
|
||||
|
||||
module.exports = [
|
||||
export default [
|
||||
{
|
||||
"files": [
|
||||
"my-lib/**/*.ts",
|
||||
@ -755,9 +774,9 @@ module.exports = [
|
||||
});
|
||||
|
||||
it('should work for compat overrides', () => {
|
||||
const content = `const baseConfig = require("../../eslint.config.cjs");
|
||||
const content = `import baseConfig from "../../eslint.config.mjs";
|
||||
|
||||
module.exports = [
|
||||
export default [
|
||||
...compat.config({ extends: ["plugin:@nx/typescript"] }).map(config => ({
|
||||
...config,
|
||||
files: [
|
||||
@ -783,9 +802,9 @@ module.exports = [
|
||||
})
|
||||
);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"const baseConfig = require("../../eslint.config.cjs");
|
||||
"import baseConfig from "../../eslint.config.mjs";
|
||||
|
||||
module.exports = [
|
||||
export default [
|
||||
...compat.config({ extends: ["plugin:@nx/typescript"] }).map(config => ({
|
||||
...config,
|
||||
"files": [
|
||||
@ -804,14 +823,17 @@ module.exports = [
|
||||
|
||||
describe('removePlugin', () => {
|
||||
it('should remove plugins from config', () => {
|
||||
const content = `const { FlatCompat } = require("@eslint/eslintrc");
|
||||
const nxEslintPlugin = require("@nx/eslint-plugin");
|
||||
const js = require("@eslint/js");
|
||||
const content = `import { FlatCompat } from "@eslint/eslintrc";
|
||||
import nxEslintPlugin from "@nx/eslint-plugin";
|
||||
import js = from ("@eslint/js");
|
||||
import { fileURLToPath} from "url";
|
||||
import { dirname } from 'path';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url));
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
module.exports = [
|
||||
export default [
|
||||
{ plugins: { "@nx": nxEslintPlugin } },
|
||||
{ ignores: ["src/ignore/to/keep.ts"] },
|
||||
{ ignores: ["something/else"] }
|
||||
@ -819,13 +841,16 @@ module.exports = [
|
||||
|
||||
const result = removePlugin(content, '@nx', '@nx/eslint-plugin');
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||
const js = require("@eslint/js");
|
||||
"import { FlatCompat } from "@eslint/eslintrc";
|
||||
import js = from ("@eslint/js");
|
||||
import { fileURLToPath} from "url";
|
||||
import { dirname } from 'path';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url));
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
module.exports = [
|
||||
export default [
|
||||
{ ignores: ["src/ignore/to/keep.ts"] },
|
||||
{ ignores: ["something/else"] }
|
||||
];"
|
||||
@ -833,15 +858,19 @@ module.exports = [
|
||||
});
|
||||
|
||||
it('should remove single plugin from config', () => {
|
||||
const content = `const { FlatCompat } = require("@eslint/eslintrc");
|
||||
const nxEslintPlugin = require("@nx/eslint-plugin");
|
||||
const otherPlugin = require("other/eslint-plugin");
|
||||
const js = require("@eslint/js");
|
||||
const content = `import { FlatCompat } from "@eslint/eslintrc";
|
||||
import nxEslintPlugin from "@nx/eslint-plugin";
|
||||
import otherPlugin from "other/eslint-plugin";
|
||||
import js from "@eslint/js";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from 'path';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
module.exports = [
|
||||
|
||||
export default [
|
||||
{ plugins: { "@nx": nxEslintPlugin, "@other": otherPlugin } },
|
||||
{ ignores: ["src/ignore/to/keep.ts"] },
|
||||
{ ignores: ["something/else"] }
|
||||
@ -849,14 +878,18 @@ module.exports = [
|
||||
|
||||
const result = removePlugin(content, '@nx', '@nx/eslint-plugin');
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||
const otherPlugin = require("other/eslint-plugin");
|
||||
const js = require("@eslint/js");
|
||||
"import { FlatCompat } from "@eslint/eslintrc";
|
||||
import otherPlugin from "other/eslint-plugin";
|
||||
import js from "@eslint/js";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from 'path';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
module.exports = [
|
||||
|
||||
export default [
|
||||
{ plugins: { "@other": otherPlugin } },
|
||||
{ ignores: ["src/ignore/to/keep.ts"] },
|
||||
{ ignores: ["something/else"] }
|
||||
@ -865,14 +898,18 @@ module.exports = [
|
||||
});
|
||||
|
||||
it('should leave other properties in config', () => {
|
||||
const content = `const { FlatCompat } = require("@eslint/eslintrc");
|
||||
const nxEslintPlugin = require("@nx/eslint-plugin");
|
||||
const js = require("@eslint/js");
|
||||
const content = `import { FlatCompat } from "@eslint/eslintrc";
|
||||
import nxEslintPlugin from "@nx/eslint-plugin";
|
||||
import js from "@eslint/js";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from 'path';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
module.exports = [
|
||||
|
||||
export default [
|
||||
{ plugins: { "@nx": nxEslintPlugin }, rules: {} },
|
||||
{ ignores: ["src/ignore/to/keep.ts"] },
|
||||
{ ignores: ["something/else"] }
|
||||
@ -880,13 +917,17 @@ module.exports = [
|
||||
|
||||
const result = removePlugin(content, '@nx', '@nx/eslint-plugin');
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||
const js = require("@eslint/js");
|
||||
"import { FlatCompat } from "@eslint/eslintrc";
|
||||
import js from "@eslint/js";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from 'path';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
module.exports = [
|
||||
|
||||
export default [
|
||||
{ rules: {} },
|
||||
{ ignores: ["src/ignore/to/keep.ts"] },
|
||||
{ ignores: ["something/else"] }
|
||||
@ -895,14 +936,18 @@ module.exports = [
|
||||
});
|
||||
|
||||
it('should remove single plugin from config array', () => {
|
||||
const content = `const { FlatCompat } = require("@eslint/eslintrc");
|
||||
const nxEslintPlugin = require("@nx/eslint-plugin");
|
||||
const js = require("@eslint/js");
|
||||
const content = `import { FlatCompat } from "@eslint/eslintrc";
|
||||
import nxEslintPlugin from "@nx/eslint-plugin";
|
||||
import js from "@eslint/js";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from 'path';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
module.exports = [
|
||||
|
||||
export default [
|
||||
{ plugins: ["@nx", "something-else"] },
|
||||
{ ignores: ["src/ignore/to/keep.ts"] },
|
||||
{ ignores: ["something/else"] }
|
||||
@ -910,13 +955,17 @@ module.exports = [
|
||||
|
||||
const result = removePlugin(content, '@nx', '@nx/eslint-plugin');
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||
const js = require("@eslint/js");
|
||||
"import { FlatCompat } from "@eslint/eslintrc";
|
||||
import js from "@eslint/js";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from 'path';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
module.exports = [
|
||||
|
||||
export default [
|
||||
{ plugins:["something-else"] },
|
||||
{ ignores: ["src/ignore/to/keep.ts"] },
|
||||
{ ignores: ["something/else"] }
|
||||
@ -925,14 +974,18 @@ module.exports = [
|
||||
});
|
||||
|
||||
it('should leave other fields in the object', () => {
|
||||
const content = `const { FlatCompat } = require("@eslint/eslintrc");
|
||||
const nxEslintPlugin = require("@nx/eslint-plugin");
|
||||
const js = require("@eslint/js");
|
||||
const content = `import { FlatCompat } from "@eslint/eslintrc";
|
||||
import nxEslintPlugin from "@nx/eslint-plugin";
|
||||
import js from "@eslint/js";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from 'path';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
module.exports = [
|
||||
|
||||
export default [
|
||||
{ plugins: ["@nx"], rules: { } },
|
||||
{ ignores: ["src/ignore/to/keep.ts"] },
|
||||
{ ignores: ["something/else"] }
|
||||
@ -940,13 +993,17 @@ module.exports = [
|
||||
|
||||
const result = removePlugin(content, '@nx', '@nx/eslint-plugin');
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||
const js = require("@eslint/js");
|
||||
"import { FlatCompat } from "@eslint/eslintrc";
|
||||
import js from "@eslint/js";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from 'path';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
module.exports = [
|
||||
|
||||
export default [
|
||||
{ rules: { } },
|
||||
{ ignores: ["src/ignore/to/keep.ts"] },
|
||||
{ ignores: ["something/else"] }
|
||||
@ -955,14 +1012,19 @@ module.exports = [
|
||||
});
|
||||
|
||||
it('should remove entire plugin when array with single element', () => {
|
||||
const content = `const { FlatCompat } = require("@eslint/eslintrc");
|
||||
const nxEslintPlugin = require("@nx/eslint-plugin");
|
||||
const js = require("@eslint/js");
|
||||
const content = `import { FlatCompat } from "@eslint/eslintrc";
|
||||
import nxEslintPlugin from "@nx/eslint-plugin";
|
||||
import js from "@eslint/js";
|
||||
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from 'path';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
module.exports = [
|
||||
|
||||
export default [
|
||||
{ plugins: ["@nx"] },
|
||||
{ ignores: ["src/ignore/to/keep.ts"] },
|
||||
{ ignores: ["something/else"] }
|
||||
@ -970,13 +1032,18 @@ module.exports = [
|
||||
|
||||
const result = removePlugin(content, '@nx', '@nx/eslint-plugin');
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||
const js = require("@eslint/js");
|
||||
"import { FlatCompat } from "@eslint/eslintrc";
|
||||
import js from "@eslint/js";
|
||||
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from 'path';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
module.exports = [
|
||||
|
||||
export default [
|
||||
{ ignores: ["src/ignore/to/keep.ts"] },
|
||||
{ ignores: ["something/else"] }
|
||||
];"
|
||||
@ -986,14 +1053,18 @@ module.exports = [
|
||||
|
||||
describe('removeCompatExtends', () => {
|
||||
it('should remove compat extends from config', () => {
|
||||
const content = `const { FlatCompat } = require("@eslint/eslintrc");
|
||||
const nxEslintPlugin = require("@nx/eslint-plugin");
|
||||
const js = require("@eslint/js");
|
||||
const content = `import { FlatCompat } from "@eslint/eslintrc";
|
||||
import nxEslintPlugin from "@nx/eslint-plugin";
|
||||
import js from "@eslint/js";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from "path";
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
module.exports = [
|
||||
|
||||
export default [
|
||||
{ plugins: { "@nx": nxEslintPlugin } },
|
||||
...compat.config({ extends: ["plugin:@nx/typescript"] }).map(config => ({
|
||||
...config,
|
||||
@ -1013,14 +1084,18 @@ module.exports = [
|
||||
'plugin:@nx/javascript',
|
||||
]);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||
const nxEslintPlugin = require("@nx/eslint-plugin");
|
||||
const js = require("@eslint/js");
|
||||
"import { FlatCompat } from "@eslint/eslintrc";
|
||||
import nxEslintPlugin from "@nx/eslint-plugin";
|
||||
import js from "@eslint/js";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from "path";
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
module.exports = [
|
||||
|
||||
export default [
|
||||
{ plugins: { "@nx": nxEslintPlugin } },
|
||||
{
|
||||
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
||||
@ -1039,9 +1114,10 @@ module.exports = [
|
||||
describe('removePredefinedConfigs', () => {
|
||||
it('should remove config objects and import', () => {
|
||||
const content = stripIndents`
|
||||
const nx = require("@nx/eslint-plugin");
|
||||
const playwright = require('eslint-plugin-playwright');
|
||||
module.exports = [
|
||||
import nx from "@nx/eslint-plugin";
|
||||
import playwright from 'eslint-plugin-playwright';
|
||||
|
||||
export default [
|
||||
...nx.config['flat/base'],
|
||||
...nx.config['flat/typescript'],
|
||||
...nx.config['flat/javascript'],
|
||||
@ -1058,8 +1134,9 @@ module.exports = [
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"
|
||||
const playwright = require('eslint-plugin-playwright');
|
||||
module.exports = [
|
||||
import playwright from 'eslint-plugin-playwright';
|
||||
|
||||
export default [
|
||||
playwright.configs['flat/recommended'],
|
||||
];"
|
||||
`);
|
||||
@ -1067,9 +1144,10 @@ module.exports = [
|
||||
|
||||
it('should keep configs that are not in the list', () => {
|
||||
const content = stripIndents`
|
||||
const nx = require("@nx/eslint-plugin");
|
||||
const playwright = require('eslint-plugin-playwright');
|
||||
module.exports = [
|
||||
import nx from "@nx/eslint-plugin";
|
||||
import playwright from 'eslint-plugin-playwright';
|
||||
|
||||
export default [
|
||||
...nx.config['flat/base'],
|
||||
...nx.config['flat/typescript'],
|
||||
...nx.config['flat/javascript'],
|
||||
@ -1086,9 +1164,10 @@ module.exports = [
|
||||
);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"const nx = require("@nx/eslint-plugin");
|
||||
const playwright = require('eslint-plugin-playwright');
|
||||
module.exports = [
|
||||
"import nx from "@nx/eslint-plugin";
|
||||
import playwright from 'eslint-plugin-playwright';
|
||||
|
||||
export default [
|
||||
...nx.config['flat/react'],
|
||||
playwright.configs['flat/recommended'],
|
||||
];"
|
||||
|
||||
@ -26,7 +26,10 @@ export function removeOverridesFromLintConfig(content: string): string {
|
||||
ts.ScriptKind.JS
|
||||
);
|
||||
|
||||
const exportsArray = findAllBlocks(source);
|
||||
const format = content.includes('export default') ? 'mjs' : 'cjs';
|
||||
|
||||
const exportsArray =
|
||||
format === 'mjs' ? findExportDefault(source) : findModuleExports(source);
|
||||
if (!exportsArray) {
|
||||
return content;
|
||||
}
|
||||
@ -47,7 +50,19 @@ export function removeOverridesFromLintConfig(content: string): string {
|
||||
return applyChangesToString(content, changes);
|
||||
}
|
||||
|
||||
function findAllBlocks(source: ts.SourceFile): ts.NodeArray<ts.Node> {
|
||||
// TODO Change name
|
||||
function findExportDefault(source: ts.SourceFile): ts.NodeArray<ts.Node> {
|
||||
return ts.forEachChild(source, function analyze(node) {
|
||||
if (
|
||||
ts.isExportAssignment(node) &&
|
||||
ts.isArrayLiteralExpression(node.expression)
|
||||
) {
|
||||
return node.expression.elements;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function findModuleExports(source: ts.SourceFile): ts.NodeArray<ts.Node> {
|
||||
return ts.forEachChild(source, function analyze(node) {
|
||||
if (
|
||||
ts.isExpressionStatement(node) &&
|
||||
@ -86,7 +101,9 @@ export function hasOverride(
|
||||
true,
|
||||
ts.ScriptKind.JS
|
||||
);
|
||||
const exportsArray = findAllBlocks(source);
|
||||
const format = content.includes('export default') ? 'mjs' : 'cjs';
|
||||
const exportsArray =
|
||||
format === 'mjs' ? findExportDefault(source) : findModuleExports(source);
|
||||
if (!exportsArray) {
|
||||
return false;
|
||||
}
|
||||
@ -120,6 +137,7 @@ function parseTextToJson(text: string): any {
|
||||
.replace(/\s([a-zA-Z0-9_]+)\s*:/g, ' "$1": ')
|
||||
// stringify any require calls to avoid JSON parsing errors, turn them into just the string value being required
|
||||
.replace(/require\(['"]([^'"]+)['"]\)/g, '"$1"')
|
||||
.replace(/\(?await import\(['"]([^'"]+)['"]\)\)?/g, '"$1"')
|
||||
);
|
||||
}
|
||||
|
||||
@ -141,7 +159,9 @@ export function replaceOverride(
|
||||
true,
|
||||
ts.ScriptKind.JS
|
||||
);
|
||||
const exportsArray = findAllBlocks(source);
|
||||
const format = content.includes('export default') ? 'mjs' : 'cjs';
|
||||
const exportsArray =
|
||||
format === 'mjs' ? findExportDefault(source) : findModuleExports(source);
|
||||
if (!exportsArray) {
|
||||
return content;
|
||||
}
|
||||
@ -174,20 +194,24 @@ export function replaceOverride(
|
||||
let updatedData = update(data);
|
||||
if (updatedData) {
|
||||
updatedData = mapFilePaths(updatedData);
|
||||
|
||||
const parserReplacement =
|
||||
format === 'mjs'
|
||||
? (parser: string) => `(await import('${parser}'))`
|
||||
: (parser: string) => `require('${parser}')`;
|
||||
|
||||
changes.push({
|
||||
type: ChangeType.Insert,
|
||||
index: start,
|
||||
// NOTE: Indentation added to format without formatting tools like Prettier.
|
||||
text:
|
||||
' ' +
|
||||
JSON.stringify(updatedData, null, 2)
|
||||
// restore any parser require calls that were stripped during JSON parsing
|
||||
.replace(/"parser": "([^"]+)"/g, (_, parser) => {
|
||||
return `"parser": require('${parser}')`;
|
||||
})
|
||||
.slice(2, -2) // remove curly braces and start/end line breaks since we are injecting just properties
|
||||
// Append indentation so file is formatted without Prettier
|
||||
.replaceAll(/\n/g, '\n '),
|
||||
.replace(
|
||||
/"parser": "([^"]+)"/g,
|
||||
(_, parser) => `"parser": ${parserReplacement(parser)}`
|
||||
)
|
||||
.slice(2, -2) // Remove curly braces and start/end line breaks
|
||||
.replaceAll(/\n/g, '\n '), // Maintain indentation
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -198,7 +222,12 @@ export function replaceOverride(
|
||||
}
|
||||
|
||||
/**
|
||||
* Adding require statement to the top of the file
|
||||
* Adding import statement to the top of the file
|
||||
* The imports are added based on a few rules:
|
||||
* 1. If it's a default import and matches the variable, return content unchanged.
|
||||
* 2. If it's a named import and the variables are not part of the import object, add them.
|
||||
* 3. If no existing import and variable is a string, add a default import.
|
||||
* 4. If no existing import and variable is an array, add it as an object import.
|
||||
*/
|
||||
export function addImportToFlatConfig(
|
||||
content: string,
|
||||
@ -214,6 +243,159 @@ export function addImportToFlatConfig(
|
||||
ts.ScriptKind.JS
|
||||
);
|
||||
|
||||
const format = content.includes('export default') ? 'mjs' : 'cjs';
|
||||
|
||||
if (format === 'mjs') {
|
||||
return addESMImportToFlatConfig(source, printer, content, variable, imp);
|
||||
}
|
||||
return addCJSImportToFlatConfig(source, printer, content, variable, imp);
|
||||
}
|
||||
|
||||
function addESMImportToFlatConfig(
|
||||
source: ts.SourceFile,
|
||||
printer: ts.Printer,
|
||||
content: string,
|
||||
variable: string | string[],
|
||||
imp: string
|
||||
): string {
|
||||
let existingImport: ts.ImportDeclaration | undefined;
|
||||
|
||||
ts.forEachChild(source, (node) => {
|
||||
if (
|
||||
ts.isImportDeclaration(node) &&
|
||||
ts.isStringLiteral(node.moduleSpecifier) &&
|
||||
node.moduleSpecifier.text === imp
|
||||
) {
|
||||
existingImport = node;
|
||||
}
|
||||
});
|
||||
|
||||
// Rule 1:
|
||||
if (
|
||||
existingImport &&
|
||||
typeof variable === 'string' &&
|
||||
existingImport.importClause?.name?.getText() === variable
|
||||
) {
|
||||
return content;
|
||||
}
|
||||
|
||||
// Rule 2:
|
||||
if (
|
||||
existingImport &&
|
||||
existingImport.importClause?.namedBindings &&
|
||||
Array.isArray(variable)
|
||||
) {
|
||||
const namedImports = existingImport.importClause
|
||||
.namedBindings as ts.NamedImports;
|
||||
const existingElements = namedImports.elements;
|
||||
|
||||
// Filter out variables that are already imported
|
||||
const newVariables = variable.filter(
|
||||
(v) => !existingElements.some((e) => e.name.getText() === v)
|
||||
);
|
||||
|
||||
if (newVariables.length === 0) {
|
||||
return content;
|
||||
}
|
||||
|
||||
const newImportSpecifiers = newVariables.map((v) =>
|
||||
ts.factory.createImportSpecifier(
|
||||
false,
|
||||
undefined,
|
||||
ts.factory.createIdentifier(v)
|
||||
)
|
||||
);
|
||||
|
||||
const lastElement = existingElements[existingElements.length - 1];
|
||||
const insertIndex = lastElement
|
||||
? lastElement.getEnd()
|
||||
: namedImports.getEnd();
|
||||
|
||||
const insertText = printer.printList(
|
||||
ts.ListFormat.NamedImportsOrExportsElements,
|
||||
ts.factory.createNodeArray(newImportSpecifiers),
|
||||
source
|
||||
);
|
||||
|
||||
return applyChangesToString(content, [
|
||||
{
|
||||
type: ChangeType.Insert,
|
||||
index: insertIndex,
|
||||
text: `, ${insertText}`,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
// Rule 3:
|
||||
if (!existingImport && typeof variable === 'string') {
|
||||
const defaultImport = ts.factory.createImportDeclaration(
|
||||
undefined,
|
||||
ts.factory.createImportClause(
|
||||
false,
|
||||
ts.factory.createIdentifier(variable),
|
||||
undefined
|
||||
),
|
||||
ts.factory.createStringLiteral(imp)
|
||||
);
|
||||
|
||||
const insert = printer.printNode(
|
||||
ts.EmitHint.Unspecified,
|
||||
defaultImport,
|
||||
source
|
||||
);
|
||||
|
||||
return applyChangesToString(content, [
|
||||
{
|
||||
type: ChangeType.Insert,
|
||||
index: 0,
|
||||
text: `${insert}\n`,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
// Rule 4:
|
||||
if (!existingImport && Array.isArray(variable)) {
|
||||
const objectImport = ts.factory.createImportDeclaration(
|
||||
undefined,
|
||||
ts.factory.createImportClause(
|
||||
false,
|
||||
undefined,
|
||||
ts.factory.createNamedImports(
|
||||
variable.map((v) =>
|
||||
ts.factory.createImportSpecifier(
|
||||
false,
|
||||
undefined,
|
||||
ts.factory.createIdentifier(v)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
ts.factory.createStringLiteral(imp)
|
||||
);
|
||||
|
||||
const insert = printer.printNode(
|
||||
ts.EmitHint.Unspecified,
|
||||
objectImport,
|
||||
source
|
||||
);
|
||||
|
||||
return applyChangesToString(content, [
|
||||
{
|
||||
type: ChangeType.Insert,
|
||||
index: 0,
|
||||
text: `${insert}\n`,
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
function addCJSImportToFlatConfig(
|
||||
source: ts.SourceFile,
|
||||
printer: ts.Printer,
|
||||
content: string,
|
||||
variable: string | string[],
|
||||
imp: string
|
||||
): string {
|
||||
const foundBindingVars: ts.NodeArray<ts.BindingElement> = ts.forEachChild(
|
||||
source,
|
||||
function analyze(node) {
|
||||
@ -322,6 +504,22 @@ export function addImportToFlatConfig(
|
||||
]);
|
||||
}
|
||||
|
||||
function existsAsNamedOrDefaultImport(
|
||||
node: ts.ImportDeclaration,
|
||||
variable: string | string[]
|
||||
) {
|
||||
const isNamed =
|
||||
node.importClause.namedBindings &&
|
||||
ts.isNamedImports(node.importClause.namedBindings);
|
||||
if (Array.isArray(variable)) {
|
||||
return isNamed || variable.includes(node.importClause?.name?.getText());
|
||||
}
|
||||
return (
|
||||
(node.importClause.namedBindings &&
|
||||
ts.isNamedImports(node.importClause.namedBindings)) ||
|
||||
node.importClause?.name?.getText() === variable
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Remove an import from flat config
|
||||
*/
|
||||
@ -338,8 +536,49 @@ export function removeImportFromFlatConfig(
|
||||
ts.ScriptKind.JS
|
||||
);
|
||||
|
||||
const format = content.includes('export default') ? 'mjs' : 'cjs';
|
||||
if (format === 'mjs') {
|
||||
return removeImportFromFlatConfigESM(source, content, variable, imp);
|
||||
} else {
|
||||
return removeImportFromFlatConfigCJS(source, content, variable, imp);
|
||||
}
|
||||
}
|
||||
|
||||
function removeImportFromFlatConfigESM(
|
||||
source: ts.SourceFile,
|
||||
content: string,
|
||||
variable: string,
|
||||
imp: string
|
||||
): string {
|
||||
const changes: StringChange[] = [];
|
||||
|
||||
ts.forEachChild(source, (node) => {
|
||||
// we can only combine object binding patterns
|
||||
if (
|
||||
ts.isImportDeclaration(node) &&
|
||||
ts.isStringLiteral(node.moduleSpecifier) &&
|
||||
node.moduleSpecifier.text === imp &&
|
||||
node.importClause &&
|
||||
existsAsNamedOrDefaultImport(node, variable)
|
||||
) {
|
||||
changes.push({
|
||||
type: ChangeType.Delete,
|
||||
start: node.pos,
|
||||
length: node.end - node.pos,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return applyChangesToString(content, changes);
|
||||
}
|
||||
|
||||
function removeImportFromFlatConfigCJS(
|
||||
source: ts.SourceFile,
|
||||
content: string,
|
||||
variable: string,
|
||||
imp: string
|
||||
): string {
|
||||
const changes: StringChange[] = [];
|
||||
ts.forEachChild(source, (node) => {
|
||||
// we can only combine object binding patterns
|
||||
if (
|
||||
@ -367,7 +606,7 @@ export function removeImportFromFlatConfig(
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects new ts.expression to the end of the module.exports array.
|
||||
* Injects new ts.expression to the end of the module.exports or export default array.
|
||||
*/
|
||||
export function addBlockToFlatConfigExport(
|
||||
content: string,
|
||||
@ -385,6 +624,79 @@ export function addBlockToFlatConfigExport(
|
||||
ts.ScriptKind.JS
|
||||
);
|
||||
|
||||
const format = content.includes('export default') ? 'mjs' : 'cjs';
|
||||
|
||||
// find the export default array statement
|
||||
if (format === 'mjs') {
|
||||
return addBlockToFlatConfigExportESM(
|
||||
content,
|
||||
config,
|
||||
source,
|
||||
printer,
|
||||
options
|
||||
);
|
||||
} else {
|
||||
return addBlockToFlatConfigExportCJS(
|
||||
content,
|
||||
config,
|
||||
source,
|
||||
printer,
|
||||
options
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function addBlockToFlatConfigExportESM(
|
||||
content: string,
|
||||
config: ts.Expression | ts.SpreadElement,
|
||||
source: ts.SourceFile,
|
||||
printer: ts.Printer,
|
||||
options: { insertAtTheEnd?: boolean; checkBaseConfig?: boolean } = {
|
||||
insertAtTheEnd: true,
|
||||
}
|
||||
): string {
|
||||
const exportDefaultStatement = source.statements.find(
|
||||
(statement): statement is ts.ExportAssignment =>
|
||||
ts.isExportAssignment(statement) &&
|
||||
ts.isArrayLiteralExpression(statement.expression)
|
||||
);
|
||||
|
||||
if (!exportDefaultStatement) return content;
|
||||
|
||||
const exportArrayLiteral =
|
||||
exportDefaultStatement.expression as ts.ArrayLiteralExpression;
|
||||
|
||||
const updatedArrayElements = options.insertAtTheEnd
|
||||
? [...exportArrayLiteral.elements, config]
|
||||
: [config, ...exportArrayLiteral.elements];
|
||||
|
||||
const updatedExportDefault = ts.factory.createExportAssignment(
|
||||
undefined,
|
||||
false,
|
||||
ts.factory.createArrayLiteralExpression(updatedArrayElements, true)
|
||||
);
|
||||
|
||||
// update the existing export default array
|
||||
const updatedStatements = source.statements.map((statement) =>
|
||||
statement === exportDefaultStatement ? updatedExportDefault : statement
|
||||
);
|
||||
|
||||
const updatedSource = ts.factory.updateSourceFile(source, updatedStatements);
|
||||
|
||||
return printer
|
||||
.printFile(updatedSource)
|
||||
.replace(/export default/, '\nexport default');
|
||||
}
|
||||
|
||||
function addBlockToFlatConfigExportCJS(
|
||||
content: string,
|
||||
config: ts.Expression | ts.SpreadElement,
|
||||
source: ts.SourceFile,
|
||||
printer: ts.Printer,
|
||||
options: { insertAtTheEnd?: boolean; checkBaseConfig?: boolean } = {
|
||||
insertAtTheEnd: true,
|
||||
}
|
||||
): string {
|
||||
const exportsArray = ts.forEachChild(source, function analyze(node) {
|
||||
if (
|
||||
ts.isExpressionStatement(node) &&
|
||||
@ -443,34 +755,57 @@ export function removePlugin(
|
||||
true,
|
||||
ts.ScriptKind.JS
|
||||
);
|
||||
const format = content.includes('export default') ? 'mjs' : 'cjs';
|
||||
const changes: StringChange[] = [];
|
||||
if (format === 'mjs') {
|
||||
ts.forEachChild(source, function analyze(node) {
|
||||
if (
|
||||
ts.isImportDeclaration(node) &&
|
||||
ts.isStringLiteral(node.moduleSpecifier) &&
|
||||
node.moduleSpecifier.text === pluginImport
|
||||
) {
|
||||
const importClause = node.importClause;
|
||||
|
||||
if (
|
||||
(importClause && importClause.name) ||
|
||||
(importClause.namedBindings &&
|
||||
ts.isNamedImports(importClause.namedBindings))
|
||||
) {
|
||||
changes.push({
|
||||
type: ChangeType.Delete,
|
||||
start: node.pos,
|
||||
length: node.end - node.pos,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ts.forEachChild(source, function analyze(node) {
|
||||
if (
|
||||
ts.isVariableStatement(node) &&
|
||||
ts.isVariableDeclaration(node.declarationList.declarations[0]) &&
|
||||
ts.isCallExpression(node.declarationList.declarations[0].initializer) &&
|
||||
node.declarationList.declarations[0].initializer.arguments.length &&
|
||||
ts.isStringLiteral(
|
||||
node.declarationList.declarations[0].initializer.arguments[0]
|
||||
) &&
|
||||
node.declarationList.declarations[0].initializer.arguments[0].text ===
|
||||
pluginImport
|
||||
) {
|
||||
changes.push({
|
||||
type: ChangeType.Delete,
|
||||
start: node.pos,
|
||||
length: node.end - node.pos,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
ts.forEachChild(source, function analyze(node) {
|
||||
if (
|
||||
ts.isVariableStatement(node) &&
|
||||
ts.isVariableDeclaration(node.declarationList.declarations[0]) &&
|
||||
ts.isCallExpression(node.declarationList.declarations[0].initializer) &&
|
||||
node.declarationList.declarations[0].initializer.arguments.length &&
|
||||
ts.isStringLiteral(
|
||||
node.declarationList.declarations[0].initializer.arguments[0]
|
||||
) &&
|
||||
node.declarationList.declarations[0].initializer.arguments[0].text ===
|
||||
pluginImport
|
||||
ts.isExportAssignment(node) &&
|
||||
ts.isArrayLiteralExpression(node.expression)
|
||||
) {
|
||||
changes.push({
|
||||
type: ChangeType.Delete,
|
||||
start: node.pos,
|
||||
length: node.end - node.pos,
|
||||
});
|
||||
}
|
||||
});
|
||||
ts.forEachChild(source, function analyze(node) {
|
||||
if (
|
||||
ts.isExpressionStatement(node) &&
|
||||
ts.isBinaryExpression(node.expression) &&
|
||||
node.expression.left.getText() === 'module.exports' &&
|
||||
ts.isArrayLiteralExpression(node.expression.right)
|
||||
) {
|
||||
const blockElements = node.expression.right.elements;
|
||||
const blockElements = node.expression.elements;
|
||||
blockElements.forEach((element) => {
|
||||
if (ts.isObjectLiteralExpression(element)) {
|
||||
const pluginsElem = element.properties.find(
|
||||
@ -583,7 +918,15 @@ export function removeCompatExtends(
|
||||
ts.ScriptKind.JS
|
||||
);
|
||||
const changes: StringChange[] = [];
|
||||
findAllBlocks(source)?.forEach((node) => {
|
||||
const format = content.includes('export default') ? 'mjs' : 'cjs';
|
||||
const exportsArray =
|
||||
format === 'mjs' ? findExportDefault(source) : findModuleExports(source);
|
||||
|
||||
if (!exportsArray) {
|
||||
return content;
|
||||
}
|
||||
|
||||
exportsArray.forEach((node) => {
|
||||
if (
|
||||
ts.isSpreadElement(node) &&
|
||||
ts.isCallExpression(node.expression) &&
|
||||
@ -644,9 +987,16 @@ export function removePredefinedConfigs(
|
||||
true,
|
||||
ts.ScriptKind.JS
|
||||
);
|
||||
const format = content.includes('export default') ? 'mjs' : 'cjs';
|
||||
const changes: StringChange[] = [];
|
||||
let removeImport = true;
|
||||
findAllBlocks(source)?.forEach((node) => {
|
||||
const exportsArray =
|
||||
format === 'mjs' ? findExportDefault(source) : findModuleExports(source);
|
||||
if (!exportsArray) {
|
||||
return content;
|
||||
}
|
||||
|
||||
exportsArray.forEach((node) => {
|
||||
if (
|
||||
ts.isSpreadElement(node) &&
|
||||
ts.isElementAccessExpression(node.expression) &&
|
||||
@ -709,14 +1059,23 @@ export function addPluginsToExportsBlock(
|
||||
* Adds compat if missing to flat config
|
||||
*/
|
||||
export function addFlatCompatToFlatConfig(content: string) {
|
||||
let result = content;
|
||||
result = addImportToFlatConfig(result, 'js', '@eslint/js');
|
||||
const result = addImportToFlatConfig(content, 'js', '@eslint/js');
|
||||
const format = content.includes('export default') ? 'mjs' : 'cjs';
|
||||
if (result.includes('const compat = new FlatCompat')) {
|
||||
return result;
|
||||
}
|
||||
result = addImportToFlatConfig(result, ['FlatCompat'], '@eslint/eslintrc');
|
||||
const index = result.indexOf('module.exports');
|
||||
return applyChangesToString(result, [
|
||||
|
||||
if (format === 'mjs') {
|
||||
return addFlatCompatToFlatConfigESM(result);
|
||||
} else {
|
||||
return addFlatCompatToFlatConfigCJS(result);
|
||||
}
|
||||
}
|
||||
|
||||
function addFlatCompatToFlatConfigCJS(content: string) {
|
||||
content = addImportToFlatConfig(content, ['FlatCompat'], '@eslint/eslintrc');
|
||||
const index = content.indexOf('module.exports');
|
||||
return applyChangesToString(content, [
|
||||
{
|
||||
type: ChangeType.Insert,
|
||||
index: index - 1,
|
||||
@ -729,6 +1088,32 @@ const compat = new FlatCompat({
|
||||
},
|
||||
]);
|
||||
}
|
||||
function addFlatCompatToFlatConfigESM(content: string) {
|
||||
const importsToAdd = [
|
||||
{ variable: 'js', module: '@eslint/js' },
|
||||
{ variable: ['fileURLToPath'], module: 'url' },
|
||||
{ variable: ['dirname'], module: 'path' },
|
||||
{ variable: ['FlatCompat'], module: '@eslint/eslintrc' },
|
||||
];
|
||||
|
||||
for (const { variable, module } of importsToAdd) {
|
||||
content = addImportToFlatConfig(content, variable, module);
|
||||
}
|
||||
|
||||
const index = content.indexOf('export default');
|
||||
return applyChangesToString(content, [
|
||||
{
|
||||
type: ChangeType.Insert,
|
||||
index: index - 1,
|
||||
text: `
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});\n
|
||||
`,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate node list representing the imports and the exports blocks
|
||||
@ -736,17 +1121,26 @@ const compat = new FlatCompat({
|
||||
*/
|
||||
export function createNodeList(
|
||||
importsMap: Map<string, string>,
|
||||
exportElements: ts.Expression[]
|
||||
exportElements: ts.Expression[],
|
||||
format: 'mjs' | 'cjs'
|
||||
): ts.NodeArray<
|
||||
ts.VariableStatement | ts.Identifier | ts.ExpressionStatement | ts.SourceFile
|
||||
> {
|
||||
const importsList = [];
|
||||
|
||||
// generateRequire(varName, imp, ts.factory);
|
||||
Array.from(importsMap.entries()).forEach(([imp, varName]) => {
|
||||
importsList.push(generateRequire(varName, imp));
|
||||
if (format === 'mjs') {
|
||||
importsList.push(generateESMImport(varName, imp));
|
||||
} else {
|
||||
importsList.push(generateRequire(varName, imp));
|
||||
}
|
||||
});
|
||||
|
||||
const exports =
|
||||
format === 'mjs'
|
||||
? generateESMExport(exportElements)
|
||||
: generateCJSExport(exportElements);
|
||||
|
||||
return ts.factory.createNodeArray([
|
||||
// add plugin imports
|
||||
...importsList,
|
||||
@ -757,21 +1151,33 @@ export function createNodeList(
|
||||
false,
|
||||
ts.ScriptKind.JS
|
||||
),
|
||||
// creates:
|
||||
// module.exports = [ ... ];
|
||||
ts.factory.createExpressionStatement(
|
||||
ts.factory.createBinaryExpression(
|
||||
ts.factory.createPropertyAccessExpression(
|
||||
ts.factory.createIdentifier('module'),
|
||||
ts.factory.createIdentifier('exports')
|
||||
),
|
||||
ts.factory.createToken(ts.SyntaxKind.EqualsToken),
|
||||
ts.factory.createArrayLiteralExpression(exportElements, true)
|
||||
)
|
||||
),
|
||||
exports,
|
||||
]);
|
||||
}
|
||||
|
||||
function generateESMExport(elements: ts.Expression[]): ts.ExportAssignment {
|
||||
// creates: export default = [...]
|
||||
return ts.factory.createExportAssignment(
|
||||
undefined,
|
||||
false,
|
||||
ts.factory.createArrayLiteralExpression(elements, true)
|
||||
);
|
||||
}
|
||||
|
||||
function generateCJSExport(elements: ts.Expression[]): ts.ExpressionStatement {
|
||||
// creates: module.exports = [...]
|
||||
return ts.factory.createExpressionStatement(
|
||||
ts.factory.createBinaryExpression(
|
||||
ts.factory.createPropertyAccessExpression(
|
||||
ts.factory.createIdentifier('module'),
|
||||
ts.factory.createIdentifier('exports')
|
||||
),
|
||||
ts.factory.createToken(ts.SyntaxKind.EqualsToken),
|
||||
ts.factory.createArrayLiteralExpression(elements, true)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function generateSpreadElement(name: string): ts.SpreadElement {
|
||||
return ts.factory.createSpreadElement(ts.factory.createIdentifier(name));
|
||||
}
|
||||
@ -831,17 +1237,20 @@ export function stringifyNodeList(
|
||||
true,
|
||||
ts.ScriptKind.JS
|
||||
);
|
||||
return (
|
||||
printer
|
||||
.printList(ts.ListFormat.MultiLine, nodes, resultFile)
|
||||
// add new line before compat initialization
|
||||
.replace(
|
||||
/const compat = new FlatCompat/,
|
||||
'\nconst compat = new FlatCompat'
|
||||
)
|
||||
// add new line before module.exports = ...
|
||||
.replace(/module\.exports/, '\nmodule.exports')
|
||||
);
|
||||
const result = printer
|
||||
.printList(ts.ListFormat.MultiLine, nodes, resultFile)
|
||||
// add new line before compat initialization
|
||||
.replace(
|
||||
/const compat = new FlatCompat/,
|
||||
'\nconst compat = new FlatCompat'
|
||||
);
|
||||
|
||||
if (result.includes('export default')) {
|
||||
return result // add new line before export default = ...
|
||||
.replace(/export default/, '\nexport default');
|
||||
} else {
|
||||
return result.replace(/module.exports/, '\nmodule.exports');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -871,6 +1280,50 @@ export function generateRequire(
|
||||
);
|
||||
}
|
||||
|
||||
// Top level imports
|
||||
export function generateESMImport(
|
||||
variableName: string | ts.ObjectBindingPattern,
|
||||
imp: string
|
||||
): ts.ImportDeclaration {
|
||||
let importClause;
|
||||
|
||||
if (typeof variableName === 'string') {
|
||||
// For single variable import e.g import foo from 'module';
|
||||
importClause = ts.factory.createImportClause(
|
||||
false,
|
||||
ts.factory.createIdentifier(variableName),
|
||||
undefined
|
||||
);
|
||||
} else {
|
||||
// For object binding pattern import e.g import { a, b, c } from 'module';
|
||||
importClause = ts.factory.createImportClause(
|
||||
false,
|
||||
undefined,
|
||||
ts.factory.createNamedImports(
|
||||
variableName.elements.map((element) => {
|
||||
const propertyName = element.propertyName
|
||||
? ts.isIdentifier(element.propertyName)
|
||||
? element.propertyName
|
||||
: ts.factory.createIdentifier(element.propertyName.getText())
|
||||
: undefined;
|
||||
|
||||
return ts.factory.createImportSpecifier(
|
||||
false,
|
||||
propertyName,
|
||||
element.name as ts.Identifier
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return ts.factory.createImportDeclaration(
|
||||
undefined,
|
||||
importClause,
|
||||
ts.factory.createStringLiteral(imp)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* FROM: https://github.com/eslint/rewrite/blob/e2a7ec809db20e638abbad250d105ddbde88a8d5/packages/migrate-config/src/migrate-config.js#L222
|
||||
*
|
||||
@ -904,7 +1357,8 @@ export function overrideNeedsCompat(
|
||||
export function generateFlatOverride(
|
||||
_override: Partial<Linter.ConfigOverride<Linter.RulesRecord>> & {
|
||||
ignores?: Linter.FlatConfig['ignores'];
|
||||
}
|
||||
},
|
||||
format: 'mjs' | 'cjs'
|
||||
): ts.ObjectLiteralExpression | ts.SpreadElement {
|
||||
const override = mapFilePaths(_override);
|
||||
|
||||
@ -981,21 +1435,10 @@ export function generateFlatOverride(
|
||||
}
|
||||
return propertyAssignment;
|
||||
} else {
|
||||
// Change parser to require statement.
|
||||
return ts.factory.createPropertyAssignment(
|
||||
'parser',
|
||||
ts.factory.createCallExpression(
|
||||
ts.factory.createIdentifier('require'),
|
||||
undefined,
|
||||
[
|
||||
ts.factory.createStringLiteral(
|
||||
override['languageOptions']?.['parserOptions']?.parser ??
|
||||
override['languageOptions']?.parser ??
|
||||
override.parser
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
// Change parser to import statement.
|
||||
return format === 'mjs'
|
||||
? generateESMParserImport(override)
|
||||
: generateCJSParserImport(override);
|
||||
}
|
||||
},
|
||||
});
|
||||
@ -1103,6 +1546,50 @@ export function generateFlatOverride(
|
||||
);
|
||||
}
|
||||
|
||||
function generateESMParserImport(
|
||||
override: Partial<Linter.ConfigOverride<Linter.RulesRecord>> & {
|
||||
ignores?: Linter.FlatConfig['ignores'];
|
||||
}
|
||||
): ts.PropertyAssignment {
|
||||
return ts.factory.createPropertyAssignment(
|
||||
'parser',
|
||||
ts.factory.createAwaitExpression(
|
||||
ts.factory.createCallExpression(
|
||||
ts.factory.createIdentifier('import'),
|
||||
undefined,
|
||||
[
|
||||
ts.factory.createStringLiteral(
|
||||
override['languageOptions']?.['parserOptions']?.parser ??
|
||||
override['languageOptions']?.parser ??
|
||||
override.parser
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function generateCJSParserImport(
|
||||
override: Partial<Linter.ConfigOverride<Linter.RulesRecord>> & {
|
||||
ignores?: Linter.FlatConfig['ignores'];
|
||||
}
|
||||
): ts.PropertyAssignment {
|
||||
return ts.factory.createPropertyAssignment(
|
||||
'parser',
|
||||
ts.factory.createCallExpression(
|
||||
ts.factory.createIdentifier('require'),
|
||||
undefined,
|
||||
[
|
||||
ts.factory.createStringLiteral(
|
||||
override['languageOptions']?.['parserOptions']?.parser ??
|
||||
override['languageOptions']?.parser ??
|
||||
override.parser
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function generateFlatPredefinedConfig(
|
||||
predefinedConfigName: string,
|
||||
moduleName = 'nx',
|
||||
|
||||
@ -21,7 +21,7 @@ import { globWithWorkspaceContext } from 'nx/src/utils/workspace-context';
|
||||
import { gte } from 'semver';
|
||||
import {
|
||||
baseEsLintConfigFile,
|
||||
baseEsLintFlatConfigFile,
|
||||
BASE_ESLINT_CONFIG_FILENAMES,
|
||||
ESLINT_CONFIG_FILENAMES,
|
||||
isFlatConfig,
|
||||
} from '../utils/config-file';
|
||||
@ -405,7 +405,7 @@ function getProjectUsingESLintConfig(
|
||||
): CreateNodesResult['projects'][string] | null {
|
||||
const rootEslintConfig = [
|
||||
baseEsLintConfigFile,
|
||||
baseEsLintFlatConfigFile,
|
||||
...BASE_ESLINT_CONFIG_FILENAMES,
|
||||
...ESLINT_CONFIG_FILENAMES,
|
||||
].find((f) => existsSync(join(context.workspaceRoot, f)));
|
||||
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import { existsSync, statSync } from 'fs';
|
||||
import { basename, dirname, join, resolve } from 'path';
|
||||
import { eslintFlatConfigFilenames } from './flat-config';
|
||||
import {
|
||||
baseEslintConfigFilenames,
|
||||
eslintFlatConfigFilenames,
|
||||
} from './flat-config';
|
||||
|
||||
export const ESLINT_FLAT_CONFIG_FILENAMES = eslintFlatConfigFilenames;
|
||||
|
||||
@ -18,8 +21,10 @@ export const ESLINT_CONFIG_FILENAMES = [
|
||||
...ESLINT_FLAT_CONFIG_FILENAMES,
|
||||
];
|
||||
|
||||
export const BASE_ESLINT_CONFIG_FILENAMES = baseEslintConfigFilenames;
|
||||
|
||||
export const baseEsLintConfigFile = '.eslintrc.base.json';
|
||||
export const baseEsLintFlatConfigFile = 'eslint.base.config.cjs';
|
||||
export const baseEsLintFlatConfigFile = 'eslint.base.config.mjs';
|
||||
// Make sure we can handle previous file extension as well for migrations or custom generators.
|
||||
export const legacyBaseEsLintFlatConfigFile = 'eslint.base.config.js';
|
||||
|
||||
|
||||
@ -1,10 +1,17 @@
|
||||
import { Tree } from '@nx/devkit';
|
||||
import { gte } from 'semver';
|
||||
|
||||
// todo: add support for eslint.config.mjs,
|
||||
export const eslintFlatConfigFilenames = [
|
||||
'eslint.config.cjs',
|
||||
'eslint.config.js',
|
||||
'eslint.config.mjs',
|
||||
];
|
||||
|
||||
export const baseEslintConfigFilenames = [
|
||||
'eslint.base.js',
|
||||
'eslint.base.config.cjs',
|
||||
'eslint.base.config.js',
|
||||
'eslint.base.config.mjs',
|
||||
];
|
||||
|
||||
export function getRootESLintFlatConfigFilename(tree: Tree): string {
|
||||
|
||||
@ -623,7 +623,41 @@ describe('app', () => {
|
||||
|
||||
describe('--linter', () => {
|
||||
describe('default (eslint)', () => {
|
||||
it('should add flat config as needed', async () => {
|
||||
it('should add flat config as needed MJS', async () => {
|
||||
tree.write('eslint.config.mjs', 'export default {};');
|
||||
const name = uniq();
|
||||
|
||||
await applicationGenerator(tree, {
|
||||
directory: name,
|
||||
style: 'css',
|
||||
});
|
||||
|
||||
expect(tree.read(`${name}/eslint.config.mjs`, 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { FlatCompat } from '@eslint/eslintrc';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import js from '@eslint/js';
|
||||
import nx from '@nx/eslint-plugin';
|
||||
import baseConfig from '../eslint.config.mjs';
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
export default [
|
||||
...compat.extends('next', 'next/core-web-vitals'),
|
||||
...baseConfig,
|
||||
...nx.configs['flat/react-typescript'],
|
||||
{
|
||||
ignores: ['.next/**/*'],
|
||||
},
|
||||
];
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should add flat config as needed CJS', async () => {
|
||||
tree.write('eslint.config.cjs', '');
|
||||
const name = uniq();
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ exports[`app generated files content - as-provided - my-app general application
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`app generated files content - as-provided - my-app general application should configure eslint correctly (flat config) 1`] = `
|
||||
exports[`app generated files content - as-provided - my-app general application should configure eslint correctly (flat config CJS) 1`] = `
|
||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||
const js = require('@eslint/js');
|
||||
const baseConfig = require('../eslint.config.cjs');
|
||||
@ -66,6 +66,40 @@ module.exports = [
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`app generated files content - as-provided - my-app general application should configure eslint correctly (flat config ESM) 1`] = `
|
||||
"import { FlatCompat } from '@eslint/eslintrc';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import js from '@eslint/js';
|
||||
import baseConfig from '../eslint.config.mjs';
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.vue'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
...compat.extends('@nuxt/eslint-config'),
|
||||
{
|
||||
files: ['**/*.vue'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: await import('@typescript-eslint/parser'),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: ['.nuxt/**', '.output/**', 'node_modules'],
|
||||
},
|
||||
];
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`app generated files content - as-provided - my-app general application should configure nuxt correctly 1`] = `
|
||||
"import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
import { defineNuxtConfig } from 'nuxt/config';
|
||||
@ -393,7 +427,7 @@ exports[`app generated files content - as-provided - myApp general application s
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`app generated files content - as-provided - myApp general application should configure eslint correctly (flat config) 1`] = `
|
||||
exports[`app generated files content - as-provided - myApp general application should configure eslint correctly (flat config CJS) 1`] = `
|
||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||
const js = require('@eslint/js');
|
||||
const baseConfig = require('../eslint.config.cjs');
|
||||
@ -426,6 +460,40 @@ module.exports = [
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`app generated files content - as-provided - myApp general application should configure eslint correctly (flat config ESM) 1`] = `
|
||||
"import { FlatCompat } from '@eslint/eslintrc';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import js from '@eslint/js';
|
||||
import baseConfig from '../eslint.config.mjs';
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.vue'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
...compat.extends('@nuxt/eslint-config'),
|
||||
{
|
||||
files: ['**/*.vue'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: await import('@typescript-eslint/parser'),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: ['.nuxt/**', '.output/**', 'node_modules'],
|
||||
},
|
||||
];
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`app generated files content - as-provided - myApp general application should configure nuxt correctly 1`] = `
|
||||
"import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
import { defineNuxtConfig } from 'nuxt/config';
|
||||
|
||||
@ -65,8 +65,21 @@ describe('app', () => {
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should configure eslint correctly (flat config)', async () => {
|
||||
tree.write('eslint.config.cjs', '');
|
||||
it('should configure eslint correctly (flat config ESM)', async () => {
|
||||
tree.write('eslint.config.mjs', 'export default {};');
|
||||
|
||||
await applicationGenerator(tree, {
|
||||
directory: name,
|
||||
unitTestRunner: 'vitest',
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${name}/eslint.config.mjs`, 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should configure eslint correctly (flat config CJS)', async () => {
|
||||
tree.write('eslint.config.cjs', 'module.exports = {};');
|
||||
|
||||
await applicationGenerator(tree, {
|
||||
directory: name,
|
||||
|
||||
@ -277,7 +277,7 @@ exports[`library should ignore test files in tsconfig.lib.json 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`library should support eslint flat config 1`] = `
|
||||
exports[`library should support eslint flat config CJS 1`] = `
|
||||
"const vue = require('eslint-plugin-vue');
|
||||
const baseConfig = require('../eslint.config.cjs');
|
||||
|
||||
@ -301,3 +301,28 @@ module.exports = [
|
||||
];
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`library should support eslint flat config ESM 1`] = `
|
||||
"import vue from 'eslint-plugin-vue';
|
||||
import baseConfig from '../eslint.config.mjs';
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
...vue.configs['flat/recommended'],
|
||||
{
|
||||
files: ['**/*.vue'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: await import('@typescript-eslint/parser'),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.vue'],
|
||||
rules: {
|
||||
'vue/multi-word-component-names': 'off',
|
||||
},
|
||||
},
|
||||
];
|
||||
"
|
||||
`;
|
||||
|
||||
@ -149,7 +149,7 @@ describe('library', () => {
|
||||
expect(eslintJson).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should support eslint flat config', async () => {
|
||||
it('should support eslint flat config CJS', async () => {
|
||||
tree.write(
|
||||
'eslint.config.cjs',
|
||||
`const { FlatCompat } = require('@eslint/eslintrc');
|
||||
@ -217,6 +217,76 @@ module.exports = [
|
||||
);
|
||||
});
|
||||
|
||||
it('should support eslint flat config ESM', async () => {
|
||||
tree.write(
|
||||
'eslint.config.mjs',
|
||||
`import { FlatCompat } from '@eslint/eslintrc';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import js from '@eslint/js';
|
||||
import nx from '@nx/eslint-plugin';
|
||||
import baseConfig from '../eslint.config.mjs';
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
||||
recommendedConfig: js.configs.recommended,
|
||||
});
|
||||
|
||||
export default [
|
||||
{ plugins: { '@nx': nxEslintPlugin } },
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||
rules: {
|
||||
'@nx/enforce-module-boundaries': [
|
||||
'error',
|
||||
{
|
||||
enforceBuildableLibDependency: true,
|
||||
allow: [],
|
||||
depConstraints: [
|
||||
{
|
||||
sourceTag: '*',
|
||||
onlyDependOnLibsWithTags: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
...compat.config({ extends: ['plugin:@nx/typescript'] }).map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
...compat.config({ extends: ['plugin:@nx/javascript'] }).map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.js', '**/*.jsx'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
...compat.config({ env: { jest: true } }).map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.spec.ts', '**/*.spec.tsx', '**/*.spec.js', '**/*.spec.jsx'],
|
||||
rules: {
|
||||
...config.rules,
|
||||
},
|
||||
})),
|
||||
]`
|
||||
);
|
||||
|
||||
await libraryGenerator(tree, defaultSchema);
|
||||
|
||||
const eslintJson = tree.read('my-lib/eslint.config.mjs', 'utf-8');
|
||||
expect(eslintJson).toMatchSnapshot();
|
||||
// assert **/*.vue was added to override in base eslint config
|
||||
const eslintBaseJson = tree.read('eslint.config.mjs', 'utf-8');
|
||||
expect(eslintBaseJson).toContain(
|
||||
`files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.vue'],`
|
||||
);
|
||||
});
|
||||
|
||||
describe('nested', () => {
|
||||
it('should update tags and implicitDependencies', async () => {
|
||||
await libraryGenerator(tree, {
|
||||
|
||||
@ -16,9 +16,11 @@ export function updateEslintConfig(
|
||||
!tree.exists('.eslintrc.json') &&
|
||||
!tree.exists('eslint.config.js') &&
|
||||
!tree.exists('eslint.config.cjs') &&
|
||||
!tree.exists('eslint.config.mjs') &&
|
||||
!tree.exists('.eslintrc.base.json') &&
|
||||
!tree.exists('eslint.base.config.js') &&
|
||||
!tree.exists('eslint.base.config.cjs')
|
||||
!tree.exists('eslint.base.config.cjs') &&
|
||||
!tree.exists('eslint.base.config.mjs')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user