Skip to content

Commit

Permalink
fix(linter): update eslint config lookup to correctly handle configs …
Browse files Browse the repository at this point in the history
…at the root (#26508)
  • Loading branch information
leosvelperez authored Jun 11, 2024
1 parent 0018842 commit 0a4551c
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 12 deletions.
60 changes: 60 additions & 0 deletions packages/eslint/src/executors/lint/lint.impl.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,36 @@ Occurred while linting ${mockContext.root}/apps/proj/src/some-file.ts`
Error: You have attempted to use the lint rule "@typescript-eslint/await-thenable" which requires the full TypeScript type-checker to be available, but you do not have "parserOptions.project" configured to point at your project tsconfig.json files in the relevant TypeScript file "overrides" block of your ESLint config "apps/proj/eslint.config.js"
Occurred while linting ${mockContext.root}/apps/proj/src/some-file.ts
Please see https://nx.dev/recipes/tips-n-tricks/eslint for full guidance on how to resolve this issue.
`
);
});

it('should intercept the error from `@typescript-eslint` regarding missing parserServices and provide a more detailed user-facing message logging the found flat config at the workspace root', async () => {
setupMocks();
tempFs.createFileSync('eslint.config.js', '');
tempFs.createFileSync('apps/proj/src/some-file.ts', '');

mockLintFiles.mockImplementation(() => {
throw new Error(
`Error while loading rule '@typescript-eslint/await-thenable': You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.
Occurred while linting ${mockContext.root}/apps/proj/src/some-file.ts`
);
});

await lintExecutor(
createValidRunBuilderOptions({
lintFilePatterns: ['includedFile1'],
format: 'json',
silent: false,
}),
mockContext
);
expect(console.error).toHaveBeenCalledWith(
`
Error: You have attempted to use the lint rule "@typescript-eslint/await-thenable" which requires the full TypeScript type-checker to be available, but you do not have "parserOptions.project" configured to point at your project tsconfig.json files in the relevant TypeScript file "overrides" block of your ESLint config "eslint.config.js"
Occurred while linting ${mockContext.root}/apps/proj/src/some-file.ts
Please see https://nx.dev/recipes/tips-n-tricks/eslint for full guidance on how to resolve this issue.
`
);
Expand Down Expand Up @@ -473,6 +503,36 @@ Occurred while linting ${mockContext.root}/apps/proj/src/some-file.ts`
Error: You have attempted to use the lint rule "@typescript-eslint/await-thenable" which requires the full TypeScript type-checker to be available, but you do not have "parserOptions.project" configured to point at your project tsconfig.json files in the relevant TypeScript file "overrides" block of your ESLint config "apps/proj/.eslintrc.json"
Occurred while linting ${mockContext.root}/apps/proj/src/some-file.ts
Please see https://nx.dev/recipes/tips-n-tricks/eslint for full guidance on how to resolve this issue.
`
);
});

it('should intercept the error from `@typescript-eslint` regarding missing parserServices and provide a more detailed user-facing message logging the found old config at the workspace root', async () => {
setupMocks();
tempFs.createFileSync('.eslintrc.json', '{}');
tempFs.createFileSync('apps/proj/src/some-file.ts', '');

mockLintFiles.mockImplementation(() => {
throw new Error(
`Error while loading rule '@typescript-eslint/await-thenable': You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.
Occurred while linting ${mockContext.root}/apps/proj/src/some-file.ts`
);
});

await lintExecutor(
createValidRunBuilderOptions({
lintFilePatterns: ['includedFile1'],
format: 'json',
silent: false,
}),
mockContext
);
expect(console.error).toHaveBeenCalledWith(
`
Error: You have attempted to use the lint rule "@typescript-eslint/await-thenable" which requires the full TypeScript type-checker to be available, but you do not have "parserOptions.project" configured to point at your project tsconfig.json files in the relevant TypeScript file "overrides" block of your ESLint config ".eslintrc.json"
Occurred while linting ${mockContext.root}/apps/proj/src/some-file.ts
Please see https://nx.dev/recipes/tips-n-tricks/eslint for full guidance on how to resolve this issue.
`
);
Expand Down
106 changes: 106 additions & 0 deletions packages/eslint/src/utils/config-file.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { TempFs } from '@nx/devkit/internal-testing-utils';
import { join } from 'path';
import {
ESLINT_FLAT_CONFIG_FILENAMES,
ESLINT_OLD_CONFIG_FILENAMES,
findFlatConfigFile,
findOldConfigFile,
} from './config-file';

describe('config-file', () => {
let fs: TempFs;

beforeEach(() => {
fs = new TempFs('eslint-config-file');
});

afterEach(() => {
fs.cleanup();
});

describe('findFlatConfigFile', () => {
it.each(ESLINT_FLAT_CONFIG_FILENAMES)(
'should find flat config file "%s" when it exists in the directory',
(configFile) => {
fs.createFilesSync({
[`libs/lib1/${configFile}`]: '',
});

const result = findFlatConfigFile('libs/lib1', fs.tempDir);

expect(result).toEqual(join(fs.tempDir, `libs/lib1/${configFile}`));
}
);

it.each(ESLINT_FLAT_CONFIG_FILENAMES)(
'should find flat config file "%s" when it exists in a parent directory',
(configFile) => {
fs.createFilesSync({
[`libs/${configFile}`]: '',
'libs/lib1/src/index.ts': '',
});

const result = findFlatConfigFile('libs/lib1', fs.tempDir);

expect(result).toEqual(join(fs.tempDir, `libs/${configFile}`));
}
);

it.each(ESLINT_FLAT_CONFIG_FILENAMES)(
'should find flat config file "%s" when it exists at the workspace root',
(configFile) => {
fs.createFilesSync({
[configFile]: '',
'libs/lib1/src/index.ts': '',
});

const result = findFlatConfigFile('libs/lib1', fs.tempDir);

expect(result).toEqual(join(fs.tempDir, configFile));
}
);
});

describe('findOldConfigFile', () => {
it.each(ESLINT_OLD_CONFIG_FILENAMES)(
'should find flat config file "%s" when it exists in the directory',
(configFile) => {
fs.createFilesSync({
[`libs/lib1/${configFile}`]: '',
});

const result = findOldConfigFile('libs/lib1', fs.tempDir);

expect(result).toEqual(join(fs.tempDir, `libs/lib1/${configFile}`));
}
);

it.each(ESLINT_OLD_CONFIG_FILENAMES)(
'should find flat config file "%s" when it exists in a parent directory',
(configFile) => {
fs.createFilesSync({
[`libs/${configFile}`]: '',
'libs/lib1/src/index.ts': '',
});

const result = findOldConfigFile('libs/lib1', fs.tempDir);

expect(result).toEqual(join(fs.tempDir, `libs/${configFile}`));
}
);

it.each(ESLINT_OLD_CONFIG_FILENAMES)(
'should find flat config file "%s" when it exists at the workspace root',
(configFile) => {
fs.createFilesSync({
[configFile]: '',
'libs/lib1/src/index.ts': '',
});

const result = findOldConfigFile('libs/lib1', fs.tempDir);

expect(result).toEqual(join(fs.tempDir, configFile));
}
);
});
});
23 changes: 11 additions & 12 deletions packages/eslint/src/utils/config-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,17 @@ export function findFlatConfigFile(
): string | null {
let currentDir = resolve(workspaceRoot, directory);

if (currentDir === workspaceRoot) {
return getConfigFileInDirectory(currentDir, ESLINT_FLAT_CONFIG_FILENAMES);
}

while (currentDir !== workspaceRoot) {
while (true) {
const configFilePath = getConfigFileInDirectory(
currentDir,
ESLINT_FLAT_CONFIG_FILENAMES
);
if (configFilePath) {
return configFilePath;
}
if (currentDir === workspaceRoot) {
break;
}
currentDir = dirname(currentDir);
}

Expand All @@ -56,22 +55,22 @@ export function findOldConfigFile(
filePathOrDirectory: string,
workspaceRoot: string
): string | null {
let currentDir = statSync(filePathOrDirectory).isDirectory()
? filePathOrDirectory
: dirname(filePathOrDirectory);

if (currentDir === workspaceRoot) {
return getConfigFileInDirectory(currentDir, ESLINT_OLD_CONFIG_FILENAMES);
let currentDir = resolve(workspaceRoot, filePathOrDirectory);
if (!statSync(currentDir).isDirectory()) {
currentDir = dirname(currentDir);
}

while (currentDir !== workspaceRoot) {
while (true) {
const configFilePath = getConfigFileInDirectory(
currentDir,
ESLINT_OLD_CONFIG_FILENAMES
);
if (configFilePath) {
return configFilePath;
}
if (currentDir === workspaceRoot) {
break;
}
currentDir = dirname(currentDir);
}

Expand Down

0 comments on commit 0a4551c

Please sign in to comment.