Skip to content

Commit b27a65e

Browse files
Support realpaths with config (#2068)
* Support realpaths with true-defaulting config * Add to package.json configuration * Default to false --------- Co-authored-by: Dirk Bäumer <[email protected]>
1 parent 55d983e commit b27a65e

File tree

6 files changed

+44
-29
lines changed

6 files changed

+44
-29
lines changed

$shared/settings.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ export type ConfigurationSettings = {
168168
packageManager: PackageManagers;
169169
useESLintClass: boolean;
170170
useFlatConfig?: boolean | undefined;
171+
useRealpaths: boolean;
171172
experimental?: {
172173
useFlatConfig: boolean;
173174
};

client/src/client.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,7 @@ export namespace ESLintClient {
678678
packageManager: config.get<PackageManagers>('packageManager', 'npm'),
679679
useESLintClass: config.get<boolean>('useESLintClass', false),
680680
useFlatConfig: useFlatConfig === null ? undefined : useFlatConfig,
681+
useRealpaths: config.get<boolean>('useRealpaths', false),
681682
experimental: {
682683
useFlatConfig: config.get<boolean>('experimental.useFlatConfig', false),
683684
},
@@ -962,7 +963,7 @@ export namespace ESLintClient {
962963
}
963964

964965
if (detail !== undefined && languageStatus.detail !== detail) {
965-
languageStatus.detail = detail;
966+
languageStatus.detail = detail;
966967
}
967968
if (languageStatus.severity !== severity) {
968969
languageStatus.severity = severity;

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,12 @@
196196
"deprecationMessage": "Use ESLint version 8.57 or later and `eslint.useFlatConfig` instead.",
197197
"description": "Enables support of experimental Flat Config (aka eslint.config.js). Requires ESLint version >= 8.21 < 8.57.0)."
198198
},
199+
"eslint.useRealpaths": {
200+
"scope": "resource",
201+
"type": "boolean",
202+
"default": false,
203+
"description": "Whether ESLint should use real paths when resolving files. This is useful when working with symlinks or when the casing of file paths is inconsistent."
204+
},
199205
"eslint.workingDirectories": {
200206
"scope": "resource",
201207
"type": "array",

server/src/eslint.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -478,11 +478,11 @@ export type SaveRuleConfigItem = { offRules: Set<string>; onRules: Set<string>;
478478
*/
479479
export namespace SaveRuleConfigs {
480480

481-
export let inferFilePath: (documentOrUri: string | TextDocument | URI | undefined) => string | undefined;
481+
export let inferFilePath: (documentOrUri: string | TextDocument | URI | undefined, useRealpaths: boolean) => string | undefined;
482482

483483
const saveRuleConfigCache = new LRUCache<string, SaveRuleConfigItem | null>(128);
484484
export async function get(uri: string, settings: TextDocumentSettings & { library: ESLintModule }): Promise<SaveRuleConfigItem | undefined> {
485-
const filePath = inferFilePath(uri);
485+
const filePath = inferFilePath(uri, settings.useRealpaths);
486486
let result = saveRuleConfigCache.get(uri);
487487
if (filePath === undefined || result === null) {
488488
return undefined;
@@ -755,7 +755,7 @@ export namespace ESLint {
755755

756756
let connection: ProposedFeatures.Connection;
757757
let documents: TextDocuments<TextDocument>;
758-
let inferFilePath: (documentOrUri: string | TextDocument | URI | undefined) => string | undefined;
758+
let inferFilePath: (documentOrUri: string | TextDocument | URI | undefined, useRealpaths: boolean) => string | undefined;
759759
let loadNodeModule: <T>(moduleName: string) => T | undefined;
760760

761761
const languageId2ParserRegExp: Map<string, RegExp[]> = function createLanguageId2ParserRegExp() {
@@ -823,7 +823,7 @@ export namespace ESLint {
823823
const document2Settings: Map<string, Promise<TextDocumentSettings>> = new Map<string, Promise<TextDocumentSettings>>();
824824
const formatterRegistrations: Map<string, Promise<Disposable>> = new Map();
825825

826-
export function initialize($connection: ProposedFeatures.Connection, $documents: TextDocuments<TextDocument>, $inferFilePath: (documentOrUri: string | TextDocument | URI | undefined) => string | undefined, $loadNodeModule: <T>(moduleName: string) => T | undefined) {
826+
export function initialize($connection: ProposedFeatures.Connection, $documents: TextDocuments<TextDocument>, $inferFilePath: (documentOrUri: string | TextDocument | URI | undefined, useRealpaths: boolean) => string | undefined, $loadNodeModule: <T>(moduleName: string) => T | undefined) {
827827
connection = $connection;
828828
documents = $documents;
829829
inferFilePath = $inferFilePath;
@@ -870,8 +870,8 @@ export namespace ESLint {
870870
return settings;
871871
}
872872
settings.resolvedGlobalPackageManagerPath = GlobalPaths.get(settings.packageManager);
873-
const filePath = inferFilePath(document);
874-
const workspaceFolderPath = settings.workspaceFolder !== undefined ? inferFilePath(settings.workspaceFolder.uri) : undefined;
873+
const filePath = inferFilePath(document, settings.useRealpaths);
874+
const workspaceFolderPath = settings.workspaceFolder !== undefined ? inferFilePath(settings.workspaceFolder.uri, settings.useRealpaths) : undefined;
875875
let assumeFlatConfig:boolean = false;
876876
const hasUserDefinedWorkingDirectories: boolean = configuration.workingDirectory !== undefined;
877877
const workingDirectoryConfig = configuration.workingDirectory ?? { mode: ModeEnum.location };
@@ -1097,7 +1097,7 @@ export namespace ESLint {
10971097
if (!isFile) {
10981098
formatterRegistrations.set(uri, connection.client.register(DocumentFormattingRequest.type, options));
10991099
} else {
1100-
const filePath = inferFilePath(uri)!;
1100+
const filePath = inferFilePath(uri, settings.useRealpaths)!;
11011101
await ESLint.withClass(async (eslintClass) => {
11021102
if (!await eslintClass.isPathIgnored(filePath)) {
11031103
formatterRegistrations.set(uri, connection.client.register(DocumentFormattingRequest.type, options));
@@ -1183,14 +1183,14 @@ export namespace ESLint {
11831183
if (uri.scheme !== 'file') {
11841184
if (settings.workspaceFolder !== undefined) {
11851185
const ext = LanguageDefaults.getExtension(document.languageId);
1186-
const workspacePath = inferFilePath(settings.workspaceFolder.uri);
1186+
const workspacePath = inferFilePath(settings.workspaceFolder.uri, settings.useRealpaths);
11871187
if (workspacePath !== undefined && ext !== undefined) {
11881188
return path.join(workspacePath, `test.${ext}`);
11891189
}
11901190
}
11911191
return undefined;
11921192
} else {
1193-
return inferFilePath(uri);
1193+
return inferFilePath(uri, settings.useRealpaths);
11941194
}
11951195
}
11961196

@@ -1418,14 +1418,14 @@ export namespace ESLint {
14181418
missingModuleReported.clear();
14191419
}
14201420

1421-
function tryHandleMissingModule(error: any, document: TextDocument, library: ESLintModule): Status | undefined {
1421+
function tryHandleMissingModule(error: any, document: TextDocument, library: ESLintModule, settings: TextDocumentSettings): Status | undefined {
14221422
if (!error.message) {
14231423
return undefined;
14241424
}
14251425

14261426
function handleMissingModule(plugin: string, module: string, error: ESLintError): Status {
14271427
if (!missingModuleReported.has(plugin)) {
1428-
const fsPath = inferFilePath(document);
1428+
const fsPath = inferFilePath(document, settings.useRealpaths);
14291429
missingModuleReported.set(plugin, library);
14301430
if (error.messageTemplate === 'plugin-missing') {
14311431
connection.console.error([

server/src/eslintServer.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,20 +116,20 @@ process.on('uncaughtException', (error: any) => {
116116
* cell document it uses the file path from the notebook with a corresponding
117117
* extension (e.g. TypeScript -> ts)
118118
*/
119-
function inferFilePath(documentOrUri: string | TextDocument | URI | undefined): string | undefined {
119+
function inferFilePath(documentOrUri: string | TextDocument | URI | undefined, useRealpaths: boolean): string | undefined {
120120
if (!documentOrUri) {
121121
return undefined;
122122
}
123123
const uri = getUri(documentOrUri);
124124
if (uri.scheme === 'file') {
125-
return getFileSystemPath(uri);
125+
return getFileSystemPath(uri, useRealpaths);
126126
}
127127

128128
const notebookDocument = notebooks.findNotebookDocumentForCell(uri.toString());
129129
if (notebookDocument !== undefined ) {
130130
const notebookUri = URI.parse(notebookDocument.uri);
131131
if (notebookUri.scheme === 'file') {
132-
const filePath = getFileSystemPath(uri);
132+
const filePath = getFileSystemPath(uri, useRealpaths);
133133
if (filePath !== undefined) {
134134
const textDocument = documents.get(uri.toString());
135135
if (textDocument !== undefined) {
@@ -299,7 +299,7 @@ connection.onDidChangeWatchedFiles(async (params) => {
299299
SaveRuleConfigs.clear();
300300

301301
await Promise.all(params.changes.map(async (change) => {
302-
const fsPath = inferFilePath(change.uri);
302+
const fsPath = inferFilePath(change.uri, false);
303303
if (fsPath === undefined || fsPath.length === 0 || isUNC(fsPath)) {
304304
return;
305305
}
@@ -756,7 +756,7 @@ async function computeAllFixes(identifier: VersionedTextDocumentIdentifier, mode
756756
if (settings.validate !== Validate.on || !TextDocumentSettings.hasLibrary(settings) || (mode === AllFixesMode.format && !settings.format)) {
757757
return [];
758758
}
759-
const filePath = inferFilePath(textDocument);
759+
const filePath = inferFilePath(textDocument, settings.useRealpaths);
760760
const problems = CodeActions.get(uri);
761761
const originalContent = textDocument.getText();
762762
let start = Date.now();

server/src/paths.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,30 +76,37 @@ export function isUNC(path: string): boolean {
7676
return true;
7777
}
7878

79-
export function getFileSystemPath(uri: URI): string {
79+
export function getFileSystemPath(uri: URI, useRealpaths: boolean): string {
8080
let result = uri.fsPath;
8181
if (process.platform === 'win32' && result.length >= 2 && result[1] === ':') {
8282
// Node by default uses an upper case drive letter and ESLint uses
8383
// === to compare paths which results in the equal check failing
8484
// if the drive letter is lower case in th URI. Ensure upper case.
8585
result = result[0].toUpperCase() + result.substr(1);
8686
}
87-
if (process.platform === 'win32' || process.platform === 'darwin') {
88-
try {
89-
const realpath = fs.realpathSync.native(result);
90-
// Only use the real path if only the casing has changed.
91-
if (realpath.toLowerCase() === result.toLowerCase()) {
92-
result = realpath;
93-
}
94-
} catch {
95-
// Silently ignore errors from `fs.realpathSync` to handle scenarios where
96-
// the file being linted is not yet written to disk. This occurs in editors
97-
// such as Neovim for non-written buffers.
87+
if (useRealpaths) {
88+
result = tryRealpath(result);
89+
} else if (process.platform === 'win32' || process.platform === 'darwin') {
90+
const realpath = tryRealpath(result);
91+
// Only use the real path if only the casing has changed.
92+
if (realpath.toLowerCase() === result.toLowerCase()) {
93+
result = realpath;
9894
}
9995
}
10096
return result;
10197
}
10298

99+
function tryRealpath(path: string): string {
100+
try {
101+
return fs.realpathSync.native(path);
102+
} catch (error) {
103+
// Silently ignore errors from `fs.realpathSync` to handle scenarios where
104+
// the file being linted is not yet written to disk. This occurs in editors
105+
// such as Neovim for non-written buffers.
106+
return path;
107+
}
108+
}
109+
103110
export function normalizePath(path: string): string;
104111
export function normalizePath(path: undefined): undefined;
105112
export function normalizePath(path: string | undefined): string | undefined {

0 commit comments

Comments
 (0)