From 6edad325073a9aedcc99e5cff1a63fa960d80389 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:33:01 +0000 Subject: [PATCH 1/2] Initial plan From 051cce06f05b0eaf54538fdf1c44415bc37d4ec4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:43:01 +0000 Subject: [PATCH 2/2] Fix scoped formatter editorconfig reads Co-authored-by: mwiemer-microsoft <80539004+mwiemer-microsoft@users.noreply.github.com> --- .../options/configurationMiddleware.ts | 12 +- .../options/universalEditorConfigProvider.ts | 9 +- ...formattingEditorConfig.integration.test.ts | 42 +++---- .../app/folderWithEditorConfig/.editorconfig | 2 + ...ationMiddleware.readConfigurations.test.ts | 112 ++++++++++++++++++ 5 files changed, 149 insertions(+), 28 deletions(-) create mode 100644 test/lsptoolshost/unitTests/configurationMiddleware.readConfigurations.test.ts diff --git a/src/lsptoolshost/options/configurationMiddleware.ts b/src/lsptoolshost/options/configurationMiddleware.ts index e8bfc1bd89..73a1e82fdc 100644 --- a/src/lsptoolshost/options/configurationMiddleware.ts +++ b/src/lsptoolshost/options/configurationMiddleware.ts @@ -12,16 +12,20 @@ export function readConfigurations(params: ConfigurationParams): (string | null) // Note: null means there is no such configuration in client. // If the configuration is null, should push 'null' to result. const result: (string | null)[] = []; - const settings = vscode.workspace.getConfiguration(); for (const configurationItem of params.items) { const section = configurationItem.section; - // Currently only support global option. - if (section === undefined || configurationItem.scopeUri !== undefined) { + if (section === undefined) { result.push(null); continue; } + const scope = + configurationItem.scopeUri === undefined + ? { languageId: 'csharp' } + : { languageId: 'csharp', uri: vscode.Uri.parse(configurationItem.scopeUri) }; + const settings = vscode.workspace.getConfiguration(undefined, scope); + // Server use a different name compare to the name defined in client, so do the remapping. const clientSideName = convertServerOptionNameToClientConfigurationName(section); if (clientSideName == null) { @@ -35,7 +39,7 @@ export function readConfigurations(params: ConfigurationParams): (string | null) continue; } - value = readEquivalentVsCodeConfiguration(clientSideName); + value = readEquivalentVsCodeConfiguration(clientSideName, scope); if (value !== undefined) { result.push(value); continue; diff --git a/src/lsptoolshost/options/universalEditorConfigProvider.ts b/src/lsptoolshost/options/universalEditorConfigProvider.ts index 45ca26cf55..de53298501 100644 --- a/src/lsptoolshost/options/universalEditorConfigProvider.ts +++ b/src/lsptoolshost/options/universalEditorConfigProvider.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { WorkspaceConfiguration, workspace } from 'vscode'; +import { ConfigurationScope, WorkspaceConfiguration, workspace } from 'vscode'; // By default we don't want to provide any fall back value for code style options. // These values are exceptions because they already exist in vscode's configuration. @@ -18,13 +18,16 @@ const universalEditorConfigOptionsToReaderMap: Map< ['codeStyle.formatting.newLine.insertFinalNewline', readInsertFinalNewline], ]); -export function readEquivalentVsCodeConfiguration(serverSideOptionName: string): string | undefined { +export function readEquivalentVsCodeConfiguration( + serverSideOptionName: string, + scope: ConfigurationScope = { languageId: 'csharp' } +): string | undefined { if (!universalEditorConfigOptionsToReaderMap.has(serverSideOptionName)) { return undefined; } const readerFunction = universalEditorConfigOptionsToReaderMap.get(serverSideOptionName)!; - const config = workspace.getConfiguration('', { languageId: 'csharp' }); + const config = workspace.getConfiguration('', scope); return readerFunction(config); } diff --git a/test/lsptoolshost/integrationTests/formattingEditorConfig.integration.test.ts b/test/lsptoolshost/integrationTests/formattingEditorConfig.integration.test.ts index 67452574e1..be9f8878f0 100644 --- a/test/lsptoolshost/integrationTests/formattingEditorConfig.integration.test.ts +++ b/test/lsptoolshost/integrationTests/formattingEditorConfig.integration.test.ts @@ -41,14 +41,14 @@ describe(`Formatting With EditorConfig Tests`, () => { 'namespace Formatting;', '', 'class DocumentFormattingWithEditorConfig {', - ' public int Property1 {', - ' get; set;', - ' }', + ' public int Property1 {', + ' get; set;', + ' }', '', - ' public void Method1() {', - ' if (true) {', - ' }', + ' public void Method1() {', + ' if (true) {', ' }', + ' }', '}', ]; await expectText(vscode.window.activeTextEditor!.document, expectedText); @@ -61,16 +61,16 @@ describe(`Formatting With EditorConfig Tests`, () => { 'namespace Formatting;', 'class DocumentFormattingWithEditorConfig', '{', - ' public int Property1 {', - ' get; set;', - ' }', + ' public int Property1 {', + ' get; set;', + ' }', '', - ' public void Method1()', + ' public void Method1()', + ' {', + ' if (true)', ' {', - ' if (true)', - ' {', - ' }', ' }', + ' }', '}', ]; await expectText(vscode.window.activeTextEditor!.document, expectedText); @@ -84,16 +84,16 @@ describe(`Formatting With EditorConfig Tests`, () => { 'namespace Formatting;', 'class DocumentFormattingWithEditorConfig', '{', - ' public int Property1', - ' {', - ' get; set;', - ' }', + ' public int Property1', + ' {', + ' get; set;', + ' }', '', - ' public void Method1()', - ' {', - ' if (true) {', - ' }', + ' public void Method1()', + ' {', + ' if (true) {', ' }', + ' }', '}', ]; await expectText(vscode.window.activeTextEditor!.document, expectedText); diff --git a/test/lsptoolshost/integrationTests/testAssets/slnWithCsproj/src/app/folderWithEditorConfig/.editorconfig b/test/lsptoolshost/integrationTests/testAssets/slnWithCsproj/src/app/folderWithEditorConfig/.editorconfig index 5a164647bb..f4815bf662 100644 --- a/test/lsptoolshost/integrationTests/testAssets/slnWithCsproj/src/app/folderWithEditorConfig/.editorconfig +++ b/test/lsptoolshost/integrationTests/testAssets/slnWithCsproj/src/app/folderWithEditorConfig/.editorconfig @@ -1,2 +1,4 @@ [*.{cs}] +indent_size = 2 +indent_style = space csharp_new_line_before_open_brace = none \ No newline at end of file diff --git a/test/lsptoolshost/unitTests/configurationMiddleware.readConfigurations.test.ts b/test/lsptoolshost/unitTests/configurationMiddleware.readConfigurations.test.ts new file mode 100644 index 0000000000..c9e8f6816b --- /dev/null +++ b/test/lsptoolshost/unitTests/configurationMiddleware.readConfigurations.test.ts @@ -0,0 +1,112 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { beforeEach, describe, expect, jest, test } from '@jest/globals'; +import { readConfigurations } from '../../../src/lsptoolshost/options/configurationMiddleware'; + +describe('configurationMiddleware.readConfigurations', () => { + const getConfiguration = jest.fn(); + const uriParse = jest.fn(); + + beforeEach(() => { + jest.resetAllMocks(); + vscode.workspace.getConfiguration = getConfiguration; + vscode.Uri.parse = uriParse; + }); + + test('reads scoped client configuration values when scopeUri is provided', () => { + const scopedSettings = { + get: jest.fn().mockImplementation((...args: unknown[]) => { + const name = args[0] as string; + return name === 'dotnet.formatting.organizeImportsOnFormat' ? 'true' : undefined; + }), + } as unknown as vscode.WorkspaceConfiguration; + const uri = { fsPath: '/workspace/Program.cs' } as vscode.Uri; + + uriParse.mockReturnValue(uri); + getConfiguration.mockReturnValue(scopedSettings); + + const values = readConfigurations({ + items: [ + { + section: 'csharp|formatting.dotnet_organize_imports_on_format', + scopeUri: 'file:///workspace/Program.cs', + }, + ], + }); + + expect(values).toEqual(['true']); + expect(uriParse).toHaveBeenCalledWith('file:///workspace/Program.cs'); + expect(getConfiguration).toHaveBeenCalledWith(undefined, { languageId: 'csharp', uri }); + expect(scopedSettings.get).toHaveBeenCalledWith('dotnet.formatting.organizeImportsOnFormat'); + }); + + test('reads scoped fallback vscode formatting configuration when explicit client setting is absent', () => { + const scopedSettings = { + get: jest.fn().mockImplementation((...args: unknown[]) => { + const name = args[0] as string; + return name === 'editor.indentSize' ? '2' : undefined; + }), + } as unknown as vscode.WorkspaceConfiguration; + const uri = { fsPath: '/workspace/Program.cs' } as vscode.Uri; + + uriParse.mockReturnValue(uri); + getConfiguration.mockReturnValue(scopedSettings); + + const values = readConfigurations({ + items: [ + { + section: 'csharp|code_style.formatting.indentation_and_spacing.indent_size', + scopeUri: 'file:///workspace/Program.cs', + }, + ], + }); + + expect(values).toEqual(['2']); + expect(getConfiguration).toHaveBeenCalledWith(undefined, { languageId: 'csharp', uri }); + expect(getConfiguration).toHaveBeenCalledWith('', { languageId: 'csharp', uri }); + expect(scopedSettings.get).toHaveBeenCalledWith('codeStyle.formatting.indentationAndSpacing.indentSize'); + expect(scopedSettings.get).toHaveBeenCalledWith('editor.indentSize'); + }); + + test('resolves indentSize=tabSize through scoped vscode formatting configuration', () => { + const scopedSettings = { + get: jest.fn().mockImplementation((...args: unknown[]) => { + const name = args[0] as string; + if (name === 'codeStyle.formatting.indentationAndSpacing.indentSize') { + return undefined; + } + + if (name === 'editor.indentSize') { + return 'tabSize'; + } + + if (name === 'editor.tabSize') { + return '2'; + } + + return undefined; + }), + } as unknown as vscode.WorkspaceConfiguration; + const uri = { fsPath: '/workspace/Program.cs' } as vscode.Uri; + + uriParse.mockReturnValue(uri); + getConfiguration.mockReturnValue(scopedSettings); + + const values = readConfigurations({ + items: [ + { + section: 'csharp|code_style.formatting.indentation_and_spacing.indent_size', + scopeUri: 'file:///workspace/Program.cs', + }, + ], + }); + + expect(values).toEqual(['2']); + expect(scopedSettings.get).toHaveBeenCalledWith('editor.indentSize'); + expect(scopedSettings.get).toHaveBeenCalledWith('editor.tabSize'); + }); +});