Skip to content

Commit ddae161

Browse files
h9jianggopherbot
authored andcommitted
extension/src: run formatter specified from setting "go.formatTool"
This change is similar to CL 709035. The "formatTool" will be the source of truth indicating which tool will be run as formatter. vscode-go will now execute the exact tool based on the setting without any special yielding logic. This change is slightly different from CL 709035. The default value for "formatTool" is "default". To avoid any disruption, the "default" will be interpreted as no formatter provided and gopls will handle the formatting. In addition, an E2E test is added to test the formatter behavior: - Create unformatted go file, and prepare mock formatters. - Execute vscode "Format file" command. - Read the formatted go file from vscode buffer. - Compare the content from buffer with the desired state. For #3861 Change-Id: Ib39b1845fb3b9a058c71dd1ab36f4ad5b9fe22b2 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/713200 Auto-Submit: Hongxiang Jiang <[email protected]> Reviewed-by: Madeline Kalil <[email protected]> Reviewed-by: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 625a648 commit ddae161

File tree

12 files changed

+285
-96
lines changed

12 files changed

+285
-96
lines changed

docs/settings.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -238,15 +238,15 @@ Default:
238238
Flags to pass to format tool (e.g. ["-s"]). Not applicable when using the language server.
239239
### `go.formatTool`
240240

241-
When the language server is enabled and one of `default`/`gofmt`/`goimports`/`gofumpt` is chosen, the language server will handle formatting. If `custom` tool is selected, the extension will use the `customFormatter` tool in the `#go.alternateTools#` section.<br/>
241+
Specifies the tool for formatting Go code. The default is `default`, which uses the language server `gopls` as formatting provider. To configure gopls's formatting, see the 'gopls.formatting' settings. When a specific tool (e.g., `gofmt`, `goimports`) is selected, the extension will run it instead.<br/>
242242
Allowed Options:
243243

244-
* `default`: If the language server is enabled, format via the language server, which already supports gofmt, goimports, goreturns, and gofumpt. Otherwise, goimports.
245-
* `gofmt`: Formats the file according to the standard Go style. (not applicable when the language server is enabled)
246-
* `goimports`: Organizes imports and formats the file with gofmt. (not applicable when the language server is enabled)
247-
* `goformat`: Configurable gofmt, see https://github.com/mbenkmann/goformat. (Deprecated due to the lack of generics support)
248-
* `gofumpt`: Stricter version of gofmt, see https://github.com/mvdan/gofumpt. . Use `#gopls.format.gofumpt#` instead)
249-
* `custom`: Formats using the custom tool specified as `customFormatter` in the `#go.alternateTools#` setting. The tool should take the input as STDIN and output the formatted code as STDOUT.
244+
* `default`: Formatting is performed by the language server, gopls. (recommended)
245+
* `gofmt`: Formats using `gofmt`, the standard Go formatter. See https://pkg.go.dev/cmd/gofmt.
246+
* `goimports`: Formats using `goimports`, which organizes imports and applies `gofmt`. See https://pkg.go.dev/golang.org/x/tools/cmd/goimports.
247+
* `goformat`: Formats using `goformat`, a configurable version of `gofmt`. See https://github.com/mbenkmann/goformat. (Deprecated due to lack of generics support)
248+
* `gofumpt`: Formats using `gofumpt`, a stricter version of `gofmt`. See https://github.com/mvdan/gofumpt. Note: `gopls` can also be configured to use `gofumpt` via the `#gopls.formatting.gofumpt#` setting.
249+
* `custom`: Formats using a custom tool. The tool's path must be specified in the `#go.alternateTools#` setting under the `customFormatter` key.
250250

251251

252252
Default: `"default"`

extension/package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,7 +1236,7 @@
12361236
"go.formatTool": {
12371237
"type": "string",
12381238
"default": "default",
1239-
"markdownDescription": "When the language server is enabled and one of `default`/`gofmt`/`goimports`/`gofumpt` is chosen, the language server will handle formatting. If `custom` tool is selected, the extension will use the `customFormatter` tool in the `#go.alternateTools#` section.",
1239+
"markdownDescription": "Specifies the tool for formatting Go code. The default is `default`, which uses the language server `gopls` as formatting provider. To configure gopls's formatting, see the 'gopls.formatting' settings. When a specific tool (e.g., `gofmt`, `goimports`) is selected, the extension will run it instead.",
12401240
"scope": "resource",
12411241
"enum": [
12421242
"default",
@@ -1247,12 +1247,12 @@
12471247
"custom"
12481248
],
12491249
"markdownEnumDescriptions": [
1250-
"If the language server is enabled, format via the language server, which already supports gofmt, goimports, goreturns, and gofumpt. Otherwise, goimports.",
1251-
"Formats the file according to the standard Go style. (not applicable when the language server is enabled)",
1252-
"Organizes imports and formats the file with gofmt. (not applicable when the language server is enabled)",
1253-
"Configurable gofmt, see https://github.com/mbenkmann/goformat. (Deprecated due to the lack of generics support)",
1254-
"Stricter version of gofmt, see https://github.com/mvdan/gofumpt. . Use `#gopls.format.gofumpt#` instead)",
1255-
"Formats using the custom tool specified as `customFormatter` in the `#go.alternateTools#` setting. The tool should take the input as STDIN and output the formatted code as STDOUT."
1250+
"Formatting is performed by the language server, gopls. (recommended)",
1251+
"Formats using `gofmt`, the standard Go formatter. See https://pkg.go.dev/cmd/gofmt.",
1252+
"Formats using `goimports`, which organizes imports and applies `gofmt`. See https://pkg.go.dev/golang.org/x/tools/cmd/goimports.",
1253+
"Formats using `goformat`, a configurable version of `gofmt`. See https://github.com/mbenkmann/goformat. (Deprecated due to lack of generics support)",
1254+
"Formats using `gofumpt`, a stricter version of `gofmt`. See https://github.com/mvdan/gofumpt. Note: `gopls` can also be configured to use `gofumpt` via the `#gopls.formatting.gofumpt#` setting.",
1255+
"Formats using a custom tool. The tool's path must be specified in the `#go.alternateTools#` setting under the `customFormatter` key."
12561256
]
12571257
},
12581258
"go.formatFlags": {

extension/src/config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,13 @@ This will result in duplicate diagnostics.`;
8686
vscode.commands.executeCommand('workbench.action.openSettingsJson');
8787
}
8888
}
89+
90+
// Format tool check.
91+
// TODO(hxjiang): send warning to user if "go.formatTool" is "gofumpt" and
92+
// "gopls.formatting.gofumpt" is true.
93+
// TODO(hxjiang): send one time notification suggest user to switch "gofmt",
94+
// "gofumpt" and "goimports" formatter to gopls. Configure using setting
95+
// "gopls.formatting".
8996
}
9097

9198
/**

extension/src/goTools.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66

77
'use strict';
88

9-
import moment = require('moment');
10-
import semver = require('semver');
11-
import { getFormatTool, usingCustomFormatTool } from './language/legacy/goFormat';
9+
import * as moment from 'moment';
10+
import * as semver from 'semver';
11+
import * as vscode from 'vscode';
12+
import { getFormatTool } from './language/legacy/goFormat';
1213
import { allToolsInformation } from './goToolsInformation';
1314
import { GoVersion } from './util';
1415

@@ -110,7 +111,10 @@ export function hasModSuffix(tool: Tool): boolean {
110111
return tool.name.endsWith('-gomod');
111112
}
112113

113-
export function getConfiguredTools(goConfig: { [key: string]: any }, goplsConfig: { [key: string]: any }): Tool[] {
114+
export function getConfiguredTools(
115+
goConfig: vscode.WorkspaceConfiguration,
116+
goplsConfig: vscode.WorkspaceConfiguration
117+
): Tool[] {
114118
// If language server is enabled, don't suggest tools that are replaced by gopls.
115119
// TODO(github.com/golang/vscode-go/issues/388): decide what to do when
116120
// the go version is no longer supported by gopls while the legacy tools are
@@ -144,11 +148,7 @@ export function getConfiguredTools(goConfig: { [key: string]: any }, goplsConfig
144148
maybeAddTool('dlv');
145149
}
146150

147-
// Only add format tools if the language server is disabled or the
148-
// format tool is known to us.
149-
if (!useLanguageServer || usingCustomFormatTool(goConfig)) {
150-
maybeAddTool(getFormatTool(goConfig));
151-
}
151+
maybeAddTool(getFormatTool(goConfig));
152152

153153
maybeAddTool(goConfig['lintTool']);
154154

extension/src/language/goLanguageServer.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import {
4040
import { Executable, LanguageClient, ServerOptions } from 'vscode-languageclient/node';
4141
import { getGoConfig, getGoplsConfig, extensionInfo } from '../config';
4242
import { toolExecutionEnvironment } from '../goEnv';
43-
import { GoDocumentFormattingEditProvider, usingCustomFormatTool } from './legacy/goFormat';
43+
import { GoDocumentFormattingEditProvider, getFormatTool } from './legacy/goFormat';
4444
import { installTools, latestModuleVersion, promptForMissingTool, promptForUpdatingTool } from '../goInstallTools';
4545
import { getTool, Tool } from '../goTools';
4646
import { updateGlobalState, updateWorkspaceState } from '../stateUtils';
@@ -79,6 +79,9 @@ export interface LanguageServerConfig {
7979
flags: string[];
8080
env: any;
8181
features: {
82+
// A custom formatter can be configured to run instead of gopls.
83+
// This is enabled when the user has configured a specific format
84+
// tool in the "go.formatTool" setting.
8285
formatter?: GoDocumentFormattingEditProvider;
8386
};
8487
checkForUpdates: string;
@@ -650,9 +653,11 @@ export async function buildLanguageClient(
650653
token: vscode.CancellationToken,
651654
next: ProvideDocumentFormattingEditsSignature
652655
) => {
656+
// If a custom formatter is configured, use it.
653657
if (cfg.features.formatter) {
654658
return cfg.features.formatter.provideDocumentFormattingEdits(document, options, token);
655659
}
660+
// Otherwise, fall back to gopls.
656661
return next(document, options, token);
657662
},
658663
handleDiagnostics: (
@@ -1016,9 +1021,10 @@ export async function buildLanguageServerConfig(
10161021
goConfig: vscode.WorkspaceConfiguration
10171022
): Promise<LanguageServerConfig> {
10181023
let formatter: GoDocumentFormattingEditProvider | undefined;
1019-
if (usingCustomFormatTool(goConfig)) {
1024+
if (getFormatTool(goConfig) !== '') {
10201025
formatter = new GoDocumentFormattingEditProvider();
10211026
}
1027+
10221028
const cfg: LanguageServerConfig = {
10231029
serverName: '', // remain empty if gopls binary can't be found.
10241030
path: '',

extension/src/language/legacy/goFormat.ts

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ import { promptForMissingTool, promptForUpdatingTool } from '../../goInstallTool
1515
import { getBinPath, resolvePath } from '../../util';
1616
import { killProcessTree } from '../../utils/processUtils';
1717

18+
/**
19+
* GoDocumentFormattingEditProvider is a feature that provides formatting
20+
* functionality. It is only used when the user has configured a formatter in
21+
* the "go.formatTool" setting.
22+
*
23+
* By default, the Go extension uses the language server (gopls) to provide
24+
* formatting, so this class is not instantiated.
25+
*/
1826
export class GoDocumentFormattingEditProvider implements vscode.DocumentFormattingEditProvider {
1927
public provideDocumentFormattingEdits(
2028
document: vscode.TextDocument,
@@ -27,7 +35,7 @@ export class GoDocumentFormattingEditProvider implements vscode.DocumentFormatti
2735

2836
const filename = document.fileName;
2937
const goConfig = getGoConfig(document.uri);
30-
const formatFlags = goConfig['formatFlags'].slice() || [];
38+
const formatFlags = goConfig.get<string[]>('formatFlags') ?? [];
3139

3240
// Ignore -w because we don't want to write directly to disk.
3341
if (formatFlags.indexOf('-w') > -1) {
@@ -118,33 +126,24 @@ export class GoDocumentFormattingEditProvider implements vscode.DocumentFormatti
118126
}
119127
}
120128

121-
export function usingCustomFormatTool(goConfig: { [key: string]: any }): boolean {
122-
const formatTool = getFormatTool(goConfig);
123-
switch (formatTool) {
124-
case 'goreturns':
125-
return false;
126-
case 'goimports':
127-
return false;
128-
case 'gofmt':
129-
return false;
130-
case 'gofumpt':
131-
// TODO(rstambler): Prompt to configure setting in gopls.
132-
return false;
133-
case 'gofumports':
134-
// TODO(rstambler): Prompt to configure setting in gopls.
135-
return false;
136-
default:
137-
return true;
138-
}
139-
}
129+
/**
130+
* getFormatTool returns the formatter tool configured through the "go.formatTool"
131+
* setting.
132+
*
133+
* If "go.formatTool" is set to "custom", it returns the "customFormatter"
134+
* specified in "go.alternateTools".
135+
*
136+
* If "go.formatTool" is not set, it returns an empty string, indicating that
137+
* no specific format tool is selected and gopls should be used.
138+
*/
139+
export function getFormatTool(goConfig: vscode.WorkspaceConfiguration): string {
140+
const formatTool = goConfig.get<string>('formatTool');
140141

141-
export function getFormatTool(goConfig: { [key: string]: any }): string {
142-
const formatTool = goConfig['formatTool'];
143-
if (formatTool === 'default') {
144-
return 'goimports';
142+
if (formatTool === undefined || formatTool === 'default') {
143+
return ''; // not specified, yield to gopls by return empty string.
145144
}
146145
if (formatTool === 'custom') {
147-
return resolvePath(goConfig['alternateTools']['customFormatter'] || 'goimports');
146+
return resolvePath(goConfig['alternateTools']['customFormatter']);
148147
}
149148
return formatTool;
150149
}

extension/test/gopls/extension.test.ts

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import assert from 'assert';
88
import * as path from 'path';
99
import * as vscode from 'vscode';
10-
import { getGoConfig } from '../../src/config';
1110
import sinon = require('sinon');
1211
import { getGoVersion, GoVersion } from '../../src/util';
1312
import { GOPLS_MAYBE_PROMPT_FOR_TELEMETRY, recordTelemetryStartTime, TelemetryService } from '../../src/goTelemetry';
@@ -159,48 +158,6 @@ suite('Go Extension Tests With Gopls', function () {
159158
}
160159
});
161160

162-
async function testCustomFormatter(goConfig: vscode.WorkspaceConfiguration, customFormatter: string) {
163-
const config = require('../../src/config');
164-
sandbox.stub(config, 'getGoConfig').returns(goConfig);
165-
const workspaceDir = path.resolve(testdataDir, 'gogetdocTestData');
166-
await env.startGopls(path.join(workspaceDir, 'test.go'), goConfig, workspaceDir);
167-
const { doc } = await env.openDoc(testdataDir, 'gogetdocTestData', 'format.go');
168-
await vscode.window.showTextDocument(doc);
169-
170-
const formatFeature = env.languageClient?.getFeature('textDocument/formatting');
171-
const formatter = formatFeature?.getProvider(doc);
172-
const tokensrc = new vscode.CancellationTokenSource();
173-
try {
174-
const result = await formatter?.provideDocumentFormattingEdits(
175-
doc,
176-
{} as vscode.FormattingOptions,
177-
tokensrc.token
178-
);
179-
assert.fail(`formatter unexpectedly succeeded and returned a result: ${JSON.stringify(result)}`);
180-
} catch (e) {
181-
assert(`${e}`.includes(`errors when formatting with ${customFormatter}`), `${e}`);
182-
}
183-
}
184-
185-
test('Nonexistent formatter', async () => {
186-
const customFormatter = 'nonexistent';
187-
const goConfig = Object.create(getGoConfig(), {
188-
formatTool: { value: customFormatter } // this should make the formatter fail.
189-
}) as vscode.WorkspaceConfiguration;
190-
191-
await testCustomFormatter(goConfig, customFormatter);
192-
});
193-
194-
test('Custom formatter', async () => {
195-
const customFormatter = 'coolCustomFormatter';
196-
const goConfig = Object.create(getGoConfig(), {
197-
formatTool: { value: 'custom' }, // this should make the formatter fail.
198-
alternateTools: { value: { customFormatter: customFormatter } } // this should make the formatter fail.
199-
}) as vscode.WorkspaceConfiguration;
200-
201-
await testCustomFormatter(goConfig, customFormatter);
202-
});
203-
204161
test('Prompt For telemetry', async () => {
205162
const workspaceDir = path.resolve(testdataDir, 'gogetdocTestData');
206163
await env.startGopls(path.join(workspaceDir, 'test.go'), undefined, workspaceDir);

0 commit comments

Comments
 (0)