Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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', false),
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": false,
"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 @@ -823,7 +823,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 @@ -870,8 +870,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 @@ -1097,7 +1097,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 @@ -1183,14 +1183,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 @@ -1418,14 +1418,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