Skip to content

Commit d431b5e

Browse files
committed
extension/src: remove default setting for "go.lintTool"
The "lintTool" will be the source of truth indicating which tool will be run as linter. vscode-go will now execute the exactly tool based on this setting without any hidden yield logic. Previously, the default value for "go.lintTool" is "staticcheck", vscode-go have special logic that yield the linter functionality to gopls if the lint tool is staticcheck and gopls staticheck is enabled. It confuses the user which tool will be executed. This CL removes this default option from "go.lintTool", consider the new default as "". An empty lintTool indicates the user does not want any linter to run from the extension. This CL also change the description of "go.lintTool" to the lint tool in addition to gopls, also mention in the description that the linting functionality provided by gopls (the language server) can be configured in a seprate setting. To eliminate further confusions, a validation logic will be triggered when gopls or go configuration changed: if "staticcheck" is chosen as lintTool and gopls staticcheck is enabled. Warning will be sent to user to disable one of them to avoid duplicate diagnostics. Alternative considered: introduce "gopls" as default lintTool. For #3862 Change-Id: Ia00ca115ca7ec34315043ad61517b5cdfb9ca1ed Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/709035 Reviewed-by: Madeline Kalil <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent c8ab8fb commit d431b5e

File tree

14 files changed

+506
-198
lines changed

14 files changed

+506
-198
lines changed

docs/settings.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -379,10 +379,15 @@ Allowed Options:
379379
Default: `"package"`
380380
### `go.lintTool`
381381

382-
Specifies Lint tool name.<br/>
383-
Allowed Options: `staticcheck`, `golint`, `golangci-lint`, `golangci-lint-v2`, `revive`
382+
Specifies an additional client-side linting tool that should be run by the Go extension. By default (unset), no additional linter is run. This feature is additional to diagnostics reported by the language server, gopls. Since Gopls incorporates the entire staticcheck analyzer suite, it is typically unnecessary to run the staticcheck tool as well. To configure gopls's linting, see the 'gopls.ui.diagnostic' settings.<br/>
383+
Allowed Options:
384+
385+
* `staticcheck`: Run `staticcheck`.
386+
* `golint`: Run `golint`.
387+
* `golangci-lint`: Run `golangci-lint` v1.
388+
* `golangci-lint-v2`: Run `golangci-lint` v2.
389+
* `revive`: Run `revive`.
384390

385-
Default: `"staticcheck"`
386391
### `go.logging.level (deprecated)`
387392

388393
This setting is deprecated. Use 'Developer: Set Log Level...' command to control logging level instead.

extension/package.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,15 +1182,21 @@
11821182
},
11831183
"go.lintTool": {
11841184
"type": "string",
1185-
"default": "staticcheck",
1186-
"description": "Specifies Lint tool name.",
1185+
"description": "Specifies an additional client-side linting tool that should be run by the Go extension. By default (unset), no additional linter is run. This feature is additional to diagnostics reported by the language server, gopls. Since Gopls incorporates the entire staticcheck analyzer suite, it is typically unnecessary to run the staticcheck tool as well. To configure gopls's linting, see the 'gopls.ui.diagnostic' settings.",
11871186
"scope": "resource",
11881187
"enum": [
11891188
"staticcheck",
11901189
"golint",
11911190
"golangci-lint",
11921191
"golangci-lint-v2",
11931192
"revive"
1193+
],
1194+
"markdownEnumDescriptions": [
1195+
"Run `staticcheck`.",
1196+
"Run `golint`.",
1197+
"Run `golangci-lint` v1.",
1198+
"Run `golangci-lint` v2.",
1199+
"Run `revive`."
11941200
]
11951201
},
11961202
"go.lintFlags": {

extension/src/config.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,64 @@ export class ExtensionInfo {
6060
}
6161

6262
export const extensionInfo = new ExtensionInfo();
63+
64+
/**
65+
* Performs cross-validation between the 'go' and 'gopls' configurations.
66+
*
67+
* This function's purpose is to detect conflicts where a setting in 'go'
68+
* impacts the behavior of the gopls. It should be called when relevant
69+
* settings in either configuration change.
70+
*
71+
* Note: This does not validate gopls settings internally, as the gopls
72+
* server is responsible for that itself.
73+
*/
74+
export async function validateConfig(
75+
goConfig: vscode.WorkspaceConfiguration,
76+
goplsConfig: vscode.WorkspaceConfiguration
77+
) {
78+
// Lint tool check.
79+
const lintTool = goConfig.get<string>('lintTool');
80+
if (lintTool && lintTool === 'staticcheck' && goplsStaticcheckEnabled(goConfig, goplsConfig)) {
81+
const message = `Warning: staticcheck is configured to run both client side (go.lintTool=staticcheck) and server side (gopls.ui.diagnostic.staticcheck=true).
82+
This will result in duplicate diagnostics.`;
83+
84+
const selected = await vscode.window.showWarningMessage(message, 'Open Settings');
85+
if (selected === 'Open Settings') {
86+
vscode.commands.executeCommand('workbench.action.openSettingsJson');
87+
}
88+
}
89+
}
90+
91+
/**
92+
* Checks if the staticcheck analyzer is enabled in the gopls configuration.
93+
*
94+
* @param goConfig The 'go' extension configuration.
95+
* @param goplsConfig The 'gopls' configuration.
96+
* @returns true if staticcheck is enabled in gopls, false otherwise.
97+
*/
98+
export function goplsStaticcheckEnabled(
99+
goConfig: vscode.WorkspaceConfiguration,
100+
goplsConfig: vscode.WorkspaceConfiguration
101+
): boolean {
102+
if (!goConfig.get<boolean>('useLanguageServer')) {
103+
return false;
104+
}
105+
106+
/**
107+
* Direct property access is used here instead of the standard
108+
* `goplsConfig.get()` method.
109+
*
110+
* The `.get('a.b')` API interprets dots as a path to a nested property.
111+
* However, 'gopls' configuration object has "flattened" keys that literally
112+
* contain dots (e.g., the key is the string 'ui.diagnostic.staticcheck').
113+
* Bracket notation is the only way to access such a property.
114+
*/
115+
if (goplsConfig['ui.diagnostic.staticcheck'] === false) {
116+
return false;
117+
}
118+
119+
// "ui.diagnostic.staticcheck" unset or true means partially enabled and
120+
// fully enabled.
121+
// See https://go.googlesource.com/tools/+/refs/heads/master/gopls/doc/analyzers.md
122+
return true;
123+
}

extension/src/goCheck.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
import path = require('path');
1111
import vscode = require('vscode');
12-
import { getGoplsConfig } from './config';
1312
import { goBuild } from './goBuild';
1413
import { goLint } from './goLint';
1514
import { isModSupported } from './goModules';
@@ -120,9 +119,8 @@ export function check(
120119
}
121120

122121
if (lintDiagnosticCollection && !!goConfig['lintOnSave'] && goConfig['lintOnSave'] !== 'off') {
123-
const goplsConfig = getGoplsConfig(fileUri);
124122
runningToolsPromises.push(
125-
goLint(fileUri, goConfig, goplsConfig, goConfig['lintOnSave']).then((errors) => ({
123+
goLint(fileUri, goConfig, goConfig['lintOnSave']).then((errors) => ({
126124
diagnosticCollection: lintDiagnosticCollection,
127125
errors
128126
}))

extension/src/goLint.ts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@
66
import path = require('path');
77
import vscode = require('vscode');
88
import { CommandFactory } from './commands';
9-
import { getGoConfig, getGoplsConfig } from './config';
9+
import { getGoConfig } from './config';
1010
import { toolExecutionEnvironment } from './goEnv';
1111
import { diagnosticsStatusBarItem, outputChannel } from './goStatus';
12-
import { goplsStaticcheckEnabled } from './goTools';
13-
import { inspectGoToolVersion } from './goInstallTools';
14-
import { getBinPath, getWorkspaceFolderPath, handleDiagnosticErrors, ICheckResult, resolvePath, runTool } from './util';
12+
import { getWorkspaceFolderPath, handleDiagnosticErrors, ICheckResult, resolvePath, runTool } from './util';
1513

1614
/**
1715
* Runs linter on the current file, package or workspace.
@@ -34,13 +32,12 @@ export function lintCode(scope?: string): CommandFactory {
3432

3533
const documentUri = editor ? editor.document.uri : undefined;
3634
const goConfig = getGoConfig(documentUri);
37-
const goplsConfig = getGoplsConfig(documentUri);
3835

3936
outputChannel.info('Linting...');
4037
diagnosticsStatusBarItem.show();
4138
diagnosticsStatusBarItem.text = 'Linting...';
4239

43-
goLint(documentUri, goConfig, goplsConfig, scope)
40+
goLint(documentUri, goConfig, scope)
4441
.then((warnings) => {
4542
handleDiagnosticErrors(
4643
goCtx,
@@ -58,7 +55,8 @@ export function lintCode(scope?: string): CommandFactory {
5855
}
5956

6057
/**
61-
* Runs linter and presents the output in the 'Go' channel and in the diagnostic collections.
58+
* Runs linter and presents the output in the 'Go' channel and in the diagnostic
59+
* collections.
6260
*
6361
* @param fileUri Document uri.
6462
* @param goConfig Configuration for the Go extension.
@@ -67,11 +65,11 @@ export function lintCode(scope?: string): CommandFactory {
6765
export async function goLint(
6866
fileUri: vscode.Uri | undefined,
6967
goConfig: vscode.WorkspaceConfiguration,
70-
goplsConfig: vscode.WorkspaceConfiguration,
7168
scope?: string
7269
): Promise<ICheckResult[]> {
73-
const lintTool = goConfig['lintTool'] || 'staticcheck';
74-
if (lintTool === 'staticcheck' && goplsStaticcheckEnabled(goConfig, goplsConfig)) {
70+
const linter = goConfig.get<string>('lintTool');
71+
if (linter === undefined || linter === '') {
72+
// Yield the linter functionality to gopls by returning empty.
7573
return Promise.resolve([]);
7674
}
7775

@@ -93,7 +91,7 @@ export async function goLint(
9391
return Promise.resolve([]);
9492
}
9593

96-
const lintFlags: string[] = goConfig['lintFlags'] || [];
94+
const lintFlags = goConfig.get<string[]>('lintFlags') === undefined ? [] : goConfig.get<string[]>('lintFlags')!;
9795
const lintEnv = toolExecutionEnvironment();
9896
const args: string[] = [];
9997

@@ -113,9 +111,9 @@ export async function goLint(
113111
}
114112
args.push(flag);
115113
});
116-
if (lintTool.startsWith('golangci-lint')) {
114+
if (linter.startsWith('golangci-lint')) {
117115
let version = 1;
118-
if (lintTool === 'golangci-lint-v2') {
116+
if (linter === 'golangci-lint-v2') {
119117
version = 2;
120118
}
121119

@@ -174,7 +172,7 @@ export async function goLint(
174172
}
175173

176174
running = true;
177-
const lintPromise = runTool(args, cwd, 'warning', false, lintTool, lintEnv, false, tokenSource.token).then(
175+
const lintPromise = runTool(args, cwd, 'warning', false, linter, lintEnv, false, tokenSource.token).then(
178176
(result) => {
179177
if (closureEpoch === epoch) {
180178
running = false;

extension/src/goMain.ts

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
'use strict';
1010

11-
import { extensionInfo, getGoConfig } from './config';
1211
import { browsePackages } from './goBrowsePackage';
1312
import { buildCode } from './goBuild';
1413
import { notifyIfGeneratedFile, removeTestStatus } from './goCheck';
@@ -39,7 +38,7 @@ import {
3938
maybeInstallVSCGO,
4039
maybeInstallImportantTools
4140
} from './goInstallTools';
42-
import { RestartReason, showServerOutputChannel, watchLanguageServerConfiguration } from './language/goLanguageServer';
41+
import { RestartReason, showServerOutputChannel, promptAboutGoplsOptOut } from './language/goLanguageServer';
4342
import { lintCode } from './goLint';
4443
import { GO_MODE } from './goMode';
4544
import { GO111MODULE, goModInit } from './goModules';
@@ -58,10 +57,8 @@ import {
5857
} from './stateUtils';
5958
import { cancelRunningTests, showTestOutput } from './testUtils';
6059
import { cleanupTempDir, getBinPath, getToolsGopath } from './util';
61-
import { clearCacheForTools } from './utils/pathUtils';
6260
import { WelcomePanel } from './welcome';
6361
import vscode = require('vscode');
64-
import { getFormatTool } from './language/legacy/goFormat';
6562
import { resetSurveyStates, showSurveyStates } from './goSurvey';
6663
import { ExtensionAPI } from './export';
6764
import extensionAPI from './extensionAPI';
@@ -75,6 +72,9 @@ import { toggleVulncheckCommandFactory } from './goVulncheck';
7572
import { GoTaskProvider } from './goTaskProvider';
7673
import { setTelemetryEnvVars, activationLatency, telemetryReporter } from './goTelemetry';
7774
import { experiments } from './experimental';
75+
import { extensionInfo, getGoConfig, getGoplsConfig, validateConfig } from './config';
76+
import { clearCacheForTools } from './utils/pathUtils';
77+
import { getFormatTool } from './language/legacy/goFormat';
7878

7979
const goCtx: GoExtensionContext = {};
8080

@@ -222,7 +222,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<ExtensionA
222222
registerCommand('go.survey.showConfig', showSurveyStates);
223223
registerCommand('go.survey.resetConfig', resetSurveyStates);
224224

225-
addOnDidChangeConfigListeners(ctx);
225+
addConfigChangeListener(ctx);
226226
addOnChangeTextDocumentListeners(ctx);
227227
addOnChangeActiveTextEditorListeners(ctx);
228228
addOnSaveTextDocumentListeners(ctx);
@@ -251,21 +251,48 @@ export function deactivate() {
251251
]);
252252
}
253253

254-
function addOnDidChangeConfigListeners(ctx: vscode.ExtensionContext) {
254+
export function addConfigChangeListener(ctx: vscode.ExtensionContext) {
255255
// Subscribe to notifications for changes to the configuration
256256
// of the language server, even if it's not currently in use.
257257
ctx.subscriptions.push(
258-
vscode.workspace.onDidChangeConfiguration((e) => watchLanguageServerConfiguration(goCtx, e))
258+
vscode.workspace.onDidChangeConfiguration((e) => {
259+
const goConfig = getGoConfig();
260+
const goplsConfig = getGoplsConfig();
261+
262+
validateConfig(goConfig, goplsConfig);
263+
264+
if (!e.affectsConfiguration('go')) {
265+
return;
266+
}
267+
268+
if (
269+
e.affectsConfiguration('go.useLanguageServer') ||
270+
e.affectsConfiguration('go.languageServerFlags') ||
271+
e.affectsConfiguration('go.alternateTools') ||
272+
e.affectsConfiguration('go.toolsEnvVars') ||
273+
e.affectsConfiguration('go.formatTool')
274+
// TODO: Should we check http.proxy too? That affects toolExecutionEnvironment too.
275+
) {
276+
vscode.commands.executeCommand('go.languageserver.restart', RestartReason.CONFIG_CHANGE);
277+
}
278+
279+
if (e.affectsConfiguration('go.useLanguageServer') && goConfig['useLanguageServer'] === false) {
280+
promptAboutGoplsOptOut(goCtx);
281+
}
282+
})
259283
);
260284
ctx.subscriptions.push(
261285
vscode.workspace.onDidChangeConfiguration(async (e: vscode.ConfigurationChangeEvent) => {
262286
if (!e.affectsConfiguration('go')) {
263287
return;
264288
}
265-
const updatedGoConfig = getGoConfig();
289+
const goConfig = getGoConfig();
290+
const goplsConfig = getGoplsConfig();
291+
292+
validateConfig(goConfig, goplsConfig);
266293

267294
if (e.affectsConfiguration('go.goroot')) {
268-
const configGOROOT = updatedGoConfig['goroot'];
295+
const configGOROOT = goConfig['goroot'];
269296
if (configGOROOT) {
270297
await setGOROOTEnvVar(configGOROOT);
271298
}
@@ -285,25 +312,19 @@ function addOnDidChangeConfigListeners(ctx: vscode.ExtensionContext) {
285312
}
286313

287314
if (e.affectsConfiguration('go.formatTool')) {
288-
const tool = getFormatTool(updatedGoConfig);
315+
const tool = getFormatTool(goConfig);
289316
// When language server gopls is active (by default), existence
290317
// checks are skipped only for tools that gopls replaces. Other
291318
// tools are still checked.
292-
if (
293-
updatedGoConfig['useLanguageServer'] === false ||
294-
!new Set(['gofmt', 'goimports', 'goformat']).has(tool)
295-
) {
319+
if (goConfig['useLanguageServer'] === false || !new Set(['gofmt', 'goimports', 'goformat']).has(tool)) {
296320
checkToolExists(tool);
297321
}
298322
}
299-
if (e.affectsConfiguration('go.lintTool')) {
300-
checkToolExists(updatedGoConfig['lintTool']);
301-
}
302323
if (e.affectsConfiguration('go.docsTool')) {
303-
checkToolExists(updatedGoConfig['docsTool']);
324+
checkToolExists(goConfig['docsTool']);
304325
}
305326
if (e.affectsConfiguration('go.coverageDecorator')) {
306-
updateCodeCoverageDecorators(updatedGoConfig['coverageDecorator']);
327+
updateCodeCoverageDecorators(goConfig['coverageDecorator']);
307328
}
308329
if (e.affectsConfiguration('go.toolsEnvVars')) {
309330
const env = toolExecutionEnvironment();
@@ -318,7 +339,9 @@ function addOnDidChangeConfigListeners(ctx: vscode.ExtensionContext) {
318339
}
319340
}
320341
if (e.affectsConfiguration('go.lintTool')) {
321-
const lintTool = lintDiagnosticCollectionName(updatedGoConfig['lintTool']);
342+
checkToolExists(goConfig['lintTool']);
343+
344+
const lintTool = lintDiagnosticCollectionName(goConfig['lintTool']);
322345
if (goCtx.lintDiagnosticCollection && goCtx.lintDiagnosticCollection.name !== lintTool) {
323346
goCtx.lintDiagnosticCollection.dispose();
324347
goCtx.lintDiagnosticCollection = vscode.languages.createDiagnosticCollection(lintTool);
@@ -381,6 +404,9 @@ function addOnChangeActiveTextEditorListeners(ctx: vscode.ExtensionContext) {
381404
}
382405

383406
function checkToolExists(tool: string) {
407+
if (tool === '') {
408+
return;
409+
}
384410
if (tool === getBinPath(tool)) {
385411
promptForMissingTool(tool);
386412
}

extension/src/goTools.ts

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -150,26 +150,9 @@ export function getConfiguredTools(goConfig: { [key: string]: any }, goplsConfig
150150
maybeAddTool(getFormatTool(goConfig));
151151
}
152152

153-
// Add the linter that was chosen by the user, but don't add staticcheck
154-
// if it is enabled via gopls.
155-
const goplsStaticheckEnabled = useLanguageServer && goplsStaticcheckEnabled(goConfig, goplsConfig);
156-
if (goConfig['lintTool'] !== 'staticcheck' || !goplsStaticheckEnabled) {
157-
maybeAddTool(goConfig['lintTool']);
158-
}
153+
maybeAddTool(goConfig['lintTool']);
159154

160-
// Remove duplicates since tools like golangci-lint v2 are both linter and formatter
155+
// Remove duplicates since tools like golangci-lint v2 and gopls are both
156+
// capable of linting and formatting.
161157
return tools.filter((v, i, self) => self.indexOf(v) === i);
162158
}
163-
164-
export function goplsStaticcheckEnabled(
165-
goConfig: { [key: string]: any },
166-
goplsConfig: { [key: string]: any }
167-
): boolean {
168-
if (
169-
goplsConfig['ui.diagnostic.staticcheck'] === false ||
170-
(goplsConfig['ui.diagnostic.staticcheck'] === undefined && goplsConfig['staticcheck'] !== true)
171-
) {
172-
return false;
173-
}
174-
return true;
175-
}

0 commit comments

Comments
 (0)