feat(linter): create new workspaces with ESLint v9 and typescript-eslint v8 (#27404)
Closes #27451 --------- Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com> Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
This commit is contained in:
parent
2e0f374964
commit
68eeb2eeed
@ -41,7 +41,7 @@ describe('Move Angular Project', () => {
|
|||||||
expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.app.json`);
|
expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.app.json`);
|
||||||
expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.json`);
|
expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.json`);
|
||||||
expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.spec.json`);
|
expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.spec.json`);
|
||||||
expect(moveOutput).toContain(`CREATE ${newPath}/.eslintrc.json`);
|
expect(moveOutput).toContain(`CREATE ${newPath}/eslint.config.js`);
|
||||||
expect(moveOutput).toContain(`CREATE ${newPath}/public/favicon.ico`);
|
expect(moveOutput).toContain(`CREATE ${newPath}/public/favicon.ico`);
|
||||||
expect(moveOutput).toContain(`CREATE ${newPath}/src/index.html`);
|
expect(moveOutput).toContain(`CREATE ${newPath}/src/index.html`);
|
||||||
expect(moveOutput).toContain(`CREATE ${newPath}/src/main.ts`);
|
expect(moveOutput).toContain(`CREATE ${newPath}/src/main.ts`);
|
||||||
|
|||||||
@ -127,8 +127,7 @@ describe('Angular Projects', () => {
|
|||||||
|
|
||||||
// check e2e tests
|
// check e2e tests
|
||||||
if (runE2ETests('playwright')) {
|
if (runE2ETests('playwright')) {
|
||||||
const e2eResults = runCLI(`e2e ${app1}-e2e`);
|
expect(() => runCLI(`e2e ${app1}-e2e`)).not.toThrow();
|
||||||
expect(e2eResults).toContain('Successfully ran target e2e for project');
|
|
||||||
expect(await killPort(4200)).toBeTruthy();
|
expect(await killPort(4200)).toBeTruthy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,10 +159,7 @@ describe('Angular Projects', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (runE2ETests('playwright')) {
|
if (runE2ETests('playwright')) {
|
||||||
const e2eResults = runCLI(`e2e ${app}-e2e`);
|
expect(() => runCLI(`e2e ${app}-e2e`)).not.toThrow();
|
||||||
expect(e2eResults).toContain(
|
|
||||||
`Successfully ran target e2e for project ${app}-e2e`
|
|
||||||
);
|
|
||||||
expect(await killPort(4200)).toBeTruthy();
|
expect(await killPort(4200)).toBeTruthy();
|
||||||
}
|
}
|
||||||
}, 1000000);
|
}, 1000000);
|
||||||
@ -495,7 +491,7 @@ describe('Angular Projects', () => {
|
|||||||
updateFile(`${lib}/src/lib/${lib}.module.ts`, moduleContent);
|
updateFile(`${lib}/src/lib/${lib}.module.ts`, moduleContent);
|
||||||
|
|
||||||
// ACT
|
// ACT
|
||||||
const buildOutput = runCLI(`build ${lib}`);
|
const buildOutput = runCLI(`build ${lib}`, { env: { CI: 'false' } });
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
expect(buildOutput).toContain(`Building entry point '@${proj}/${lib}'`);
|
expect(buildOutput).toContain(`Building entry point '@${proj}/${lib}'`);
|
||||||
@ -516,14 +512,9 @@ describe('Angular Projects', () => {
|
|||||||
// check files are generated with the layout directory ("apps/")
|
// check files are generated with the layout directory ("apps/")
|
||||||
checkFilesExist(`apps/${appName}/src/app/app.module.ts`);
|
checkFilesExist(`apps/${appName}/src/app/app.module.ts`);
|
||||||
// check build works
|
// check build works
|
||||||
expect(runCLI(`build ${appName}`)).toContain(
|
expect(() => runCLI(`build ${appName}`)).not.toThrow();
|
||||||
`Successfully ran target build for project ${appName}`
|
|
||||||
);
|
|
||||||
// check tests pass
|
// check tests pass
|
||||||
const appTestResult = runCLI(`test ${appName}`);
|
expect(() => runCLI(`test ${appName}`)).not.toThrow();
|
||||||
expect(appTestResult).toContain(
|
|
||||||
`Successfully ran target test for project ${appName}`
|
|
||||||
);
|
|
||||||
|
|
||||||
runCLI(
|
runCLI(
|
||||||
`generate @nx/angular:lib ${libName} --standalone --buildable --project-name-and-root-format=derived`
|
`generate @nx/angular:lib ${libName} --standalone --buildable --project-name-and-root-format=derived`
|
||||||
@ -535,14 +526,9 @@ describe('Angular Projects', () => {
|
|||||||
`libs/${libName}/src/lib/${libName}/${libName}.component.ts`
|
`libs/${libName}/src/lib/${libName}/${libName}.component.ts`
|
||||||
);
|
);
|
||||||
// check build works
|
// check build works
|
||||||
expect(runCLI(`build ${libName}`)).toContain(
|
expect(() => runCLI(`build ${libName}`)).not.toThrow();
|
||||||
`Successfully ran target build for project ${libName}`
|
|
||||||
);
|
|
||||||
// check tests pass
|
// check tests pass
|
||||||
const libTestResult = runCLI(`test ${libName}`);
|
expect(() => runCLI(`test ${libName}`)).not.toThrow();
|
||||||
expect(libTestResult).toContain(
|
|
||||||
`Successfully ran target test for project ${libName}`
|
|
||||||
);
|
|
||||||
}, 500_000);
|
}, 500_000);
|
||||||
|
|
||||||
it('should support generating libraries with a scoped name when --project-name-and-root-format=as-provided', () => {
|
it('should support generating libraries with a scoped name when --project-name-and-root-format=as-provided', () => {
|
||||||
@ -568,14 +554,9 @@ describe('Angular Projects', () => {
|
|||||||
}.component.ts`
|
}.component.ts`
|
||||||
);
|
);
|
||||||
// check build works
|
// check build works
|
||||||
expect(runCLI(`build ${libName}`)).toContain(
|
expect(() => runCLI(`build ${libName}`)).not.toThrow();
|
||||||
`Successfully ran target build for project ${libName}`
|
|
||||||
);
|
|
||||||
// check tests pass
|
// check tests pass
|
||||||
const libTestResult = runCLI(`test ${libName}`);
|
expect(() => runCLI(`test ${libName}`)).not.toThrow();
|
||||||
expect(libTestResult).toContain(
|
|
||||||
`Successfully ran target test for project ${libName}`
|
|
||||||
);
|
|
||||||
}, 500_000);
|
}, 500_000);
|
||||||
|
|
||||||
it('should support generating applications with SSR and converting targets with webpack-based executors to use the application executor', async () => {
|
it('should support generating applications with SSR and converting targets with webpack-based executors to use the application executor', async () => {
|
||||||
|
|||||||
@ -162,6 +162,7 @@ describe('EsBuild Plugin', () => {
|
|||||||
expect(
|
expect(
|
||||||
readJson(`dist/libs/${parentLib}/package.json`).dependencies
|
readJson(`dist/libs/${parentLib}/package.json`).dependencies
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
'jsonc-eslint-parser': expect.any(String),
|
||||||
// Don't care about the versions, just that they exist
|
// Don't care about the versions, just that they exist
|
||||||
rambda: expect.any(String),
|
rambda: expect.any(String),
|
||||||
lodash: expect.any(String),
|
lodash: expect.any(String),
|
||||||
|
|||||||
@ -14,14 +14,17 @@ import {
|
|||||||
} from '@nx/e2e/utils';
|
} from '@nx/e2e/utils';
|
||||||
|
|
||||||
describe('Linter (legacy)', () => {
|
describe('Linter (legacy)', () => {
|
||||||
describe('Integrated', () => {
|
describe('Integrated (eslintrc config)', () => {
|
||||||
|
let originalEslintUseFlatConfigVal: string | undefined;
|
||||||
const myapp = uniq('myapp');
|
const myapp = uniq('myapp');
|
||||||
const mylib = uniq('mylib');
|
const mylib = uniq('mylib');
|
||||||
|
|
||||||
let projScope;
|
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
projScope = newProject({
|
// Opt into legacy .eslintrc config format for these tests
|
||||||
|
originalEslintUseFlatConfigVal = process.env.ESLINT_USE_FLAT_CONFIG;
|
||||||
|
process.env.ESLINT_USE_FLAT_CONFIG = 'false';
|
||||||
|
|
||||||
|
newProject({
|
||||||
packages: ['@nx/react', '@nx/js', '@nx/eslint'],
|
packages: ['@nx/react', '@nx/js', '@nx/eslint'],
|
||||||
});
|
});
|
||||||
runCLI(`generate @nx/react:app ${myapp} --tags=validtag`, {
|
runCLI(`generate @nx/react:app ${myapp} --tags=validtag`, {
|
||||||
@ -31,7 +34,10 @@ describe('Linter (legacy)', () => {
|
|||||||
env: { NX_ADD_PLUGINS: 'false' },
|
env: { NX_ADD_PLUGINS: 'false' },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
afterAll(() => cleanupProject());
|
afterAll(() => {
|
||||||
|
process.env.ESLINT_USE_FLAT_CONFIG = originalEslintUseFlatConfigVal;
|
||||||
|
cleanupProject();
|
||||||
|
});
|
||||||
|
|
||||||
describe('linting errors', () => {
|
describe('linting errors', () => {
|
||||||
let defaultEslintrc;
|
let defaultEslintrc;
|
||||||
@ -58,8 +64,7 @@ describe('Linter (legacy)', () => {
|
|||||||
updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2));
|
updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2));
|
||||||
|
|
||||||
// 1. linting should error when rules are not followed
|
// 1. linting should error when rules are not followed
|
||||||
let out = runCLI(`lint ${myapp}`, { silenceError: true });
|
expect(() => runCLI(`lint ${myapp}`)).toThrow();
|
||||||
expect(out).toContain('Unexpected console statement');
|
|
||||||
|
|
||||||
// 2. linting should not error when rules are not followed and the force flag is specified
|
// 2. linting should not error when rules are not followed and the force flag is specified
|
||||||
expect(() => runCLI(`lint ${myapp} --force`)).not.toThrow();
|
expect(() => runCLI(`lint ${myapp} --force`)).not.toThrow();
|
||||||
@ -72,8 +77,9 @@ describe('Linter (legacy)', () => {
|
|||||||
updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2));
|
updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2));
|
||||||
|
|
||||||
// 3. linting should not error when all rules are followed
|
// 3. linting should not error when all rules are followed
|
||||||
out = runCLI(`lint ${myapp}`, { silenceError: true });
|
expect(() =>
|
||||||
expect(out).toContain('All files pass linting');
|
runCLI(`lint ${myapp}`, { silenceError: true })
|
||||||
|
).not.toThrow();
|
||||||
}, 1000000);
|
}, 1000000);
|
||||||
|
|
||||||
it('should print the effective configuration for a file specified using --print-config', () => {
|
it('should print the effective configuration for a file specified using --print-config', () => {
|
||||||
@ -86,6 +92,7 @@ describe('Linter (legacy)', () => {
|
|||||||
});
|
});
|
||||||
updateFile('.eslintrc.json', JSON.stringify(eslint, null, 2));
|
updateFile('.eslintrc.json', JSON.stringify(eslint, null, 2));
|
||||||
const out = runCLI(`lint ${myapp} --print-config src/index.ts`, {
|
const out = runCLI(`lint ${myapp} --print-config src/index.ts`, {
|
||||||
|
env: { CI: 'false' }, // We don't want to show the summary table from cloud runner
|
||||||
silenceError: true,
|
silenceError: true,
|
||||||
});
|
});
|
||||||
expect(out).toContain('"specific-rule": [');
|
expect(out).toContain('"specific-rule": [');
|
||||||
@ -93,9 +100,19 @@ describe('Linter (legacy)', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Flat config', () => {
|
describe('eslintrc convert to flat config', () => {
|
||||||
|
let originalEslintUseFlatConfigVal: string | undefined;
|
||||||
const packageManager = getSelectedPackageManager() || 'pnpm';
|
const packageManager = getSelectedPackageManager() || 'pnpm';
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
// Opt into legacy .eslintrc config format for these tests
|
||||||
|
originalEslintUseFlatConfigVal = process.env.ESLINT_USE_FLAT_CONFIG;
|
||||||
|
process.env.ESLINT_USE_FLAT_CONFIG = 'false';
|
||||||
|
});
|
||||||
|
afterAll(() => {
|
||||||
|
process.env.ESLINT_USE_FLAT_CONFIG = originalEslintUseFlatConfigVal;
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
process.env.NX_ADD_PLUGINS = 'false';
|
process.env.NX_ADD_PLUGINS = 'false';
|
||||||
});
|
});
|
||||||
@ -162,7 +179,9 @@ describe('Linter (legacy)', () => {
|
|||||||
const outFlat = runCLI(`affected -t lint`, {
|
const outFlat = runCLI(`affected -t lint`, {
|
||||||
silenceError: true,
|
silenceError: true,
|
||||||
});
|
});
|
||||||
expect(outFlat).toContain('ran target lint');
|
expect(outFlat).toContain(`${myapp}:lint`);
|
||||||
|
expect(outFlat).toContain(`${mylib}:lint`);
|
||||||
|
expect(outFlat).toContain(`${mylib2}:lint`);
|
||||||
}, 1000000);
|
}, 1000000);
|
||||||
|
|
||||||
it('should convert standalone to flat config', () => {
|
it('should convert standalone to flat config', () => {
|
||||||
@ -199,7 +218,8 @@ describe('Linter (legacy)', () => {
|
|||||||
const outFlat = runCLI(`affected -t lint`, {
|
const outFlat = runCLI(`affected -t lint`, {
|
||||||
silenceError: true,
|
silenceError: true,
|
||||||
});
|
});
|
||||||
expect(outFlat).toContain('ran target lint');
|
expect(outFlat).toContain(`${myapp}:lint`);
|
||||||
|
expect(outFlat).toContain(`${mylib}:lint`);
|
||||||
}, 1000000);
|
}, 1000000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -15,6 +15,16 @@ import {
|
|||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
describe('Linter', () => {
|
describe('Linter', () => {
|
||||||
|
let originalEslintUseFlatConfigVal: string | undefined;
|
||||||
|
beforeAll(() => {
|
||||||
|
// Opt into legacy .eslintrc config format for these tests
|
||||||
|
originalEslintUseFlatConfigVal = process.env.ESLINT_USE_FLAT_CONFIG;
|
||||||
|
process.env.ESLINT_USE_FLAT_CONFIG = 'false';
|
||||||
|
});
|
||||||
|
afterAll(() => {
|
||||||
|
process.env.ESLINT_USE_FLAT_CONFIG = originalEslintUseFlatConfigVal;
|
||||||
|
});
|
||||||
|
|
||||||
describe('Integrated', () => {
|
describe('Integrated', () => {
|
||||||
const myapp = uniq('myapp');
|
const myapp = uniq('myapp');
|
||||||
const mylib = uniq('mylib');
|
const mylib = uniq('mylib');
|
||||||
@ -54,7 +64,10 @@ describe('Linter', () => {
|
|||||||
});
|
});
|
||||||
updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2));
|
updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2));
|
||||||
|
|
||||||
let out = runCLI(`lint ${myapp}`, { silenceError: true });
|
let out = runCLI(`lint ${myapp}`, {
|
||||||
|
silenceError: true,
|
||||||
|
env: { CI: 'false' },
|
||||||
|
});
|
||||||
expect(out).toContain('Unexpected console statement');
|
expect(out).toContain('Unexpected console statement');
|
||||||
|
|
||||||
eslintrc.overrides.forEach((override) => {
|
eslintrc.overrides.forEach((override) => {
|
||||||
@ -65,7 +78,10 @@ describe('Linter', () => {
|
|||||||
updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2));
|
updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2));
|
||||||
|
|
||||||
// 3. linting should not error when all rules are followed
|
// 3. linting should not error when all rules are followed
|
||||||
out = runCLI(`lint ${myapp}`, { silenceError: true });
|
out = runCLI(`lint ${myapp}`, {
|
||||||
|
silenceError: true,
|
||||||
|
env: { CI: 'false' },
|
||||||
|
});
|
||||||
expect(out).toContain('Successfully ran target lint');
|
expect(out).toContain('Successfully ran target lint');
|
||||||
}, 1000000);
|
}, 1000000);
|
||||||
|
|
||||||
@ -80,7 +96,10 @@ describe('Linter', () => {
|
|||||||
// should generate a default cache file
|
// should generate a default cache file
|
||||||
let cachePath = path.join('apps', myapp, '.eslintcache');
|
let cachePath = path.join('apps', myapp, '.eslintcache');
|
||||||
expect(() => checkFilesExist(cachePath)).toThrow();
|
expect(() => checkFilesExist(cachePath)).toThrow();
|
||||||
runCLI(`lint ${myapp} --cache`, { silenceError: true });
|
runCLI(`lint ${myapp} --cache`, {
|
||||||
|
silenceError: true,
|
||||||
|
env: { CI: 'false' },
|
||||||
|
});
|
||||||
expect(() => checkFilesExist(cachePath)).not.toThrow();
|
expect(() => checkFilesExist(cachePath)).not.toThrow();
|
||||||
expect(readCacheFile(cachePath)).toContain(
|
expect(readCacheFile(cachePath)).toContain(
|
||||||
path.normalize(`${myapp}/src/app/app.spec.tsx`)
|
path.normalize(`${myapp}/src/app/app.spec.tsx`)
|
||||||
@ -91,6 +110,7 @@ describe('Linter', () => {
|
|||||||
expect(() => checkFilesExist(cachePath)).toThrow();
|
expect(() => checkFilesExist(cachePath)).toThrow();
|
||||||
runCLI(`lint ${myapp} --cache --cache-location="my-cache"`, {
|
runCLI(`lint ${myapp} --cache --cache-location="my-cache"`, {
|
||||||
silenceError: true,
|
silenceError: true,
|
||||||
|
env: { CI: 'false' },
|
||||||
});
|
});
|
||||||
expect(() => checkFilesExist(cachePath)).not.toThrow();
|
expect(() => checkFilesExist(cachePath)).not.toThrow();
|
||||||
expect(readCacheFile(cachePath)).toContain(
|
expect(readCacheFile(cachePath)).toContain(
|
||||||
@ -116,6 +136,7 @@ describe('Linter', () => {
|
|||||||
`lint ${myapp} --output-file="${outputFile}" --format=json`,
|
`lint ${myapp} --output-file="${outputFile}" --format=json`,
|
||||||
{
|
{
|
||||||
silenceError: true,
|
silenceError: true,
|
||||||
|
env: { CI: 'false' },
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
expect(stdout).not.toContain('Unexpected console statement');
|
expect(stdout).not.toContain('Unexpected console statement');
|
||||||
@ -147,8 +168,7 @@ describe('Linter', () => {
|
|||||||
runCLI(`generate @nx/eslint:workspace-rule ${newRuleName}`);
|
runCLI(`generate @nx/eslint:workspace-rule ${newRuleName}`);
|
||||||
|
|
||||||
// Ensure that the unit tests for the new rule are runnable
|
// Ensure that the unit tests for the new rule are runnable
|
||||||
const unitTestsOutput = runCLI(`test eslint-rules`);
|
expect(() => runCLI(`test eslint-rules`)).not.toThrow();
|
||||||
expect(unitTestsOutput).toContain('Successfully ran target test');
|
|
||||||
|
|
||||||
// Update the rule for the e2e test so that we can assert that it produces the expected lint failure when used
|
// Update the rule for the e2e test so that we can assert that it produces the expected lint failure when used
|
||||||
const knownLintErrorMessage = 'e2e test known error message';
|
const knownLintErrorMessage = 'e2e test known error message';
|
||||||
@ -177,6 +197,7 @@ describe('Linter', () => {
|
|||||||
|
|
||||||
const lintOutput = runCLI(`lint ${myapp} --verbose`, {
|
const lintOutput = runCLI(`lint ${myapp} --verbose`, {
|
||||||
silenceError: true,
|
silenceError: true,
|
||||||
|
env: { CI: 'false' },
|
||||||
});
|
});
|
||||||
expect(lintOutput).toContain(newRuleNameForUsage);
|
expect(lintOutput).toContain(newRuleNameForUsage);
|
||||||
expect(lintOutput).toContain(knownLintErrorMessage);
|
expect(lintOutput).toContain(knownLintErrorMessage);
|
||||||
@ -232,7 +253,10 @@ describe('Linter', () => {
|
|||||||
`
|
`
|
||||||
);
|
);
|
||||||
|
|
||||||
const out = runCLI(`lint ${myapp}`, { silenceError: true });
|
const out = runCLI(`lint ${myapp}`, {
|
||||||
|
silenceError: true,
|
||||||
|
env: { CI: 'false' },
|
||||||
|
});
|
||||||
expect(out).toContain(
|
expect(out).toContain(
|
||||||
'Projects cannot be imported by a relative or absolute path, and must begin with a npm scope'
|
'Projects cannot be imported by a relative or absolute path, and must begin with a npm scope'
|
||||||
);
|
);
|
||||||
@ -379,6 +403,7 @@ describe('Linter', () => {
|
|||||||
it('should fix noSelfCircularDependencies', () => {
|
it('should fix noSelfCircularDependencies', () => {
|
||||||
const stdout = runCLI(`lint ${libC}`, {
|
const stdout = runCLI(`lint ${libC}`, {
|
||||||
silenceError: true,
|
silenceError: true,
|
||||||
|
env: { CI: 'false' },
|
||||||
});
|
});
|
||||||
expect(stdout).toContain(
|
expect(stdout).toContain(
|
||||||
'Projects should use relative imports to import from other files within the same project'
|
'Projects should use relative imports to import from other files within the same project'
|
||||||
@ -387,6 +412,7 @@ describe('Linter', () => {
|
|||||||
// fix them
|
// fix them
|
||||||
const fixedStout = runCLI(`lint ${libC} --fix`, {
|
const fixedStout = runCLI(`lint ${libC} --fix`, {
|
||||||
silenceError: true,
|
silenceError: true,
|
||||||
|
env: { CI: 'false' },
|
||||||
});
|
});
|
||||||
expect(fixedStout).toContain(
|
expect(fixedStout).toContain(
|
||||||
`Successfully ran target lint for project ${libC}`
|
`Successfully ran target lint for project ${libC}`
|
||||||
@ -407,6 +433,7 @@ describe('Linter', () => {
|
|||||||
it('should fix noRelativeOrAbsoluteImportsAcrossLibraries', () => {
|
it('should fix noRelativeOrAbsoluteImportsAcrossLibraries', () => {
|
||||||
const stdout = runCLI(`lint ${libB}`, {
|
const stdout = runCLI(`lint ${libB}`, {
|
||||||
silenceError: true,
|
silenceError: true,
|
||||||
|
env: { CI: 'false' },
|
||||||
});
|
});
|
||||||
expect(stdout).toContain(
|
expect(stdout).toContain(
|
||||||
'Projects cannot be imported by a relative or absolute path, and must begin with a npm scope'
|
'Projects cannot be imported by a relative or absolute path, and must begin with a npm scope'
|
||||||
@ -415,6 +442,7 @@ describe('Linter', () => {
|
|||||||
// fix them
|
// fix them
|
||||||
const fixedStout = runCLI(`lint ${libB} --fix`, {
|
const fixedStout = runCLI(`lint ${libB} --fix`, {
|
||||||
silenceError: true,
|
silenceError: true,
|
||||||
|
env: { CI: 'false' },
|
||||||
});
|
});
|
||||||
expect(fixedStout).toContain(
|
expect(fixedStout).toContain(
|
||||||
`Successfully ran target lint for project ${libB}`
|
`Successfully ran target lint for project ${libB}`
|
||||||
@ -468,7 +496,10 @@ describe('Linter', () => {
|
|||||||
const nxVersion = rootPackageJson.devDependencies.nx;
|
const nxVersion = rootPackageJson.devDependencies.nx;
|
||||||
const tslibVersion = rootPackageJson.dependencies['tslib'];
|
const tslibVersion = rootPackageJson.dependencies['tslib'];
|
||||||
|
|
||||||
let out = runCLI(`lint ${mylib}`, { silenceError: true });
|
let out = runCLI(`lint ${mylib}`, {
|
||||||
|
silenceError: true,
|
||||||
|
env: { CI: 'false' },
|
||||||
|
});
|
||||||
expect(out).toContain('Successfully ran target lint');
|
expect(out).toContain('Successfully ran target lint');
|
||||||
|
|
||||||
// make an explict dependency to nx
|
// make an explict dependency to nx
|
||||||
@ -485,7 +516,10 @@ describe('Linter', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// output should now report missing dependency and obsolete dependency
|
// output should now report missing dependency and obsolete dependency
|
||||||
out = runCLI(`lint ${mylib}`, { silenceError: true });
|
out = runCLI(`lint ${mylib}`, {
|
||||||
|
silenceError: true,
|
||||||
|
env: { CI: 'false' },
|
||||||
|
});
|
||||||
expect(out).toContain('they are missing');
|
expect(out).toContain('they are missing');
|
||||||
expect(out).toContain('@nx/devkit');
|
expect(out).toContain('@nx/devkit');
|
||||||
expect(out).toContain(
|
expect(out).toContain(
|
||||||
@ -493,7 +527,10 @@ describe('Linter', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// should fix the missing and obsolete dependency issues
|
// should fix the missing and obsolete dependency issues
|
||||||
out = runCLI(`lint ${mylib} --fix`, { silenceError: true });
|
out = runCLI(`lint ${mylib} --fix`, {
|
||||||
|
silenceError: true,
|
||||||
|
env: { CI: 'false' },
|
||||||
|
});
|
||||||
expect(out).toContain(
|
expect(out).toContain(
|
||||||
`Successfully ran target lint for project ${mylib}`
|
`Successfully ran target lint for project ${mylib}`
|
||||||
);
|
);
|
||||||
@ -518,13 +555,19 @@ describe('Linter', () => {
|
|||||||
json.dependencies['@nx/devkit'] = '100.0.0';
|
json.dependencies['@nx/devkit'] = '100.0.0';
|
||||||
return json;
|
return json;
|
||||||
});
|
});
|
||||||
out = runCLI(`lint ${mylib}`, { silenceError: true });
|
out = runCLI(`lint ${mylib}`, {
|
||||||
|
silenceError: true,
|
||||||
|
env: { CI: 'false' },
|
||||||
|
});
|
||||||
expect(out).toContain(
|
expect(out).toContain(
|
||||||
'version specifier does not contain the installed version of "@nx/devkit"'
|
'version specifier does not contain the installed version of "@nx/devkit"'
|
||||||
);
|
);
|
||||||
|
|
||||||
// should fix the version mismatch issue
|
// should fix the version mismatch issue
|
||||||
out = runCLI(`lint ${mylib} --fix`, { silenceError: true });
|
out = runCLI(`lint ${mylib} --fix`, {
|
||||||
|
silenceError: true,
|
||||||
|
env: { CI: 'false' },
|
||||||
|
});
|
||||||
expect(out).toContain(
|
expect(out).toContain(
|
||||||
`Successfully ran target lint for project ${mylib}`
|
`Successfully ran target lint for project ${mylib}`
|
||||||
);
|
);
|
||||||
@ -532,8 +575,15 @@ describe('Linter', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('flat config', () => {
|
describe('flat config', () => {
|
||||||
|
let envVar: string | undefined;
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
runCLI(`generate @nx/eslint:convert-to-flat-config`);
|
runCLI(`generate @nx/eslint:convert-to-flat-config`);
|
||||||
|
envVar = process.env.ESLINT_USE_FLAT_CONFIG;
|
||||||
|
// Now that we have converted the existing configs to flat config we need to clear the explicitly set env var to allow it to infer things from the root config file type
|
||||||
|
delete process.env.ESLINT_USE_FLAT_CONFIG;
|
||||||
|
});
|
||||||
|
afterAll(() => {
|
||||||
|
process.env.ESLINT_USE_FLAT_CONFIG = envVar;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate new projects using flat config', () => {
|
it('should generate new projects using flat config', () => {
|
||||||
@ -557,14 +607,8 @@ describe('Linter', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// validate that the new projects are linted successfully
|
// validate that the new projects are linted successfully
|
||||||
let output = runCLI(`lint ${reactLib}`);
|
expect(() => runCLI(`lint ${reactLib}`)).not.toThrow();
|
||||||
expect(output).toContain(
|
expect(() => runCLI(`lint ${jsLib}`)).not.toThrow();
|
||||||
`Successfully ran target lint for project ${reactLib}`
|
|
||||||
);
|
|
||||||
output = runCLI(`lint ${jsLib}`);
|
|
||||||
expect(output).toContain(
|
|
||||||
`Successfully ran target lint for project ${jsLib}`
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -578,12 +622,12 @@ describe('Linter', () => {
|
|||||||
afterEach(() => cleanupProject());
|
afterEach(() => cleanupProject());
|
||||||
|
|
||||||
function verifySuccessfulStandaloneSetup(myapp: string) {
|
function verifySuccessfulStandaloneSetup(myapp: string) {
|
||||||
expect(runCLI(`lint ${myapp}`, { silenceError: true })).toContain(
|
expect(
|
||||||
'Successfully ran target lint'
|
runCLI(`lint ${myapp}`, { silenceError: true, env: { CI: 'false' } })
|
||||||
);
|
).toContain('Successfully ran target lint');
|
||||||
expect(runCLI(`lint e2e`, { silenceError: true })).toContain(
|
expect(
|
||||||
'Successfully ran target lint'
|
runCLI(`lint e2e`, { silenceError: true, env: { CI: 'false' } })
|
||||||
);
|
).toContain('Successfully ran target lint');
|
||||||
expect(() => checkFilesExist(`.eslintrc.base.json`)).toThrow();
|
expect(() => checkFilesExist(`.eslintrc.base.json`)).toThrow();
|
||||||
|
|
||||||
const rootEslint = readJson('.eslintrc.json');
|
const rootEslint = readJson('.eslintrc.json');
|
||||||
@ -595,15 +639,15 @@ describe('Linter', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function verifySuccessfulMigratedSetup(myapp: string, mylib: string) {
|
function verifySuccessfulMigratedSetup(myapp: string, mylib: string) {
|
||||||
expect(runCLI(`lint ${myapp}`, { silenceError: true })).toContain(
|
expect(
|
||||||
'Successfully ran target lint'
|
runCLI(`lint ${myapp}`, { silenceError: true, env: { CI: 'false' } })
|
||||||
);
|
).toContain('Successfully ran target lint');
|
||||||
expect(runCLI(`lint e2e`, { silenceError: true })).toContain(
|
expect(
|
||||||
'Successfully ran target lint'
|
runCLI(`lint e2e`, { silenceError: true, env: { CI: 'false' } })
|
||||||
);
|
).toContain('Successfully ran target lint');
|
||||||
expect(runCLI(`lint ${mylib}`, { silenceError: true })).toContain(
|
expect(
|
||||||
'Successfully ran target lint'
|
runCLI(`lint ${mylib}`, { silenceError: true, env: { CI: 'false' } })
|
||||||
);
|
).toContain('Successfully ran target lint');
|
||||||
expect(() => checkFilesExist(`.eslintrc.base.json`)).not.toThrow();
|
expect(() => checkFilesExist(`.eslintrc.base.json`)).not.toThrow();
|
||||||
|
|
||||||
const rootEslint = readJson('.eslintrc.base.json');
|
const rootEslint = readJson('.eslintrc.base.json');
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
checkFilesExist,
|
checkFilesExist,
|
||||||
cleanupProject,
|
cleanupProject,
|
||||||
expectTestsPass,
|
|
||||||
getPackageManagerCommand,
|
getPackageManagerCommand,
|
||||||
killPorts,
|
killPorts,
|
||||||
newProject,
|
newProject,
|
||||||
@ -64,8 +63,8 @@ describe('@nx/expo (legacy)', () => {
|
|||||||
return updated;
|
return updated;
|
||||||
});
|
});
|
||||||
|
|
||||||
expectTestsPass(await runCLIAsync(`test ${appName}`));
|
expect(() => runCLI(`test ${appName}`)).not.toThrow();
|
||||||
expectTestsPass(await runCLIAsync(`test ${libName}`));
|
expect(() => runCLI(`test ${libName}`)).not.toThrow();
|
||||||
|
|
||||||
const appLintResults = await runCLIAsync(`lint ${appName}`);
|
const appLintResults = await runCLIAsync(`lint ${appName}`);
|
||||||
expect(appLintResults.combinedOutput).toContain(
|
expect(appLintResults.combinedOutput).toContain(
|
||||||
|
|||||||
@ -16,10 +16,7 @@ describe('Jest root projects', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should test root level app projects', async () => {
|
it('should test root level app projects', async () => {
|
||||||
const rootProjectTestResults = await runCLIAsync(`test ${myapp}`);
|
expect(() => runCLI(`test ${myapp}`)).not.toThrow();
|
||||||
expect(rootProjectTestResults.combinedOutput).toContain(
|
|
||||||
'Test Suites: 1 passed, 1 total'
|
|
||||||
);
|
|
||||||
}, 300_000);
|
}, 300_000);
|
||||||
|
|
||||||
it('should add lib project and tests should still work', async () => {
|
it('should add lib project and tests should still work', async () => {
|
||||||
@ -27,17 +24,8 @@ describe('Jest root projects', () => {
|
|||||||
`generate @nx/angular:lib ${mylib} --projectNameAndRootFormat as-provided --no-interactive`
|
`generate @nx/angular:lib ${mylib} --projectNameAndRootFormat as-provided --no-interactive`
|
||||||
);
|
);
|
||||||
|
|
||||||
const libProjectTestResults = await runCLIAsync(`test ${mylib}`);
|
expect(() => runCLI(`test ${mylib}`)).not.toThrow();
|
||||||
|
expect(() => runCLI(`test ${myapp}`)).not.toThrow();
|
||||||
expect(libProjectTestResults.combinedOutput).toContain(
|
|
||||||
'Test Suites: 1 passed, 1 total'
|
|
||||||
);
|
|
||||||
|
|
||||||
const rootProjectTestResults = await runCLIAsync(`test ${myapp}`);
|
|
||||||
|
|
||||||
expect(rootProjectTestResults.combinedOutput).toContain(
|
|
||||||
'Test Suites: 1 passed, 1 total'
|
|
||||||
);
|
|
||||||
}, 300_000);
|
}, 300_000);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -53,11 +41,7 @@ describe('Jest root projects', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should test root level app projects', async () => {
|
it('should test root level app projects', async () => {
|
||||||
const rootProjectTestResults = await runCLIAsync(`test ${myapp}`);
|
expect(() => runCLI(`test ${myapp}`)).not.toThrow();
|
||||||
|
|
||||||
expect(rootProjectTestResults.combinedOutput).toContain(
|
|
||||||
'Test Suites: 1 passed, 1 total'
|
|
||||||
);
|
|
||||||
}, 300_000);
|
}, 300_000);
|
||||||
|
|
||||||
it('should add lib project and tests should still work', async () => {
|
it('should add lib project and tests should still work', async () => {
|
||||||
@ -65,17 +49,8 @@ describe('Jest root projects', () => {
|
|||||||
`generate @nx/react:lib ${mylib} --unitTestRunner=jest --projectNameAndRootFormat as-provided`
|
`generate @nx/react:lib ${mylib} --unitTestRunner=jest --projectNameAndRootFormat as-provided`
|
||||||
);
|
);
|
||||||
|
|
||||||
const libProjectTestResults = await runCLIAsync(`test ${mylib}`);
|
expect(() => runCLI(`test ${mylib}`)).not.toThrow();
|
||||||
|
expect(() => runCLI(`test ${myapp}`)).not.toThrow();
|
||||||
expect(libProjectTestResults.combinedOutput).toContain(
|
|
||||||
'Test Suites: 1 passed, 1 total'
|
|
||||||
);
|
|
||||||
|
|
||||||
const rootProjectTestResults = await runCLIAsync(`test ${myapp}`);
|
|
||||||
|
|
||||||
expect(rootProjectTestResults.combinedOutput).toContain(
|
|
||||||
'Test Suites: 1 passed, 1 total'
|
|
||||||
);
|
|
||||||
}, 300_000);
|
}, 300_000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
getPackageManagerCommand,
|
getPackageManagerCommand,
|
||||||
readJson,
|
readJson,
|
||||||
updateFile,
|
updateFile,
|
||||||
|
renameFile,
|
||||||
} from '@nx/e2e/utils';
|
} from '@nx/e2e/utils';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
@ -169,6 +170,16 @@ describe('packaging libs', () => {
|
|||||||
`libs/${swcEsmLib}/src/index.ts`,
|
`libs/${swcEsmLib}/src/index.ts`,
|
||||||
`export * from './lib/${swcEsmLib}.js';`
|
`export * from './lib/${swcEsmLib}.js';`
|
||||||
);
|
);
|
||||||
|
// We also need to update the eslint config file extensions to be explicitly commonjs
|
||||||
|
// TODO: re-evaluate this once we support ESM eslint configs
|
||||||
|
renameFile(
|
||||||
|
`libs/${tscEsmLib}/eslint.config.js`,
|
||||||
|
`libs/${tscEsmLib}/eslint.config.cjs`
|
||||||
|
);
|
||||||
|
renameFile(
|
||||||
|
`libs/${swcEsmLib}/eslint.config.js`,
|
||||||
|
`libs/${swcEsmLib}/eslint.config.cjs`
|
||||||
|
);
|
||||||
|
|
||||||
// Add additional entry points for `exports` field
|
// Add additional entry points for `exports` field
|
||||||
updateJson(join('libs', tscLib, 'project.json'), (json) => {
|
updateJson(join('libs', tscLib, 'project.json'), (json) => {
|
||||||
|
|||||||
@ -30,22 +30,17 @@ describe('Nuxt Plugin', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should build application', async () => {
|
it('should build application', async () => {
|
||||||
const result = runCLI(`build ${app}`);
|
expect(() => runCLI(`build ${app}`)).not.toThrow();
|
||||||
expect(result).toContain(
|
|
||||||
`Successfully ran target build for project ${app}`
|
|
||||||
);
|
|
||||||
checkFilesExist(`${app}/.nuxt/nuxt.d.ts`);
|
checkFilesExist(`${app}/.nuxt/nuxt.d.ts`);
|
||||||
checkFilesExist(`${app}/.output/nitro.json`);
|
checkFilesExist(`${app}/.output/nitro.json`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should test application', async () => {
|
it('should test application', async () => {
|
||||||
const result = runCLI(`test ${app}`);
|
expect(() => runCLI(`test ${app}`)).not.toThrow();
|
||||||
expect(result).toContain(`Successfully ran target test for project ${app}`);
|
|
||||||
}, 150_000);
|
}, 150_000);
|
||||||
|
|
||||||
it('should lint application', async () => {
|
it('should lint application', async () => {
|
||||||
const result = runCLI(`lint ${app}`);
|
expect(() => runCLI(`lint ${app}`)).not.toThrow();
|
||||||
expect(result).toContain(`Successfully ran target lint for project ${app}`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should build storybook for app', () => {
|
it('should build storybook for app', () => {
|
||||||
|
|||||||
@ -120,28 +120,6 @@ describe('nx init (for React - legacy)', () => {
|
|||||||
process.env.SELECTED_PM = originalPM;
|
process.env.SELECTED_PM = originalPM;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should convert to a standalone workspace with craco (webpack)', () => {
|
|
||||||
const appName = 'my-app';
|
|
||||||
createReactApp(appName);
|
|
||||||
|
|
||||||
const craToNxOutput = runCommand(
|
|
||||||
`${
|
|
||||||
pmc.runUninstalledPackage
|
|
||||||
} nx@${getPublishedVersion()} init --no-interactive --vite=false`
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(craToNxOutput).toContain('🎉 Done!');
|
|
||||||
|
|
||||||
runCLI(`build ${appName}`, {
|
|
||||||
env: {
|
|
||||||
// since craco 7.1.0 the NODE_ENV is used, since the tests set it
|
|
||||||
// to "test" is causes an issue with React Refresh Babel
|
|
||||||
NODE_ENV: undefined,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
checkFilesExist(`dist/${appName}/index.html`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should convert to an standalone workspace with Vite', () => {
|
it('should convert to an standalone workspace with Vite', () => {
|
||||||
const appName = 'my-app';
|
const appName = 'my-app';
|
||||||
createReactApp(appName);
|
createReactApp(appName);
|
||||||
|
|||||||
@ -25,8 +25,8 @@ exports[`Extra Nx Misc Tests task graph inputs should correctly expand dependent
|
|||||||
"nx.json",
|
"nx.json",
|
||||||
],
|
],
|
||||||
"lib-base-123": [
|
"lib-base-123": [
|
||||||
"libs/lib-base-123/.eslintrc.json",
|
|
||||||
"libs/lib-base-123/README.md",
|
"libs/lib-base-123/README.md",
|
||||||
|
"libs/lib-base-123/eslint.config.js",
|
||||||
"libs/lib-base-123/jest.config.ts",
|
"libs/lib-base-123/jest.config.ts",
|
||||||
"libs/lib-base-123/package.json",
|
"libs/lib-base-123/package.json",
|
||||||
"libs/lib-base-123/project.json",
|
"libs/lib-base-123/project.json",
|
||||||
@ -38,8 +38,8 @@ exports[`Extra Nx Misc Tests task graph inputs should correctly expand dependent
|
|||||||
"libs/lib-base-123/tsconfig.spec.json",
|
"libs/lib-base-123/tsconfig.spec.json",
|
||||||
],
|
],
|
||||||
"lib-dependent-123": [
|
"lib-dependent-123": [
|
||||||
"libs/lib-dependent-123/.eslintrc.json",
|
|
||||||
"libs/lib-dependent-123/README.md",
|
"libs/lib-dependent-123/README.md",
|
||||||
|
"libs/lib-dependent-123/eslint.config.js",
|
||||||
"libs/lib-dependent-123/jest.config.ts",
|
"libs/lib-dependent-123/jest.config.ts",
|
||||||
"libs/lib-dependent-123/package.json",
|
"libs/lib-dependent-123/package.json",
|
||||||
"libs/lib-dependent-123/project.json",
|
"libs/lib-dependent-123/project.json",
|
||||||
|
|||||||
@ -60,9 +60,14 @@ describe('Nx Import', () => {
|
|||||||
execSync(`git commit -am "initial commit"`, {
|
execSync(`git commit -am "initial commit"`, {
|
||||||
cwd: tempViteProjectPath,
|
cwd: tempViteProjectPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
execSync(`git checkout -b main`, {
|
execSync(`git checkout -b main`, {
|
||||||
cwd: tempViteProjectPath,
|
cwd: tempViteProjectPath,
|
||||||
});
|
});
|
||||||
|
} catch {
|
||||||
|
// This fails if git is already configured to have `main` branch, but that's OK
|
||||||
|
}
|
||||||
|
|
||||||
const remote = tempViteProjectPath;
|
const remote = tempViteProjectPath;
|
||||||
const ref = 'main';
|
const ref = 'main';
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
checkFilesExist,
|
checkFilesExist,
|
||||||
cleanupProject,
|
cleanupProject,
|
||||||
expectTestsPass,
|
|
||||||
getPackageManagerCommand,
|
getPackageManagerCommand,
|
||||||
isOSX,
|
isOSX,
|
||||||
killProcessAndPorts,
|
killProcessAndPorts,
|
||||||
@ -52,8 +51,7 @@ describe('@nx/react-native (legacy)', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should build for web', async () => {
|
it('should build for web', async () => {
|
||||||
const results = runCLI(`build ${appName}`);
|
expect(() => runCLI(`build ${appName}`)).not.toThrow();
|
||||||
expect(results).toContain('Successfully ran target build');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should test and lint', async () => {
|
it('should test and lint', async () => {
|
||||||
@ -67,37 +65,28 @@ describe('@nx/react-native (legacy)', () => {
|
|||||||
return updated;
|
return updated;
|
||||||
});
|
});
|
||||||
|
|
||||||
expectTestsPass(await runCLIAsync(`test ${appName}`));
|
expect(() => runCLI(`test ${appName}`)).not.toThrow();
|
||||||
expectTestsPass(await runCLIAsync(`test ${libName}`));
|
expect(() => runCLI(`test ${libName}`)).not.toThrow();
|
||||||
|
expect(() => runCLI(`lint ${appName}`)).not.toThrow();
|
||||||
const appLintResults = await runCLIAsync(`lint ${appName}`);
|
expect(() => runCLI(`lint ${libName}`)).not.toThrow();
|
||||||
expect(appLintResults.combinedOutput).toContain(
|
|
||||||
'Successfully ran target lint'
|
|
||||||
);
|
|
||||||
|
|
||||||
const libLintResults = await runCLIAsync(`lint ${libName}`);
|
|
||||||
expect(libLintResults.combinedOutput).toContain(
|
|
||||||
'Successfully ran target lint'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should run e2e for cypress', async () => {
|
it('should run e2e for cypress', async () => {
|
||||||
if (runE2ETests()) {
|
if (runE2ETests()) {
|
||||||
let results = runCLI(`e2e ${appName}-e2e`);
|
expect(() => runCLI(`e2e ${appName}-e2e`)).not.toThrow();
|
||||||
expect(results).toContain('Successfully ran target e2e');
|
|
||||||
|
|
||||||
results = runCLI(`e2e ${appName}-e2e --configuration=ci`);
|
expect(() =>
|
||||||
expect(results).toContain('Successfully ran target e2e');
|
runCLI(`e2e ${appName}-e2e --configuration=ci`)
|
||||||
|
).not.toThrow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should bundle-ios', async () => {
|
it('should bundle-ios', async () => {
|
||||||
const iosBundleResult = await runCLIAsync(
|
expect(() =>
|
||||||
|
runCLI(
|
||||||
`bundle-ios ${appName} --sourcemapOutput=../../dist/apps/${appName}/ios/main.map`
|
`bundle-ios ${appName} --sourcemapOutput=../../dist/apps/${appName}/ios/main.map`
|
||||||
);
|
)
|
||||||
expect(iosBundleResult.combinedOutput).toContain(
|
).not.toThrow();
|
||||||
'Done writing bundle output'
|
|
||||||
);
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
checkFilesExist(`dist/apps/${appName}/ios/main.jsbundle`);
|
checkFilesExist(`dist/apps/${appName}/ios/main.jsbundle`);
|
||||||
checkFilesExist(`dist/apps/${appName}/ios/main.map`);
|
checkFilesExist(`dist/apps/${appName}/ios/main.map`);
|
||||||
@ -105,12 +94,12 @@ describe('@nx/react-native (legacy)', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should bundle-android', async () => {
|
it('should bundle-android', async () => {
|
||||||
const androidBundleResult = await runCLIAsync(
|
expect(() =>
|
||||||
|
runCLI(
|
||||||
`bundle-android ${appName} --sourcemapOutput=../../dist/apps/${appName}/android/main.map`
|
`bundle-android ${appName} --sourcemapOutput=../../dist/apps/${appName}/android/main.map`
|
||||||
);
|
)
|
||||||
expect(androidBundleResult.combinedOutput).toContain(
|
).not.toThrow();
|
||||||
'Done writing bundle output'
|
|
||||||
);
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
checkFilesExist(`dist/apps/${appName}/android/main.jsbundle`);
|
checkFilesExist(`dist/apps/${appName}/android/main.jsbundle`);
|
||||||
checkFilesExist(`dist/apps/${appName}/android/main.map`);
|
checkFilesExist(`dist/apps/${appName}/android/main.map`);
|
||||||
@ -283,10 +272,7 @@ describe('@nx/react-native (legacy)', () => {
|
|||||||
// using the project name as the directory when no directory is provided
|
// using the project name as the directory when no directory is provided
|
||||||
checkFilesExist(`${appName}/src/app/App.tsx`);
|
checkFilesExist(`${appName}/src/app/App.tsx`);
|
||||||
// check tests pass
|
// check tests pass
|
||||||
const appTestResult = runCLI(`test ${appName}`);
|
expect(() => runCLI(`test ${appName}`)).not.toThrow();
|
||||||
expect(appTestResult).toContain(
|
|
||||||
`Successfully ran target test for project ${appName}`
|
|
||||||
);
|
|
||||||
|
|
||||||
// assert scoped project names are not supported when --project-name-and-root-format=derived
|
// assert scoped project names are not supported when --project-name-and-root-format=derived
|
||||||
expect(() =>
|
expect(() =>
|
||||||
@ -303,10 +289,7 @@ describe('@nx/react-native (legacy)', () => {
|
|||||||
// using the project name as the directory when no directory is provided
|
// using the project name as the directory when no directory is provided
|
||||||
checkFilesExist(`${libName}/src/index.ts`);
|
checkFilesExist(`${libName}/src/index.ts`);
|
||||||
// check tests pass
|
// check tests pass
|
||||||
const libTestResult = runCLI(`test ${libName}`);
|
expect(() => runCLI(`test ${libName}`)).not.toThrow();
|
||||||
expect(libTestResult).toContain(
|
|
||||||
`Successfully ran target test for project ${libName}`
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should run build with vite bundler and e2e with playwright', async () => {
|
it('should run build with vite bundler and e2e with playwright', async () => {
|
||||||
@ -314,11 +297,9 @@ describe('@nx/react-native (legacy)', () => {
|
|||||||
runCLI(
|
runCLI(
|
||||||
`generate @nx/react-native:application ${appName2} --bundler=vite --e2eTestRunner=playwright --install=false --no-interactive`
|
`generate @nx/react-native:application ${appName2} --bundler=vite --e2eTestRunner=playwright --install=false --no-interactive`
|
||||||
);
|
);
|
||||||
const buildResults = runCLI(`build ${appName2}`);
|
expect(() => runCLI(`build ${appName2}`)).not.toThrow();
|
||||||
expect(buildResults).toContain('Successfully ran target build');
|
|
||||||
if (runE2ETests()) {
|
if (runE2ETests()) {
|
||||||
const e2eResults = runCLI(`e2e ${appName2}-e2e`);
|
expect(() => runCLI(`e2e ${appName2}-e2e`)).not.toThrow();
|
||||||
expect(e2eResults).toContain('Successfully ran target e2e');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
runCLI(
|
runCLI(
|
||||||
|
|||||||
@ -25,14 +25,12 @@ describe('@nx/react-native', () => {
|
|||||||
afterAll(() => cleanupProject());
|
afterAll(() => cleanupProject());
|
||||||
|
|
||||||
it('should bundle the app', async () => {
|
it('should bundle the app', async () => {
|
||||||
const result = runCLI(
|
expect(() =>
|
||||||
|
runCLI(
|
||||||
`bundle ${appName} --platform=ios --bundle-output=dist.js --entry-file=src/main.tsx`
|
`bundle ${appName} --platform=ios --bundle-output=dist.js --entry-file=src/main.tsx`
|
||||||
);
|
)
|
||||||
|
).not.toThrow();
|
||||||
fileExists(` ${appName}/dist.js`);
|
fileExists(` ${appName}/dist.js`);
|
||||||
|
|
||||||
expect(result).toContain(
|
|
||||||
`Successfully ran target bundle for project ${appName}`
|
|
||||||
);
|
|
||||||
}, 200_000);
|
}, 200_000);
|
||||||
|
|
||||||
it('should start the app', async () => {
|
it('should start the app', async () => {
|
||||||
@ -87,11 +85,11 @@ describe('@nx/react-native', () => {
|
|||||||
|
|
||||||
it('should run e2e for cypress', async () => {
|
it('should run e2e for cypress', async () => {
|
||||||
if (runE2ETests()) {
|
if (runE2ETests()) {
|
||||||
let results = runCLI(`e2e ${appName}-e2e`);
|
expect(() => runCLI(`e2e ${appName}-e2e`)).not.toThrow();
|
||||||
expect(results).toContain('Successfully ran target e2e');
|
|
||||||
|
|
||||||
results = runCLI(`e2e ${appName}-e2e --configuration=ci`);
|
expect(() =>
|
||||||
expect(results).toContain('Successfully ran target e2e');
|
runCLI(`e2e ${appName}-e2e --configuration=ci`)
|
||||||
|
).not.toThrow();
|
||||||
|
|
||||||
// port and process cleanup
|
// port and process cleanup
|
||||||
try {
|
try {
|
||||||
@ -119,11 +117,9 @@ describe('@nx/react-native', () => {
|
|||||||
runCLI(
|
runCLI(
|
||||||
`generate @nx/react-native:application ${appName2} --bundler=vite --e2eTestRunner=playwright --install=false --no-interactive`
|
`generate @nx/react-native:application ${appName2} --bundler=vite --e2eTestRunner=playwright --install=false --no-interactive`
|
||||||
);
|
);
|
||||||
const buildResults = runCLI(`build ${appName2}`);
|
expect(() => runCLI(`build ${appName2}`)).not.toThrow();
|
||||||
expect(buildResults).toContain('Successfully ran target build');
|
|
||||||
if (runE2ETests()) {
|
if (runE2ETests()) {
|
||||||
const e2eResults = runCLI(`e2e ${appName2}-e2e`);
|
expect(() => runCLI(`e2e ${appName2}-e2e`)).not.toThrow();
|
||||||
expect(e2eResults).toContain('Successfully ran target e2e');
|
|
||||||
// port and process cleanup
|
// port and process cleanup
|
||||||
try {
|
try {
|
||||||
if (process && process.pid) {
|
if (process && process.pid) {
|
||||||
|
|||||||
@ -232,14 +232,26 @@ export function runCommandAsync(
|
|||||||
},
|
},
|
||||||
(err, stdout, stderr) => {
|
(err, stdout, stderr) => {
|
||||||
if (!opts.silenceError && err) {
|
if (!opts.silenceError && err) {
|
||||||
|
logError(`Original command: ${command}`, `${stdout}\n\n${stderr}`);
|
||||||
reject(err);
|
reject(err);
|
||||||
}
|
}
|
||||||
resolve({
|
|
||||||
|
const outputs = {
|
||||||
stdout: stripConsoleColors(stdout),
|
stdout: stripConsoleColors(stdout),
|
||||||
stderr: stripConsoleColors(stderr),
|
stderr: stripConsoleColors(stderr),
|
||||||
combinedOutput: stripConsoleColors(`${stdout}${stderr}`),
|
combinedOutput: stripConsoleColors(`${stdout}${stderr}`),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (opts.verbose ?? isVerboseE2ERun()) {
|
||||||
|
output.log({
|
||||||
|
title: `Original command: ${command}`,
|
||||||
|
bodyLines: [outputs.combinedOutput],
|
||||||
|
color: 'green',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolve(outputs);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -302,10 +314,11 @@ export function runCLIAsync(
|
|||||||
}
|
}
|
||||||
): Promise<{ stdout: string; stderr: string; combinedOutput: string }> {
|
): Promise<{ stdout: string; stderr: string; combinedOutput: string }> {
|
||||||
const pm = getPackageManagerCommand();
|
const pm = getPackageManagerCommand();
|
||||||
return runCommandAsync(
|
const commandToRun = `${opts.silent ? pm.runNxSilent : pm.runNx} ${command} ${
|
||||||
`${opts.silent ? pm.runNxSilent : pm.runNx} ${command}`,
|
opts.verbose ?? isVerboseE2ERun() ? ' --verbose' : ''
|
||||||
opts
|
}${opts.redirectStderr ? ' 2>&1' : ''}`;
|
||||||
);
|
|
||||||
|
return runCommandAsync(commandToRun, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function runNgAdd(
|
export function runNgAdd(
|
||||||
|
|||||||
@ -45,24 +45,20 @@ describe('@nx/vite/plugin', () => {
|
|||||||
|
|
||||||
describe('build and test React app', () => {
|
describe('build and test React app', () => {
|
||||||
it('should build application', () => {
|
it('should build application', () => {
|
||||||
const result = runCLI(`build ${myApp}`);
|
expect(() => runCLI(`build ${myApp}`)).not.toThrow();
|
||||||
expect(result).toContain('Successfully ran target build');
|
|
||||||
}, 200_000);
|
}, 200_000);
|
||||||
|
|
||||||
it('should test application', () => {
|
it('should test application', () => {
|
||||||
const result = runCLI(`test ${myApp} --watch=false`);
|
expect(() => runCLI(`test ${myApp} --watch=false`)).not.toThrow();
|
||||||
expect(result).toContain('Successfully ran target test');
|
|
||||||
}, 200_000);
|
}, 200_000);
|
||||||
});
|
});
|
||||||
describe('build and test Vue app', () => {
|
describe('build and test Vue app', () => {
|
||||||
it('should build application', () => {
|
it('should build application', () => {
|
||||||
const result = runCLI(`build ${myVueApp}`);
|
expect(() => runCLI(`build ${myVueApp}`)).not.toThrow();
|
||||||
expect(result).toContain('Successfully ran target build');
|
|
||||||
}, 200_000);
|
}, 200_000);
|
||||||
|
|
||||||
it('should test application', () => {
|
it('should test application', () => {
|
||||||
const result = runCLI(`test ${myVueApp} --watch=false`);
|
expect(() => runCLI(`test ${myVueApp} --watch=false`)).not.toThrow();
|
||||||
expect(result).toContain('Successfully ran target test');
|
|
||||||
}, 200_000);
|
}, 200_000);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -129,13 +125,7 @@ describe('@nx/vite/plugin', () => {
|
|||||||
});`
|
});`
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = runCLI(`build ${myApp}`);
|
expect(() => runCLI(`build ${myApp}`)).not.toThrow();
|
||||||
expect(result).toContain(
|
|
||||||
`Running target build for project ${myApp} and 1 task it depends on`
|
|
||||||
);
|
|
||||||
expect(result).toContain(
|
|
||||||
`Successfully ran target build for project ${myApp} and 1 task it depends on`
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Page, test, expect } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
/**
|
/**
|
||||||
* Assert a text is present on the visited page.
|
* Assert a text is present on the visited page.
|
||||||
* @param page
|
* @param page
|
||||||
@ -11,11 +11,12 @@ export function assertTextOnPage(
|
|||||||
title: string,
|
title: string,
|
||||||
selector: string = 'h1'
|
selector: string = 'h1'
|
||||||
): void {
|
): void {
|
||||||
test.describe(path, () =>
|
// eslint-disable-next-line playwright/valid-title
|
||||||
|
test.describe(path, () => {
|
||||||
test(`should display "${title}"`, async ({ page }) => {
|
test(`should display "${title}"`, async ({ page }) => {
|
||||||
await page.goto(path);
|
await page.goto(path);
|
||||||
const locator = page.locator(selector);
|
const locator = page.locator(selector);
|
||||||
await expect(locator).toContainText(title);
|
await expect(locator).toContainText(title);
|
||||||
})
|
});
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
24
package.json
24
package.json
@ -30,9 +30,9 @@
|
|||||||
"@angular-devkit/build-angular": "~18.2.0",
|
"@angular-devkit/build-angular": "~18.2.0",
|
||||||
"@angular-devkit/core": "~18.2.0",
|
"@angular-devkit/core": "~18.2.0",
|
||||||
"@angular-devkit/schematics": "~18.2.0",
|
"@angular-devkit/schematics": "~18.2.0",
|
||||||
"@angular-eslint/eslint-plugin": "^18.0.1",
|
"@angular-eslint/eslint-plugin": "^18.3.0",
|
||||||
"@angular-eslint/eslint-plugin-template": "^18.0.1",
|
"@angular-eslint/eslint-plugin-template": "^18.3.0",
|
||||||
"@angular-eslint/template-parser": "^18.0.1",
|
"@angular-eslint/template-parser": "^18.3.0",
|
||||||
"@angular/cli": "~18.2.0",
|
"@angular/cli": "~18.2.0",
|
||||||
"@angular/common": "~18.2.0",
|
"@angular/common": "~18.2.0",
|
||||||
"@angular/compiler": "~18.2.0",
|
"@angular/compiler": "~18.2.0",
|
||||||
@ -45,6 +45,7 @@
|
|||||||
"@babel/preset-react": "^7.22.5",
|
"@babel/preset-react": "^7.22.5",
|
||||||
"@babel/preset-typescript": "^7.22.5",
|
"@babel/preset-typescript": "^7.22.5",
|
||||||
"@babel/runtime": "^7.22.6",
|
"@babel/runtime": "^7.22.6",
|
||||||
|
"@eslint/compat": "^1.1.1",
|
||||||
"@eslint/eslintrc": "^2.1.1",
|
"@eslint/eslintrc": "^2.1.1",
|
||||||
"@eslint/js": "^8.48.0",
|
"@eslint/js": "^8.48.0",
|
||||||
"@floating-ui/react": "0.26.6",
|
"@floating-ui/react": "0.26.6",
|
||||||
@ -116,6 +117,7 @@
|
|||||||
"@types/detect-port": "^1.3.2",
|
"@types/detect-port": "^1.3.2",
|
||||||
"@types/ejs": "3.1.2",
|
"@types/ejs": "3.1.2",
|
||||||
"@types/eslint": "~8.56.10",
|
"@types/eslint": "~8.56.10",
|
||||||
|
"@types/eslint__js": "^8.42.3",
|
||||||
"@types/express": "4.17.14",
|
"@types/express": "4.17.14",
|
||||||
"@types/flat": "^5.0.1",
|
"@types/flat": "^5.0.1",
|
||||||
"@types/fs-extra": "^11.0.0",
|
"@types/fs-extra": "^11.0.0",
|
||||||
@ -134,16 +136,16 @@
|
|||||||
"@types/tmp": "^0.2.0",
|
"@types/tmp": "^0.2.0",
|
||||||
"@types/yargs": "17.0.10",
|
"@types/yargs": "17.0.10",
|
||||||
"@types/yarnpkg__lockfile": "^1.1.5",
|
"@types/yarnpkg__lockfile": "^1.1.5",
|
||||||
"@typescript-eslint/eslint-plugin": "7.16.0",
|
"@typescript-eslint/rule-tester": "^8.0.0",
|
||||||
"@typescript-eslint/parser": "7.16.0",
|
"@typescript-eslint/type-utils": "^8.0.0",
|
||||||
"@typescript-eslint/type-utils": "^7.16.0",
|
"@typescript-eslint/utils": "^8.0.0",
|
||||||
"@typescript-eslint/utils": "7.16.0",
|
|
||||||
"@xstate/immer": "0.3.1",
|
"@xstate/immer": "0.3.1",
|
||||||
"@xstate/inspect": "0.7.0",
|
"@xstate/inspect": "0.7.0",
|
||||||
"@xstate/react": "3.0.1",
|
"@xstate/react": "3.0.1",
|
||||||
"@zkochan/js-yaml": "0.0.7",
|
"@zkochan/js-yaml": "0.0.7",
|
||||||
"ai": "^2.2.10",
|
"ai": "^2.2.10",
|
||||||
"ajv": "^8.12.0",
|
"ajv": "^8.12.0",
|
||||||
|
"angular-eslint": "^18.3.0",
|
||||||
"autoprefixer": "10.4.13",
|
"autoprefixer": "10.4.13",
|
||||||
"babel-jest": "29.7.0",
|
"babel-jest": "29.7.0",
|
||||||
"babel-loader": "^9.1.2",
|
"babel-loader": "^9.1.2",
|
||||||
@ -175,10 +177,10 @@
|
|||||||
"eslint-config-next": "14.2.3",
|
"eslint-config-next": "14.2.3",
|
||||||
"eslint-config-prettier": "9.1.0",
|
"eslint-config-prettier": "9.1.0",
|
||||||
"eslint-plugin-cypress": "2.14.0",
|
"eslint-plugin-cypress": "2.14.0",
|
||||||
"eslint-plugin-import": "2.27.5",
|
"eslint-plugin-import": "2.30.0",
|
||||||
"eslint-plugin-jsx-a11y": "6.7.1",
|
"eslint-plugin-jsx-a11y": "6.7.1",
|
||||||
"eslint-plugin-playwright": "^0.15.3",
|
"eslint-plugin-playwright": "^1.6.2",
|
||||||
"eslint-plugin-react": "7.32.2",
|
"eslint-plugin-react": "7.35.0",
|
||||||
"eslint-plugin-react-hooks": "4.6.0",
|
"eslint-plugin-react-hooks": "4.6.0",
|
||||||
"eslint-plugin-storybook": "^0.8.0",
|
"eslint-plugin-storybook": "^0.8.0",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
@ -190,6 +192,7 @@
|
|||||||
"fork-ts-checker-webpack-plugin": "7.2.13",
|
"fork-ts-checker-webpack-plugin": "7.2.13",
|
||||||
"fs-extra": "^11.1.0",
|
"fs-extra": "^11.1.0",
|
||||||
"github-slugger": "^2.0.0",
|
"github-slugger": "^2.0.0",
|
||||||
|
"globals": "^15.9.0",
|
||||||
"gpt3-tokenizer": "^1.1.5",
|
"gpt3-tokenizer": "^1.1.5",
|
||||||
"handlebars": "4.7.7",
|
"handlebars": "4.7.7",
|
||||||
"html-webpack-plugin": "5.5.0",
|
"html-webpack-plugin": "5.5.0",
|
||||||
@ -287,6 +290,7 @@
|
|||||||
"typedoc": "0.25.12",
|
"typedoc": "0.25.12",
|
||||||
"typedoc-plugin-markdown": "3.17.1",
|
"typedoc-plugin-markdown": "3.17.1",
|
||||||
"typescript": "~5.5.2",
|
"typescript": "~5.5.2",
|
||||||
|
"typescript-eslint": "^8.0.0",
|
||||||
"unist-builder": "^4.0.0",
|
"unist-builder": "^4.0.0",
|
||||||
"unzipper": "^0.10.11",
|
"unzipper": "^0.10.11",
|
||||||
"url-loader": "^4.1.1",
|
"url-loader": "^4.1.1",
|
||||||
|
|||||||
@ -48,7 +48,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@phenomnomnominal/tsquery": "~5.0.1",
|
"@phenomnomnominal/tsquery": "~5.0.1",
|
||||||
"@typescript-eslint/type-utils": "^7.16.0",
|
"@typescript-eslint/type-utils": "^8.0.0",
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
"find-cache-dir": "^3.3.2",
|
"find-cache-dir": "^3.3.2",
|
||||||
"magic-string": "~0.30.2",
|
"magic-string": "~0.30.2",
|
||||||
|
|||||||
@ -7,11 +7,14 @@ import {
|
|||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { camelize, dasherize } from '@nx/devkit/src/utils/string-utils';
|
import { camelize, dasherize } from '@nx/devkit/src/utils/string-utils';
|
||||||
import { Linter, lintProjectGenerator } from '@nx/eslint';
|
import { Linter, lintProjectGenerator } from '@nx/eslint';
|
||||||
|
import type * as eslint from 'eslint';
|
||||||
import {
|
import {
|
||||||
javaScriptOverride,
|
javaScriptOverride,
|
||||||
typeScriptOverride,
|
typeScriptOverride,
|
||||||
} from '@nx/eslint/src/generators/init/global-eslint-config';
|
} from '@nx/eslint/src/generators/init/global-eslint-config';
|
||||||
import {
|
import {
|
||||||
|
addOverrideToLintConfig,
|
||||||
|
addPredefinedConfigToFlatLintConfig,
|
||||||
findEslintFile,
|
findEslintFile,
|
||||||
isEslintConfigSupported,
|
isEslintConfigSupported,
|
||||||
replaceOverridesInLintConfig,
|
replaceOverridesInLintConfig,
|
||||||
@ -19,6 +22,7 @@ import {
|
|||||||
import { addAngularEsLintDependencies } from './lib/add-angular-eslint-dependencies';
|
import { addAngularEsLintDependencies } from './lib/add-angular-eslint-dependencies';
|
||||||
import { isBuildableLibraryProject } from './lib/buildable-project';
|
import { isBuildableLibraryProject } from './lib/buildable-project';
|
||||||
import type { AddLintingGeneratorSchema } from './schema';
|
import type { AddLintingGeneratorSchema } from './schema';
|
||||||
|
import { useFlatConfig } from '@nx/eslint/src/utils/flat-config';
|
||||||
|
|
||||||
export async function addLintingGenerator(
|
export async function addLintingGenerator(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
@ -49,6 +53,59 @@ export async function addLintingGenerator(
|
|||||||
.read(joinPathFragments(options.projectRoot, eslintFile), 'utf8')
|
.read(joinPathFragments(options.projectRoot, eslintFile), 'utf8')
|
||||||
.includes(`${options.projectRoot}/tsconfig.*?.json`);
|
.includes(`${options.projectRoot}/tsconfig.*?.json`);
|
||||||
|
|
||||||
|
if (useFlatConfig(tree)) {
|
||||||
|
addPredefinedConfigToFlatLintConfig(
|
||||||
|
tree,
|
||||||
|
options.projectRoot,
|
||||||
|
'flat/angular'
|
||||||
|
);
|
||||||
|
addPredefinedConfigToFlatLintConfig(
|
||||||
|
tree,
|
||||||
|
options.projectRoot,
|
||||||
|
'flat/angular-template'
|
||||||
|
);
|
||||||
|
addOverrideToLintConfig(tree, options.projectRoot, {
|
||||||
|
files: ['*.ts'],
|
||||||
|
rules: {
|
||||||
|
'@angular-eslint/directive-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'attribute',
|
||||||
|
prefix: camelize(options.prefix),
|
||||||
|
style: 'camelCase',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@angular-eslint/component-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
prefix: dasherize(options.prefix),
|
||||||
|
style: 'kebab-case',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
addOverrideToLintConfig(tree, options.projectRoot, {
|
||||||
|
files: ['*.html'],
|
||||||
|
rules: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isBuildableLibraryProject(tree, options.projectName)) {
|
||||||
|
addOverrideToLintConfig(tree, '', {
|
||||||
|
files: ['*.json'],
|
||||||
|
parser: 'jsonc-eslint-parser',
|
||||||
|
rules: {
|
||||||
|
'@nx/dependency-checks': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
// With flat configs, we don't want to include imports in the eslint js/cjs/mjs files to be checked
|
||||||
|
ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs}'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
replaceOverridesInLintConfig(tree, options.projectRoot, [
|
replaceOverridesInLintConfig(tree, options.projectRoot, [
|
||||||
...(rootProject ? [typeScriptOverride, javaScriptOverride] : []),
|
...(rootProject ? [typeScriptOverride, javaScriptOverride] : []),
|
||||||
{
|
{
|
||||||
@ -98,13 +155,22 @@ export async function addLintingGenerator(
|
|||||||
files: ['*.json'],
|
files: ['*.json'],
|
||||||
parser: 'jsonc-eslint-parser',
|
parser: 'jsonc-eslint-parser',
|
||||||
rules: {
|
rules: {
|
||||||
'@nx/dependency-checks': 'error',
|
'@nx/dependency-checks': [
|
||||||
} as any,
|
'error',
|
||||||
|
{
|
||||||
|
// With flat configs, we don't want to include imports in the eslint js/cjs/mjs files to be checked
|
||||||
|
ignoredFiles: [
|
||||||
|
'{projectRoot}/eslint.config.{js,cjs,mjs}',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
} as any,
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!options.skipPackageJson) {
|
if (!options.skipPackageJson) {
|
||||||
const installTask = addAngularEsLintDependencies(tree, options.projectName);
|
const installTask = addAngularEsLintDependencies(tree, options.projectName);
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { versions } from '../../utils/version-utils';
|
import { versions } from '../../utils/version-utils';
|
||||||
import { isBuildableLibraryProject } from './buildable-project';
|
import { isBuildableLibraryProject } from './buildable-project';
|
||||||
|
import { useFlatConfig } from '@nx/eslint/src/utils/flat-config';
|
||||||
|
|
||||||
export function addAngularEsLintDependencies(
|
export function addAngularEsLintDependencies(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
@ -12,7 +13,11 @@ export function addAngularEsLintDependencies(
|
|||||||
): GeneratorCallback {
|
): GeneratorCallback {
|
||||||
const compatVersions = versions(tree);
|
const compatVersions = versions(tree);
|
||||||
const angularEslintVersionToInstall = compatVersions.angularEslintVersion;
|
const angularEslintVersionToInstall = compatVersions.angularEslintVersion;
|
||||||
const devDependencies = {
|
const devDependencies = useFlatConfig(tree)
|
||||||
|
? {
|
||||||
|
'angular-eslint': angularEslintVersionToInstall,
|
||||||
|
}
|
||||||
|
: {
|
||||||
'@angular-eslint/eslint-plugin': angularEslintVersionToInstall,
|
'@angular-eslint/eslint-plugin': angularEslintVersionToInstall,
|
||||||
'@angular-eslint/eslint-plugin-template': angularEslintVersionToInstall,
|
'@angular-eslint/eslint-plugin-template': angularEslintVersionToInstall,
|
||||||
'@angular-eslint/template-parser': angularEslintVersionToInstall,
|
'@angular-eslint/template-parser': angularEslintVersionToInstall,
|
||||||
|
|||||||
@ -701,7 +701,14 @@ describe('lib', () => {
|
|||||||
],
|
],
|
||||||
"parser": "jsonc-eslint-parser",
|
"parser": "jsonc-eslint-parser",
|
||||||
"rules": {
|
"rules": {
|
||||||
"@nx/dependency-checks": "error"
|
"@nx/dependency-checks": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"ignoredFiles": [
|
||||||
|
"{projectRoot}/eslint.config.{js,cjs,mjs}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -1193,7 +1200,52 @@ describe('lib', () => {
|
|||||||
|
|
||||||
describe('--linter', () => {
|
describe('--linter', () => {
|
||||||
describe('eslint', () => {
|
describe('eslint', () => {
|
||||||
it('should add valid eslint JSON configuration which extends from Nx presets', async () => {
|
it('should add valid eslint JSON configuration which extends from Nx presets (flat config)', async () => {
|
||||||
|
tree.write('eslint.config.js', '');
|
||||||
|
|
||||||
|
await runLibraryGeneratorWithOpts({ linter: Linter.EsLint });
|
||||||
|
|
||||||
|
const eslintConfig = tree.read('my-lib/eslint.config.js', 'utf-8');
|
||||||
|
expect(eslintConfig).toMatchInlineSnapshot(`
|
||||||
|
"const nx = require("@nx/eslint-plugin");
|
||||||
|
const baseConfig = require("../eslint.config.js");
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
...baseConfig,
|
||||||
|
...nx.configs["flat/angular"],
|
||||||
|
...nx.configs["flat/angular-template"],
|
||||||
|
{
|
||||||
|
files: ["**/*.ts"],
|
||||||
|
rules: {
|
||||||
|
"@angular-eslint/directive-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
type: "attribute",
|
||||||
|
prefix: "lib",
|
||||||
|
style: "camelCase"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@angular-eslint/component-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
prefix: "lib",
|
||||||
|
style: "kebab-case"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["**/*.html"],
|
||||||
|
// Override or add rules here
|
||||||
|
rules: {}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add valid eslint JSON configuration which extends from Nx presets (eslintrc)', async () => {
|
||||||
// ACT
|
// ACT
|
||||||
await runLibraryGeneratorWithOpts({ linter: Linter.EsLint });
|
await runLibraryGeneratorWithOpts({ linter: Linter.EsLint });
|
||||||
|
|
||||||
@ -1311,7 +1363,14 @@ describe('lib', () => {
|
|||||||
],
|
],
|
||||||
"parser": "jsonc-eslint-parser",
|
"parser": "jsonc-eslint-parser",
|
||||||
"rules": {
|
"rules": {
|
||||||
"@nx/dependency-checks": "error",
|
"@nx/dependency-checks": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"ignoredFiles": [
|
||||||
|
"{projectRoot}/eslint.config.{js,cjs,mjs}",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -17,7 +17,7 @@ export const browserSyncVersion = '^3.0.0';
|
|||||||
export const moduleFederationNodeVersion = '~2.5.0';
|
export const moduleFederationNodeVersion = '~2.5.0';
|
||||||
export const moduleFederationEnhancedVersion = '~0.6.0';
|
export const moduleFederationEnhancedVersion = '~0.6.0';
|
||||||
|
|
||||||
export const angularEslintVersion = '^18.0.1';
|
export const angularEslintVersion = '^18.3.0';
|
||||||
export const typescriptEslintVersion = '^7.16.0';
|
export const typescriptEslintVersion = '^7.16.0';
|
||||||
export const tailwindVersion = '^3.0.2';
|
export const tailwindVersion = '^3.0.2';
|
||||||
export const postcssVersion = '^8.4.5';
|
export const postcssVersion = '^8.4.5';
|
||||||
|
|||||||
@ -11,7 +11,13 @@ export function spawnAndWait(command: string, args: string[], cwd: string) {
|
|||||||
const childProcess = spawn(command, args, {
|
const childProcess = spawn(command, args, {
|
||||||
cwd,
|
cwd,
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
env: { ...process.env, NX_DAEMON: 'false' },
|
env: {
|
||||||
|
...process.env,
|
||||||
|
NX_DAEMON: 'false',
|
||||||
|
// This is the same environment variable that ESLint uses to determine if it should use a flat config.
|
||||||
|
// Default to true for all new workspaces.
|
||||||
|
ESLINT_USE_FLAT_CONFIG: process.env.ESLINT_USE_FLAT_CONFIG ?? 'true',
|
||||||
|
},
|
||||||
shell: true,
|
shell: true,
|
||||||
windowsHide: true,
|
windowsHide: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
addExtendsToLintConfig,
|
addExtendsToLintConfig,
|
||||||
addOverrideToLintConfig,
|
addOverrideToLintConfig,
|
||||||
addPluginsToLintConfig,
|
addPluginsToLintConfig,
|
||||||
|
addPredefinedConfigToFlatLintConfig,
|
||||||
findEslintFile,
|
findEslintFile,
|
||||||
isEslintConfigSupported,
|
isEslintConfigSupported,
|
||||||
replaceOverridesInLintConfig,
|
replaceOverridesInLintConfig,
|
||||||
@ -21,6 +22,7 @@ import {
|
|||||||
javaScriptOverride,
|
javaScriptOverride,
|
||||||
typeScriptOverride,
|
typeScriptOverride,
|
||||||
} from '@nx/eslint/src/generators/init/global-eslint-config';
|
} from '@nx/eslint/src/generators/init/global-eslint-config';
|
||||||
|
import { useFlatConfig } from '@nx/eslint/src/utils/flat-config';
|
||||||
|
|
||||||
export interface CyLinterOptions {
|
export interface CyLinterOptions {
|
||||||
project: string;
|
project: string;
|
||||||
@ -90,16 +92,33 @@ export async function addLinterToCyProject(
|
|||||||
isEslintConfigSupported(tree)
|
isEslintConfigSupported(tree)
|
||||||
) {
|
) {
|
||||||
const overrides = [];
|
const overrides = [];
|
||||||
|
if (useFlatConfig(tree)) {
|
||||||
|
addPredefinedConfigToFlatLintConfig(
|
||||||
|
tree,
|
||||||
|
projectConfig.root,
|
||||||
|
'recommended',
|
||||||
|
'cypress',
|
||||||
|
'eslint-plugin-cypress/flat',
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
addOverrideToLintConfig(tree, projectConfig.root, {
|
||||||
|
files: ['*.ts', '*.js'],
|
||||||
|
rules: {},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
if (options.rootProject) {
|
if (options.rootProject) {
|
||||||
addPluginsToLintConfig(tree, projectConfig.root, '@nx');
|
addPluginsToLintConfig(tree, projectConfig.root, '@nx');
|
||||||
overrides.push(typeScriptOverride);
|
overrides.push(typeScriptOverride);
|
||||||
overrides.push(javaScriptOverride);
|
overrides.push(javaScriptOverride);
|
||||||
}
|
}
|
||||||
addExtendsToLintConfig(
|
const addExtendsTask = addExtendsToLintConfig(
|
||||||
tree,
|
tree,
|
||||||
projectConfig.root,
|
projectConfig.root,
|
||||||
'plugin:cypress/recommended'
|
'plugin:cypress/recommended'
|
||||||
);
|
);
|
||||||
|
tasks.push(addExtendsTask);
|
||||||
|
}
|
||||||
const cyVersion = installedCypressVersion();
|
const cyVersion = installedCypressVersion();
|
||||||
/**
|
/**
|
||||||
* We need this override because we enabled allowJS in the tsconfig to allow for JS based Cypress tests.
|
* We need this override because we enabled allowJS in the tsconfig to allow for JS based Cypress tests.
|
||||||
@ -116,7 +135,10 @@ export async function addLinterToCyProject(
|
|||||||
|
|
||||||
if (options.overwriteExisting) {
|
if (options.overwriteExisting) {
|
||||||
overrides.unshift({
|
overrides.unshift({
|
||||||
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
files: useFlatConfig(tree)
|
||||||
|
? // For flat configs we don't need to specify the files
|
||||||
|
undefined
|
||||||
|
: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
||||||
parserOptions: !options.setParserOptionsProject
|
parserOptions: !options.setParserOptionsProject
|
||||||
? undefined
|
? undefined
|
||||||
: {
|
: {
|
||||||
@ -130,7 +152,10 @@ export async function addLinterToCyProject(
|
|||||||
replaceOverridesInLintConfig(tree, projectConfig.root, overrides);
|
replaceOverridesInLintConfig(tree, projectConfig.root, overrides);
|
||||||
} else {
|
} else {
|
||||||
overrides.unshift({
|
overrides.unshift({
|
||||||
files: [
|
files: useFlatConfig(tree)
|
||||||
|
? // For flat configs we don't need to specify the files
|
||||||
|
undefined
|
||||||
|
: [
|
||||||
'*.cy.{ts,js,tsx,jsx}',
|
'*.cy.{ts,js,tsx,jsx}',
|
||||||
`${options.cypressDir}/**/*.{ts,js,tsx,jsx}`,
|
`${options.cypressDir}/**/*.{ts,js,tsx,jsx}`,
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
export const nxVersion = require('../../package.json').version;
|
export const nxVersion = require('../../package.json').version;
|
||||||
export const eslintPluginCypressVersion = '^2.13.4';
|
export const eslintPluginCypressVersion = '^3.5.0';
|
||||||
export const typesNodeVersion = '18.16.9';
|
export const typesNodeVersion = '18.16.9';
|
||||||
export const cypressViteDevServerVersion = '^2.2.1';
|
export const cypressViteDevServerVersion = '^2.2.1';
|
||||||
export const cypressVersion = '^13.13.0';
|
export const cypressVersion = '^13.13.0';
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { Linter, lintProjectGenerator } from '@nx/eslint';
|
import { Linter, lintProjectGenerator } from '@nx/eslint';
|
||||||
import {
|
import {
|
||||||
addDependenciesToPackageJson,
|
addDependenciesToPackageJson,
|
||||||
|
GeneratorCallback,
|
||||||
joinPathFragments,
|
joinPathFragments,
|
||||||
runTasksInSerial,
|
runTasksInSerial,
|
||||||
Tree,
|
Tree,
|
||||||
@ -9,14 +10,18 @@ import { extraEslintDependencies } from '@nx/react';
|
|||||||
import { NormalizedSchema } from './normalize-options';
|
import { NormalizedSchema } from './normalize-options';
|
||||||
import {
|
import {
|
||||||
addExtendsToLintConfig,
|
addExtendsToLintConfig,
|
||||||
|
addOverrideToLintConfig,
|
||||||
|
addPredefinedConfigToFlatLintConfig,
|
||||||
isEslintConfigSupported,
|
isEslintConfigSupported,
|
||||||
} from '@nx/eslint/src/generators/utils/eslint-file';
|
} from '@nx/eslint/src/generators/utils/eslint-file';
|
||||||
|
import { useFlatConfig } from '@nx/eslint/src/utils/flat-config';
|
||||||
|
|
||||||
export async function addLinting(host: Tree, options: NormalizedSchema) {
|
export async function addLinting(host: Tree, options: NormalizedSchema) {
|
||||||
if (options.linter === Linter.None) {
|
if (options.linter === Linter.None) {
|
||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tasks: GeneratorCallback[] = [];
|
||||||
const lintTask = await lintProjectGenerator(host, {
|
const lintTask = await lintProjectGenerator(host, {
|
||||||
linter: options.linter,
|
linter: options.linter,
|
||||||
project: options.e2eProjectName,
|
project: options.e2eProjectName,
|
||||||
@ -26,9 +31,28 @@ export async function addLinting(host: Tree, options: NormalizedSchema) {
|
|||||||
skipFormat: true,
|
skipFormat: true,
|
||||||
addPlugin: options.addPlugin,
|
addPlugin: options.addPlugin,
|
||||||
});
|
});
|
||||||
|
tasks.push(lintTask);
|
||||||
|
|
||||||
if (isEslintConfigSupported(host)) {
|
if (isEslintConfigSupported(host)) {
|
||||||
addExtendsToLintConfig(host, options.e2eProjectRoot, 'plugin:@nx/react');
|
if (useFlatConfig(host)) {
|
||||||
|
addPredefinedConfigToFlatLintConfig(
|
||||||
|
host,
|
||||||
|
options.e2eProjectRoot,
|
||||||
|
'flat/react'
|
||||||
|
);
|
||||||
|
// Add an empty rules object to users know how to add/override rules
|
||||||
|
addOverrideToLintConfig(host, options.e2eProjectRoot, {
|
||||||
|
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
||||||
|
rules: {},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const addExtendsTask = addExtendsToLintConfig(
|
||||||
|
host,
|
||||||
|
options.e2eProjectRoot,
|
||||||
|
{ name: 'plugin:@nx/react', needCompatFixup: true }
|
||||||
|
);
|
||||||
|
tasks.push(addExtendsTask);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const installTask = addDependenciesToPackageJson(
|
const installTask = addDependenciesToPackageJson(
|
||||||
@ -36,6 +60,7 @@ export async function addLinting(host: Tree, options: NormalizedSchema) {
|
|||||||
extraEslintDependencies.dependencies,
|
extraEslintDependencies.dependencies,
|
||||||
extraEslintDependencies.devDependencies
|
extraEslintDependencies.devDependencies
|
||||||
);
|
);
|
||||||
|
tasks.push(installTask);
|
||||||
|
|
||||||
return runTasksInSerial(lintTask, installTask);
|
return runTasksInSerial(...tasks);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,7 +37,14 @@
|
|||||||
// Installed to workspace by plugins
|
// Installed to workspace by plugins
|
||||||
"@typescript-eslint/parser",
|
"@typescript-eslint/parser",
|
||||||
"eslint-config-prettier",
|
"eslint-config-prettier",
|
||||||
"@angular-eslint/eslint-plugin"
|
"@angular-eslint/eslint-plugin",
|
||||||
|
"angular-eslint",
|
||||||
|
"typescript-eslint",
|
||||||
|
"@eslint/js",
|
||||||
|
"eslint-plugin-import",
|
||||||
|
"eslint-plugin-jsx-a11y",
|
||||||
|
"eslint-plugin-react",
|
||||||
|
"eslint-plugin-react-hooks"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
16
packages/eslint-plugin/angular.ts
Normal file
16
packages/eslint-plugin/angular.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import angular from './src/flat-configs/angular';
|
||||||
|
import angularTemplate from './src/flat-configs/angular-template';
|
||||||
|
|
||||||
|
const plugin = {
|
||||||
|
configs: {
|
||||||
|
angular,
|
||||||
|
'angular-template': angularTemplate,
|
||||||
|
},
|
||||||
|
rules: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
// ESM
|
||||||
|
export default plugin;
|
||||||
|
|
||||||
|
// CommonJS
|
||||||
|
module.exports = plugin;
|
||||||
27
packages/eslint-plugin/nx.ts
Normal file
27
packages/eslint-plugin/nx.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { workspaceRules } from './src/resolve-workspace-rules';
|
||||||
|
import dependencyChecks, {
|
||||||
|
RULE_NAME as dependencyChecksRuleName,
|
||||||
|
} from './src/rules/dependency-checks';
|
||||||
|
import enforceModuleBoundaries, {
|
||||||
|
RULE_NAME as enforceModuleBoundariesRuleName,
|
||||||
|
} from './src/rules/enforce-module-boundaries';
|
||||||
|
import nxPluginChecksRule, {
|
||||||
|
RULE_NAME as nxPluginChecksRuleName,
|
||||||
|
} from './src/rules/nx-plugin-checks';
|
||||||
|
|
||||||
|
const plugin = {
|
||||||
|
configs: {},
|
||||||
|
rules: {
|
||||||
|
[enforceModuleBoundariesRuleName]: enforceModuleBoundaries,
|
||||||
|
[nxPluginChecksRuleName]: nxPluginChecksRule,
|
||||||
|
[dependencyChecksRuleName]: dependencyChecks,
|
||||||
|
// Resolve any custom rules that might exist in the current workspace
|
||||||
|
...workspaceRules,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// ESM
|
||||||
|
export default plugin;
|
||||||
|
|
||||||
|
// CommonJS
|
||||||
|
module.exports = plugin;
|
||||||
@ -25,7 +25,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://nx.dev",
|
"homepage": "https://nx.dev",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@typescript-eslint/parser": "^6.13.2 || ^7.0.0",
|
"@typescript-eslint/parser": "^6.13.2 || ^7.0.0 || ^8.0.0",
|
||||||
"eslint-config-prettier": "^9.0.0"
|
"eslint-config-prettier": "^9.0.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
@ -34,12 +34,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@eslint/compat": "^1.1.1",
|
||||||
"@nx/devkit": "file:../devkit",
|
"@nx/devkit": "file:../devkit",
|
||||||
"@nx/js": "file:../js",
|
"@nx/js": "file:../js",
|
||||||
"@typescript-eslint/type-utils": "^7.16.0",
|
"@typescript-eslint/type-utils": "^8.0.0",
|
||||||
"@typescript-eslint/utils": "^7.16.0",
|
"@typescript-eslint/utils": "^8.0.0",
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
"confusing-browser-globals": "^1.0.9",
|
"confusing-browser-globals": "^1.0.9",
|
||||||
|
"globals": "^15.9.0",
|
||||||
"jsonc-eslint-parser": "^2.1.0",
|
"jsonc-eslint-parser": "^2.1.0",
|
||||||
"semver": "^7.5.3",
|
"semver": "^7.5.3",
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
|
|||||||
20
packages/eslint-plugin/react.ts
Normal file
20
packages/eslint-plugin/react.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import reactBase from './src/flat-configs/react-base';
|
||||||
|
import reactJsx from './src/flat-configs/react-jsx';
|
||||||
|
import reactTmp from './src/flat-configs/react-tmp';
|
||||||
|
import reactTypescript from './src/flat-configs/react-typescript';
|
||||||
|
|
||||||
|
const plugin = {
|
||||||
|
configs: {
|
||||||
|
react: reactTmp,
|
||||||
|
'react-base': reactBase,
|
||||||
|
'react-typescript': reactTypescript,
|
||||||
|
'react-jsx': reactJsx,
|
||||||
|
},
|
||||||
|
rules: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
// ESM
|
||||||
|
export default plugin;
|
||||||
|
|
||||||
|
// CommonJS
|
||||||
|
module.exports = plugin;
|
||||||
@ -66,5 +66,12 @@ export default {
|
|||||||
'@typescript-eslint/no-unused-vars': 'warn',
|
'@typescript-eslint/no-unused-vars': 'warn',
|
||||||
'@typescript-eslint/no-empty-interface': 'error',
|
'@typescript-eslint/no-empty-interface': 'error',
|
||||||
'@typescript-eslint/no-explicit-any': 'warn',
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
|
/**
|
||||||
|
* During the migration to use ESLint v9 and typescript-eslint v8 for new workspaces,
|
||||||
|
* this rule would have created a lot of noise, so we are disabling it by default for now.
|
||||||
|
*
|
||||||
|
* TODO(v20): we should make this part of what we re-evaluate in v20
|
||||||
|
*/
|
||||||
|
'@typescript-eslint/no-require-imports': 'off',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -49,5 +49,12 @@ export default {
|
|||||||
'@typescript-eslint/no-unused-vars': 'warn',
|
'@typescript-eslint/no-unused-vars': 'warn',
|
||||||
'@typescript-eslint/no-empty-interface': 'error',
|
'@typescript-eslint/no-empty-interface': 'error',
|
||||||
'@typescript-eslint/no-explicit-any': 'warn',
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
|
/**
|
||||||
|
* During the migration to use ESLint v9 and typescript-eslint v8 for new workspaces,
|
||||||
|
* this rule would have created a lot of noise, so we are disabling it by default for now.
|
||||||
|
*
|
||||||
|
* TODO(v20): we should make this part of what we re-evaluate in v20
|
||||||
|
*/
|
||||||
|
'@typescript-eslint/no-require-imports': 'off',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
25
packages/eslint-plugin/src/flat-configs/angular-template.ts
Normal file
25
packages/eslint-plugin/src/flat-configs/angular-template.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import angular from 'angular-eslint';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This configuration is intended to be applied to ALL .html files in Angular
|
||||||
|
* projects within an Nx workspace, as well as extracted inline templates from
|
||||||
|
* .component.ts files (or similar).
|
||||||
|
*
|
||||||
|
* It should therefore NOT contain any rules or plugins which are related to
|
||||||
|
* Angular source code.
|
||||||
|
*
|
||||||
|
* NOTE: The processor to extract the inline templates is applied in users'
|
||||||
|
* configs by the relevant schematic.
|
||||||
|
*
|
||||||
|
* This configuration is intended to be combined with other configs from this
|
||||||
|
* package.
|
||||||
|
*/
|
||||||
|
export default tseslint.config({
|
||||||
|
files: ['**/*.html'],
|
||||||
|
extends: [
|
||||||
|
...angular.configs.templateRecommended,
|
||||||
|
...angular.configs.templateAccessibility,
|
||||||
|
],
|
||||||
|
rules: {},
|
||||||
|
});
|
||||||
26
packages/eslint-plugin/src/flat-configs/angular.ts
Normal file
26
packages/eslint-plugin/src/flat-configs/angular.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import angularEslint from 'angular-eslint';
|
||||||
|
import globals from 'globals';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This configuration is intended to be applied to ALL .ts files in Angular
|
||||||
|
* projects within an Nx workspace.
|
||||||
|
*
|
||||||
|
* It should therefore NOT contain any rules or plugins which are related to
|
||||||
|
* Angular Templates, or more cross-cutting concerns which are not specific
|
||||||
|
* to Angular.
|
||||||
|
*
|
||||||
|
* This configuration is intended to be combined with other configs from this
|
||||||
|
* package.
|
||||||
|
*/
|
||||||
|
export default tseslint.config(...angularEslint.configs.tsRecommended, {
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.es2015,
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
processor: angularEslint.processInlineTemplates,
|
||||||
|
plugins: { '@angular-eslint': angularEslint.tsPlugin },
|
||||||
|
});
|
||||||
10
packages/eslint-plugin/src/flat-configs/base.ts
Normal file
10
packages/eslint-plugin/src/flat-configs/base.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export default [
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
get ['@nx']() {
|
||||||
|
return require('../index');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ignores: ['.nx'],
|
||||||
|
},
|
||||||
|
];
|
||||||
82
packages/eslint-plugin/src/flat-configs/javascript.ts
Normal file
82
packages/eslint-plugin/src/flat-configs/javascript.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import eslint from '@eslint/js';
|
||||||
|
import globals from 'globals';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
import { packageExists } from '../utils/config-utils';
|
||||||
|
|
||||||
|
const isPrettierAvailable =
|
||||||
|
packageExists('prettier') && packageExists('eslint-config-prettier');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This configuration is intended to be applied to ALL .js and .jsx files
|
||||||
|
* within an Nx workspace.
|
||||||
|
*
|
||||||
|
* It should therefore NOT contain any rules or plugins which are specific
|
||||||
|
* to one ecosystem, such as React, Angular, Node etc.
|
||||||
|
*
|
||||||
|
* We use @typescript-eslint/parser rather than the built in JS parser
|
||||||
|
* because that is what Nx ESLint configs have always done and we don't
|
||||||
|
* want to change too much all at once.
|
||||||
|
*
|
||||||
|
* TODO: Evaluate switching to the built-in JS parser (espree) in Nx v11,
|
||||||
|
* it should yield a performance improvement but could introduce subtle
|
||||||
|
* breaking changes - we should also look to replace all the @typescript-eslint
|
||||||
|
* related plugins and rules below.
|
||||||
|
*/
|
||||||
|
export default tseslint.config(
|
||||||
|
eslint.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
...(isPrettierAvailable ? [require('eslint-config-prettier')] : []),
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
parser: tseslint.parser,
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
sourceType: 'module',
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: { '@typescript-eslint': tseslint.plugin },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.js', '**/*.jsx'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/explicit-member-accessibility': 'off',
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
|
'@typescript-eslint/no-parameter-properties': 'off',
|
||||||
|
/**
|
||||||
|
* Until ESM usage in Node matures, using require in e.g. JS config files
|
||||||
|
* is by far the more common thing to do, so disabling this to avoid users
|
||||||
|
* having to frequently use "eslint-disable-next-line" in their configs.
|
||||||
|
*/
|
||||||
|
'@typescript-eslint/no-var-requires': 'off',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From https://typescript-eslint.io/blog/announcing-typescript-eslint-v6/#updated-configuration-rules
|
||||||
|
*
|
||||||
|
* The following rules were added to preserve the linting rules that were
|
||||||
|
* previously defined v5 of `@typescript-eslint`. v6 of `@typescript-eslint`
|
||||||
|
* changed how configurations are defined.
|
||||||
|
*
|
||||||
|
* TODO(v20): re-evalute these deviations from @typescript-eslint/recommended in v20 of Nx
|
||||||
|
*/
|
||||||
|
'@typescript-eslint/no-non-null-assertion': 'warn',
|
||||||
|
'@typescript-eslint/adjacent-overload-signatures': 'error',
|
||||||
|
'@typescript-eslint/prefer-namespace-keyword': 'error',
|
||||||
|
'no-empty-function': 'off',
|
||||||
|
'@typescript-eslint/no-empty-function': 'error',
|
||||||
|
'@typescript-eslint/no-inferrable-types': 'error',
|
||||||
|
'@typescript-eslint/no-unused-vars': 'warn',
|
||||||
|
'@typescript-eslint/no-empty-interface': 'error',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
|
/**
|
||||||
|
* During the migration to use ESLint v9 and typescript-eslint v8 for new workspaces,
|
||||||
|
* this rule would have created a lot of noise, so we are disabling it by default for now.
|
||||||
|
*
|
||||||
|
* TODO(v20): we should make this part of what we re-evaluate in v20
|
||||||
|
*/
|
||||||
|
'@typescript-eslint/no-require-imports': 'off',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
148
packages/eslint-plugin/src/flat-configs/react-base.ts
Normal file
148
packages/eslint-plugin/src/flat-configs/react-base.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import { fixupPluginRules } from '@eslint/compat';
|
||||||
|
import * as importPlugin from 'eslint-plugin-import';
|
||||||
|
import globals from 'globals';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This configuration is intended to be applied to ALL files within a React
|
||||||
|
* project in an Nx workspace.
|
||||||
|
*
|
||||||
|
* It should therefore NOT contain any rules or plugins which are specific
|
||||||
|
* to one particular variant, e.g. TypeScript vs JavaScript, .js vs .jsx etc
|
||||||
|
*
|
||||||
|
* This configuration is intended to be combined with other configs from this
|
||||||
|
* package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rule set originally adapted from:
|
||||||
|
* https://github.com/facebook/create-react-app/blob/567f36c9235f1e1fd4a76dc6d1ae00be754ca047/packages/eslint-config-react-app/index.js
|
||||||
|
*/
|
||||||
|
export default tseslint.config({
|
||||||
|
plugins: { import: fixupPluginRules(importPlugin) },
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.commonjs,
|
||||||
|
...globals.es2015,
|
||||||
|
...globals.jest,
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
/**
|
||||||
|
* Standard ESLint rule configurations
|
||||||
|
* https://eslint.org/docs/rules
|
||||||
|
*/
|
||||||
|
'array-callback-return': 'warn',
|
||||||
|
'dot-location': ['warn', 'property'],
|
||||||
|
eqeqeq: ['warn', 'smart'],
|
||||||
|
'new-parens': 'warn',
|
||||||
|
'no-caller': 'warn',
|
||||||
|
'no-cond-assign': ['warn', 'except-parens'],
|
||||||
|
'no-const-assign': 'warn',
|
||||||
|
'no-control-regex': 'warn',
|
||||||
|
'no-delete-var': 'warn',
|
||||||
|
'no-dupe-args': 'warn',
|
||||||
|
'no-dupe-keys': 'warn',
|
||||||
|
'no-duplicate-case': 'warn',
|
||||||
|
'no-empty-character-class': 'warn',
|
||||||
|
'no-empty-pattern': 'warn',
|
||||||
|
'no-eval': 'warn',
|
||||||
|
'no-ex-assign': 'warn',
|
||||||
|
'no-extend-native': 'warn',
|
||||||
|
'no-extra-bind': 'warn',
|
||||||
|
'no-extra-label': 'warn',
|
||||||
|
'no-fallthrough': 'warn',
|
||||||
|
'no-func-assign': 'warn',
|
||||||
|
'no-implied-eval': 'warn',
|
||||||
|
'no-invalid-regexp': 'warn',
|
||||||
|
'no-iterator': 'warn',
|
||||||
|
'no-label-var': 'warn',
|
||||||
|
'no-labels': ['warn', { allowLoop: true, allowSwitch: false }],
|
||||||
|
'no-lone-blocks': 'warn',
|
||||||
|
'no-loop-func': 'warn',
|
||||||
|
'no-mixed-operators': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
groups: [
|
||||||
|
['&', '|', '^', '~', '<<', '>>', '>>>'],
|
||||||
|
['==', '!=', '===', '!==', '>', '>=', '<', '<='],
|
||||||
|
['&&', '||'],
|
||||||
|
['in', 'instanceof'],
|
||||||
|
],
|
||||||
|
allowSamePrecedence: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'no-multi-str': 'warn',
|
||||||
|
'no-native-reassign': 'warn',
|
||||||
|
'no-negated-in-lhs': 'warn',
|
||||||
|
'no-new-func': 'warn',
|
||||||
|
'no-new-object': 'warn',
|
||||||
|
'no-new-symbol': 'warn',
|
||||||
|
'no-new-wrappers': 'warn',
|
||||||
|
'no-obj-calls': 'warn',
|
||||||
|
'no-octal': 'warn',
|
||||||
|
'no-octal-escape': 'warn',
|
||||||
|
'no-redeclare': 'warn',
|
||||||
|
'no-regex-spaces': 'warn',
|
||||||
|
'no-restricted-syntax': ['warn', 'WithStatement'],
|
||||||
|
'no-script-url': 'warn',
|
||||||
|
'no-self-assign': 'warn',
|
||||||
|
'no-self-compare': 'warn',
|
||||||
|
'no-sequences': 'warn',
|
||||||
|
'no-shadow-restricted-names': 'warn',
|
||||||
|
'no-sparse-arrays': 'warn',
|
||||||
|
'no-template-curly-in-string': 'warn',
|
||||||
|
'no-this-before-super': 'warn',
|
||||||
|
'no-throw-literal': 'warn',
|
||||||
|
'no-restricted-globals': ['error', ...require('confusing-browser-globals')],
|
||||||
|
'no-unexpected-multiline': 'warn',
|
||||||
|
'no-unreachable': 'warn',
|
||||||
|
'no-unused-expressions': 'off',
|
||||||
|
'no-unused-labels': 'warn',
|
||||||
|
'no-useless-computed-key': 'warn',
|
||||||
|
'no-useless-concat': 'warn',
|
||||||
|
'no-useless-escape': 'warn',
|
||||||
|
'no-useless-rename': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
ignoreDestructuring: false,
|
||||||
|
ignoreImport: false,
|
||||||
|
ignoreExport: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'no-with': 'warn',
|
||||||
|
'no-whitespace-before-property': 'warn',
|
||||||
|
'require-yield': 'warn',
|
||||||
|
'rest-spread-spacing': ['warn', 'never'],
|
||||||
|
strict: ['warn', 'never'],
|
||||||
|
'unicode-bom': ['warn', 'never'],
|
||||||
|
'use-isnan': 'warn',
|
||||||
|
'valid-typeof': 'warn',
|
||||||
|
'no-restricted-properties': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
object: 'require',
|
||||||
|
property: 'ensure',
|
||||||
|
message:
|
||||||
|
'Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'System',
|
||||||
|
property: 'import',
|
||||||
|
message:
|
||||||
|
'Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'getter-return': 'warn',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import rule configurations
|
||||||
|
* https://github.com/benmosher/eslint-plugin-import
|
||||||
|
*/
|
||||||
|
'import/first': 'error',
|
||||||
|
'import/no-amd': 'error',
|
||||||
|
'import/no-webpack-loader-syntax': 'error',
|
||||||
|
},
|
||||||
|
});
|
||||||
78
packages/eslint-plugin/src/flat-configs/react-jsx.ts
Normal file
78
packages/eslint-plugin/src/flat-configs/react-jsx.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';
|
||||||
|
import reactPlugin from 'eslint-plugin-react';
|
||||||
|
import reactHooksPlugin from 'eslint-plugin-react-hooks';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This configuration is intended to be applied to ONLY files which contain JSX/TSX
|
||||||
|
* code.
|
||||||
|
*
|
||||||
|
* It should therefore NOT contain any rules or plugins which are generic
|
||||||
|
* to all file types within variants of React projects.
|
||||||
|
*
|
||||||
|
* This configuration is intended to be combined with other configs from this
|
||||||
|
* package.
|
||||||
|
*/
|
||||||
|
export default tseslint.config(
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
'react-hooks': reactHooksPlugin,
|
||||||
|
},
|
||||||
|
rules: reactHooksPlugin.configs.recommended.rules,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
settings: { react: { version: 'detect' } },
|
||||||
|
plugins: {
|
||||||
|
'jsx-a11y': jsxA11yPlugin,
|
||||||
|
react: reactPlugin,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
/**
|
||||||
|
* React-specific rule configurations
|
||||||
|
* https://github.com/yannickcr/eslint-plugin-react
|
||||||
|
*/
|
||||||
|
'react/forbid-foreign-prop-types': ['warn', { allowInPropTypes: true }],
|
||||||
|
'react/jsx-no-comment-textnodes': 'warn',
|
||||||
|
'react/jsx-no-duplicate-props': 'warn',
|
||||||
|
'react/jsx-no-target-blank': 'warn',
|
||||||
|
'react/jsx-no-undef': 'error',
|
||||||
|
'react/jsx-pascal-case': ['warn', { allowAllCaps: true, ignore: [] }],
|
||||||
|
'react/jsx-uses-vars': 'warn',
|
||||||
|
'react/no-danger-with-children': 'warn',
|
||||||
|
'react/no-direct-mutation-state': 'warn',
|
||||||
|
'react/no-is-mounted': 'warn',
|
||||||
|
'react/no-typos': 'error',
|
||||||
|
'react/jsx-uses-react': 'off',
|
||||||
|
'react/react-in-jsx-scope': 'off',
|
||||||
|
'react/require-render-return': 'error',
|
||||||
|
'react/style-prop-object': 'warn',
|
||||||
|
'react/jsx-no-useless-fragment': 'warn',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSX Accessibility rule configurations
|
||||||
|
* https://github.com/evcohen/eslint-plugin-jsx-a11y
|
||||||
|
*/
|
||||||
|
'jsx-a11y/accessible-emoji': 'warn',
|
||||||
|
'jsx-a11y/alt-text': 'warn',
|
||||||
|
'jsx-a11y/anchor-has-content': 'warn',
|
||||||
|
'jsx-a11y/anchor-is-valid': [
|
||||||
|
'warn',
|
||||||
|
{ aspects: ['noHref', 'invalidHref'] },
|
||||||
|
],
|
||||||
|
'jsx-a11y/aria-activedescendant-has-tabindex': 'warn',
|
||||||
|
'jsx-a11y/aria-props': 'warn',
|
||||||
|
'jsx-a11y/aria-proptypes': 'warn',
|
||||||
|
'jsx-a11y/aria-role': 'warn',
|
||||||
|
'jsx-a11y/aria-unsupported-elements': 'warn',
|
||||||
|
'jsx-a11y/heading-has-content': 'warn',
|
||||||
|
'jsx-a11y/iframe-has-title': 'warn',
|
||||||
|
'jsx-a11y/img-redundant-alt': 'warn',
|
||||||
|
'jsx-a11y/no-access-key': 'warn',
|
||||||
|
'jsx-a11y/no-distracting-elements': 'warn',
|
||||||
|
'jsx-a11y/no-redundant-roles': 'warn',
|
||||||
|
'jsx-a11y/role-has-required-aria-props': 'warn',
|
||||||
|
'jsx-a11y/role-supports-aria-props': 'warn',
|
||||||
|
'jsx-a11y/scope': 'warn',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
14
packages/eslint-plugin/src/flat-configs/react-tmp.ts
Normal file
14
packages/eslint-plugin/src/flat-configs/react-tmp.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
import reactBase from './react-base';
|
||||||
|
import reactTypescript from './react-typescript';
|
||||||
|
import reactJsx from './react-jsx';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* THIS IS A TEMPORARY CONFIG WHICH MATCHES THE CURRENT BEHAVIOR
|
||||||
|
* of including all the rules for all file types within the ESLint
|
||||||
|
* config for React projects.
|
||||||
|
*
|
||||||
|
* It will be refactored in a follow up PR to correctly apply rules
|
||||||
|
* to the right file types via overrides.
|
||||||
|
*/
|
||||||
|
export default tseslint.config(...reactBase, ...reactTypescript, ...reactJsx);
|
||||||
55
packages/eslint-plugin/src/flat-configs/react-typescript.ts
Normal file
55
packages/eslint-plugin/src/flat-configs/react-typescript.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This configuration is intended to be applied to ONLY .ts and .tsx files within a
|
||||||
|
* React project in an Nx workspace.
|
||||||
|
*
|
||||||
|
* It should therefore NOT contain any rules or plugins which are generic
|
||||||
|
* to all variants of React projects, e.g. TypeScript vs JavaScript, .js vs .jsx etc
|
||||||
|
*
|
||||||
|
* This configuration is intended to be combined with other configs from this
|
||||||
|
* package.
|
||||||
|
*/
|
||||||
|
export default tseslint.config({
|
||||||
|
rules: {
|
||||||
|
// TypeScript"s `noFallthroughCasesInSwitch` option is more robust (#6906)
|
||||||
|
'default-case': 'off',
|
||||||
|
// "tsc" already handles this (https://github.com/typescript-eslint/typescript-eslint/issues/291)
|
||||||
|
'no-dupe-class-members': 'off',
|
||||||
|
// "tsc" already handles this (https://github.com/typescript-eslint/typescript-eslint/issues/477)
|
||||||
|
'no-undef': 'off',
|
||||||
|
|
||||||
|
// Add TypeScript specific rules (and turn off ESLint equivalents)
|
||||||
|
'no-array-constructor': 'off',
|
||||||
|
'@typescript-eslint/no-array-constructor': 'warn',
|
||||||
|
'@typescript-eslint/no-namespace': 'error',
|
||||||
|
'no-use-before-define': 'off',
|
||||||
|
'@typescript-eslint/no-use-before-define': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
functions: false,
|
||||||
|
classes: false,
|
||||||
|
variables: false,
|
||||||
|
typedefs: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
args: 'none',
|
||||||
|
ignoreRestSiblings: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'no-useless-constructor': 'off',
|
||||||
|
'@typescript-eslint/no-useless-constructor': 'warn',
|
||||||
|
'@typescript-eslint/no-unused-expressions': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
allowShortCircuit: true,
|
||||||
|
allowTernary: true,
|
||||||
|
allowTaggedTemplates: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
66
packages/eslint-plugin/src/flat-configs/typescript.ts
Normal file
66
packages/eslint-plugin/src/flat-configs/typescript.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import eslint from '@eslint/js';
|
||||||
|
import { workspaceRoot } from '@nx/devkit';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
import { packageExists } from '../utils/config-utils';
|
||||||
|
|
||||||
|
const isPrettierAvailable =
|
||||||
|
packageExists('prettier') && packageExists('eslint-config-prettier');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This configuration is intended to be applied to ALL .ts and .tsx files
|
||||||
|
* within an Nx workspace.
|
||||||
|
*
|
||||||
|
* It should therefore NOT contain any rules or plugins which are specific
|
||||||
|
* to one ecosystem, such as React, Angular, Node etc.
|
||||||
|
*/
|
||||||
|
export default tseslint.config(
|
||||||
|
eslint.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
...(isPrettierAvailable ? [require('eslint-config-prettier')] : []),
|
||||||
|
{
|
||||||
|
plugins: { '@typescript-eslint': tseslint.plugin },
|
||||||
|
languageOptions: {
|
||||||
|
parser: tseslint.parser,
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
sourceType: 'module',
|
||||||
|
parserOptions: {
|
||||||
|
tsconfigRootDir: workspaceRoot,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.tsx'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/explicit-member-accessibility': 'off',
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
|
'@typescript-eslint/no-parameter-properties': 'off',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From https://typescript-eslint.io/blog/announcing-typescript-eslint-v6/#updated-configuration-rules
|
||||||
|
*
|
||||||
|
* The following rules were added to preserve the linting rules that were
|
||||||
|
* previously defined v5 of `@typescript-eslint`. v6 of `@typescript-eslint`
|
||||||
|
* changed how configurations are defined.
|
||||||
|
*
|
||||||
|
* TODO(v20): re-evalute these deviations from @typescript-eslint/recommended in v20 of Nx
|
||||||
|
*/
|
||||||
|
'@typescript-eslint/no-non-null-assertion': 'warn',
|
||||||
|
'@typescript-eslint/adjacent-overload-signatures': 'error',
|
||||||
|
'@typescript-eslint/prefer-namespace-keyword': 'error',
|
||||||
|
'no-empty-function': 'off',
|
||||||
|
'@typescript-eslint/no-empty-function': 'error',
|
||||||
|
'@typescript-eslint/no-inferrable-types': 'error',
|
||||||
|
'@typescript-eslint/no-unused-vars': 'warn',
|
||||||
|
'@typescript-eslint/no-empty-interface': 'error',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
|
/**
|
||||||
|
* During the migration to use ESLint v9 and typescript-eslint v8 for new workspaces,
|
||||||
|
* this rule would have created a lot of noise, so we are disabling it by default for now.
|
||||||
|
*
|
||||||
|
* TODO(v20): we should make this part of what we re-evaluate in v20
|
||||||
|
*/
|
||||||
|
'@typescript-eslint/no-require-imports': 'off',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
@ -7,6 +7,8 @@ import reactTypescript from './configs/react-typescript';
|
|||||||
import angularCode from './configs/angular';
|
import angularCode from './configs/angular';
|
||||||
import angularTemplate from './configs/angular-template';
|
import angularTemplate from './configs/angular-template';
|
||||||
|
|
||||||
|
import flatBase from './flat-configs/base';
|
||||||
|
|
||||||
import enforceModuleBoundaries, {
|
import enforceModuleBoundaries, {
|
||||||
RULE_NAME as enforceModuleBoundariesRuleName,
|
RULE_NAME as enforceModuleBoundariesRuleName,
|
||||||
} from './rules/enforce-module-boundaries';
|
} from './rules/enforce-module-boundaries';
|
||||||
@ -24,6 +26,7 @@ import { workspaceRules } from './resolve-workspace-rules';
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
configs: {
|
configs: {
|
||||||
|
// eslintrc configs
|
||||||
typescript,
|
typescript,
|
||||||
javascript,
|
javascript,
|
||||||
react: reactTmp,
|
react: reactTmp,
|
||||||
@ -32,6 +35,34 @@ module.exports = {
|
|||||||
'react-jsx': reactJsx,
|
'react-jsx': reactJsx,
|
||||||
angular: angularCode,
|
angular: angularCode,
|
||||||
'angular-template': angularTemplate,
|
'angular-template': angularTemplate,
|
||||||
|
|
||||||
|
// flat configs
|
||||||
|
// Note: Using getters here to avoid importing packages `angular-eslint` statically, which can lead to errors if not installed.
|
||||||
|
'flat/base': flatBase,
|
||||||
|
get ['flat/typescript']() {
|
||||||
|
return require('./flat-configs/typescript').default;
|
||||||
|
},
|
||||||
|
get ['flat/javascript']() {
|
||||||
|
return require('./flat-configs/javascript').default;
|
||||||
|
},
|
||||||
|
get ['flat/react']() {
|
||||||
|
return require('./flat-configs/react-tmp').default;
|
||||||
|
},
|
||||||
|
get ['flat/react-base']() {
|
||||||
|
return require('./flat-configs/react-base').default;
|
||||||
|
},
|
||||||
|
get ['flat/react-typescript']() {
|
||||||
|
return require('./flat-configs/react-typescript').default;
|
||||||
|
},
|
||||||
|
get ['flat/react-jsx']() {
|
||||||
|
return require('./flat-configs/react-jsx').default;
|
||||||
|
},
|
||||||
|
get ['flat/angular']() {
|
||||||
|
return require('./flat-configs/angular').default;
|
||||||
|
},
|
||||||
|
get ['flat/angular-template']() {
|
||||||
|
return require('./flat-configs/angular-template').default;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
[enforceModuleBoundariesRuleName]: enforceModuleBoundaries,
|
[enforceModuleBoundariesRuleName]: enforceModuleBoundaries,
|
||||||
|
|||||||
@ -47,7 +47,6 @@ export default ESLintUtils.RuleCreator(
|
|||||||
type: 'suggestion',
|
type: 'suggestion',
|
||||||
docs: {
|
docs: {
|
||||||
description: `Checks dependencies in project's package.json for version mismatches`,
|
description: `Checks dependencies in project's package.json for version mismatches`,
|
||||||
recommended: 'recommended',
|
|
||||||
},
|
},
|
||||||
fixable: 'code',
|
fixable: 'code',
|
||||||
schema: [
|
schema: [
|
||||||
|
|||||||
@ -91,7 +91,6 @@ export default ESLintUtils.RuleCreator(
|
|||||||
type: 'suggestion',
|
type: 'suggestion',
|
||||||
docs: {
|
docs: {
|
||||||
description: `Ensure that module boundaries are respected within the monorepo`,
|
description: `Ensure that module boundaries are respected within the monorepo`,
|
||||||
recommended: 'recommended',
|
|
||||||
},
|
},
|
||||||
fixable: 'code',
|
fixable: 'code',
|
||||||
schema: [
|
schema: [
|
||||||
|
|||||||
@ -57,7 +57,6 @@ export default ESLintUtils.RuleCreator(() => ``)<Options, MessageIds>({
|
|||||||
meta: {
|
meta: {
|
||||||
docs: {
|
docs: {
|
||||||
description: 'Checks common nx-plugin configuration files for validity',
|
description: 'Checks common nx-plugin configuration files for validity',
|
||||||
recommended: 'recommended',
|
|
||||||
},
|
},
|
||||||
schema: [
|
schema: [
|
||||||
{
|
{
|
||||||
|
|||||||
16
packages/eslint-plugin/typescript.ts
Normal file
16
packages/eslint-plugin/typescript.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import javascript from './src/flat-configs/javascript';
|
||||||
|
import typescript from './src/flat-configs/typescript';
|
||||||
|
|
||||||
|
const plugin = {
|
||||||
|
configs: {
|
||||||
|
javascript,
|
||||||
|
typescript,
|
||||||
|
},
|
||||||
|
rules: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
// ESM
|
||||||
|
export default plugin;
|
||||||
|
|
||||||
|
// CommonJS
|
||||||
|
module.exports = plugin;
|
||||||
@ -1,8 +1,8 @@
|
|||||||
jest.mock('eslint', () => ({
|
jest.mock('eslint/use-at-your-own-risk', () => ({
|
||||||
ESLint: jest.fn(),
|
LegacyESLint: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { ESLint } from 'eslint';
|
const { LegacyESLint } = require('eslint/use-at-your-own-risk');
|
||||||
import { resolveAndInstantiateESLint } from './eslint-utils';
|
import { resolveAndInstantiateESLint } from './eslint-utils';
|
||||||
|
|
||||||
describe('eslint-utils', () => {
|
describe('eslint-utils', () => {
|
||||||
@ -18,7 +18,7 @@ describe('eslint-utils', () => {
|
|||||||
cacheStrategy: 'content',
|
cacheStrategy: 'content',
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
|
|
||||||
expect(ESLint).toHaveBeenCalledWith({
|
expect(LegacyESLint).toHaveBeenCalledWith({
|
||||||
overrideConfigFile: './.eslintrc.json',
|
overrideConfigFile: './.eslintrc.json',
|
||||||
fix: true,
|
fix: true,
|
||||||
cache: true,
|
cache: true,
|
||||||
@ -40,7 +40,7 @@ describe('eslint-utils', () => {
|
|||||||
cacheStrategy: 'content',
|
cacheStrategy: 'content',
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
|
|
||||||
expect(ESLint).toHaveBeenCalledWith({
|
expect(LegacyESLint).toHaveBeenCalledWith({
|
||||||
overrideConfigFile: undefined,
|
overrideConfigFile: undefined,
|
||||||
fix: true,
|
fix: true,
|
||||||
cache: true,
|
cache: true,
|
||||||
@ -63,7 +63,7 @@ describe('eslint-utils', () => {
|
|||||||
noEslintrc: true,
|
noEslintrc: true,
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
|
|
||||||
expect(ESLint).toHaveBeenCalledWith({
|
expect(LegacyESLint).toHaveBeenCalledWith({
|
||||||
overrideConfigFile: undefined,
|
overrideConfigFile: undefined,
|
||||||
fix: true,
|
fix: true,
|
||||||
cache: true,
|
cache: true,
|
||||||
@ -89,7 +89,7 @@ describe('eslint-utils', () => {
|
|||||||
rulesdir: extraRuleDirectories,
|
rulesdir: extraRuleDirectories,
|
||||||
} as any).catch(() => {});
|
} as any).catch(() => {});
|
||||||
|
|
||||||
expect(ESLint).toHaveBeenCalledWith({
|
expect(LegacyESLint).toHaveBeenCalledWith({
|
||||||
overrideConfigFile: undefined,
|
overrideConfigFile: undefined,
|
||||||
fix: true,
|
fix: true,
|
||||||
cache: true,
|
cache: true,
|
||||||
@ -114,7 +114,7 @@ describe('eslint-utils', () => {
|
|||||||
resolvePluginsRelativeTo: './some-path',
|
resolvePluginsRelativeTo: './some-path',
|
||||||
} as any).catch(() => {});
|
} as any).catch(() => {});
|
||||||
|
|
||||||
expect(ESLint).toHaveBeenCalledWith({
|
expect(LegacyESLint).toHaveBeenCalledWith({
|
||||||
overrideConfigFile: undefined,
|
overrideConfigFile: undefined,
|
||||||
fix: true,
|
fix: true,
|
||||||
cache: true,
|
cache: true,
|
||||||
@ -135,7 +135,7 @@ describe('eslint-utils', () => {
|
|||||||
reportUnusedDisableDirectives: 'error',
|
reportUnusedDisableDirectives: 'error',
|
||||||
} as any).catch(() => {});
|
} as any).catch(() => {});
|
||||||
|
|
||||||
expect(ESLint).toHaveBeenCalledWith({
|
expect(LegacyESLint).toHaveBeenCalledWith({
|
||||||
cache: false,
|
cache: false,
|
||||||
cacheLocation: undefined,
|
cacheLocation: undefined,
|
||||||
cacheStrategy: undefined,
|
cacheStrategy: undefined,
|
||||||
@ -153,7 +153,7 @@ describe('eslint-utils', () => {
|
|||||||
it('should create a ESLint instance with no "reportUnusedDisableDirectives" if it is undefined', async () => {
|
it('should create a ESLint instance with no "reportUnusedDisableDirectives" if it is undefined', async () => {
|
||||||
await resolveAndInstantiateESLint(undefined, {} as any);
|
await resolveAndInstantiateESLint(undefined, {} as any);
|
||||||
|
|
||||||
expect(ESLint).toHaveBeenCalledWith(
|
expect(LegacyESLint).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
reportUnusedDisableDirectives: undefined,
|
reportUnusedDisableDirectives: undefined,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -14,7 +14,9 @@ export async function resolveAndInstantiateESLint(
|
|||||||
'When using the new Flat Config with ESLint, all configs must be named eslint.config.js or eslint.config.cjs and .eslintrc files may not be used. See https://eslint.org/docs/latest/use/configure/configuration-files'
|
'When using the new Flat Config with ESLint, all configs must be named eslint.config.js or eslint.config.cjs and .eslintrc files may not be used. See https://eslint.org/docs/latest/use/configure/configuration-files'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const ESLint = await resolveESLintClass(useFlatConfig);
|
const ESLint = await resolveESLintClass({
|
||||||
|
useFlatConfigOverrideVal: useFlatConfig,
|
||||||
|
});
|
||||||
|
|
||||||
const eslintOptions: ESLint.Options = {
|
const eslintOptions: ESLint.Options = {
|
||||||
overrideConfigFile: eslintConfigPath,
|
overrideConfigFile: eslintConfigPath,
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
exports[`convert-to-flat-config generator should add env configuration 1`] = `
|
exports[`convert-to-flat-config generator should add env configuration 1`] = `
|
||||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||||
|
const js = require('@eslint/js');
|
||||||
const nxEslintPlugin = require('@nx/eslint-plugin');
|
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||||
const globals = require('globals');
|
const globals = require('globals');
|
||||||
const js = require('@eslint/js');
|
|
||||||
|
|
||||||
const compat = new FlatCompat({
|
const compat = new FlatCompat({
|
||||||
baseDirectory: __dirname,
|
baseDirectory: __dirname,
|
||||||
@ -52,9 +52,9 @@ module.exports = [
|
|||||||
|
|
||||||
exports[`convert-to-flat-config generator should add global and env configuration 1`] = `
|
exports[`convert-to-flat-config generator should add global and env configuration 1`] = `
|
||||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||||
|
const js = require('@eslint/js');
|
||||||
const nxEslintPlugin = require('@nx/eslint-plugin');
|
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||||
const globals = require('globals');
|
const globals = require('globals');
|
||||||
const js = require('@eslint/js');
|
|
||||||
|
|
||||||
const compat = new FlatCompat({
|
const compat = new FlatCompat({
|
||||||
baseDirectory: __dirname,
|
baseDirectory: __dirname,
|
||||||
@ -106,8 +106,8 @@ module.exports = [
|
|||||||
|
|
||||||
exports[`convert-to-flat-config generator should add global configuration 1`] = `
|
exports[`convert-to-flat-config generator should add global configuration 1`] = `
|
||||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||||
const nxEslintPlugin = require('@nx/eslint-plugin');
|
|
||||||
const js = require('@eslint/js');
|
const js = require('@eslint/js');
|
||||||
|
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||||
|
|
||||||
const compat = new FlatCompat({
|
const compat = new FlatCompat({
|
||||||
baseDirectory: __dirname,
|
baseDirectory: __dirname,
|
||||||
@ -155,8 +155,8 @@ module.exports = [
|
|||||||
|
|
||||||
exports[`convert-to-flat-config generator should add global eslintignores 1`] = `
|
exports[`convert-to-flat-config generator should add global eslintignores 1`] = `
|
||||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||||
const nxEslintPlugin = require('@nx/eslint-plugin');
|
|
||||||
const js = require('@eslint/js');
|
const js = require('@eslint/js');
|
||||||
|
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||||
|
|
||||||
const compat = new FlatCompat({
|
const compat = new FlatCompat({
|
||||||
baseDirectory: __dirname,
|
baseDirectory: __dirname,
|
||||||
@ -204,9 +204,9 @@ module.exports = [
|
|||||||
|
|
||||||
exports[`convert-to-flat-config generator should add parser 1`] = `
|
exports[`convert-to-flat-config generator should add parser 1`] = `
|
||||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||||
|
const js = require('@eslint/js');
|
||||||
const nxEslintPlugin = require('@nx/eslint-plugin');
|
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||||
const typescriptEslintParser = require('@typescript-eslint/parser');
|
const typescriptEslintParser = require('@typescript-eslint/parser');
|
||||||
const js = require('@eslint/js');
|
|
||||||
|
|
||||||
const compat = new FlatCompat({
|
const compat = new FlatCompat({
|
||||||
baseDirectory: __dirname,
|
baseDirectory: __dirname,
|
||||||
@ -254,11 +254,11 @@ module.exports = [
|
|||||||
|
|
||||||
exports[`convert-to-flat-config generator should add plugins 1`] = `
|
exports[`convert-to-flat-config generator should add plugins 1`] = `
|
||||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||||
|
const js = require('@eslint/js');
|
||||||
const eslintPluginImport = require('eslint-plugin-import');
|
const eslintPluginImport = require('eslint-plugin-import');
|
||||||
const eslintPluginSingleName = require('eslint-plugin-single-name');
|
const eslintPluginSingleName = require('eslint-plugin-single-name');
|
||||||
const scopeEslintPluginWithName = require('@scope/eslint-plugin-with-name');
|
const scopeEslintPluginWithName = require('@scope/eslint-plugin-with-name');
|
||||||
const justScopeEslintPlugin = require('@just-scope/eslint-plugin');
|
const justScopeEslintPlugin = require('@just-scope/eslint-plugin');
|
||||||
const js = require('@eslint/js');
|
|
||||||
|
|
||||||
const compat = new FlatCompat({
|
const compat = new FlatCompat({
|
||||||
baseDirectory: __dirname,
|
baseDirectory: __dirname,
|
||||||
@ -312,8 +312,8 @@ module.exports = [
|
|||||||
|
|
||||||
exports[`convert-to-flat-config generator should add settings 1`] = `
|
exports[`convert-to-flat-config generator should add settings 1`] = `
|
||||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||||
const nxEslintPlugin = require('@nx/eslint-plugin');
|
|
||||||
const js = require('@eslint/js');
|
const js = require('@eslint/js');
|
||||||
|
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||||
|
|
||||||
const compat = new FlatCompat({
|
const compat = new FlatCompat({
|
||||||
baseDirectory: __dirname,
|
baseDirectory: __dirname,
|
||||||
@ -361,8 +361,8 @@ module.exports = [
|
|||||||
|
|
||||||
exports[`convert-to-flat-config generator should convert json successfully 1`] = `
|
exports[`convert-to-flat-config generator should convert json successfully 1`] = `
|
||||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||||
const nxEslintPlugin = require('@nx/eslint-plugin');
|
|
||||||
const js = require('@eslint/js');
|
const js = require('@eslint/js');
|
||||||
|
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||||
|
|
||||||
const compat = new FlatCompat({
|
const compat = new FlatCompat({
|
||||||
baseDirectory: __dirname,
|
baseDirectory: __dirname,
|
||||||
@ -414,14 +414,17 @@ module.exports = [
|
|||||||
...baseConfig,
|
...baseConfig,
|
||||||
{
|
{
|
||||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||||
|
// Override or add rules here
|
||||||
rules: {},
|
rules: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.ts', '**/*.tsx'],
|
files: ['**/*.ts', '**/*.tsx'],
|
||||||
|
// Override or add rules here
|
||||||
rules: {},
|
rules: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.js', '**/*.jsx'],
|
files: ['**/*.js', '**/*.jsx'],
|
||||||
|
// Override or add rules here
|
||||||
rules: {},
|
rules: {},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -430,8 +433,8 @@ module.exports = [
|
|||||||
|
|
||||||
exports[`convert-to-flat-config generator should convert yaml successfully 1`] = `
|
exports[`convert-to-flat-config generator should convert yaml successfully 1`] = `
|
||||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||||
const nxEslintPlugin = require('@nx/eslint-plugin');
|
|
||||||
const js = require('@eslint/js');
|
const js = require('@eslint/js');
|
||||||
|
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||||
|
|
||||||
const compat = new FlatCompat({
|
const compat = new FlatCompat({
|
||||||
baseDirectory: __dirname,
|
baseDirectory: __dirname,
|
||||||
@ -483,14 +486,17 @@ module.exports = [
|
|||||||
...baseConfig,
|
...baseConfig,
|
||||||
{
|
{
|
||||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||||
|
// Override or add rules here
|
||||||
rules: {},
|
rules: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.ts', '**/*.tsx'],
|
files: ['**/*.ts', '**/*.tsx'],
|
||||||
|
// Override or add rules here
|
||||||
rules: {},
|
rules: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.js', '**/*.jsx'],
|
files: ['**/*.js', '**/*.jsx'],
|
||||||
|
// Override or add rules here
|
||||||
rules: {},
|
rules: {},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -499,8 +505,8 @@ module.exports = [
|
|||||||
|
|
||||||
exports[`convert-to-flat-config generator should convert yml successfully 1`] = `
|
exports[`convert-to-flat-config generator should convert yml successfully 1`] = `
|
||||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||||
const nxEslintPlugin = require('@nx/eslint-plugin');
|
|
||||||
const js = require('@eslint/js');
|
const js = require('@eslint/js');
|
||||||
|
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||||
|
|
||||||
const compat = new FlatCompat({
|
const compat = new FlatCompat({
|
||||||
baseDirectory: __dirname,
|
baseDirectory: __dirname,
|
||||||
@ -552,14 +558,17 @@ module.exports = [
|
|||||||
...baseConfig,
|
...baseConfig,
|
||||||
{
|
{
|
||||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||||
|
// Override or add rules here
|
||||||
rules: {},
|
rules: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.ts', '**/*.tsx'],
|
files: ['**/*.ts', '**/*.tsx'],
|
||||||
|
// Override or add rules here
|
||||||
rules: {},
|
rules: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.js', '**/*.jsx'],
|
files: ['**/*.js', '**/*.jsx'],
|
||||||
|
// Override or add rules here
|
||||||
rules: {},
|
rules: {},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -573,14 +582,17 @@ module.exports = [
|
|||||||
...baseConfig,
|
...baseConfig,
|
||||||
{
|
{
|
||||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||||
|
// Override or add rules here
|
||||||
rules: {},
|
rules: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.ts', '**/*.tsx'],
|
files: ['**/*.ts', '**/*.tsx'],
|
||||||
|
// Override or add rules here
|
||||||
rules: {},
|
rules: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.js', '**/*.jsx'],
|
files: ['**/*.js', '**/*.jsx'],
|
||||||
|
// Override or add rules here
|
||||||
rules: {},
|
rules: {},
|
||||||
},
|
},
|
||||||
{ ignores: ['ignore/me'] },
|
{ ignores: ['ignore/me'] },
|
||||||
|
|||||||
@ -67,8 +67,8 @@ describe('convertEslintJsonToFlatConfig', () => {
|
|||||||
|
|
||||||
expect(content).toMatchInlineSnapshot(`
|
expect(content).toMatchInlineSnapshot(`
|
||||||
"const { FlatCompat } = require("@eslint/eslintrc");
|
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||||
const nxEslintPlugin = require("@nx/eslint-plugin");
|
|
||||||
const js = require("@eslint/js");
|
const js = require("@eslint/js");
|
||||||
|
const nxEslintPlugin = require("@nx/eslint-plugin");
|
||||||
|
|
||||||
const compat = new FlatCompat({
|
const compat = new FlatCompat({
|
||||||
baseDirectory: __dirname,
|
baseDirectory: __dirname,
|
||||||
@ -182,9 +182,9 @@ describe('convertEslintJsonToFlatConfig', () => {
|
|||||||
|
|
||||||
expect(content).toMatchInlineSnapshot(`
|
expect(content).toMatchInlineSnapshot(`
|
||||||
"const { FlatCompat } = require("@eslint/eslintrc");
|
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||||
|
const js = require("@eslint/js");
|
||||||
const baseConfig = require("../../eslint.config.js");
|
const baseConfig = require("../../eslint.config.js");
|
||||||
const globals = require("globals");
|
const globals = require("globals");
|
||||||
const js = require("@eslint/js");
|
|
||||||
|
|
||||||
const compat = new FlatCompat({
|
const compat = new FlatCompat({
|
||||||
baseDirectory: __dirname,
|
baseDirectory: __dirname,
|
||||||
@ -213,6 +213,7 @@ describe('convertEslintJsonToFlatConfig', () => {
|
|||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"**/*.tsx"
|
"**/*.tsx"
|
||||||
],
|
],
|
||||||
|
// Override or add rules here
|
||||||
rules: {}
|
rules: {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -220,16 +221,14 @@ describe('convertEslintJsonToFlatConfig', () => {
|
|||||||
"**/*.js",
|
"**/*.js",
|
||||||
"**/*.jsx"
|
"**/*.jsx"
|
||||||
],
|
],
|
||||||
|
// Override or add rules here
|
||||||
rules: {}
|
rules: {}
|
||||||
},
|
},
|
||||||
...compat.config({ parser: "jsonc-eslint-parser" }).map(config => ({
|
{
|
||||||
...config,
|
|
||||||
files: ["**/*.json"],
|
files: ["**/*.json"],
|
||||||
rules: {
|
rules: { "@nx/dependency-checks": "error" },
|
||||||
...config.rules,
|
languageOptions: { parser: require("jsonc-eslint-parser") }
|
||||||
"@nx/dependency-checks": "error"
|
},
|
||||||
}
|
|
||||||
})),
|
|
||||||
{ ignores: [".next/**/*"] },
|
{ ignores: [".next/**/*"] },
|
||||||
{ ignores: ["something/else"] }
|
{ ignores: ["something/else"] }
|
||||||
];
|
];
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { Tree, names } from '@nx/devkit';
|
|||||||
import { ESLint } from 'eslint';
|
import { ESLint } from 'eslint';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import {
|
import {
|
||||||
|
addFlatCompatToFlatConfig,
|
||||||
createNodeList,
|
createNodeList,
|
||||||
generateAst,
|
generateAst,
|
||||||
generateFlatOverride,
|
generateFlatOverride,
|
||||||
@ -149,6 +150,21 @@ export function convertEslintJsonToFlatConfig(
|
|||||||
isFlatCompatNeeded = true;
|
isFlatCompatNeeded = true;
|
||||||
}
|
}
|
||||||
exportElements.push(generateFlatOverride(override));
|
exportElements.push(generateFlatOverride(override));
|
||||||
|
|
||||||
|
// eslint-plugin-import cannot be used with ESLint v9 yet
|
||||||
|
// TODO(jack): Once v9 support is released, remove this block.
|
||||||
|
// See: https://github.com/import-js/eslint-plugin-import/pull/2996
|
||||||
|
if (override.extends === 'plugin:@nx/react') {
|
||||||
|
exportElements.push(
|
||||||
|
generateFlatOverride({
|
||||||
|
rules: {
|
||||||
|
'import/first': 'off',
|
||||||
|
'import/no-amd': 'off',
|
||||||
|
'import/no-webpack-loader-syntax': 'off',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,14 +197,14 @@ export function convertEslintJsonToFlatConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create the node list and print it to new file
|
// create the node list and print it to new file
|
||||||
const nodeList = createNodeList(
|
const nodeList = createNodeList(importsMap, exportElements);
|
||||||
importsMap,
|
let content = stringifyNodeList(nodeList);
|
||||||
exportElements,
|
if (isFlatCompatNeeded) {
|
||||||
isFlatCompatNeeded
|
content = addFlatCompatToFlatConfig(content);
|
||||||
);
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: stringifyNodeList(nodeList),
|
content,
|
||||||
addESLintRC: isFlatCompatNeeded,
|
addESLintRC: isFlatCompatNeeded,
|
||||||
addESLintJS: isESLintJSNeeded,
|
addESLintJS: isESLintJSNeeded,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -147,8 +147,8 @@ describe('convert-to-flat-config generator', () => {
|
|||||||
|
|
||||||
expect(tree.read('eslint.config.js', 'utf-8')).toMatchInlineSnapshot(`
|
expect(tree.read('eslint.config.js', 'utf-8')).toMatchInlineSnapshot(`
|
||||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||||
const nxEslintPlugin = require('@nx/eslint-plugin');
|
|
||||||
const js = require('@eslint/js');
|
const js = require('@eslint/js');
|
||||||
|
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||||
|
|
||||||
const compat = new FlatCompat({
|
const compat = new FlatCompat({
|
||||||
baseDirectory: __dirname,
|
baseDirectory: __dirname,
|
||||||
@ -201,14 +201,17 @@ describe('convert-to-flat-config generator', () => {
|
|||||||
...baseConfig,
|
...baseConfig,
|
||||||
{
|
{
|
||||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||||
|
// Override or add rules here
|
||||||
rules: {},
|
rules: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.ts', '**/*.tsx'],
|
files: ['**/*.ts', '**/*.tsx'],
|
||||||
|
// Override or add rules here
|
||||||
rules: {},
|
rules: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.js', '**/*.jsx'],
|
files: ['**/*.js', '**/*.jsx'],
|
||||||
|
// Override or add rules here
|
||||||
rules: {},
|
rules: {},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -392,8 +395,8 @@ describe('convert-to-flat-config generator', () => {
|
|||||||
|
|
||||||
expect(tree.read('eslint.config.js', 'utf-8')).toMatchInlineSnapshot(`
|
expect(tree.read('eslint.config.js', 'utf-8')).toMatchInlineSnapshot(`
|
||||||
"const { FlatCompat } = require('@eslint/eslintrc');
|
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||||
const nxEslintPlugin = require('@nx/eslint-plugin');
|
|
||||||
const js = require('@eslint/js');
|
const js = require('@eslint/js');
|
||||||
|
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||||
|
|
||||||
const compat = new FlatCompat({
|
const compat = new FlatCompat({
|
||||||
baseDirectory: __dirname,
|
baseDirectory: __dirname,
|
||||||
@ -554,6 +557,7 @@ describe('convert-to-flat-config generator', () => {
|
|||||||
...baseConfig,
|
...baseConfig,
|
||||||
{
|
{
|
||||||
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||||
|
// Override or add rules here
|
||||||
rules: {},
|
rules: {},
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
parserOptions: { project: ['apps/dx-assets-ui/tsconfig.*?.json'] },
|
parserOptions: { project: ['apps/dx-assets-ui/tsconfig.*?.json'] },
|
||||||
@ -561,10 +565,12 @@ describe('convert-to-flat-config generator', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.ts', '**/*.tsx'],
|
files: ['**/*.ts', '**/*.tsx'],
|
||||||
|
// Override or add rules here
|
||||||
rules: {},
|
rules: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.js', '**/*.jsx'],
|
files: ['**/*.js', '**/*.jsx'],
|
||||||
|
// Override or add rules here
|
||||||
rules: {},
|
rules: {},
|
||||||
},
|
},
|
||||||
{ ignores: ['__fixtures__/**/*'] },
|
{ ignores: ['__fixtures__/**/*'] },
|
||||||
|
|||||||
@ -15,12 +15,16 @@ import {
|
|||||||
import { ConvertToFlatConfigGeneratorSchema } from './schema';
|
import { ConvertToFlatConfigGeneratorSchema } from './schema';
|
||||||
import { findEslintFile } from '../utils/eslint-file';
|
import { findEslintFile } from '../utils/eslint-file';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { eslintrcVersion, eslintVersion } from '../../utils/versions';
|
import {
|
||||||
|
eslint9__eslintVersion,
|
||||||
|
eslint9__typescriptESLintVersion,
|
||||||
|
eslintConfigPrettierVersion,
|
||||||
|
eslintrcVersion,
|
||||||
|
eslintVersion,
|
||||||
|
} from '../../utils/versions';
|
||||||
import { ESLint } from 'eslint';
|
import { ESLint } from 'eslint';
|
||||||
import { convertEslintJsonToFlatConfig } from './converters/json-converter';
|
import { convertEslintJsonToFlatConfig } from './converters/json-converter';
|
||||||
|
|
||||||
let shouldInstallDeps = false;
|
|
||||||
|
|
||||||
export async function convertToFlatConfigGenerator(
|
export async function convertToFlatConfigGenerator(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
options: ConvertToFlatConfigGeneratorSchema
|
options: ConvertToFlatConfigGeneratorSchema
|
||||||
@ -65,10 +69,8 @@ export async function convertToFlatConfigGenerator(
|
|||||||
await formatFiles(tree);
|
await formatFiles(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldInstallDeps) {
|
|
||||||
return () => installPackagesTask(tree);
|
return () => installPackagesTask(tree);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export default convertToFlatConfigGenerator;
|
export default convertToFlatConfigGenerator;
|
||||||
|
|
||||||
@ -221,25 +223,21 @@ function processConvertedConfig(
|
|||||||
// save new
|
// save new
|
||||||
tree.write(join(root, target), content);
|
tree.write(join(root, target), content);
|
||||||
|
|
||||||
|
// These dependencies are required for flat configs that are generated by subsequent app/lib generators.
|
||||||
|
const devDependencies: Record<string, string> = {
|
||||||
|
eslint: eslint9__eslintVersion,
|
||||||
|
'eslint-config-prettier': eslintConfigPrettierVersion,
|
||||||
|
'typescript-eslint': eslint9__typescriptESLintVersion,
|
||||||
|
};
|
||||||
|
|
||||||
// add missing packages
|
// add missing packages
|
||||||
if (addESLintRC) {
|
if (addESLintRC) {
|
||||||
shouldInstallDeps = true;
|
devDependencies['@eslint/eslintrc'] = eslintrcVersion;
|
||||||
addDependenciesToPackageJson(
|
|
||||||
tree,
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
'@eslint/eslintrc': eslintrcVersion,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addESLintJS) {
|
if (addESLintJS) {
|
||||||
shouldInstallDeps = true;
|
devDependencies['@eslint/js'] = eslintVersion;
|
||||||
addDependenciesToPackageJson(
|
|
||||||
tree,
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
'@eslint/js': eslintVersion,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addDependenciesToPackageJson(tree, {}, devDependencies);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,10 +2,9 @@ import { Linter } from 'eslint';
|
|||||||
import {
|
import {
|
||||||
addBlockToFlatConfigExport,
|
addBlockToFlatConfigExport,
|
||||||
addImportToFlatConfig,
|
addImportToFlatConfig,
|
||||||
addPluginsToExportsBlock,
|
|
||||||
createNodeList,
|
createNodeList,
|
||||||
generateAst,
|
|
||||||
generateFlatOverride,
|
generateFlatOverride,
|
||||||
|
generateFlatPredefinedConfig,
|
||||||
stringifyNodeList,
|
stringifyNodeList,
|
||||||
} from '../utils/flat-config/ast-utils';
|
} from '../utils/flat-config/ast-utils';
|
||||||
|
|
||||||
@ -93,40 +92,56 @@ export const getGlobalEsLintConfiguration = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getGlobalFlatEslintConfiguration = (
|
export const getGlobalFlatEslintConfiguration = (
|
||||||
unitTestRunner?: string,
|
|
||||||
rootProject?: boolean
|
rootProject?: boolean
|
||||||
): string => {
|
): string => {
|
||||||
const nodeList = createNodeList(new Map(), [], true);
|
const nodeList = createNodeList(new Map(), []);
|
||||||
let content = stringifyNodeList(nodeList);
|
let content = stringifyNodeList(nodeList);
|
||||||
content = addImportToFlatConfig(content, 'nxPlugin', '@nx/eslint-plugin');
|
content = addImportToFlatConfig(content, 'nx', '@nx/eslint-plugin');
|
||||||
content = addPluginsToExportsBlock(content, [
|
|
||||||
{ name: '@nx', varName: 'nxPlugin', imp: '@nx/eslint-plugin' },
|
content = addBlockToFlatConfigExport(
|
||||||
]);
|
content,
|
||||||
|
generateFlatPredefinedConfig('flat/base'),
|
||||||
|
{ insertAtTheEnd: false }
|
||||||
|
);
|
||||||
|
content = addBlockToFlatConfigExport(
|
||||||
|
content,
|
||||||
|
generateFlatPredefinedConfig('flat/typescript')
|
||||||
|
);
|
||||||
|
content = addBlockToFlatConfigExport(
|
||||||
|
content,
|
||||||
|
generateFlatPredefinedConfig('flat/javascript')
|
||||||
|
);
|
||||||
|
|
||||||
if (!rootProject) {
|
if (!rootProject) {
|
||||||
content = addBlockToFlatConfigExport(
|
content = addBlockToFlatConfigExport(
|
||||||
content,
|
content,
|
||||||
generateFlatOverride(moduleBoundariesOverride)
|
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,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
content = addBlockToFlatConfigExport(
|
content = addBlockToFlatConfigExport(
|
||||||
content,
|
content,
|
||||||
generateFlatOverride(typeScriptOverride)
|
generateFlatOverride({
|
||||||
);
|
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||||
content = addBlockToFlatConfigExport(
|
rules: {},
|
||||||
content,
|
|
||||||
generateFlatOverride(javaScriptOverride)
|
|
||||||
);
|
|
||||||
if (unitTestRunner === 'jest') {
|
|
||||||
content = addBlockToFlatConfigExport(
|
|
||||||
content,
|
|
||||||
generateFlatOverride(jestOverride)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// add ignore for .nx folder
|
|
||||||
content = addBlockToFlatConfigExport(
|
|
||||||
content,
|
|
||||||
generateAst({
|
|
||||||
ignores: ['.nx'],
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import {
|
|||||||
generateSpreadElement,
|
generateSpreadElement,
|
||||||
removeCompatExtends,
|
removeCompatExtends,
|
||||||
removePlugin,
|
removePlugin,
|
||||||
|
removePredefinedConfigs,
|
||||||
} from '../utils/flat-config/ast-utils';
|
} from '../utils/flat-config/ast-utils';
|
||||||
import { hasEslintPlugin } from '../utils/plugin';
|
import { hasEslintPlugin } from '../utils/plugin';
|
||||||
import { ESLINT_CONFIG_FILENAMES } from '../../utils/config-file';
|
import { ESLINT_CONFIG_FILENAMES } from '../../utils/config-file';
|
||||||
@ -59,7 +60,7 @@ export function migrateConfigToMonorepoStyle(
|
|||||||
tree.exists('eslint.config.js')
|
tree.exists('eslint.config.js')
|
||||||
? 'eslint.base.config.js'
|
? 'eslint.base.config.js'
|
||||||
: 'eslint.config.js',
|
: 'eslint.config.js',
|
||||||
getGlobalFlatEslintConfiguration(unitTestRunner)
|
getGlobalFlatEslintConfiguration()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const eslintFile = findEslintFile(tree, '.');
|
const eslintFile = findEslintFile(tree, '.');
|
||||||
@ -152,6 +153,11 @@ function migrateEslintFile(projectEslintPath: string, tree: Tree) {
|
|||||||
'plugin:@nrwl/typescript',
|
'plugin:@nrwl/typescript',
|
||||||
'plugin:@nrwl/javascript',
|
'plugin:@nrwl/javascript',
|
||||||
]);
|
]);
|
||||||
|
config = removePredefinedConfigs(config, '@nx/eslint-plugin', 'nx', [
|
||||||
|
'flat/base',
|
||||||
|
'flat/typescript',
|
||||||
|
'flat/javascript',
|
||||||
|
]);
|
||||||
tree.write(projectEslintPath, config);
|
tree.write(projectEslintPath, config);
|
||||||
} else {
|
} else {
|
||||||
updateJson(tree, projectEslintPath, (json) => {
|
updateJson(tree, projectEslintPath, (json) => {
|
||||||
|
|||||||
@ -42,7 +42,53 @@ describe('@nx/eslint:lint-project', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate a eslint config and configure the target in project configuration', async () => {
|
it('should generate a flat eslint base config', 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,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tree.read('eslint.config.js', 'utf-8')).toMatchInlineSnapshot(`
|
||||||
|
"const nx = require('@nx/eslint-plugin');
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
...nx.configs['flat/base'],
|
||||||
|
...nx.configs['flat/typescript'],
|
||||||
|
...nx.configs['flat/javascript'],
|
||||||
|
{
|
||||||
|
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'],
|
||||||
|
// Override or add rules here
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
process.env.ESLINT_USE_FLAT_CONFIG = originalEslintUseFlatConfigVal;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a eslint config (legacy)', async () => {
|
||||||
await lintProjectGenerator(tree, {
|
await lintProjectGenerator(tree, {
|
||||||
...defaultOptions,
|
...defaultOptions,
|
||||||
linter: Linter.EsLint,
|
linter: Linter.EsLint,
|
||||||
@ -121,7 +167,12 @@ describe('@nx/eslint:lint-project', () => {
|
|||||||
"files": ["*.json"],
|
"files": ["*.json"],
|
||||||
"parser": "jsonc-eslint-parser",
|
"parser": "jsonc-eslint-parser",
|
||||||
"rules": {
|
"rules": {
|
||||||
"@nx/dependency-checks": "error"
|
"@nx/dependency-checks": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"ignoredFiles": ["{projectRoot}/eslint.config.{js,cjs,mjs}"]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -197,9 +197,16 @@ function createEsLintConfiguration(
|
|||||||
const pathToRootConfig = extendedRootConfig
|
const pathToRootConfig = extendedRootConfig
|
||||||
? `${offsetFromRoot(projectConfig.root)}${extendedRootConfig}`
|
? `${offsetFromRoot(projectConfig.root)}${extendedRootConfig}`
|
||||||
: undefined;
|
: undefined;
|
||||||
const addDependencyChecks = isBuildableLibraryProject(projectConfig);
|
const addDependencyChecks =
|
||||||
|
options.addPackageJsonDependencyChecks ||
|
||||||
|
isBuildableLibraryProject(projectConfig);
|
||||||
|
|
||||||
const overrides: Linter.ConfigOverride<Linter.RulesRecord>[] = [
|
const overrides: Linter.ConfigOverride<Linter.RulesRecord>[] = useFlatConfig(
|
||||||
|
tree
|
||||||
|
)
|
||||||
|
? // For flat configs, we don't need to generate different overrides for each file. Users should add their own overrides as needed.
|
||||||
|
[]
|
||||||
|
: [
|
||||||
{
|
{
|
||||||
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
||||||
/**
|
/**
|
||||||
@ -236,21 +243,23 @@ function createEsLintConfiguration(
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (
|
if (addDependencyChecks) {
|
||||||
options.addPackageJsonDependencyChecks ||
|
|
||||||
isBuildableLibraryProject(projectConfig)
|
|
||||||
) {
|
|
||||||
overrides.push({
|
overrides.push({
|
||||||
files: ['*.json'],
|
files: ['*.json'],
|
||||||
parser: 'jsonc-eslint-parser',
|
parser: 'jsonc-eslint-parser',
|
||||||
rules: {
|
rules: {
|
||||||
'@nx/dependency-checks': 'error',
|
'@nx/dependency-checks': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
// With flat configs, we don't want to include imports in the eslint js/cjs/mjs files to be checked
|
||||||
|
ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs}'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useFlatConfig(tree)) {
|
if (useFlatConfig(tree)) {
|
||||||
const isCompatNeeded = addDependencyChecks;
|
|
||||||
const nodes = [];
|
const nodes = [];
|
||||||
const importMap = new Map();
|
const importMap = new Map();
|
||||||
if (extendedRootConfig) {
|
if (extendedRootConfig) {
|
||||||
@ -260,7 +269,7 @@ function createEsLintConfiguration(
|
|||||||
overrides.forEach((override) => {
|
overrides.forEach((override) => {
|
||||||
nodes.push(generateFlatOverride(override));
|
nodes.push(generateFlatOverride(override));
|
||||||
});
|
});
|
||||||
const nodeList = createNodeList(importMap, nodes, isCompatNeeded);
|
const nodeList = createNodeList(importMap, nodes);
|
||||||
const content = stringifyNodeList(nodeList);
|
const content = stringifyNodeList(nodeList);
|
||||||
tree.write(join(projectConfig.root, 'eslint.config.js'), content);
|
tree.write(join(projectConfig.root, 'eslint.config.js'), content);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -4,12 +4,18 @@ import {
|
|||||||
type GeneratorCallback,
|
type GeneratorCallback,
|
||||||
type Tree,
|
type Tree,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
|
import { useFlatConfig } from '../../utils/flat-config';
|
||||||
import {
|
import {
|
||||||
|
eslint9__eslintVersion,
|
||||||
|
eslint9__typescriptESLintVersion,
|
||||||
eslintConfigPrettierVersion,
|
eslintConfigPrettierVersion,
|
||||||
nxVersion,
|
nxVersion,
|
||||||
typescriptESLintVersion,
|
typescriptESLintVersion,
|
||||||
} from '../../utils/versions';
|
} from '../../utils/versions';
|
||||||
import { getGlobalEsLintConfiguration } from '../init/global-eslint-config';
|
import {
|
||||||
|
getGlobalEsLintConfiguration,
|
||||||
|
getGlobalFlatEslintConfiguration,
|
||||||
|
} from '../init/global-eslint-config';
|
||||||
import { findEslintFile } from '../utils/eslint-file';
|
import { findEslintFile } from '../utils/eslint-file';
|
||||||
|
|
||||||
export type SetupRootEsLintOptions = {
|
export type SetupRootEsLintOptions = {
|
||||||
@ -26,7 +32,13 @@ export function setupRootEsLint(
|
|||||||
if (rootEslintFile) {
|
if (rootEslintFile) {
|
||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
|
if (!useFlatConfig(tree)) {
|
||||||
|
return setUpLegacyRootEslintRc(tree, options);
|
||||||
|
}
|
||||||
|
return setUpRootFlatConfig(tree, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setUpLegacyRootEslintRc(tree: Tree, options: SetupRootEsLintOptions) {
|
||||||
writeJson(
|
writeJson(
|
||||||
tree,
|
tree,
|
||||||
'.eslintrc.json',
|
'.eslintrc.json',
|
||||||
@ -56,3 +68,24 @@ export function setupRootEsLint(
|
|||||||
)
|
)
|
||||||
: () => {};
|
: () => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setUpRootFlatConfig(tree: Tree, options: SetupRootEsLintOptions) {
|
||||||
|
tree.write(
|
||||||
|
'eslint.config.js',
|
||||||
|
getGlobalFlatEslintConfiguration(options.rootProject)
|
||||||
|
);
|
||||||
|
|
||||||
|
return !options.skipPackageJson
|
||||||
|
? addDependenciesToPackageJson(
|
||||||
|
tree,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
'@eslint/js': eslint9__eslintVersion,
|
||||||
|
'@nx/eslint-plugin': nxVersion,
|
||||||
|
eslint: eslint9__eslintVersion,
|
||||||
|
'eslint-config-prettier': eslintConfigPrettierVersion,
|
||||||
|
'typescript-eslint': eslint9__typescriptESLintVersion,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: () => {};
|
||||||
|
}
|
||||||
|
|||||||
@ -1,3 +1,10 @@
|
|||||||
|
import { readJson, type Tree } from '@nx/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import * as devkitInternals from 'nx/src/devkit-internals';
|
||||||
|
import {
|
||||||
|
ESLINT_CONFIG_FILENAMES,
|
||||||
|
baseEsLintConfigFile,
|
||||||
|
} from '../../utils/config-file';
|
||||||
import {
|
import {
|
||||||
addExtendsToLintConfig,
|
addExtendsToLintConfig,
|
||||||
findEslintFile,
|
findEslintFile,
|
||||||
@ -5,13 +12,6 @@ import {
|
|||||||
replaceOverridesInLintConfig,
|
replaceOverridesInLintConfig,
|
||||||
} from './eslint-file';
|
} from './eslint-file';
|
||||||
|
|
||||||
import { Tree, readJson } from '@nx/devkit';
|
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
|
||||||
import {
|
|
||||||
ESLINT_CONFIG_FILENAMES,
|
|
||||||
baseEsLintConfigFile,
|
|
||||||
} from '../../utils/config-file';
|
|
||||||
|
|
||||||
describe('@nx/eslint:lint-file', () => {
|
describe('@nx/eslint:lint-file', () => {
|
||||||
let tree: Tree;
|
let tree: Tree;
|
||||||
|
|
||||||
@ -120,6 +120,236 @@ describe('@nx/eslint:lint-file', () => {
|
|||||||
'../../.eslintrc',
|
'../../.eslintrc',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add extends to flat config', () => {
|
||||||
|
tree.write('eslint.config.js', 'module.exports = {};');
|
||||||
|
tree.write(
|
||||||
|
'apps/demo/eslint.config.js',
|
||||||
|
`const baseConfig = require("../../eslint.config.js");
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
...baseConfig,
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
"**/*.js",
|
||||||
|
"**/*.jsx"
|
||||||
|
],
|
||||||
|
rules: {}
|
||||||
|
},
|
||||||
|
];`
|
||||||
|
);
|
||||||
|
|
||||||
|
addExtendsToLintConfig(tree, 'apps/demo', 'plugin:playwright/recommend');
|
||||||
|
|
||||||
|
expect(tree.read('apps/demo/eslint.config.js', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||||
|
const js = require("@eslint/js");
|
||||||
|
const baseConfig = require("../../eslint.config.js");
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended,
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
...compat.extends("plugin:playwright/recommend"),
|
||||||
|
...baseConfig,
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
"**/*.js",
|
||||||
|
"**/*.jsx"
|
||||||
|
],
|
||||||
|
rules: {}
|
||||||
|
},
|
||||||
|
];"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add wrapped plugin for compat in extends when using eslint v9', () => {
|
||||||
|
// mock eslint version
|
||||||
|
jest.spyOn(devkitInternals, 'readModulePackageJson').mockReturnValue({
|
||||||
|
packageJson: { name: 'eslint', version: '9.0.0' },
|
||||||
|
path: '',
|
||||||
|
});
|
||||||
|
tree.write('eslint.config.js', 'module.exports = {};');
|
||||||
|
tree.write(
|
||||||
|
'apps/demo/eslint.config.js',
|
||||||
|
`const baseConfig = require("../../eslint.config.js");
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
...baseConfig,
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
"**/*.js",
|
||||||
|
"**/*.jsx"
|
||||||
|
],
|
||||||
|
rules: {}
|
||||||
|
},
|
||||||
|
];`
|
||||||
|
);
|
||||||
|
|
||||||
|
addExtendsToLintConfig(tree, 'apps/demo', {
|
||||||
|
name: 'plugin:playwright/recommend',
|
||||||
|
needCompatFixup: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tree.read('apps/demo/eslint.config.js', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||||
|
const js = require("@eslint/js");
|
||||||
|
const { fixupConfigRules } = require("@eslint/compat");
|
||||||
|
const baseConfig = require("../../eslint.config.js");
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended,
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
...fixupConfigRules(compat.extends("plugin:playwright/recommend")),
|
||||||
|
...baseConfig,
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
"**/*.js",
|
||||||
|
"**/*.jsx"
|
||||||
|
],
|
||||||
|
rules: {}
|
||||||
|
},
|
||||||
|
];"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle mixed multiple incompatible and compatible plugins and add them to extends in the specified order when using eslint v9', () => {
|
||||||
|
// mock eslint version
|
||||||
|
jest.spyOn(devkitInternals, 'readModulePackageJson').mockReturnValue({
|
||||||
|
packageJson: { name: 'eslint', version: '9.0.0' },
|
||||||
|
path: '',
|
||||||
|
});
|
||||||
|
tree.write('eslint.config.js', 'module.exports = {};');
|
||||||
|
tree.write(
|
||||||
|
'apps/demo/eslint.config.js',
|
||||||
|
`const baseConfig = require("../../eslint.config.js");
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
...baseConfig,
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
"**/*.js",
|
||||||
|
"**/*.jsx"
|
||||||
|
],
|
||||||
|
rules: {}
|
||||||
|
},
|
||||||
|
];`
|
||||||
|
);
|
||||||
|
|
||||||
|
addExtendsToLintConfig(tree, 'apps/demo', [
|
||||||
|
'plugin:some-plugin1',
|
||||||
|
'plugin:some-plugin2',
|
||||||
|
{ name: 'incompatible-plugin1', needCompatFixup: true },
|
||||||
|
{ name: 'incompatible-plugin2', needCompatFixup: true },
|
||||||
|
'plugin:some-plugin3',
|
||||||
|
{ name: 'incompatible-plugin3', needCompatFixup: true },
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(tree.read('apps/demo/eslint.config.js', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||||
|
const js = require("@eslint/js");
|
||||||
|
const { fixupConfigRules } = require("@eslint/compat");
|
||||||
|
const baseConfig = require("../../eslint.config.js");
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended,
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
...compat.extends("plugin:some-plugin1", "plugin:some-plugin2"),
|
||||||
|
...fixupConfigRules(compat.extends("incompatible-plugin1")),
|
||||||
|
...fixupConfigRules(compat.extends("incompatible-plugin2")),
|
||||||
|
...compat.extends("plugin:some-plugin3"),
|
||||||
|
...fixupConfigRules(compat.extends("incompatible-plugin3")),
|
||||||
|
...baseConfig,
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
"**/*.js",
|
||||||
|
"**/*.jsx"
|
||||||
|
],
|
||||||
|
rules: {}
|
||||||
|
},
|
||||||
|
];"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not add wrapped plugin for compat in extends when not using eslint v9', () => {
|
||||||
|
// mock eslint version
|
||||||
|
jest.spyOn(devkitInternals, 'readModulePackageJson').mockReturnValue({
|
||||||
|
packageJson: { name: 'eslint', version: '8.0.0' },
|
||||||
|
path: '',
|
||||||
|
});
|
||||||
|
tree.write('eslint.config.js', 'module.exports = {};');
|
||||||
|
tree.write(
|
||||||
|
'apps/demo/eslint.config.js',
|
||||||
|
`const baseConfig = require("../../eslint.config.js");
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
...baseConfig,
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
"**/*.js",
|
||||||
|
"**/*.jsx"
|
||||||
|
],
|
||||||
|
rules: {}
|
||||||
|
},
|
||||||
|
];`
|
||||||
|
);
|
||||||
|
|
||||||
|
addExtendsToLintConfig(tree, 'apps/demo', {
|
||||||
|
name: 'plugin:playwright/recommend',
|
||||||
|
needCompatFixup: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tree.read('apps/demo/eslint.config.js', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||||
|
const js = require("@eslint/js");
|
||||||
|
const baseConfig = require("../../eslint.config.js");
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended,
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
...compat.extends("plugin:playwright/recommend"),
|
||||||
|
...baseConfig,
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
"**/*.js",
|
||||||
|
"**/*.jsx"
|
||||||
|
],
|
||||||
|
rules: {}
|
||||||
|
},
|
||||||
|
];"
|
||||||
|
`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('replaceOverridesInLintConfig', () => {
|
describe('replaceOverridesInLintConfig', () => {
|
||||||
@ -201,7 +431,6 @@ module.exports = [
|
|||||||
recommendedConfig: js.configs.recommended,
|
recommendedConfig: js.configs.recommended,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
...baseConfig,
|
...baseConfig,
|
||||||
...compat.config({ extends: [
|
...compat.config({ extends: [
|
||||||
|
|||||||
@ -1,35 +1,43 @@
|
|||||||
import {
|
import {
|
||||||
|
addDependenciesToPackageJson,
|
||||||
|
type GeneratorCallback,
|
||||||
joinPathFragments,
|
joinPathFragments,
|
||||||
names,
|
names,
|
||||||
offsetFromRoot,
|
offsetFromRoot,
|
||||||
readJson,
|
readJson,
|
||||||
|
type Tree,
|
||||||
updateJson,
|
updateJson,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import type { Tree } from '@nx/devkit';
|
|
||||||
import type { Linter } from 'eslint';
|
import type { Linter } from 'eslint';
|
||||||
import {
|
import { gte } from 'semver';
|
||||||
flatConfigEslintFilename,
|
|
||||||
useFlatConfig,
|
|
||||||
} from '../../utils/flat-config';
|
|
||||||
import {
|
|
||||||
addBlockToFlatConfigExport,
|
|
||||||
addCompatToFlatConfig,
|
|
||||||
addImportToFlatConfig,
|
|
||||||
addPluginsToExportsBlock,
|
|
||||||
generateAst,
|
|
||||||
generateFlatOverride,
|
|
||||||
generatePluginExtendsElement,
|
|
||||||
hasOverride,
|
|
||||||
removeOverridesFromLintConfig,
|
|
||||||
replaceOverride,
|
|
||||||
} from './flat-config/ast-utils';
|
|
||||||
import ts = require('typescript');
|
|
||||||
import { mapFilePath } from './flat-config/path-utils';
|
|
||||||
import {
|
import {
|
||||||
baseEsLintConfigFile,
|
baseEsLintConfigFile,
|
||||||
baseEsLintFlatConfigFile,
|
baseEsLintFlatConfigFile,
|
||||||
ESLINT_CONFIG_FILENAMES,
|
ESLINT_CONFIG_FILENAMES,
|
||||||
} from '../../utils/config-file';
|
} from '../../utils/config-file';
|
||||||
|
import {
|
||||||
|
getRootESLintFlatConfigFilename,
|
||||||
|
useFlatConfig,
|
||||||
|
} from '../../utils/flat-config';
|
||||||
|
import { getInstalledEslintVersion } from '../../utils/version-utils';
|
||||||
|
import { eslint9__eslintVersion, eslintCompat } from '../../utils/versions';
|
||||||
|
import {
|
||||||
|
addBlockToFlatConfigExport,
|
||||||
|
addFlatCompatToFlatConfig,
|
||||||
|
addImportToFlatConfig,
|
||||||
|
addPluginsToExportsBlock,
|
||||||
|
generateAst,
|
||||||
|
generateFlatOverride,
|
||||||
|
generateFlatPredefinedConfig,
|
||||||
|
generatePluginExtendsElement,
|
||||||
|
generatePluginExtendsElementWithCompatFixup,
|
||||||
|
hasOverride,
|
||||||
|
overrideNeedsCompat,
|
||||||
|
removeOverridesFromLintConfig,
|
||||||
|
replaceOverride,
|
||||||
|
} from './flat-config/ast-utils';
|
||||||
|
import { mapFilePath } from './flat-config/path-utils';
|
||||||
|
import ts = require('typescript');
|
||||||
|
|
||||||
export function findEslintFile(
|
export function findEslintFile(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
@ -167,7 +175,7 @@ function offsetFilePath(
|
|||||||
export function addOverrideToLintConfig(
|
export function addOverrideToLintConfig(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
root: string,
|
root: string,
|
||||||
override: Linter.ConfigOverride<Linter.RulesRecord>,
|
override: Partial<Linter.ConfigOverride<Linter.RulesRecord>>,
|
||||||
options: { insertAtTheEnd?: boolean; checkBaseConfig?: boolean } = {
|
options: { insertAtTheEnd?: boolean; checkBaseConfig?: boolean } = {
|
||||||
insertAtTheEnd: true,
|
insertAtTheEnd: true,
|
||||||
}
|
}
|
||||||
@ -177,13 +185,13 @@ export function addOverrideToLintConfig(
|
|||||||
if (useFlatConfig(tree)) {
|
if (useFlatConfig(tree)) {
|
||||||
const fileName = joinPathFragments(
|
const fileName = joinPathFragments(
|
||||||
root,
|
root,
|
||||||
isBase ? baseEsLintFlatConfigFile : flatConfigEslintFilename(tree)
|
isBase ? baseEsLintFlatConfigFile : getRootESLintFlatConfigFilename(tree)
|
||||||
);
|
);
|
||||||
const flatOverride = generateFlatOverride(override);
|
const flatOverride = generateFlatOverride(override);
|
||||||
let content = tree.read(fileName, 'utf8');
|
let content = tree.read(fileName, 'utf8');
|
||||||
// we will be using compat here so we need to make sure it's added
|
// Check if the provided override using legacy eslintrc properties or plugins, if so we need to add compat
|
||||||
if (overrideNeedsCompat(override)) {
|
if (overrideNeedsCompat(override)) {
|
||||||
content = addCompatToFlatConfig(content);
|
content = addFlatCompatToFlatConfig(content);
|
||||||
}
|
}
|
||||||
tree.write(
|
tree.write(
|
||||||
fileName,
|
fileName,
|
||||||
@ -206,14 +214,6 @@ export function addOverrideToLintConfig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function overrideNeedsCompat(
|
|
||||||
override: Linter.ConfigOverride<Linter.RulesRecord>
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
override.env || override.extends || override.plugins || override.parser
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateOverrideInLintConfig(
|
export function updateOverrideInLintConfig(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
root: string,
|
root: string,
|
||||||
@ -223,7 +223,10 @@ export function updateOverrideInLintConfig(
|
|||||||
) => Linter.ConfigOverride<Linter.RulesRecord>
|
) => Linter.ConfigOverride<Linter.RulesRecord>
|
||||||
) {
|
) {
|
||||||
if (useFlatConfig(tree)) {
|
if (useFlatConfig(tree)) {
|
||||||
const fileName = joinPathFragments(root, flatConfigEslintFilename(tree));
|
const fileName = joinPathFragments(
|
||||||
|
root,
|
||||||
|
getRootESLintFlatConfigFilename(tree)
|
||||||
|
);
|
||||||
let content = tree.read(fileName, 'utf8');
|
let content = tree.read(fileName, 'utf8');
|
||||||
content = replaceOverride(content, root, lookup, update);
|
content = replaceOverride(content, root, lookup, update);
|
||||||
tree.write(fileName, content);
|
tree.write(fileName, content);
|
||||||
@ -265,7 +268,7 @@ export function lintConfigHasOverride(
|
|||||||
if (useFlatConfig(tree)) {
|
if (useFlatConfig(tree)) {
|
||||||
const fileName = joinPathFragments(
|
const fileName = joinPathFragments(
|
||||||
root,
|
root,
|
||||||
isBase ? baseEsLintFlatConfigFile : flatConfigEslintFilename(tree)
|
isBase ? baseEsLintFlatConfigFile : getRootESLintFlatConfigFilename(tree)
|
||||||
);
|
);
|
||||||
const content = tree.read(fileName, 'utf8');
|
const content = tree.read(fileName, 'utf8');
|
||||||
return hasOverride(content, lookup);
|
return hasOverride(content, lookup);
|
||||||
@ -285,11 +288,14 @@ export function replaceOverridesInLintConfig(
|
|||||||
overrides: Linter.ConfigOverride<Linter.RulesRecord>[]
|
overrides: Linter.ConfigOverride<Linter.RulesRecord>[]
|
||||||
) {
|
) {
|
||||||
if (useFlatConfig(tree)) {
|
if (useFlatConfig(tree)) {
|
||||||
const fileName = joinPathFragments(root, flatConfigEslintFilename(tree));
|
const fileName = joinPathFragments(
|
||||||
|
root,
|
||||||
|
getRootESLintFlatConfigFilename(tree)
|
||||||
|
);
|
||||||
let content = tree.read(fileName, 'utf8');
|
let content = tree.read(fileName, 'utf8');
|
||||||
// we will be using compat here so we need to make sure it's added
|
// Check if any of the provided overrides using legacy eslintrc properties or plugins, if so we need to add compat
|
||||||
if (overrides.some(overrideNeedsCompat)) {
|
if (overrides.some(overrideNeedsCompat)) {
|
||||||
content = addCompatToFlatConfig(content);
|
content = addFlatCompatToFlatConfig(content);
|
||||||
}
|
}
|
||||||
content = removeOverridesFromLintConfig(content);
|
content = removeOverridesFromLintConfig(content);
|
||||||
overrides.forEach((override) => {
|
overrides.forEach((override) => {
|
||||||
@ -310,21 +316,92 @@ export function replaceOverridesInLintConfig(
|
|||||||
export function addExtendsToLintConfig(
|
export function addExtendsToLintConfig(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
root: string,
|
root: string,
|
||||||
plugin: string | string[]
|
plugin:
|
||||||
) {
|
| string
|
||||||
const plugins = Array.isArray(plugin) ? plugin : [plugin];
|
| { name: string; needCompatFixup: boolean }
|
||||||
|
| Array<string | { name: string; needCompatFixup: boolean }>,
|
||||||
|
insertAtTheEnd = false
|
||||||
|
): GeneratorCallback {
|
||||||
if (useFlatConfig(tree)) {
|
if (useFlatConfig(tree)) {
|
||||||
const fileName = joinPathFragments(root, flatConfigEslintFilename(tree));
|
const pluginExtends: ts.SpreadElement[] = [];
|
||||||
const pluginExtends = generatePluginExtendsElement(plugins);
|
const fileName = joinPathFragments(
|
||||||
let content = tree.read(fileName, 'utf8');
|
root,
|
||||||
content = addCompatToFlatConfig(content);
|
getRootESLintFlatConfigFilename(tree)
|
||||||
tree.write(
|
|
||||||
fileName,
|
|
||||||
addBlockToFlatConfigExport(content, pluginExtends, {
|
|
||||||
insertAtTheEnd: false,
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
let shouldImportEslintCompat = false;
|
||||||
|
// assume eslint version is 9 if not found, as it's what we'd be generating by default
|
||||||
|
const eslintVersion =
|
||||||
|
getInstalledEslintVersion(tree) ?? eslint9__eslintVersion;
|
||||||
|
if (gte(eslintVersion, '9.0.0')) {
|
||||||
|
// eslint v9 requires the incompatible plugins to be wrapped with a helper from @eslint/compat
|
||||||
|
const plugins = (Array.isArray(plugin) ? plugin : [plugin]).map((p) =>
|
||||||
|
typeof p === 'string' ? { name: p, needCompatFixup: false } : p
|
||||||
|
);
|
||||||
|
let compatiblePluginsBatch: string[] = [];
|
||||||
|
plugins.forEach(({ name, needCompatFixup }) => {
|
||||||
|
if (needCompatFixup) {
|
||||||
|
if (compatiblePluginsBatch.length > 0) {
|
||||||
|
// flush the current batch of compatible plugins and reset it
|
||||||
|
pluginExtends.push(
|
||||||
|
generatePluginExtendsElement(compatiblePluginsBatch)
|
||||||
|
);
|
||||||
|
compatiblePluginsBatch = [];
|
||||||
|
}
|
||||||
|
// generate the extends for the incompatible plugin
|
||||||
|
pluginExtends.push(generatePluginExtendsElementWithCompatFixup(name));
|
||||||
|
shouldImportEslintCompat = true;
|
||||||
} else {
|
} else {
|
||||||
|
// add the compatible plugin to the current batch
|
||||||
|
compatiblePluginsBatch.push(name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (compatiblePluginsBatch.length > 0) {
|
||||||
|
// flush the batch of compatible plugins
|
||||||
|
pluginExtends.push(
|
||||||
|
generatePluginExtendsElement(compatiblePluginsBatch)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const plugins = (Array.isArray(plugin) ? plugin : [plugin]).map((p) =>
|
||||||
|
typeof p === 'string' ? p : p.name
|
||||||
|
);
|
||||||
|
pluginExtends.push(generatePluginExtendsElement(plugins));
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = tree.read(fileName, 'utf8');
|
||||||
|
if (shouldImportEslintCompat) {
|
||||||
|
content = addImportToFlatConfig(
|
||||||
|
content,
|
||||||
|
['fixupConfigRules'],
|
||||||
|
'@eslint/compat'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
content = addFlatCompatToFlatConfig(content);
|
||||||
|
// reverse the order to ensure they are added in the correct order at the
|
||||||
|
// start of the `extends` array
|
||||||
|
for (const pluginExtend of pluginExtends.reverse()) {
|
||||||
|
content = addBlockToFlatConfigExport(content, pluginExtend, {
|
||||||
|
insertAtTheEnd,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tree.write(fileName, content);
|
||||||
|
|
||||||
|
if (shouldImportEslintCompat) {
|
||||||
|
return addDependenciesToPackageJson(
|
||||||
|
tree,
|
||||||
|
{},
|
||||||
|
{ '@eslint/compat': eslintCompat },
|
||||||
|
undefined,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {};
|
||||||
|
} else {
|
||||||
|
const plugins = (Array.isArray(plugin) ? plugin : [plugin]).map((p) =>
|
||||||
|
typeof p === 'string' ? p : p.name
|
||||||
|
);
|
||||||
const fileName = joinPathFragments(root, '.eslintrc.json');
|
const fileName = joinPathFragments(root, '.eslintrc.json');
|
||||||
updateJson(tree, fileName, (json) => {
|
updateJson(tree, fileName, (json) => {
|
||||||
json.extends ??= [];
|
json.extends ??= [];
|
||||||
@ -334,9 +411,39 @@ export function addExtendsToLintConfig(
|
|||||||
];
|
];
|
||||||
return json;
|
return json;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return () => {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function addPredefinedConfigToFlatLintConfig(
|
||||||
|
tree: Tree,
|
||||||
|
root: string,
|
||||||
|
predefinedConfigName: string,
|
||||||
|
moduleName = 'nx',
|
||||||
|
moduleImportPath = '@nx/eslint-plugin',
|
||||||
|
spread = true,
|
||||||
|
insertAtTheEnd = true
|
||||||
|
): void {
|
||||||
|
if (!useFlatConfig(tree))
|
||||||
|
throw new Error('Predefined configs can only be used with flat configs');
|
||||||
|
|
||||||
|
const fileName = joinPathFragments(
|
||||||
|
root,
|
||||||
|
getRootESLintFlatConfigFilename(tree)
|
||||||
|
);
|
||||||
|
|
||||||
|
let content = tree.read(fileName, 'utf8');
|
||||||
|
content = addImportToFlatConfig(content, moduleName, moduleImportPath);
|
||||||
|
content = addBlockToFlatConfigExport(
|
||||||
|
content,
|
||||||
|
generateFlatPredefinedConfig(predefinedConfigName, moduleName, spread),
|
||||||
|
{ insertAtTheEnd }
|
||||||
|
);
|
||||||
|
|
||||||
|
tree.write(fileName, content);
|
||||||
|
}
|
||||||
|
|
||||||
export function addPluginsToLintConfig(
|
export function addPluginsToLintConfig(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
root: string,
|
root: string,
|
||||||
@ -344,7 +451,10 @@ export function addPluginsToLintConfig(
|
|||||||
) {
|
) {
|
||||||
const plugins = Array.isArray(plugin) ? plugin : [plugin];
|
const plugins = Array.isArray(plugin) ? plugin : [plugin];
|
||||||
if (useFlatConfig(tree)) {
|
if (useFlatConfig(tree)) {
|
||||||
const fileName = joinPathFragments(root, flatConfigEslintFilename(tree));
|
const fileName = joinPathFragments(
|
||||||
|
root,
|
||||||
|
getRootESLintFlatConfigFilename(tree)
|
||||||
|
);
|
||||||
let content = tree.read(fileName, 'utf8');
|
let content = tree.read(fileName, 'utf8');
|
||||||
const mappedPlugins: { name: string; varName: string; imp: string }[] = [];
|
const mappedPlugins: { name: string; varName: string; imp: string }[] = [];
|
||||||
plugins.forEach((name) => {
|
plugins.forEach((name) => {
|
||||||
@ -372,7 +482,10 @@ export function addIgnoresToLintConfig(
|
|||||||
ignorePatterns: string[]
|
ignorePatterns: string[]
|
||||||
) {
|
) {
|
||||||
if (useFlatConfig(tree)) {
|
if (useFlatConfig(tree)) {
|
||||||
const fileName = joinPathFragments(root, flatConfigEslintFilename(tree));
|
const fileName = joinPathFragments(
|
||||||
|
root,
|
||||||
|
getRootESLintFlatConfigFilename(tree)
|
||||||
|
);
|
||||||
const block = generateAst<ts.ObjectLiteralExpression>({
|
const block = generateAst<ts.ObjectLiteralExpression>({
|
||||||
ignores: ignorePatterns.map((path) => mapFilePath(path)),
|
ignores: ignorePatterns.map((path) => mapFilePath(path)),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,16 +1,153 @@
|
|||||||
import ts = require('typescript');
|
import ts = require('typescript');
|
||||||
import {
|
import {
|
||||||
addBlockToFlatConfigExport,
|
addBlockToFlatConfigExport,
|
||||||
generateAst,
|
addFlatCompatToFlatConfig,
|
||||||
addImportToFlatConfig,
|
addImportToFlatConfig,
|
||||||
addCompatToFlatConfig,
|
generateAst,
|
||||||
removeOverridesFromLintConfig,
|
generateFlatOverride,
|
||||||
replaceOverride,
|
generatePluginExtendsElementWithCompatFixup,
|
||||||
removePlugin,
|
|
||||||
removeCompatExtends,
|
removeCompatExtends,
|
||||||
|
removeImportFromFlatConfig,
|
||||||
|
removeOverridesFromLintConfig,
|
||||||
|
removePlugin,
|
||||||
|
removePredefinedConfigs,
|
||||||
|
replaceOverride,
|
||||||
} from './ast-utils';
|
} from './ast-utils';
|
||||||
|
import { stripIndents } from '@nx/devkit';
|
||||||
|
|
||||||
describe('ast-utils', () => {
|
describe('ast-utils', () => {
|
||||||
|
const printer = ts.createPrinter();
|
||||||
|
|
||||||
|
function printTsNode(node: ts.Node) {
|
||||||
|
return printer.printNode(
|
||||||
|
ts.EmitHint.Unspecified,
|
||||||
|
node,
|
||||||
|
ts.createSourceFile('test.ts', '', ts.ScriptTarget.Latest)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('generateFlatOverride', () => {
|
||||||
|
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);
|
||||||
|
return printTsNode(ast);
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getOutput({})).toMatchInlineSnapshot(`"{}"`);
|
||||||
|
|
||||||
|
// It should apply rules directly
|
||||||
|
expect(
|
||||||
|
getOutput({
|
||||||
|
rules: {
|
||||||
|
a: 'error',
|
||||||
|
b: 'off',
|
||||||
|
c: [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
some: {
|
||||||
|
rich: ['config', 'options'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"{
|
||||||
|
rules: {
|
||||||
|
a: "error",
|
||||||
|
b: "off",
|
||||||
|
c: [
|
||||||
|
"error",
|
||||||
|
{ some: { rich: [
|
||||||
|
"config",
|
||||||
|
"options"
|
||||||
|
] } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`);
|
||||||
|
|
||||||
|
// It should normalize and apply files as an array
|
||||||
|
expect(
|
||||||
|
getOutput({
|
||||||
|
files: '*.ts', // old single * syntax should be replaced by **/*
|
||||||
|
})
|
||||||
|
).toMatchInlineSnapshot(`"{ files: ["**/*.ts"] }"`);
|
||||||
|
|
||||||
|
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
|
||||||
|
parser: 'jsonc-eslint-parser',
|
||||||
|
})
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"{
|
||||||
|
languageOptions: { parser: require("jsonc-eslint-parser") }
|
||||||
|
}"
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
getOutput({
|
||||||
|
// It should nest parserOptions in languageOptions
|
||||||
|
parserOptions: {
|
||||||
|
foo: 'bar',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"{
|
||||||
|
languageOptions: { parserOptions: { foo: "bar" } }
|
||||||
|
}"
|
||||||
|
`);
|
||||||
|
|
||||||
|
// It should add the compat tooling for extends, and spread the rules object to allow for easier editing by users
|
||||||
|
expect(getOutput({ extends: ['plugin:@nx/typescript'] }))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"...compat.config({ extends: ["plugin:@nx/typescript"] }).map(config => ({
|
||||||
|
...config,
|
||||||
|
rules: {
|
||||||
|
...config.rules
|
||||||
|
}
|
||||||
|
}))"
|
||||||
|
`);
|
||||||
|
|
||||||
|
// It should add the compat tooling for plugins, and spread the rules object to allow for easier editing by users
|
||||||
|
expect(getOutput({ plugins: ['@nx/eslint-plugin'] }))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"...compat.config({ plugins: ["@nx/eslint-plugin"] }).map(config => ({
|
||||||
|
...config,
|
||||||
|
rules: {
|
||||||
|
...config.rules
|
||||||
|
}
|
||||||
|
}))"
|
||||||
|
`);
|
||||||
|
|
||||||
|
// It should add the compat tooling for env, and spread the rules object to allow for easier editing by users
|
||||||
|
expect(getOutput({ env: { jest: true } })).toMatchInlineSnapshot(`
|
||||||
|
"...compat.config({ env: { jest: true } }).map(config => ({
|
||||||
|
...config,
|
||||||
|
rules: {
|
||||||
|
...config.rules
|
||||||
|
}
|
||||||
|
}))"
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Files for the compat tooling should be added appropriately
|
||||||
|
expect(getOutput({ env: { jest: true }, files: ['*.ts', '*.tsx'] }))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"...compat.config({ env: { jest: true } }).map(config => ({
|
||||||
|
...config,
|
||||||
|
files: [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx"
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
...config.rules
|
||||||
|
}
|
||||||
|
}))"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('addBlockToFlatConfigExport', () => {
|
describe('addBlockToFlatConfigExport', () => {
|
||||||
it('should inject block to the end of the file', () => {
|
it('should inject block to the end of the file', () => {
|
||||||
const content = `const baseConfig = require("../../eslint.config.js");
|
const content = `const baseConfig = require("../../eslint.config.js");
|
||||||
@ -207,6 +344,32 @@ 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 = [
|
||||||
|
playwright.configs['flat/recommended'],
|
||||||
|
];
|
||||||
|
`;
|
||||||
|
const result = removeImportFromFlatConfig(
|
||||||
|
content,
|
||||||
|
'nx',
|
||||||
|
'@nx/eslint-plugin'
|
||||||
|
);
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
const thisShouldRemain = require("@nx/eslint-plugin");
|
||||||
|
const playwright = require('eslint-plugin-playwright');
|
||||||
|
module.exports = [
|
||||||
|
playwright.configs['flat/recommended'],
|
||||||
|
];"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('addCompatToFlatConfig', () => {
|
describe('addCompatToFlatConfig', () => {
|
||||||
it('should add compat to config', () => {
|
it('should add compat to config', () => {
|
||||||
const content = `const baseConfig = require("../../eslint.config.js");
|
const content = `const baseConfig = require("../../eslint.config.js");
|
||||||
@ -221,7 +384,7 @@ describe('ast-utils', () => {
|
|||||||
},
|
},
|
||||||
{ ignores: ["my-lib/.cache/**/*"] },
|
{ ignores: ["my-lib/.cache/**/*"] },
|
||||||
];`;
|
];`;
|
||||||
const result = addCompatToFlatConfig(content);
|
const result = addFlatCompatToFlatConfig(content);
|
||||||
expect(result).toMatchInlineSnapshot(`
|
expect(result).toMatchInlineSnapshot(`
|
||||||
"const { FlatCompat } = require("@eslint/eslintrc");
|
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||||
const js = require("@eslint/js");
|
const js = require("@eslint/js");
|
||||||
@ -231,7 +394,6 @@ describe('ast-utils', () => {
|
|||||||
baseDirectory: __dirname,
|
baseDirectory: __dirname,
|
||||||
recommendedConfig: js.configs.recommended,
|
recommendedConfig: js.configs.recommended,
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
...baseConfig,
|
...baseConfig,
|
||||||
{
|
{
|
||||||
@ -260,7 +422,7 @@ describe('ast-utils', () => {
|
|||||||
},
|
},
|
||||||
{ ignores: ["my-lib/.cache/**/*"] },
|
{ ignores: ["my-lib/.cache/**/*"] },
|
||||||
];`;
|
];`;
|
||||||
const result = addCompatToFlatConfig(content);
|
const result = addFlatCompatToFlatConfig(content);
|
||||||
expect(result).toMatchInlineSnapshot(`
|
expect(result).toMatchInlineSnapshot(`
|
||||||
"const { FlatCompat } = require("@eslint/eslintrc");
|
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||||
const baseConfig = require("../../eslint.config.js");
|
const baseConfig = require("../../eslint.config.js");
|
||||||
@ -270,7 +432,6 @@ describe('ast-utils', () => {
|
|||||||
baseDirectory: __dirname,
|
baseDirectory: __dirname,
|
||||||
recommendedConfig: js.configs.recommended,
|
recommendedConfig: js.configs.recommended,
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
...baseConfig,
|
...baseConfig,
|
||||||
{
|
{
|
||||||
@ -306,7 +467,7 @@ describe('ast-utils', () => {
|
|||||||
},
|
},
|
||||||
{ ignores: ["my-lib/.cache/**/*"] },
|
{ ignores: ["my-lib/.cache/**/*"] },
|
||||||
];`;
|
];`;
|
||||||
const result = addCompatToFlatConfig(content);
|
const result = addFlatCompatToFlatConfig(content);
|
||||||
expect(result).toEqual(content);
|
expect(result).toEqual(content);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -833,4 +994,74 @@ describe('ast-utils', () => {
|
|||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 = [
|
||||||
|
...nx.config['flat/base'],
|
||||||
|
...nx.config['flat/typescript'],
|
||||||
|
...nx.config['flat/javascript'],
|
||||||
|
playwright.configs['flat/recommended'],
|
||||||
|
];
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = removePredefinedConfigs(
|
||||||
|
content,
|
||||||
|
'@nx/eslint-plugin',
|
||||||
|
'nx',
|
||||||
|
['flat/base', 'flat/typescript', 'flat/javascript']
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
const playwright = require('eslint-plugin-playwright');
|
||||||
|
module.exports = [
|
||||||
|
playwright.configs['flat/recommended'],
|
||||||
|
];"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
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 = [
|
||||||
|
...nx.config['flat/base'],
|
||||||
|
...nx.config['flat/typescript'],
|
||||||
|
...nx.config['flat/javascript'],
|
||||||
|
...nx.config['flat/react'],
|
||||||
|
playwright.configs['flat/recommended'],
|
||||||
|
];
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = removePredefinedConfigs(
|
||||||
|
content,
|
||||||
|
'@nx/eslint-plugin',
|
||||||
|
'nx',
|
||||||
|
['flat/base', 'flat/typescript', 'flat/javascript']
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
"const nx = require("@nx/eslint-plugin");
|
||||||
|
const playwright = require('eslint-plugin-playwright');
|
||||||
|
module.exports = [
|
||||||
|
...nx.config['flat/react'],
|
||||||
|
playwright.configs['flat/recommended'],
|
||||||
|
];"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('generatePluginExtendsElementWithCompatFixup', () => {
|
||||||
|
it('should return spread element with fixupConfigRules call wrapping the extended plugin', () => {
|
||||||
|
const result = generatePluginExtendsElementWithCompatFixup('my-plugin');
|
||||||
|
|
||||||
|
expect(printTsNode(result)).toMatchInlineSnapshot(
|
||||||
|
`"...fixupConfigRules(compat.extends("my-plugin"))"`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
ChangeType,
|
|
||||||
StringChange,
|
|
||||||
applyChangesToString,
|
applyChangesToString,
|
||||||
|
ChangeType,
|
||||||
parseJson,
|
parseJson,
|
||||||
|
StringChange,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { Linter } from 'eslint';
|
import { Linter } from 'eslint';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
@ -101,12 +101,7 @@ export function hasOverride(
|
|||||||
// strip any spread elements
|
// strip any spread elements
|
||||||
objSource = fullNodeText.replace(SPREAD_ELEMENTS_REGEXP, '');
|
objSource = fullNodeText.replace(SPREAD_ELEMENTS_REGEXP, '');
|
||||||
}
|
}
|
||||||
const data = parseJson(
|
const data = parseTextToJson(objSource);
|
||||||
objSource
|
|
||||||
// ensure property names have double quotes so that JSON.parse works
|
|
||||||
.replace(/'/g, '"')
|
|
||||||
.replace(/\s([a-zA-Z0-9_]+)\s*:/g, ' "$1": ')
|
|
||||||
);
|
|
||||||
if (lookup(data)) {
|
if (lookup(data)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -121,6 +116,8 @@ function parseTextToJson(text: string): any {
|
|||||||
// ensure property names have double quotes so that JSON.parse works
|
// ensure property names have double quotes so that JSON.parse works
|
||||||
.replace(/'/g, '"')
|
.replace(/'/g, '"')
|
||||||
.replace(/\s([a-zA-Z0-9_]+)\s*:/g, ' "$1": ')
|
.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"')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,8 +129,8 @@ export function replaceOverride(
|
|||||||
root: string,
|
root: string,
|
||||||
lookup: (override: Linter.ConfigOverride<Linter.RulesRecord>) => boolean,
|
lookup: (override: Linter.ConfigOverride<Linter.RulesRecord>) => boolean,
|
||||||
update?: (
|
update?: (
|
||||||
override: Linter.ConfigOverride<Linter.RulesRecord>
|
override: Partial<Linter.ConfigOverride<Linter.RulesRecord>>
|
||||||
) => Linter.ConfigOverride<Linter.RulesRecord>
|
) => Partial<Linter.ConfigOverride<Linter.RulesRecord>>
|
||||||
): string {
|
): string {
|
||||||
const source = ts.createSourceFile(
|
const source = ts.createSourceFile(
|
||||||
'',
|
'',
|
||||||
@ -172,13 +169,18 @@ export function replaceOverride(
|
|||||||
start,
|
start,
|
||||||
length: end - start,
|
length: end - start,
|
||||||
});
|
});
|
||||||
const updatedData = update(data);
|
let updatedData = update(data);
|
||||||
if (updatedData) {
|
if (updatedData) {
|
||||||
mapFilePaths(updatedData);
|
updatedData = mapFilePaths(updatedData);
|
||||||
changes.push({
|
changes.push({
|
||||||
type: ChangeType.Insert,
|
type: ChangeType.Insert,
|
||||||
index: start,
|
index: start,
|
||||||
text: JSON.stringify(updatedData, null, 2).slice(2, -2), // remove curly braces and start/end line breaks since we are injecting just properties
|
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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -313,6 +315,50 @@ export function addImportToFlatConfig(
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an import from flat config
|
||||||
|
*/
|
||||||
|
export function removeImportFromFlatConfig(
|
||||||
|
content: string,
|
||||||
|
variable: string,
|
||||||
|
imp: string
|
||||||
|
): string {
|
||||||
|
const source = ts.createSourceFile(
|
||||||
|
'',
|
||||||
|
content,
|
||||||
|
ts.ScriptTarget.Latest,
|
||||||
|
true,
|
||||||
|
ts.ScriptKind.JS
|
||||||
|
);
|
||||||
|
|
||||||
|
const changes: StringChange[] = [];
|
||||||
|
|
||||||
|
ts.forEachChild(source, (node) => {
|
||||||
|
// we can only combine object binding patterns
|
||||||
|
if (
|
||||||
|
ts.isVariableStatement(node) &&
|
||||||
|
ts.isVariableDeclaration(node.declarationList.declarations[0]) &&
|
||||||
|
ts.isIdentifier(node.declarationList.declarations[0].name) &&
|
||||||
|
node.declarationList.declarations[0].name.getText() === variable &&
|
||||||
|
ts.isCallExpression(node.declarationList.declarations[0].initializer) &&
|
||||||
|
node.declarationList.declarations[0].initializer.expression.getText() ===
|
||||||
|
'require' &&
|
||||||
|
ts.isStringLiteral(
|
||||||
|
node.declarationList.declarations[0].initializer.arguments[0]
|
||||||
|
) &&
|
||||||
|
node.declarationList.declarations[0].initializer.arguments[0].text === imp
|
||||||
|
) {
|
||||||
|
changes.push({
|
||||||
|
type: ChangeType.Delete,
|
||||||
|
start: node.pos,
|
||||||
|
length: node.end - node.pos,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return applyChangesToString(content, changes);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injects new ts.expression to the end of the module.exports array.
|
* Injects new ts.expression to the end of the module.exports array.
|
||||||
*/
|
*/
|
||||||
@ -342,6 +388,12 @@ export function addBlockToFlatConfigExport(
|
|||||||
return node.expression.right.elements;
|
return node.expression.right.elements;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The config is not in the format that we generate with, skip update.
|
||||||
|
// This could happen during `init-migration` when extracting config from the base, but
|
||||||
|
// base config was not generated by Nx.
|
||||||
|
if (!exportsArray) return content;
|
||||||
|
|
||||||
const insert = printer.printNode(ts.EmitHint.Expression, config, source);
|
const insert = printer.printNode(ts.EmitHint.Expression, config, source);
|
||||||
if (options.insertAtTheEnd) {
|
if (options.insertAtTheEnd) {
|
||||||
const index =
|
const index =
|
||||||
@ -520,7 +572,7 @@ export function removeCompatExtends(
|
|||||||
ts.ScriptKind.JS
|
ts.ScriptKind.JS
|
||||||
);
|
);
|
||||||
const changes: StringChange[] = [];
|
const changes: StringChange[] = [];
|
||||||
findAllBlocks(source).forEach((node) => {
|
findAllBlocks(source)?.forEach((node) => {
|
||||||
if (
|
if (
|
||||||
ts.isSpreadElement(node) &&
|
ts.isSpreadElement(node) &&
|
||||||
ts.isCallExpression(node.expression) &&
|
ts.isCallExpression(node.expression) &&
|
||||||
@ -554,7 +606,10 @@ export function removeCompatExtends(
|
|||||||
text:
|
text:
|
||||||
'\n' +
|
'\n' +
|
||||||
body.replace(
|
body.replace(
|
||||||
new RegExp('[ \t]s*...' + paramName + '[ \t]*,?\\s*', 'g'),
|
new RegExp(
|
||||||
|
'[ \t]s*...' + paramName + '(\\.rules)?[ \t]*,?\\s*',
|
||||||
|
'g'
|
||||||
|
),
|
||||||
''
|
''
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@ -565,6 +620,52 @@ export function removeCompatExtends(
|
|||||||
return applyChangesToString(content, changes);
|
return applyChangesToString(content, changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function removePredefinedConfigs(
|
||||||
|
content: string,
|
||||||
|
moduleImport: string,
|
||||||
|
moduleVariable: string,
|
||||||
|
configs: string[]
|
||||||
|
): string {
|
||||||
|
const source = ts.createSourceFile(
|
||||||
|
'',
|
||||||
|
content,
|
||||||
|
ts.ScriptTarget.Latest,
|
||||||
|
true,
|
||||||
|
ts.ScriptKind.JS
|
||||||
|
);
|
||||||
|
const changes: StringChange[] = [];
|
||||||
|
let removeImport = true;
|
||||||
|
findAllBlocks(source)?.forEach((node) => {
|
||||||
|
if (
|
||||||
|
ts.isSpreadElement(node) &&
|
||||||
|
ts.isElementAccessExpression(node.expression) &&
|
||||||
|
ts.isPropertyAccessExpression(node.expression.expression) &&
|
||||||
|
ts.isIdentifier(node.expression.expression.expression) &&
|
||||||
|
node.expression.expression.expression.getText() === moduleVariable &&
|
||||||
|
ts.isStringLiteral(node.expression.argumentExpression)
|
||||||
|
) {
|
||||||
|
const config = node.expression.argumentExpression.getText();
|
||||||
|
// Check the text without quotes
|
||||||
|
if (configs.includes(config.substring(1, config.length - 1))) {
|
||||||
|
changes.push({
|
||||||
|
type: ChangeType.Delete,
|
||||||
|
start: node.pos,
|
||||||
|
length: node.end - node.pos + 1, // trailing comma
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// If there is still a config used, do not remove import
|
||||||
|
removeImport = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let updated = applyChangesToString(content, changes);
|
||||||
|
if (removeImport) {
|
||||||
|
updated = removeImportFromFlatConfig(updated, moduleVariable, moduleImport);
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add plugins block to the top of the export blocks
|
* Add plugins block to the top of the export blocks
|
||||||
*/
|
*/
|
||||||
@ -596,7 +697,7 @@ export function addPluginsToExportsBlock(
|
|||||||
/**
|
/**
|
||||||
* Adds compat if missing to flat config
|
* Adds compat if missing to flat config
|
||||||
*/
|
*/
|
||||||
export function addCompatToFlatConfig(content: string) {
|
export function addFlatCompatToFlatConfig(content: string) {
|
||||||
let result = content;
|
let result = content;
|
||||||
result = addImportToFlatConfig(result, 'js', '@eslint/js');
|
result = addImportToFlatConfig(result, 'js', '@eslint/js');
|
||||||
if (result.includes('const compat = new FlatCompat')) {
|
if (result.includes('const compat = new FlatCompat')) {
|
||||||
@ -608,17 +709,15 @@ export function addCompatToFlatConfig(content: string) {
|
|||||||
{
|
{
|
||||||
type: ChangeType.Insert,
|
type: ChangeType.Insert,
|
||||||
index: index - 1,
|
index: index - 1,
|
||||||
text: `${DEFAULT_FLAT_CONFIG}\n`,
|
text: `
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULT_FLAT_CONFIG = `
|
|
||||||
const compat = new FlatCompat({
|
const compat = new FlatCompat({
|
||||||
baseDirectory: __dirname,
|
baseDirectory: __dirname,
|
||||||
recommendedConfig: js.configs.recommended,
|
recommendedConfig: js.configs.recommended,
|
||||||
});
|
});
|
||||||
`;
|
`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate node list representing the imports and the exports blocks
|
* Generate node list representing the imports and the exports blocks
|
||||||
@ -626,24 +725,11 @@ const compat = new FlatCompat({
|
|||||||
*/
|
*/
|
||||||
export function createNodeList(
|
export function createNodeList(
|
||||||
importsMap: Map<string, string>,
|
importsMap: Map<string, string>,
|
||||||
exportElements: ts.Expression[],
|
exportElements: ts.Expression[]
|
||||||
isFlatCompatNeeded: boolean
|
|
||||||
): ts.NodeArray<
|
): ts.NodeArray<
|
||||||
ts.VariableStatement | ts.Identifier | ts.ExpressionStatement | ts.SourceFile
|
ts.VariableStatement | ts.Identifier | ts.ExpressionStatement | ts.SourceFile
|
||||||
> {
|
> {
|
||||||
const importsList = [];
|
const importsList = [];
|
||||||
if (isFlatCompatNeeded) {
|
|
||||||
importsMap.set('@eslint/js', 'js');
|
|
||||||
|
|
||||||
importsList.push(
|
|
||||||
generateRequire(
|
|
||||||
ts.factory.createObjectBindingPattern([
|
|
||||||
ts.factory.createBindingElement(undefined, undefined, 'FlatCompat'),
|
|
||||||
]),
|
|
||||||
'@eslint/eslintrc'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateRequire(varName, imp, ts.factory);
|
// generateRequire(varName, imp, ts.factory);
|
||||||
Array.from(importsMap.entries()).forEach(([imp, varName]) => {
|
Array.from(importsMap.entries()).forEach(([imp, varName]) => {
|
||||||
@ -655,7 +741,7 @@ export function createNodeList(
|
|||||||
...importsList,
|
...importsList,
|
||||||
ts.createSourceFile(
|
ts.createSourceFile(
|
||||||
'',
|
'',
|
||||||
isFlatCompatNeeded ? DEFAULT_FLAT_CONFIG : '',
|
'',
|
||||||
ts.ScriptTarget.Latest,
|
ts.ScriptTarget.Latest,
|
||||||
false,
|
false,
|
||||||
ts.ScriptKind.JS
|
ts.ScriptKind.JS
|
||||||
@ -694,6 +780,27 @@ export function generatePluginExtendsElement(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function generatePluginExtendsElementWithCompatFixup(
|
||||||
|
plugin: string
|
||||||
|
): ts.SpreadElement {
|
||||||
|
return ts.factory.createSpreadElement(
|
||||||
|
ts.factory.createCallExpression(
|
||||||
|
ts.factory.createIdentifier('fixupConfigRules'),
|
||||||
|
undefined,
|
||||||
|
[
|
||||||
|
ts.factory.createCallExpression(
|
||||||
|
ts.factory.createPropertyAccessExpression(
|
||||||
|
ts.factory.createIdentifier('compat'),
|
||||||
|
ts.factory.createIdentifier('extends')
|
||||||
|
),
|
||||||
|
undefined,
|
||||||
|
[ts.factory.createStringLiteral(plugin)]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stringifies TS nodes to file content string
|
* Stringifies TS nodes to file content string
|
||||||
*/
|
*/
|
||||||
@ -754,25 +861,132 @@ export function generateRequire(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates AST object or spread element based on JSON override object
|
* FROM: https://github.com/eslint/rewrite/blob/e2a7ec809db20e638abbad250d105ddbde88a8d5/packages/migrate-config/src/migrate-config.js#L222
|
||||||
|
*
|
||||||
|
* Converts a glob pattern to a format that can be used in a flat config.
|
||||||
|
* @param {string} pattern The glob pattern to convert.
|
||||||
|
* @returns {string} The converted glob pattern.
|
||||||
|
*/
|
||||||
|
function convertGlobPattern(pattern: string): string {
|
||||||
|
const isNegated = pattern.startsWith('!');
|
||||||
|
const patternToTest = isNegated ? pattern.slice(1) : pattern;
|
||||||
|
// if the pattern is already in the correct format, return it
|
||||||
|
if (patternToTest === '**' || patternToTest.includes('/')) {
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
return `${isNegated ? '!' : ''}**/${patternToTest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FROM: https://github.com/eslint/rewrite/blob/e2a7ec809db20e638abbad250d105ddbde88a8d5/packages/migrate-config/src/migrate-config.js#L38
|
||||||
|
const keysToCopy = ['settings', 'rules', 'processor'];
|
||||||
|
|
||||||
|
export function overrideNeedsCompat(
|
||||||
|
override: Partial<Linter.ConfigOverride<Linter.RulesRecord>>
|
||||||
|
) {
|
||||||
|
return override.env || override.extends || override.plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an AST object or spread element representing a modern flat config entry,
|
||||||
|
* based on a given legacy eslintrc JSON override object
|
||||||
*/
|
*/
|
||||||
export function generateFlatOverride(
|
export function generateFlatOverride(
|
||||||
override: Linter.ConfigOverride<Linter.RulesRecord>
|
_override: Partial<Linter.ConfigOverride<Linter.RulesRecord>>
|
||||||
): ts.ObjectLiteralExpression | ts.SpreadElement {
|
): ts.ObjectLiteralExpression | ts.SpreadElement {
|
||||||
mapFilePaths(override);
|
const override = mapFilePaths(_override);
|
||||||
if (
|
|
||||||
!override.env &&
|
// We do not need the compat tooling for this override
|
||||||
!override.extends &&
|
if (!overrideNeedsCompat(override)) {
|
||||||
!override.plugins &&
|
// Ensure files is an array
|
||||||
!override.parser
|
let files = override.files;
|
||||||
) {
|
if (typeof files === 'string') {
|
||||||
|
files = [files];
|
||||||
|
}
|
||||||
|
|
||||||
|
const flatConfigOverride: Linter.FlatConfig = {
|
||||||
|
files,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (override.rules) {
|
||||||
|
flatConfigOverride.rules = override.rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy over everything that stays the same
|
||||||
|
keysToCopy.forEach((key) => {
|
||||||
|
if (override[key]) {
|
||||||
|
flatConfigOverride[key] = override[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (override.parser || override.parserOptions) {
|
||||||
|
const languageOptions = {};
|
||||||
|
if (override.parser) {
|
||||||
|
languageOptions['parser'] = override.parser;
|
||||||
|
}
|
||||||
if (override.parserOptions) {
|
if (override.parserOptions) {
|
||||||
const { parserOptions, ...rest } = override;
|
languageOptions['parserOptions'] = override.parserOptions;
|
||||||
return generateAst({ ...rest, languageOptions: { parserOptions } });
|
|
||||||
}
|
}
|
||||||
return generateAst(override);
|
if (Object.keys(languageOptions).length) {
|
||||||
|
flatConfigOverride.languageOptions = languageOptions;
|
||||||
}
|
}
|
||||||
const { files, excludedFiles, rules, parserOptions, ...rest } = override;
|
}
|
||||||
|
|
||||||
|
if (override['languageOptions']) {
|
||||||
|
flatConfigOverride.languageOptions = override['languageOptions'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (override.excludedFiles) {
|
||||||
|
flatConfigOverride.ignores = (
|
||||||
|
Array.isArray(override.excludedFiles)
|
||||||
|
? override.excludedFiles
|
||||||
|
: [override.excludedFiles]
|
||||||
|
).map((p) => convertGlobPattern(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
return generateAst(flatConfigOverride, {
|
||||||
|
keyToMatch: /^(parser|rules)$/,
|
||||||
|
replacer: (propertyAssignment, propertyName) => {
|
||||||
|
if (propertyName === 'rules') {
|
||||||
|
// Add comment that user can override rules if there are no overrides.
|
||||||
|
if (
|
||||||
|
ts.isObjectLiteralExpression(propertyAssignment.initializer) &&
|
||||||
|
propertyAssignment.initializer.properties.length === 0
|
||||||
|
) {
|
||||||
|
return ts.addSyntheticLeadingComment(
|
||||||
|
ts.factory.createPropertyAssignment(
|
||||||
|
propertyAssignment.name,
|
||||||
|
ts.factory.createObjectLiteralExpression([])
|
||||||
|
),
|
||||||
|
|
||||||
|
ts.SyntaxKind.SingleLineCommentTrivia,
|
||||||
|
' Override or add rules here'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point we are applying the flat config compat tooling to the override
|
||||||
|
const { excludedFiles, parser, parserOptions, rules, files, ...rest } =
|
||||||
|
override;
|
||||||
|
|
||||||
const objectLiteralElements: ts.ObjectLiteralElementLike[] = [
|
const objectLiteralElements: ts.ObjectLiteralElementLike[] = [
|
||||||
ts.factory.createSpreadAssignment(ts.factory.createIdentifier('config')),
|
ts.factory.createSpreadAssignment(ts.factory.createIdentifier('config')),
|
||||||
@ -844,9 +1058,28 @@ export function generateFlatOverride(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function generateFlatPredefinedConfig(
|
||||||
|
predefinedConfigName: string,
|
||||||
|
moduleName = 'nx',
|
||||||
|
spread = true
|
||||||
|
): ts.ObjectLiteralExpression | ts.SpreadElement | ts.ElementAccessExpression {
|
||||||
|
const node = ts.factory.createElementAccessExpression(
|
||||||
|
ts.factory.createPropertyAccessExpression(
|
||||||
|
ts.factory.createIdentifier(moduleName),
|
||||||
|
ts.factory.createIdentifier('configs')
|
||||||
|
),
|
||||||
|
ts.factory.createStringLiteral(predefinedConfigName)
|
||||||
|
);
|
||||||
|
|
||||||
|
return spread ? ts.factory.createSpreadElement(node) : node;
|
||||||
|
}
|
||||||
|
|
||||||
export function mapFilePaths(
|
export function mapFilePaths(
|
||||||
override: Linter.ConfigOverride<Linter.RulesRecord>
|
_override: Partial<Linter.ConfigOverride<Linter.RulesRecord>>
|
||||||
) {
|
) {
|
||||||
|
const override: Partial<Linter.ConfigOverride<Linter.RulesRecord>> = {
|
||||||
|
..._override,
|
||||||
|
};
|
||||||
if (override.files) {
|
if (override.files) {
|
||||||
override.files = Array.isArray(override.files)
|
override.files = Array.isArray(override.files)
|
||||||
? override.files
|
? override.files
|
||||||
@ -861,6 +1094,7 @@ export function mapFilePaths(
|
|||||||
mapFilePath(file)
|
mapFilePath(file)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return override;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addTSObjectProperty(
|
function addTSObjectProperty(
|
||||||
@ -876,10 +1110,21 @@ function addTSObjectProperty(
|
|||||||
/**
|
/**
|
||||||
* Generates an AST from a JSON-type input
|
* Generates an AST from a JSON-type input
|
||||||
*/
|
*/
|
||||||
export function generateAst<T>(input: unknown): T {
|
export function generateAst<T>(
|
||||||
|
input: unknown,
|
||||||
|
propertyAssignmentReplacer?: {
|
||||||
|
keyToMatch: RegExp | string;
|
||||||
|
replacer: (
|
||||||
|
propertyAssignment: ts.PropertyAssignment,
|
||||||
|
propertyName: string
|
||||||
|
) => ts.PropertyAssignment;
|
||||||
|
}
|
||||||
|
): T {
|
||||||
if (Array.isArray(input)) {
|
if (Array.isArray(input)) {
|
||||||
return ts.factory.createArrayLiteralExpression(
|
return ts.factory.createArrayLiteralExpression(
|
||||||
input.map((item) => generateAst<ts.Expression>(item)),
|
input.map((item) =>
|
||||||
|
generateAst<ts.Expression>(item, propertyAssignmentReplacer)
|
||||||
|
),
|
||||||
input.length > 1 // multiline only if more than one item
|
input.length > 1 // multiline only if more than one item
|
||||||
) as T;
|
) as T;
|
||||||
}
|
}
|
||||||
@ -888,13 +1133,9 @@ export function generateAst<T>(input: unknown): T {
|
|||||||
}
|
}
|
||||||
if (typeof input === 'object') {
|
if (typeof input === 'object') {
|
||||||
return ts.factory.createObjectLiteralExpression(
|
return ts.factory.createObjectLiteralExpression(
|
||||||
Object.entries(input)
|
generatePropertyAssignmentsFromObjectEntries(
|
||||||
.filter(([_, value]) => value !== undefined)
|
input,
|
||||||
.map(([key, value]) =>
|
propertyAssignmentReplacer
|
||||||
ts.factory.createPropertyAssignment(
|
|
||||||
isValidKey(key) ? key : ts.factory.createStringLiteral(key),
|
|
||||||
generateAst<ts.Expression>(value)
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
Object.keys(input).length > 1 // multiline only if more than one property
|
Object.keys(input).length > 1 // multiline only if more than one property
|
||||||
) as T;
|
) as T;
|
||||||
@ -912,6 +1153,35 @@ export function generateAst<T>(input: unknown): T {
|
|||||||
throw new Error(`Unknown type: ${typeof input} `);
|
throw new Error(`Unknown type: ${typeof input} `);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generatePropertyAssignmentsFromObjectEntries(
|
||||||
|
input: object,
|
||||||
|
propertyAssignmentReplacer?: {
|
||||||
|
keyToMatch: RegExp | string;
|
||||||
|
replacer: (
|
||||||
|
propertyAssignment: ts.PropertyAssignment,
|
||||||
|
propertyName: string
|
||||||
|
) => ts.PropertyAssignment;
|
||||||
|
}
|
||||||
|
): ts.PropertyAssignment[] {
|
||||||
|
return Object.entries(input)
|
||||||
|
.filter(([_, value]) => value !== undefined)
|
||||||
|
.map(([key, value]) => {
|
||||||
|
const original = ts.factory.createPropertyAssignment(
|
||||||
|
isValidKey(key) ? key : ts.factory.createStringLiteral(key),
|
||||||
|
generateAst<ts.Expression>(value, propertyAssignmentReplacer)
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
propertyAssignmentReplacer &&
|
||||||
|
(typeof propertyAssignmentReplacer.keyToMatch === 'string'
|
||||||
|
? key === propertyAssignmentReplacer.keyToMatch
|
||||||
|
: propertyAssignmentReplacer.keyToMatch.test(key))
|
||||||
|
) {
|
||||||
|
return propertyAssignmentReplacer.replacer(original, key);
|
||||||
|
}
|
||||||
|
return original;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function isValidKey(key: string): boolean {
|
function isValidKey(key: string): boolean {
|
||||||
return /^[a-zA-Z0-9_]+$/.test(key);
|
return /^[a-zA-Z0-9_]+$/.test(key);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -78,8 +78,7 @@ exports[`@nx/eslint:workspace-rules-project should generate the required files 4
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`@nx/eslint:workspace-rules-project should generate the required files 5`] = `
|
exports[`@nx/eslint:workspace-rules-project should generate the required files 5`] = `
|
||||||
"/* eslint-disable */
|
"export default {
|
||||||
export default {
|
|
||||||
displayName: 'eslint-rules',
|
displayName: 'eslint-rules',
|
||||||
preset: '../../jest.preset.js',
|
preset: '../../jest.preset.js',
|
||||||
transform: {
|
transform: {
|
||||||
|
|||||||
@ -96,7 +96,9 @@ const internalCreateNodes = async (
|
|||||||
).sort((a, b) => (a !== b && isSubDir(a, b) ? -1 : 1));
|
).sort((a, b) => (a !== b && isSubDir(a, b) ? -1 : 1));
|
||||||
const excludePatterns = dedupedProjectRoots.map((root) => `${root}/**/*`);
|
const excludePatterns = dedupedProjectRoots.map((root) => `${root}/**/*`);
|
||||||
|
|
||||||
const ESLint = await resolveESLintClass(isFlatConfig(configFilePath));
|
const ESLint = await resolveESLintClass({
|
||||||
|
useFlatConfigOverrideVal: isFlatConfig(configFilePath),
|
||||||
|
});
|
||||||
const eslintVersion = ESLint.version;
|
const eslintVersion = ESLint.version;
|
||||||
|
|
||||||
const projects: CreateNodesResult['projects'] = {};
|
const projects: CreateNodesResult['projects'] = {};
|
||||||
@ -180,7 +182,9 @@ const internalCreateNodesV2 = async (
|
|||||||
): Promise<CreateNodesResult> => {
|
): Promise<CreateNodesResult> => {
|
||||||
const configDir = dirname(configFilePath);
|
const configDir = dirname(configFilePath);
|
||||||
|
|
||||||
const ESLint = await resolveESLintClass(isFlatConfig(configFilePath));
|
const ESLint = await resolveESLintClass({
|
||||||
|
useFlatConfigOverrideVal: isFlatConfig(configFilePath),
|
||||||
|
});
|
||||||
const eslintVersion = ESLint.version;
|
const eslintVersion = ESLint.version;
|
||||||
|
|
||||||
const projects: CreateNodesResult['projects'] = {};
|
const projects: CreateNodesResult['projects'] = {};
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Tree } from '@nx/devkit';
|
import { Tree } from '@nx/devkit';
|
||||||
|
import { gte } from 'semver';
|
||||||
|
|
||||||
// todo: add support for eslint.config.mjs,
|
// todo: add support for eslint.config.mjs,
|
||||||
export const eslintFlatConfigFilenames = [
|
export const eslintFlatConfigFilenames = [
|
||||||
@ -6,19 +7,42 @@ export const eslintFlatConfigFilenames = [
|
|||||||
'eslint.config.cjs',
|
'eslint.config.cjs',
|
||||||
];
|
];
|
||||||
|
|
||||||
export function flatConfigEslintFilename(tree: Tree): string {
|
export function getRootESLintFlatConfigFilename(tree: Tree): string {
|
||||||
for (const file of eslintFlatConfigFilenames) {
|
for (const file of eslintFlatConfigFilenames) {
|
||||||
if (tree.exists(file)) {
|
if (tree.exists(file)) {
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error('Could not find flat config file');
|
throw new Error('Could not find root flat config file');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useFlatConfig(tree: Tree): boolean {
|
export function useFlatConfig(tree?: Tree): boolean {
|
||||||
try {
|
// Prioritize taking ESLint's own environment variable into account when determining if we should use flat config
|
||||||
return !!flatConfigEslintFilename(tree);
|
// If it is not defined, then default to true.
|
||||||
} catch {
|
if (process.env.ESLINT_USE_FLAT_CONFIG === 'true') {
|
||||||
|
return true;
|
||||||
|
} else if (process.env.ESLINT_USE_FLAT_CONFIG === 'false') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we find an existing flat config file in the root of the provided tree, we should use flat config
|
||||||
|
if (tree) {
|
||||||
|
const hasRootFlatConfig = eslintFlatConfigFilenames.some((filename) =>
|
||||||
|
tree.exists(filename)
|
||||||
|
);
|
||||||
|
if (hasRootFlatConfig) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise fallback to checking the installed eslint version
|
||||||
|
try {
|
||||||
|
const { ESLint } = require('eslint');
|
||||||
|
// Default to any v8 version to compare against in this case as it implies a much older version of ESLint was found (and gte() requires a valid version)
|
||||||
|
const eslintVersion = ESLint.version || '8.0.0';
|
||||||
|
return gte(eslintVersion, '9.0.0');
|
||||||
|
} catch {
|
||||||
|
// Default to assuming flat config in case ESLint is not yet installed
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,26 @@
|
|||||||
import type { ESLint } from 'eslint';
|
import type { ESLint } from 'eslint';
|
||||||
|
import { useFlatConfig } from '../utils/flat-config';
|
||||||
|
|
||||||
export async function resolveESLintClass(
|
export async function resolveESLintClass(opts?: {
|
||||||
useFlatConfig = false
|
useFlatConfigOverrideVal: boolean;
|
||||||
): Promise<typeof ESLint> {
|
}): Promise<typeof ESLint> {
|
||||||
try {
|
try {
|
||||||
// In eslint 8.57.0 (the final v8 version), a dedicated API was added for resolving the correct ESLint class.
|
// Explicitly use the FlatESLint and LegacyESLint classes here because the ESLint class points at a different one based on ESLint v8 vs ESLint v9
|
||||||
const eslint = await import('eslint');
|
// But the decision on which one to use is not just based on the major version of ESLint.
|
||||||
if (typeof (eslint as any).loadESLint === 'function') {
|
// @ts-expect-error The may be wrong based on our installed eslint version
|
||||||
return await (eslint as any).loadESLint({ useFlatConfig });
|
const { LegacyESLint, FlatESLint } = await import(
|
||||||
}
|
'eslint/use-at-your-own-risk'
|
||||||
// If that API is not available (an older version of v8), we need to use the old way of resolving the ESLint class.
|
);
|
||||||
if (!useFlatConfig) {
|
|
||||||
return eslint.ESLint;
|
const shouldESLintUseFlatConfig =
|
||||||
}
|
typeof opts?.useFlatConfigOverrideVal === 'boolean'
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
? opts.useFlatConfigOverrideVal
|
||||||
const { FlatESLint } = require('eslint/use-at-your-own-risk');
|
: useFlatConfig();
|
||||||
return FlatESLint;
|
|
||||||
|
return shouldESLintUseFlatConfig ? FlatESLint : LegacyESLint;
|
||||||
} catch {
|
} catch {
|
||||||
throw new Error('Unable to find ESLint. Ensure ESLint is installed.');
|
throw new Error(
|
||||||
|
'Unable to find `eslint`. Ensure a valid `eslint` version is installed.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
packages/eslint/src/utils/version-utils.ts
Normal file
32
packages/eslint/src/utils/version-utils.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { readJson, readJsonFile, type Tree } from '@nx/devkit';
|
||||||
|
import { checkAndCleanWithSemver } from '@nx/devkit/src/utils/semver';
|
||||||
|
import { readModulePackageJson } from 'nx/src/devkit-internals';
|
||||||
|
|
||||||
|
export function getInstalledEslintVersion(tree?: Tree): string | null {
|
||||||
|
try {
|
||||||
|
const eslintPackageJson = readModulePackageJson('eslint').packageJson;
|
||||||
|
return eslintPackageJson.version;
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
// eslint is not installed on disk, it could be in the package.json
|
||||||
|
// but waiting to be installed
|
||||||
|
const rootPackageJson = tree
|
||||||
|
? readJson(tree, 'package.json')
|
||||||
|
: readJsonFile('package.json');
|
||||||
|
const eslintVersionInRootPackageJson =
|
||||||
|
rootPackageJson.devDependencies?.['eslint'] ??
|
||||||
|
rootPackageJson.dependencies?.['eslint'];
|
||||||
|
|
||||||
|
if (!eslintVersionInRootPackageJson) {
|
||||||
|
// eslint is not installed
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// try to parse and return the version
|
||||||
|
return checkAndCleanWithSemver('eslint', eslintVersionInRootPackageJson);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
// we could not resolve the version
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@ -4,3 +4,8 @@ export const eslintVersion = '~8.57.0';
|
|||||||
export const eslintrcVersion = '^2.1.1';
|
export const eslintrcVersion = '^2.1.1';
|
||||||
export const eslintConfigPrettierVersion = '^9.0.0';
|
export const eslintConfigPrettierVersion = '^9.0.0';
|
||||||
export const typescriptESLintVersion = '^7.16.0';
|
export const typescriptESLintVersion = '^7.16.0';
|
||||||
|
|
||||||
|
// Updated linting stack for ESLint v9, typescript-eslint v8
|
||||||
|
export const eslint9__typescriptESLintVersion = '^8.0.0';
|
||||||
|
export const eslint9__eslintVersion = '^9.8.0';
|
||||||
|
export const eslintCompat = '^1.1.1';
|
||||||
|
|||||||
@ -35,7 +35,15 @@ export async function installAndUpdatePackageJson(
|
|||||||
context: ExecutorContext,
|
context: ExecutorContext,
|
||||||
options: ExpoInstallOptions
|
options: ExpoInstallOptions
|
||||||
) {
|
) {
|
||||||
await installAsync(context.root, options);
|
const { installAsync } = require('@expo/cli/build/src/install/installAsync');
|
||||||
|
|
||||||
|
const packages =
|
||||||
|
typeof options.packages === 'string'
|
||||||
|
? options.packages.split(',')
|
||||||
|
: options.packages ?? [];
|
||||||
|
|
||||||
|
// Use force in case there are any unmet peer dependencies.
|
||||||
|
await installAsync(packages, createInstallOptions(options), ['--force']);
|
||||||
|
|
||||||
const projectRoot =
|
const projectRoot =
|
||||||
context.projectsConfigurations.projects[context.projectName].root;
|
context.projectsConfigurations.projects[context.projectName].root;
|
||||||
@ -48,10 +56,6 @@ export async function installAndUpdatePackageJson(
|
|||||||
|
|
||||||
const workspacePackageJson = readJsonFile(workspacePackageJsonPath);
|
const workspacePackageJson = readJsonFile(workspacePackageJsonPath);
|
||||||
const projectPackageJson = readJsonFile(projectPackageJsonPath);
|
const projectPackageJson = readJsonFile(projectPackageJsonPath);
|
||||||
const packages =
|
|
||||||
typeof options.packages === 'string'
|
|
||||||
? options.packages.split(',')
|
|
||||||
: options.packages;
|
|
||||||
displayNewlyAddedDepsMessage(
|
displayNewlyAddedDepsMessage(
|
||||||
context.projectName,
|
context.projectName,
|
||||||
await syncDeps(
|
await syncDeps(
|
||||||
@ -65,42 +69,10 @@ export async function installAndUpdatePackageJson(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function installAsync(
|
|
||||||
workspaceRoot: string,
|
|
||||||
options: ExpoInstallOptions
|
|
||||||
): Promise<number> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
childProcess = fork(
|
|
||||||
require.resolve('@expo/cli/build/bin/cli'),
|
|
||||||
['install', ...createInstallOptions(options)],
|
|
||||||
{ cwd: workspaceRoot, env: process.env }
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ensure the child process is killed when the parent exits
|
|
||||||
process.on('exit', () => childProcess.kill());
|
|
||||||
process.on('SIGTERM', () => childProcess.kill());
|
|
||||||
|
|
||||||
childProcess.on('error', (err) => {
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
childProcess.on('exit', (code) => {
|
|
||||||
if (code === 0) {
|
|
||||||
resolve(code);
|
|
||||||
} else {
|
|
||||||
reject(code);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// options from https://github.com/expo/expo/blob/main/packages/%40expo/cli/src/install/index.ts
|
// options from https://github.com/expo/expo/blob/main/packages/%40expo/cli/src/install/index.ts
|
||||||
function createInstallOptions(options: ExpoInstallOptions) {
|
function createInstallOptions(options: ExpoInstallOptions) {
|
||||||
return Object.keys(options).reduce((acc, k) => {
|
return Object.keys(options).reduce((acc, k) => {
|
||||||
const v = options[k];
|
const v = options[k];
|
||||||
if (k === 'packages') {
|
|
||||||
const packages = typeof v === 'string' ? v.split(',') : v;
|
|
||||||
acc.push(...packages);
|
|
||||||
} else {
|
|
||||||
if (typeof v === 'boolean') {
|
if (typeof v === 'boolean') {
|
||||||
if (v === true) {
|
if (v === true) {
|
||||||
// when true, does not need to pass the value true, just need to pass the flag in kebob case
|
// when true, does not need to pass the value true, just need to pass the flag in kebob case
|
||||||
@ -109,7 +81,7 @@ function createInstallOptions(options: ExpoInstallOptions) {
|
|||||||
} else {
|
} else {
|
||||||
acc.push(`--${names(k).fileName}`, v);
|
acc.push(`--${names(k).fileName}`, v);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return acc;
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import { ChildProcess, fork } from 'child_process';
|
|||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
import { podInstall } from '../../utils/pod-install-task';
|
import { podInstall } from '../../utils/pod-install-task';
|
||||||
import { installAsync } from '../install/install.impl';
|
|
||||||
import { ExpoPrebuildOptions } from './schema';
|
import { ExpoPrebuildOptions } from './schema';
|
||||||
|
|
||||||
export interface ExpoPrebuildOutput {
|
export interface ExpoPrebuildOutput {
|
||||||
@ -23,7 +22,10 @@ export default async function* prebuildExecutor(
|
|||||||
await prebuildAsync(context.root, projectRoot, options);
|
await prebuildAsync(context.root, projectRoot, options);
|
||||||
|
|
||||||
if (options.install) {
|
if (options.install) {
|
||||||
await installAsync(workspaceRoot, {});
|
const {
|
||||||
|
installAsync,
|
||||||
|
} = require('@expo/cli/build/src/install/installAsync');
|
||||||
|
await installAsync([], {});
|
||||||
if (options.platform === 'ios') {
|
if (options.platform === 'ios') {
|
||||||
podInstall(join(context.root, projectRoot, 'ios'));
|
podInstall(join(context.root, projectRoot, 'ios'));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { existsSync } from 'fs-extra';
|
|||||||
import { ExpoRunOptions } from './schema';
|
import { ExpoRunOptions } from './schema';
|
||||||
import { prebuildAsync } from '../prebuild/prebuild.impl';
|
import { prebuildAsync } from '../prebuild/prebuild.impl';
|
||||||
import { podInstall } from '../../utils/pod-install-task';
|
import { podInstall } from '../../utils/pod-install-task';
|
||||||
import { installAsync } from '../install/install.impl';
|
|
||||||
|
|
||||||
export interface ExpoRunOutput {
|
export interface ExpoRunOutput {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
@ -34,7 +33,10 @@ export default async function* runExecutor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options.install) {
|
if (options.install) {
|
||||||
await installAsync(context.root, {});
|
const {
|
||||||
|
installAsync,
|
||||||
|
} = require('@expo/cli/build/src/install/installAsync');
|
||||||
|
await installAsync([], {});
|
||||||
if (options.platform === 'ios') {
|
if (options.platform === 'ios') {
|
||||||
podInstall(join(context.root, projectRoot, 'ios'));
|
podInstall(join(context.root, projectRoot, 'ios'));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,8 +9,11 @@ import { extraEslintDependencies } from '@nx/react/src/utils/lint';
|
|||||||
import {
|
import {
|
||||||
addExtendsToLintConfig,
|
addExtendsToLintConfig,
|
||||||
addIgnoresToLintConfig,
|
addIgnoresToLintConfig,
|
||||||
|
addOverrideToLintConfig,
|
||||||
|
addPredefinedConfigToFlatLintConfig,
|
||||||
isEslintConfigSupported,
|
isEslintConfigSupported,
|
||||||
} from '@nx/eslint/src/generators/utils/eslint-file';
|
} from '@nx/eslint/src/generators/utils/eslint-file';
|
||||||
|
import { useFlatConfig } from '@nx/eslint/src/utils/flat-config';
|
||||||
|
|
||||||
interface NormalizedSchema {
|
interface NormalizedSchema {
|
||||||
linter?: Linter | LinterType;
|
linter?: Linter | LinterType;
|
||||||
@ -40,7 +43,24 @@ export async function addLinting(host: Tree, options: NormalizedSchema) {
|
|||||||
tasks.push(lintTask);
|
tasks.push(lintTask);
|
||||||
|
|
||||||
if (isEslintConfigSupported(host)) {
|
if (isEslintConfigSupported(host)) {
|
||||||
addExtendsToLintConfig(host, options.projectRoot, 'plugin:@nx/react');
|
if (useFlatConfig(host)) {
|
||||||
|
addPredefinedConfigToFlatLintConfig(
|
||||||
|
host,
|
||||||
|
options.projectRoot,
|
||||||
|
'flat/react'
|
||||||
|
);
|
||||||
|
// Add an empty rules object to users know how to add/override rules
|
||||||
|
addOverrideToLintConfig(host, options.projectRoot, {
|
||||||
|
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
||||||
|
rules: {},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const addExtendsTask = addExtendsToLintConfig(host, options.projectRoot, {
|
||||||
|
name: 'plugin:@nx/react',
|
||||||
|
needCompatFixup: true,
|
||||||
|
});
|
||||||
|
tasks.push(addExtendsTask);
|
||||||
|
}
|
||||||
addIgnoresToLintConfig(host, options.projectRoot, [
|
addIgnoresToLintConfig(host, options.projectRoot, [
|
||||||
'.expo',
|
'.expo',
|
||||||
'web-build',
|
'web-build',
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`jestProject --babelJest should generate proper jest.transform when --compiler=swc and supportTsx is true 1`] = `
|
exports[`jestProject --babelJest should generate proper jest.transform when --compiler=swc and supportTsx is true 1`] = `
|
||||||
"/* eslint-disable */
|
"export default {
|
||||||
export default {
|
|
||||||
displayName: 'lib1',
|
displayName: 'lib1',
|
||||||
preset: '../../jest.preset.js',
|
preset: '../../jest.preset.js',
|
||||||
transform: {
|
transform: {
|
||||||
@ -23,8 +22,7 @@ export default {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`jestProject --babelJest should generate proper jest.transform when babelJest and supportTsx is true 1`] = `
|
exports[`jestProject --babelJest should generate proper jest.transform when babelJest and supportTsx is true 1`] = `
|
||||||
"/* eslint-disable */
|
"export default {
|
||||||
export default {
|
|
||||||
displayName: 'lib1',
|
displayName: 'lib1',
|
||||||
preset: '../../jest.preset.js',
|
preset: '../../jest.preset.js',
|
||||||
transform: {
|
transform: {
|
||||||
@ -37,8 +35,7 @@ export default {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`jestProject --babelJest should generate proper jest.transform when babelJest is true 1`] = `
|
exports[`jestProject --babelJest should generate proper jest.transform when babelJest is true 1`] = `
|
||||||
"/* eslint-disable */
|
"export default {
|
||||||
export default {
|
|
||||||
displayName: 'lib1',
|
displayName: 'lib1',
|
||||||
preset: '../../jest.preset.js',
|
preset: '../../jest.preset.js',
|
||||||
transform: {
|
transform: {
|
||||||
@ -51,8 +48,7 @@ export default {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`jestProject --setup-file should have setupFilesAfterEnv and globals.ts-jest in the jest.config when generated for angular 1`] = `
|
exports[`jestProject --setup-file should have setupFilesAfterEnv and globals.ts-jest in the jest.config when generated for angular 1`] = `
|
||||||
"/* eslint-disable */
|
"export default {
|
||||||
export default {
|
|
||||||
displayName: 'lib1',
|
displayName: 'lib1',
|
||||||
preset: '../../jest.preset.js',
|
preset: '../../jest.preset.js',
|
||||||
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
||||||
@ -77,8 +73,7 @@ export default {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`jestProject should create a jest.config.ts 1`] = `
|
exports[`jestProject should create a jest.config.ts 1`] = `
|
||||||
"/* eslint-disable */
|
"export default {
|
||||||
export default {
|
|
||||||
displayName: 'lib1',
|
displayName: 'lib1',
|
||||||
preset: '../../jest.preset.js',
|
preset: '../../jest.preset.js',
|
||||||
coverageDirectory: '../../coverage/libs/lib1',
|
coverageDirectory: '../../coverage/libs/lib1',
|
||||||
@ -87,8 +82,7 @@ export default {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`jestProject should generate files 2`] = `
|
exports[`jestProject should generate files 2`] = `
|
||||||
"/* eslint-disable */
|
"export default {
|
||||||
export default {
|
|
||||||
displayName: 'lib1',
|
displayName: 'lib1',
|
||||||
preset: '../../jest.preset.js',
|
preset: '../../jest.preset.js',
|
||||||
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
||||||
|
|||||||
@ -357,8 +357,7 @@ describe('jestProject', () => {
|
|||||||
project: 'my-project',
|
project: 'my-project',
|
||||||
});
|
});
|
||||||
expect(tree.read('jest.config.ts', 'utf-8')).toMatchInlineSnapshot(`
|
expect(tree.read('jest.config.ts', 'utf-8')).toMatchInlineSnapshot(`
|
||||||
"/* eslint-disable */
|
"export default {
|
||||||
export default {
|
|
||||||
displayName: 'my-project',
|
displayName: 'my-project',
|
||||||
preset: './jest.preset.js',
|
preset: './jest.preset.js',
|
||||||
coverageDirectory: './coverage/my-project',
|
coverageDirectory: './coverage/my-project',
|
||||||
@ -389,8 +388,7 @@ describe('jestProject', () => {
|
|||||||
js: true,
|
js: true,
|
||||||
});
|
});
|
||||||
expect(tree.read('jest.config.js', 'utf-8')).toMatchInlineSnapshot(`
|
expect(tree.read('jest.config.js', 'utf-8')).toMatchInlineSnapshot(`
|
||||||
"/* eslint-disable */
|
"module.exports = {
|
||||||
module.exports = {
|
|
||||||
displayName: 'my-project',
|
displayName: 'my-project',
|
||||||
preset: './jest.preset.js',
|
preset: './jest.preset.js',
|
||||||
coverageDirectory: './coverage/my-project',
|
coverageDirectory: './coverage/my-project',
|
||||||
@ -424,8 +422,7 @@ describe('jestProject', () => {
|
|||||||
// ASSERT
|
// ASSERT
|
||||||
expect(tree.read('libs/lib1/jest.config.ts', 'utf-8'))
|
expect(tree.read('libs/lib1/jest.config.ts', 'utf-8'))
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
"/* eslint-disable */
|
"export default {
|
||||||
export default {
|
|
||||||
displayName: 'lib1',
|
displayName: 'lib1',
|
||||||
preset: '../../jest.preset.cjs',
|
preset: '../../jest.preset.cjs',
|
||||||
coverageDirectory: '../../coverage/libs/lib1',
|
coverageDirectory: '../../coverage/libs/lib1',
|
||||||
@ -451,8 +448,7 @@ describe('jestProject', () => {
|
|||||||
expect(tree.exists('jest.preset.cjs')).toBeTruthy();
|
expect(tree.exists('jest.preset.cjs')).toBeTruthy();
|
||||||
expect(tree.read('libs/lib1/jest.config.ts', 'utf-8'))
|
expect(tree.read('libs/lib1/jest.config.ts', 'utf-8'))
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
"/* eslint-disable */
|
"export default {
|
||||||
export default {
|
|
||||||
displayName: 'lib1',
|
displayName: 'lib1',
|
||||||
preset: '../../jest.preset.cjs',
|
preset: '../../jest.preset.cjs',
|
||||||
coverageDirectory: '../../coverage/libs/lib1',
|
coverageDirectory: '../../coverage/libs/lib1',
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable */
|
|
||||||
<% if(js){ %>module.exports =<% } else{ %>export default<% } %> {
|
<% if(js){ %>module.exports =<% } else{ %>export default<% } %> {
|
||||||
displayName: '<%= project %>',
|
displayName: '<%= project %>',
|
||||||
preset: '<%= offsetFromRoot %>jest.preset.<%= presetExt %>',
|
preset: '<%= offsetFromRoot %>jest.preset.<%= presetExt %>',
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable */
|
|
||||||
<% if(js){ %>module.exports =<% } else{ %>export default<% } %> {
|
<% if(js){ %>module.exports =<% } else{ %>export default<% } %> {
|
||||||
displayName: '<%= project %>',
|
displayName: '<%= project %>',
|
||||||
preset: '<%= offsetFromRoot %>jest.preset.<%= presetExt %>',<% if(setupFile !== 'none') { %>
|
preset: '<%= offsetFromRoot %>jest.preset.<%= presetExt %>',<% if(setupFile !== 'none') { %>
|
||||||
|
|||||||
@ -537,7 +537,14 @@ describe('lib', () => {
|
|||||||
],
|
],
|
||||||
"parser": "jsonc-eslint-parser",
|
"parser": "jsonc-eslint-parser",
|
||||||
"rules": {
|
"rules": {
|
||||||
"@nx/dependency-checks": "error",
|
"@nx/dependency-checks": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"ignoredFiles": [
|
||||||
|
"{projectRoot}/eslint.config.{js,cjs,mjs}",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -594,7 +601,14 @@ describe('lib', () => {
|
|||||||
],
|
],
|
||||||
"parser": "jsonc-eslint-parser",
|
"parser": "jsonc-eslint-parser",
|
||||||
"rules": {
|
"rules": {
|
||||||
"@nx/dependency-checks": "error",
|
"@nx/dependency-checks": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"ignoredFiles": [
|
||||||
|
"{projectRoot}/eslint.config.{js,cjs,mjs}",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -719,7 +733,14 @@ describe('lib', () => {
|
|||||||
],
|
],
|
||||||
"parser": "jsonc-eslint-parser",
|
"parser": "jsonc-eslint-parser",
|
||||||
"rules": {
|
"rules": {
|
||||||
"@nx/dependency-checks": "error",
|
"@nx/dependency-checks": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"ignoredFiles": [
|
||||||
|
"{projectRoot}/eslint.config.{js,cjs,mjs}",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -745,8 +766,7 @@ describe('lib', () => {
|
|||||||
expect(tree.exists(`my-lib/jest.config.ts`)).toBeTruthy();
|
expect(tree.exists(`my-lib/jest.config.ts`)).toBeTruthy();
|
||||||
expect(tree.read(`my-lib/jest.config.ts`, 'utf-8'))
|
expect(tree.read(`my-lib/jest.config.ts`, 'utf-8'))
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
"/* eslint-disable */
|
"export default {
|
||||||
export default {
|
|
||||||
displayName: 'my-lib',
|
displayName: 'my-lib',
|
||||||
preset: '../jest.preset.js',
|
preset: '../jest.preset.js',
|
||||||
transform: {
|
transform: {
|
||||||
@ -1483,7 +1503,10 @@ describe('lib', () => {
|
|||||||
'@nx/dependency-checks': [
|
'@nx/dependency-checks': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
ignoredFiles: ['{projectRoot}/esbuild.config.{js,ts,mjs,mts}'],
|
ignoredFiles: [
|
||||||
|
'{projectRoot}/eslint.config.{js,cjs,mjs}',
|
||||||
|
'{projectRoot}/esbuild.config.{js,ts,mjs,mts}',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -1508,7 +1531,10 @@ describe('lib', () => {
|
|||||||
'@nx/dependency-checks': [
|
'@nx/dependency-checks': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
ignoredFiles: ['{projectRoot}/rollup.config.{js,ts,mjs,mts}'],
|
ignoredFiles: [
|
||||||
|
'{projectRoot}/eslint.config.{js,cjs,mjs}',
|
||||||
|
'{projectRoot}/rollup.config.{js,ts,mjs,mts}',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -347,7 +347,13 @@ export async function addLint(
|
|||||||
files: ['*.json'],
|
files: ['*.json'],
|
||||||
parser: 'jsonc-eslint-parser',
|
parser: 'jsonc-eslint-parser',
|
||||||
rules: {
|
rules: {
|
||||||
'@nx/dependency-checks': 'error',
|
'@nx/dependency-checks': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
// With flat configs, we don't want to include imports in the eslint js/cjs/mjs files to be checked
|
||||||
|
ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs}'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -382,19 +388,22 @@ export async function addLint(
|
|||||||
ruleOptions = {};
|
ruleOptions = {};
|
||||||
}
|
}
|
||||||
if (options.bundler === 'vite' || options.unitTestRunner === 'vitest') {
|
if (options.bundler === 'vite' || options.unitTestRunner === 'vitest') {
|
||||||
ruleOptions.ignoredFiles = [
|
ruleOptions.ignoredFiles ??= [];
|
||||||
'{projectRoot}/vite.config.{js,ts,mjs,mts}',
|
ruleOptions.ignoredFiles.push(
|
||||||
];
|
'{projectRoot}/vite.config.{js,ts,mjs,mts}'
|
||||||
|
);
|
||||||
o.rules['@nx/dependency-checks'] = [ruleSeverity, ruleOptions];
|
o.rules['@nx/dependency-checks'] = [ruleSeverity, ruleOptions];
|
||||||
} else if (options.bundler === 'rollup') {
|
} else if (options.bundler === 'rollup') {
|
||||||
ruleOptions.ignoredFiles = [
|
ruleOptions.ignoredFiles ??= [];
|
||||||
'{projectRoot}/rollup.config.{js,ts,mjs,mts}',
|
ruleOptions.ignoredFiles.push(
|
||||||
];
|
'{projectRoot}/rollup.config.{js,ts,mjs,mts}'
|
||||||
|
);
|
||||||
o.rules['@nx/dependency-checks'] = [ruleSeverity, ruleOptions];
|
o.rules['@nx/dependency-checks'] = [ruleSeverity, ruleOptions];
|
||||||
} else if (options.bundler === 'esbuild') {
|
} else if (options.bundler === 'esbuild') {
|
||||||
ruleOptions.ignoredFiles = [
|
ruleOptions.ignoredFiles ??= [];
|
||||||
'{projectRoot}/esbuild.config.{js,ts,mjs,mts}',
|
ruleOptions.ignoredFiles.push(
|
||||||
];
|
'{projectRoot}/esbuild.config.{js,ts,mjs,mts}'
|
||||||
|
);
|
||||||
o.rules['@nx/dependency-checks'] = [ruleSeverity, ruleOptions];
|
o.rules['@nx/dependency-checks'] = [ruleSeverity, ruleOptions];
|
||||||
}
|
}
|
||||||
return o;
|
return o;
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`lib --testEnvironment should set target jest testEnvironment to jsdom 1`] = `
|
exports[`lib --testEnvironment should set target jest testEnvironment to jsdom 1`] = `
|
||||||
"/* eslint-disable */
|
"export default {
|
||||||
export default {
|
|
||||||
displayName: 'my-lib',
|
displayName: 'my-lib',
|
||||||
preset: '../jest.preset.js',
|
preset: '../jest.preset.js',
|
||||||
transform: {
|
transform: {
|
||||||
@ -15,8 +14,7 @@ export default {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`lib --testEnvironment should set target jest testEnvironment to node by default 1`] = `
|
exports[`lib --testEnvironment should set target jest testEnvironment to node by default 1`] = `
|
||||||
"/* eslint-disable */
|
"export default {
|
||||||
export default {
|
|
||||||
displayName: 'my-lib',
|
displayName: 'my-lib',
|
||||||
preset: '../jest.preset.js',
|
preset: '../jest.preset.js',
|
||||||
testEnvironment: 'node',
|
testEnvironment: 'node',
|
||||||
|
|||||||
@ -601,6 +601,38 @@ describe('app', () => {
|
|||||||
|
|
||||||
describe('--linter', () => {
|
describe('--linter', () => {
|
||||||
describe('default (eslint)', () => {
|
describe('default (eslint)', () => {
|
||||||
|
it('should add flat config as needed', async () => {
|
||||||
|
tree.write('eslint.config.js', '');
|
||||||
|
const name = uniq();
|
||||||
|
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name,
|
||||||
|
style: 'css',
|
||||||
|
projectNameAndRootFormat: 'as-provided',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tree.read(`${name}/eslint.config.js`, 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||||
|
const js = require('@eslint/js');
|
||||||
|
const nx = require('@nx/eslint-plugin');
|
||||||
|
const baseConfig = require('../eslint.config.js');
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended,
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
...compat.extends('next', 'next/core-web-vitals'),
|
||||||
|
...baseConfig,
|
||||||
|
...nx.configs['flat/react-typescript'],
|
||||||
|
{ ignores: ['.next/**/*'] },
|
||||||
|
];
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
it('should add .eslintrc.json and dependencies', async () => {
|
it('should add .eslintrc.json and dependencies', async () => {
|
||||||
const name = uniq();
|
const name = uniq();
|
||||||
|
|
||||||
@ -660,17 +692,6 @@ describe('app', () => {
|
|||||||
],
|
],
|
||||||
"rules": {},
|
"rules": {},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"env": {
|
|
||||||
"jest": true,
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"*.spec.ts",
|
|
||||||
"*.spec.tsx",
|
|
||||||
"*.spec.js",
|
|
||||||
"*.spec.jsx",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|||||||
@ -90,17 +90,6 @@ describe('updateEslint', () => {
|
|||||||
],
|
],
|
||||||
"rules": {},
|
"rules": {},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"env": {
|
|
||||||
"jest": true,
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"*.spec.ts",
|
|
||||||
"*.spec.tsx",
|
|
||||||
"*.spec.js",
|
|
||||||
"*.spec.jsx",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
@ -115,6 +104,7 @@ describe('updateEslint', () => {
|
|||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
"const { FlatCompat } = require("@eslint/eslintrc");
|
"const { FlatCompat } = require("@eslint/eslintrc");
|
||||||
const js = require("@eslint/js");
|
const js = require("@eslint/js");
|
||||||
|
const nx = require("@nx/eslint-plugin");
|
||||||
const baseConfig = require("../eslint.config.js");
|
const baseConfig = require("../eslint.config.js");
|
||||||
|
|
||||||
const compat = new FlatCompat({
|
const compat = new FlatCompat({
|
||||||
@ -122,50 +112,10 @@ describe('updateEslint', () => {
|
|||||||
recommendedConfig: js.configs.recommended,
|
recommendedConfig: js.configs.recommended,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
...compat.extends("plugin:@nx/react-typescript", "next", "next/core-web-vitals"),
|
...compat.extends("next", "next/core-web-vitals"),
|
||||||
...baseConfig,
|
...baseConfig,
|
||||||
{
|
...nx.configs["flat/react-typescript"],
|
||||||
"files": [
|
|
||||||
"**/*.ts",
|
|
||||||
"**/*.tsx",
|
|
||||||
"**/*.js",
|
|
||||||
"**/*.jsx"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"@next/next/no-html-link-for-pages": [
|
|
||||||
"error",
|
|
||||||
"my-app/pages"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: [
|
|
||||||
"**/*.ts",
|
|
||||||
"**/*.tsx"
|
|
||||||
],
|
|
||||||
rules: {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: [
|
|
||||||
"**/*.js",
|
|
||||||
"**/*.jsx"
|
|
||||||
],
|
|
||||||
rules: {}
|
|
||||||
},
|
|
||||||
...compat.config({ env: { jest: true } }).map(config => ({
|
|
||||||
...config,
|
|
||||||
files: [
|
|
||||||
"**/*.spec.ts",
|
|
||||||
"**/*.spec.tsx",
|
|
||||||
"**/*.spec.js",
|
|
||||||
"**/*.spec.jsx"
|
|
||||||
],
|
|
||||||
rules: {
|
|
||||||
...config.rules
|
|
||||||
}
|
|
||||||
})),
|
|
||||||
{ ignores: [".next/**/*"] }
|
{ ignores: [".next/**/*"] }
|
||||||
];
|
];
|
||||||
"
|
"
|
||||||
|
|||||||
@ -11,11 +11,12 @@ import { NormalizedSchema } from './normalize-options';
|
|||||||
import {
|
import {
|
||||||
addExtendsToLintConfig,
|
addExtendsToLintConfig,
|
||||||
addIgnoresToLintConfig,
|
addIgnoresToLintConfig,
|
||||||
addOverrideToLintConfig,
|
addPredefinedConfigToFlatLintConfig,
|
||||||
isEslintConfigSupported,
|
isEslintConfigSupported,
|
||||||
updateOverrideInLintConfig,
|
updateOverrideInLintConfig,
|
||||||
} from '@nx/eslint/src/generators/utils/eslint-file';
|
} from '@nx/eslint/src/generators/utils/eslint-file';
|
||||||
import { eslintConfigNextVersion } from '../../../utils/versions';
|
import { eslintConfigNextVersion } from '../../../utils/versions';
|
||||||
|
import { useFlatConfig } from '@nx/eslint/src/utils/flat-config';
|
||||||
|
|
||||||
export async function addLinting(
|
export async function addLinting(
|
||||||
host: Tree,
|
host: Tree,
|
||||||
@ -39,11 +40,34 @@ export async function addLinting(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (options.linter === Linter.EsLint && isEslintConfigSupported(host)) {
|
if (options.linter === Linter.EsLint && isEslintConfigSupported(host)) {
|
||||||
addExtendsToLintConfig(host, options.appProjectRoot, [
|
if (useFlatConfig(host)) {
|
||||||
|
addPredefinedConfigToFlatLintConfig(
|
||||||
|
host,
|
||||||
|
options.appProjectRoot,
|
||||||
|
'flat/react-typescript'
|
||||||
|
);
|
||||||
|
// Since Next.js does not support flat configs yet, we need to use compat fixup.
|
||||||
|
const addExtendsTask = addExtendsToLintConfig(
|
||||||
|
host,
|
||||||
|
options.appProjectRoot,
|
||||||
|
[
|
||||||
|
{ name: 'next', needCompatFixup: true },
|
||||||
|
{ name: 'next/core-web-vitals', needCompatFixup: true },
|
||||||
|
]
|
||||||
|
);
|
||||||
|
tasks.push(addExtendsTask);
|
||||||
|
} else {
|
||||||
|
const addExtendsTask = addExtendsToLintConfig(
|
||||||
|
host,
|
||||||
|
options.appProjectRoot,
|
||||||
|
[
|
||||||
'plugin:@nx/react-typescript',
|
'plugin:@nx/react-typescript',
|
||||||
'next',
|
{ name: 'next', needCompatFixup: true },
|
||||||
'next/core-web-vitals',
|
{ name: 'next/core-web-vitals', needCompatFixup: true },
|
||||||
]);
|
]
|
||||||
|
);
|
||||||
|
tasks.push(addExtendsTask);
|
||||||
|
}
|
||||||
|
|
||||||
updateOverrideInLintConfig(
|
updateOverrideInLintConfig(
|
||||||
host,
|
host,
|
||||||
@ -65,15 +89,6 @@ export async function addLinting(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
// add jest specific config
|
|
||||||
if (options.unitTestRunner === 'jest') {
|
|
||||||
addOverrideToLintConfig(host, options.appProjectRoot, {
|
|
||||||
files: ['*.spec.ts', '*.spec.tsx', '*.spec.js', '*.spec.jsx'],
|
|
||||||
env: {
|
|
||||||
jest: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
addIgnoresToLintConfig(host, options.appProjectRoot, ['.next/**/*']);
|
addIgnoresToLintConfig(host, options.appProjectRoot, ['.next/**/*']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -433,8 +433,7 @@ describe('app', () => {
|
|||||||
|
|
||||||
expect(tree.read(`my-node-app/jest.config.ts`, 'utf-8'))
|
expect(tree.read(`my-node-app/jest.config.ts`, 'utf-8'))
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
"/* eslint-disable */
|
"export default {
|
||||||
export default {
|
|
||||||
displayName: 'my-node-app',
|
displayName: 'my-node-app',
|
||||||
preset: '../jest.preset.js',
|
preset: '../jest.preset.js',
|
||||||
testEnvironment: 'node',
|
testEnvironment: 'node',
|
||||||
@ -460,8 +459,7 @@ describe('app', () => {
|
|||||||
|
|
||||||
expect(tree.read(`my-node-app/jest.config.ts`, 'utf-8'))
|
expect(tree.read(`my-node-app/jest.config.ts`, 'utf-8'))
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
"/* eslint-disable */
|
"export default {
|
||||||
export default {
|
|
||||||
displayName: 'my-node-app',
|
displayName: 'my-node-app',
|
||||||
preset: '../jest.preset.js',
|
preset: '../jest.preset.js',
|
||||||
testEnvironment: 'node',
|
testEnvironment: 'node',
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable */
|
|
||||||
export default {
|
export default {
|
||||||
displayName: '<%= e2eProjectName %>',
|
displayName: '<%= e2eProjectName %>',
|
||||||
preset: '<%= offsetFromRoot %><%= jestPreset %>',
|
preset: '<%= offsetFromRoot %><%= jestPreset %>',
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable */
|
|
||||||
export default {
|
export default {
|
||||||
displayName: '<%= e2eProjectName %>',
|
displayName: '<%= e2eProjectName %>',
|
||||||
preset: '<%= offsetFromRoot %><%= jestPreset %>',
|
preset: '<%= offsetFromRoot %><%= jestPreset %>',
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`lib not nested should update configuration 1`] = `
|
exports[`lib not nested should update configuration 1`] = `
|
||||||
"/* eslint-disable */
|
"export default {
|
||||||
export default {
|
|
||||||
displayName: 'my-lib',
|
displayName: 'my-lib',
|
||||||
preset: '../jest.preset.js',
|
preset: '../jest.preset.js',
|
||||||
testEnvironment: 'node',
|
testEnvironment: 'node',
|
||||||
|
|||||||
@ -440,8 +440,7 @@ describe('lib', () => {
|
|||||||
|
|
||||||
expect(tree.read(`my-lib/jest.config.ts`, 'utf-8'))
|
expect(tree.read(`my-lib/jest.config.ts`, 'utf-8'))
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
"/* eslint-disable */
|
"export default {
|
||||||
export default {
|
|
||||||
displayName: 'my-lib',
|
displayName: 'my-lib',
|
||||||
preset: '../jest.preset.js',
|
preset: '../jest.preset.js',
|
||||||
testEnvironment: 'node',
|
testEnvironment: 'node',
|
||||||
|
|||||||
@ -31,6 +31,7 @@
|
|||||||
"buildTargets": ["build-base"],
|
"buildTargets": ["build-base"],
|
||||||
"ignoredDependencies": [
|
"ignoredDependencies": [
|
||||||
"nx",
|
"nx",
|
||||||
|
"eslint",
|
||||||
"typescript",
|
"typescript",
|
||||||
"@nx/cypress",
|
"@nx/cypress",
|
||||||
"@nx/playwright",
|
"@nx/playwright",
|
||||||
|
|||||||
@ -18,22 +18,49 @@ 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 1`] = `
|
exports[`app generated files content - as-provided - my-app general application should configure eslint correctly (eslintrc) 1`] = `
|
||||||
"{
|
"{
|
||||||
"extends": ["@nuxt/eslint-config", "../.eslintrc.json"],
|
"extends": ["@nuxt/eslint-config", "../.eslintrc.json"],
|
||||||
"ignorePatterns": ["!**/*", ".nuxt/**", ".output/**", "node_modules"],
|
"ignorePatterns": ["!**/*", ".nuxt/**", ".output/**", "node_modules"],
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"],
|
"files": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"],
|
||||||
"rules": {
|
"rules": {}
|
||||||
"vue/multi-word-component-names": "off"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`app generated files content - as-provided - my-app general application should configure eslint correctly (flat config) 1`] = `
|
||||||
|
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||||
|
const js = require('@eslint/js');
|
||||||
|
const baseConfig = require('../eslint.config.js');
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended,
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
...baseConfig,
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.vue'],
|
||||||
|
// Override or add rules here
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
...compat.extends('@nuxt/eslint-config'),
|
||||||
|
{
|
||||||
|
files: ['**/*.vue'],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: { parser: require('@typescript-eslint/parser') },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ ignores: ['.nuxt/**', '.output/**', 'node_modules'] },
|
||||||
|
];
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`app generated files content - as-provided - my-app general application should configure nuxt correctly 1`] = `
|
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 { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
import { defineNuxtConfig } from 'nuxt/config';
|
import { defineNuxtConfig } from 'nuxt/config';
|
||||||
@ -358,22 +385,49 @@ 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 1`] = `
|
exports[`app generated files content - as-provided - myApp general application should configure eslint correctly (eslintrc) 1`] = `
|
||||||
"{
|
"{
|
||||||
"extends": ["@nuxt/eslint-config", "../.eslintrc.json"],
|
"extends": ["@nuxt/eslint-config", "../.eslintrc.json"],
|
||||||
"ignorePatterns": ["!**/*", ".nuxt/**", ".output/**", "node_modules"],
|
"ignorePatterns": ["!**/*", ".nuxt/**", ".output/**", "node_modules"],
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"],
|
"files": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"],
|
||||||
"rules": {
|
"rules": {}
|
||||||
"vue/multi-word-component-names": "off"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`app generated files content - as-provided - myApp general application should configure eslint correctly (flat config) 1`] = `
|
||||||
|
"const { FlatCompat } = require('@eslint/eslintrc');
|
||||||
|
const js = require('@eslint/js');
|
||||||
|
const baseConfig = require('../eslint.config.js');
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended,
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
...baseConfig,
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.vue'],
|
||||||
|
// Override or add rules here
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
...compat.extends('@nuxt/eslint-config'),
|
||||||
|
{
|
||||||
|
files: ['**/*.vue'],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: { parser: require('@typescript-eslint/parser') },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ ignores: ['.nuxt/**', '.output/**', 'node_modules'] },
|
||||||
|
];
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`app generated files content - as-provided - myApp general application should configure nuxt correctly 1`] = `
|
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 { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
import { defineNuxtConfig } from 'nuxt/config';
|
import { defineNuxtConfig } from 'nuxt/config';
|
||||||
|
|||||||
@ -13,14 +13,15 @@ describe('app', () => {
|
|||||||
describe('general application', () => {
|
describe('general application', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
tree = createTreeWithEmptyWorkspace();
|
tree = createTreeWithEmptyWorkspace();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not add targets', async () => {
|
||||||
await applicationGenerator(tree, {
|
await applicationGenerator(tree, {
|
||||||
name,
|
name,
|
||||||
projectNameAndRootFormat: 'as-provided',
|
projectNameAndRootFormat: 'as-provided',
|
||||||
unitTestRunner: 'vitest',
|
unitTestRunner: 'vitest',
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it('should not add targets', async () => {
|
|
||||||
const projectConfig = readProjectConfiguration(tree, name);
|
const projectConfig = readProjectConfiguration(tree, name);
|
||||||
expect(projectConfig.targets.build).toBeUndefined();
|
expect(projectConfig.targets.build).toBeUndefined();
|
||||||
expect(projectConfig.targets.serve).toBeUndefined();
|
expect(projectConfig.targets.serve).toBeUndefined();
|
||||||
@ -30,27 +31,71 @@ describe('app', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should create all new files in the correct location', async () => {
|
it('should create all new files in the correct location', async () => {
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name,
|
||||||
|
projectNameAndRootFormat: 'as-provided',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
});
|
||||||
|
|
||||||
const newFiles = tree.listChanges().map((change) => change.path);
|
const newFiles = tree.listChanges().map((change) => change.path);
|
||||||
expect(newFiles).toMatchSnapshot();
|
expect(newFiles).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add nuxt entries in .gitignore', () => {
|
it('should add nuxt entries in .gitignore', async () => {
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name,
|
||||||
|
projectNameAndRootFormat: 'as-provided',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
});
|
||||||
|
|
||||||
expect(tree.read('.gitignore', 'utf-8')).toMatchSnapshot();
|
expect(tree.read('.gitignore', 'utf-8')).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should configure nuxt correctly', () => {
|
it('should configure nuxt correctly', async () => {
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name,
|
||||||
|
projectNameAndRootFormat: 'as-provided',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
});
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
tree.read(`${name}/nuxt.config.ts`, 'utf-8')
|
tree.read(`${name}/nuxt.config.ts`, 'utf-8')
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should configure eslint correctly', () => {
|
it('should configure eslint correctly (flat config)', async () => {
|
||||||
|
tree.write('eslint.config.js', '');
|
||||||
|
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name,
|
||||||
|
projectNameAndRootFormat: 'as-provided',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
tree.read(`${name}/eslint.config.js`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should configure eslint correctly (eslintrc)', async () => {
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name,
|
||||||
|
projectNameAndRootFormat: 'as-provided',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
});
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
tree.read(`${name}/.eslintrc.json`, 'utf-8')
|
tree.read(`${name}/.eslintrc.json`, 'utf-8')
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should configure vitest correctly', () => {
|
it('should configure vitest correctly', async () => {
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name,
|
||||||
|
projectNameAndRootFormat: 'as-provided',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
});
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
tree.read(`${name}/vitest.config.ts`, 'utf-8')
|
tree.read(`${name}/vitest.config.ts`, 'utf-8')
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
@ -62,12 +107,24 @@ describe('app', () => {
|
|||||||
expect(packageJson.devDependencies['vitest']).toEqual('^1.3.1');
|
expect(packageJson.devDependencies['vitest']).toEqual('^1.3.1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should configure tsconfig and project.json correctly', () => {
|
it('should configure tsconfig and project.json correctly', async () => {
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name,
|
||||||
|
projectNameAndRootFormat: 'as-provided',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
});
|
||||||
|
|
||||||
expect(tree.read(`${name}/project.json`, 'utf-8')).toMatchSnapshot();
|
expect(tree.read(`${name}/project.json`, 'utf-8')).toMatchSnapshot();
|
||||||
expect(tree.read(`${name}/tsconfig.json`, 'utf-8')).toMatchSnapshot();
|
expect(tree.read(`${name}/tsconfig.json`, 'utf-8')).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add the nuxt and vitest plugins', () => {
|
it('should add the nuxt and vitest plugins', async () => {
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name,
|
||||||
|
projectNameAndRootFormat: 'as-provided',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
});
|
||||||
|
|
||||||
const nxJson = readJson(tree, 'nx.json');
|
const nxJson = readJson(tree, 'nx.json');
|
||||||
expect(nxJson.plugins).toMatchObject([
|
expect(nxJson.plugins).toMatchObject([
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,15 +1,25 @@
|
|||||||
import { Tree } from 'nx/src/generators/tree';
|
import { Tree } from 'nx/src/generators/tree';
|
||||||
import { lintProjectGenerator, Linter, LinterType } from '@nx/eslint';
|
import type { Linter as EsLintLinter } from 'eslint';
|
||||||
|
import { Linter, LinterType, lintProjectGenerator } from '@nx/eslint';
|
||||||
import { joinPathFragments } from 'nx/src/utils/path';
|
import { joinPathFragments } from 'nx/src/utils/path';
|
||||||
import {
|
import {
|
||||||
GeneratorCallback,
|
|
||||||
addDependenciesToPackageJson,
|
addDependenciesToPackageJson,
|
||||||
|
GeneratorCallback,
|
||||||
runTasksInSerial,
|
runTasksInSerial,
|
||||||
updateJson,
|
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { editEslintConfigFiles } from '@nx/vue';
|
import {
|
||||||
|
addExtendsToLintConfig,
|
||||||
|
addIgnoresToLintConfig,
|
||||||
|
addOverrideToLintConfig,
|
||||||
|
isEslintConfigSupported,
|
||||||
|
lintConfigHasOverride,
|
||||||
|
replaceOverridesInLintConfig,
|
||||||
|
updateOverrideInLintConfig,
|
||||||
|
} from '@nx/eslint/src/generators/utils/eslint-file';
|
||||||
import { nuxtEslintConfigVersion } from './versions';
|
import { nuxtEslintConfigVersion } from './versions';
|
||||||
|
import { useFlatConfig } from '@nx/eslint/src/utils/flat-config';
|
||||||
|
|
||||||
|
// TODO(colum): Look into the recommended set up using `withNuxt` inside eslint.config.mjs. https://eslint.nuxt.com/packages/config
|
||||||
export async function addLinting(
|
export async function addLinting(
|
||||||
host: Tree,
|
host: Tree,
|
||||||
options: {
|
options: {
|
||||||
@ -33,30 +43,36 @@ export async function addLinting(
|
|||||||
});
|
});
|
||||||
tasks.push(lintTask);
|
tasks.push(lintTask);
|
||||||
|
|
||||||
|
if (isEslintConfigSupported(host, options.projectRoot)) {
|
||||||
editEslintConfigFiles(host, options.projectRoot);
|
editEslintConfigFiles(host, options.projectRoot);
|
||||||
|
|
||||||
updateJson(
|
const addExtendsTask = addExtendsToLintConfig(
|
||||||
host,
|
host,
|
||||||
joinPathFragments(options.projectRoot, '.eslintrc.json'),
|
options.projectRoot,
|
||||||
(json) => {
|
['@nuxt/eslint-config'],
|
||||||
const {
|
true
|
||||||
extends: pluginExtends,
|
);
|
||||||
ignorePatterns: pluginIgnorePatters,
|
tasks.push(addExtendsTask);
|
||||||
...config
|
|
||||||
} = json;
|
|
||||||
|
|
||||||
return {
|
if (useFlatConfig(host)) {
|
||||||
extends: ['@nuxt/eslint-config', ...(pluginExtends || [])],
|
addOverrideToLintConfig(
|
||||||
ignorePatterns: [
|
host,
|
||||||
...(pluginIgnorePatters || []),
|
options.projectRoot,
|
||||||
|
{
|
||||||
|
files: ['**/*.vue'],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: { parser: '@typescript-eslint/parser' },
|
||||||
|
},
|
||||||
|
} as unknown // languageOptions is not in eslintrc format but for flat config
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addIgnoresToLintConfig(host, options.projectRoot, [
|
||||||
'.nuxt/**',
|
'.nuxt/**',
|
||||||
'.output/**',
|
'.output/**',
|
||||||
'node_modules',
|
'node_modules',
|
||||||
],
|
]);
|
||||||
...config,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
const installTask = addDependenciesToPackageJson(
|
const installTask = addDependenciesToPackageJson(
|
||||||
host,
|
host,
|
||||||
@ -69,3 +85,68 @@ export async function addLinting(
|
|||||||
}
|
}
|
||||||
return runTasksInSerial(...tasks);
|
return runTasksInSerial(...tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function editEslintConfigFiles(tree: Tree, projectRoot: string) {
|
||||||
|
const hasVueFiles = (
|
||||||
|
o: EsLintLinter.ConfigOverride<EsLintLinter.RulesRecord>
|
||||||
|
) =>
|
||||||
|
o.files &&
|
||||||
|
(Array.isArray(o.files)
|
||||||
|
? o.files.some((f) => f.endsWith('*.vue'))
|
||||||
|
: o.files.endsWith('*.vue'));
|
||||||
|
const addVueFiles = (
|
||||||
|
o: EsLintLinter.ConfigOverride<EsLintLinter.RulesRecord>
|
||||||
|
) => {
|
||||||
|
if (!o.files) {
|
||||||
|
o.files = ['*.vue'];
|
||||||
|
} else if (Array.isArray(o.files)) {
|
||||||
|
o.files.push('*.vue');
|
||||||
|
} else {
|
||||||
|
o.files = [o.files, '*.vue'];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (
|
||||||
|
lintConfigHasOverride(
|
||||||
|
tree,
|
||||||
|
projectRoot,
|
||||||
|
(o) => o.parserOptions && !hasVueFiles(o),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
updateOverrideInLintConfig(
|
||||||
|
tree,
|
||||||
|
projectRoot,
|
||||||
|
(o) => !!o.parserOptions,
|
||||||
|
(o) => {
|
||||||
|
addVueFiles(o);
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
replaceOverridesInLintConfig(tree, projectRoot, [
|
||||||
|
{
|
||||||
|
files: ['*.ts', '*.tsx', '*.js', '*.jsx', '*.vue'],
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
lintConfigHasOverride(
|
||||||
|
tree,
|
||||||
|
'',
|
||||||
|
(o) => o.rules?.['@nx/enforce-module-boundaries'] && !hasVueFiles(o),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
updateOverrideInLintConfig(
|
||||||
|
tree,
|
||||||
|
'',
|
||||||
|
(o) => !!o.rules?.['@nx/enforce-module-boundaries'],
|
||||||
|
(o) => {
|
||||||
|
addVueFiles(o);
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -7,4 +7,4 @@ export const nuxtDevtoolsVersion = '1.0.0';
|
|||||||
export const nuxtUiTemplatesVersion = '^1.3.1';
|
export const nuxtUiTemplatesVersion = '^1.3.1';
|
||||||
|
|
||||||
// linting deps
|
// linting deps
|
||||||
export const nuxtEslintConfigVersion = '~0.3.6';
|
export const nuxtEslintConfigVersion = '~0.5.6';
|
||||||
|
|||||||
@ -13,9 +13,11 @@ import {
|
|||||||
addExtendsToLintConfig,
|
addExtendsToLintConfig,
|
||||||
addOverrideToLintConfig,
|
addOverrideToLintConfig,
|
||||||
addPluginsToLintConfig,
|
addPluginsToLintConfig,
|
||||||
|
addPredefinedConfigToFlatLintConfig,
|
||||||
findEslintFile,
|
findEslintFile,
|
||||||
isEslintConfigSupported,
|
isEslintConfigSupported,
|
||||||
} from '@nx/eslint/src/generators/utils/eslint-file';
|
} from '@nx/eslint/src/generators/utils/eslint-file';
|
||||||
|
import { useFlatConfig } from '@nx/eslint/src/utils/flat-config';
|
||||||
|
|
||||||
export interface PlaywrightLinterOptions {
|
export interface PlaywrightLinterOptions {
|
||||||
project: string;
|
project: string;
|
||||||
@ -76,11 +78,28 @@ export async function addLinterToPlaywrightProject(
|
|||||||
isEslintConfigSupported(tree, projectConfig.root) ||
|
isEslintConfigSupported(tree, projectConfig.root) ||
|
||||||
isEslintConfigSupported(tree)
|
isEslintConfigSupported(tree)
|
||||||
) {
|
) {
|
||||||
addExtendsToLintConfig(
|
if (useFlatConfig(tree)) {
|
||||||
|
addPredefinedConfigToFlatLintConfig(
|
||||||
|
tree,
|
||||||
|
projectConfig.root,
|
||||||
|
'flat/recommended',
|
||||||
|
'playwright',
|
||||||
|
'eslint-plugin-playwright',
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
addOverrideToLintConfig(tree, projectConfig.root, {
|
||||||
|
files: ['*.ts', '*.js'],
|
||||||
|
rules: {},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const addExtendsTask = addExtendsToLintConfig(
|
||||||
tree,
|
tree,
|
||||||
projectConfig.root,
|
projectConfig.root,
|
||||||
'plugin:playwright/recommended'
|
'plugin:playwright/recommended'
|
||||||
);
|
);
|
||||||
|
tasks.push(addExtendsTask);
|
||||||
|
|
||||||
if (options.rootProject) {
|
if (options.rootProject) {
|
||||||
addPluginsToLintConfig(tree, projectConfig.root, '@nx');
|
addPluginsToLintConfig(tree, projectConfig.root, '@nx');
|
||||||
addOverrideToLintConfig(tree, projectConfig.root, javaScriptOverride);
|
addOverrideToLintConfig(tree, projectConfig.root, javaScriptOverride);
|
||||||
@ -95,6 +114,7 @@ export async function addLinterToPlaywrightProject(
|
|||||||
rules: {},
|
rules: {},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return runTasksInSerial(...tasks);
|
return runTasksInSerial(...tasks);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
export const nxVersion = require('../../package.json').version;
|
export const nxVersion = require('../../package.json').version;
|
||||||
export const playwrightVersion = '^1.36.0';
|
export const playwrightVersion = '^1.36.0';
|
||||||
export const eslintPluginPlaywrightVersion = '^0.15.3';
|
export const eslintPluginPlaywrightVersion = '^1.6.2';
|
||||||
|
|||||||
@ -156,7 +156,14 @@ describe('lint-checks generator', () => {
|
|||||||
],
|
],
|
||||||
"parser": "jsonc-eslint-parser",
|
"parser": "jsonc-eslint-parser",
|
||||||
"rules": {
|
"rules": {
|
||||||
"@nx/dependency-checks": "error",
|
"@nx/dependency-checks": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"ignoredFiles": [
|
||||||
|
"{projectRoot}/eslint.config.{js,cjs,mjs}",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
readProjectConfiguration,
|
readProjectConfiguration,
|
||||||
TargetConfiguration,
|
TargetConfiguration,
|
||||||
Tree,
|
Tree,
|
||||||
|
updateJson,
|
||||||
updateProjectConfiguration,
|
updateProjectConfiguration,
|
||||||
writeJson,
|
writeJson,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
@ -113,12 +114,23 @@ export function addMigrationJsonChecks(
|
|||||||
fileSet.add(relativeMigrationsJsonPath);
|
fileSet.add(relativeMigrationsJsonPath);
|
||||||
return {
|
return {
|
||||||
...o,
|
...o,
|
||||||
files: Array.from(fileSet),
|
files: formatFilesEntries(host, Array.from(fileSet)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatFilesEntries(tree: Tree, files: string[]): string[] {
|
||||||
|
if (!useFlatConfig(tree)) {
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
const filesAfter = files.map((f) => {
|
||||||
|
const after = f.startsWith('./') ? f.replace('./', '**/') : f;
|
||||||
|
return after;
|
||||||
|
});
|
||||||
|
return filesAfter;
|
||||||
|
}
|
||||||
|
|
||||||
function updateProjectTarget(
|
function updateProjectTarget(
|
||||||
host: Tree,
|
host: Tree,
|
||||||
options: PluginLintChecksGeneratorSchema,
|
options: PluginLintChecksGeneratorSchema,
|
||||||
@ -199,12 +211,12 @@ function updateProjectEslintConfig(
|
|||||||
// update it
|
// update it
|
||||||
updateOverrideInLintConfig(host, options.root, lookup, (o) => ({
|
updateOverrideInLintConfig(host, options.root, lookup, (o) => ({
|
||||||
...o,
|
...o,
|
||||||
files: [
|
files: formatFilesEntries(host, [
|
||||||
...new Set([
|
...new Set([
|
||||||
...(Array.isArray(o.files) ? o.files : [o.files]),
|
...(Array.isArray(o.files) ? o.files : [o.files]),
|
||||||
...files,
|
...files,
|
||||||
]),
|
]),
|
||||||
],
|
]),
|
||||||
...parser,
|
...parser,
|
||||||
rules: {
|
rules: {
|
||||||
...o.rules,
|
...o.rules,
|
||||||
@ -214,7 +226,7 @@ function updateProjectEslintConfig(
|
|||||||
} else {
|
} else {
|
||||||
// add it
|
// add it
|
||||||
addOverrideToLintConfig(host, options.root, {
|
addOverrideToLintConfig(host, options.root, {
|
||||||
files,
|
files: formatFilesEntries(host, files),
|
||||||
...parser,
|
...parser,
|
||||||
rules: {
|
rules: {
|
||||||
'@nx/nx-plugin-checks': 'error',
|
'@nx/nx-plugin-checks': 'error',
|
||||||
|
|||||||
@ -9,8 +9,11 @@ import { extraEslintDependencies } from '@nx/react/src/utils/lint';
|
|||||||
import {
|
import {
|
||||||
addExtendsToLintConfig,
|
addExtendsToLintConfig,
|
||||||
addIgnoresToLintConfig,
|
addIgnoresToLintConfig,
|
||||||
|
addOverrideToLintConfig,
|
||||||
|
addPredefinedConfigToFlatLintConfig,
|
||||||
isEslintConfigSupported,
|
isEslintConfigSupported,
|
||||||
} from '@nx/eslint/src/generators/utils/eslint-file';
|
} from '@nx/eslint/src/generators/utils/eslint-file';
|
||||||
|
import { useFlatConfig } from '@nx/eslint/src/utils/flat-config';
|
||||||
|
|
||||||
interface NormalizedSchema {
|
interface NormalizedSchema {
|
||||||
linter?: Linter | LinterType;
|
linter?: Linter | LinterType;
|
||||||
@ -40,7 +43,24 @@ export async function addLinting(host: Tree, options: NormalizedSchema) {
|
|||||||
tasks.push(lintTask);
|
tasks.push(lintTask);
|
||||||
|
|
||||||
if (isEslintConfigSupported(host)) {
|
if (isEslintConfigSupported(host)) {
|
||||||
addExtendsToLintConfig(host, options.projectRoot, 'plugin:@nx/react');
|
if (useFlatConfig(host)) {
|
||||||
|
addPredefinedConfigToFlatLintConfig(
|
||||||
|
host,
|
||||||
|
options.projectRoot,
|
||||||
|
'flat/react'
|
||||||
|
);
|
||||||
|
// Add an empty rules object to users know how to add/override rules
|
||||||
|
addOverrideToLintConfig(host, options.projectRoot, {
|
||||||
|
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
||||||
|
rules: {},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const addExtendsTask = addExtendsToLintConfig(host, options.projectRoot, {
|
||||||
|
name: 'plugin:@nx/react',
|
||||||
|
needCompatFixup: true,
|
||||||
|
});
|
||||||
|
tasks.push(addExtendsTask);
|
||||||
|
}
|
||||||
addIgnoresToLintConfig(host, options.projectRoot, [
|
addIgnoresToLintConfig(host, options.projectRoot, [
|
||||||
'public',
|
'public',
|
||||||
'.cache',
|
'.cache',
|
||||||
|
|||||||
@ -38,11 +38,14 @@ import { showPossibleWarnings } from './lib/show-possible-warnings';
|
|||||||
import { addE2e } from './lib/add-e2e';
|
import { addE2e } from './lib/add-e2e';
|
||||||
import {
|
import {
|
||||||
addExtendsToLintConfig,
|
addExtendsToLintConfig,
|
||||||
|
addOverrideToLintConfig,
|
||||||
|
addPredefinedConfigToFlatLintConfig,
|
||||||
isEslintConfigSupported,
|
isEslintConfigSupported,
|
||||||
} from '@nx/eslint/src/generators/utils/eslint-file';
|
} from '@nx/eslint/src/generators/utils/eslint-file';
|
||||||
import { initGenerator as jsInitGenerator } from '@nx/js';
|
import { initGenerator as jsInitGenerator } from '@nx/js';
|
||||||
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
|
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
|
||||||
import { setupTailwindGenerator } from '../setup-tailwind/setup-tailwind';
|
import { setupTailwindGenerator } from '../setup-tailwind/setup-tailwind';
|
||||||
|
import { useFlatConfig } from '@nx/eslint/src/utils/flat-config';
|
||||||
|
|
||||||
async function addLinting(host: Tree, options: NormalizedSchema) {
|
async function addLinting(host: Tree, options: NormalizedSchema) {
|
||||||
const tasks: GeneratorCallback[] = [];
|
const tasks: GeneratorCallback[] = [];
|
||||||
@ -62,7 +65,25 @@ async function addLinting(host: Tree, options: NormalizedSchema) {
|
|||||||
tasks.push(lintTask);
|
tasks.push(lintTask);
|
||||||
|
|
||||||
if (isEslintConfigSupported(host)) {
|
if (isEslintConfigSupported(host)) {
|
||||||
addExtendsToLintConfig(host, options.appProjectRoot, 'plugin:@nx/react');
|
if (useFlatConfig(host)) {
|
||||||
|
addPredefinedConfigToFlatLintConfig(
|
||||||
|
host,
|
||||||
|
options.appProjectRoot,
|
||||||
|
'flat/react'
|
||||||
|
);
|
||||||
|
// Add an empty rules object to users know how to add/override rules
|
||||||
|
addOverrideToLintConfig(host, options.appProjectRoot, {
|
||||||
|
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
||||||
|
rules: {},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const addExtendsTask = addExtendsToLintConfig(
|
||||||
|
host,
|
||||||
|
options.appProjectRoot,
|
||||||
|
{ name: 'plugin:@nx/react', needCompatFixup: true }
|
||||||
|
);
|
||||||
|
tasks.push(addExtendsTask);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.skipPackageJson) {
|
if (!options.skipPackageJson) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user