Skip to content

Commit b6e4065

Browse files
authored
Add the option for Roslyn LSP to support organize imports (#7935)
2 parents 2c09c08 + fc5c696 commit b6e4065

File tree

10 files changed

+125
-26
lines changed

10 files changed

+125
-26
lines changed

.vscode/launch.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"preLaunchTask": "buildDev"
1313
},
1414
{
15-
"name": "Launch Current File slnWithCsproj Integration Tests",
15+
"name": "[Roslyn] Run Current File Integration Tests",
1616
"type": "extensionHost",
1717
"request": "launch",
1818
"runtimeExecutable": "${execPath}",
@@ -39,7 +39,7 @@
3939
"internalConsoleOptions": "openOnSessionStart"
4040
},
4141
{
42-
"name": "[DevKit] Launch Current File slnWithCsproj Integration Tests",
42+
"name": "[DevKit] Run Current File Integration Tests",
4343
"type": "extensionHost",
4444
"request": "launch",
4545
"runtimeExecutable": "${execPath}",
@@ -93,7 +93,7 @@
9393
"internalConsoleOptions": "openOnSessionStart"
9494
},
9595
{
96-
"name": "Omnisharp: Launch Current File Integration Tests",
96+
"name": "[O#] Run Current File Integration Tests",
9797
"type": "extensionHost",
9898
"request": "launch",
9999
"runtimeExecutable": "${execPath}",
@@ -115,10 +115,10 @@
115115
"sourceMaps": true,
116116
"outFiles": ["${workspaceRoot}/dist/*.js", "${workspaceRoot}/out/test/**/*.js"],
117117
"resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"],
118-
"preLaunchTask": "buildDev"
118+
"preLaunchTask": "buildTest"
119119
},
120120
{
121-
"name": "Omnisharp: Launch Current File Integration Tests [LSP]",
121+
"name": "[O# LSP] Run Current File Integration Tests",
122122
"type": "extensionHost",
123123
"request": "launch",
124124
"runtimeExecutable": "${execPath}",
@@ -140,7 +140,7 @@
140140
"sourceMaps": true,
141141
"outFiles": ["${workspaceRoot}/dist/*.js", "${workspaceRoot}/out/test/**/*.js"],
142142
"resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"],
143-
"preLaunchTask": "buildDev"
143+
"preLaunchTask": "buildTest"
144144
},
145145
{
146146
"type": "node",

CHANGELOG.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
# 2.63.x
77
* Bump xamlTools to 17.14.35716.216 (PR: [#7932](https://github.com/dotnet/vscode-csharp/pull/7932))
8-
* Update Roslyn to 4.14.0-1.25065.8 (PR: [#7927](https://github.com/dotnet/vscode-csharp/pull/7927))
8+
* Update Roslyn to 4.14.0-1.25072.1 (PR: [#7935](https://github.com/dotnet/vscode-csharp/pull/7935))
99
* Remove explicit recursion in the json detection analyzer (#76764) (PR: [#76764](https://github.com/dotnet/roslyn/pull/76764))
1010
* Consider silly cyclic assignment in scoped variance (#76261) (PR: [#76261](https://github.com/dotnet/roslyn/pull/76261))
1111
* Fix ordering of 'params' vs 'scoped' in metadata as source (#76745) (PR: [#76745](https://github.com/dotnet/roslyn/pull/76745))
@@ -20,6 +20,32 @@
2020
* Adding checks for mutable structs. (#76711) (PR: [#76711](https://github.com/dotnet/roslyn/pull/76711))
2121
* Stash and restore original culture in CultureNormalizer (#76713) (PR: [#76713](https://github.com/dotnet/roslyn/pull/76713))
2222
* Add option for choosing stdio as the LSP communication channel (#76437) (PR: [#76437](https://github.com/dotnet/roslyn/pull/76437))
23+
* Support organizing imports as part of LSP document formatting (PR: [#76806](https://github.com/dotnet/roslyn/pull/76806))
24+
* Improve collapsing of members followed by pp directives (PR: [#76837](https://github.com/dotnet/roslyn/pull/76837))
25+
* Load razor assembly directly: (PR: [#76808](https://github.com/dotnet/roslyn/pull/76808))
26+
* Special case inlining a collection expr into a spreaded element (PR: [#76823](https://github.com/dotnet/roslyn/pull/76823))
27+
* Do not offer to simplify interpolations when using formattable strings (PR: [#76830](https://github.com/dotnet/roslyn/pull/76830))
28+
* Add support for outlining switch expressions (PR: [#76827](https://github.com/dotnet/roslyn/pull/76827))
29+
* Do no offer to make fields readonly if they are a struct and are written to through an indexer (PR: [#76825](https://github.com/dotnet/roslyn/pull/76825))
30+
* Do not offer to inline a decl into a switch arm when it is referenced outside of it. (PR: [#76822](https://github.com/dotnet/roslyn/pull/76822))
31+
* Fix gen-method generating into top level. (PR: [#76821](https://github.com/dotnet/roslyn/pull/76821))
32+
* Fix 'invert if' refactor to properly enclose #region/#endregion blocks (PR: [#74145](https://github.com/dotnet/roslyn/pull/74145))
33+
* Do not offer use-conditional when it would cause name collisions (PR: [#76807](https://github.com/dotnet/roslyn/pull/76807))
34+
* Remove unnecessary cast in one conditional expression branch, based on the other branch and outer conversion. (PR: [#76798](https://github.com/dotnet/roslyn/pull/76798))
35+
* Convert a return value to return type even if it has errors (PR: [#76699](https://github.com/dotnet/roslyn/pull/76699))
36+
* Add EmbeddedAttribute API for source generators (PR: [#76583](https://github.com/dotnet/roslyn/pull/76583))
37+
* Fix formatting when doing a 'fix all' with 'use auto prop' (PR: [#76791](https://github.com/dotnet/roslyn/pull/76791))
38+
* Initialize naming style preferences when language is added to workspace (PR: [#76795](https://github.com/dotnet/roslyn/pull/76795))
39+
* Support target type completion tags in object creation contexts (PR: [#76786](https://github.com/dotnet/roslyn/pull/76786))
40+
* Fix 'use conditional expression' where it was causing a null-ref warning. (PR: [#76792](https://github.com/dotnet/roslyn/pull/76792))
41+
* Keep comments on an 'else' keyword when converting to conditional expressions. (PR: [#76789](https://github.com/dotnet/roslyn/pull/76789))
42+
* VB: Don't capture conditional access receiver into a temp local during lowering. (PR: [#76712](https://github.com/dotnet/roslyn/pull/76712))
43+
* Update regex parsing to latest .Net core parsing (and diagnostic messages). (PR: [#76269](https://github.com/dotnet/roslyn/pull/76269))
44+
* Forbid pointer types as instance fields in records (PR: [#76588](https://github.com/dotnet/roslyn/pull/76588))
45+
* Field-backed properties: report diagnostic for variable named field declared in accessor (PR: [#76671](https://github.com/dotnet/roslyn/pull/76671))
46+
* Update 'use nameof instead of typeof' to support generic types (PR: [#76780](https://github.com/dotnet/roslyn/pull/76780))
47+
* Add feature to convert from an explicitly typed lambda to an implicitly typed one. (PR: [#76770](https://github.com/dotnet/roslyn/pull/76770))
48+
* Support modifiers with simple lambda parameters. (PR: [#75400](https://github.com/dotnet/roslyn/pull/75400))
2349
* Update Razor to 9.0.0-preview.25064.4 (PR: [#7927](https://github.com/dotnet/vscode-csharp/pull/7927))
2450
* Wire up the UseRoslynTokenizer feature properly (#11386) (PR: [#11386](https://github.com/dotnet/razor/pull/11386))
2551
* New Razor document formatting engine (#11364) (PR: [#11364](https://github.com/dotnet/razor/pull/11364))

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
}
3838
},
3939
"defaults": {
40-
"roslyn": "4.14.0-1.25065.8",
40+
"roslyn": "4.14.0-1.25072.1",
4141
"omniSharp": "1.39.12",
4242
"razor": "9.0.0-preview.25064.4",
4343
"razorOmnisharp": "7.0.0-preview.23363.1",
@@ -739,6 +739,11 @@
739739
"default": true,
740740
"description": "%configuration.dotnet.autoInsert.enableAutoInsert%"
741741
},
742+
"dotnet.formatting.organizeImportsOnFormat": {
743+
"type": "boolean",
744+
"default": false,
745+
"description": "%configuration.dotnet.formatting.organizeImportsOnFormat%"
746+
},
742747
"dotnet.typeMembers.memberInsertionLocation": {
743748
"type": "string",
744749
"enum": [
@@ -1696,11 +1701,6 @@
16961701
"default": false,
16971702
"description": "%configuration.omnisharp.enableLspDriver%"
16981703
},
1699-
"omnisharp.organizeImportsOnFormat": {
1700-
"type": "boolean",
1701-
"default": false,
1702-
"description": "%configuration.omnisharp.organizeImportsOnFormat%"
1703-
},
17041704
"omnisharp.enableAsyncCompletion": {
17051705
"type": "boolean",
17061706
"default": false,

package.nls.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"command.dotnet.test.debugTestsInContext": "Debug Tests in Context",
2525
"command.dotnet.restartServer": "Restart Language Server",
2626
"configuration.dotnet.autoInsert.enableAutoInsert": "Enable automatic insertion of documentation comments.",
27+
"configuration.dotnet.formatting.organizeImportsOnFormat": "Specifies whether 'using' directives should be grouped and sorted during document formatting. (Previously `omnisharp.organizeImportsOnFormat`)",
2728
"configuration.dotnet.defaultSolution.description": "The path of the default solution to be opened in the workspace, or set to 'disable' to skip it. (Previously `omnisharp.defaultLaunchSolution`)",
2829
"configuration.dotnet.server.path": "Specifies the absolute path to the server (LSP or O#) executable. When left empty the version pinned to the C# Extension is used. (Previously `omnisharp.path`)",
2930
"configuration.dotnet.server.componentPaths": "Allows overriding the folder path for built in components of the language server (for example, override the .roslynDevKit path in the extension directory to use locally built components)",
@@ -116,7 +117,6 @@
116117
"configuration.omnisharp.enableEditorConfigSupport": "Enables support for reading code style, naming convention and analyzer settings from .editorconfig.",
117118
"configuration.omnisharp.enableDecompilationSupport": "Enables support for decompiling external references instead of viewing metadata.",
118119
"configuration.omnisharp.enableLspDriver": "Enables support for the experimental language protocol based engine (requires reload to setup bindings correctly)",
119-
"configuration.omnisharp.organizeImportsOnFormat": "Specifies whether 'using' directives should be grouped and sorted during document formatting.",
120120
"configuration.omnisharp.enableAsyncCompletion": "(EXPERIMENTAL) Enables support for resolving completion edits asynchronously. This can speed up time to show the completion list, particularly override and partial method completion lists, at the cost of slight delays after inserting a completion item. Most completion items will have no noticeable impact with this feature, but typing immediately after inserting an override or partial method completion, before the insert is completed, can have unpredictable results.",
121121
"configuration.omnisharp.dotNetCliPaths": "Paths to a local download of the .NET CLI to use for running any user code.",
122122
"configuration.omnisharp.razor.plugin.path": "Overrides the path to the Razor plugin dll.",

src/omnisharp/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ export class OmniSharpServer {
458458
args.push('FormattingOptions:EnableEditorConfigSupport=true');
459459
}
460460

461-
if (omnisharpOptions.organizeImportsOnFormat === true) {
461+
if (commonOptions.organizeImportsOnFormat === true) {
462462
args.push('FormattingOptions:OrganizeImports=true');
463463
}
464464

src/shared/migrateOptions.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ export const migrateOptions = [
100100
oldName: 'dotnet.implementType.propertyGenerationBehavior',
101101
newName: 'dotnet.typeMembers.propertyGenerationBehavior',
102102
},
103+
{
104+
oldName: 'omnisharp.organizeImportsOnFormat',
105+
newName: 'dotnet.formatting.organizeImportsOnFormat',
106+
},
103107
];
104108

105109
export async function MigrateOptions(vscode: vscode): Promise<void> {

src/shared/options.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface CommonOptions {
1717
readonly defaultSolution: string;
1818
readonly unitTestDebuggingOptions: object;
1919
readonly runSettingsPath: string;
20+
readonly organizeImportsOnFormat: boolean;
2021
}
2122

2223
export interface OmnisharpServerOptions {
@@ -34,7 +35,6 @@ export interface OmnisharpServerOptions {
3435
readonly enableImportCompletion: boolean;
3536
readonly enableAsyncCompletion: boolean;
3637
readonly analyzeOpenDocumentsOnly: boolean;
37-
readonly organizeImportsOnFormat: boolean;
3838
readonly disableMSBuildDiagnosticWarning: boolean;
3939
readonly showOmnisharpLogOnError: boolean;
4040
readonly minFindSymbolsFilterLength: number;
@@ -157,6 +157,9 @@ class CommonOptionsImpl implements CommonOptions {
157157
public get runSettingsPath() {
158158
return readOption<string>('dotnet.unitTests.runSettingsPath', '', 'omnisharp.testRunSettings');
159159
}
160+
public get organizeImportsOnFormat() {
161+
return readOption<boolean>('dotnet.formatting.organizeImportsOnFormat', false);
162+
}
160163
}
161164

162165
class OmnisharpOptionsImpl implements OmnisharpServerOptions {
@@ -227,9 +230,6 @@ class OmnisharpOptionsImpl implements OmnisharpServerOptions {
227230
const analyzeOpenDocumentsOnlyNewOption = diagnosticAnalysisScope == 'openFiles';
228231
return analyzeOpenDocumentsOnlyLegacyOption || analyzeOpenDocumentsOnlyNewOption;
229232
}
230-
public get organizeImportsOnFormat() {
231-
return readOption<boolean>('omnisharp.organizeImportsOnFormat', false);
232-
}
233233
public get disableMSBuildDiagnosticWarning() {
234234
return readOption<boolean>('omnisharp.disableMSBuildDiagnosticWarning', false);
235235
}
@@ -483,7 +483,6 @@ export const OmnisharpOptionsThatTriggerReload: ReadonlyArray<keyof OmnisharpSer
483483
'enableEditorConfigSupport',
484484
'enableDecompilationSupport',
485485
'enableImportCompletion',
486-
'organizeImportsOnFormat',
487486
'enableAsyncCompletion',
488487
'useModernNet',
489488
'enableLspDriver',

test/lsptoolshost/integrationTests/formatting.integration.test.ts

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,19 @@ import {
1212
expectText,
1313
openFileInWorkspaceAsync,
1414
} from './integrationHelpers';
15-
import { describe, beforeAll, beforeEach, afterAll, test, afterEach } from '@jest/globals';
15+
import { describe, beforeEach, afterAll, test, afterEach } from '@jest/globals';
1616
import { formatDocumentAsync, formatOnTypeAsync, formatRangeAsync } from './formattingTestHelpers';
1717

1818
describe(`Formatting Tests`, () => {
19-
beforeAll(async () => {
19+
beforeEach(async () => {
20+
await setOrganizeImportsOnFormat(undefined);
2021
await activateCSharpExtension();
21-
});
2222

23-
beforeEach(async () => {
2423
await openFileInWorkspaceAsync(path.join('src', 'app', 'Formatting.cs'));
2524
});
2625

2726
afterAll(async () => {
27+
await setOrganizeImportsOnFormat(undefined);
2828
await testAssetWorkspace.cleanupWorkspace();
2929
});
3030

@@ -36,6 +36,8 @@ describe(`Formatting Tests`, () => {
3636
await formatDocumentAsync();
3737

3838
const expectedText = [
39+
'using Options;',
40+
'using System;',
3941
'namespace Formatting;',
4042
'class DocumentFormatting',
4143
'{',
@@ -54,9 +56,11 @@ describe(`Formatting Tests`, () => {
5456
});
5557

5658
test('Document range formatting formats only the range', async () => {
57-
await formatRangeAsync(new vscode.Range(3, 0, 5, 0));
59+
await formatRangeAsync(new vscode.Range(5, 0, 7, 0));
5860

5961
const expectedText = [
62+
'using Options;',
63+
'using System;',
6064
'namespace Formatting;',
6165
'class DocumentFormatting',
6266
'{',
@@ -75,9 +79,11 @@ describe(`Formatting Tests`, () => {
7579

7680
test('Document on type formatting formats the typed location', async () => {
7781
// The server expects the position to be the position after the inserted character `;`
78-
await formatOnTypeAsync(new vscode.Position(7, 37), ';');
82+
await formatOnTypeAsync(new vscode.Position(9, 37), ';');
7983

8084
const expectedText = [
85+
'using Options;',
86+
'using System;',
8187
'namespace Formatting;',
8288
'class DocumentFormatting',
8389
'{',
@@ -91,4 +97,60 @@ describe(`Formatting Tests`, () => {
9197
];
9298
await expectText(vscode.window.activeTextEditor!.document, expectedText);
9399
});
100+
101+
test('Document formatting can organize imports', async () => {
102+
await setOrganizeImportsOnFormat(true);
103+
await activateCSharpExtension();
104+
105+
await formatDocumentAsync();
106+
107+
const expectedText = [
108+
'using System;',
109+
'using Options;',
110+
'namespace Formatting;',
111+
'class DocumentFormatting',
112+
'{',
113+
' public int Property1',
114+
' {',
115+
' get; set;',
116+
' }',
117+
'',
118+
' public void Method1()',
119+
' {',
120+
' System.Console.Write("");',
121+
' }',
122+
'}',
123+
];
124+
await expectText(vscode.window.activeTextEditor!.document, expectedText);
125+
});
126+
127+
test('Document range formatting does not organize imports', async () => {
128+
await setOrganizeImportsOnFormat(true);
129+
await activateCSharpExtension();
130+
131+
await formatRangeAsync(new vscode.Range(0, 0, 7, 0));
132+
133+
const expectedText = [
134+
'using Options;',
135+
'using System;',
136+
'namespace Formatting;',
137+
'class DocumentFormatting',
138+
'{',
139+
' public int Property1',
140+
' {',
141+
' get; set;',
142+
' }',
143+
'',
144+
' public void Method1() {',
145+
' System.Console.Write("");',
146+
' }',
147+
'}',
148+
];
149+
await expectText(vscode.window.activeTextEditor!.document, expectedText);
150+
});
151+
152+
async function setOrganizeImportsOnFormat(value: boolean | undefined) {
153+
const dotnetConfig = vscode.workspace.getConfiguration('dotnet');
154+
await dotnetConfig.update('formatting.organizeImportsOnFormat', value, true);
155+
}
94156
});

test/lsptoolshost/integrationTests/testAssets/slnWithCsproj/src/app/Formatting.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using Options;
2+
using System;
13
namespace Formatting;
24
class DocumentFormatting
35
{

test/lsptoolshost/unitTests/configurationMiddleware.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,12 @@ const testData = [
267267
declareInPackageJson: true,
268268
section: editorBehaviorSection,
269269
},
270+
{
271+
serverOption: 'csharp|formatting.dotnet_organize_imports_on_format',
272+
vsCodeConfiguration: 'dotnet.formatting.organizeImportsOnFormat',
273+
declareInPackageJson: true,
274+
section: editorBehaviorSection,
275+
},
270276
];
271277

272278
describe('Server option name to vscode configuration name test', () => {

0 commit comments

Comments
 (0)