Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/eslint-plugin",
"comment": "Fix calculation of project root folder when using the ESLint extension in VS Code. Report the paths being compared in 'no-external-local-imports' rule violations.",
"type": "patch"
}
],
"packageName": "@rushstack/eslint-plugin"
}
38 changes: 28 additions & 10 deletions eslint/eslint-plugin/src/LintUtilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import * as path from 'node:path';

import { ESLintUtils, TSESTree, type TSESLint } from '@typescript-eslint/utils';
import type { Program } from 'typescript';
import type { CompilerOptions, Program } from 'typescript';

export interface IParsedImportSpecifier {
loader?: string;
Expand Down Expand Up @@ -33,23 +33,41 @@ export function getFilePathFromContext(context: TSESLint.RuleContext<string, unk
export function getRootDirectoryFromContext(
context: TSESLint.RuleContext<string, unknown[]>
): string | undefined {
let rootDirectory: string | undefined;
/*
* Precedence of root directory resolution:
* 1. parserOptions.tsconfigRootDir if available (since set by repo maintainer)
* 2. tsconfig.json directory if available (but might be in a subfolder)
* 3. TS Program current directory if available
* 4. ESLint working directory (probably wrong, but better than nothing?)
*/
const tsConfigRootDir: string | undefined = context.parserOptions?.tsconfigRootDir;
if (tsConfigRootDir) {
return tsConfigRootDir;
}

try {
// First attempt to get the root directory from the tsconfig baseUrl, then the program current directory
const program: Program | null | undefined = (
context.sourceCode?.parserServices ?? ESLintUtils.getParserServices(context)
).program;
rootDirectory = program?.getCompilerOptions().baseUrl ?? program?.getCurrentDirectory();
const compilerOptions: CompilerOptions | undefined = program?.getCompilerOptions();

const tsConfigPath: string | undefined = compilerOptions?.configFilePath as string | undefined;
if (tsConfigPath) {
const tsConfigDir: string = path.dirname(tsConfigPath);
return tsConfigDir;
}

// Next, try to get the current directory from the TS program
const rootDirectory: string | undefined = program?.getCurrentDirectory();
if (rootDirectory) {
return rootDirectory;
}
} catch {
// Ignore the error if we cannot retrieve a TS program
}

// Fall back to the parserOptions.tsconfigRootDir if available, otherwise the eslint working directory
if (!rootDirectory) {
rootDirectory = context.parserOptions?.tsconfigRootDir ?? context.getCwd?.();
}

return rootDirectory;
// Last resort: use ESLint's current working directory
return context.getCwd?.();
}

export function parseImportSpecifierFromExpression(
Expand Down
16 changes: 10 additions & 6 deletions eslint/eslint-plugin/src/no-external-local-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ export const noExternalLocalImportsRule: RuleModule = {
type: 'problem',
messages: {
[MESSAGE_ID]:
'The specified import target is not under the root directory. Ensure that ' +
'all local import targets are either under the "rootDir" specified in your tsconfig.json (if one ' +
'exists) or under the package directory.'
'The specified import target "{{ importAbsolutePath }}" is not under the root directory, "{{ rootDirectory }}". Ensure that ' +
'all local import targets are either under the "parserOptions.tsconfigRootDir" specified in your eslint.config.js (if one ' +
'exists) or else under the folder that contains your tsconfig.json.'
},
schema: [],
docs: {
description:
'Prevents referencing relative imports that are either not under the "rootDir" specified in ' +
'the tsconfig.json (if one exists) or not under the package directory.',
'Prevents referencing relative imports that are either not under the "parserOptions.tsconfigRootDir" specified in ' +
'your eslint.config.js (if one exists) or else not under the folder that contains your tsconfig.json.',
url: 'https://www.npmjs.com/package/@rushstack/eslint-plugin'
}
},
Expand All @@ -54,7 +54,11 @@ export const noExternalLocalImportsRule: RuleModule = {

const relativePathToRoot: string = path.relative(importAbsolutePath, rootDirectory);
if (!_relativePathRegex.test(relativePathToRoot)) {
context.report({ node: importExpression, messageId: MESSAGE_ID });
context.report({
node: importExpression,
messageId: MESSAGE_ID,
data: { importAbsolutePath, rootDirectory }
});
}
};

Expand Down
Loading