Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions $shared/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ export type ConfigurationSettings = {
packageManager: PackageManagers;
useESLintClass: boolean;
useFlatConfig?: boolean | undefined;
useRealpaths: boolean;
experimental?: {
useFlatConfig: boolean;
};
Expand Down
3 changes: 2 additions & 1 deletion client/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@ export namespace ESLintClient {
packageManager: config.get<PackageManagers>('packageManager', 'npm'),
useESLintClass: config.get<boolean>('useESLintClass', false),
useFlatConfig: useFlatConfig === null ? undefined : useFlatConfig,
useRealpaths: config.get<boolean>('useRealpaths', true),
experimental: {
useFlatConfig: config.get<boolean>('experimental.useFlatConfig', false),
},
Expand Down Expand Up @@ -962,7 +963,7 @@ export namespace ESLintClient {
}

if (detail !== undefined && languageStatus.detail !== detail) {
languageStatus.detail = detail;
languageStatus.detail = detail;
}
if (languageStatus.severity !== severity) {
languageStatus.severity = severity;
Expand Down
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@
"deprecationMessage": "Use ESLint version 8.57 or later and `eslint.useFlatConfig` instead.",
"description": "Enables support of experimental Flat Config (aka eslint.config.js). Requires ESLint version >= 8.21 < 8.57.0)."
},
"eslint.useRealpaths": {
"scope": "resource",
"type": "boolean",
"default": true,
"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."
},
"eslint.workingDirectories": {
"scope": "resource",
"type": "array",
Expand Down
22 changes: 11 additions & 11 deletions server/src/eslint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,11 +478,11 @@ export type SaveRuleConfigItem = { offRules: Set<string>; onRules: Set<string>;
*/
export namespace SaveRuleConfigs {

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

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

let connection: ProposedFeatures.Connection;
let documents: TextDocuments<TextDocument>;
let inferFilePath: (documentOrUri: string | TextDocument | URI | undefined) => string | undefined;
let inferFilePath: (documentOrUri: string | TextDocument | URI | undefined, useRealpaths: boolean) => string | undefined;
let loadNodeModule: <T>(moduleName: string) => T | undefined;

const languageId2ParserRegExp: Map<string, RegExp[]> = function createLanguageId2ParserRegExp() {
Expand Down Expand Up @@ -821,7 +821,7 @@ export namespace ESLint {
const document2Settings: Map<string, Promise<TextDocumentSettings>> = new Map<string, Promise<TextDocumentSettings>>();
const formatterRegistrations: Map<string, Promise<Disposable>> = new Map();

export function initialize($connection: ProposedFeatures.Connection, $documents: TextDocuments<TextDocument>, $inferFilePath: (documentOrUri: string | TextDocument | URI | undefined) => string | undefined, $loadNodeModule: <T>(moduleName: string) => T | undefined) {
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) {
connection = $connection;
documents = $documents;
inferFilePath = $inferFilePath;
Expand Down Expand Up @@ -868,8 +868,8 @@ export namespace ESLint {
return settings;
}
settings.resolvedGlobalPackageManagerPath = GlobalPaths.get(settings.packageManager);
const filePath = inferFilePath(document);
const workspaceFolderPath = settings.workspaceFolder !== undefined ? inferFilePath(settings.workspaceFolder.uri) : undefined;
const filePath = inferFilePath(document, settings.useRealpaths);
const workspaceFolderPath = settings.workspaceFolder !== undefined ? inferFilePath(settings.workspaceFolder.uri, settings.useRealpaths) : undefined;
let assumeFlatConfig:boolean = false;
const hasUserDefinedWorkingDirectories: boolean = configuration.workingDirectory !== undefined;
const workingDirectoryConfig = configuration.workingDirectory ?? { mode: ModeEnum.location };
Expand Down Expand Up @@ -1095,7 +1095,7 @@ export namespace ESLint {
if (!isFile) {
formatterRegistrations.set(uri, connection.client.register(DocumentFormattingRequest.type, options));
} else {
const filePath = inferFilePath(uri)!;
const filePath = inferFilePath(uri, settings.useRealpaths)!;
await ESLint.withClass(async (eslintClass) => {
if (!await eslintClass.isPathIgnored(filePath)) {
formatterRegistrations.set(uri, connection.client.register(DocumentFormattingRequest.type, options));
Expand Down Expand Up @@ -1181,14 +1181,14 @@ export namespace ESLint {
if (uri.scheme !== 'file') {
if (settings.workspaceFolder !== undefined) {
const ext = LanguageDefaults.getExtension(document.languageId);
const workspacePath = inferFilePath(settings.workspaceFolder.uri);
const workspacePath = inferFilePath(settings.workspaceFolder.uri, settings.useRealpaths);
if (workspacePath !== undefined && ext !== undefined) {
return path.join(workspacePath, `test.${ext}`);
}
}
return undefined;
} else {
return inferFilePath(uri);
return inferFilePath(uri, settings.useRealpaths);
}
}

Expand Down Expand Up @@ -1416,14 +1416,14 @@ export namespace ESLint {
missingModuleReported.clear();
}

function tryHandleMissingModule(error: any, document: TextDocument, library: ESLintModule): Status | undefined {
function tryHandleMissingModule(error: any, document: TextDocument, library: ESLintModule, settings: TextDocumentSettings): Status | undefined {
if (!error.message) {
return undefined;
}

function handleMissingModule(plugin: string, module: string, error: ESLintError): Status {
if (!missingModuleReported.has(plugin)) {
const fsPath = inferFilePath(document);
const fsPath = inferFilePath(document, settings.useRealpaths);
missingModuleReported.set(plugin, library);
if (error.messageTemplate === 'plugin-missing') {
connection.console.error([
Expand Down
10 changes: 5 additions & 5 deletions server/src/eslintServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,20 +116,20 @@ process.on('uncaughtException', (error: any) => {
* cell document it uses the file path from the notebook with a corresponding
* extension (e.g. TypeScript -> ts)
*/
function inferFilePath(documentOrUri: string | TextDocument | URI | undefined): string | undefined {
function inferFilePath(documentOrUri: string | TextDocument | URI | undefined, useRealpaths: boolean): string | undefined {
if (!documentOrUri) {
return undefined;
}
const uri = getUri(documentOrUri);
if (uri.scheme === 'file') {
return getFileSystemPath(uri);
return getFileSystemPath(uri, useRealpaths);
}

const notebookDocument = notebooks.findNotebookDocumentForCell(uri.toString());
if (notebookDocument !== undefined ) {
const notebookUri = URI.parse(notebookDocument.uri);
if (notebookUri.scheme === 'file') {
const filePath = getFileSystemPath(uri);
const filePath = getFileSystemPath(uri, useRealpaths);
if (filePath !== undefined) {
const textDocument = documents.get(uri.toString());
if (textDocument !== undefined) {
Expand Down Expand Up @@ -299,7 +299,7 @@ connection.onDidChangeWatchedFiles(async (params) => {
SaveRuleConfigs.clear();

await Promise.all(params.changes.map(async (change) => {
const fsPath = inferFilePath(change.uri);
const fsPath = inferFilePath(change.uri, false);
if (fsPath === undefined || fsPath.length === 0 || isUNC(fsPath)) {
return;
}
Expand Down Expand Up @@ -756,7 +756,7 @@ async function computeAllFixes(identifier: VersionedTextDocumentIdentifier, mode
if (settings.validate !== Validate.on || !TextDocumentSettings.hasLibrary(settings) || (mode === AllFixesMode.format && !settings.format)) {
return [];
}
const filePath = inferFilePath(textDocument);
const filePath = inferFilePath(textDocument, settings.useRealpaths);
const problems = CodeActions.get(uri);
const originalContent = textDocument.getText();
let start = Date.now();
Expand Down
31 changes: 19 additions & 12 deletions server/src/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,30 +76,37 @@ export function isUNC(path: string): boolean {
return true;
}

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

function tryRealpath(path: string): string {
try {
return fs.realpathSync.native(path);
} catch (error) {
// Silently ignore errors from `fs.realpathSync` to handle scenarios where
// the file being linted is not yet written to disk. This occurs in editors
// such as Neovim for non-written buffers.
return path;
}
}

export function normalizePath(path: string): string;
export function normalizePath(path: undefined): undefined;
export function normalizePath(path: string | undefined): string | undefined {
Expand Down