From 2f76c77fd4e25cba98fbaebbc684849a662b280c Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Thu, 27 Feb 2025 11:33:10 -0800 Subject: [PATCH 01/39] CHANGELOG.md: add release heading for v0.46.0 This is an automated CL which updates the CHANGELOG.md. Change-Id: I1e66f469b76af1f57781c4af8569fec537e59703 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/653415 Auto-Submit: Gopher Robot kokoro-CI: kokoro Reviewed-by: Hongxiang Jiang Reviewed-by: Madeline Kalil LUCI-TryBot-Result: Go LUCI --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a20f22cbb..2e4fdb8d3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## Unreleased +## v0.46.0 + +Date: 2025-02-27 + ### Code Health * The testing framework is migrated from `Kokoro` to `LUCI`. The presubmit tests will be triggered against multiple go versions. From d30437cad46af2483023d07b9e2d06582930801d Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Thu, 27 Feb 2025 12:57:38 -0800 Subject: [PATCH 02/39] extension/package.json: update version to 0.48.0-dev This is an automated CL which updates the package.json and package-lock.json. Change-Id: I8cab906d6981592a81a42cfd21ddb0b2f2569764 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/653416 Reviewed-by: Hongxiang Jiang LUCI-TryBot-Result: Go LUCI Reviewed-by: Madeline Kalil Auto-Submit: Gopher Robot kokoro-CI: kokoro --- extension/package-lock.json | 4 ++-- extension/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extension/package-lock.json b/extension/package-lock.json index fd2cbee086..92d3416bac 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "go", - "version": "0.46.0-dev", + "version": "0.48.0-dev", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "go", - "version": "0.46.0-dev", + "version": "0.48.0-dev", "license": "MIT", "dependencies": { "diff": "4.0.2", diff --git a/extension/package.json b/extension/package.json index 12755b0c38..2233693e8f 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1,7 +1,7 @@ { "name": "go", "displayName": "Go", - "version": "0.46.0-dev", + "version": "0.48.0-dev", "publisher": "golang", "description": "Rich Go language support for Visual Studio Code", "author": { From 1579d6e07994bd2b0e37fc95088622c87aea57d0 Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Thu, 27 Feb 2025 14:06:26 -0800 Subject: [PATCH 03/39] CHANGELOG.md: add release heading for v0.47.0 This is an automated CL which updates the CHANGELOG.md. Change-Id: I2c66df4877242f19a751d3e14e43980de8784a7a Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/653475 Auto-Submit: Gopher Robot kokoro-CI: kokoro Reviewed-by: Hongxiang Jiang Reviewed-by: Madeline Kalil LUCI-TryBot-Result: Go LUCI --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e4fdb8d3d..fcf176df69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## Unreleased +## v0.47.0 (prerelease) + +Date: 2025-02-27 + +This is the [pre-release version](https://code.visualstudio.com/api/working-with-extensions/publishing-extension#prerelease-extensions) of v0.48. + +**Full Changelog**: https://github.com/golang/vscode-go/compare/v0.46.0-rc.1...v0.47.0 +**Milestone**: https://github.com/golang/vscode-go/issues?q=milestone%3Av0.48.0 + ## v0.46.0 Date: 2025-02-27 From 25da9746f7e09c3ad06d8ffdc82efb5b7c125f3d Mon Sep 17 00:00:00 2001 From: Hongxiang Jiang Date: Mon, 3 Mar 2025 10:22:01 -0500 Subject: [PATCH 04/39] extension/src: add separator in command go.environment.choose Go: Choose Go Environment command will separate the options in three parts: - Fixed options: selecting go from file browser & clear selection. - Locally discovered: go currently being selected or available in $HOME/sdk. - Downloadable: go version that is downloadable from golang.org/dl. UI screenshot before: https://github.com/user-attachments/assets/094fe705-886f-4074-a564-b8021208ebe2 UI screenshot after: https://github.com/user-attachments/assets/6f5858f9-9c78-4610-9fa0-da30c78bb7c9 https://google.github.io/styleguide/tsguide.html#comments-documentation - JSDoc for documentation, line comments for implementation. - Method/Function comment should be written as if there is an implied "This method ..." before it. For golang/vscode-go#3697 Change-Id: I779bbd978103974b7c3e68a56820934312cdce04 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/654215 LUCI-TryBot-Result: Go LUCI Auto-Submit: Hongxiang Jiang kokoro-CI: kokoro Reviewed-by: Peter Weinberger --- extension/src/goEnvironmentStatus.ts | 173 ++++++++++++++++----------- 1 file changed, 102 insertions(+), 71 deletions(-) diff --git a/extension/src/goEnvironmentStatus.ts b/extension/src/goEnvironmentStatus.ts index 9efd868fe1..eaddf6dd11 100644 --- a/extension/src/goEnvironmentStatus.ts +++ b/extension/src/goEnvironmentStatus.ts @@ -65,8 +65,9 @@ function canChooseGoEnvironment() { return { ok: true }; } + /** - * Present a command palette menu to the user to select their go binary + * Presents a command palette menu to the user to select their go binary. */ export const chooseGoEnvironment: CommandFactory = () => async () => { if (!goEnvStatusbarItem) { @@ -78,48 +79,63 @@ export const chooseGoEnvironment: CommandFactory = () => async () => { return; } - // fetch default go and uninstalled go versions - let defaultOption: GoEnvironmentOption | undefined; - let uninstalledOptions: GoEnvironmentOption[]; - let goSDKOptions: GoEnvironmentOption[]; + let options: vscode.QuickPickItem[] = [ + // Option to choose go binary from file browser. + { + label: CHOOSE_FROM_FILE_BROWSER, + description: 'Select the go binary to use' + }, + // Option to clear the existing selection. + { label: CLEAR_SELECTION } + ]; try { - [defaultOption, uninstalledOptions, goSDKOptions] = await Promise.all([ - getDefaultGoOption(), - fetchDownloadableGoVersions(), - getSDKGoOptions() - ]); + const seenDescriptions = new Set(); + const seenLabels = new Set(); + // addOption adds the option to the input array only if it is unique, + // based on its description and label. + const addOption = (options: GoEnvironmentOption[], option: GoEnvironmentOption | undefined) => { + if (option === undefined) { + return; + } + if (!seenDescriptions.has(option.description) && !seenLabels.has(option.label)) { + seenDescriptions.add(option.description); + seenLabels.add(option.label); + options.push(option); + } + }; + + const defaultOption = await Promise.resolve(getDefaultGoOption()); + const goSDKOptions = await getSDKGoOptions(); + + const local: GoEnvironmentOption[] = []; + addOption(local, defaultOption); + goSDKOptions.forEach((option) => addOption(local, option)); + + if (local.length > 0) { + options.push({ kind: vscode.QuickPickItemKind.Separator, label: 'Locally discovered' }); + options.push(...local); + } + + const downloadableOptions = await getDownloadableGoVersions(); + const downloadable: GoEnvironmentOption[] = []; + downloadableOptions.forEach((option) => addOption(downloadable, option)); + + if (downloadable.length > 0) { + options.push({ kind: vscode.QuickPickItemKind.Separator, label: 'Downloadable' }); + options.push(...downloadable); + } } catch (e) { vscode.window.showErrorMessage((e as Error).message); return; } - // create quick pick items - const defaultQuickPick = defaultOption ? [defaultOption] : []; - - // dedup options by eliminating duplicate paths (description) - const clearOption: vscode.QuickPickItem = { label: CLEAR_SELECTION }; - const filePickerOption: vscode.QuickPickItem = { - label: CHOOSE_FROM_FILE_BROWSER, - description: 'Select the go binary to use' - }; - // TODO(hyangah): Add separators after clearOption if github.com/microsoft/vscode#74967 is resolved. - const options = [filePickerOption, clearOption, ...defaultQuickPick, ...goSDKOptions, ...uninstalledOptions].reduce( - (opts, nextOption) => { - if (opts.find((op) => op.description === nextOption.description || op.label === nextOption.label)) { - return opts; - } - return [...opts, nextOption]; - }, - [] as vscode.QuickPickItem[] - ); - - // get user's selection, return if none was made + // Get user's selection, return if none was made. const selection = await vscode.window.showQuickPick(options); if (!selection) { return; } - // update currently selected go + // Update currently selected go. try { await setSelectedGo(selection); } catch (e) { @@ -128,17 +144,18 @@ export const chooseGoEnvironment: CommandFactory = () => async () => { }; /** - * update the selected go path and label in the workspace state + * Updates the selected go path and label in the workspace state. + * @returns true if set successfully, false otherwise. */ export async function setSelectedGo(goOption: vscode.QuickPickItem, promptReload = true): Promise { if (!goOption) { return false; } - // if the selected go version is not installed, install it + // If the selected go version is not installed, install it. if (goOption instanceof GoEnvironmentOption) { const o = goOption.available ? (goOption as GoEnvironmentOption) : await downloadGo(goOption); - // check that the given binary is not already at the beginning of the PATH + // Check that the given binary is not already at the beginning of the PATH. const go = await getGoVersion(); if (!!go && (go.binaryPath === o.binpath || 'Go ' + go.format() === o.label)) { return false; @@ -183,7 +200,8 @@ export async function setSelectedGo(goOption: vscode.QuickPickItem, promptReload } } } - // prompt the user to reload the window. + // Show modal dialog to the user to reload the window, this require user's + // immediate attention. // promptReload defaults to true and should only be false for tests. if (promptReload) { const choice = await vscode.window.showWarningMessage( @@ -203,7 +221,9 @@ export async function setSelectedGo(goOption: vscode.QuickPickItem, promptReload return true; } -// downloadGo downloads the specified go version available in dl.golang.org. +/** + * Downloads the specified go version available in dl.golang.org. + */ async function downloadGo(goOption: GoEnvironmentOption): Promise { if (goOption.available) { return Promise.resolve(goOption); @@ -268,7 +288,9 @@ async function downloadGo(goOption: GoEnvironmentOption): Promise { if ( !terminal || - // don't interfere if this terminal was created to run a Go task (goTaskProvider.ts). + // Don't interfere if this terminal was created to run a Go task (goTaskProvider.ts). // Go task uses ProcessExecution which results in the terminal having `go` or `go.exe` // as its shellPath. (isTerminalOptions(terminal.creationOptions) && @@ -385,7 +411,7 @@ export async function updateIntegratedTerminal(terminal: vscode.Terminal): Promi return; } - // append the goroot to the beginning of the PATH so it takes precedence + // Append the goroot to the beginning of the PATH so it takes precedence. // TODO: add support for more terminal names if (vscode.env.shell.search(/(powershell|pwsh)$/i) !== -1) { terminal.sendText(`$env:Path="${gorootBin};$env:Path"`, true); @@ -400,14 +426,14 @@ export async function updateIntegratedTerminal(terminal: vscode.Terminal): Promi } /** - * retreive the current selected Go from the workspace state + * Retreives the current selected Go from the workspace state. */ export function getSelectedGo(): GoEnvironmentOption { return getFromWorkspaceState('selectedGo'); } /** - * return reference to the statusbar item + * @returns reference to the statusbar item. */ export function getGoEnvironmentStatusbarItem(): vscode.StatusBarItem { return goEnvStatusbarItem; @@ -427,8 +453,11 @@ export function formatGoVersion(version?: GoVersion): string { } } +/** + * @returns go versions available in `$HOME/sdk`. + */ async function getSDKGoOptions(): Promise { - // get list of Go versions + // Get list of Go versions. const sdkPath = path.join(os.homedir(), 'sdk'); if (!(await dirExists(sdkPath))) { @@ -436,8 +465,8 @@ async function getSDKGoOptions(): Promise { } const readdir = promisify(fs.readdir); const subdirs = await readdir(sdkPath); - // the dir happens to be the version, which will be used as the label - // the path is assembled and used as the description + // The dir happens to be the version, which will be used as the label. + // The path is assembled and used as the description. return subdirs.map( (dir: string) => new GoEnvironmentOption(path.join(sdkPath, dir, 'bin', correctBinname('go')), dir.replace('go', 'Go ')) @@ -445,26 +474,28 @@ async function getSDKGoOptions(): Promise { } export async function getDefaultGoOption(): Promise { - // make goroot default to go.goroot + // Make goroot default to "go.goroot" in vscode-go settings. const goroot = getCurrentGoRoot(); if (!goroot) { return undefined; } - // set Go version and command + // Set Go version and command. const version = await getGoVersion(); return new GoEnvironmentOption(path.join(goroot, 'bin', correctBinname('go')), formatGoVersion(version)); } /** - * make a web request to get versions of Go + * Makes a web request to get versions of Go. */ interface GoVersionWebResult { version: string; stable: boolean; } - -async function fetchDownloadableGoVersions(): Promise { +/** + * @returns downloadable go versions from `golang.org/dl`. + */ +async function getDownloadableGoVersions(): Promise { // TODO: use `go list -m --versions -json go` when go1.20+ is the minimum supported version. // fetch information about what Go versions are available to install let webResults; @@ -478,13 +509,13 @@ async function fetchDownloadableGoVersions(): Promise { if (!webResults) { return []; } - // turn the web result into GoEnvironmentOption model - return webResults.reduce((opts, result: GoVersionWebResult) => { + // Turn the web result into GoEnvironmentOption model. + return webResults.reduce((opts: GoEnvironmentOption[], result: GoVersionWebResult) => { // TODO: allow downloading from different sites const dlPath = `golang.org/dl/${result.version}`; const label = result.version.replace('go', 'Go '); return [...opts, new GoEnvironmentOption(dlPath, label, false)]; - }, [] as GoEnvironmentOption[]); + }, []); } export const latestGoVersionKey = 'latestGoVersions'; @@ -501,10 +532,10 @@ export async function getLatestGoVersions(): Promise { if (cachedResults && now - cachedResults.timestamp < timeout) { results = cachedResults.goVersions; } else { - // fetch the latest supported Go versions + // Fetch the latest supported Go versions. try { - // fetch the latest Go versions and cache the results - results = await fetchDownloadableGoVersions(); + // Fetch the latest Go versions and cache the results. + results = await getDownloadableGoVersions(); await updateGlobalState(latestGoVersionKey, { timestamp: now, goVersions: results @@ -535,20 +566,20 @@ export async function offerToInstallLatestGoVersion(ctx: Pick !dismissedOptions.find((x) => x.label === version.label)); } - // compare to current go version. + // Compare to current go version. const currentVersion = await getGoVersion(); if (currentVersion) { options = options.filter((version) => currentVersion.lt(version.label)); } - // notify user that there is a newer version of Go available + // Notify user that there is a newer version of Go available. if (options.length > 0) { const versionsText = options.map((x) => x.label).join(', '); const statusBarItem = addGoStatus(STATUS_BAR_ITEM_NAME); @@ -576,7 +607,7 @@ export async function offerToInstallLatestGoVersion(ctx: Pick Date: Tue, 4 Mar 2025 11:04:18 -0500 Subject: [PATCH 05/39] extension/src/language: check token field existence before assertion The error happens when the workspace/executeCommand returns results without any token. vscode-go tried to assert the type of "Token" field without checking for it's existence. Command with token returned (gopls.vulncheck): https://github.com/user-attachments/assets/931524ba-a40b-4a8c-b7af-a350a4c9a135 Command without token returned (gopls.upgrade_dependency): https://github.com/user-attachments/assets/cb23c5bc-7d0b-4e60-aa4e-66ba1fed31d8 For golang/vscode-go#3698 Change-Id: I07183b7e0f30912a6b3f6c0ab53a40624ccd7880 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/654555 Reviewed-by: Robert Findley LUCI-TryBot-Result: Go LUCI kokoro-CI: kokoro Reviewed-by: Madeline Kalil --- extension/src/language/goLanguageServer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/src/language/goLanguageServer.ts b/extension/src/language/goLanguageServer.ts index b446e8c0a7..5923aa88f6 100644 --- a/extension/src/language/goLanguageServer.ts +++ b/extension/src/language/goLanguageServer.ts @@ -565,7 +565,7 @@ export async function buildLanguageClient( } const res = await next(command, args); - const progressToken = res.Token; + const progressToken = res?.Token as ProgressToken; // The progressToken from executeCommand indicates that // gopls may trigger a related workDoneProgress // notification, either before or after the command From 82dc98bbbdaef7cf6810f0e6cb94d5bea5081421 Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Tue, 4 Mar 2025 10:29:27 -0800 Subject: [PATCH 06/39] CHANGELOG.md: add release heading for v0.47.1 This is an automated CL which updates the CHANGELOG.md. Change-Id: I0bd2235d828ee3f84bf79455dbfd797a8272fca0 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/654417 LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley kokoro-CI: kokoro Reviewed-by: Hongxiang Jiang Auto-Submit: Gopher Robot --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcf176df69..a0e5e333ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## Unreleased +## v0.47.1 (prerelease) + +Date: 2025-03-04 + +This is the [pre-release version](https://code.visualstudio.com/api/working-with-extensions/publishing-extension#prerelease-extensions) of v0.48. + +**Full Changelog**: https://github.com/golang/vscode-go/compare/v0.46.0-rc.1...v0.47.1 +**Milestone**: https://github.com/golang/vscode-go/issues?q=milestone%3Av0.48.0 + ## v0.47.0 (prerelease) Date: 2025-02-27 From e988883f7500dc8b443798c35b7ce1fc203a0eae Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Tue, 4 Mar 2025 14:36:05 -0800 Subject: [PATCH 07/39] CHANGELOG.md: add release heading for v0.46.1 This is an automated CL which updates the CHANGELOG.md. Change-Id: I09564d6f2261b837e803a77d82254de3c8ba3f82 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/654420 Auto-Submit: Gopher Robot Reviewed-by: Alan Donovan kokoro-CI: kokoro Reviewed-by: Hongxiang Jiang LUCI-TryBot-Result: Go LUCI --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0e5e333ff..820a1fdcf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## Unreleased +## v0.46.1 + +Date: 2025-03-04 + +**Full Changelog**: https://github.com/golang/vscode-go/compare/v0.46.1...v0.46.1 +**Milestone**: https://github.com/golang/vscode-go/issues?q=milestone%3Av0.46.1 + ## v0.47.1 (prerelease) Date: 2025-03-04 From b1e08dadf496d801759f96d24f937eb642910868 Mon Sep 17 00:00:00 2001 From: Hongxiang Jiang Date: Tue, 4 Mar 2025 18:13:45 -0500 Subject: [PATCH 08/39] CHANGELOG.md: update change log to reflect recent changes Fix the link showing the diff between v0.46.1 and v0.46.0. Permanent fix in x/build CL 654815 Change-Id: I2728557213b5665ef806bd70b6bc03858f8ae38a Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/654855 Auto-Submit: Hongxiang Jiang LUCI-TryBot-Result: Go LUCI kokoro-CI: kokoro Reviewed-by: Madeline Kalil --- CHANGELOG.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 820a1fdcf8..5e3a8b00f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,22 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## Unreleased +### Changes + +* Introduced quick pick separator in command `Go: Choose Go Environment` showing +diff between options locally discovered and options available for download. + +### Fixes + +* Addressed an issue that caused a `Cannot read properties of null (reading 'Token')` +error during command execution when the command result did not include a token. +([Issue 3698](https://github.com/golang/vscode-go/issues/3698)) + ## v0.46.1 Date: 2025-03-04 -**Full Changelog**: https://github.com/golang/vscode-go/compare/v0.46.1...v0.46.1 +**Full Changelog**: https://github.com/golang/vscode-go/compare/v0.46.0...v0.46.1 **Milestone**: https://github.com/golang/vscode-go/issues?q=milestone%3Av0.46.1 ## v0.47.1 (prerelease) From 97c9ecd4d2cb2748bbb707db859c603b1ee7e5a3 Mon Sep 17 00:00:00 2001 From: Madeline Kalil Date: Wed, 26 Feb 2025 16:18:18 -0500 Subject: [PATCH 09/39] extension/src: add integration test for package symbols outline Change-Id: I96b93f48a2ee625597ff906e1eab6d39021b53fa Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/653035 Reviewed-by: Hongxiang Jiang LUCI-TryBot-Result: Go LUCI kokoro-CI: kokoro --- extension/src/goPackageOutline.ts | 2 +- .../test/integration/goPackageOutline.test.ts | 110 ++++++++++++++++++ .../testdata/packageOutlineTest/symbols_1.go | 22 ++++ .../testdata/packageOutlineTest/symbols_2.go | 9 ++ .../testdata/packageOutlineTest/symbols_3.ts | 0 5 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 extension/test/integration/goPackageOutline.test.ts create mode 100644 extension/test/testdata/packageOutlineTest/symbols_1.go create mode 100644 extension/test/testdata/packageOutlineTest/symbols_2.go create mode 100644 extension/test/testdata/packageOutlineTest/symbols_3.ts diff --git a/extension/src/goPackageOutline.ts b/extension/src/goPackageOutline.ts index 486f61f3ce..ce57fdfd7b 100644 --- a/extension/src/goPackageOutline.ts +++ b/extension/src/goPackageOutline.ts @@ -167,7 +167,7 @@ interface PackageSymbolData { file: number; } -class PackageSymbol extends vscode.TreeItem { +export class PackageSymbol extends vscode.TreeItem { constructor( private readonly data: PackageSymbolData, private readonly files: string[], diff --git a/extension/test/integration/goPackageOutline.test.ts b/extension/test/integration/goPackageOutline.test.ts new file mode 100644 index 0000000000..e81dba7e40 --- /dev/null +++ b/extension/test/integration/goPackageOutline.test.ts @@ -0,0 +1,110 @@ +/*--------------------------------------------------------- + * Copyright 2025 The Go Authors. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------*/ + +import assert from 'assert'; +import path from 'path'; +import { MockExtensionContext } from '../mocks/MockContext'; +import { GoPackageOutlineProvider, PackageSymbol } from '../../src/goPackageOutline'; +import { updateGoVarsFromConfig } from '../../src/goInstallTools'; +import { window } from 'vscode'; +import { Env } from '../gopls/goplsTestEnv.utils'; + +import { getGoConfig } from '../../src/config'; + +import vscode = require('vscode'); + +suite('GoPackageOutlineProvider', function () { + this.timeout(20000); + let provider: GoPackageOutlineProvider; + const fixtureDir = path.join(__dirname, '../../../test/testdata/packageOutlineTest'); + const ctx = MockExtensionContext.new(); + const env = new Env(); + + suiteSetup(async () => { + await updateGoVarsFromConfig({}); + await env.startGopls(undefined, getGoConfig(), fixtureDir); + provider = GoPackageOutlineProvider.setup(ctx); + }); + + suiteTeardown(() => { + ctx.teardown(); + }); + + test('opening a document should trigger package outline response', async () => { + const document = await vscode.workspace.openTextDocument( + vscode.Uri.file(path.join(fixtureDir, 'symbols_1.go')) + ); + await window.showTextDocument(document); + await sleep(500); // wait for gopls response + const res = provider.result; + assert.strictEqual(res?.PackageName, 'package_outline_test'); + assert.strictEqual(res?.Files.length, 2); + assert.strictEqual(res?.Symbols.length, 3); + assert.strictEqual(res?.Symbols[0].name, 'TestReceiver'); + assert.strictEqual(res?.Symbols[0].children.length, 6); // 3 fields and 3 receiver methods + }); + + test('clicking on symbol should navigate to definition', async () => { + const document = await vscode.workspace.openTextDocument( + vscode.Uri.file(path.join(fixtureDir, 'symbols_1.go')) + ); + await window.showTextDocument(document); + await sleep(500); // wait for gopls response + await vscode.commands.executeCommand('setContext', 'go.showPackageOutline'); + const children = await provider.getChildren(); + const receiver = children?.find((symbol) => symbol.label === 'TestReceiver'); + assert.ok(receiver, 'receiver symbol not found'); + const method1 = receiver.children?.find((symbol) => symbol.label === 'method1'); + assert.ok(method1, 'method1 symbol not found'); + clickSymbol(method1); + await sleep(500); // wait for editor to navigate to symbol + assert.strictEqual(window.activeTextEditor?.document.uri.fsPath, document.uri.fsPath); + assert.strictEqual(window.activeTextEditor?.selection.active.line, 19); + assert.strictEqual(window.activeTextEditor?.selection.active.character, 0); + }); + + test('clicking on symbol in different file should open file', async () => { + const document = await vscode.workspace.openTextDocument( + vscode.Uri.file(path.join(fixtureDir, 'symbols_1.go')) + ); + await window.showTextDocument(document); + await sleep(500); // wait for gopls response + await vscode.commands.executeCommand('setContext', 'go.showPackageOutline'); + const children = await provider.getChildren(); + const receiver = children?.find((symbol) => symbol.label === 'TestReceiver'); + assert.ok(receiver, 'receiver symbol not found'); + const method2 = receiver.children?.find((symbol) => symbol.label === 'method2'); + assert.ok(method2, 'method2 symbol not found'); + clickSymbol(method2); + await sleep(500); // wait for editor to navigate to symbol + const symbols2 = vscode.workspace.textDocuments.find( + (doc) => doc.uri.fsPath === path.join(fixtureDir, 'symbols_2.go') + ); + assert.strictEqual(window.activeTextEditor?.document.uri.fsPath, symbols2?.uri.fsPath); + assert.strictEqual(window.activeTextEditor?.selection.active.line, 2); + assert.strictEqual(window.activeTextEditor?.selection.active.character, 0); + }); + + test('non-go file does not trigger outline', async () => { + const document = await vscode.workspace.openTextDocument( + vscode.Uri.file(path.join(fixtureDir, 'symbols_3.ts')) + ); + await window.showTextDocument(document); + await sleep(500); // wait for gopls response + assert.strictEqual(provider.result, undefined); + }); +}); + +function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function clickSymbol(symbol: PackageSymbol) { + if (symbol.command) { + vscode.commands.executeCommand(symbol.command.command, ...(symbol.command.arguments || [])); + } else { + assert.fail(symbol.label + ' symbol has no command'); + } +} diff --git a/extension/test/testdata/packageOutlineTest/symbols_1.go b/extension/test/testdata/packageOutlineTest/symbols_1.go new file mode 100644 index 0000000000..9bf42f88f7 --- /dev/null +++ b/extension/test/testdata/packageOutlineTest/symbols_1.go @@ -0,0 +1,22 @@ +package package_outline_test + +import ( + "fmt" +) + +func print(txt string) { + fmt.Println(txt) +} +func main() { + print("Hello") +} + +type TestReceiver struct { + field1 int + field2 string + field3 bool +} + +func (*TestReceiver) method1() { + +} diff --git a/extension/test/testdata/packageOutlineTest/symbols_2.go b/extension/test/testdata/packageOutlineTest/symbols_2.go new file mode 100644 index 0000000000..8ce1cb4642 --- /dev/null +++ b/extension/test/testdata/packageOutlineTest/symbols_2.go @@ -0,0 +1,9 @@ +package package_outline_test + +func (*TestReceiver) method2() { + +} + +func (*TestReceiver) method3() { + +} diff --git a/extension/test/testdata/packageOutlineTest/symbols_3.ts b/extension/test/testdata/packageOutlineTest/symbols_3.ts new file mode 100644 index 0000000000..e69de29bb2 From 81151617e6992b5d5bd2f07d4ac402fd1b2f32b7 Mon Sep 17 00:00:00 2001 From: Hongxiang Jiang Date: Tue, 25 Feb 2025 14:47:49 -0500 Subject: [PATCH 10/39] tools/goplssetting: read enum key and enum value status VSCode-Go release tool can read gopls apijson's enum keys and enum values' status field (experimental, debug, advanced). The status will be added in the front of markdone description. x/tools CL 652356 Change-Id: I00333a9b63813369f1989e25131b8ce82827a022 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/652357 Reviewed-by: Alan Donovan Auto-Submit: Hongxiang Jiang LUCI-TryBot-Result: Go LUCI kokoro-CI: kokoro --- extension/tools/goplssetting/goplssetting.go | 25 ++++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/extension/tools/goplssetting/goplssetting.go b/extension/tools/goplssetting/goplssetting.go index a1aa7fff1a..7f725b3c62 100644 --- a/extension/tools/goplssetting/goplssetting.go +++ b/extension/tools/goplssetting/goplssetting.go @@ -111,9 +111,20 @@ func extractOptions(api *API) ([]*Option, error) { opts := []*Option{} for _, v := range options { - if name := statusName(v.Option); name != "" { + if name := statusName(v.Option.Status); name != "" { v.Option.Doc = name + " " + v.Option.Doc } + // Enum keys or values can be marked with status individually. + for i, key := range v.EnumKeys.Keys { + if name := statusName(key.Status); name != "" { + v.EnumKeys.Keys[i].Doc = name + " " + key.Doc + } + } + for i, value := range v.EnumValues { + if name := statusName(value.Status); name != "" { + v.EnumValues[i].Doc = name + " " + value.Doc + } + } opts = append(opts, v.Option) } return opts, nil @@ -129,8 +140,8 @@ func priority(opt *Option) int { return 1000 } -func statusName(opt *Option) string { - switch toStatus(opt.Status) { +func statusName(s string) string { + switch toStatus(s) { case Experimental: return "(Experimental)" case Advanced: @@ -453,11 +464,13 @@ type EnumKey struct { Name string // in JSON syntax (quoted) Doc string Default string + Status string // = "" | "advanced" | "experimental" | "deprecated" } type EnumValue struct { - Value string // in JSON syntax (quoted) - Doc string // doc comment; always starts with `Value` + Value string // in JSON syntax (quoted) + Doc string // doc comment; always starts with `Value` + Status string // = "" | "advanced" | "experimental" | "deprecated" } type Lens struct { @@ -466,6 +479,7 @@ type Lens struct { Title string Doc string Default bool + Status string // = "" | "advanced" | "experimental" | "deprecated" } type Analyzer struct { @@ -479,4 +493,5 @@ type Hint struct { Name string Doc string Default bool + Status string // = "" | "advanced" | "experimental" | "deprecated" } From 3a9150388362e003987cb5cc3c0da32d25ba6c7f Mon Sep 17 00:00:00 2001 From: Ethan Reesor Date: Wed, 26 Feb 2025 15:48:23 -0600 Subject: [PATCH 11/39] src/extensionAPI: expose isPreview Expose isPreview - whether the extension is running in preview mode (e.g. a prerelease version) - in the extension API. Also, fix a typo. Updates golang/vscode-go#3651. Change-Id: I991d3118d2f7786facb43d8252bf3e4fb294118d Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/653115 Reviewed-by: Hongxiang Jiang kokoro-CI: kokoro LUCI-TryBot-Result: Go LUCI Reviewed-by: Madeline Kalil --- extension/src/config.ts | 2 +- extension/src/export.d.ts | 13 +++++++++++++ extension/src/extensionAPI.ts | 3 +++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/extension/src/config.ts b/extension/src/config.ts index 5519e3b3fd..32eaddc88e 100644 --- a/extension/src/config.ts +++ b/extension/src/config.ts @@ -37,7 +37,7 @@ export class ExtensionInfo { readonly appName: string; /** True if the extension runs in preview mode (e.g. Nightly, prerelease) */ readonly isPreview: boolean; - /** True if the extension runs in well-kwnon cloud IDEs */ + /** True if the extension runs in well-known cloud IDEs */ readonly isInCloudIDE: boolean; constructor() { diff --git a/extension/src/export.d.ts b/extension/src/export.d.ts index 5a4e0a8b94..d63d2eb253 100644 --- a/extension/src/export.d.ts +++ b/extension/src/export.d.ts @@ -10,7 +10,20 @@ export interface CommandInvocation { binPath: string; } +/** + * The API we expose to other extensions. + * + * @example + * const Go = await vscode.extensions + * .getExtension('golang.go') + * .then(x => x.activate()); + * + * console.log(`Go extension is a ${Go.isPreview ? 'preview' : 'release'} version`); + */ export interface ExtensionAPI { + /** True if the extension is running in preview mode (e.g. prerelease) */ + isPreview: boolean; + settings: { /** * Returns the execution command corresponding to the specified resource, taking into account diff --git a/extension/src/extensionAPI.ts b/extension/src/extensionAPI.ts index accc455dd0..4ae11f8939 100644 --- a/extension/src/extensionAPI.ts +++ b/extension/src/extensionAPI.ts @@ -6,8 +6,11 @@ import { Uri } from 'vscode'; import { CommandInvocation, ExtensionAPI } from './export'; import { getBinPathWithExplanation } from './util'; +import { extensionInfo } from './config'; const api: ExtensionAPI = { + isPreview: extensionInfo.isPreview, + settings: { getExecutionCommand(toolName: string, resource?: Uri): CommandInvocation | undefined { const { binPath } = getBinPathWithExplanation(toolName, true, resource); From e9409b120b785b9cf801ed0e0806c02b52b9a66f Mon Sep 17 00:00:00 2001 From: Hongxiang Jiang Date: Mon, 3 Mar 2025 17:58:03 -0500 Subject: [PATCH 12/39] extension/src/language: remove interface wrapping LanguageServerConfig buildLanguageClientOption is confusing because it's wrapping over LanguageServerConfig with additional channel information. Explicitly annotate the type of serverOptions with additional documentation. Remove no effect flag from gopls "-mode=stdio". For golang/vscode-go#3697 Change-Id: I7a04ce0ff2e76bb960bfda714c83c5b6823854cc Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/654435 LUCI-TryBot-Result: Go LUCI Reviewed-by: Madeline Kalil Reviewed-by: Peter Weinberger kokoro-CI: kokoro --- extension/src/commands/startLanguageServer.ts | 3 +- extension/src/language/goLanguageServer.ts | 63 +++++++------------ extension/test/gopls/goplsTestEnv.utils.ts | 8 ++- 3 files changed, 30 insertions(+), 44 deletions(-) diff --git a/extension/src/commands/startLanguageServer.ts b/extension/src/commands/startLanguageServer.ts index b61f89f6bb..608fa8830f 100644 --- a/extension/src/commands/startLanguageServer.ts +++ b/extension/src/commands/startLanguageServer.ts @@ -11,7 +11,6 @@ import { GoExtensionContext } from '../context'; import { outputChannel, updateLanguageServerIconGoStatusBar } from '../goStatus'; import { buildLanguageClient, - buildLanguageClientOption, buildLanguageServerConfig, errorKind, RestartReason, @@ -85,7 +84,7 @@ export const startLanguageServer: CommandFactory = (ctx, goCtx) => { return; } - goCtx.languageClient = await buildLanguageClient(goCtx, buildLanguageClientOption(goCtx, cfg)); + goCtx.languageClient = await buildLanguageClient(goCtx, cfg); await goCtx.languageClient.start(); goCtx.serverInfo = toServerInfo(goCtx.languageClient.initializeResult); goCtx.telemetryService = new TelemetryService( diff --git a/extension/src/language/goLanguageServer.ts b/extension/src/language/goLanguageServer.ts index 5923aa88f6..e9b57986a8 100644 --- a/extension/src/language/goLanguageServer.ts +++ b/extension/src/language/goLanguageServer.ts @@ -36,7 +36,7 @@ import { ResponseError, RevealOutputChannelOn } from 'vscode-languageclient'; -import { LanguageClient, ServerOptions } from 'vscode-languageclient/node'; +import { Executable, LanguageClient, ServerOptions } from 'vscode-languageclient/node'; import { getGoConfig, getGoplsConfig, extensionInfo } from '../config'; import { toolExecutionEnvironment } from '../goEnv'; import { GoDocumentFormattingEditProvider, usingCustomFormatTool } from './legacy/goFormat'; @@ -336,36 +336,6 @@ export function toServerInfo(res?: InitializeResult): ServerInfo | undefined { return info; } -export interface BuildLanguageClientOption extends LanguageServerConfig { - outputChannel?: vscode.OutputChannel; - traceOutputChannel?: vscode.OutputChannel; -} - -// buildLanguageClientOption returns the default, extra configuration -// used in building a new LanguageClient instance. Options specified -// in LanguageServerConfig -export function buildLanguageClientOption( - goCtx: GoExtensionContext, - cfg: LanguageServerConfig -): BuildLanguageClientOption { - // Reuse the same output channel for each instance of the server. - if (cfg.enabled) { - if (!goCtx.serverOutputChannel) { - goCtx.serverOutputChannel = vscode.window.createOutputChannel(cfg.serverName + ' (server)'); - } - if (!goCtx.serverTraceChannel) { - goCtx.serverTraceChannel = vscode.window.createOutputChannel(cfg.serverName); - } - } - return Object.assign( - { - outputChannel: goCtx.serverOutputChannel, - traceOutputChannel: goCtx.serverTraceChannel - }, - cfg - ); -} - export class GoLanguageClient extends LanguageClient implements vscode.Disposable { constructor( id: string, @@ -409,8 +379,18 @@ type VulncheckEvent = { // The returned language client need to be started before use. export async function buildLanguageClient( goCtx: GoExtensionContext, - cfg: BuildLanguageClientOption + cfg: LanguageServerConfig ): Promise { + // Reuse the same output channel for each instance of the server. + if (cfg.enabled) { + if (!goCtx.serverOutputChannel) { + goCtx.serverOutputChannel = vscode.window.createOutputChannel(cfg.serverName + ' (server)'); + } + if (!goCtx.serverTraceChannel) { + goCtx.serverTraceChannel = vscode.window.createOutputChannel(cfg.serverName); + } + } + await getLocalGoplsVersion(cfg); // populate and cache cfg.version const goplsWorkspaceConfig = await adjustGoplsWorkspaceConfiguration(cfg, getGoplsConfig(), 'gopls', undefined); @@ -424,15 +404,20 @@ export async function buildLanguageClient( const pendingVulncheckProgressToken = new Map(); const onDidChangeVulncheckResultEmitter = new vscode.EventEmitter(); + // VSCode-Go prepares the information needed to start the language server. + // vscode-languageclient-node.LanguageClient will spin up the language + // server based on the provided information below. + const serverOption: Executable = { + command: cfg.path, + args: cfg.flags, + options: { env: cfg.env } + }; + // cfg is captured by closures for later use during error report. const c = new GoLanguageClient( 'go', // id cfg.serverName, // name e.g. gopls - { - command: cfg.path, - args: ['-mode=stdio', ...cfg.flags], - options: { env: cfg.env } - } as ServerOptions, + serverOption as ServerOptions, { initializationOptions: goplsWorkspaceConfig, documentSelector: GoDocumentSelector, @@ -442,8 +427,8 @@ export async function buildLanguageClient( (uri.scheme ? uri : uri.with({ scheme: 'file' })).toString(), protocol2Code: (uri: string) => vscode.Uri.parse(uri) }, - outputChannel: cfg.outputChannel, - traceOutputChannel: cfg.traceOutputChannel, + outputChannel: goCtx.serverOutputChannel, + traceOutputChannel: goCtx.serverTraceChannel, revealOutputChannelOn: RevealOutputChannelOn.Never, initializationFailedHandler: (error: ResponseError): boolean => { initializationError = error; diff --git a/extension/test/gopls/goplsTestEnv.utils.ts b/extension/test/gopls/goplsTestEnv.utils.ts index 2778bad910..1dae7279a0 100644 --- a/extension/test/gopls/goplsTestEnv.utils.ts +++ b/extension/test/gopls/goplsTestEnv.utils.ts @@ -12,7 +12,7 @@ import { LanguageClient } from 'vscode-languageclient/node'; import { getGoConfig } from '../../src/config'; import { buildLanguageClient, - BuildLanguageClientOption, + LanguageServerConfig, buildLanguageServerConfig, toServerInfo } from '../../src/language/goLanguageServer'; @@ -108,14 +108,16 @@ export class Env { if (!goConfig) { goConfig = getGoConfig(); } - const cfg: BuildLanguageClientOption = await buildLanguageServerConfig( + const cfg: LanguageServerConfig = await buildLanguageServerConfig( Object.create(goConfig, { useLanguageServer: { value: true }, languageServerFlags: { value: ['-rpc.trace'] } // enable rpc tracing to monitor progress reports }) ); - cfg.outputChannel = this.fakeOutputChannel; // inject our fake output channel. this.goCtx.latestConfig = cfg; + // Inject fake output channel. + this.goCtx.serverOutputChannel = this.fakeOutputChannel; + this.goCtx.serverTraceChannel = this.fakeOutputChannel; this.languageClient = await buildLanguageClient(this.goCtx, cfg); if (!this.languageClient) { throw new Error('Language client not initialized.'); From 4370329b913c75e4fa845ba56acca422e7108f98 Mon Sep 17 00:00:00 2001 From: "lqw@wsl" Date: Tue, 11 Mar 2025 15:24:23 +0000 Subject: [PATCH 13/39] extension/src/debugAdapter: fix broken link in README.md add .. in the link Change-Id: Ibad2a748cf7449b07a613e409fbbe6a80592cf4e GitHub-Last-Rev: 2d19ae4d86b18ecb6a598dc4392df12c3bc2ce54 GitHub-Pull-Request: golang/vscode-go#3709 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/656715 Reviewed-by: Madeline Kalil Reviewed-by: Hongxiang Jiang kokoro-CI: kokoro LUCI-TryBot-Result: Go LUCI --- extension/src/debugAdapter/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/src/debugAdapter/README.md b/extension/src/debugAdapter/README.md index c24d4f8a74..438cd9470c 100644 --- a/extension/src/debugAdapter/README.md +++ b/extension/src/debugAdapter/README.md @@ -1,3 +1,3 @@ # Debug Adapter -See the [contribution documentation](../../docs/contributing.md) to learn how to develop the debug adapter. +See the [contribution documentation](../../../docs/contributing.md) to learn how to develop the debug adapter. From eeb3c24fe991e47e130a0ac70a9b214664b4a0ea Mon Sep 17 00:00:00 2001 From: "lqw@wsl" Date: Wed, 12 Mar 2025 09:21:19 +0000 Subject: [PATCH 14/39] docs/contributing: add missing ` add missing ` in docs/contributing.md Change-Id: Id19f59177c3ec6f428adaf0a38f56486bd8d0837 GitHub-Last-Rev: 1a21ede94c3ffd440d24ccc128ef9e4d7786dd08 GitHub-Pull-Request: golang/vscode-go#3711 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/656935 kokoro-CI: kokoro Reviewed-by: Hongxiang Jiang LUCI-TryBot-Result: Go LUCI Reviewed-by: Madeline Kalil Reviewed-by: Jorropo --- docs/contributing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributing.md b/docs/contributing.md index 9442080e40..501e2cf941 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -101,7 +101,7 @@ When running them from terminal: You can supply environment variables (e.g. `MOCHA_GREP`) by modifying the launch configuration entry's `env` property. - `Launch Unit Tests`: runs unit tests in `test/unit` (same as `npm run unit-test`) - `Launch Extension Tests`: runs tests in `test/integration` directory (similar to `npm run test` but runs only tests under `test/integration` directory) - - `Launch Extension Tests with Gopls`: runs tests in `test/gopls directory (similar to `npm run test` but runs only tests under `test/gopls` directory) + - `Launch Extension Tests with Gopls`: runs tests in `test/gopls` directory (similar to `npm run test` but runs only tests under `test/gopls` directory) When you want to filter tests while debugging, utilize the `MOCAH_GREP` environment variable discussed previously - i.e., set the environment variable in the `env` property of the launch configuration. From 77a4fdb77ded9b3b6a70a70d810eed1cb7e9a631 Mon Sep 17 00:00:00 2001 From: Ethan Reesor Date: Wed, 12 Mar 2025 17:46:39 -0500 Subject: [PATCH 15/39] extension/src/goTest: fix debug subtest at cursor When the user manually executes the command, `args` is undefined. However the command implementation doesn't actually allow that but this was hidden because `args` was typed as `any`. I updated the typings to make the expected usage clear and fixed the bug. Fixes golang/vscode-go#3718. Change-Id: Ie0a28fa93985178c32532920b1a74576603fdf8b Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/657395 LUCI-TryBot-Result: Go LUCI Reviewed-by: Hongxiang Jiang Reviewed-by: Peter Weinberger kokoro-CI: kokoro --- CHANGELOG.md | 1 + extension/src/goTest.ts | 45 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e3a8b00f4..df2c37eb35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ diff between options locally discovered and options available for download. * Addressed an issue that caused a `Cannot read properties of null (reading 'Token')` error during command execution when the command result did not include a token. ([Issue 3698](https://github.com/golang/vscode-go/issues/3698)) +* Addressed an issue that broke the `Debug Subtest At Cursor` command. ([Issue 3718](https://github.com/golang/vscode-go/issues/3718)) ## v0.46.1 diff --git a/extension/src/goTest.ts b/extension/src/goTest.ts index 595b2c0eb2..dacfb1565d 100644 --- a/extension/src/goTest.ts +++ b/extension/src/goTest.ts @@ -25,6 +25,7 @@ import { SuiteToTestMap, getTestFunctions } from './testUtils'; +import type { GoRunTestCodeLensProvider } from './goRunTestCodelens'; // lastTestConfig holds a reference to the last executed TestConfig which allows // the last test to be easily re-executed. @@ -80,11 +81,27 @@ async function _testAtCursor( } } +/** + * Arguments for the run/debug subtest at cursor command. + */ +type SubTestAtCursorArgs = { + /** + * The name of the test that contains the subtest. If unspecified, this will + * be deduced from the cursor location. + */ + functionName?: string; + + /** + * The name of the subtest. If unspecified, this will prompt the user. + */ + subTestName?: string; +} & TestAtCursor; + async function _subTestAtCursor( goCtx: GoExtensionContext, goConfig: vscode.WorkspaceConfiguration, cmd: SubTestAtCursorCmd, - args: any + args?: SubTestAtCursorArgs ) { const editor = vscode.window.activeTextEditor; if (!editor) { @@ -100,7 +117,7 @@ async function _subTestAtCursor( const { testFunctions, suiteToTest } = await getTestFunctionsAndTestSuite(false, goCtx, editor.document); // We use functionName if it was provided as argument // Otherwise find any test function containing the cursor. - const currentTestFunctions = args.functionName + const currentTestFunctions = args?.functionName ? testFunctions.filter((func) => func.name === args.functionName) : testFunctions.filter((func) => func.range.contains(editor.selection.start)); const testFunctionName = @@ -202,6 +219,16 @@ export function testAtCursorOrPrevious(cmd: TestAtCursorCmd): CommandFactory { }; } +/** + * Arguments for the run test at cursor command. + */ +type TestAtCursor = { + /** + * Flags to be passed to `go test`. + */ + flags?: string[]; +}; + /** * Runs the test at cursor. */ @@ -212,7 +239,7 @@ async function runTestAtCursor( suiteToTest: SuiteToTestMap, goConfig: vscode.WorkspaceConfiguration, cmd: TestAtCursorCmd, - args: any + args?: TestAtCursor ) { const testConfigFns = [testFunctionName]; if (cmd !== 'benchmark' && extractInstanceTestName(testFunctionName)) { @@ -240,9 +267,17 @@ async function runTestAtCursor( * @param cmd Whether the command is test or debug. */ export function subTestAtCursor(cmd: SubTestAtCursorCmd): CommandFactory { - return (_, goCtx) => async (args: string[]) => { + return (_, goCtx) => async ( + /** + * When this command is run manually by the user (e.g. via vscode's + * command pallet), args is undefined. When this command is run via a + * codelens provided by {@link GoRunTestCodeLensProvider}, args + * specifies the function and subtest names. + */ + args?: [SubTestAtCursorArgs] + ) => { try { - return await _subTestAtCursor(goCtx, getGoConfig(), cmd, args); + return await _subTestAtCursor(goCtx, getGoConfig(), cmd, args?.[0]); } catch (err) { if (err instanceof NotFoundError) { vscode.window.showInformationMessage(err.message); From 1b300c4d8828242f66da0d291b179efe8fc9f478 Mon Sep 17 00:00:00 2001 From: Hongxiang Jiang Date: Fri, 21 Mar 2025 15:17:13 -0400 Subject: [PATCH 16/39] extension/src: change function doc using JSDoc Also update a outdated link in docs/advance.md. For golang/vscode-go#3697 Change-Id: I4e4b2cf7def757367d9ca02b51a8d16811cb0734 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/660055 Reviewed-by: Madeline Kalil LUCI-TryBot-Result: Go LUCI kokoro-CI: kokoro --- docs/advanced.md | 2 +- extension/src/goInstallTools.ts | 67 ++++++++++++++++++++++----------- 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/docs/advanced.md b/docs/advanced.md index a2c0ba0c53..a79c2b3231 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -63,7 +63,7 @@ name. The extension uses pinned versions of command-line tools. See the pinned versions in tools information -[here](https://github.com/golang/vscode-go/blob/master/src/goToolsInformation.ts). +[here](https://github.com/golang/vscode-go/blob/master/extension/src/goToolsInformation.ts). To use an alternate version of a tool install it manually with with go install. ## Using a custom linter diff --git a/extension/src/goInstallTools.ts b/extension/src/goInstallTools.ts index f914043201..3f08ce5755 100644 --- a/extension/src/goInstallTools.ts +++ b/extension/src/goInstallTools.ts @@ -44,13 +44,19 @@ import { allToolsInformation } from './goToolsInformation'; const STATUS_BAR_ITEM_NAME = 'Go Tools'; -// minimum go version required for tools installation. +/** + * Minimum go version required for tools installation. + */ const MINIMUM_GO_VERSION = '1.21.0'; -// declinedUpdates tracks the tools that the user has declined to update. +/** + * Tracks the tools that the user has declined to update. + */ const declinedUpdates: Tool[] = []; -// declinedUpdates tracks the tools that the user has declined to install. +/** + * Tracks the tools that the user has declined to install. + */ const declinedInstalls: Tool[] = []; export interface IToolsManager { @@ -107,10 +113,11 @@ export async function installAllTools(updateExistingToolsOnly = false) { goVersion ); } - -// Returns the go version to be used for tools installation. -// If `go.toolsManagement.go` is set, it is preferred. Otherwise, the provided -// goVersion or the default version returned by getGoVersion is returned. +/** + * Returns the go version to be used for tools installation. + * If `go.toolsManagement.go` is set, it is preferred. Otherwise, the provided + * goVersion or the default version returned by getGoVersion is returned. + */ export const getGoVersionForInstall = _getGoVersionForInstall; async function _getGoVersionForInstall(goVersion?: GoVersion): Promise { let configuredGoForInstall = getGoConfig().get('toolsManagement.go'); @@ -282,8 +289,10 @@ async function tmpDirForToolInstallation() { return toolsTmpDir; } -// installTool is used by goEnvironmentStatus.ts. -// TODO(hyangah): replace the callsite to use defaultToolsManager and remove this. +/** + * installTool is used by goEnvironmentStatus.ts. + * TODO(hyangah): replace the callsite to use defaultToolsManager and remove this. + */ export async function installTool(tool: ToolAtVersion): Promise { const goVersionForInstall = await getGoVersionForInstall(); if (!goVersionForInstall) { @@ -553,9 +562,11 @@ export function updateGoVarsFromConfig(goCtx: GoExtensionContext): Promise }); } -// maybeInstallImportantTools checks whether important tools are installed -// and they meet the version requirement. -// Then it tries to auto-install them if missing. +/** + * Checks whether important tools are installed and they meet the version + * requirement. + * Then it tries to auto-install them if missing. + */ export async function maybeInstallImportantTools( alternateTools: { [key: string]: string } | undefined, tm: IToolsManager = defaultToolsManager @@ -648,8 +659,10 @@ async function updateImportantToolsStatus(tm: IToolsManager = defaultToolsManage } } -// getMissingTools returns missing tools. -// If matcher is provided, only the tools that match the filter will be checked. +/** + * Returns missing tools. + * If matcher is provided, only the tools that match the filter will be checked. + */ function getMissingTools(matcher?: (value: Tool) => boolean): Promise { let keys = getConfiguredTools(getGoConfig(), getGoplsConfig()); if (matcher) { @@ -688,13 +701,17 @@ async function suggestDownloadGo() { suggestedDownloadGo = true; } -// ListVersionsOutput is the output of `go list -m -versions -json`. +/** + * The output of `go list -m -versions -json`. + */ interface ListVersionsOutput { Version: string; // module version Versions?: string[]; // available module versions (with -versions) } -// latestToolVersion returns the latest version of the tool. +/** + * Returns the latest version of the tool. + */ export async function latestToolVersion(tool: Tool, includePrerelease?: boolean): Promise { const goCmd = getBinPath('go'); const tmpDir = await tmpDirForToolInstallation(); @@ -729,8 +746,10 @@ export async function latestToolVersion(tool: Tool, includePrerelease?: boolean) return ret; } -// inspectGoToolVersion reads the go version and module version -// of the given go tool using `go version -m` command. +/** + * Reads the go version and module version of the given go tool using + * `go version -m` command. + */ export const inspectGoToolVersion = defaultInspectGoToolVersion; async function defaultInspectGoToolVersion( binPath: string @@ -803,6 +822,10 @@ export async function shouldUpdateTool(tool: Tool, toolPath: string): Promise !!tool); } -// maybeInstallVSCGO is a special program released and installed with the Go extension. -// Unlike other tools, it is installed under the extension path (which is cleared -// when a new version is installed). +/** + * VSCGO is a special program released and installed with the Go extension. + * Unlike other tools, it is installed under the extension path + * (which is cleared when a new version is installed). + */ export async function maybeInstallVSCGO( extensionMode: vscode.ExtensionMode, extensionId: string, From 20e95362ab11814fcfc9c8a7bdeb6895d19555d2 Mon Sep 17 00:00:00 2001 From: aarzilli Date: Sat, 25 Jan 2025 15:33:50 +0100 Subject: [PATCH 17/39] extension: use Delve's guess-substitute-path-helper when available When the remote mode is specified try to call dlv guess-substitute-path-helper and if it is available use its ouptut and switch to dlv-dap mode unless otherwise specified. The legacy adapter has been unmaintained for years and has known bugs (for example, breakpoints can not be set while the program is running) but couldn't be deleted because it implements automatic mappings of source files. Updates golang/vscode-go#3096, golang/vscode-go#3193 Change-Id: Ie2f731b149e0ba4d5031bb57d61e7b407c3a40f8 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/643280 Reviewed-by: Hongxiang Jiang kokoro-CI: kokoro Reviewed-by: Robert Findley LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui --- extension/src/goDebugConfiguration.ts | 55 +++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/extension/src/goDebugConfiguration.ts b/extension/src/goDebugConfiguration.ts index aa57a06317..723a9ad5e4 100644 --- a/extension/src/goDebugConfiguration.ts +++ b/extension/src/goDebugConfiguration.ts @@ -33,6 +33,7 @@ import { parseEnvFiles } from './utils/envUtils'; import { resolveHomeDir } from './utils/pathUtils'; import { createRegisterCommand } from './commands'; import { GoExtensionContext } from './context'; +import { spawn } from 'child_process'; let dlvDAPVersionChecked = false; @@ -182,12 +183,23 @@ export class GoDebugConfigurationProvider implements vscode.DebugConfigurationPr debugConfiguration['debugAdapter'] = dlvConfig['debugAdapter']; } } - // If neither launch.json nor settings.json gave us the debugAdapter value, we go with the default - // from package.json (dlv-dap) unless this is remote attach with a stable release. + + // If neither launch.json nor settings.json gave us the debugAdapter, we go with the default from pacakge.json (dlv-dap) for all modes except 'remote'. + // For remote we will use 'dlv-dap' if we can call 'dlv substitute-path-guess-helper' or 'legacy' otherwise. if (!debugConfiguration['debugAdapter']) { + // set dlv-dap by default debugConfiguration['debugAdapter'] = defaultConfig.debugAdapter.default; - if (debugConfiguration['mode'] === 'remote' && !extensionInfo.isPreview) { - debugConfiguration['debugAdapter'] = 'legacy'; + if (debugConfiguration['mode'] === 'remote') { + const substitutePathGuess = await this.guessSubstitutePath(); + if (substitutePathGuess === null) { + if (!extensionInfo.isPreview) { + // can't guess substitute path and isPreview isn't set, fall back to legacy. + debugConfiguration['debugAdapter'] = 'legacy'; + } + } else { + debugConfiguration['debugAdapter'] = defaultConfig.debugAdapter.default; + debugConfiguration['guessSubstitutePath'] = substitutePathGuess; + } } } if (debugConfiguration['debugAdapter'] === 'dlv-dap') { @@ -372,6 +384,41 @@ export class GoDebugConfigurationProvider implements vscode.DebugConfigurationPr return debugConfiguration; } + /** + * Calls `dlv substitute-path-guess-helper` to get a set of parameters used by Delve to guess the substitutePath configuration after also examining the executable. + * + * See https://github.com/go-delve/delve/blob/d5fb3bee427202f0d4b1683bf743bfd2adb41757/service/debugger/debugger.go#L2466 + */ + private async guessSubstitutePath(): Promise { + return new Promise((resolve) => { + const child = spawn(getBinPath('dlv'), ['substitute-path-guess-helper']); + let stdoutData = ''; + let stderrData = ''; + child.stdout.on('data', (data) => { + stdoutData += data; + }); + child.stderr.on('data', (data) => { + stderrData += data; + }); + + child.on('close', (code) => { + if (code !== 0) { + resolve(null); + } else { + try { + resolve(JSON.parse(stdoutData)); + } catch (error) { + resolve(null); + } + } + }); + + child.on('error', (error) => { + resolve(null); + }); + }); + } + public removeGcflags(args: string): { args: string; removed: boolean } { // From `go help build` // ... From e8cc0213faff180c194676c776353bc2a3e988c3 Mon Sep 17 00:00:00 2001 From: Hongxiang Jiang Date: Fri, 21 Mar 2025 16:59:52 -0400 Subject: [PATCH 18/39] extension/src: rename telemetry state starting to running - The _state of telemetry reporter is reflecting whether an ongoing telemetry data write is happening. flush method set the _state to STARTING before calling vscgo to write telemetry data. Also the RUNNING state is not being referenced (safe to remove). However, RUNNING is more accurate compares to STARTING so I replace all STARTING appearance with RUNNING. - Replace comments with JSDoc, explain public methods of telemetry reporter class in more detail. For golang/vscode-go#3697 Change-Id: Iff696748b7550d5fb40853be99bb6054665b518a Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/660095 kokoro-CI: kokoro Reviewed-by: Peter Weinberger LUCI-TryBot-Result: Go LUCI --- extension/src/goTelemetry.ts | 87 +++++++++++++++++++++++++++--------- 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/extension/src/goTelemetry.ts b/extension/src/goTelemetry.ts index a6daa489ce..1ad2e83b11 100644 --- a/extension/src/goTelemetry.ts +++ b/extension/src/goTelemetry.ts @@ -13,17 +13,25 @@ import * as cp from 'child_process'; import { getWorkspaceFolderPath } from './util'; import { toolExecutionEnvironment } from './goEnv'; -// Name of the prompt telemetry command. This is also used to determine if the gopls instance supports telemetry. -// Exported for testing. +/** + * Name of the prompt telemetry command. This is also used to determine if the + * gopls instance supports telemetry. + * Exported for testing. + */ export const GOPLS_MAYBE_PROMPT_FOR_TELEMETRY = 'gopls.maybe_prompt_for_telemetry'; -// Key for the global state that holds the very first time the telemetry-enabled gopls was observed. -// Exported for testing. +/** + * Key for the global state that holds the very first time the telemetry-enabled + * gopls was observed. + * Exported for testing. + */ export const TELEMETRY_START_TIME_KEY = 'telemetryStartTime'; -// Run our encode/decode function for the Date object, to be defensive -// from vscode Memento API behavior change. -// Exported for testing. +/** + * Run our encode/decode function for the Date object, to be defensive from + * vscode Memento API behavior change. + * Exported for testing. + */ export function recordTelemetryStartTime(storage: vscode.Memento, date: Date) { storage.update(TELEMETRY_START_TIME_KEY, date.toJSON()); } @@ -43,11 +51,29 @@ function readTelemetryStartTime(storage: vscode.Memento): Date | null { enum ReporterState { NOT_INITIALIZED, IDLE, - STARTING, RUNNING } -// exported for testing. +/** + * Manages Go telemetry data and persists them to disk using a storage tool. + * + * **Usage:** + * 1. Call `setTool(tool)` once, before any other methods. + * 2. Call `add(key, value)` to add values associated with keys. + * 3. Data is automatically flushed to disk periodically. + * 4. To force an immediate flush, call `flush(true)`. + * + * **Example:** + * ```typescript + * const r = new TelemetryReporter(); + * r.setTool(vscgo); + * r.add("count", 10); + * r.add("count", 5); + * r.flush(true); // Force a flush + * ``` + * + * Exported for testing. + */ export class TelemetryReporter implements vscode.Disposable { private _state = ReporterState.NOT_INITIALIZED; private _counters: { [key: string]: number } = {}; @@ -55,13 +81,17 @@ export class TelemetryReporter implements vscode.Disposable { private _tool = ''; constructor(flushIntervalMs = 60_000, private counterFile: string = '') { if (flushIntervalMs > 0) { - // periodically call flush. + // Periodically call flush. this._flushTimer = setInterval(this.flush.bind(this), flushIntervalMs); } } + /** + * Initializes the tool. + * This method should be called once. Subsequent calls have no effect. + */ public setTool(tool: string) { - // allow only once. + // Allow only once. if (tool === '' || this._state !== ReporterState.NOT_INITIALIZED) { return; } @@ -69,6 +99,9 @@ export class TelemetryReporter implements vscode.Disposable { this._tool = tool; } + /** + * Adds a numeric value to a counter associated with the given key. + */ public add(key: string, value: number) { if (value <= 0) { return; @@ -77,8 +110,12 @@ export class TelemetryReporter implements vscode.Disposable { this._counters[key] = (this._counters[key] || 0) + value; } - // flush is called periodically (by the callback set up in the constructor) - // or when the extension is deactivated (with force=true). + /** + * Flushes Go telemetry data. + * * When `force` is true, telemetry is flushed immediately, bypassing the + * IDLE state check. + * * When `force` is false, telemetry is flushed only if the reporter is IDLE. + */ public async flush(force = false) { // If flush runs with force=true, ignore the state and skip state update. if (!force && this._state !== ReporterState.IDLE) { @@ -86,7 +123,7 @@ export class TelemetryReporter implements vscode.Disposable { return 0; } if (!force) { - this._state = ReporterState.STARTING; + this._state = ReporterState.RUNNING; } try { await this.writeGoTelemetry(); @@ -142,11 +179,13 @@ export class TelemetryReporter implements vscode.Disposable { clearInterval(this._flushTimer); } this._flushTimer = undefined; - await this.flush(true); // flush any remaining data in buffer. + await this.flush(true); // Flush any remaining data in buffer. } } -// global telemetryReporter instance. +/** + * Global telemetryReporter instance. + */ export const telemetryReporter = new TelemetryReporter(); // TODO(hyangah): consolidate the list of all the telemetries and bucketting functions. @@ -155,8 +194,10 @@ export function addTelemetryEvent(name: string, count: number) { telemetryReporter.add(name, count); } -// Go extension delegates most of the telemetry logic to gopls. -// TelemetryService provides API to interact with gopls's telemetry. +/** + * Go extension delegates most of the telemetry logic to gopls. + * TelemetryService provides API to interact with gopls's telemetry. + */ export class TelemetryService { private active = false; constructor( @@ -219,8 +260,10 @@ export class TelemetryService { } } -// Set telemetry env vars for gopls. See gopls/internal/server/prompt.go -// TODO(hyangah): add an integration testing after gopls v0.17 becomes available. +/** + * Set telemetry env vars for gopls. See gopls/internal/server/prompt.go + * TODO(hyangah): add an integration testing after gopls v0.17 becomes available. + */ export function setTelemetryEnvVars(globalState: vscode.Memento, env: NodeJS.ProcessEnv) { if (!env['GOTELEMETRY_GOPLS_CLIENT_TOKEN']) { env['GOTELEMETRY_GOPLS_CLIENT_TOKEN'] = `${hashMachineID() + 1}`; // [1, 1000] @@ -234,7 +277,9 @@ export function setTelemetryEnvVars(globalState: vscode.Memento, env: NodeJS.Pro } } -// Map vscode.env.machineId to an integer in [0, 1000). +/** + * Map vscode.env.machineId to an integer in [0, 1000). + */ function hashMachineID(salt?: string): number { const hash = createHash('md5').update(`${vscode.env.machineId}${salt}`).digest('hex'); return parseInt(hash.substring(0, 8), 16) % 1000; From 5496123f34dad8790a3bc33dd160c3bb71c0c988 Mon Sep 17 00:00:00 2001 From: Hongxiang Jiang Date: Mon, 24 Mar 2025 14:45:45 -0400 Subject: [PATCH 19/39] extension/src: remove sampling and machine ID from telemetry service - CL 625495 increased the telemetry prompting to 100%, sampling threshold and machine ID is no longer needed. (Previuosly, it's determined by hash % 1000 > threshold). Clean up test that relevant to sampling threshold and machine ID. - Remove TODO to prompt telemetry regardless of vscode env isTelemetryEnabled. See https://code.visualstudio.com/api/extension-guides/telemetry#without-the-telemetry-module - VSCode-Go delegates most of telemetry logic to gopls even including the telemetry opt in prompting. VSCode-Go send a ExecuteCommandRequest to gopls and gopls returns a ShowMessageRequest back to client for prompting. It's the language server (gopls)'s responsibility to memorize the user's selection after prompting and avoid re-sending the same prompt next time. For golang/vscode-go#3697 Change-Id: I89cd0324354ed00192dc8d696996d23ba0946861 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/660396 Reviewed-by: Madeline Kalil kokoro-CI: kokoro LUCI-TryBot-Result: Go LUCI --- extension/src/goTelemetry.ts | 36 +++++-------- extension/src/language/goLanguageServer.ts | 2 +- extension/test/gopls/extension.test.ts | 6 +-- extension/test/gopls/telemetry.test.ts | 61 +++------------------- internal/vscgo/main.go | 38 +++++++------- 5 files changed, 44 insertions(+), 99 deletions(-) diff --git a/extension/src/goTelemetry.ts b/extension/src/goTelemetry.ts index 1ad2e83b11..6db84ce2cc 100644 --- a/extension/src/goTelemetry.ts +++ b/extension/src/goTelemetry.ts @@ -79,6 +79,13 @@ export class TelemetryReporter implements vscode.Disposable { private _counters: { [key: string]: number } = {}; private _flushTimer: NodeJS.Timeout | undefined; private _tool = ''; + + /** + * @param flushIntervalMs is the interval (in milliseconds) between periodic + * `flush()` calls. + * @param counterFile is the file path for writing telemetry data (used for + * testing). + */ constructor(flushIntervalMs = 60_000, private counterFile: string = '') { if (flushIntervalMs > 0) { // Periodically call flush. @@ -203,15 +210,15 @@ export class TelemetryService { constructor( private languageClient: Pick | undefined, private globalState: vscode.Memento, - commands: string[] = [] + serverCommands: string[] = [] ) { - if (!languageClient || !commands.includes(GOPLS_MAYBE_PROMPT_FOR_TELEMETRY)) { - // we are not backed by the gopls version that supports telemetry. + if (!languageClient || !serverCommands.includes(GOPLS_MAYBE_PROMPT_FOR_TELEMETRY)) { + // We are not backed by the gopls version that supports telemetry. return; } this.active = true; - // record the first time we see the gopls with telemetry support. + // Record the first time we see the gopls with telemetry support. // The timestamp will be used to avoid prompting too early. const telemetryStartTime = readTelemetryStartTime(globalState); if (!telemetryStartTime) { @@ -219,16 +226,11 @@ export class TelemetryService { } } - async promptForTelemetry( - isPreviewExtension: boolean, - isVSCodeTelemetryEnabled: boolean = vscode.env.isTelemetryEnabled, - samplingInterval = 1000 /* prompt N out of 1000. 1000 = 100% */ - ) { + async promptForTelemetry(isVSCodeTelemetryEnabled: boolean = vscode.env.isTelemetryEnabled) { if (!this.active) return; - // Do not prompt yet if the user disabled vscode's telemetry. - // TODO(hyangah): remove this condition after we roll out to 100%. It's possible - // users who don't want vscode's telemetry are still willing to opt in. + // Do not prompt if the user disabled vscode's telemetry. + // See https://code.visualstudio.com/api/extension-guides/telemetry#without-the-telemetry-module if (!isVSCodeTelemetryEnabled) return; // Allow at least 7days for gopls to collect some data. @@ -240,11 +242,6 @@ export class TelemetryService { return; } - // For official extension users, prompt only N out of 1000. - if (!isPreviewExtension && this.hashMachineID() % 1000 >= samplingInterval) { - return; - } - try { await this.languageClient?.sendRequest(ExecuteCommandRequest.type, { command: GOPLS_MAYBE_PROMPT_FOR_TELEMETRY @@ -253,11 +250,6 @@ export class TelemetryService { console.log(`failed to send telemetry request: ${e}`); } } - - // exported for testing. - public hashMachineID(salt?: string): number { - return hashMachineID(salt); - } } /** diff --git a/extension/src/language/goLanguageServer.ts b/extension/src/language/goLanguageServer.ts index e9b57986a8..60f5d7770d 100644 --- a/extension/src/language/goLanguageServer.ts +++ b/extension/src/language/goLanguageServer.ts @@ -1702,7 +1702,7 @@ export function maybePromptForTelemetry(goCtx: GoExtensionContext) { setTimeout(callback, 5 * timeMinute - Math.max(idleTime, 0)); return; } - goCtx.telemetryService?.promptForTelemetry(extensionInfo.isPreview); + goCtx.telemetryService?.promptForTelemetry(); }; callback(); } diff --git a/extension/test/gopls/extension.test.ts b/extension/test/gopls/extension.test.ts index 69d1eb5b1c..6d8180c1a7 100644 --- a/extension/test/gopls/extension.test.ts +++ b/extension/test/gopls/extension.test.ts @@ -211,11 +211,7 @@ suite('Go Extension Tests With Gopls', function () { await Promise.all([ // we want to see the prompt command flowing. env.onMessageInTrace(GOPLS_MAYBE_PROMPT_FOR_TELEMETRY, 60_000), - sut.promptForTelemetry( - false /* not a preview */, - true /* vscode telemetry not disabled */, - 1000 /* 1000 out of 1000 users */ - ) + sut.promptForTelemetry(true /* vscode telemetry not disabled */) ]); } catch (e) { assert(false, `unexpected failure: ${e}`); diff --git a/extension/test/gopls/telemetry.test.ts b/extension/test/gopls/telemetry.test.ts index aa2f849e83..6865db7654 100644 --- a/extension/test/gopls/telemetry.test.ts +++ b/extension/test/gopls/telemetry.test.ts @@ -33,8 +33,7 @@ describe('# prompt for telemetry', async () => { testTelemetryPrompt( { noLangClient: true, - previewExtension: true, - samplingInterval: 1000 + previewExtension: true }, false ) @@ -44,8 +43,7 @@ describe('# prompt for telemetry', async () => { testTelemetryPrompt( { goplsWithoutTelemetry: true, - previewExtension: true, - samplingInterval: 1000 + previewExtension: true }, false ) @@ -54,8 +52,7 @@ describe('# prompt for telemetry', async () => { 'prompt when telemetry started a while ago', testTelemetryPrompt( { - firstDate: new Date('2022-01-01'), - samplingInterval: 1000 + firstDate: new Date('2022-01-01') }, true ) @@ -64,8 +61,7 @@ describe('# prompt for telemetry', async () => { 'do not prompt if telemetry started two days ago', testTelemetryPrompt( { - firstDate: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000), // two days ago! - samplingInterval: 1000 + firstDate: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000) // two days ago! }, false ) @@ -74,40 +70,7 @@ describe('# prompt for telemetry', async () => { 'do not prompt if gopls with telemetry never ran', testTelemetryPrompt( { - firstDate: undefined, // gopls with telemetry not seen before. - samplingInterval: 1000 - }, - false - ) - ); - it( - 'do not prompt if not sampled', - testTelemetryPrompt( - { - firstDate: new Date('2022-01-01'), - samplingInterval: 0 - }, - false - ) - ); - it( - 'prompt only if sampled (machineID = 0, samplingInterval = 1)', - testTelemetryPrompt( - { - firstDate: new Date('2022-01-01'), - samplingInterval: 1, - hashMachineID: 0 - }, - true - ) - ); - it( - 'prompt only if sampled (machineID = 1, samplingInterval = 1)', - testTelemetryPrompt( - { - firstDate: new Date('2022-01-01'), - samplingInterval: 1, - hashMachineID: 1 + firstDate: undefined // gopls with telemetry not seen before. }, false ) @@ -117,8 +80,7 @@ describe('# prompt for telemetry', async () => { testTelemetryPrompt( { firstDate: new Date('2022-01-01'), - previewExtension: true, - samplingInterval: 0 + previewExtension: true }, true ) @@ -129,8 +91,7 @@ describe('# prompt for telemetry', async () => { { firstDate: new Date('2022-01-01'), vsTelemetryDisabled: true, - previewExtension: true, - samplingInterval: 1000 + previewExtension: true }, false ) @@ -145,7 +106,6 @@ describe('# prompt for telemetry', async () => { testExtensionAPI.globalState.update(TELEMETRY_START_TIME_KEY, new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)); await testTelemetryPrompt( { - samplingInterval: 1000, mementoInstance: testExtensionAPI.globalState }, true @@ -159,8 +119,6 @@ interface testCase { firstDate?: Date; // first date the extension observed gopls with telemetry feature. previewExtension?: boolean; // assume we are in dev/nightly extension. vsTelemetryDisabled?: boolean; // assume the user disabled vscode general telemetry. - samplingInterval: number; // N where N out of 1000 are sampled. - hashMachineID?: number; // stub the machine id hash computation function. mementoInstance?: Memento; // if set, use this instead of mock memento. } @@ -181,10 +139,7 @@ function testTelemetryPrompt(tc: testCase, wantPrompt: boolean) { const commands = tc.goplsWithoutTelemetry ? [] : [GOPLS_MAYBE_PROMPT_FOR_TELEMETRY]; const sut = new TelemetryService(lc, memento, commands); - if (tc.hashMachineID !== undefined) { - sinon.stub(sut, 'hashMachineID').returns(tc.hashMachineID); - } - await sut.promptForTelemetry(!!tc.previewExtension, !tc.vsTelemetryDisabled, tc.samplingInterval); + await sut.promptForTelemetry(!tc.vsTelemetryDisabled); if (wantPrompt) { sinon.assert.calledOnce(spy); } else { diff --git a/internal/vscgo/main.go b/internal/vscgo/main.go index a8972e6f96..831cf89df3 100644 --- a/internal/vscgo/main.go +++ b/internal/vscgo/main.go @@ -164,33 +164,32 @@ func help(name string) { } // runIncCounters increments telemetry counters read from stdin. -func runIncCounters(_ []string) error { +// Write the counters to file provided by env var TELEMETRY_COUNTER_FILE. +func runIncCounters(_ []string) (rerr error) { scanner := bufio.NewScanner(os.Stdin) - if counterFile := os.Getenv("TELEMETRY_COUNTER_FILE"); counterFile != "" { - return printCounter(counterFile, scanner) - } - return runIncCountersImpl(scanner, counter.Add) -} -func printCounter(fname string, scanner *bufio.Scanner) (rerr error) { - f, err := os.OpenFile(fname, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) - if err != nil { - return err - } - defer func() { - if err := f.Close(); rerr == nil { - rerr = err + if counterFile := os.Getenv("TELEMETRY_COUNTER_FILE"); counterFile != "" { + f, err := os.OpenFile(counterFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return err } - }() - return runIncCountersImpl(scanner, func(name string, count int64) { - fmt.Fprintln(f, name, count) - }) + defer func() { + if err := f.Close(); err == nil { + rerr = err + } + }() + return runIncCountersImpl(scanner, func(name string, count int64) { fmt.Fprintln(f, name, count) }) + } else { + return runIncCountersImpl(scanner, counter.Add) + } } const ( incCountersBadInput = "inc_counters_bad_input" ) +// incCountersInputLength returns the counter name based on input counters +// length. func incCountersInputLength(n int) string { const name = "inc_counters_num_input" for i := 1; i < 8; i *= 2 { @@ -201,6 +200,7 @@ func incCountersInputLength(n int) string { return name + ":>=8" } +// incCountersDuration returns the counter name based on input duration. func incCountersDuration(duration time.Duration) string { const name = "inc_counters_duration" switch { @@ -233,7 +233,9 @@ func runIncCountersImpl(scanner *bufio.Scanner, incCounter func(name string, cou linenum++ incCounter(name, int64(count)) } + // Keep track of counter line number. incCounter(incCountersInputLength(linenum), 1) + // Keep track of time consumed for each round of counter increment. incCounter(incCountersDuration(time.Since(start)), 1) return nil } From ebc887ffd64a415fb454105059a43a64ca92f448 Mon Sep 17 00:00:00 2001 From: Hongxiang Jiang Date: Wed, 26 Mar 2025 15:42:35 -0400 Subject: [PATCH 20/39] extension/src: use enum for telemetry key - Move activationLatency function from goMain.ts to goTelemetry.ts. - Replace add() input parameter type of string with enum type TelemetryKey. Follow variable naming conventions: - enum declaration use UpperCamelCase - enum value use CONSTANT_CASE https://google.github.io/styleguide/tsguide.html#naming-rules-by-identifier-type Bucketing variable name follow prometheus histogram naming: L: less than, LE: less than or equal to G: greater than, GE: greater than or equal to For golang/vscode-go#3697 Change-Id: I1472bc58752f08b6f0feb1316284cfe10b214f4b Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/661056 LUCI-TryBot-Result: Go LUCI Auto-Submit: Hongxiang Jiang Reviewed-by: Madeline Kalil Reviewed-by: Peter Weinberger kokoro-CI: kokoro --- extension/src/goInstallTools.ts | 6 ++-- extension/src/goMain.ts | 18 +---------- extension/src/goTelemetry.ts | 43 +++++++++++++++++++++++--- extension/src/goTest.ts | 1 - extension/test/gopls/telemetry.test.ts | 11 ++++--- 5 files changed, 49 insertions(+), 30 deletions(-) diff --git a/extension/src/goInstallTools.ts b/extension/src/goInstallTools.ts index 3f08ce5755..67bc0441ef 100644 --- a/extension/src/goInstallTools.ts +++ b/extension/src/goInstallTools.ts @@ -39,7 +39,7 @@ import { import util = require('util'); import vscode = require('vscode'); import { RestartReason } from './language/goLanguageServer'; -import { telemetryReporter } from './goTelemetry'; +import { TelemetryKey, telemetryReporter } from './goTelemetry'; import { allToolsInformation } from './goToolsInformation'; const STATUS_BAR_ITEM_NAME = 'Go Tools'; @@ -937,7 +937,7 @@ export async function maybeInstallVSCGO( if (extensionMode === vscode.ExtensionMode.Production && executableFileExists(progPath)) { return progPath; // reuse existing executable. } - telemetryReporter.add('vscgo_install', 1); + telemetryReporter.add(TelemetryKey.VSCGO_INSTALL, 1); const mkdir = util.promisify(fs.mkdir); await mkdir(path.dirname(progPath), { recursive: true }); const execFile = util.promisify(cp.execFile); @@ -965,7 +965,7 @@ export async function maybeInstallVSCGO( await execFile(goBinary, args, { cwd, env }); return progPath; } catch (e) { - telemetryReporter.add('vscgo_install_fail', 1); + telemetryReporter.add(TelemetryKey.VSCGO_INSTALL_FAIL, 1); return Promise.reject(`failed to install vscgo - ${e}`); } } diff --git a/extension/src/goMain.ts b/extension/src/goMain.ts index 45f22daf1a..9a6d5977c8 100644 --- a/extension/src/goMain.ts +++ b/extension/src/goMain.ts @@ -73,7 +73,7 @@ import { GoExtensionContext } from './context'; import * as commands from './commands'; import { toggleVulncheckCommandFactory } from './goVulncheck'; import { GoTaskProvider } from './goTaskProvider'; -import { setTelemetryEnvVars, telemetryReporter } from './goTelemetry'; +import { setTelemetryEnvVars, activationLatency, telemetryReporter } from './goTelemetry'; import { experiments } from './experimental'; import { allToolsInformation } from './goToolsInformation'; @@ -240,22 +240,6 @@ export async function activate(ctx: vscode.ExtensionContext): Promise(TELEMETRY_START_TIME_KEY); if (!value) { @@ -109,12 +144,12 @@ export class TelemetryReporter implements vscode.Disposable { /** * Adds a numeric value to a counter associated with the given key. */ - public add(key: string, value: number) { + public add(key: TelemetryKey, value: number) { if (value <= 0) { return; } - key = key.replace(/[\s\n]/g, '_'); - this._counters[key] = (this._counters[key] || 0) + value; + const sanitized = key.replace(/[\s\n]/g, '_'); + this._counters[sanitized] = (this._counters[sanitized] || 0) + value; } /** @@ -197,7 +232,7 @@ export const telemetryReporter = new TelemetryReporter(); // TODO(hyangah): consolidate the list of all the telemetries and bucketting functions. -export function addTelemetryEvent(name: string, count: number) { +export function addTelemetryEvent(name: TelemetryKey, count: number) { telemetryReporter.add(name, count); } diff --git a/extension/src/goTest.ts b/extension/src/goTest.ts index dacfb1565d..22c972e82b 100644 --- a/extension/src/goTest.ts +++ b/extension/src/goTest.ts @@ -25,7 +25,6 @@ import { SuiteToTestMap, getTestFunctions } from './testUtils'; -import type { GoRunTestCodeLensProvider } from './goRunTestCodelens'; // lastTestConfig holds a reference to the last executed TestConfig which allows // the last test to be easily re-executed. diff --git a/extension/test/gopls/telemetry.test.ts b/extension/test/gopls/telemetry.test.ts index 6865db7654..dc6af31a1b 100644 --- a/extension/test/gopls/telemetry.test.ts +++ b/extension/test/gopls/telemetry.test.ts @@ -9,6 +9,7 @@ import { describe, it } from 'mocha'; import { GOPLS_MAYBE_PROMPT_FOR_TELEMETRY, TELEMETRY_START_TIME_KEY, + TelemetryKey, TelemetryReporter, TelemetryService, recordTelemetryStartTime @@ -189,8 +190,8 @@ describe('# telemetry reporter using vscgo', async function () { }); it('add succeeds before telemetryReporter.setTool runs', () => { - sut.add('hello', 1); - sut.add('world', 2); + sut.add(TelemetryKey.VSCGO_INSTALL, 1); + sut.add(TelemetryKey.VSCGO_INSTALL_FAIL, 2); }); it('flush is noop before setTool', async () => { @@ -202,13 +203,13 @@ describe('# telemetry reporter using vscgo', async function () { sut.setTool(vscgo); await sut.flush(); const readAll = fs.readFileSync(counterfile).toString(); - assert(readAll.includes('hello 1\n') && readAll.includes('world 2\n'), readAll); + assert(readAll.includes('vscgo_install 1\n') && readAll.includes('vscgo_install_fail 2\n'), readAll); }); it('dispose triggers flush', async () => { - sut.add('bye', 3); + sut.add(TelemetryKey.ACTIVATION_LATENCY_GE_5S, 3); await sut.dispose(); const readAll = fs.readFileSync(counterfile).toString(); - assert(readAll.includes('bye 3\n'), readAll); + assert(readAll.includes('activation_latency:>=5s 3\n'), readAll); }); }); From 55e8698dde1907b8d9a7322ba8d80ab026290a07 Mon Sep 17 00:00:00 2001 From: Hongxiang Jiang Date: Wed, 26 Mar 2025 17:13:48 -0400 Subject: [PATCH 21/39] extension/src: collect third party tool usage CL 661055 under review for x/telemetry change. For golang/go#73036 Change-Id: I373d6217bdb206b967285c5ac3fddaba27cf2f67 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/661175 kokoro-CI: kokoro Reviewed-by: Robert Findley LUCI-TryBot-Result: Go LUCI Auto-Submit: Hongxiang Jiang --- extension/src/goGenerateTests.ts | 3 +++ extension/src/goModifytags.ts | 3 +++ extension/src/goPlayground.ts | 3 +++ extension/src/goTelemetry.ts | 7 ++++++- 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/extension/src/goGenerateTests.ts b/extension/src/goGenerateTests.ts index 00cb913987..06d0957935 100644 --- a/extension/src/goGenerateTests.ts +++ b/extension/src/goGenerateTests.ts @@ -19,6 +19,7 @@ import { outputChannel } from './goStatus'; import { getBinPath, resolvePath } from './util'; import { CommandFactory } from './commands'; import { GoExtensionContext } from './context'; +import { TelemetryKey, telemetryReporter } from './goTelemetry'; const generatedWord = 'Generated '; @@ -165,6 +166,8 @@ function generateTests( goConfig: vscode.WorkspaceConfiguration ): Promise { return new Promise((resolve, reject) => { + telemetryReporter.add(TelemetryKey.TOOL_USAGE_GOTESTS, 1); + const cmd = getBinPath('gotests'); let args = ['-w']; const goGenerateTestsFlags: string[] = goConfig['generateTestsFlags'] || []; diff --git a/extension/src/goModifytags.ts b/extension/src/goModifytags.ts index 5cd87ece75..faf00e7d35 100644 --- a/extension/src/goModifytags.ts +++ b/extension/src/goModifytags.ts @@ -14,6 +14,7 @@ import { getGoConfig } from './config'; import { toolExecutionEnvironment } from './goEnv'; import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools'; import { byteOffsetAt, getBinPath, getFileArchive } from './util'; +import { TelemetryKey, telemetryReporter } from './goTelemetry'; // Interface for the output from gomodifytags interface GomodifytagsOutput { @@ -162,6 +163,8 @@ function getTagsAndOptions(config: GoTagsConfig, commandArgs: GoTagsConfig): The } function runGomodifytags(args: string[]) { + telemetryReporter.add(TelemetryKey.TOOL_USAGE_GOMODIFYTAGS, 1); + const gomodifytags = getBinPath('gomodifytags'); const editor = vscode.window.activeTextEditor; if (!editor) { diff --git a/extension/src/goPlayground.ts b/extension/src/goPlayground.ts index a6c8f9e3f2..f2f9e32c45 100644 --- a/extension/src/goPlayground.ts +++ b/extension/src/goPlayground.ts @@ -12,6 +12,7 @@ import { outputChannel } from './goStatus'; import { getBinPath } from './util'; import vscode = require('vscode'); import { CommandFactory } from './commands'; +import { TelemetryKey, telemetryReporter } from './goTelemetry'; const TOOL_CMD_NAME = 'goplay'; @@ -46,6 +47,8 @@ export const playgroundCommand: CommandFactory = () => () => { }; export function goPlay(code: string, goConfig?: vscode.WorkspaceConfiguration): Thenable { + telemetryReporter.add(TelemetryKey.TOOL_USAGE_GOPLAY, 1); + const cliArgs = goConfig ? Object.keys(goConfig).map((key) => `-${key}=${goConfig[key]}`) : []; const binaryLocation = getBinPath(TOOL_CMD_NAME); diff --git a/extension/src/goTelemetry.ts b/extension/src/goTelemetry.ts index a6ce031711..b0a85b3dab 100644 --- a/extension/src/goTelemetry.ts +++ b/extension/src/goTelemetry.ts @@ -49,7 +49,12 @@ export enum TelemetryKey { ACTIVATION_LATENCY_L_500MS = 'activation_latency:<500ms', ACTIVATION_LATENCY_L_1000MS = 'activation_latency:<1000ms', ACTIVATION_LATENCY_L_5000MS = 'activation_latency:<5000ms', - ACTIVATION_LATENCY_GE_5S = 'activation_latency:>=5s' + ACTIVATION_LATENCY_GE_5S = 'activation_latency:>=5s', + + // Indicates the tools usage. + TOOL_USAGE_GOTESTS = 'vscode-go/tool/usage:gotests', + TOOL_USAGE_GOPLAY = 'vscode-go/tool/usage:goplay', + TOOL_USAGE_GOMODIFYTAGS = 'vscode-go/tool/usage:gomodifytags' } /** From 6e371c5dc9db465694b0dcc174ffbb5be8da94ca Mon Sep 17 00:00:00 2001 From: Hongxiang Jiang Date: Tue, 1 Apr 2025 13:14:16 -0400 Subject: [PATCH 22/39] extension/package-lock.json: run npm audit fix Change-Id: Iea2451881fac28b42163c2c2affd11bfeb4f3b8b Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/662095 kokoro-CI: kokoro Reviewed-by: Madeline Kalil LUCI-TryBot-Result: Go LUCI Auto-Submit: Hongxiang Jiang --- extension/package-lock.json | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/extension/package-lock.json b/extension/package-lock.json index 92d3416bac..84283fa444 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1834,10 +1834,11 @@ "dev": true }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -5304,10 +5305,11 @@ } }, "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", + "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "chownr": "^1.1.1", @@ -7146,9 +7148,9 @@ "dev": true }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -9717,9 +9719,9 @@ } }, "tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", + "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", "dev": true, "optional": true, "requires": { From f122b06045fd67bdc0ea6bc6612ed9831932dc52 Mon Sep 17 00:00:00 2001 From: Ethan Reesor Date: Wed, 26 Mar 2025 21:26:09 -0500 Subject: [PATCH 23/39] extension/src/goDebugConfiguration: fix debugging package urls A package URL such as example.com/foo/bar is a valid program for dlv debug. This updates goDebugConfiguration's handling of the program attribute to prevent it from A) treating it as a path (prepending the workspace folder path) and B) throwing an error due to it not being a valid filesystem path. Updates golang/vscode-go#1627. Change-Id: I9ad15752271c84fa069106c3a96206b6edcc3797 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/661195 kokoro-CI: kokoro Reviewed-by: Hongxiang Jiang Reviewed-by: Dmitri Shuralyov LUCI-TryBot-Result: Go LUCI --- extension/src/goDebugConfiguration.ts | 100 +++++++++++++++--- extension/test/integration/goDebug.test.ts | 1 + .../integration/goDebugConfiguration.test.ts | 23 ++++ 3 files changed, 109 insertions(+), 15 deletions(-) diff --git a/extension/src/goDebugConfiguration.ts b/extension/src/goDebugConfiguration.ts index 723a9ad5e4..94c7e12d9c 100644 --- a/extension/src/goDebugConfiguration.ts +++ b/extension/src/goDebugConfiguration.ts @@ -488,24 +488,45 @@ export class GoDebugConfigurationProvider implements vscode.DebugConfigurationPr debugConfiguration['env'] = Object.assign(goToolsEnvVars, fileEnvs, env); debugConfiguration['envFile'] = undefined; // unset, since we already processed. - const entriesWithRelativePaths = ['cwd', 'output', 'program'].filter( - (attr) => debugConfiguration[attr] && !path.isAbsolute(debugConfiguration[attr]) - ); if (debugAdapter === 'dlv-dap') { - // 1. Relative paths -> absolute paths - if (entriesWithRelativePaths.length > 0) { - const workspaceRoot = folder?.uri.fsPath; - if (workspaceRoot) { - entriesWithRelativePaths.forEach((attr) => { - debugConfiguration[attr] = path.join(workspaceRoot, debugConfiguration[attr]); - }); - } else { + // If the user provides a relative path outside of a workspace + // folder, warn them, but only once. + let didWarn = false; + const makeRelative = (s: string) => { + if (folder) { + return path.join(folder.uri.fsPath, s); + } + + if (!didWarn) { + didWarn = true; this.showWarning( 'relativePathsWithoutWorkspaceFolder', 'Behavior when using relative paths without a workspace folder for `cwd`, `program`, or `output` is undefined.' ); } - } + + return s; + }; + + // 1. Relative paths -> absolute paths + ['cwd', 'output', 'program'].forEach((attr) => { + const value = debugConfiguration[attr]; + if (!value || path.isAbsolute(value)) return; + + // Make the path relative (the program attribute needs + // additional checks). + if (attr !== 'program') { + debugConfiguration[attr] = makeRelative(value); + return; + } + + // If the program could be a package URL, don't alter it unless + // we can confirm that it is also a file path. + if (!isPackageUrl(value) || isFsPath(value, folder?.uri.fsPath)) { + debugConfiguration[attr] = makeRelative(value); + } + }); + // 2. For launch debug/test modes that builds the debug target, // delve needs to be launched from the right directory (inside the main module of the target). // Compute the launch dir heuristically, and translate the dirname in program to a path relative to buildDir. @@ -518,7 +539,8 @@ export class GoDebugConfigurationProvider implements vscode.DebugConfigurationPr // with a relative path. (https://github.com/golang/vscode-go/issues/1713) // parseDebugProgramArgSync will throw an error if `program` is invalid. const { program, dirname, programIsDirectory } = parseDebugProgramArgSync( - debugConfiguration['program'] + debugConfiguration['program'], + folder?.uri.fsPath ); if ( dirname && @@ -583,11 +605,15 @@ export async function maybeJoinFlags(dlvToolPath: string, flags: string | string // parseDebugProgramArgSync parses program arg of debug/auto/test launch requests. export function parseDebugProgramArgSync( - program: string -): { program: string; dirname: string; programIsDirectory: boolean } { + program: string, + cwd?: string +): { program: string; dirname?: string; programIsDirectory: boolean } { if (!program) { throw new Error('The program attribute is missing in the debug configuration in launch.json'); } + if (isPackageUrl(program) && !isFsPath(program, cwd)) { + return { program, programIsDirectory: true }; + } try { const pstats = lstatSync(program); if (pstats.isDirectory()) { @@ -606,3 +632,47 @@ export function parseDebugProgramArgSync( `The program attribute '${program}' must be a valid directory or .go file in debug/test/auto modes.` ); } + +/** + * Returns true if the given string is an absolute path or refers to a file or + * directory in the current working directory, or `wd` if specified. + * @param s The prospective file or directory path. + * @param wd The working directory to use instead of `process.cwd()`. + */ +function isFsPath(s: string, wd?: string) { + // If it's absolute, it's a path. + if (path.isAbsolute(s)) return; + + // If the caller specifies a working directory, make the prospective path + // absolute. + if (wd) s = path.join(wd, s); + + try { + // If lstat doesn't throw, the path refers to a file or directory. + lstatSync(s); + return true; + } catch (error) { + // ENOENT means nothing exists at the specified path. + if (error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT') { + return false; + } + + // If the error is something unexpected, rethrow it. + throw error; + } +} + +function isPackageUrl(s: string) { + // If the string does not contain `/` and ends with .go it is most likely + // intended to be a file path. If the file exists it would be caught by + // isFsPath, but otherwise "the file doesn't exist" is much less confusing + // than "the package doesn't exist" if the user is trying to execute a test + // file and got the path wrong. + if (s.match(/^[^/]*\.go$/)) { + return s; + } + + // If the string starts with domain.tld/ and it doesn't reference a file, + // assume it's a package URL + return s.match(/^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9](\.[a-zA-Z]{2,})+\//); +} diff --git a/extension/test/integration/goDebug.test.ts b/extension/test/integration/goDebug.test.ts index 8db072bcec..cd0ef62207 100644 --- a/extension/test/integration/goDebug.test.ts +++ b/extension/test/integration/goDebug.test.ts @@ -2051,6 +2051,7 @@ const testAll = (ctx: Mocha.Context, isDlvDap: boolean, withConsole?: string) => // second test has a chance to run it. if (!config['output'] && ['debug', 'auto', 'test'].includes(config['mode'])) { const dir = parseDebugProgramArgSync(config['program']).dirname; + if (!dir) throw new Error('Debug configuration does not define an output directory'); config['output'] = path.join(dir, `__debug_bin_${testNumber}`); } testNumber++; diff --git a/extension/test/integration/goDebugConfiguration.test.ts b/extension/test/integration/goDebugConfiguration.test.ts index e2e21a11ad..877e72cf01 100644 --- a/extension/test/integration/goDebugConfiguration.test.ts +++ b/extension/test/integration/goDebugConfiguration.test.ts @@ -717,6 +717,29 @@ suite('Debug Configuration Converts Relative Paths', () => { ); }); + test('allow package path in dlv-dap mode', () => { + const config = debugConfig('dlv-dap'); + config.program = 'example.com/foo/bar'; + + const workspaceFolder = { + uri: vscode.Uri.file(workspaceDir), + name: 'test', + index: 0 + }; + const { program, cwd, __buildDir } = debugConfigProvider.resolveDebugConfigurationWithSubstitutedVariables( + workspaceFolder, + config + )!; + assert.deepStrictEqual( + { program, cwd, __buildDir }, + { + program: 'example.com/foo/bar', + cwd: workspaceDir, + __buildDir: undefined + } + ); + }); + test('program and __buildDir are updated while resolving debug configuration in dlv-dap mode', () => { createDirRecursively(path.join(workspaceDir, 'foo', 'bar', 'pkg')); From 5e6ba196ad79435b0cbad30ed229be97fb8a7e20 Mon Sep 17 00:00:00 2001 From: logica0419 Date: Tue, 25 Mar 2025 04:28:01 +0900 Subject: [PATCH 24/39] extension/src/goLint: adding golangci-lint-v2 to lintTool This change adds support for golangci-lint v2 to the extension. golangci-lint deleted some flags this extension used during the update, so v1 and v2 must be handled differently. Fixes golang/vscode-go#3732 Change-Id: I4bb1ec4e6d806227254d7f5de9bbdc00cde1afa6 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/660415 Reviewed-by: Dmitri Shuralyov kokoro-CI: kokoro Reviewed-by: Hongxiang Jiang LUCI-TryBot-Result: Go LUCI --- CHANGELOG.md | 5 +++ docs/settings.md | 2 +- extension/package.json | 1 + extension/src/goInstallTools.ts | 26 ++++++++++++ extension/src/goLint.ts | 63 +++++++++++++++++++++++------ extension/src/goTools.ts | 4 +- extension/src/goToolsInformation.ts | 9 +++++ extension/tools/allTools.ts.in | 9 +++++ 8 files changed, 104 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df2c37eb35..7db714406e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). * Introduced quick pick separator in command `Go: Choose Go Environment` showing diff between options locally discovered and options available for download. +* Added support for golangci-lint v2 ([Issue 3732](https://github.com/golang/vscode-go/issues/3732)) + * Added a new lint tool, `golangci-lint-v2`. It's added as an installable tool, so you can install it via the `Go: Install/Update Tools` command. + * You can switch v1 and v2 per workspace by using `golangci-lint` and `golangci-lint-v2` option. You must keep the `golangci-lint` executable version on your machine to v1 for that. + * You can also use `golangci-lint` executable updated to v2. Just keep using the `golangci-lint` option for that. + * Looking for a way to format your code with golangci-lint v2 on VS Code? Check the [golangci-lint documentation](https://golangci-lint.run/welcome/integrations/#visual-studio-code). ### Fixes diff --git a/docs/settings.md b/docs/settings.md index fa4abf37a6..c72d0b0fd7 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -362,7 +362,7 @@ Default: `"package"` ### `go.lintTool` Specifies Lint tool name.
-Allowed Options: `staticcheck`, `golint`, `golangci-lint`, `revive` +Allowed Options: `staticcheck`, `golint`, `golangci-lint`, `golangci-lint-v2`, `revive` Default: `"staticcheck"` ### `go.logging.level (deprecated)` diff --git a/extension/package.json b/extension/package.json index 2233693e8f..14bd3abaf3 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1174,6 +1174,7 @@ "staticcheck", "golint", "golangci-lint", + "golangci-lint-v2", "revive" ] }, diff --git a/extension/src/goInstallTools.ts b/extension/src/goInstallTools.ts index 67bc0441ef..02499c5afa 100644 --- a/extension/src/goInstallTools.ts +++ b/extension/src/goInstallTools.ts @@ -221,8 +221,10 @@ export async function installTools( } outputChannel.appendLine(envMsg); + let installingPath = ''; let installingMsg = `Installing ${missing.length} ${missing.length > 1 ? 'tools' : 'tool'} at `; if (envForTools['GOBIN']) { + installingPath = envForTools['GOBIN']; installingMsg += `the configured GOBIN: ${envForTools['GOBIN']}`; } else { const p = toolsGopath @@ -230,6 +232,10 @@ export async function installTools( .map((e) => path.join(e, 'bin')) .join(path.delimiter); installingMsg += `${p}`; + + if (p) { + installingPath = p; + } } outputChannel.appendLine(installingMsg); @@ -246,6 +252,14 @@ export async function installTools( const failures: { tool: ToolAtVersion; reason: string }[] = []; const tm = options?.toolsManager ?? defaultToolsManager; for (const tool of missing) { + // v2, v3... of the tools are installed with the same name as v1, + // but must be able to co-exist with other major versions in the GOBIN. + // Thus, we install it in a tmp directory and copy it to the GOBIN. + // See detail: golang/vscode-go#3732 + if (tool.name.match('-v\\d+$')) { + envForTools['GOBIN'] = path.join(installingPath, 'tmp'); + } + const failed = await tm.installTool(tool, goForInstall, envForTools); if (failed) { failures.push({ tool, reason: failed }); @@ -253,6 +267,18 @@ export async function installTools( // Restart the language server if a new binary has been installed. vscode.commands.executeCommand('go.languageserver.restart', RestartReason.INSTALLATION); } + + if (tool.name.match('-v\\d+$')) { + // grep the tool name without version. + const toolName = tool.name.match('^(?.+)-v\\d+$')?.groups?.tool; + if (!toolName) { + failures.push({ tool, reason: 'failed to grep tool name with regex' }); + continue; + } + + fs.copyFileSync(path.join(installingPath, 'tmp', toolName), path.join(installingPath, tool.name)); + fs.rmdirSync(path.join(installingPath, 'tmp'), { recursive: true }); + } } // Report detailed information about any failures. diff --git a/extension/src/goLint.ts b/extension/src/goLint.ts index b9d64503f9..d2b692379c 100644 --- a/extension/src/goLint.ts +++ b/extension/src/goLint.ts @@ -10,7 +10,8 @@ import { getGoConfig, getGoplsConfig } from './config'; import { toolExecutionEnvironment } from './goEnv'; import { diagnosticsStatusBarItem, outputChannel } from './goStatus'; import { goplsStaticcheckEnabled } from './goTools'; -import { getWorkspaceFolderPath, handleDiagnosticErrors, ICheckResult, resolvePath, runTool } from './util'; +import { inspectGoToolVersion } from './goInstallTools'; +import { getBinPath, getWorkspaceFolderPath, handleDiagnosticErrors, ICheckResult, resolvePath, runTool } from './util'; /** * Runs linter on the current file, package or workspace. @@ -63,7 +64,7 @@ export function lintCode(scope?: string): CommandFactory { * @param goConfig Configuration for the Go extension. * @param scope Scope in which to run the linter. */ -export function goLint( +export async function goLint( fileUri: vscode.Uri | undefined, goConfig: vscode.WorkspaceConfiguration, goplsConfig: vscode.WorkspaceConfiguration, @@ -112,24 +113,60 @@ export function goLint( } args.push(flag); }); - if (lintTool === 'golangci-lint') { + if (lintTool.startsWith('golangci-lint')) { + let version: number; + if (lintTool === 'golangci-lint-v2') { + version = 2; + } else { + const { moduleVersion } = await inspectGoToolVersion(getBinPath(lintTool)); + // if moduleVersion is undefined, treat it as version=1 + // if moduleVersion is higher than v1 (v2, v3...), treat it as version=2 + !moduleVersion || moduleVersion.startsWith('v1') ? (version = 1) : (version = 2); + } + + // append common flags if (args.indexOf('run') === -1) { args.unshift('run'); } - if (args.indexOf('--print-issued-lines=false') === -1) { - // print only file:number:column - args.push('--print-issued-lines=false'); - } - if (args.indexOf('--out-format=colored-line-number') === -1) { - // print file:number:column. - // Explicit override in case .golangci.yml calls for a format we don't understand - args.push('--out-format=colored-line-number'); - } - if (args.indexOf('--issues-exit-code=') === -1) { + if (args.indexOf('--issues-exit-code=0') === -1) { // adds an explicit no-error-code return argument, to avoid npm error // message detection logic. See golang/vscode-go/issues/411 args.push('--issues-exit-code=0'); } + switch (version) { + case 1: // append golangci-lint v1 flags + if (args.indexOf('--print-issued-lines=false') === -1) { + // print only file:number:column + args.push('--print-issued-lines=false'); + } + if (args.indexOf('--out-format=colored-line-number') === -1) { + // print file:number:column. + // Explicit override in case .golangci.yml calls for a format we don't understand + args.push('--out-format=colored-line-number'); + } + break; + + case 2: // append golangci-lint v2 flags + if (args.indexOf('--output.text.print-issued-lines=false') === -1) { + // print only file:number:column + args.push('--output.text.print-issued-lines=false'); + } + if (args.indexOf('--show-stats=false') === -1) { + // print only file:number:column + args.push('--show-stats=false'); + } + if (args.indexOf('--output.text.path=stdout') === -1) { + // print file:number:column. + // Explicit override in case .golangci.yml calls for a format we don't understand + args.push('--output.text.path=stdout'); + } + if (args.indexOf('--output.text.colors=true') === -1) { + // print file:number:column. + // Explicit override in case .golangci.yml calls for a format we don't understand + args.push('--output.text.colors=true'); + } + break; + } } if (scope === 'workspace' && currentWorkspace) { diff --git a/extension/src/goTools.ts b/extension/src/goTools.ts index 31ea0db9d1..c4d1b70cd4 100644 --- a/extension/src/goTools.ts +++ b/extension/src/goTools.ts @@ -156,7 +156,9 @@ export function getConfiguredTools(goConfig: { [key: string]: any }, goplsConfig if (goConfig['lintTool'] !== 'staticcheck' || !goplsStaticheckEnabled) { maybeAddTool(goConfig['lintTool']); } - return tools; + + // Remove duplicates since tools like golangci-lint v2 are both linter and formatter + return tools.filter((v, i, self) => self.indexOf(v) === i); } export function goplsStaticcheckEnabled( diff --git a/extension/src/goToolsInformation.ts b/extension/src/goToolsInformation.ts index b14bb686ec..2a0ee8b066 100644 --- a/extension/src/goToolsInformation.ts +++ b/extension/src/goToolsInformation.ts @@ -85,6 +85,15 @@ export const allToolsInformation: { [key: string]: Tool } = { description: 'Linter', minimumGoVersion: semver.coerce('1.20') }, + 'golangci-lint-v2': { + name: 'golangci-lint-v2', + importPath: 'github.com/golangci/golangci-lint/v2/cmd/golangci-lint', + modulePath: 'github.com/golangci/golangci-lint/v2', + replacedByGopls: false, + isImportant: true, + description: 'Linter', + minimumGoVersion: semver.coerce('1.23') + }, 'revive': { name: 'revive', importPath: 'github.com/mgechev/revive', diff --git a/extension/tools/allTools.ts.in b/extension/tools/allTools.ts.in index 67e8fcd333..e97db3a599 100644 --- a/extension/tools/allTools.ts.in +++ b/extension/tools/allTools.ts.in @@ -83,6 +83,15 @@ export const allToolsInformation: { [key: string]: Tool } = { description: 'Linter', minimumGoVersion: semver.coerce('1.20') }, + 'golangci-lint-v2': { + name: 'golangci-lint-v2', + importPath: 'github.com/golangci/golangci-lint/v2/cmd/golangci-lint', + modulePath: 'github.com/golangci/golangci-lint/v2', + replacedByGopls: false, + isImportant: true, + description: 'Linter', + minimumGoVersion: semver.coerce('1.23') + }, 'revive': { name: 'revive', importPath: 'github.com/mgechev/revive', From d1526fa93defd3b19e89a058e820b9eb54a11f4a Mon Sep 17 00:00:00 2001 From: logica0419 Date: Sat, 12 Apr 2025 03:02:53 +0900 Subject: [PATCH 25/39] extension/src/goLint: adding `--path-mode=abs` flag for golangci-lint v2 This change sets path mode to abs (supported from golangci-lint v2.1) when running golangci-lint v2. When `run.relative-path-mode` is set to `cfg` (which is the default), the file path in the linter output is relative to the config file, while vscode-go treats it as relative to the linted file. That makes the file path invalid, which prevents diagnostic errors from showing. The absolute output path is the efficient way to fix this problem. Fixes golang/vscode-go#3750 Change-Id: I77bc8a54062bad094128a14d764db94198379ee9 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/664877 Reviewed-by: Hongxiang Jiang Reviewed-by: Madeline Kalil LUCI-TryBot-Result: Go LUCI kokoro-CI: kokoro --- extension/src/goLint.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extension/src/goLint.ts b/extension/src/goLint.ts index d2b692379c..767f9005fe 100644 --- a/extension/src/goLint.ts +++ b/extension/src/goLint.ts @@ -165,6 +165,10 @@ export async function goLint( // Explicit override in case .golangci.yml calls for a format we don't understand args.push('--output.text.colors=true'); } + if (args.indexOf('--path-mode=abs') === -1) { + // print file name as absolute path + args.push('--path-mode=abs'); + } break; } } From 04fc87c6e7ee2c1e6ce49c03a4350a843d2b498d Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Tue, 15 Apr 2025 07:37:05 -0700 Subject: [PATCH 26/39] CHANGELOG.md: add release heading for v0.47.2 This is an automated CL which updates the CHANGELOG.md. Change-Id: I099810d508f7e3fbc0acc73acebd7fa2b72f7327 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/665675 LUCI-TryBot-Result: Go LUCI Reviewed-by: Madeline Kalil Reviewed-by: Hongxiang Jiang kokoro-CI: kokoro Auto-Submit: Gopher Robot --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7db714406e..0de49e40ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,15 @@ error during command execution when the command result did not include a token. ([Issue 3698](https://github.com/golang/vscode-go/issues/3698)) * Addressed an issue that broke the `Debug Subtest At Cursor` command. ([Issue 3718](https://github.com/golang/vscode-go/issues/3718)) +## v0.47.2 (prerelease) + +Date: 2025-04-15 + +This is the [pre-release version](https://code.visualstudio.com/api/working-with-extensions/publishing-extension#prerelease-extensions) of v0.48. + +**Full Changelog**: https://github.com/golang/vscode-go/compare/v0.46.0-rc.1...v0.47.2 +**Milestone**: https://github.com/golang/vscode-go/issues?q=milestone%3Av0.48.0 + ## v0.46.1 Date: 2025-03-04 From 0b77054321bf93d2446cfaef5b37c607760f65b5 Mon Sep 17 00:00:00 2001 From: logica0419 Date: Thu, 24 Apr 2025 16:00:52 +0900 Subject: [PATCH 27/39] extension/src/goLint: enabled to override `path-mode` flag Currently, users can't override or disable any flags added by vscode-go. However, in some environments, the `path-mode` flag needs to be overridden. (https://github.com/golang/vscode-go/issues/3750#issuecomment-2822315588) The flags related to the output format shouldn't be changed so that vscode-go can parse them correctly. Thus, I enable overriding the `path-mode` flag. Fixes golang/vscode-go#3750 Change-Id: I6258c7054ef91137c13d566744ac01a33caf0bd4 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/667735 kokoro-CI: kokoro LUCI-TryBot-Result: Go LUCI Reviewed-by: Carlos Amedee Reviewed-by: Hongxiang Jiang Auto-Submit: Hongxiang Jiang --- CHANGELOG.md | 1 + extension/src/goLint.ts | 13 ++++--------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0de49e40ac..14f263077d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ diff between options locally discovered and options available for download. * Added a new lint tool, `golangci-lint-v2`. It's added as an installable tool, so you can install it via the `Go: Install/Update Tools` command. * You can switch v1 and v2 per workspace by using `golangci-lint` and `golangci-lint-v2` option. You must keep the `golangci-lint` executable version on your machine to v1 for that. * You can also use `golangci-lint` executable updated to v2. Just keep using the `golangci-lint` option for that. + * The `path-mode` flag set by vscode-go can be overridden by the`go.lintFlags` option. * Looking for a way to format your code with golangci-lint v2 on VS Code? Check the [golangci-lint documentation](https://golangci-lint.run/welcome/integrations/#visual-studio-code). ### Fixes diff --git a/extension/src/goLint.ts b/extension/src/goLint.ts index 767f9005fe..de4f8849b5 100644 --- a/extension/src/goLint.ts +++ b/extension/src/goLint.ts @@ -121,7 +121,7 @@ export async function goLint( const { moduleVersion } = await inspectGoToolVersion(getBinPath(lintTool)); // if moduleVersion is undefined, treat it as version=1 // if moduleVersion is higher than v1 (v2, v3...), treat it as version=2 - !moduleVersion || moduleVersion.startsWith('v1') ? (version = 1) : (version = 2); + version = !moduleVersion || moduleVersion.startsWith('v1') ? 1 : 2; } // append common flags @@ -139,10 +139,10 @@ export async function goLint( // print only file:number:column args.push('--print-issued-lines=false'); } - if (args.indexOf('--out-format=colored-line-number') === -1) { + if (args.indexOf('--out-format=line-number') === -1) { // print file:number:column. // Explicit override in case .golangci.yml calls for a format we don't understand - args.push('--out-format=colored-line-number'); + args.push('--out-format=line-number'); } break; @@ -160,12 +160,7 @@ export async function goLint( // Explicit override in case .golangci.yml calls for a format we don't understand args.push('--output.text.path=stdout'); } - if (args.indexOf('--output.text.colors=true') === -1) { - // print file:number:column. - // Explicit override in case .golangci.yml calls for a format we don't understand - args.push('--output.text.colors=true'); - } - if (args.indexOf('--path-mode=abs') === -1) { + if (!args.some((v) => v.startsWith('--path-mode='))) { // print file name as absolute path args.push('--path-mode=abs'); } From 94938006af84c1b84e5b9bb712016b2da6d5cb41 Mon Sep 17 00:00:00 2001 From: Madeline Kalilh Date: Thu, 6 Feb 2025 16:14:40 -0500 Subject: [PATCH 28/39] extension/src: call gopls command for modify tags When the user has the newer version of gopls, call the gopls command modify_tags which uses the modifytags library to implement adding and removing struct tags. If the user does not have the latest gopls, we maintain the old behavior of downloading and invoking the gomodifytags executable. Change-Id: If01e9ff0a1eeb127ec5139b5b3cb97ef938ce620 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/652017 Reviewed-by: Hongxiang Jiang Reviewed-by: Robert Findley LUCI-TryBot-Result: Go LUCI kokoro-CI: kokoro --- docs/commands.md | 4 +- docs/features.md | 2 +- docs/tools.md | 4 +- extension/package.json | 4 +- extension/src/goModifytags.ts | 224 +++++++++++++++++----------- extension/src/goToolsInformation.ts | 2 +- 6 files changed, 150 insertions(+), 90 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index cd61af788b..6730c7caeb 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -169,11 +169,11 @@ Start the Go language server's maintainer interface (a web server). ### `Go: Add Tags To Struct Fields` -Add tags configured in go.addTags setting to selected struct using gomodifytags +Add tags configured in go.addTags setting to selected struct using gomodifytags (via gopls) ### `Go: Remove Tags From Struct Fields` -Remove tags configured in go.removeTags setting from selected struct using gomodifytags +Remove tags configured in go.removeTags setting from selected struct using gomodifytags (via gopls) ### `Go: Show All Commands...` diff --git a/docs/features.md b/docs/features.md index 953ac4bd1f..4396c32301 100644 --- a/docs/features.md +++ b/docs/features.md @@ -253,7 +253,7 @@ For known issues with this feature see [golang/go#37170](https://github.com/gola ### Add or remove struct tags -Use the [`Go: Add Tags to Struct Fields`](commands.md#go-add-tags-to-struct-fields) command to automatically generate or remove [tags](https://pkg.go.dev/reflect?tab=doc#StructTag) for your struct. This feature is provided by the [`gomodifytags`](tools.md#gomodifytags) tool. +Use the [`Go: Add Tags to Struct Fields`](commands.md#go-add-tags-to-struct-fields) command to automatically generate or remove [tags](https://pkg.go.dev/reflect?tab=doc#StructTag) for your struct. This feature is provided by the [`gomodifytags`](tools.md#gomodifytags) tool invoked via gopls.
Add tags to struct fields
diff --git a/docs/tools.md b/docs/tools.md index 10ff1edc85..fc09e84679 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -48,7 +48,9 @@ This tool provides support for the [`Go: Run on Go Playground`](features.md#go-p ### [`gomodifytags`](https://pkg.go.dev/github.com/fatih/gomodifytags?tab=overview) -This tool provides support for the [`Go: Add Tags to Struct Fields`](features.md#add-or-remove-struct-tags) and [`Go: Remove Tags From Struct Fields`](features.md#add-or-remove-struct-tags) commands. +This tool provides support for the [`Go: Add Tags to Struct Fields`](features.md#add-or-remove-struct-tags) and [`Go: Remove Tags From Struct Fields`](features.md#add-or-remove-struct-tags) commands when using older versions of gopls. The latest +version of gopls has a gopls.modify_tags command which directly invokes the +gomodifytags library. ### [`impl`](https://github.com/josharian/impl) diff --git a/extension/package.json b/extension/package.json index 14bd3abaf3..6928346700 100644 --- a/extension/package.json +++ b/extension/package.json @@ -391,12 +391,12 @@ { "command": "go.add.tags", "title": "Go: Add Tags To Struct Fields", - "description": "Add tags configured in go.addTags setting to selected struct using gomodifytags" + "description": "Add tags configured in go.addTags setting to selected struct using gomodifytags (via gopls)" }, { "command": "go.remove.tags", "title": "Go: Remove Tags From Struct Fields", - "description": "Remove tags configured in go.removeTags setting from selected struct using gomodifytags" + "description": "Remove tags configured in go.removeTags setting from selected struct using gomodifytags (via gopls)" }, { "command": "go.show.commands", diff --git a/extension/src/goModifytags.ts b/extension/src/goModifytags.ts index faf00e7d35..d097031843 100644 --- a/extension/src/goModifytags.ts +++ b/extension/src/goModifytags.ts @@ -16,6 +16,8 @@ import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools'; import { byteOffsetAt, getBinPath, getFileArchive } from './util'; import { TelemetryKey, telemetryReporter } from './goTelemetry'; +const COMMAND = 'gopls.modify_tags'; + // Interface for the output from gomodifytags interface GomodifytagsOutput { start: number; @@ -23,6 +25,22 @@ interface GomodifytagsOutput { lines: string[]; } +// Interface for the arguments passed to gopls.modify_tags command. URI and range +// are required parameters collected by the extension based on the open editor, +// and the rest of the args are collected by user input or user settings. +interface GoModifyTagsArgs { + URI: string; + range: vscode.Range; + add?: string; + addOptions?: string; + remove?: string; + removeOptions?: string; + transform?: string; + valueFormat?: string; + clear?: boolean; + clearOptions?: boolean; +} + // Interface for settings configuration for adding and removing tags interface GoTagsConfig { [key: string]: any; @@ -32,45 +50,84 @@ interface GoTagsConfig { template: string; } -export const addTags: CommandFactory = () => (commandArgs: GoTagsConfig) => { - const args = getCommonArgs(); - if (!args) { - return; +export const addTags: CommandFactory = (_ctx, goCtx) => async (commandArgs: GoTagsConfig) => { + const useGoplsCommand = goCtx.serverInfo?.Commands?.includes(COMMAND); + if (useGoplsCommand) { + const args = getCommonArgs(); + if (!args) { + return; + } + const [tags, options, transformValue, template] = await getTagsAndOptions(getGoConfig()?.addTags, commandArgs); + if (!tags && !options) { + return; + } + if (tags) { + args.add = tags; + } + if (options) { + args.addOptions = options; + } + if (transformValue) { + args.transform = transformValue; + } + if (template) { + args.valueFormat = template; + } + await vscode.commands.executeCommand(COMMAND, args); + } else { + const args = getCommonArgsOld(); + if (!args) { + return; + } + const [tags, options, transformValue, template] = await getTagsAndOptions(getGoConfig()?.addTags, commandArgs); + if (!tags && !options) { + return; + } + if (tags) { + args.push('--add-tags'); + args.push(tags); + } + if (options) { + args.push('--add-options'); + args.push(options); + } + if (transformValue) { + args.push('--transform'); + args.push(transformValue); + } + if (template) { + args.push('--template'); + args.push(template); + } + runGomodifytags(args); } - - getTagsAndOptions(getGoConfig()['addTags'], commandArgs).then( - ([tags, options, transformValue, template]) => { - if (!tags && !options) { - return; - } - if (tags) { - args.push('--add-tags'); - args.push(tags); - } - if (options) { - args.push('--add-options'); - args.push(options); - } - if (transformValue) { - args.push('--transform'); - args.push(transformValue); - } - if (template) { - args.push('--template'); - args.push(template); - } - runGomodifytags(args); - } - ); }; -export const removeTags: CommandFactory = () => (commandArgs: GoTagsConfig) => { - const args = getCommonArgs(); - if (!args) { - return; - } - - getTagsAndOptions(getGoConfig()['removeTags'], commandArgs).then(([tags, options]) => { +export const removeTags: CommandFactory = (_ctx, goCtx) => async (commandArgs: GoTagsConfig) => { + const useGoplsCommand = goCtx.serverInfo?.Commands?.includes(COMMAND); + if (useGoplsCommand) { + const args = getCommonArgs(); + if (!args) { + return; + } + const [tags, options] = await getTagsAndOptions(getGoConfig()?.removeTags, commandArgs); + if (!tags && !options) { + args.clear = true; + args.clearOptions = true; + } + if (tags) { + args.remove = tags; + } + if (options) { + args.removeOptions = options; + } + vscode.commands.executeCommand(COMMAND, args); + } else { + const args = getCommonArgsOld(); + if (!args) { + return; + } + const [tags, options] = await getTagsAndOptions(getGoConfig()?.removeTags, commandArgs); if (!tags && !options) { args.push('--clear-tags'); args.push('--clear-options'); @@ -84,18 +141,19 @@ export const removeTags: CommandFactory = () => (commandArgs: GoTagsConfig) => { args.push(options); } runGomodifytags(args); - }); + } }; -function getCommonArgs(): string[] { +// getCommonArgsOld produces the flags used for executing the gomodifytags binary. +function getCommonArgsOld(): string[] | undefined { const editor = vscode.window.activeTextEditor; if (!editor) { vscode.window.showInformationMessage('No editor is active.'); - return []; + return undefined; } if (!editor.document.fileName.endsWith('.go')) { vscode.window.showInformationMessage('Current file is not a Go file.'); - return []; + return undefined; } const args = ['-modified', '-file', editor.document.fileName, '-format', 'json']; if ( @@ -115,61 +173,61 @@ function getCommonArgs(): string[] { return args; } -function getTagsAndOptions(config: GoTagsConfig, commandArgs: GoTagsConfig): Thenable<(string | undefined)[]> { - const tags = commandArgs && commandArgs.hasOwnProperty('tags') ? commandArgs['tags'] : config['tags']; - const options = commandArgs && commandArgs.hasOwnProperty('options') ? commandArgs['options'] : config['options']; - const promptForTags = - commandArgs && commandArgs.hasOwnProperty('promptForTags') - ? commandArgs['promptForTags'] - : config['promptForTags']; - const transformValue: string = - commandArgs && commandArgs.hasOwnProperty('transform') ? commandArgs['transform'] : config['transform']; - const format: string = - commandArgs && commandArgs.hasOwnProperty('template') ? commandArgs['template'] : config['template']; +// getCommonArgs produces the args used for calling the gopls.modify_tags command. +function getCommonArgs(): GoModifyTagsArgs | undefined { + const editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showInformationMessage('No editor is active.'); + return undefined; + } + if (!editor.document.fileName.endsWith('.go')) { + vscode.window.showInformationMessage('Current file is not a Go file.'); + return undefined; + } + const args: GoModifyTagsArgs = { + URI: editor.document.uri.toString(), + range: editor.selection + }; + return args; +} + +async function getTagsAndOptions(config: GoTagsConfig, commandArgs: GoTagsConfig): Promise<(string | undefined)[]> { + const tags = commandArgs && commandArgs.tags ? commandArgs.tags : config.tags; + const options = commandArgs && commandArgs.options ? commandArgs.options : config.options; + const promptForTags = commandArgs && commandArgs.promptForTags ? commandArgs.promptForTags : config.promptForTags; + const transformValue: string = commandArgs && commandArgs.transform ? commandArgs.transform : config.transform; + const format: string = commandArgs && commandArgs.template ? commandArgs.template : config.template; if (!promptForTags) { return Promise.resolve([tags, options, transformValue, format]); } - return vscode.window - .showInputBox({ - value: tags, - prompt: 'Enter comma separated tag names' - }) - .then((inputTags) => { - return vscode.window - .showInputBox({ - value: options, - prompt: 'Enter comma separated options' - }) - .then((inputOptions) => { - return vscode.window - .showInputBox({ - value: transformValue, - prompt: 'Enter transform value' - }) - .then((transformOption) => { - return vscode.window - .showInputBox({ - value: format, - prompt: 'Enter template value' - }) - .then((template) => { - return [inputTags, inputOptions, transformOption, template]; - }); - }); - }); - }); + const inputTags = await vscode.window.showInputBox({ + value: tags, + prompt: 'Enter comma separated tag names' + }); + const inputOptions = await vscode.window.showInputBox({ + value: options, + prompt: 'Enter comma separated options' + }); + const transformOption = await vscode.window.showInputBox({ + value: transformValue, + prompt: 'Enter transform value' + }); + const template = await vscode.window.showInputBox({ + value: format, + prompt: 'Enter template value' + }); + return [inputTags, inputOptions, transformOption, template]; } -function runGomodifytags(args: string[]) { +async function runGomodifytags(args: string[]) { telemetryReporter.add(TelemetryKey.TOOL_USAGE_GOMODIFYTAGS, 1); - - const gomodifytags = getBinPath('gomodifytags'); const editor = vscode.window.activeTextEditor; if (!editor) { return; } + const gomodifytags = getBinPath('gomodifytags'); const input = getFileArchive(editor.document); const p = cp.execFile(gomodifytags, args, { env: toolExecutionEnvironment() }, (err, stdout, stderr) => { if (err && (err).code === 'ENOENT') { diff --git a/extension/src/goToolsInformation.ts b/extension/src/goToolsInformation.ts index 2a0ee8b066..f4c81b363a 100644 --- a/extension/src/goToolsInformation.ts +++ b/extension/src/goToolsInformation.ts @@ -9,7 +9,7 @@ export const allToolsInformation: { [key: string]: Tool } = { name: 'gomodifytags', importPath: 'github.com/fatih/gomodifytags', modulePath: 'github.com/fatih/gomodifytags', - replacedByGopls: false, + replacedByGopls: true, isImportant: false, description: 'Modify tags on structs', defaultVersion: 'v1.17.0' From 3673270a540086ac93d176350bd6fd8d30fd058e Mon Sep 17 00:00:00 2001 From: Hongxiang Jiang Date: Thu, 29 May 2025 10:58:27 -0400 Subject: [PATCH 29/39] extension/package.json: upgrade @types/vscode and vscode engine vscode v1.71.0 TestRunProfile interface introduce property supportContinuousRun (default false). microsoft/vscode#134941 vscode v1.81 TestController interface introduce property invalidateTestResults mircosoft/vscode#134970 vscode v1.86 TestRunProfile interface introduce property invalidate onDidChangeDefault. microsoft/vscode#193160 vscode v1.88 TestRun interface introduce property addCoverage, onDidDispose. microsoft/vscode#208463 vscode v1.90 TestRunRequest class introduce non-optional property preserveFocus. microsoft/vscode#213601 Last update CL 559737. The latest version of vscode 1.100.0 and he vscode 1.90.0 is released May 2024. See https://code.visualstudio.com/updates/v1_90 Change-Id: I8abc9e206cdd842b1b75e264feeaee3cb27eda07 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/677156 kokoro-CI: kokoro LUCI-TryBot-Result: Go LUCI Reviewed-by: Madeline Kalil Reviewed-by: Ethan Reesor --- README.md | 2 +- extension/package-lock.json | 645 ++++++++++++++++++-- extension/package.json | 8 +- extension/test/gopls/goTest.explore.test.ts | 12 +- extension/test/gopls/goTest.run.test.ts | 18 +- extension/test/mocks/MockTest.ts | 19 +- 6 files changed, 622 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index f78618bd16..3f74266473 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ provides rich language support for the ## Requirements -* Visual Studio Code 1.75 or newer (or editors compatible with VS Code 1.75+ APIs) +* Visual Studio Code 1.90 or newer (or editors compatible with VS Code 1.90+ APIs) * Go 1.21 or newer. ## Quick Start diff --git a/extension/package-lock.json b/extension/package-lock.json index 84283fa444..8486f764ee 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -31,9 +31,9 @@ "@types/node-fetch": "2.6.9", "@types/semver": "7.3.4", "@types/sinon": "9.0.11", - "@types/vscode": "1.75.0", - "@vscode/debugadapter-testsupport": "1.58.0", - "@vscode/test-electron": "2.3.8", + "@types/vscode": "1.90.0", + "@vscode/debugadapter-testsupport": "1.68.0", + "@vscode/test-electron": "2.5.2", "@vscode/vsce": "2.23.0", "adm-zip": "0.4.16", "esbuild": "0.17.10", @@ -638,15 +638,6 @@ "node": ">=10" } }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/@types/adm-zip": { "version": "0.4.33", "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.4.33.tgz", @@ -779,10 +770,11 @@ "dev": true }, "node_modules/@types/vscode": { - "version": "1.75.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.75.0.tgz", - "integrity": "sha512-SAr0PoOhJS6FUq5LjNr8C/StBKALZwDVm3+U4pjF/3iYkt3GioJOPV/oB1Sf1l7lROe4TgrMyL5N1yaEgTWycw==", - "dev": true + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.90.0.tgz", + "integrity": "sha512-oT+ZJL7qHS9Z8bs0+WKf/kQ27qWYR3trsXpq46YDjFqBsMLG4ygGGjPaJ2tyrH0wJzjOEmDyg9PDJBBhWg9pkQ==", + "dev": true, + "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.30.5", @@ -1070,38 +1062,79 @@ "dev": true }, "node_modules/@vscode/debugadapter-testsupport": { - "version": "1.58.0", - "resolved": "https://registry.npmjs.org/@vscode/debugadapter-testsupport/-/debugadapter-testsupport-1.58.0.tgz", - "integrity": "sha512-a6Q4K6jTG0J5+yb7bRKiAPu3Ob+rnkRJRSagK5SHsZi64s4JhEpqf842IeTqqEjAmjt9/FXcpk1WViqEtx53qw==", + "version": "1.68.0", + "resolved": "https://registry.npmjs.org/@vscode/debugadapter-testsupport/-/debugadapter-testsupport-1.68.0.tgz", + "integrity": "sha512-UpbaPsCGMKyjIvJFtqFKDD1MVvME50xuOtRBPrqY1WdhAOLjWQN7FcKEoHv3X85twfNL21jW2M54FYwEdEQv4Q==", "dev": true, + "license": "MIT", "dependencies": { - "@vscode/debugprotocol": "1.58.0" + "@vscode/debugprotocol": "1.68.0" }, "engines": { "node": ">=14" } }, "node_modules/@vscode/debugprotocol": { - "version": "1.58.0", - "resolved": "https://registry.npmjs.org/@vscode/debugprotocol/-/debugprotocol-1.58.0.tgz", - "integrity": "sha512-64gY3PdU7jmYDwLRJFZ5XL2BC8TK5mdhZ60XLTZn17yfbJPKCcmFDuQAkVfOPsjn7o4f6YWFy3AXSR0V9gY6jA==", - "dev": true + "version": "1.68.0", + "resolved": "https://registry.npmjs.org/@vscode/debugprotocol/-/debugprotocol-1.68.0.tgz", + "integrity": "sha512-2J27dysaXmvnfuhFGhfeuxfHRXunqNPxtBoR3koiTOA9rdxWNDTa1zIFLCFMSHJ9MPTPKFcBeblsyaCJCIlQxg==", + "dev": true, + "license": "MIT" }, "node_modules/@vscode/test-electron": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.3.8.tgz", - "integrity": "sha512-b4aZZsBKtMGdDljAsOPObnAi7+VWIaYl3ylCz1jTs+oV6BZ4TNHcVNC3xUn0azPeszBmwSBDQYfFESIaUQnrOg==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.5.2.tgz", + "integrity": "sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==", "dev": true, + "license": "MIT", "dependencies": { - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", "jszip": "^3.10.1", - "semver": "^7.5.2" + "ora": "^8.1.0", + "semver": "^7.6.2" }, "engines": { "node": ">=16" } }, + "node_modules/@vscode/test-electron/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@vscode/test-electron/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@vscode/test-electron/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@vscode/vsce": { "version": "2.23.0", "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.23.0.tgz", @@ -1742,6 +1775,19 @@ "node": ">=8" } }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", @@ -2814,6 +2860,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", @@ -3118,19 +3177,54 @@ "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==" }, "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, + "license": "MIT", "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/http2-wrapper": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", @@ -3364,6 +3458,19 @@ "node": ">=0.10.0" } }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3834,6 +3941,19 @@ "node": ">=6" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", @@ -4289,6 +4409,202 @@ "node": ">= 0.8.0" } }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ora/node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ora/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -5224,6 +5540,19 @@ "spdx-ranges": "^2.0.0" } }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -6271,12 +6600,6 @@ "defer-to-connect": "^2.0.0" } }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true - }, "@types/adm-zip": { "version": "0.4.33", "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.4.33.tgz", @@ -6409,9 +6732,9 @@ "dev": true }, "@types/vscode": { - "version": "1.75.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.75.0.tgz", - "integrity": "sha512-SAr0PoOhJS6FUq5LjNr8C/StBKALZwDVm3+U4pjF/3iYkt3GioJOPV/oB1Sf1l7lROe4TgrMyL5N1yaEgTWycw==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.90.0.tgz", + "integrity": "sha512-oT+ZJL7qHS9Z8bs0+WKf/kQ27qWYR3trsXpq46YDjFqBsMLG4ygGGjPaJ2tyrH0wJzjOEmDyg9PDJBBhWg9pkQ==", "dev": true }, "@typescript-eslint/eslint-plugin": { @@ -6577,30 +6900,55 @@ "dev": true }, "@vscode/debugadapter-testsupport": { - "version": "1.58.0", - "resolved": "https://registry.npmjs.org/@vscode/debugadapter-testsupport/-/debugadapter-testsupport-1.58.0.tgz", - "integrity": "sha512-a6Q4K6jTG0J5+yb7bRKiAPu3Ob+rnkRJRSagK5SHsZi64s4JhEpqf842IeTqqEjAmjt9/FXcpk1WViqEtx53qw==", + "version": "1.68.0", + "resolved": "https://registry.npmjs.org/@vscode/debugadapter-testsupport/-/debugadapter-testsupport-1.68.0.tgz", + "integrity": "sha512-UpbaPsCGMKyjIvJFtqFKDD1MVvME50xuOtRBPrqY1WdhAOLjWQN7FcKEoHv3X85twfNL21jW2M54FYwEdEQv4Q==", "dev": true, "requires": { - "@vscode/debugprotocol": "1.58.0" + "@vscode/debugprotocol": "1.68.0" } }, "@vscode/debugprotocol": { - "version": "1.58.0", - "resolved": "https://registry.npmjs.org/@vscode/debugprotocol/-/debugprotocol-1.58.0.tgz", - "integrity": "sha512-64gY3PdU7jmYDwLRJFZ5XL2BC8TK5mdhZ60XLTZn17yfbJPKCcmFDuQAkVfOPsjn7o4f6YWFy3AXSR0V9gY6jA==", + "version": "1.68.0", + "resolved": "https://registry.npmjs.org/@vscode/debugprotocol/-/debugprotocol-1.68.0.tgz", + "integrity": "sha512-2J27dysaXmvnfuhFGhfeuxfHRXunqNPxtBoR3koiTOA9rdxWNDTa1zIFLCFMSHJ9MPTPKFcBeblsyaCJCIlQxg==", "dev": true }, "@vscode/test-electron": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.3.8.tgz", - "integrity": "sha512-b4aZZsBKtMGdDljAsOPObnAi7+VWIaYl3ylCz1jTs+oV6BZ4TNHcVNC3xUn0azPeszBmwSBDQYfFESIaUQnrOg==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.5.2.tgz", + "integrity": "sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==", "dev": true, "requires": { - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", "jszip": "^3.10.1", - "semver": "^7.5.2" + "ora": "^8.1.0", + "semver": "^7.6.2" + }, + "dependencies": { + "agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true + }, + "https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "requires": { + "agent-base": "^7.1.2", + "debug": "4" + } + }, + "semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true + } } }, "@vscode/vsce": { @@ -7072,6 +7420,12 @@ "restore-cursor": "^3.1.0" } }, + "cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true + }, "cli-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", @@ -7873,6 +8227,12 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true + }, "get-intrinsic": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", @@ -8084,14 +8444,36 @@ "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==" }, "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "dependencies": { + "agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true + }, + "debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } } }, "http2-wrapper": { @@ -8264,6 +8646,12 @@ "is-extglob": "^2.1.1" } }, + "is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -8630,6 +9018,12 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, + "mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true + }, "mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", @@ -8965,6 +9359,121 @@ "word-wrap": "^1.2.3" } }, + "ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "requires": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true + }, + "chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true + }, + "cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "requires": { + "restore-cursor": "^5.0.0" + } + }, + "emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true + }, + "log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "requires": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "dependencies": { + "is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true + } + } + }, + "onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "requires": { + "mimic-function": "^5.0.0" + } + }, + "restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "requires": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + } + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, + "string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "requires": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + } + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -9659,6 +10168,12 @@ "spdx-ranges": "^2.0.0" } }, + "stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", diff --git a/extension/package.json b/extension/package.json index 6928346700..8a7cc3b5ad 100644 --- a/extension/package.json +++ b/extension/package.json @@ -74,9 +74,9 @@ "@types/node-fetch": "2.6.9", "@types/semver": "7.3.4", "@types/sinon": "9.0.11", - "@types/vscode": "1.75.0", - "@vscode/debugadapter-testsupport": "1.58.0", - "@vscode/test-electron": "2.3.8", + "@types/vscode": "1.90.0", + "@vscode/debugadapter-testsupport": "1.68.0", + "@vscode/test-electron": "2.5.2", "@vscode/vsce": "2.23.0", "adm-zip": "0.4.16", "esbuild": "0.17.10", @@ -91,7 +91,7 @@ "yarn": "1.22.22" }, "engines": { - "vscode": "^1.75.0", + "vscode": "^1.90.0", "node": ">=16.14.2" }, "activationEvents": [ diff --git a/extension/test/gopls/goTest.explore.test.ts b/extension/test/gopls/goTest.explore.test.ts index 6cec1d5637..b2882ca082 100644 --- a/extension/test/gopls/goTest.explore.test.ts +++ b/extension/test/gopls/goTest.explore.test.ts @@ -361,7 +361,8 @@ suite('Go Test Explorer', () => { await explorer.runner.run({ include: [tests[0]], exclude: undefined, - profile: undefined + profile: undefined, + preserveFocus: false }); assert.strictEqual(runStub.callCount, 1, 'Expected goTest to be called once'); assert.deepStrictEqual(runStub.lastCall.args[0].functions, ['TestFoo']); @@ -383,7 +384,8 @@ suite('Go Test Explorer', () => { await explorer.runner.run({ include: [tests[0]], exclude: undefined, - profile: undefined + profile: undefined, + preserveFocus: false }); assert.strictEqual(runStub.callCount, 2, 'Expected goTest to be called twice'); assert.deepStrictEqual(runStub.firstCall.args[0].functions, ['TestFoo']); @@ -431,7 +433,8 @@ suite('Go Test Explorer', () => { await explorer.runner.run({ include: [test], exclude: undefined, - profile: undefined + profile: undefined, + preserveFocus: false }); assert.strictEqual(runStub.callCount, 1, 'Expected goTest to be called once'); @@ -456,7 +459,8 @@ suite('Go Test Explorer', () => { await explorer.runner.run({ include: [test], exclude: undefined, - profile: undefined + profile: undefined, + preserveFocus: false }); assert.strictEqual(runStub.callCount, 1, 'Expected goTest to be called once'); diff --git a/extension/test/gopls/goTest.run.test.ts b/extension/test/gopls/goTest.run.test.ts index 1e342b9924..d42509c4d5 100644 --- a/extension/test/gopls/goTest.run.test.ts +++ b/extension/test/gopls/goTest.run.test.ts @@ -117,7 +117,8 @@ suite('Go Test Runner', () => { { include: [test], exclude: undefined, - profile: undefined + profile: undefined, + preserveFocus: false }, undefined, { kind: 'cpu' } @@ -137,7 +138,8 @@ suite('Go Test Runner', () => { await testExplorer.runner.run({ include: tests, exclude: undefined, - profile: undefined + profile: undefined, + preserveFocus: false }), 'Failed to execute `go test`' ); @@ -159,7 +161,8 @@ suite('Go Test Runner', () => { { include: tests, exclude: undefined, - profile: undefined + profile: undefined, + preserveFocus: false }, undefined, { kind: 'cpu' } @@ -232,7 +235,8 @@ suite('Go Test Runner', () => { await testExplorer.runner.run({ include: [tMain], exclude: undefined, - profile: undefined + profile: undefined, + preserveFocus: false }), 'Failed to execute `go test`' ); @@ -268,7 +272,8 @@ suite('Go Test Runner', () => { await testExplorer.runner.run({ include: [tSub], exclude: undefined, - profile: undefined + profile: undefined, + preserveFocus: false }), 'Failed to execute `go test`' ); @@ -291,7 +296,8 @@ suite('Go Test Runner', () => { await testExplorer.runner.run({ include: [tSub, tOther], exclude: undefined, - profile: undefined + profile: undefined, + preserveFocus: false }), 'Failed to execute `go test`' ); diff --git a/extension/test/mocks/MockTest.ts b/extension/test/mocks/MockTest.ts index bcbb861329..78505c7d6d 100644 --- a/extension/test/mocks/MockTest.ts +++ b/extension/test/mocks/MockTest.ts @@ -8,6 +8,9 @@ import path = require('path'); import { CancellationToken, EndOfLine, + Event, + EventEmitter, + FileCoverage, FileType, MarkdownString, Position, @@ -111,9 +114,13 @@ class MockTestRunProfile implements TestRunProfile { public kind: TestRunProfileKind, public runHandler: TestRunHandler, public isDefault: boolean - ) {} + ) { + const emitter = new EventEmitter(); + this.onDidChangeDefault = emitter.event; + } tag: TestTag | undefined; - + onDidChangeDefault: Event; + supportsContinuousRun = false; configureHandler(): void {} dispose(): void {} } @@ -126,6 +133,13 @@ class MockTestRun implements TestRun { throw new Error('Method not implemented.'); } + constructor() { + const emitter = new EventEmitter(); + this.onDidDispose = emitter.event; + } + + addCoverage(fileCoverage: FileCoverage): void {} + onDidDispose: Event; enqueued(test: TestItem): void {} started(test: TestItem): void {} skipped(test: TestItem): void {} @@ -161,6 +175,7 @@ export class MockTestController implements TestController { return new MockTestItem(id, label, uri, this); } + invalidateTestResults(items?: TestItem | readonly TestItem[]): void {} dispose(): void {} } From 0acba5fa7a53da3bd0ee46d41c4b48759d3832bc Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Wed, 4 Jun 2025 06:27:48 -0700 Subject: [PATCH 30/39] extension: update gopls v0.19.0-pre.1 settings This is an automated CL which updates the gopls version and settings. For golang/go#73965 Change-Id: I575192e929b2c5c2fce3ac9ccea26f48da9aa366 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/678735 LUCI-TryBot-Result: Go LUCI Reviewed-by: Alan Donovan Auto-Submit: Gopher Robot Reviewed-by: Hongxiang Jiang kokoro-CI: kokoro --- docs/settings.md | 202 ++++++- extension/package.json | 828 +++++++++++++++++++++++++++- extension/src/goToolsInformation.ts | 2 +- 3 files changed, 1021 insertions(+), 11 deletions(-) diff --git a/docs/settings.md b/docs/settings.md index c72d0b0fd7..7052e18e98 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -676,12 +676,12 @@ Example Usage: | --- | --- | | `generate` | `"generate"`: Run `go generate`
This codelens source annotates any `//go:generate` comments with commands to run `go generate` in this directory, on all directories recursively beneath this one.
See [Generating code](https://go.dev/blog/generate) for more details.

Default: `true` | | `regenerate_cgo` | `"regenerate_cgo"`: Re-generate cgo declarations
This codelens source annotates an `import "C"` declaration with a command to re-run the [cgo command](https://pkg.go.dev/cmd/cgo) to regenerate the corresponding Go declarations.
Use this after editing the C code in comments attached to the import, or in C header files included by it.

Default: `true` | -| `run_govulncheck` | `"run_govulncheck"`: Run govulncheck (legacy)
This codelens source annotates the `module` directive in a go.mod file with a command to run Govulncheck asynchronously.
[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that computes the set of functions reachable within your application, including dependencies; queries a database of known security vulnerabilities; and reports any potential problems it finds.

Default: `false` | +| `run_govulncheck` | (Experimental) `"run_govulncheck"`: Run govulncheck (legacy)
This codelens source annotates the `module` directive in a go.mod file with a command to run Govulncheck asynchronously.
[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that computes the set of functions reachable within your application, including dependencies; queries a database of known security vulnerabilities; and reports any potential problems it finds.

Default: `false` | | `test` | `"test"`: Run tests and benchmarks
This codelens source annotates each `Test` and `Benchmark` function in a `*_test.go` file with a command to run it.
This source is off by default because VS Code has a client-side custom UI for testing, and because progress notifications are not a great UX for streamed test output. See: - golang/go#67400 for a discussion of this feature. - https://github.com/joaotavora/eglot/discussions/1402 for an alternative approach.

Default: `false` | | `tidy` | `"tidy"`: Tidy go.mod file
This codelens source annotates the `module` directive in a go.mod file with a command to run [`go mod tidy`](https://go.dev/ref/mod#go-mod-tidy), which ensures that the go.mod file matches the source code in the module.

Default: `true` | | `upgrade_dependency` | `"upgrade_dependency"`: Update dependencies
This codelens source annotates the `module` directive in a go.mod file with commands to:
- check for available upgrades, - upgrade direct dependencies, and - upgrade all dependencies transitively.

Default: `true` | | `vendor` | `"vendor"`: Update vendor directory
This codelens source annotates the `module` directive in a go.mod file with a command to run [`go mod vendor`](https://go.dev/ref/mod#go-mod-vendor), which creates or updates the directory named `vendor` in the module root so that it contains an up-to-date copy of all necessary package dependencies.

Default: `true` | -| `vulncheck` | `"vulncheck"`: Run govulncheck
This codelens source annotates the `module` directive in a go.mod file with a command to run govulncheck synchronously.
[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that computes the set of functions reachable within your application, including dependencies; queries a database of known security vulnerabilities; and reports any potential problems it finds.

Default: `false` | +| `vulncheck` | (Experimental) `"vulncheck"`: Run govulncheck
This codelens source annotates the `module` directive in a go.mod file with a command to run govulncheck synchronously.
[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that computes the set of functions reachable within your application, including dependencies; queries a database of known security vulnerabilities; and reports any potential problems it finds.

Default: `false` | ### `ui.completion.completeFunctionCalls` completeFunctionCalls enables function call completion. @@ -744,6 +744,161 @@ Example Usage: | Properties | Description | | --- | --- | +| `QF1001` | Apply De Morgan's law
Available since 2021.1

Default: `false` | +| `QF1002` | Convert untagged switch to tagged switch
An untagged switch that compares a single variable against a series of values can be replaced with a tagged switch.
Before:
switch { case x == 1 || x == 2, x == 3: ... case x == 4: ... default: ... }
After:
switch x { case 1, 2, 3: ... case 4: ... default: ... }
Available since 2021.1

Default: `true` | +| `QF1003` | Convert if/else-if chain to tagged switch
A series of if/else-if checks comparing the same variable against values can be replaced with a tagged switch.
Before:
if x == 1 || x == 2 { ... } else if x == 3 { ... } else { ... }
After:
switch x { case 1, 2: ... case 3: ... default: ... }
Available since 2021.1

Default: `true` | +| `QF1004` | Use strings.ReplaceAll instead of strings.Replace with n == -1
Available since 2021.1

Default: `true` | +| `QF1005` | Expand call to math.Pow
Some uses of math.Pow can be simplified to basic multiplication.
Before:
math.Pow(x, 2)
After:
x * x
Available since 2021.1

Default: `false` | +| `QF1006` | Lift if+break into loop condition
Before:
for { if done { break } ... }
After:
for !done { ... }
Available since 2021.1

Default: `false` | +| `QF1007` | Merge conditional assignment into variable declaration
Before:
x := false if someCondition { x = true }
After:
x := someCondition
Available since 2021.1

Default: `false` | +| `QF1008` | Omit embedded fields from selector expression
Available since 2021.1

Default: `false` | +| `QF1009` | Use time.Time.Equal instead of == operator
Available since 2021.1

Default: `true` | +| `QF1010` | Convert slice of bytes to string when printing it
Available since 2021.1

Default: `true` | +| `QF1011` | Omit redundant type from variable declaration
Available since 2021.1

Default: `false` | +| `QF1012` | Use fmt.Fprintf(x, ...) instead of x.Write(fmt.Sprintf(...))
Available since 2022.1

Default: `true` | +| `S1000` | Use plain channel send or receive instead of single-case select
Select statements with a single case can be replaced with a simple send or receive.
Before:
select { case x := <-ch: fmt.Println(x) }
After:
x := <-ch fmt.Println(x)
Available since 2017.1

Default: `true` | +| `S1001` | Replace for loop with call to copy
Use copy() for copying elements from one slice to another. For arrays of identical size, you can use simple assignment.
Before:
for i, x := range src { dst[i] = x }
After:
copy(dst, src)
Available since 2017.1

Default: `true` | +| `S1002` | Omit comparison with boolean constant
Before:
if x == true {}
After:
if x {}
Available since 2017.1

Default: `false` | +| `S1003` | Replace call to strings.Index with strings.Contains
Before:
if strings.Index(x, y) != -1 {}
After:
if strings.Contains(x, y) {}
Available since 2017.1

Default: `true` | +| `S1004` | Replace call to bytes.Compare with bytes.Equal
Before:
if bytes.Compare(x, y) == 0 {}
After:
if bytes.Equal(x, y) {}
Available since 2017.1

Default: `true` | +| `S1005` | Drop unnecessary use of the blank identifier
In many cases, assigning to the blank identifier is unnecessary.
Before:
for _ = range s {} x, _ = someMap[key] _ = <-ch
After:
for range s{} x = someMap[key] <-ch
Available since 2017.1

Default: `false` | +| `S1006` | Use 'for { ... }' for infinite loops
For infinite loops, using for { ... } is the most idiomatic choice.
Available since 2017.1

Default: `false` | +| `S1007` | Simplify regular expression by using raw string literal
Raw string literals use backticks instead of quotation marks and do not support any escape sequences. This means that the backslash can be used freely, without the need of escaping.
Since regular expressions have their own escape sequences, raw strings can improve their readability.
Before:
regexp.Compile("\\A(\\w+) profile: total \\d+\\n\\z")
After:
regexp.Compile(`\A(\w+) profile: total \d+\n\z`)
Available since 2017.1

Default: `true` | +| `S1008` | Simplify returning boolean expression
Before:
if { return true } return false
After:
return
Available since 2017.1

Default: `false` | +| `S1009` | Omit redundant nil check on slices, maps, and channels
The len function is defined for all slices, maps, and channels, even nil ones, which have a length of zero. It is not necessary to check for nil before checking that their length is not zero.
Before:
if x != nil && len(x) != 0 {}
After:
if len(x) != 0 {}
Available since 2017.1

Default: `true` | +| `S1010` | Omit default slice index
When slicing, the second index defaults to the length of the value, making s[n:len(s)] and s[n:] equivalent.
Available since 2017.1

Default: `true` | +| `S1011` | Use a single append to concatenate two slices
Before:
for _, e := range y { x = append(x, e) } for i := range y { x = append(x, y[i]) } for i := range y { v := y[i] x = append(x, v) }
After:
x = append(x, y...) x = append(x, y...) x = append(x, y...)
Available since 2017.1

Default: `false` | +| `S1012` | Replace time.Now().Sub(x) with time.Since(x)
The time.Since helper has the same effect as using time.Now().Sub(x) but is easier to read.
Before:
time.Now().Sub(x)
After:
time.Since(x)
Available since 2017.1

Default: `true` | +| `S1016` | Use a type conversion instead of manually copying struct fields
Two struct types with identical fields can be converted between each other. In older versions of Go, the fields had to have identical struct tags. Since Go 1.8, however, struct tags are ignored during conversions. It is thus not necessary to manually copy every field individually.
Before:
var x T1 y := T2{ Field1: x.Field1, Field2: x.Field2, }
After:
var x T1 y := T2(x)
Available since 2017.1

Default: `false` | +| `S1017` | Replace manual trimming with strings.TrimPrefix
Instead of using strings.HasPrefix and manual slicing, use the strings.TrimPrefix function. If the string doesn't start with the prefix, the original string will be returned. Using strings.TrimPrefix reduces complexity, and avoids common bugs, such as off-by-one mistakes.
Before:
if strings.HasPrefix(str, prefix) { str = str[len(prefix):] }
After:
str = strings.TrimPrefix(str, prefix)
Available since 2017.1

Default: `true` | +| `S1018` | Use 'copy' for sliding elements
copy() permits using the same source and destination slice, even with overlapping ranges. This makes it ideal for sliding elements in a slice.
Before:
for i := 0; i < n; i++ { bs[i] = bs[offset+i] }
After:
copy(bs[:n], bs[offset:])
Available since 2017.1

Default: `true` | +| `S1019` | Simplify 'make' call by omitting redundant arguments
The 'make' function has default values for the length and capacity arguments. For channels, the length defaults to zero, and for slices, the capacity defaults to the length.
Available since 2017.1

Default: `true` | +| `S1020` | Omit redundant nil check in type assertion
Before:
if _, ok := i.(T); ok && i != nil {}
After:
if _, ok := i.(T); ok {}
Available since 2017.1

Default: `true` | +| `S1021` | Merge variable declaration and assignment
Before:
var x uint x = 1
After:
var x uint = 1
Available since 2017.1

Default: `false` | +| `S1023` | Omit redundant control flow
Functions that have no return value do not need a return statement as the final statement of the function.
Switches in Go do not have automatic fallthrough, unlike languages like C. It is not necessary to have a break statement as the final statement in a case block.
Available since 2017.1

Default: `true` | +| `S1024` | Replace x.Sub(time.Now()) with time.Until(x)
The time.Until helper has the same effect as using x.Sub(time.Now()) but is easier to read.
Before:
x.Sub(time.Now())
After:
time.Until(x)
Available since 2017.1

Default: `true` | +| `S1025` | Don't use fmt.Sprintf("%s", x) unnecessarily
In many instances, there are easier and more efficient ways of getting a value's string representation. Whenever a value's underlying type is a string already, or the type has a String method, they should be used directly.
Given the following shared definitions
type T1 string type T2 int
func (T2) String() string { return "Hello, world" }
var x string var y T1 var z T2
we can simplify
fmt.Sprintf("%s", x) fmt.Sprintf("%s", y) fmt.Sprintf("%s", z)
to
x string(y) z.String()
Available since 2017.1

Default: `false` | +| `S1028` | Simplify error construction with fmt.Errorf
Before:
errors.New(fmt.Sprintf(...))
After:
fmt.Errorf(...)
Available since 2017.1

Default: `true` | +| `S1029` | Range over the string directly
Ranging over a string will yield byte offsets and runes. If the offset isn't used, this is functionally equivalent to converting the string to a slice of runes and ranging over that. Ranging directly over the string will be more performant, however, as it avoids allocating a new slice, the size of which depends on the length of the string.
Before:
for _, r := range []rune(s) {}
After:
for _, r := range s {}
Available since 2017.1

Default: `false` | +| `S1030` | Use bytes.Buffer.String or bytes.Buffer.Bytes
bytes.Buffer has both a String and a Bytes method. It is almost never necessary to use string(buf.Bytes()) or []byte(buf.String()) – simply use the other method.
The only exception to this are map lookups. Due to a compiler optimization, m[string(buf.Bytes())] is more efficient than m[buf.String()].
Available since 2017.1

Default: `true` | +| `S1031` | Omit redundant nil check around loop
You can use range on nil slices and maps, the loop will simply never execute. This makes an additional nil check around the loop unnecessary.
Before:
if s != nil { for _, x := range s { ... } }
After:
for _, x := range s { ... }
Available since 2017.1

Default: `true` | +| `S1032` | Use sort.Ints(x), sort.Float64s(x), and sort.Strings(x)
The sort.Ints, sort.Float64s and sort.Strings functions are easier to read than sort.Sort(sort.IntSlice(x)), sort.Sort(sort.Float64Slice(x)) and sort.Sort(sort.StringSlice(x)).
Before:
sort.Sort(sort.StringSlice(x))
After:
sort.Strings(x)
Available since 2019.1

Default: `true` | +| `S1033` | Unnecessary guard around call to 'delete'
Calling delete on a nil map is a no-op.
Available since 2019.2

Default: `true` | +| `S1034` | Use result of type assertion to simplify cases
Available since 2019.2

Default: `true` | +| `S1035` | Redundant call to net/http.CanonicalHeaderKey in method call on net/http.Header
The methods on net/http.Header, namely Add, Del, Get and Set, already canonicalize the given header name.
Available since 2020.1

Default: `true` | +| `S1036` | Unnecessary guard around map access
When accessing a map key that doesn't exist yet, one receives a zero value. Often, the zero value is a suitable value, for example when using append or doing integer math.
The following
if _, ok := m["foo"]; ok { m["foo"] = append(m["foo"], "bar") } else { m["foo"] = []string{"bar"} }
can be simplified to
m["foo"] = append(m["foo"], "bar")
and
if _, ok := m2["k"]; ok { m2["k"] += 4 } else { m2["k"] = 4 }
can be simplified to
m["k"] += 4
Available since 2020.1

Default: `true` | +| `S1037` | Elaborate way of sleeping
Using a select statement with a single case receiving from the result of time.After is a very elaborate way of sleeping that can much simpler be expressed with a simple call to time.Sleep.
Available since 2020.1

Default: `true` | +| `S1038` | Unnecessarily complex way of printing formatted string
Instead of using fmt.Print(fmt.Sprintf(...)), one can use fmt.Printf(...).
Available since 2020.1

Default: `true` | +| `S1039` | Unnecessary use of fmt.Sprint
Calling fmt.Sprint with a single string argument is unnecessary and identical to using the string directly.
Available since 2020.1

Default: `true` | +| `S1040` | Type assertion to current type
The type assertion x.(SomeInterface), when x already has type SomeInterface, can only fail if x is nil. Usually, this is left-over code from when x had a different type and you can safely delete the type assertion. If you want to check that x is not nil, consider being explicit and using an actual if x == nil comparison instead of relying on the type assertion panicking.
Available since 2021.1

Default: `true` | +| `SA1000` | Invalid regular expression
Available since 2017.1

Default: `false` | +| `SA1001` | Invalid template
Available since 2017.1

Default: `true` | +| `SA1002` | Invalid format in time.Parse
Available since 2017.1

Default: `false` | +| `SA1003` | Unsupported argument to functions in encoding/binary
The encoding/binary package can only serialize types with known sizes. This precludes the use of the int and uint types, as their sizes differ on different architectures. Furthermore, it doesn't support serializing maps, channels, strings, or functions.
Before Go 1.8, bool wasn't supported, either.
Available since 2017.1

Default: `false` | +| `SA1004` | Suspiciously small untyped constant in time.Sleep
The time.Sleep function takes a time.Duration as its only argument. Durations are expressed in nanoseconds. Thus, calling time.Sleep(1) will sleep for 1 nanosecond. This is a common source of bugs, as sleep functions in other languages often accept seconds or milliseconds.
The time package provides constants such as time.Second to express large durations. These can be combined with arithmetic to express arbitrary durations, for example 5 * time.Second for 5 seconds.
If you truly meant to sleep for a tiny amount of time, use n * time.Nanosecond to signal to Staticcheck that you did mean to sleep for some amount of nanoseconds.
Available since 2017.1

Default: `true` | +| `SA1005` | Invalid first argument to exec.Command
os/exec runs programs directly (using variants of the fork and exec system calls on Unix systems). This shouldn't be confused with running a command in a shell. The shell will allow for features such as input redirection, pipes, and general scripting. The shell is also responsible for splitting the user's input into a program name and its arguments. For example, the equivalent to
ls / /tmp
would be
exec.Command("ls", "/", "/tmp")
If you want to run a command in a shell, consider using something like the following – but be aware that not all systems, particularly Windows, will have a /bin/sh program:
exec.Command("/bin/sh", "-c", "ls | grep Awesome")
Available since 2017.1

Default: `true` | +| `SA1007` | Invalid URL in net/url.Parse
Available since 2017.1

Default: `false` | +| `SA1008` | Non-canonical key in http.Header map
Keys in http.Header maps are canonical, meaning they follow a specific combination of uppercase and lowercase letters. Methods such as http.Header.Add and http.Header.Del convert inputs into this canonical form before manipulating the map.
When manipulating http.Header maps directly, as opposed to using the provided methods, care should be taken to stick to canonical form in order to avoid inconsistencies. The following piece of code demonstrates one such inconsistency:
h := http.Header{} h["etag"] = []string{"1234"} h.Add("etag", "5678") fmt.Println(h)
// Output: // map[Etag:[5678] etag:[1234]]
The easiest way of obtaining the canonical form of a key is to use http.CanonicalHeaderKey.
Available since 2017.1

Default: `true` | +| `SA1010` | (*regexp.Regexp).FindAll called with n == 0, which will always return zero results
If n >= 0, the function returns at most n matches/submatches. To return all results, specify a negative number.
Available since 2017.1

Default: `false` | +| `SA1011` | Various methods in the 'strings' package expect valid UTF-8, but invalid input is provided
Available since 2017.1

Default: `false` | +| `SA1012` | A nil context.Context is being passed to a function, consider using context.TODO instead
Available since 2017.1

Default: `true` | +| `SA1013` | io.Seeker.Seek is being called with the whence constant as the first argument, but it should be the second
Available since 2017.1

Default: `true` | +| `SA1014` | Non-pointer value passed to Unmarshal or Decode
Available since 2017.1

Default: `false` | +| `SA1015` | Using time.Tick in a way that will leak. Consider using time.NewTicker, and only use time.Tick in tests, commands and endless functions
Before Go 1.23, time.Tickers had to be closed to be able to be garbage collected. Since time.Tick doesn't make it possible to close the underlying ticker, using it repeatedly would leak memory.
Go 1.23 fixes this by allowing tickers to be collected even if they weren't closed.
Available since 2017.1

Default: `false` | +| `SA1016` | Trapping a signal that cannot be trapped
Not all signals can be intercepted by a process. Specifically, on UNIX-like systems, the syscall.SIGKILL and syscall.SIGSTOP signals are never passed to the process, but instead handled directly by the kernel. It is therefore pointless to try and handle these signals.
Available since 2017.1

Default: `true` | +| `SA1017` | Channels used with os/signal.Notify should be buffered
The os/signal package uses non-blocking channel sends when delivering signals. If the receiving end of the channel isn't ready and the channel is either unbuffered or full, the signal will be dropped. To avoid missing signals, the channel should be buffered and of the appropriate size. For a channel used for notification of just one signal value, a buffer of size 1 is sufficient.
Available since 2017.1

Default: `false` | +| `SA1018` | strings.Replace called with n == 0, which does nothing
With n == 0, zero instances will be replaced. To replace all instances, use a negative number, or use strings.ReplaceAll.
Available since 2017.1

Default: `false` | +| `SA1020` | Using an invalid host:port pair with a net.Listen-related function
Available since 2017.1

Default: `false` | +| `SA1021` | Using bytes.Equal to compare two net.IP
A net.IP stores an IPv4 or IPv6 address as a slice of bytes. The length of the slice for an IPv4 address, however, can be either 4 or 16 bytes long, using different ways of representing IPv4 addresses. In order to correctly compare two net.IPs, the net.IP.Equal method should be used, as it takes both representations into account.
Available since 2017.1

Default: `false` | +| `SA1023` | Modifying the buffer in an io.Writer implementation
Write must not modify the slice data, even temporarily.
Available since 2017.1

Default: `false` | +| `SA1024` | A string cutset contains duplicate characters
The strings.TrimLeft and strings.TrimRight functions take cutsets, not prefixes. A cutset is treated as a set of characters to remove from a string. For example,
strings.TrimLeft("42133word", "1234")
will result in the string "word" – any characters that are 1, 2, 3 or 4 are cut from the left of the string.
In order to remove one string from another, use strings.TrimPrefix instead.
Available since 2017.1

Default: `false` | +| `SA1025` | It is not possible to use (*time.Timer).Reset's return value correctly
Available since 2019.1

Default: `false` | +| `SA1026` | Cannot marshal channels or functions
Available since 2019.2

Default: `false` | +| `SA1027` | Atomic access to 64-bit variable must be 64-bit aligned
On ARM, x86-32, and 32-bit MIPS, it is the caller's responsibility to arrange for 64-bit alignment of 64-bit words accessed atomically. The first word in a variable or in an allocated struct, array, or slice can be relied upon to be 64-bit aligned.
You can use the structlayout tool to inspect the alignment of fields in a struct.
Available since 2019.2

Default: `false` | +| `SA1028` | sort.Slice can only be used on slices
The first argument of sort.Slice must be a slice.
Available since 2020.1

Default: `false` | +| `SA1029` | Inappropriate key in call to context.WithValue
The provided key must be comparable and should not be of type string or any other built-in type to avoid collisions between packages using context. Users of WithValue should define their own types for keys.
To avoid allocating when assigning to an interface{}, context keys often have concrete type struct{}. Alternatively, exported context key variables' static type should be a pointer or interface.
Available since 2020.1

Default: `false` | +| `SA1030` | Invalid argument in call to a strconv function
This check validates the format, number base and bit size arguments of the various parsing and formatting functions in strconv.
Available since 2021.1

Default: `false` | +| `SA1031` | Overlapping byte slices passed to an encoder
In an encoding function of the form Encode(dst, src), dst and src were found to reference the same memory. This can result in src bytes being overwritten before they are read, when the encoder writes more than one byte per src byte.
Available since 2024.1

Default: `false` | +| `SA1032` | Wrong order of arguments to errors.Is
The first argument of the function errors.Is is the error that we have and the second argument is the error we're trying to match against. For example:
if errors.Is(err, io.EOF) { ... }

This check detects some cases where the two arguments have been swapped. It flags any calls where the first argument is referring to a package-level error variable, such as
if errors.Is(io.EOF, err) { /* this is wrong */ }

Available since 2024.1

Default: `false` | +| `SA2001` | Empty critical section, did you mean to defer the unlock?
Empty critical sections of the kind
mu.Lock() mu.Unlock()
are very often a typo, and the following was intended instead:
mu.Lock() defer mu.Unlock()
Do note that sometimes empty critical sections can be useful, as a form of signaling to wait on another goroutine. Many times, there are simpler ways of achieving the same effect. When that isn't the case, the code should be amply commented to avoid confusion. Combining such comments with a //lint:ignore directive can be used to suppress this rare false positive.
Available since 2017.1

Default: `true` | +| `SA2002` | Called testing.T.FailNow or SkipNow in a goroutine, which isn't allowed
Available since 2017.1

Default: `false` | +| `SA2003` | Deferred Lock right after locking, likely meant to defer Unlock instead
Available since 2017.1

Default: `false` | +| `SA3000` | TestMain doesn't call os.Exit, hiding test failures
Test executables (and in turn 'go test') exit with a non-zero status code if any tests failed. When specifying your own TestMain function, it is your responsibility to arrange for this, by calling os.Exit with the correct code. The correct code is returned by (*testing.M).Run, so the usual way of implementing TestMain is to end it with os.Exit(m.Run()).
Available since 2017.1

Default: `true` | +| `SA3001` | Assigning to b.N in benchmarks distorts the results
The testing package dynamically sets b.N to improve the reliability of benchmarks and uses it in computations to determine the duration of a single operation. Benchmark code must not alter b.N as this would falsify results.
Available since 2017.1

Default: `true` | +| `SA4000` | Binary operator has identical expressions on both sides
Available since 2017.1

Default: `true` | +| `SA4001` | &*x gets simplified to x, it does not copy x
Available since 2017.1

Default: `true` | +| `SA4003` | Comparing unsigned values against negative values is pointless
Available since 2017.1

Default: `true` | +| `SA4004` | The loop exits unconditionally after one iteration
Available since 2017.1

Default: `true` | +| `SA4005` | Field assignment that will never be observed. Did you mean to use a pointer receiver?
Available since 2021.1

Default: `false` | +| `SA4006` | A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?
Available since 2017.1

Default: `false` | +| `SA4008` | The variable in the loop condition never changes, are you incrementing the wrong variable?
For example:
for i := 0; i < 10; j++ { ... }

This may also occur when a loop can only execute once because of unconditional control flow that terminates the loop. For example, when a loop body contains an unconditional break, return, or panic:
func f() {
panic("oops")
}
func g() {
for i := 0; i < 10; i++ {
// f unconditionally calls panic, which means "i" is
// never incremented.
f()
}
}

Available since 2017.1

Default: `false` | +| `SA4009` | A function argument is overwritten before its first use
Available since 2017.1

Default: `false` | +| `SA4010` | The result of append will never be observed anywhere
Available since 2017.1

Default: `false` | +| `SA4011` | Break statement with no effect. Did you mean to break out of an outer loop?
Available since 2017.1

Default: `true` | +| `SA4012` | Comparing a value against NaN even though no value is equal to NaN
Available since 2017.1

Default: `false` | +| `SA4013` | Negating a boolean twice (!!b) is the same as writing b. This is either redundant, or a typo.
Available since 2017.1

Default: `true` | +| `SA4014` | An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either
Available since 2017.1

Default: `true` | +| `SA4015` | Calling functions like math.Ceil on floats converted from integers doesn't do anything useful
Available since 2017.1

Default: `false` | +| `SA4016` | Certain bitwise operations, such as x ^ 0, do not do anything useful
Available since 2017.1

Default: `true` | +| `SA4017` | Discarding the return values of a function without side effects, making the call pointless
Available since 2017.1

Default: `false` | +| `SA4018` | Self-assignment of variables
Available since 2017.1

Default: `false` | +| `SA4019` | Multiple, identical build constraints in the same file
Available since 2017.1

Default: `true` | +| `SA4020` | Unreachable case clause in a type switch
In a type switch like the following
type T struct{} func (T) Read(b []byte) (int, error) { return 0, nil }
var v any = T{}
switch v.(type) { case io.Reader: // ... case T: // unreachable }
the second case clause can never be reached because T implements io.Reader and case clauses are evaluated in source order.
Another example:
type T struct{} func (T) Read(b []byte) (int, error) { return 0, nil } func (T) Close() error { return nil }
var v any = T{}
switch v.(type) { case io.Reader: // ... case io.ReadCloser: // unreachable }
Even though T has a Close method and thus implements io.ReadCloser, io.Reader will always match first. The method set of io.Reader is a subset of io.ReadCloser. Thus it is impossible to match the second case without matching the first case.

Structurally equivalent interfaces
A special case of the previous example are structurally identical interfaces. Given these declarations
type T error type V error
func doSomething() error { err, ok := doAnotherThing() if ok { return T(err) }
return U(err) }
the following type switch will have an unreachable case clause:
switch doSomething().(type) { case T: // ... case V: // unreachable }
T will always match before V because they are structurally equivalent and therefore doSomething()'s return value implements both.
Available since 2019.2

Default: `true` | +| `SA4022` | Comparing the address of a variable against nil
Code such as 'if &x == nil' is meaningless, because taking the address of a variable always yields a non-nil pointer.
Available since 2020.1

Default: `true` | +| `SA4023` | Impossible comparison of interface value with untyped nil
Under the covers, interfaces are implemented as two elements, a type T and a value V. V is a concrete value such as an int, struct or pointer, never an interface itself, and has type T. For instance, if we store the int value 3 in an interface, the resulting interface value has, schematically, (T=int, V=3). The value V is also known as the interface's dynamic value, since a given interface variable might hold different values V (and corresponding types T) during the execution of the program.
An interface value is nil only if the V and T are both unset, (T=nil, V is not set), In particular, a nil interface will always hold a nil type. If we store a nil pointer of type *int inside an interface value, the inner type will be *int regardless of the value of the pointer: (T=*int, V=nil). Such an interface value will therefore be non-nil even when the pointer value V inside is nil.
This situation can be confusing, and arises when a nil value is stored inside an interface value such as an error return:
func returnsError() error { var p *MyError = nil if bad() { p = ErrBad } return p // Will always return a non-nil error. }
If all goes well, the function returns a nil p, so the return value is an error interface value holding (T=*MyError, V=nil). This means that if the caller compares the returned error to nil, it will always look as if there was an error even if nothing bad happened. To return a proper nil error to the caller, the function must return an explicit nil:
func returnsError() error { if bad() { return ErrBad } return nil }
It's a good idea for functions that return errors always to use the error type in their signature (as we did above) rather than a concrete type such as *MyError, to help guarantee the error is created correctly. As an example, os.Open returns an error even though, if not nil, it's always of concrete type *os.PathError.
Similar situations to those described here can arise whenever interfaces are used. Just keep in mind that if any concrete value has been stored in the interface, the interface will not be nil. For more information, see The Laws of Reflection at https://golang.org/doc/articles/laws_of_reflection.html.
This text has been copied from https://golang.org/doc/faq#nil_error, licensed under the Creative Commons Attribution 3.0 License.
Available since 2020.2

Default: `false` | +| `SA4024` | Checking for impossible return value from a builtin function
Return values of the len and cap builtins cannot be negative.
See https://golang.org/pkg/builtin/#len and https://golang.org/pkg/builtin/#cap.
Example:
if len(slice) < 0 { fmt.Println("unreachable code") }
Available since 2021.1

Default: `true` | +| `SA4025` | Integer division of literals that results in zero
When dividing two integer constants, the result will also be an integer. Thus, a division such as 2 / 3 results in 0. This is true for all of the following examples:
_ = 2 / 3
const _ = 2 / 3
const _ float64 = 2 / 3
_ = float64(2 / 3)

Staticcheck will flag such divisions if both sides of the division are integer literals, as it is highly unlikely that the division was intended to truncate to zero. Staticcheck will not flag integer division involving named constants, to avoid noisy positives.
Available since 2021.1

Default: `true` | +| `SA4026` | Go constants cannot express negative zero
In IEEE 754 floating point math, zero has a sign and can be positive or negative. This can be useful in certain numerical code.
Go constants, however, cannot express negative zero. This means that the literals -0.0 and 0.0 have the same ideal value (zero) and will both represent positive zero at runtime.
To explicitly and reliably create a negative zero, you can use the math.Copysign function: math.Copysign(0, -1).
Available since 2021.1

Default: `true` | +| `SA4027` | (*net/url.URL).Query returns a copy, modifying it doesn't change the URL
(*net/url.URL).Query parses the current value of net/url.URL.RawQuery and returns it as a map of type net/url.Values. Subsequent changes to this map will not affect the URL unless the map gets encoded and assigned to the URL's RawQuery.
As a consequence, the following code pattern is an expensive no-op: u.Query().Add(key, value).
Available since 2021.1

Default: `true` | +| `SA4028` | x % 1 is always zero
Available since 2022.1

Default: `true` | +| `SA4029` | Ineffective attempt at sorting slice
sort.Float64Slice, sort.IntSlice, and sort.StringSlice are types, not functions. Doing x = sort.StringSlice(x) does nothing, especially not sort any values. The correct usage is sort.Sort(sort.StringSlice(x)) or sort.StringSlice(x).Sort(), but there are more convenient helpers, namely sort.Float64s, sort.Ints, and sort.Strings.
Available since 2022.1

Default: `true` | +| `SA4030` | Ineffective attempt at generating random number
Functions in the math/rand package that accept upper limits, such as Intn, generate random numbers in the half-open interval [0,n). In other words, the generated numbers will be >= 0 and < n – they don't include n. rand.Intn(1) therefore doesn't generate 0 or 1, it always generates 0.
Available since 2022.1

Default: `true` | +| `SA4031` | Checking never-nil value against nil
Available since 2022.1

Default: `false` | +| `SA4032` | Comparing runtime.GOOS or runtime.GOARCH against impossible value
Available since 2024.1

Default: `true` | +| `SA5000` | Assignment to nil map
Available since 2017.1

Default: `false` | +| `SA5001` | Deferring Close before checking for a possible error
Available since 2017.1

Default: `true` | +| `SA5002` | The empty for loop ('for {}') spins and can block the scheduler
Available since 2017.1

Default: `false` | +| `SA5003` | Defers in infinite loops will never execute
Defers are scoped to the surrounding function, not the surrounding block. In a function that never returns, i.e. one containing an infinite loop, defers will never execute.
Available since 2017.1

Default: `true` | +| `SA5004` | 'for { select { ...' with an empty default branch spins
Available since 2017.1

Default: `true` | +| `SA5005` | The finalizer references the finalized object, preventing garbage collection
A finalizer is a function associated with an object that runs when the garbage collector is ready to collect said object, that is when the object is no longer referenced by anything.
If the finalizer references the object, however, it will always remain as the final reference to that object, preventing the garbage collector from collecting the object. The finalizer will never run, and the object will never be collected, leading to a memory leak. That is why the finalizer should instead use its first argument to operate on the object. That way, the number of references can temporarily go to zero before the object is being passed to the finalizer.
Available since 2017.1

Default: `false` | +| `SA5007` | Infinite recursive call
A function that calls itself recursively needs to have an exit condition. Otherwise it will recurse forever, until the system runs out of memory.
This issue can be caused by simple bugs such as forgetting to add an exit condition. It can also happen "on purpose". Some languages have tail call optimization which makes certain infinite recursive calls safe to use. Go, however, does not implement TCO, and as such a loop should be used instead.
Available since 2017.1

Default: `false` | +| `SA5008` | Invalid struct tag
Available since 2019.2

Default: `true` | +| `SA5010` | Impossible type assertion
Some type assertions can be statically proven to be impossible. This is the case when the method sets of both arguments of the type assertion conflict with each other, for example by containing the same method with different signatures.
The Go compiler already applies this check when asserting from an interface value to a concrete type. If the concrete type misses methods from the interface, or if function signatures don't match, then the type assertion can never succeed.
This check applies the same logic when asserting from one interface to another. If both interface types contain the same method but with different signatures, then the type assertion can never succeed, either.
Available since 2020.1

Default: `false` | +| `SA5011` | Possible nil pointer dereference
A pointer is being dereferenced unconditionally, while also being checked against nil in another place. This suggests that the pointer may be nil and dereferencing it may panic. This is commonly a result of improperly ordered code or missing return statements. Consider the following examples:
func fn(x *int) { fmt.Println(*x)
// This nil check is equally important for the previous dereference if x != nil { foo(*x) } }
func TestFoo(t *testing.T) { x := compute() if x == nil { t.Errorf("nil pointer received") }
// t.Errorf does not abort the test, so if x is nil, the next line will panic. foo(*x) }
Staticcheck tries to deduce which functions abort control flow. For example, it is aware that a function will not continue execution after a call to panic or log.Fatal. However, sometimes this detection fails, in particular in the presence of conditionals. Consider the following example:
func Log(msg string, level int) { fmt.Println(msg) if level == levelFatal { os.Exit(1) } }
func Fatal(msg string) { Log(msg, levelFatal) }
func fn(x *int) { if x == nil { Fatal("unexpected nil pointer") } fmt.Println(*x) }
Staticcheck will flag the dereference of x, even though it is perfectly safe. Staticcheck is not able to deduce that a call to Fatal will exit the program. For the time being, the easiest workaround is to modify the definition of Fatal like so:
func Fatal(msg string) { Log(msg, levelFatal) panic("unreachable") }
We also hard-code functions from common logging packages such as logrus. Please file an issue if we're missing support for a popular package.
Available since 2020.1

Default: `false` | +| `SA5012` | Passing odd-sized slice to function expecting even size
Some functions that take slices as parameters expect the slices to have an even number of elements. Often, these functions treat elements in a slice as pairs. For example, strings.NewReplacer takes pairs of old and new strings, and calling it with an odd number of elements would be an error.
Available since 2020.2

Default: `false` | +| `SA6000` | Using regexp.Match or related in a loop, should use regexp.Compile
Available since 2017.1

Default: `false` | +| `SA6001` | Missing an optimization opportunity when indexing maps by byte slices
Map keys must be comparable, which precludes the use of byte slices. This usually leads to using string keys and converting byte slices to strings.
Normally, a conversion of a byte slice to a string needs to copy the data and causes allocations. The compiler, however, recognizes m[string(b)] and uses the data of b directly, without copying it, because it knows that the data can't change during the map lookup. This leads to the counter-intuitive situation that
k := string(b) println(m[k]) println(m[k])
will be less efficient than
println(m[string(b)]) println(m[string(b)])
because the first version needs to copy and allocate, while the second one does not.
For some history on this optimization, check out commit f5f5a8b6209f84961687d993b93ea0d397f5d5bf in the Go repository.
Available since 2017.1

Default: `false` | +| `SA6002` | Storing non-pointer values in sync.Pool allocates memory
A sync.Pool is used to avoid unnecessary allocations and reduce the amount of work the garbage collector has to do.
When passing a value that is not a pointer to a function that accepts an interface, the value needs to be placed on the heap, which means an additional allocation. Slices are a common thing to put in sync.Pools, and they're structs with 3 fields (length, capacity, and a pointer to an array). In order to avoid the extra allocation, one should store a pointer to the slice instead.
See the comments on https://go-review.googlesource.com/c/go/+/24371 that discuss this problem.
Available since 2017.1

Default: `false` | +| `SA6003` | Converting a string to a slice of runes before ranging over it
You may want to loop over the runes in a string. Instead of converting the string to a slice of runes and looping over that, you can loop over the string itself. That is,
for _, r := range s {}
and
for _, r := range []rune(s) {}
will yield the same values. The first version, however, will be faster and avoid unnecessary memory allocations.
Do note that if you are interested in the indices, ranging over a string and over a slice of runes will yield different indices. The first one yields byte offsets, while the second one yields indices in the slice of runes.
Available since 2017.1

Default: `false` | +| `SA6005` | Inefficient string comparison with strings.ToLower or strings.ToUpper
Converting two strings to the same case and comparing them like so
if strings.ToLower(s1) == strings.ToLower(s2) { ... }
is significantly more expensive than comparing them with strings.EqualFold(s1, s2). This is due to memory usage as well as computational complexity.
strings.ToLower will have to allocate memory for the new strings, as well as convert both strings fully, even if they differ on the very first byte. strings.EqualFold, on the other hand, compares the strings one character at a time. It doesn't need to create two intermediate strings and can return as soon as the first non-matching character has been found.
For a more in-depth explanation of this issue, see https://blog.digitalocean.com/how-to-efficiently-compare-strings-in-go/
Available since 2019.2

Default: `true` | +| `SA6006` | Using io.WriteString to write []byte
Using io.WriteString to write a slice of bytes, as in
io.WriteString(w, string(b))
is both unnecessary and inefficient. Converting from []byte to string has to allocate and copy the data, and we could simply use w.Write(b) instead.
Available since 2024.1

Default: `true` | +| `SA9001` | Defers in range loops may not run when you expect them to
Available since 2017.1

Default: `false` | +| `SA9002` | Using a non-octal os.FileMode that looks like it was meant to be in octal.
Available since 2017.1

Default: `true` | +| `SA9003` | Empty body in an if or else branch
Available since 2017.1, non-default

Default: `false` | +| `SA9004` | Only the first constant has an explicit type
In a constant declaration such as the following:
const ( First byte = 1 Second = 2 )
the constant Second does not have the same type as the constant First. This construct shouldn't be confused with
const ( First byte = iota Second )
where First and Second do indeed have the same type. The type is only passed on when no explicit value is assigned to the constant.
When declaring enumerations with explicit values it is therefore important not to write
const ( EnumFirst EnumType = 1 EnumSecond = 2 EnumThird = 3 )
This discrepancy in types can cause various confusing behaviors and bugs.

Wrong type in variable declarations
The most obvious issue with such incorrect enumerations expresses itself as a compile error:
package pkg
const ( EnumFirst uint8 = 1 EnumSecond = 2 )
func fn(useFirst bool) { x := EnumSecond if useFirst { x = EnumFirst } }
fails to compile with
./const.go:11:5: cannot use EnumFirst (type uint8) as type int in assignment

Losing method sets
A more subtle issue occurs with types that have methods and optional interfaces. Consider the following:
package main
import "fmt"
type Enum int
func (e Enum) String() string { return "an enum" }
const ( EnumFirst Enum = 1 EnumSecond = 2 )
func main() { fmt.Println(EnumFirst) fmt.Println(EnumSecond) }
This code will output
an enum 2
as EnumSecond has no explicit type, and thus defaults to int.
Available since 2019.1

Default: `true` | +| `SA9005` | Trying to marshal a struct with no public fields nor custom marshaling
The encoding/json and encoding/xml packages only operate on exported fields in structs, not unexported ones. It is usually an error to try to (un)marshal structs that only consist of unexported fields.
This check will not flag calls involving types that define custom marshaling behavior, e.g. via MarshalJSON methods. It will also not flag empty structs.
Available since 2019.2

Default: `false` | +| `SA9006` | Dubious bit shifting of a fixed size integer value
Bit shifting a value past its size will always clear the value.
For instance:
v := int8(42) v >>= 8
will always result in 0.
This check flags bit shifting operations on fixed size integer values only. That is, int, uint and uintptr are never flagged to avoid potential false positives in somewhat exotic but valid bit twiddling tricks:
// Clear any value above 32 bits if integers are more than 32 bits. func f(i int) int { v := i >> 32 v = v << 32 return i-v }
Available since 2020.2

Default: `true` | +| `SA9007` | Deleting a directory that shouldn't be deleted
It is virtually never correct to delete system directories such as /tmp or the user's home directory. However, it can be fairly easy to do by mistake, for example by mistakenly using os.TempDir instead of ioutil.TempDir, or by forgetting to add a suffix to the result of os.UserHomeDir.
Writing
d := os.TempDir() defer os.RemoveAll(d)
in your unit tests will have a devastating effect on the stability of your system.
This check flags attempts at deleting the following directories:
- os.TempDir - os.UserCacheDir - os.UserConfigDir - os.UserHomeDir
Available since 2022.1

Default: `false` | +| `SA9008` | else branch of a type assertion is probably not reading the right value
When declaring variables as part of an if statement (like in 'if foo := ...; foo {'), the same variables will also be in the scope of the else branch. This means that in the following example
if x, ok := x.(int); ok { // ... } else { fmt.Printf("unexpected type %T", x) }
x in the else branch will refer to the x from x, ok :=; it will not refer to the x that is being type-asserted. The result of a failed type assertion is the zero value of the type that is being asserted to, so x in the else branch will always have the value 0 and the type int.
Available since 2022.1

Default: `false` | +| `SA9009` | Ineffectual Go compiler directive
A potential Go compiler directive was found, but is ineffectual as it begins with whitespace.
Available since 2024.1

Default: `true` | +| `ST1000` | Incorrect or missing package comment
Packages must have a package comment that is formatted according to the guidelines laid out in https://go.dev/wiki/CodeReviewComments#package-comments.
Available since 2019.1, non-default

Default: `false` | +| `ST1001` | Dot imports are discouraged
Dot imports that aren't in external test packages are discouraged.
The dot_import_whitelist option can be used to whitelist certain imports.
Quoting Go Code Review Comments:
> The import . form can be useful in tests that, due to circular > dependencies, cannot be made part of the package being tested: > > package foo_test > > import ( > "bar/testutil" // also imports "foo" > . "foo" > ) > > In this case, the test file cannot be in package foo because it > uses bar/testutil, which imports foo. So we use the import . > form to let the file pretend to be part of package foo even though > it is not. Except for this one case, do not use import . in your > programs. It makes the programs much harder to read because it is > unclear whether a name like Quux is a top-level identifier in the > current package or in an imported package.
Available since 2019.1
Options dot_import_whitelist

Default: `false` | +| `ST1003` | Poorly chosen identifier
Identifiers, such as variable and package names, follow certain rules.
See the following links for details:
- https://go.dev/doc/effective_go#package-names - https://go.dev/doc/effective_go#mixed-caps - https://go.dev/wiki/CodeReviewComments#initialisms - https://go.dev/wiki/CodeReviewComments#variable-names
Available since 2019.1, non-default
Options initialisms

Default: `false` | +| `ST1005` | Incorrectly formatted error string
Error strings follow a set of guidelines to ensure uniformity and good composability.
Quoting Go Code Review Comments:
> Error strings should not be capitalized (unless beginning with > proper nouns or acronyms) or end with punctuation, since they are > usually printed following other context. That is, use > fmt.Errorf("something bad") not fmt.Errorf("Something bad"), so > that log.Printf("Reading %s: %v", filename, err) formats without a > spurious capital letter mid-message.
Available since 2019.1

Default: `false` | +| `ST1006` | Poorly chosen receiver name
Quoting Go Code Review Comments:
> The name of a method's receiver should be a reflection of its > identity; often a one or two letter abbreviation of its type > suffices (such as "c" or "cl" for "Client"). Don't use generic > names such as "me", "this" or "self", identifiers typical of > object-oriented languages that place more emphasis on methods as > opposed to functions. The name need not be as descriptive as that > of a method argument, as its role is obvious and serves no > documentary purpose. It can be very short as it will appear on > almost every line of every method of the type; familiarity admits > brevity. Be consistent, too: if you call the receiver "c" in one > method, don't call it "cl" in another.
Available since 2019.1

Default: `false` | +| `ST1008` | A function's error value should be its last return value
A function's error value should be its last return value.
Available since 2019.1

Default: `false` | +| `ST1011` | Poorly chosen name for variable of type time.Duration
time.Duration values represent an amount of time, which is represented as a count of nanoseconds. An expression like 5 * time.Microsecond yields the value 5000. It is therefore not appropriate to suffix a variable of type time.Duration with any time unit, such as Msec or Milli.
Available since 2019.1

Default: `false` | +| `ST1012` | Poorly chosen name for error variable
Error variables that are part of an API should be called errFoo or ErrFoo.
Available since 2019.1

Default: `false` | +| `ST1013` | Should use constants for HTTP error codes, not magic numbers
HTTP has a tremendous number of status codes. While some of those are well known (200, 400, 404, 500), most of them are not. The net/http package provides constants for all status codes that are part of the various specifications. It is recommended to use these constants instead of hard-coding magic numbers, to vastly improve the readability of your code.
Available since 2019.1
Options http_status_code_whitelist

Default: `false` | +| `ST1015` | A switch's default case should be the first or last case
Available since 2019.1

Default: `false` | +| `ST1016` | Use consistent method receiver names
Available since 2019.1, non-default

Default: `false` | +| `ST1017` | Don't use Yoda conditions
Yoda conditions are conditions of the kind 'if 42 == x', where the literal is on the left side of the comparison. These are a common idiom in languages in which assignment is an expression, to avoid bugs of the kind 'if (x = 42)'. In Go, which doesn't allow for this kind of bug, we prefer the more idiomatic 'if x == 42'.
Available since 2019.2

Default: `false` | +| `ST1018` | Avoid zero-width and control characters in string literals
Available since 2019.2

Default: `false` | +| `ST1019` | Importing the same package multiple times
Go allows importing the same package multiple times, as long as different import aliases are being used. That is, the following bit of code is valid:
import ( "fmt" fumpt "fmt" format "fmt" _ "fmt" )
However, this is very rarely done on purpose. Usually, it is a sign of code that got refactored, accidentally adding duplicate import statements. It is also a rarely known feature, which may contribute to confusion.
Do note that sometimes, this feature may be used intentionally (see for example https://github.com/golang/go/commit/3409ce39bfd7584523b7a8c150a310cea92d879d) – if you want to allow this pattern in your code base, you're advised to disable this check.
Available since 2020.1

Default: `false` | +| `ST1020` | The documentation of an exported function should start with the function's name
Doc comments work best as complete sentences, which allow a wide variety of automated presentations. The first sentence should be a one-sentence summary that starts with the name being declared.
If every doc comment begins with the name of the item it describes, you can use the doc subcommand of the go tool and run the output through grep.
See https://go.dev/doc/effective_go#commentary for more information on how to write good documentation.
Available since 2020.1, non-default

Default: `false` | +| `ST1021` | The documentation of an exported type should start with type's name
Doc comments work best as complete sentences, which allow a wide variety of automated presentations. The first sentence should be a one-sentence summary that starts with the name being declared.
If every doc comment begins with the name of the item it describes, you can use the doc subcommand of the go tool and run the output through grep.
See https://go.dev/doc/effective_go#commentary for more information on how to write good documentation.
Available since 2020.1, non-default

Default: `false` | +| `ST1022` | The documentation of an exported variable or constant should start with variable's name
Doc comments work best as complete sentences, which allow a wide variety of automated presentations. The first sentence should be a one-sentence summary that starts with the name being declared.
If every doc comment begins with the name of the item it describes, you can use the doc subcommand of the go tool and run the output through grep.
See https://go.dev/doc/effective_go#commentary for more information on how to write good documentation.
Available since 2020.1, non-default

Default: `false` | +| `ST1023` | Redundant type in variable declaration
Available since 2021.1, non-default

Default: `false` | | `appends` | check for missing values after append
This checker reports calls to append that pass no values to be appended to the slice.
s := []string{"a", "b", "c"}
_ = append(s)

Such calls are always no-ops and often indicate an underlying mistake.
Default: `true` | | `asmdecl` | report mismatches between assembly files and Go declarations
Default: `true` | | `assign` | check for useless assignments
This checker reports assignments of the form x = x or a[i] = a[i]. These are almost always useless, and even when they aren't they are usually a mistake.
Default: `true` | @@ -762,19 +917,21 @@ Example Usage: | `errorsas` | report passing non-pointer or non-error values to errors.As
The errorsas analysis reports calls to errors.As where the type of the second argument is not a pointer to a type implementing error.
Default: `true` | | `fillreturns` | suggest fixes for errors due to an incorrect number of return values
This checker provides suggested fixes for type errors of the type "wrong number of return values (want %d, got %d)". For example:
func m() (int, string, *bool, error) {
return
}

will turn into
func m() (int, string, *bool, error) {
return 0, "", nil, nil
}

This functionality is similar to https://github.com/sqs/goreturns.
Default: `true` | | `framepointer` | report assembly that clobbers the frame pointer before saving it
Default: `true` | -| `gofix` | apply fixes based on go:fix comment directives
The gofix analyzer inlines functions and constants that are marked for inlining.
Default: `true` | +| `gofix` | apply fixes based on go:fix comment directives
The gofix analyzer inlines functions and constants that are marked for inlining.
## Functions
Given a function that is marked for inlining, like this one:
//go:fix inline
func Square(x int) int { return Pow(x, 2) }

this analyzer will recommend that calls to the function elsewhere, in the same or other packages, should be inlined.
Inlining can be used to move off of a deprecated function:
// Deprecated: prefer Pow(x, 2).
//go:fix inline
func Square(x int) int { return Pow(x, 2) }

It can also be used to move off of an obsolete package, as when the import path has changed or a higher major version is available:
package pkg

import pkg2 "pkg/v2"

//go:fix inline
func F() { pkg2.F(nil) }

Replacing a call pkg.F() by pkg2.F(nil) can have no effect on the program, so this mechanism provides a low-risk way to update large numbers of calls. We recommend, where possible, expressing the old API in terms of the new one to enable automatic migration.
The inliner takes care to avoid behavior changes, even subtle ones, such as changes to the order in which argument expressions are evaluated. When it cannot safely eliminate all parameter variables, it may introduce a "binding declaration" of the form
var params = args

to evaluate argument expressions in the correct order and bind them to parameter variables. Since the resulting code transformation may be stylistically suboptimal, such inlinings may be disabled by specifying the -gofix.allow_binding_decl=false flag to the analyzer driver.
(In cases where it is not safe to "reduce" a call—that is, to replace a call f(x) by the body of function f, suitably substituted—the inliner machinery is capable of replacing f by a function literal, func(){...}(). However, the gofix analyzer discards all such "literalizations" unconditionally, again on grounds of style.)
## Constants
Given a constant that is marked for inlining, like this one:
//go:fix inline
const Ptr = Pointer

this analyzer will recommend that uses of Ptr should be replaced with Pointer.
As with functions, inlining can be used to replace deprecated constants and constants in obsolete packages.
A constant definition can be marked for inlining only if it refers to another named constant.
The "//go:fix inline" comment must appear before a single const declaration on its own, as above; before a const declaration that is part of a group, as in this case:
const (
C = 1
//go:fix inline
Ptr = Pointer
)

or before a group, applying to every constant in the group:
//go:fix inline
const (
Ptr = Pointer
Val = Value
)

The proposal https://go.dev/issue/32816 introduces the "//go:fix" directives.
You can use this (officially unsupported) command to apply gofix fixes en masse:
$ go run golang.org/x/tools/internal/gofix/cmd/gofix@latest -test ./...

(Do not use "go get -tool" to add gopls as a dependency of your module; gopls commands must be built from their release branch.)
Default: `true` | | `hostport` | check format of addresses passed to net.Dial
This analyzer flags code that produce network address strings using fmt.Sprintf, as in this example:
addr := fmt.Sprintf("%s:%d", host, 12345) // "will not work with IPv6" ... conn, err := net.Dial("tcp", addr) // "when passed to dial here"
The analyzer suggests a fix to use the correct approach, a call to net.JoinHostPort:
addr := net.JoinHostPort(host, "12345") ... conn, err := net.Dial("tcp", addr)
A similar diagnostic and fix are produced for a format string of "%s:%s".

Default: `true` | | `httpresponse` | check for mistakes using HTTP responses
A common mistake when using the net/http package is to defer a function call to close the http.Response Body before checking the error that determines whether the response is valid:
resp, err := http.Head(url)
defer resp.Body.Close()
if err != nil {
log.Fatal(err)
}
// (defer statement belongs here)

This checker helps uncover latent nil dereference bugs by reporting a diagnostic for such mistakes.
Default: `true` | | `ifaceassert` | detect impossible interface-to-interface type assertions
This checker flags type assertions v.(T) and corresponding type-switch cases in which the static type V of v is an interface that cannot possibly implement the target interface T. This occurs when V and T contain methods with the same name but different signatures. Example:
var v interface {
Read()
}
_ = v.(io.Reader)

The Read method in v has a different signature than the Read method in io.Reader, so this assertion cannot succeed.
Default: `true` | | `infertypeargs` | check for unnecessary type arguments in call expressions
Explicit type arguments may be omitted from call expressions if they can be inferred from function arguments, or from other type arguments:
func f[T any](T) {}


func _() {
f[string]("foo") // string could be inferred
}


Default: `true` | | `loopclosure` | check references to loop variables from within nested functions
This analyzer reports places where a function literal references the iteration variable of an enclosing loop, and the loop calls the function in such a way (e.g. with go or defer) that it may outlive the loop iteration and possibly observe the wrong value of the variable.
Note: An iteration variable can only outlive a loop iteration in Go versions <=1.21. In Go 1.22 and later, the loop variable lifetimes changed to create a new iteration variable per loop iteration. (See go.dev/issue/60078.)
In this example, all the deferred functions run after the loop has completed, so all observe the final value of v [
for _, v := range list {
defer func() {
use(v) // incorrect
}()
}

One fix is to create a new variable for each iteration of the loop:
for _, v := range list {
v := v // new var per iteration
defer func() {
use(v) // ok
}()
}

After Go version 1.22, the previous two for loops are equivalent and both are correct.
The next example uses a go statement and has a similar problem [
for _, v := range elem {
go func() {
use(v) // incorrect, and a data race
}()
}

A fix is the same as before. The checker also reports problems in goroutines started by golang.org/x/sync/errgroup.Group. A hard-to-spot variant of this form is common in parallel tests:
func Test(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
use(test) // incorrect, and a data race
})
}
}

The t.Parallel() call causes the rest of the function to execute concurrent with the loop [ The analyzer reports references only in the last statement, as it is not deep enough to understand the effects of subsequent statements that might render the reference benign. ("Last statement" is defined recursively in compound statements such as if, switch, and select.)
See: https://golang.org/doc/go_faq.html#closures_and_goroutines
Default: `true` | | `lostcancel` | check cancel func returned by context.WithCancel is called
The cancellation function returned by context.WithCancel, WithTimeout, WithDeadline and variants such as WithCancelCause must be called, or the new context will remain live until its parent context is cancelled. (The background context is never cancelled.)
Default: `true` | -| `modernize` | simplify code by using modern constructs
This analyzer reports opportunities for simplifying and clarifying existing code by using more modern features of Go, such as:
- replacing an if/else conditional assignment by a call to the built-in min or max functions added in go1.21; - replacing sort.Slice(x, func(i, j int) bool) { return s[i] < s[j] } by a call to slices.Sort(s), added in go1.21; - replacing interface{} by the 'any' type added in go1.18; - replacing append([]T(nil), s...) by slices.Clone(s) or slices.Concat(s), added in go1.21; - replacing a loop around an m[k]=v map update by a call to one of the Collect, Copy, Clone, or Insert functions from the maps package, added in go1.21; - replacing []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...), added in go1.19; - replacing uses of context.WithCancel in tests with t.Context, added in go1.24; - replacing omitempty by omitzero on structs, added in go1.24; - replacing append(s[:i], s[i+1]...) by slices.Delete(s, i, i+1), added in go1.21 - replacing a 3-clause for i := 0; i < n; i++ {} loop by for i := range n {}, added in go1.22; - replacing Split in "for range strings.Split(...)" by go1.24's more efficient SplitSeq;
To apply all modernization fixes en masse, you can use the following command:
$ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test ./...

If the tool warns of conflicting fixes, you may need to run it more than once until it has applied all fixes cleanly. This command is not an officially supported interface and may change in the future.
Default: `true` | +| `maprange` | checks for unnecessary calls to maps.Keys and maps.Values in range statements
Consider a loop written like this:
for val := range maps.Values(m) {
fmt.Println(val)
}

This should instead be written without the call to maps.Values:
for _, val := range m {
fmt.Println(val)
}

golang.org/x/exp/maps returns slices for Keys/Values instead of iterators, but unnecessary calls should similarly be removed:
for _, key := range maps.Keys(m) {
fmt.Println(key)
}

should be rewritten as:
for key := range m {
fmt.Println(key)
}

Default: `true` | +| `modernize` | simplify code by using modern constructs
This analyzer reports opportunities for simplifying and clarifying existing code by using more modern features of Go and its standard library.
Each diagnostic provides a fix. Our intent is that these fixes may be safely applied en masse without changing the behavior of your program. In some cases the suggested fixes are imperfect and may lead to (for example) unused imports or unused local variables, causing build breakage. However, these problems are generally trivial to fix. We regard any modernizer whose fix changes program behavior to have a serious bug and will endeavor to fix it.
To apply all modernization fixes en masse, you can use the following command:
$ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...

(Do not use "go get -tool" to add gopls as a dependency of your module; gopls commands must be built from their release branch.)
If the tool warns of conflicting fixes, you may need to run it more than once until it has applied all fixes cleanly. This command is not an officially supported interface and may change in the future.
Changes produced by this tool should be reviewed as usual before being merged. In some cases, a loop may be replaced by a simple function call, causing comments within the loop to be discarded. Human judgment may be required to avoid losing comments of value.
Each diagnostic reported by modernize has a specific category. (The categories are listed below.) Diagnostics in some categories, such as "efaceany" (which replaces "interface{}" with "any" where it is safe to do so) are particularly numerous. It may ease the burden of code review to apply fixes in two passes, the first change consisting only of fixes of category "efaceany", the second consisting of all others. This can be achieved using the -category flag:
$ modernize -category=efaceany  -fix -test ./...
$ modernize -category=-efaceany -fix -test ./...

Categories of modernize diagnostic:
- forvar: remove x := x variable declarations made unnecessary by the new semantics of loops in go1.22.
- slicescontains: replace 'for i, elem := range s { if elem == needle { ...; break }' by a call to slices.Contains, added in go1.21.
- minmax: replace an if/else conditional assignment by a call to the built-in min or max functions added in go1.21.
- sortslice: replace sort.Slice(x, func(i, j int) bool) { return s[i] < s[j] } by a call to slices.Sort(s), added in go1.21.
- efaceany: replace interface{} by the 'any' type added in go1.18.
- mapsloop: replace a loop around an m[k]=v map update by a call to one of the Collect, Copy, Clone, or Insert functions from the maps package, added in go1.21.
- fmtappendf: replace []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...), added in go1.19.
- testingcontext: replace uses of context.WithCancel in tests with t.Context, added in go1.24.
- omitzero: replace omitempty by omitzero on structs, added in go1.24.
- bloop: replace "for i := range b.N" or "for range b.N" in a benchmark with "for b.Loop()", and remove any preceding calls to b.StopTimer, b.StartTimer, and b.ResetTimer.
B.Loop intentionally defeats compiler optimizations such as inlining so that the benchmark is not entirely optimized away. Currently, however, it may cause benchmarks to become slower in some cases due to increased allocation; see https://go.dev/issue/73137.
- rangeint: replace a 3-clause "for i := 0; i < n; i++" loop by "for i := range n", added in go1.22.
- stringsseq: replace Split in "for range strings.Split(...)" by go1.24's more efficient SplitSeq, or Fields with FieldSeq.
- stringscutprefix: replace some uses of HasPrefix followed by TrimPrefix with CutPrefix, added to the strings package in go1.20.
- waitgroup: replace old complex usages of sync.WaitGroup by less complex WaitGroup.Go method in go1.25.
Default: `true` | | `nilfunc` | check for useless comparisons between functions and nil
A useless comparison is one like f == nil as opposed to f() == nil.
Default: `true` | | `nilness` | check for redundant or impossible nil comparisons
The nilness checker inspects the control-flow graph of each function in a package and reports nil pointer dereferences, degenerate nil pointers, and panics with nil values. A degenerate comparison is of the form x==nil or x!=nil where x is statically known to be nil or non-nil. These are often a mistake, especially in control flow related to errors. Panics with nil values are checked because they are not detectable by
if r := recover(); r != nil {

This check reports conditions such as:
if f == nil { // impossible condition (f is a function)
}

and:
p := &v
...
if p != nil { // tautological condition
}

and:
if p == nil {
print(*p) // nil dereference
}

and:
if p == nil {
panic(p)
}

Sometimes the control flow may be quite complex, making bugs hard to spot. In the example below, the err.Error expression is guaranteed to panic because, after the first return, err must be nil. The intervening loop is just a distraction.
...
err := g.Wait()
if err != nil {
return err
}
partialSuccess := false
for _, err := range errs {
if err == nil {
partialSuccess = true
break
}
}
if partialSuccess {
reportStatus(StatusMessage{
Code: code.ERROR,
Detail: err.Error(), // "nil dereference in dynamic method call"
})
return nil
}

...
Default: `true` | | `nonewvars` | suggested fixes for "no new vars on left side of :="
This checker provides suggested fixes for type errors of the type "no new vars on left side of :=". For example:
z := 1
z := 2

will turn into
z := 1
z = 2

Default: `true` | | `noresultvalues` | suggested fixes for unexpected return values
This checker provides suggested fixes for type errors of the type "no result values expected" or "too many return values". For example:
func z() { return nil }

will turn into
func z() { return }

Default: `true` | | `printf` | check consistency of Printf format strings and arguments
The check applies to calls of the formatting functions such as [fmt.Printf] and [fmt.Sprintf], as well as any detected wrappers of those functions such as [log.Printf]. It reports a variety of mistakes such as syntax errors in the format string and mismatches (of number and type) between the verbs and their arguments.
See the documentation of the fmt package for the complete set of format operators and their operand types.
Default: `true` | +| `recursiveiter` | check for inefficient recursive iterators
This analyzer reports when a function that returns an iterator (iter.Seq or iter.Seq2) calls itself as the operand of a range statement, as this is inefficient.
When implementing an iterator (e.g. iter.Seq[T]) for a recursive data type such as a tree or linked list, it is tempting to recursively range over the iterator for each child element.
Here's an example of a naive iterator over a binary tree:
type tree struct {
value int
left, right *tree
}

func (t *tree) All() iter.Seq[int] {
return func(yield func(int) bool) {
if t != nil {
for elem := range t.left.All() { // "inefficient recursive iterator"
if !yield(elem) {
return
}
}
if !yield(t.value) {
return
}
for elem := range t.right.All() { // "inefficient recursive iterator"
if !yield(elem) {
return
}
}
}
}
}

Though it correctly enumerates the elements of the tree, it hides a significant performance problem--two, in fact. Consider a balanced tree of N nodes. Iterating the root node will cause All to be called once on every node of the tree. This results in a chain of nested active range-over-func statements when yield(t.value) is called on a leaf node.
The first performance problem is that each range-over-func statement must typically heap-allocate a variable, so iteration of the tree allocates as many variables as there are elements in the tree, for a total of O(N) allocations, all unnecessary.
The second problem is that each call to yield for a leaf of the tree causes each of the enclosing range loops to receive a value, which they then immediately pass on to their respective yield function. This results in a chain of log(N) dynamic yield calls per element, a total of O(N*log N) dynamic calls overall, when only O(N) are necessary.
A better implementation strategy for recursive iterators is to first define the "every" operator for your recursive data type, where every(f) reports whether f(x) is true for every element x in the data type. For our tree, the every function would be:
func (t *tree) every(f func(int) bool) bool {
return t == nil ||
t.left.every(f) && f(t.value) && t.right.every(f)
}

Then the iterator can be simply expressed as a trivial wrapper around this function:
func (t *tree) All() iter.Seq[int] {
return func(yield func(int) bool) {
_ = t.every(yield)
}
}

In effect, tree.All computes whether yield returns true for each element, short-circuiting if it every returns false, then discards the final boolean result.
This has much better performance characteristics: it makes one dynamic call per element of the tree, and it doesn't heap-allocate anything. It is also clearer.
Default: `true` | | `shadow` | check for possible unintended shadowing of variables
This analyzer check for shadowed variables. A shadowed variable is a variable declared in an inner scope with the same name and type as a variable in an outer scope, and where the outer variable is mentioned after the inner one is declared.
(This definition can be refined; the module generates too many false positives and is not yet enabled by default.)
For example:
func BadRead(f *os.File, buf []byte) error {
var err error
for {
n, err := f.Read(buf) // shadows the function variable 'err'
if err != nil {
break // causes return of wrong value
}
foo(buf)
}
return err
}

Default: `false` | | `shift` | check for shifts that equal or exceed the width of the integer
Default: `true` | | `sigchanyzer` | check for unbuffered channel of os.Signal
This checker reports call expression of the form
signal.Notify(c <-chan os.Signal, sig ...os.Signal),

where c is an unbuffered channel, which can be at risk of missing the signal.
Default: `true` | @@ -814,6 +971,26 @@ filesystem, so subsequent analysis should be faster. Default: `true` +### `ui.diagnostic.annotations` + +annotations specifies the various kinds of compiler +optimization details that should be reported as diagnostics +when enabled for a package by the "Toggle compiler +optimization details" (`gopls.gc_details`) command. + +(Some users care only about one kind of annotation in their +profiling efforts. More importantly, in large packages, the +number of annotations can sometimes overwhelm the user +interface and exceed the per-file diagnostic limit.) + +TODO(adonovan): rename this field to CompilerOptDetail. + +| Properties | Description | +| --- | --- | +| `bounds` | `"bounds"` controls bounds checking diagnostics.

Default: `true` | +| `escape` | `"escape"` controls diagnostics about escape choices.

Default: `true` | +| `inline` | `"inline"` controls diagnostics about inlining choices.

Default: `true` | +| `nil` | `"nil"` controls nil checks.

Default: `true` | ### `ui.diagnostic.diagnosticsDelay` (Advanced) diagnosticsDelay controls the amount of time that gopls waits @@ -839,10 +1016,25 @@ or configuration change will still trigger diagnostics. Default: `"Edit"` ### `ui.diagnostic.staticcheck` -(Experimental) staticcheck enables additional analyses from staticcheck.io. +(Experimental) staticcheck configures the default set of analyses staticcheck.io. These analyses are documented on [Staticcheck's website](https://staticcheck.io/docs/checks/). +The "staticcheck" option has three values: +- false: disable all staticcheck analyzers +- true: enable all staticcheck analyzers +- unset: enable a subset of staticcheck analyzers + selected by gopls maintainers for runtime efficiency + and analytic precision. + +Regardless of this setting, individual analyzers can be +selectively enabled or disabled using the `analyses` setting. + + +Default: `false` +### `ui.diagnostic.staticcheckProvided` + +(Experimental) Default: `false` ### `ui.documentation.hoverKind` diff --git a/extension/package.json b/extension/package.json index 8a7cc3b5ad..57d9a5e31c 100644 --- a/extension/package.json +++ b/extension/package.json @@ -2033,7 +2033,7 @@ }, "run_govulncheck": { "type": "boolean", - "markdownDescription": "`\"run_govulncheck\"`: Run govulncheck (legacy)\n\nThis codelens source annotates the `module` directive in a go.mod file\nwith a command to run Govulncheck asynchronously.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that\ncomputes the set of functions reachable within your application, including\ndependencies; queries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n", + "markdownDescription": "(Experimental) `\"run_govulncheck\"`: Run govulncheck (legacy)\n\nThis codelens source annotates the `module` directive in a go.mod file\nwith a command to run Govulncheck asynchronously.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that\ncomputes the set of functions reachable within your application, including\ndependencies; queries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n", "default": false }, "test": { @@ -2058,7 +2058,7 @@ }, "vulncheck": { "type": "boolean", - "markdownDescription": "`\"vulncheck\"`: Run govulncheck\n\nThis codelens source annotates the `module` directive in a go.mod file\nwith a command to run govulncheck synchronously.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that\ncomputes the set of functions reachable within your application, including\ndependencies; queries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n", + "markdownDescription": "(Experimental) `\"vulncheck\"`: Run govulncheck\n\nThis codelens source annotates the `module` directive in a go.mod file\nwith a command to run govulncheck synchronously.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that\ncomputes the set of functions reachable within your application, including\ndependencies; queries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n", "default": false } } @@ -2108,6 +2108,781 @@ "markdownDescription": "analyses specify analyses that the user would like to enable or disable.\nA map of the names of analysis passes that should be enabled/disabled.\nA full list of analyzers that gopls uses can be found in\n[analyzers.md](https://github.com/golang/tools/blob/master/gopls/doc/analyzers.md).\n\nExample Usage:\n\n```json5\n...\n\"analyses\": {\n \"unreachable\": false, // Disable the unreachable analyzer.\n \"unusedvariable\": true // Enable the unusedvariable analyzer.\n}\n...\n```\n", "scope": "resource", "properties": { + "QF1001": { + "type": "boolean", + "markdownDescription": "Apply De Morgan's law\n\nAvailable since\n 2021.1\n", + "default": false + }, + "QF1002": { + "type": "boolean", + "markdownDescription": "Convert untagged switch to tagged switch\n\nAn untagged switch that compares a single variable against a series of\nvalues can be replaced with a tagged switch.\n\nBefore:\n\n switch {\n case x == 1 || x == 2, x == 3:\n ...\n case x == 4:\n ...\n default:\n ...\n }\n\nAfter:\n\n switch x {\n case 1, 2, 3:\n ...\n case 4:\n ...\n default:\n ...\n }\n\nAvailable since\n 2021.1\n", + "default": true + }, + "QF1003": { + "type": "boolean", + "markdownDescription": "Convert if/else-if chain to tagged switch\n\nA series of if/else-if checks comparing the same variable against\nvalues can be replaced with a tagged switch.\n\nBefore:\n\n if x == 1 || x == 2 {\n ...\n } else if x == 3 {\n ...\n } else {\n ...\n }\n\nAfter:\n\n switch x {\n case 1, 2:\n ...\n case 3:\n ...\n default:\n ...\n }\n\nAvailable since\n 2021.1\n", + "default": true + }, + "QF1004": { + "type": "boolean", + "markdownDescription": "Use strings.ReplaceAll instead of strings.Replace with n == -1\n\nAvailable since\n 2021.1\n", + "default": true + }, + "QF1005": { + "type": "boolean", + "markdownDescription": "Expand call to math.Pow\n\nSome uses of math.Pow can be simplified to basic multiplication.\n\nBefore:\n\n math.Pow(x, 2)\n\nAfter:\n\n x * x\n\nAvailable since\n 2021.1\n", + "default": false + }, + "QF1006": { + "type": "boolean", + "markdownDescription": "Lift if+break into loop condition\n\nBefore:\n\n for {\n if done {\n break\n }\n ...\n }\n\nAfter:\n\n for !done {\n ...\n }\n\nAvailable since\n 2021.1\n", + "default": false + }, + "QF1007": { + "type": "boolean", + "markdownDescription": "Merge conditional assignment into variable declaration\n\nBefore:\n\n x := false\n if someCondition {\n x = true\n }\n\nAfter:\n\n x := someCondition\n\nAvailable since\n 2021.1\n", + "default": false + }, + "QF1008": { + "type": "boolean", + "markdownDescription": "Omit embedded fields from selector expression\n\nAvailable since\n 2021.1\n", + "default": false + }, + "QF1009": { + "type": "boolean", + "markdownDescription": "Use time.Time.Equal instead of == operator\n\nAvailable since\n 2021.1\n", + "default": true + }, + "QF1010": { + "type": "boolean", + "markdownDescription": "Convert slice of bytes to string when printing it\n\nAvailable since\n 2021.1\n", + "default": true + }, + "QF1011": { + "type": "boolean", + "markdownDescription": "Omit redundant type from variable declaration\n\nAvailable since\n 2021.1\n", + "default": false + }, + "QF1012": { + "type": "boolean", + "markdownDescription": "Use fmt.Fprintf(x, ...) instead of x.Write(fmt.Sprintf(...))\n\nAvailable since\n 2022.1\n", + "default": true + }, + "S1000": { + "type": "boolean", + "markdownDescription": "Use plain channel send or receive instead of single-case select\n\nSelect statements with a single case can be replaced with a simple\nsend or receive.\n\nBefore:\n\n select {\n case x := <-ch:\n fmt.Println(x)\n }\n\nAfter:\n\n x := <-ch\n fmt.Println(x)\n\nAvailable since\n 2017.1\n", + "default": true + }, + "S1001": { + "type": "boolean", + "markdownDescription": "Replace for loop with call to copy\n\nUse copy() for copying elements from one slice to another. For\narrays of identical size, you can use simple assignment.\n\nBefore:\n\n for i, x := range src {\n dst[i] = x\n }\n\nAfter:\n\n copy(dst, src)\n\nAvailable since\n 2017.1\n", + "default": true + }, + "S1002": { + "type": "boolean", + "markdownDescription": "Omit comparison with boolean constant\n\nBefore:\n\n if x == true {}\n\nAfter:\n\n if x {}\n\nAvailable since\n 2017.1\n", + "default": false + }, + "S1003": { + "type": "boolean", + "markdownDescription": "Replace call to strings.Index with strings.Contains\n\nBefore:\n\n if strings.Index(x, y) != -1 {}\n\nAfter:\n\n if strings.Contains(x, y) {}\n\nAvailable since\n 2017.1\n", + "default": true + }, + "S1004": { + "type": "boolean", + "markdownDescription": "Replace call to bytes.Compare with bytes.Equal\n\nBefore:\n\n if bytes.Compare(x, y) == 0 {}\n\nAfter:\n\n if bytes.Equal(x, y) {}\n\nAvailable since\n 2017.1\n", + "default": true + }, + "S1005": { + "type": "boolean", + "markdownDescription": "Drop unnecessary use of the blank identifier\n\nIn many cases, assigning to the blank identifier is unnecessary.\n\nBefore:\n\n for _ = range s {}\n x, _ = someMap[key]\n _ = <-ch\n\nAfter:\n\n for range s{}\n x = someMap[key]\n <-ch\n\nAvailable since\n 2017.1\n", + "default": false + }, + "S1006": { + "type": "boolean", + "markdownDescription": "Use 'for { ... }' for infinite loops\n\nFor infinite loops, using for { ... } is the most idiomatic choice.\n\nAvailable since\n 2017.1\n", + "default": false + }, + "S1007": { + "type": "boolean", + "markdownDescription": "Simplify regular expression by using raw string literal\n\nRaw string literals use backticks instead of quotation marks and do not support\nany escape sequences. This means that the backslash can be used\nfreely, without the need of escaping.\n\nSince regular expressions have their own escape sequences, raw strings\ncan improve their readability.\n\nBefore:\n\n regexp.Compile(\"\\\\A(\\\\w+) profile: total \\\\d+\\\\n\\\\z\")\n\nAfter:\n\n regexp.Compile(`\\A(\\w+) profile: total \\d+\\n\\z`)\n\nAvailable since\n 2017.1\n", + "default": true + }, + "S1008": { + "type": "boolean", + "markdownDescription": "Simplify returning boolean expression\n\nBefore:\n\n if {\n return true\n }\n return false\n\nAfter:\n\n return \n\nAvailable since\n 2017.1\n", + "default": false + }, + "S1009": { + "type": "boolean", + "markdownDescription": "Omit redundant nil check on slices, maps, and channels\n\nThe len function is defined for all slices, maps, and\nchannels, even nil ones, which have a length of zero. It is not necessary to\ncheck for nil before checking that their length is not zero.\n\nBefore:\n\n if x != nil && len(x) != 0 {}\n\nAfter:\n\n if len(x) != 0 {}\n\nAvailable since\n 2017.1\n", + "default": true + }, + "S1010": { + "type": "boolean", + "markdownDescription": "Omit default slice index\n\nWhen slicing, the second index defaults to the length of the value,\nmaking s[n:len(s)] and s[n:] equivalent.\n\nAvailable since\n 2017.1\n", + "default": true + }, + "S1011": { + "type": "boolean", + "markdownDescription": "Use a single append to concatenate two slices\n\nBefore:\n\n for _, e := range y {\n x = append(x, e)\n }\n \n for i := range y {\n x = append(x, y[i])\n }\n \n for i := range y {\n v := y[i]\n x = append(x, v)\n }\n\nAfter:\n\n x = append(x, y...)\n x = append(x, y...)\n x = append(x, y...)\n\nAvailable since\n 2017.1\n", + "default": false + }, + "S1012": { + "type": "boolean", + "markdownDescription": "Replace time.Now().Sub(x) with time.Since(x)\n\nThe time.Since helper has the same effect as using time.Now().Sub(x)\nbut is easier to read.\n\nBefore:\n\n time.Now().Sub(x)\n\nAfter:\n\n time.Since(x)\n\nAvailable since\n 2017.1\n", + "default": true + }, + "S1016": { + "type": "boolean", + "markdownDescription": "Use a type conversion instead of manually copying struct fields\n\nTwo struct types with identical fields can be converted between each\nother. In older versions of Go, the fields had to have identical\nstruct tags. Since Go 1.8, however, struct tags are ignored during\nconversions. It is thus not necessary to manually copy every field\nindividually.\n\nBefore:\n\n var x T1\n y := T2{\n Field1: x.Field1,\n Field2: x.Field2,\n }\n\nAfter:\n\n var x T1\n y := T2(x)\n\nAvailable since\n 2017.1\n", + "default": false + }, + "S1017": { + "type": "boolean", + "markdownDescription": "Replace manual trimming with strings.TrimPrefix\n\nInstead of using strings.HasPrefix and manual slicing, use the\nstrings.TrimPrefix function. If the string doesn't start with the\nprefix, the original string will be returned. Using strings.TrimPrefix\nreduces complexity, and avoids common bugs, such as off-by-one\nmistakes.\n\nBefore:\n\n if strings.HasPrefix(str, prefix) {\n str = str[len(prefix):]\n }\n\nAfter:\n\n str = strings.TrimPrefix(str, prefix)\n\nAvailable since\n 2017.1\n", + "default": true + }, + "S1018": { + "type": "boolean", + "markdownDescription": "Use 'copy' for sliding elements\n\ncopy() permits using the same source and destination slice, even with\noverlapping ranges. This makes it ideal for sliding elements in a\nslice.\n\nBefore:\n\n for i := 0; i < n; i++ {\n bs[i] = bs[offset+i]\n }\n\nAfter:\n\n copy(bs[:n], bs[offset:])\n\nAvailable since\n 2017.1\n", + "default": true + }, + "S1019": { + "type": "boolean", + "markdownDescription": "Simplify 'make' call by omitting redundant arguments\n\nThe 'make' function has default values for the length and capacity\narguments. For channels, the length defaults to zero, and for slices,\nthe capacity defaults to the length.\n\nAvailable since\n 2017.1\n", + "default": true + }, + "S1020": { + "type": "boolean", + "markdownDescription": "Omit redundant nil check in type assertion\n\nBefore:\n\n if _, ok := i.(T); ok && i != nil {}\n\nAfter:\n\n if _, ok := i.(T); ok {}\n\nAvailable since\n 2017.1\n", + "default": true + }, + "S1021": { + "type": "boolean", + "markdownDescription": "Merge variable declaration and assignment\n\nBefore:\n\n var x uint\n x = 1\n\nAfter:\n\n var x uint = 1\n\nAvailable since\n 2017.1\n", + "default": false + }, + "S1023": { + "type": "boolean", + "markdownDescription": "Omit redundant control flow\n\nFunctions that have no return value do not need a return statement as\nthe final statement of the function.\n\nSwitches in Go do not have automatic fallthrough, unlike languages\nlike C. It is not necessary to have a break statement as the final\nstatement in a case block.\n\nAvailable since\n 2017.1\n", + "default": true + }, + "S1024": { + "type": "boolean", + "markdownDescription": "Replace x.Sub(time.Now()) with time.Until(x)\n\nThe time.Until helper has the same effect as using x.Sub(time.Now())\nbut is easier to read.\n\nBefore:\n\n x.Sub(time.Now())\n\nAfter:\n\n time.Until(x)\n\nAvailable since\n 2017.1\n", + "default": true + }, + "S1025": { + "type": "boolean", + "markdownDescription": "Don't use fmt.Sprintf(\"%s\", x) unnecessarily\n\nIn many instances, there are easier and more efficient ways of getting\na value's string representation. Whenever a value's underlying type is\na string already, or the type has a String method, they should be used\ndirectly.\n\nGiven the following shared definitions\n\n type T1 string\n type T2 int\n\n func (T2) String() string { return \"Hello, world\" }\n\n var x string\n var y T1\n var z T2\n\nwe can simplify\n\n fmt.Sprintf(\"%s\", x)\n fmt.Sprintf(\"%s\", y)\n fmt.Sprintf(\"%s\", z)\n\nto\n\n x\n string(y)\n z.String()\n\nAvailable since\n 2017.1\n", + "default": false + }, + "S1028": { + "type": "boolean", + "markdownDescription": "Simplify error construction with fmt.Errorf\n\nBefore:\n\n errors.New(fmt.Sprintf(...))\n\nAfter:\n\n fmt.Errorf(...)\n\nAvailable since\n 2017.1\n", + "default": true + }, + "S1029": { + "type": "boolean", + "markdownDescription": "Range over the string directly\n\nRanging over a string will yield byte offsets and runes. If the offset\nisn't used, this is functionally equivalent to converting the string\nto a slice of runes and ranging over that. Ranging directly over the\nstring will be more performant, however, as it avoids allocating a new\nslice, the size of which depends on the length of the string.\n\nBefore:\n\n for _, r := range []rune(s) {}\n\nAfter:\n\n for _, r := range s {}\n\nAvailable since\n 2017.1\n", + "default": false + }, + "S1030": { + "type": "boolean", + "markdownDescription": "Use bytes.Buffer.String or bytes.Buffer.Bytes\n\nbytes.Buffer has both a String and a Bytes method. It is almost never\nnecessary to use string(buf.Bytes()) or []byte(buf.String()) – simply\nuse the other method.\n\nThe only exception to this are map lookups. Due to a compiler optimization,\nm[string(buf.Bytes())] is more efficient than m[buf.String()].\n\nAvailable since\n 2017.1\n", + "default": true + }, + "S1031": { + "type": "boolean", + "markdownDescription": "Omit redundant nil check around loop\n\nYou can use range on nil slices and maps, the loop will simply never\nexecute. This makes an additional nil check around the loop\nunnecessary.\n\nBefore:\n\n if s != nil {\n for _, x := range s {\n ...\n }\n }\n\nAfter:\n\n for _, x := range s {\n ...\n }\n\nAvailable since\n 2017.1\n", + "default": true + }, + "S1032": { + "type": "boolean", + "markdownDescription": "Use sort.Ints(x), sort.Float64s(x), and sort.Strings(x)\n\nThe sort.Ints, sort.Float64s and sort.Strings functions are easier to\nread than sort.Sort(sort.IntSlice(x)), sort.Sort(sort.Float64Slice(x))\nand sort.Sort(sort.StringSlice(x)).\n\nBefore:\n\n sort.Sort(sort.StringSlice(x))\n\nAfter:\n\n sort.Strings(x)\n\nAvailable since\n 2019.1\n", + "default": true + }, + "S1033": { + "type": "boolean", + "markdownDescription": "Unnecessary guard around call to 'delete'\n\nCalling delete on a nil map is a no-op.\n\nAvailable since\n 2019.2\n", + "default": true + }, + "S1034": { + "type": "boolean", + "markdownDescription": "Use result of type assertion to simplify cases\n\nAvailable since\n 2019.2\n", + "default": true + }, + "S1035": { + "type": "boolean", + "markdownDescription": "Redundant call to net/http.CanonicalHeaderKey in method call on net/http.Header\n\nThe methods on net/http.Header, namely Add, Del, Get\nand Set, already canonicalize the given header name.\n\nAvailable since\n 2020.1\n", + "default": true + }, + "S1036": { + "type": "boolean", + "markdownDescription": "Unnecessary guard around map access\n\nWhen accessing a map key that doesn't exist yet, one receives a zero\nvalue. Often, the zero value is a suitable value, for example when\nusing append or doing integer math.\n\nThe following\n\n if _, ok := m[\"foo\"]; ok {\n m[\"foo\"] = append(m[\"foo\"], \"bar\")\n } else {\n m[\"foo\"] = []string{\"bar\"}\n }\n\ncan be simplified to\n\n m[\"foo\"] = append(m[\"foo\"], \"bar\")\n\nand\n\n if _, ok := m2[\"k\"]; ok {\n m2[\"k\"] += 4\n } else {\n m2[\"k\"] = 4\n }\n\ncan be simplified to\n\n m[\"k\"] += 4\n\nAvailable since\n 2020.1\n", + "default": true + }, + "S1037": { + "type": "boolean", + "markdownDescription": "Elaborate way of sleeping\n\nUsing a select statement with a single case receiving\nfrom the result of time.After is a very elaborate way of sleeping that\ncan much simpler be expressed with a simple call to time.Sleep.\n\nAvailable since\n 2020.1\n", + "default": true + }, + "S1038": { + "type": "boolean", + "markdownDescription": "Unnecessarily complex way of printing formatted string\n\nInstead of using fmt.Print(fmt.Sprintf(...)), one can use fmt.Printf(...).\n\nAvailable since\n 2020.1\n", + "default": true + }, + "S1039": { + "type": "boolean", + "markdownDescription": "Unnecessary use of fmt.Sprint\n\nCalling fmt.Sprint with a single string argument is unnecessary\nand identical to using the string directly.\n\nAvailable since\n 2020.1\n", + "default": true + }, + "S1040": { + "type": "boolean", + "markdownDescription": "Type assertion to current type\n\nThe type assertion x.(SomeInterface), when x already has type\nSomeInterface, can only fail if x is nil. Usually, this is\nleft-over code from when x had a different type and you can safely\ndelete the type assertion. If you want to check that x is not nil,\nconsider being explicit and using an actual if x == nil comparison\ninstead of relying on the type assertion panicking.\n\nAvailable since\n 2021.1\n", + "default": true + }, + "SA1000": { + "type": "boolean", + "markdownDescription": "Invalid regular expression\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA1001": { + "type": "boolean", + "markdownDescription": "Invalid template\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA1002": { + "type": "boolean", + "markdownDescription": "Invalid format in time.Parse\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA1003": { + "type": "boolean", + "markdownDescription": "Unsupported argument to functions in encoding/binary\n\nThe encoding/binary package can only serialize types with known sizes.\nThis precludes the use of the int and uint types, as their sizes\ndiffer on different architectures. Furthermore, it doesn't support\nserializing maps, channels, strings, or functions.\n\nBefore Go 1.8, bool wasn't supported, either.\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA1004": { + "type": "boolean", + "markdownDescription": "Suspiciously small untyped constant in time.Sleep\n\nThe time.Sleep function takes a time.Duration as its only argument.\nDurations are expressed in nanoseconds. Thus, calling time.Sleep(1)\nwill sleep for 1 nanosecond. This is a common source of bugs, as sleep\nfunctions in other languages often accept seconds or milliseconds.\n\nThe time package provides constants such as time.Second to express\nlarge durations. These can be combined with arithmetic to express\narbitrary durations, for example 5 * time.Second for 5 seconds.\n\nIf you truly meant to sleep for a tiny amount of time, use\nn * time.Nanosecond to signal to Staticcheck that you did mean to sleep\nfor some amount of nanoseconds.\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA1005": { + "type": "boolean", + "markdownDescription": "Invalid first argument to exec.Command\n\nos/exec runs programs directly (using variants of the fork and exec\nsystem calls on Unix systems). This shouldn't be confused with running\na command in a shell. The shell will allow for features such as input\nredirection, pipes, and general scripting. The shell is also\nresponsible for splitting the user's input into a program name and its\narguments. For example, the equivalent to\n\n ls / /tmp\n\nwould be\n\n exec.Command(\"ls\", \"/\", \"/tmp\")\n\nIf you want to run a command in a shell, consider using something like\nthe following – but be aware that not all systems, particularly\nWindows, will have a /bin/sh program:\n\n exec.Command(\"/bin/sh\", \"-c\", \"ls | grep Awesome\")\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA1007": { + "type": "boolean", + "markdownDescription": "Invalid URL in net/url.Parse\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA1008": { + "type": "boolean", + "markdownDescription": "Non-canonical key in http.Header map\n\nKeys in http.Header maps are canonical, meaning they follow a specific\ncombination of uppercase and lowercase letters. Methods such as\nhttp.Header.Add and http.Header.Del convert inputs into this canonical\nform before manipulating the map.\n\nWhen manipulating http.Header maps directly, as opposed to using the\nprovided methods, care should be taken to stick to canonical form in\norder to avoid inconsistencies. The following piece of code\ndemonstrates one such inconsistency:\n\n h := http.Header{}\n h[\"etag\"] = []string{\"1234\"}\n h.Add(\"etag\", \"5678\")\n fmt.Println(h)\n\n // Output:\n // map[Etag:[5678] etag:[1234]]\n\nThe easiest way of obtaining the canonical form of a key is to use\nhttp.CanonicalHeaderKey.\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA1010": { + "type": "boolean", + "markdownDescription": "(*regexp.Regexp).FindAll called with n == 0, which will always return zero results\n\nIf n >= 0, the function returns at most n matches/submatches. To\nreturn all results, specify a negative number.\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA1011": { + "type": "boolean", + "markdownDescription": "Various methods in the 'strings' package expect valid UTF-8, but invalid input is provided\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA1012": { + "type": "boolean", + "markdownDescription": "A nil context.Context is being passed to a function, consider using context.TODO instead\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA1013": { + "type": "boolean", + "markdownDescription": "io.Seeker.Seek is being called with the whence constant as the first argument, but it should be the second\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA1014": { + "type": "boolean", + "markdownDescription": "Non-pointer value passed to Unmarshal or Decode\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA1015": { + "type": "boolean", + "markdownDescription": "Using time.Tick in a way that will leak. Consider using time.NewTicker, and only use time.Tick in tests, commands and endless functions\n\nBefore Go 1.23, time.Tickers had to be closed to be able to be garbage\ncollected. Since time.Tick doesn't make it possible to close the underlying\nticker, using it repeatedly would leak memory.\n\nGo 1.23 fixes this by allowing tickers to be collected even if they weren't closed.\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA1016": { + "type": "boolean", + "markdownDescription": "Trapping a signal that cannot be trapped\n\nNot all signals can be intercepted by a process. Specifically, on\nUNIX-like systems, the syscall.SIGKILL and syscall.SIGSTOP signals are\nnever passed to the process, but instead handled directly by the\nkernel. It is therefore pointless to try and handle these signals.\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA1017": { + "type": "boolean", + "markdownDescription": "Channels used with os/signal.Notify should be buffered\n\nThe os/signal package uses non-blocking channel sends when delivering\nsignals. If the receiving end of the channel isn't ready and the\nchannel is either unbuffered or full, the signal will be dropped. To\navoid missing signals, the channel should be buffered and of the\nappropriate size. For a channel used for notification of just one\nsignal value, a buffer of size 1 is sufficient.\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA1018": { + "type": "boolean", + "markdownDescription": "strings.Replace called with n == 0, which does nothing\n\nWith n == 0, zero instances will be replaced. To replace all\ninstances, use a negative number, or use strings.ReplaceAll.\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA1020": { + "type": "boolean", + "markdownDescription": "Using an invalid host:port pair with a net.Listen-related function\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA1021": { + "type": "boolean", + "markdownDescription": "Using bytes.Equal to compare two net.IP\n\nA net.IP stores an IPv4 or IPv6 address as a slice of bytes. The\nlength of the slice for an IPv4 address, however, can be either 4 or\n16 bytes long, using different ways of representing IPv4 addresses. In\norder to correctly compare two net.IPs, the net.IP.Equal method should\nbe used, as it takes both representations into account.\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA1023": { + "type": "boolean", + "markdownDescription": "Modifying the buffer in an io.Writer implementation\n\nWrite must not modify the slice data, even temporarily.\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA1024": { + "type": "boolean", + "markdownDescription": "A string cutset contains duplicate characters\n\nThe strings.TrimLeft and strings.TrimRight functions take cutsets, not\nprefixes. A cutset is treated as a set of characters to remove from a\nstring. For example,\n\n strings.TrimLeft(\"42133word\", \"1234\")\n\nwill result in the string \"word\" – any characters that are 1, 2, 3 or\n4 are cut from the left of the string.\n\nIn order to remove one string from another, use strings.TrimPrefix instead.\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA1025": { + "type": "boolean", + "markdownDescription": "It is not possible to use (*time.Timer).Reset's return value correctly\n\nAvailable since\n 2019.1\n", + "default": false + }, + "SA1026": { + "type": "boolean", + "markdownDescription": "Cannot marshal channels or functions\n\nAvailable since\n 2019.2\n", + "default": false + }, + "SA1027": { + "type": "boolean", + "markdownDescription": "Atomic access to 64-bit variable must be 64-bit aligned\n\nOn ARM, x86-32, and 32-bit MIPS, it is the caller's responsibility to\narrange for 64-bit alignment of 64-bit words accessed atomically. The\nfirst word in a variable or in an allocated struct, array, or slice\ncan be relied upon to be 64-bit aligned.\n\nYou can use the structlayout tool to inspect the alignment of fields\nin a struct.\n\nAvailable since\n 2019.2\n", + "default": false + }, + "SA1028": { + "type": "boolean", + "markdownDescription": "sort.Slice can only be used on slices\n\nThe first argument of sort.Slice must be a slice.\n\nAvailable since\n 2020.1\n", + "default": false + }, + "SA1029": { + "type": "boolean", + "markdownDescription": "Inappropriate key in call to context.WithValue\n\nThe provided key must be comparable and should not be\nof type string or any other built-in type to avoid collisions between\npackages using context. Users of WithValue should define their own\ntypes for keys.\n\nTo avoid allocating when assigning to an interface{},\ncontext keys often have concrete type struct{}. Alternatively,\nexported context key variables' static type should be a pointer or\ninterface.\n\nAvailable since\n 2020.1\n", + "default": false + }, + "SA1030": { + "type": "boolean", + "markdownDescription": "Invalid argument in call to a strconv function\n\nThis check validates the format, number base and bit size arguments of\nthe various parsing and formatting functions in strconv.\n\nAvailable since\n 2021.1\n", + "default": false + }, + "SA1031": { + "type": "boolean", + "markdownDescription": "Overlapping byte slices passed to an encoder\n\nIn an encoding function of the form Encode(dst, src), dst and\nsrc were found to reference the same memory. This can result in\nsrc bytes being overwritten before they are read, when the encoder\nwrites more than one byte per src byte.\n\nAvailable since\n 2024.1\n", + "default": false + }, + "SA1032": { + "type": "boolean", + "markdownDescription": "Wrong order of arguments to errors.Is\n\nThe first argument of the function errors.Is is the error\nthat we have and the second argument is the error we're trying to match against.\nFor example:\n\n\tif errors.Is(err, io.EOF) { ... }\n\nThis check detects some cases where the two arguments have been swapped. It\nflags any calls where the first argument is referring to a package-level error\nvariable, such as\n\n\tif errors.Is(io.EOF, err) { /* this is wrong */ }\n\nAvailable since\n 2024.1\n", + "default": false + }, + "SA2001": { + "type": "boolean", + "markdownDescription": "Empty critical section, did you mean to defer the unlock?\n\nEmpty critical sections of the kind\n\n mu.Lock()\n mu.Unlock()\n\nare very often a typo, and the following was intended instead:\n\n mu.Lock()\n defer mu.Unlock()\n\nDo note that sometimes empty critical sections can be useful, as a\nform of signaling to wait on another goroutine. Many times, there are\nsimpler ways of achieving the same effect. When that isn't the case,\nthe code should be amply commented to avoid confusion. Combining such\ncomments with a //lint:ignore directive can be used to suppress this\nrare false positive.\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA2002": { + "type": "boolean", + "markdownDescription": "Called testing.T.FailNow or SkipNow in a goroutine, which isn't allowed\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA2003": { + "type": "boolean", + "markdownDescription": "Deferred Lock right after locking, likely meant to defer Unlock instead\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA3000": { + "type": "boolean", + "markdownDescription": "TestMain doesn't call os.Exit, hiding test failures\n\nTest executables (and in turn 'go test') exit with a non-zero status\ncode if any tests failed. When specifying your own TestMain function,\nit is your responsibility to arrange for this, by calling os.Exit with\nthe correct code. The correct code is returned by (*testing.M).Run, so\nthe usual way of implementing TestMain is to end it with\nos.Exit(m.Run()).\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA3001": { + "type": "boolean", + "markdownDescription": "Assigning to b.N in benchmarks distorts the results\n\nThe testing package dynamically sets b.N to improve the reliability of\nbenchmarks and uses it in computations to determine the duration of a\nsingle operation. Benchmark code must not alter b.N as this would\nfalsify results.\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA4000": { + "type": "boolean", + "markdownDescription": "Binary operator has identical expressions on both sides\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA4001": { + "type": "boolean", + "markdownDescription": "&*x gets simplified to x, it does not copy x\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA4003": { + "type": "boolean", + "markdownDescription": "Comparing unsigned values against negative values is pointless\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA4004": { + "type": "boolean", + "markdownDescription": "The loop exits unconditionally after one iteration\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA4005": { + "type": "boolean", + "markdownDescription": "Field assignment that will never be observed. Did you mean to use a pointer receiver?\n\nAvailable since\n 2021.1\n", + "default": false + }, + "SA4006": { + "type": "boolean", + "markdownDescription": "A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA4008": { + "type": "boolean", + "markdownDescription": "The variable in the loop condition never changes, are you incrementing the wrong variable?\n\nFor example:\n\n\tfor i := 0; i < 10; j++ { ... }\n\nThis may also occur when a loop can only execute once because of unconditional\ncontrol flow that terminates the loop. For example, when a loop body contains an\nunconditional break, return, or panic:\n\n\tfunc f() {\n\t\tpanic(\"oops\")\n\t}\n\tfunc g() {\n\t\tfor i := 0; i < 10; i++ {\n\t\t\t// f unconditionally calls panic, which means \"i\" is\n\t\t\t// never incremented.\n\t\t\tf()\n\t\t}\n\t}\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA4009": { + "type": "boolean", + "markdownDescription": "A function argument is overwritten before its first use\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA4010": { + "type": "boolean", + "markdownDescription": "The result of append will never be observed anywhere\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA4011": { + "type": "boolean", + "markdownDescription": "Break statement with no effect. Did you mean to break out of an outer loop?\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA4012": { + "type": "boolean", + "markdownDescription": "Comparing a value against NaN even though no value is equal to NaN\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA4013": { + "type": "boolean", + "markdownDescription": "Negating a boolean twice (!!b) is the same as writing b. This is either redundant, or a typo.\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA4014": { + "type": "boolean", + "markdownDescription": "An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA4015": { + "type": "boolean", + "markdownDescription": "Calling functions like math.Ceil on floats converted from integers doesn't do anything useful\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA4016": { + "type": "boolean", + "markdownDescription": "Certain bitwise operations, such as x ^ 0, do not do anything useful\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA4017": { + "type": "boolean", + "markdownDescription": "Discarding the return values of a function without side effects, making the call pointless\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA4018": { + "type": "boolean", + "markdownDescription": "Self-assignment of variables\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA4019": { + "type": "boolean", + "markdownDescription": "Multiple, identical build constraints in the same file\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA4020": { + "type": "boolean", + "markdownDescription": "Unreachable case clause in a type switch\n\nIn a type switch like the following\n\n type T struct{}\n func (T) Read(b []byte) (int, error) { return 0, nil }\n\n var v any = T{}\n\n switch v.(type) {\n case io.Reader:\n // ...\n case T:\n // unreachable\n }\n\nthe second case clause can never be reached because T implements\nio.Reader and case clauses are evaluated in source order.\n\nAnother example:\n\n type T struct{}\n func (T) Read(b []byte) (int, error) { return 0, nil }\n func (T) Close() error { return nil }\n\n var v any = T{}\n\n switch v.(type) {\n case io.Reader:\n // ...\n case io.ReadCloser:\n // unreachable\n }\n\nEven though T has a Close method and thus implements io.ReadCloser,\nio.Reader will always match first. The method set of io.Reader is a\nsubset of io.ReadCloser. Thus it is impossible to match the second\ncase without matching the first case.\n\n\nStructurally equivalent interfaces\n\nA special case of the previous example are structurally identical\ninterfaces. Given these declarations\n\n type T error\n type V error\n\n func doSomething() error {\n err, ok := doAnotherThing()\n if ok {\n return T(err)\n }\n\n return U(err)\n }\n\nthe following type switch will have an unreachable case clause:\n\n switch doSomething().(type) {\n case T:\n // ...\n case V:\n // unreachable\n }\n\nT will always match before V because they are structurally equivalent\nand therefore doSomething()'s return value implements both.\n\nAvailable since\n 2019.2\n", + "default": true + }, + "SA4022": { + "type": "boolean", + "markdownDescription": "Comparing the address of a variable against nil\n\nCode such as 'if &x == nil' is meaningless, because taking the address of a variable always yields a non-nil pointer.\n\nAvailable since\n 2020.1\n", + "default": true + }, + "SA4023": { + "type": "boolean", + "markdownDescription": "Impossible comparison of interface value with untyped nil\n\nUnder the covers, interfaces are implemented as two elements, a\ntype T and a value V. V is a concrete value such as an int,\nstruct or pointer, never an interface itself, and has type T. For\ninstance, if we store the int value 3 in an interface, the\nresulting interface value has, schematically, (T=int, V=3). The\nvalue V is also known as the interface's dynamic value, since a\ngiven interface variable might hold different values V (and\ncorresponding types T) during the execution of the program.\n\nAn interface value is nil only if the V and T are both\nunset, (T=nil, V is not set), In particular, a nil interface will\nalways hold a nil type. If we store a nil pointer of type *int\ninside an interface value, the inner type will be *int regardless\nof the value of the pointer: (T=*int, V=nil). Such an interface\nvalue will therefore be non-nil even when the pointer value V\ninside is nil.\n\nThis situation can be confusing, and arises when a nil value is\nstored inside an interface value such as an error return:\n\n func returnsError() error {\n var p *MyError = nil\n if bad() {\n p = ErrBad\n }\n return p // Will always return a non-nil error.\n }\n\nIf all goes well, the function returns a nil p, so the return\nvalue is an error interface value holding (T=*MyError, V=nil).\nThis means that if the caller compares the returned error to nil,\nit will always look as if there was an error even if nothing bad\nhappened. To return a proper nil error to the caller, the\nfunction must return an explicit nil:\n\n func returnsError() error {\n if bad() {\n return ErrBad\n }\n return nil\n }\n\nIt's a good idea for functions that return errors always to use\nthe error type in their signature (as we did above) rather than a\nconcrete type such as *MyError, to help guarantee the error is\ncreated correctly. As an example, os.Open returns an error even\nthough, if not nil, it's always of concrete type *os.PathError.\n\nSimilar situations to those described here can arise whenever\ninterfaces are used. Just keep in mind that if any concrete value\nhas been stored in the interface, the interface will not be nil.\nFor more information, see The Laws of\nReflection at https://golang.org/doc/articles/laws_of_reflection.html.\n\nThis text has been copied from\nhttps://golang.org/doc/faq#nil_error, licensed under the Creative\nCommons Attribution 3.0 License.\n\nAvailable since\n 2020.2\n", + "default": false + }, + "SA4024": { + "type": "boolean", + "markdownDescription": "Checking for impossible return value from a builtin function\n\nReturn values of the len and cap builtins cannot be negative.\n\nSee https://golang.org/pkg/builtin/#len and https://golang.org/pkg/builtin/#cap.\n\nExample:\n\n if len(slice) < 0 {\n fmt.Println(\"unreachable code\")\n }\n\nAvailable since\n 2021.1\n", + "default": true + }, + "SA4025": { + "type": "boolean", + "markdownDescription": "Integer division of literals that results in zero\n\nWhen dividing two integer constants, the result will\nalso be an integer. Thus, a division such as 2 / 3 results in 0.\nThis is true for all of the following examples:\n\n\t_ = 2 / 3\n\tconst _ = 2 / 3\n\tconst _ float64 = 2 / 3\n\t_ = float64(2 / 3)\n\nStaticcheck will flag such divisions if both sides of the division are\ninteger literals, as it is highly unlikely that the division was\nintended to truncate to zero. Staticcheck will not flag integer\ndivision involving named constants, to avoid noisy positives.\n\nAvailable since\n 2021.1\n", + "default": true + }, + "SA4026": { + "type": "boolean", + "markdownDescription": "Go constants cannot express negative zero\n\nIn IEEE 754 floating point math, zero has a sign and can be positive\nor negative. This can be useful in certain numerical code.\n\nGo constants, however, cannot express negative zero. This means that\nthe literals -0.0 and 0.0 have the same ideal value (zero) and\nwill both represent positive zero at runtime.\n\nTo explicitly and reliably create a negative zero, you can use the\nmath.Copysign function: math.Copysign(0, -1).\n\nAvailable since\n 2021.1\n", + "default": true + }, + "SA4027": { + "type": "boolean", + "markdownDescription": "(*net/url.URL).Query returns a copy, modifying it doesn't change the URL\n\n(*net/url.URL).Query parses the current value of net/url.URL.RawQuery\nand returns it as a map of type net/url.Values. Subsequent changes to\nthis map will not affect the URL unless the map gets encoded and\nassigned to the URL's RawQuery.\n\nAs a consequence, the following code pattern is an expensive no-op:\nu.Query().Add(key, value).\n\nAvailable since\n 2021.1\n", + "default": true + }, + "SA4028": { + "type": "boolean", + "markdownDescription": "x % 1 is always zero\n\nAvailable since\n 2022.1\n", + "default": true + }, + "SA4029": { + "type": "boolean", + "markdownDescription": "Ineffective attempt at sorting slice\n\nsort.Float64Slice, sort.IntSlice, and sort.StringSlice are\ntypes, not functions. Doing x = sort.StringSlice(x) does nothing,\nespecially not sort any values. The correct usage is\nsort.Sort(sort.StringSlice(x)) or sort.StringSlice(x).Sort(),\nbut there are more convenient helpers, namely sort.Float64s,\nsort.Ints, and sort.Strings.\n\nAvailable since\n 2022.1\n", + "default": true + }, + "SA4030": { + "type": "boolean", + "markdownDescription": "Ineffective attempt at generating random number\n\nFunctions in the math/rand package that accept upper limits, such\nas Intn, generate random numbers in the half-open interval [0,n). In\nother words, the generated numbers will be >= 0 and < n – they\ndon't include n. rand.Intn(1) therefore doesn't generate 0\nor 1, it always generates 0.\n\nAvailable since\n 2022.1\n", + "default": true + }, + "SA4031": { + "type": "boolean", + "markdownDescription": "Checking never-nil value against nil\n\nAvailable since\n 2022.1\n", + "default": false + }, + "SA4032": { + "type": "boolean", + "markdownDescription": "Comparing runtime.GOOS or runtime.GOARCH against impossible value\n\nAvailable since\n 2024.1\n", + "default": true + }, + "SA5000": { + "type": "boolean", + "markdownDescription": "Assignment to nil map\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA5001": { + "type": "boolean", + "markdownDescription": "Deferring Close before checking for a possible error\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA5002": { + "type": "boolean", + "markdownDescription": "The empty for loop ('for {}') spins and can block the scheduler\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA5003": { + "type": "boolean", + "markdownDescription": "Defers in infinite loops will never execute\n\nDefers are scoped to the surrounding function, not the surrounding\nblock. In a function that never returns, i.e. one containing an\ninfinite loop, defers will never execute.\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA5004": { + "type": "boolean", + "markdownDescription": "'for { select { ...' with an empty default branch spins\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA5005": { + "type": "boolean", + "markdownDescription": "The finalizer references the finalized object, preventing garbage collection\n\nA finalizer is a function associated with an object that runs when the\ngarbage collector is ready to collect said object, that is when the\nobject is no longer referenced by anything.\n\nIf the finalizer references the object, however, it will always remain\nas the final reference to that object, preventing the garbage\ncollector from collecting the object. The finalizer will never run,\nand the object will never be collected, leading to a memory leak. That\nis why the finalizer should instead use its first argument to operate\non the object. That way, the number of references can temporarily go\nto zero before the object is being passed to the finalizer.\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA5007": { + "type": "boolean", + "markdownDescription": "Infinite recursive call\n\nA function that calls itself recursively needs to have an exit\ncondition. Otherwise it will recurse forever, until the system runs\nout of memory.\n\nThis issue can be caused by simple bugs such as forgetting to add an\nexit condition. It can also happen \"on purpose\". Some languages have\ntail call optimization which makes certain infinite recursive calls\nsafe to use. Go, however, does not implement TCO, and as such a loop\nshould be used instead.\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA5008": { + "type": "boolean", + "markdownDescription": "Invalid struct tag\n\nAvailable since\n 2019.2\n", + "default": true + }, + "SA5010": { + "type": "boolean", + "markdownDescription": "Impossible type assertion\n\nSome type assertions can be statically proven to be\nimpossible. This is the case when the method sets of both\narguments of the type assertion conflict with each other, for\nexample by containing the same method with different\nsignatures.\n\nThe Go compiler already applies this check when asserting from an\ninterface value to a concrete type. If the concrete type misses\nmethods from the interface, or if function signatures don't match,\nthen the type assertion can never succeed.\n\nThis check applies the same logic when asserting from one interface to\nanother. If both interface types contain the same method but with\ndifferent signatures, then the type assertion can never succeed,\neither.\n\nAvailable since\n 2020.1\n", + "default": false + }, + "SA5011": { + "type": "boolean", + "markdownDescription": "Possible nil pointer dereference\n\nA pointer is being dereferenced unconditionally, while\nalso being checked against nil in another place. This suggests that\nthe pointer may be nil and dereferencing it may panic. This is\ncommonly a result of improperly ordered code or missing return\nstatements. Consider the following examples:\n\n func fn(x *int) {\n fmt.Println(*x)\n\n // This nil check is equally important for the previous dereference\n if x != nil {\n foo(*x)\n }\n }\n\n func TestFoo(t *testing.T) {\n x := compute()\n if x == nil {\n t.Errorf(\"nil pointer received\")\n }\n\n // t.Errorf does not abort the test, so if x is nil, the next line will panic.\n foo(*x)\n }\n\nStaticcheck tries to deduce which functions abort control flow.\nFor example, it is aware that a function will not continue\nexecution after a call to panic or log.Fatal. However, sometimes\nthis detection fails, in particular in the presence of\nconditionals. Consider the following example:\n\n func Log(msg string, level int) {\n fmt.Println(msg)\n if level == levelFatal {\n os.Exit(1)\n }\n }\n\n func Fatal(msg string) {\n Log(msg, levelFatal)\n }\n\n func fn(x *int) {\n if x == nil {\n Fatal(\"unexpected nil pointer\")\n }\n fmt.Println(*x)\n }\n\nStaticcheck will flag the dereference of x, even though it is perfectly\nsafe. Staticcheck is not able to deduce that a call to\nFatal will exit the program. For the time being, the easiest\nworkaround is to modify the definition of Fatal like so:\n\n func Fatal(msg string) {\n Log(msg, levelFatal)\n panic(\"unreachable\")\n }\n\nWe also hard-code functions from common logging packages such as\nlogrus. Please file an issue if we're missing support for a\npopular package.\n\nAvailable since\n 2020.1\n", + "default": false + }, + "SA5012": { + "type": "boolean", + "markdownDescription": "Passing odd-sized slice to function expecting even size\n\nSome functions that take slices as parameters expect the slices to have an even number of elements. \nOften, these functions treat elements in a slice as pairs. \nFor example, strings.NewReplacer takes pairs of old and new strings, \nand calling it with an odd number of elements would be an error.\n\nAvailable since\n 2020.2\n", + "default": false + }, + "SA6000": { + "type": "boolean", + "markdownDescription": "Using regexp.Match or related in a loop, should use regexp.Compile\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA6001": { + "type": "boolean", + "markdownDescription": "Missing an optimization opportunity when indexing maps by byte slices\n\nMap keys must be comparable, which precludes the use of byte slices.\nThis usually leads to using string keys and converting byte slices to\nstrings.\n\nNormally, a conversion of a byte slice to a string needs to copy the data and\ncauses allocations. The compiler, however, recognizes m[string(b)] and\nuses the data of b directly, without copying it, because it knows that\nthe data can't change during the map lookup. This leads to the\ncounter-intuitive situation that\n\n k := string(b)\n println(m[k])\n println(m[k])\n\nwill be less efficient than\n\n println(m[string(b)])\n println(m[string(b)])\n\nbecause the first version needs to copy and allocate, while the second\none does not.\n\nFor some history on this optimization, check out commit\nf5f5a8b6209f84961687d993b93ea0d397f5d5bf in the Go repository.\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA6002": { + "type": "boolean", + "markdownDescription": "Storing non-pointer values in sync.Pool allocates memory\n\nA sync.Pool is used to avoid unnecessary allocations and reduce the\namount of work the garbage collector has to do.\n\nWhen passing a value that is not a pointer to a function that accepts\nan interface, the value needs to be placed on the heap, which means an\nadditional allocation. Slices are a common thing to put in sync.Pools,\nand they're structs with 3 fields (length, capacity, and a pointer to\nan array). In order to avoid the extra allocation, one should store a\npointer to the slice instead.\n\nSee the comments on https://go-review.googlesource.com/c/go/+/24371\nthat discuss this problem.\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA6003": { + "type": "boolean", + "markdownDescription": "Converting a string to a slice of runes before ranging over it\n\nYou may want to loop over the runes in a string. Instead of converting\nthe string to a slice of runes and looping over that, you can loop\nover the string itself. That is,\n\n for _, r := range s {}\n\nand\n\n for _, r := range []rune(s) {}\n\nwill yield the same values. The first version, however, will be faster\nand avoid unnecessary memory allocations.\n\nDo note that if you are interested in the indices, ranging over a\nstring and over a slice of runes will yield different indices. The\nfirst one yields byte offsets, while the second one yields indices in\nthe slice of runes.\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA6005": { + "type": "boolean", + "markdownDescription": "Inefficient string comparison with strings.ToLower or strings.ToUpper\n\nConverting two strings to the same case and comparing them like so\n\n if strings.ToLower(s1) == strings.ToLower(s2) {\n ...\n }\n\nis significantly more expensive than comparing them with\nstrings.EqualFold(s1, s2). This is due to memory usage as well as\ncomputational complexity.\n\nstrings.ToLower will have to allocate memory for the new strings, as\nwell as convert both strings fully, even if they differ on the very\nfirst byte. strings.EqualFold, on the other hand, compares the strings\none character at a time. It doesn't need to create two intermediate\nstrings and can return as soon as the first non-matching character has\nbeen found.\n\nFor a more in-depth explanation of this issue, see\nhttps://blog.digitalocean.com/how-to-efficiently-compare-strings-in-go/\n\nAvailable since\n 2019.2\n", + "default": true + }, + "SA6006": { + "type": "boolean", + "markdownDescription": "Using io.WriteString to write []byte\n\nUsing io.WriteString to write a slice of bytes, as in\n\n io.WriteString(w, string(b))\n\nis both unnecessary and inefficient. Converting from []byte to string\nhas to allocate and copy the data, and we could simply use w.Write(b)\ninstead.\n\nAvailable since\n 2024.1\n", + "default": true + }, + "SA9001": { + "type": "boolean", + "markdownDescription": "Defers in range loops may not run when you expect them to\n\nAvailable since\n 2017.1\n", + "default": false + }, + "SA9002": { + "type": "boolean", + "markdownDescription": "Using a non-octal os.FileMode that looks like it was meant to be in octal.\n\nAvailable since\n 2017.1\n", + "default": true + }, + "SA9003": { + "type": "boolean", + "markdownDescription": "Empty body in an if or else branch\n\nAvailable since\n 2017.1, non-default\n", + "default": false + }, + "SA9004": { + "type": "boolean", + "markdownDescription": "Only the first constant has an explicit type\n\nIn a constant declaration such as the following:\n\n const (\n First byte = 1\n Second = 2\n )\n\nthe constant Second does not have the same type as the constant First.\nThis construct shouldn't be confused with\n\n const (\n First byte = iota\n Second\n )\n\nwhere First and Second do indeed have the same type. The type is only\npassed on when no explicit value is assigned to the constant.\n\nWhen declaring enumerations with explicit values it is therefore\nimportant not to write\n\n const (\n EnumFirst EnumType = 1\n EnumSecond = 2\n EnumThird = 3\n )\n\nThis discrepancy in types can cause various confusing behaviors and\nbugs.\n\n\nWrong type in variable declarations\n\nThe most obvious issue with such incorrect enumerations expresses\nitself as a compile error:\n\n package pkg\n\n const (\n EnumFirst uint8 = 1\n EnumSecond = 2\n )\n\n func fn(useFirst bool) {\n x := EnumSecond\n if useFirst {\n x = EnumFirst\n }\n }\n\nfails to compile with\n\n ./const.go:11:5: cannot use EnumFirst (type uint8) as type int in assignment\n\n\nLosing method sets\n\nA more subtle issue occurs with types that have methods and optional\ninterfaces. Consider the following:\n\n package main\n\n import \"fmt\"\n\n type Enum int\n\n func (e Enum) String() string {\n return \"an enum\"\n }\n\n const (\n EnumFirst Enum = 1\n EnumSecond = 2\n )\n\n func main() {\n fmt.Println(EnumFirst)\n fmt.Println(EnumSecond)\n }\n\nThis code will output\n\n an enum\n 2\n\nas EnumSecond has no explicit type, and thus defaults to int.\n\nAvailable since\n 2019.1\n", + "default": true + }, + "SA9005": { + "type": "boolean", + "markdownDescription": "Trying to marshal a struct with no public fields nor custom marshaling\n\nThe encoding/json and encoding/xml packages only operate on exported\nfields in structs, not unexported ones. It is usually an error to try\nto (un)marshal structs that only consist of unexported fields.\n\nThis check will not flag calls involving types that define custom\nmarshaling behavior, e.g. via MarshalJSON methods. It will also not\nflag empty structs.\n\nAvailable since\n 2019.2\n", + "default": false + }, + "SA9006": { + "type": "boolean", + "markdownDescription": "Dubious bit shifting of a fixed size integer value\n\nBit shifting a value past its size will always clear the value.\n\nFor instance:\n\n v := int8(42)\n v >>= 8\n\nwill always result in 0.\n\nThis check flags bit shifting operations on fixed size integer values only.\nThat is, int, uint and uintptr are never flagged to avoid potential false\npositives in somewhat exotic but valid bit twiddling tricks:\n\n // Clear any value above 32 bits if integers are more than 32 bits.\n func f(i int) int {\n v := i >> 32\n v = v << 32\n return i-v\n }\n\nAvailable since\n 2020.2\n", + "default": true + }, + "SA9007": { + "type": "boolean", + "markdownDescription": "Deleting a directory that shouldn't be deleted\n\nIt is virtually never correct to delete system directories such as\n/tmp or the user's home directory. However, it can be fairly easy to\ndo by mistake, for example by mistakenly using os.TempDir instead\nof ioutil.TempDir, or by forgetting to add a suffix to the result\nof os.UserHomeDir.\n\nWriting\n\n d := os.TempDir()\n defer os.RemoveAll(d)\n\nin your unit tests will have a devastating effect on the stability of your system.\n\nThis check flags attempts at deleting the following directories:\n\n- os.TempDir\n- os.UserCacheDir\n- os.UserConfigDir\n- os.UserHomeDir\n\nAvailable since\n 2022.1\n", + "default": false + }, + "SA9008": { + "type": "boolean", + "markdownDescription": "else branch of a type assertion is probably not reading the right value\n\nWhen declaring variables as part of an if statement (like in 'if\nfoo := ...; foo {'), the same variables will also be in the scope of\nthe else branch. This means that in the following example\n\n if x, ok := x.(int); ok {\n // ...\n } else {\n fmt.Printf(\"unexpected type %T\", x)\n }\n\nx in the else branch will refer to the x from x, ok\n:=; it will not refer to the x that is being type-asserted. The\nresult of a failed type assertion is the zero value of the type that\nis being asserted to, so x in the else branch will always have the\nvalue 0 and the type int.\n\nAvailable since\n 2022.1\n", + "default": false + }, + "SA9009": { + "type": "boolean", + "markdownDescription": "Ineffectual Go compiler directive\n\nA potential Go compiler directive was found, but is ineffectual as it begins\nwith whitespace.\n\nAvailable since\n 2024.1\n", + "default": true + }, + "ST1000": { + "type": "boolean", + "markdownDescription": "Incorrect or missing package comment\n\nPackages must have a package comment that is formatted according to\nthe guidelines laid out in\nhttps://go.dev/wiki/CodeReviewComments#package-comments.\n\nAvailable since\n 2019.1, non-default\n", + "default": false + }, + "ST1001": { + "type": "boolean", + "markdownDescription": "Dot imports are discouraged\n\nDot imports that aren't in external test packages are discouraged.\n\nThe dot_import_whitelist option can be used to whitelist certain\nimports.\n\nQuoting Go Code Review Comments:\n\n> The import . form can be useful in tests that, due to circular\n> dependencies, cannot be made part of the package being tested:\n> \n> package foo_test\n> \n> import (\n> \"bar/testutil\" // also imports \"foo\"\n> . \"foo\"\n> )\n> \n> In this case, the test file cannot be in package foo because it\n> uses bar/testutil, which imports foo. So we use the import .\n> form to let the file pretend to be part of package foo even though\n> it is not. Except for this one case, do not use import . in your\n> programs. It makes the programs much harder to read because it is\n> unclear whether a name like Quux is a top-level identifier in the\n> current package or in an imported package.\n\nAvailable since\n 2019.1\n\nOptions\n dot_import_whitelist\n", + "default": false + }, + "ST1003": { + "type": "boolean", + "markdownDescription": "Poorly chosen identifier\n\nIdentifiers, such as variable and package names, follow certain rules.\n\nSee the following links for details:\n\n- https://go.dev/doc/effective_go#package-names\n- https://go.dev/doc/effective_go#mixed-caps\n- https://go.dev/wiki/CodeReviewComments#initialisms\n- https://go.dev/wiki/CodeReviewComments#variable-names\n\nAvailable since\n 2019.1, non-default\n\nOptions\n initialisms\n", + "default": false + }, + "ST1005": { + "type": "boolean", + "markdownDescription": "Incorrectly formatted error string\n\nError strings follow a set of guidelines to ensure uniformity and good\ncomposability.\n\nQuoting Go Code Review Comments:\n\n> Error strings should not be capitalized (unless beginning with\n> proper nouns or acronyms) or end with punctuation, since they are\n> usually printed following other context. That is, use\n> fmt.Errorf(\"something bad\") not fmt.Errorf(\"Something bad\"), so\n> that log.Printf(\"Reading %s: %v\", filename, err) formats without a\n> spurious capital letter mid-message.\n\nAvailable since\n 2019.1\n", + "default": false + }, + "ST1006": { + "type": "boolean", + "markdownDescription": "Poorly chosen receiver name\n\nQuoting Go Code Review Comments:\n\n> The name of a method's receiver should be a reflection of its\n> identity; often a one or two letter abbreviation of its type\n> suffices (such as \"c\" or \"cl\" for \"Client\"). Don't use generic\n> names such as \"me\", \"this\" or \"self\", identifiers typical of\n> object-oriented languages that place more emphasis on methods as\n> opposed to functions. The name need not be as descriptive as that\n> of a method argument, as its role is obvious and serves no\n> documentary purpose. It can be very short as it will appear on\n> almost every line of every method of the type; familiarity admits\n> brevity. Be consistent, too: if you call the receiver \"c\" in one\n> method, don't call it \"cl\" in another.\n\nAvailable since\n 2019.1\n", + "default": false + }, + "ST1008": { + "type": "boolean", + "markdownDescription": "A function's error value should be its last return value\n\nA function's error value should be its last return value.\n\nAvailable since\n 2019.1\n", + "default": false + }, + "ST1011": { + "type": "boolean", + "markdownDescription": "Poorly chosen name for variable of type time.Duration\n\ntime.Duration values represent an amount of time, which is represented\nas a count of nanoseconds. An expression like 5 * time.Microsecond\nyields the value 5000. It is therefore not appropriate to suffix a\nvariable of type time.Duration with any time unit, such as Msec or\nMilli.\n\nAvailable since\n 2019.1\n", + "default": false + }, + "ST1012": { + "type": "boolean", + "markdownDescription": "Poorly chosen name for error variable\n\nError variables that are part of an API should be called errFoo or\nErrFoo.\n\nAvailable since\n 2019.1\n", + "default": false + }, + "ST1013": { + "type": "boolean", + "markdownDescription": "Should use constants for HTTP error codes, not magic numbers\n\nHTTP has a tremendous number of status codes. While some of those are\nwell known (200, 400, 404, 500), most of them are not. The net/http\npackage provides constants for all status codes that are part of the\nvarious specifications. It is recommended to use these constants\ninstead of hard-coding magic numbers, to vastly improve the\nreadability of your code.\n\nAvailable since\n 2019.1\n\nOptions\n http_status_code_whitelist\n", + "default": false + }, + "ST1015": { + "type": "boolean", + "markdownDescription": "A switch's default case should be the first or last case\n\nAvailable since\n 2019.1\n", + "default": false + }, + "ST1016": { + "type": "boolean", + "markdownDescription": "Use consistent method receiver names\n\nAvailable since\n 2019.1, non-default\n", + "default": false + }, + "ST1017": { + "type": "boolean", + "markdownDescription": "Don't use Yoda conditions\n\nYoda conditions are conditions of the kind 'if 42 == x', where the\nliteral is on the left side of the comparison. These are a common\nidiom in languages in which assignment is an expression, to avoid bugs\nof the kind 'if (x = 42)'. In Go, which doesn't allow for this kind of\nbug, we prefer the more idiomatic 'if x == 42'.\n\nAvailable since\n 2019.2\n", + "default": false + }, + "ST1018": { + "type": "boolean", + "markdownDescription": "Avoid zero-width and control characters in string literals\n\nAvailable since\n 2019.2\n", + "default": false + }, + "ST1019": { + "type": "boolean", + "markdownDescription": "Importing the same package multiple times\n\nGo allows importing the same package multiple times, as long as\ndifferent import aliases are being used. That is, the following\nbit of code is valid:\n\n import (\n \"fmt\"\n fumpt \"fmt\"\n format \"fmt\"\n _ \"fmt\"\n )\n\nHowever, this is very rarely done on purpose. Usually, it is a\nsign of code that got refactored, accidentally adding duplicate\nimport statements. It is also a rarely known feature, which may\ncontribute to confusion.\n\nDo note that sometimes, this feature may be used\nintentionally (see for example\nhttps://github.com/golang/go/commit/3409ce39bfd7584523b7a8c150a310cea92d879d)\n– if you want to allow this pattern in your code base, you're\nadvised to disable this check.\n\nAvailable since\n 2020.1\n", + "default": false + }, + "ST1020": { + "type": "boolean", + "markdownDescription": "The documentation of an exported function should start with the function's name\n\nDoc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the doc subcommand of the go tool and run the output\nthrough grep.\n\nSee https://go.dev/doc/effective_go#commentary for more\ninformation on how to write good documentation.\n\nAvailable since\n 2020.1, non-default\n", + "default": false + }, + "ST1021": { + "type": "boolean", + "markdownDescription": "The documentation of an exported type should start with type's name\n\nDoc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the doc subcommand of the go tool and run the output\nthrough grep.\n\nSee https://go.dev/doc/effective_go#commentary for more\ninformation on how to write good documentation.\n\nAvailable since\n 2020.1, non-default\n", + "default": false + }, + "ST1022": { + "type": "boolean", + "markdownDescription": "The documentation of an exported variable or constant should start with variable's name\n\nDoc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the doc subcommand of the go tool and run the output\nthrough grep.\n\nSee https://go.dev/doc/effective_go#commentary for more\ninformation on how to write good documentation.\n\nAvailable since\n 2020.1, non-default\n", + "default": false + }, + "ST1023": { + "type": "boolean", + "markdownDescription": "Redundant type in variable declaration\n\nAvailable since\n 2021.1, non-default\n", + "default": false + }, "appends": { "type": "boolean", "markdownDescription": "check for missing values after append\n\nThis checker reports calls to append that pass\nno values to be appended to the slice.\n\n\ts := []string{\"a\", \"b\", \"c\"}\n\t_ = append(s)\n\nSuch calls are always no-ops and often indicate an\nunderlying mistake.", @@ -2200,7 +2975,7 @@ }, "gofix": { "type": "boolean", - "markdownDescription": "apply fixes based on go:fix comment directives\n\nThe gofix analyzer inlines functions and constants that are marked for inlining.", + "markdownDescription": "apply fixes based on go:fix comment directives\n\nThe gofix analyzer inlines functions and constants that are marked for inlining.\n\n## Functions\n\nGiven a function that is marked for inlining, like this one:\n\n\t//go:fix inline\n\tfunc Square(x int) int { return Pow(x, 2) }\n\nthis analyzer will recommend that calls to the function elsewhere, in the same\nor other packages, should be inlined.\n\nInlining can be used to move off of a deprecated function:\n\n\t// Deprecated: prefer Pow(x, 2).\n\t//go:fix inline\n\tfunc Square(x int) int { return Pow(x, 2) }\n\nIt can also be used to move off of an obsolete package,\nas when the import path has changed or a higher major version is available:\n\n\tpackage pkg\n\n\timport pkg2 \"pkg/v2\"\n\n\t//go:fix inline\n\tfunc F() { pkg2.F(nil) }\n\nReplacing a call pkg.F() by pkg2.F(nil) can have no effect on the program,\nso this mechanism provides a low-risk way to update large numbers of calls.\nWe recommend, where possible, expressing the old API in terms of the new one\nto enable automatic migration.\n\nThe inliner takes care to avoid behavior changes, even subtle ones,\nsuch as changes to the order in which argument expressions are\nevaluated. When it cannot safely eliminate all parameter variables,\nit may introduce a \"binding declaration\" of the form\n\n\tvar params = args\n\nto evaluate argument expressions in the correct order and bind them to\nparameter variables. Since the resulting code transformation may be\nstylistically suboptimal, such inlinings may be disabled by specifying\nthe -gofix.allow_binding_decl=false flag to the analyzer driver.\n\n(In cases where it is not safe to \"reduce\" a call—that is, to replace\na call f(x) by the body of function f, suitably substituted—the\ninliner machinery is capable of replacing f by a function literal,\nfunc(){...}(). However, the gofix analyzer discards all such\n\"literalizations\" unconditionally, again on grounds of style.)\n\n## Constants\n\nGiven a constant that is marked for inlining, like this one:\n\n\t//go:fix inline\n\tconst Ptr = Pointer\n\nthis analyzer will recommend that uses of Ptr should be replaced with Pointer.\n\nAs with functions, inlining can be used to replace deprecated constants and\nconstants in obsolete packages.\n\nA constant definition can be marked for inlining only if it refers to another\nnamed constant.\n\nThe \"//go:fix inline\" comment must appear before a single const declaration on its own,\nas above; before a const declaration that is part of a group, as in this case:\n\n\tconst (\n\t C = 1\n\t //go:fix inline\n\t Ptr = Pointer\n\t)\n\nor before a group, applying to every constant in the group:\n\n\t//go:fix inline\n\tconst (\n\t\tPtr = Pointer\n\t Val = Value\n\t)\n\nThe proposal https://go.dev/issue/32816 introduces the \"//go:fix\" directives.\n\nYou can use this (officially unsupported) command to apply gofix fixes en masse:\n\n\t$ go run golang.org/x/tools/internal/gofix/cmd/gofix@latest -test ./...\n\n(Do not use \"go get -tool\" to add gopls as a dependency of your\nmodule; gopls commands must be built from their release branch.)", "default": true }, "hostport": { @@ -2233,9 +3008,14 @@ "markdownDescription": "check cancel func returned by context.WithCancel is called\n\nThe cancellation function returned by context.WithCancel, WithTimeout,\nWithDeadline and variants such as WithCancelCause must be called,\nor the new context will remain live until its parent context is cancelled.\n(The background context is never cancelled.)", "default": true }, + "maprange": { + "type": "boolean", + "markdownDescription": "checks for unnecessary calls to maps.Keys and maps.Values in range statements\n\nConsider a loop written like this:\n\n\tfor val := range maps.Values(m) {\n\t\tfmt.Println(val)\n\t}\n\nThis should instead be written without the call to maps.Values:\n\n\tfor _, val := range m {\n\t\tfmt.Println(val)\n\t}\n\ngolang.org/x/exp/maps returns slices for Keys/Values instead of iterators,\nbut unnecessary calls should similarly be removed:\n\n\tfor _, key := range maps.Keys(m) {\n\t\tfmt.Println(key)\n\t}\n\nshould be rewritten as:\n\n\tfor key := range m {\n\t\tfmt.Println(key)\n\t}", + "default": true + }, "modernize": { "type": "boolean", - "markdownDescription": "simplify code by using modern constructs\n\nThis analyzer reports opportunities for simplifying and clarifying\nexisting code by using more modern features of Go, such as:\n\n - replacing an if/else conditional assignment by a call to the\n built-in min or max functions added in go1.21;\n - replacing sort.Slice(x, func(i, j int) bool) { return s[i] < s[j] }\n by a call to slices.Sort(s), added in go1.21;\n - replacing interface{} by the 'any' type added in go1.18;\n - replacing append([]T(nil), s...) by slices.Clone(s) or\n slices.Concat(s), added in go1.21;\n - replacing a loop around an m[k]=v map update by a call\n to one of the Collect, Copy, Clone, or Insert functions\n from the maps package, added in go1.21;\n - replacing []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...),\n added in go1.19;\n - replacing uses of context.WithCancel in tests with t.Context, added in\n go1.24;\n - replacing omitempty by omitzero on structs, added in go1.24;\n - replacing append(s[:i], s[i+1]...) by slices.Delete(s, i, i+1),\n added in go1.21\n - replacing a 3-clause for i := 0; i < n; i++ {} loop by\n for i := range n {}, added in go1.22;\n - replacing Split in \"for range strings.Split(...)\" by go1.24's\n more efficient SplitSeq;\n\nTo apply all modernization fixes en masse, you can use the\nfollowing command:\n\n\t$ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test ./...\n\nIf the tool warns of conflicting fixes, you may need to run it more\nthan once until it has applied all fixes cleanly. This command is\nnot an officially supported interface and may change in the future.", + "markdownDescription": "simplify code by using modern constructs\n\nThis analyzer reports opportunities for simplifying and clarifying\nexisting code by using more modern features of Go and its standard\nlibrary.\n\nEach diagnostic provides a fix. Our intent is that these fixes may\nbe safely applied en masse without changing the behavior of your\nprogram. In some cases the suggested fixes are imperfect and may\nlead to (for example) unused imports or unused local variables,\ncausing build breakage. However, these problems are generally\ntrivial to fix. We regard any modernizer whose fix changes program\nbehavior to have a serious bug and will endeavor to fix it.\n\nTo apply all modernization fixes en masse, you can use the\nfollowing command:\n\n\t$ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...\n\n(Do not use \"go get -tool\" to add gopls as a dependency of your\nmodule; gopls commands must be built from their release branch.)\n\nIf the tool warns of conflicting fixes, you may need to run it more\nthan once until it has applied all fixes cleanly. This command is\nnot an officially supported interface and may change in the future.\n\nChanges produced by this tool should be reviewed as usual before\nbeing merged. In some cases, a loop may be replaced by a simple\nfunction call, causing comments within the loop to be discarded.\nHuman judgment may be required to avoid losing comments of value.\n\nEach diagnostic reported by modernize has a specific category. (The\ncategories are listed below.) Diagnostics in some categories, such\nas \"efaceany\" (which replaces \"interface{}\" with \"any\" where it is\nsafe to do so) are particularly numerous. It may ease the burden of\ncode review to apply fixes in two passes, the first change\nconsisting only of fixes of category \"efaceany\", the second\nconsisting of all others. This can be achieved using the -category flag:\n\n\t$ modernize -category=efaceany -fix -test ./...\n\t$ modernize -category=-efaceany -fix -test ./...\n\nCategories of modernize diagnostic:\n\n - forvar: remove x := x variable declarations made unnecessary by the new semantics of loops in go1.22.\n\n - slicescontains: replace 'for i, elem := range s { if elem == needle { ...; break }'\n by a call to slices.Contains, added in go1.21.\n\n - minmax: replace an if/else conditional assignment by a call to\n the built-in min or max functions added in go1.21.\n\n - sortslice: replace sort.Slice(x, func(i, j int) bool) { return s[i] < s[j] }\n by a call to slices.Sort(s), added in go1.21.\n\n - efaceany: replace interface{} by the 'any' type added in go1.18.\n\n - mapsloop: replace a loop around an m[k]=v map update by a call\n to one of the Collect, Copy, Clone, or Insert functions from\n the maps package, added in go1.21.\n\n - fmtappendf: replace []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...),\n added in go1.19.\n\n - testingcontext: replace uses of context.WithCancel in tests\n with t.Context, added in go1.24.\n\n - omitzero: replace omitempty by omitzero on structs, added in go1.24.\n\n - bloop: replace \"for i := range b.N\" or \"for range b.N\" in a\n benchmark with \"for b.Loop()\", and remove any preceding calls\n to b.StopTimer, b.StartTimer, and b.ResetTimer.\n\n B.Loop intentionally defeats compiler optimizations such as\n inlining so that the benchmark is not entirely optimized away.\n Currently, however, it may cause benchmarks to become slower\n in some cases due to increased allocation; see\n https://go.dev/issue/73137.\n\n - rangeint: replace a 3-clause \"for i := 0; i < n; i++\" loop by\n \"for i := range n\", added in go1.22.\n\n - stringsseq: replace Split in \"for range strings.Split(...)\" by go1.24's\n more efficient SplitSeq, or Fields with FieldSeq.\n\n - stringscutprefix: replace some uses of HasPrefix followed by TrimPrefix with CutPrefix,\n added to the strings package in go1.20.\n\n - waitgroup: replace old complex usages of sync.WaitGroup by less complex WaitGroup.Go method in go1.25.", "default": true }, "nilfunc": { @@ -2263,6 +3043,11 @@ "markdownDescription": "check consistency of Printf format strings and arguments\n\nThe check applies to calls of the formatting functions such as\n[fmt.Printf] and [fmt.Sprintf], as well as any detected wrappers of\nthose functions such as [log.Printf]. It reports a variety of\nmistakes such as syntax errors in the format string and mismatches\n(of number and type) between the verbs and their arguments.\n\nSee the documentation of the fmt package for the complete set of\nformat operators and their operand types.", "default": true }, + "recursiveiter": { + "type": "boolean", + "markdownDescription": "check for inefficient recursive iterators\n\nThis analyzer reports when a function that returns an iterator\n(iter.Seq or iter.Seq2) calls itself as the operand of a range\nstatement, as this is inefficient.\n\nWhen implementing an iterator (e.g. iter.Seq[T]) for a recursive\ndata type such as a tree or linked list, it is tempting to\nrecursively range over the iterator for each child element.\n\nHere's an example of a naive iterator over a binary tree:\n\n\ttype tree struct {\n\t\tvalue int\n\t\tleft, right *tree\n\t}\n\n\tfunc (t *tree) All() iter.Seq[int] {\n\t\treturn func(yield func(int) bool) {\n\t\t\tif t != nil {\n\t\t\t\tfor elem := range t.left.All() { // \"inefficient recursive iterator\"\n\t\t\t\t\tif !yield(elem) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !yield(t.value) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tfor elem := range t.right.All() { // \"inefficient recursive iterator\"\n\t\t\t\t\tif !yield(elem) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\nThough it correctly enumerates the elements of the tree, it hides a\nsignificant performance problem--two, in fact. Consider a balanced\ntree of N nodes. Iterating the root node will cause All to be\ncalled once on every node of the tree. This results in a chain of\nnested active range-over-func statements when yield(t.value) is\ncalled on a leaf node.\n\nThe first performance problem is that each range-over-func\nstatement must typically heap-allocate a variable, so iteration of\nthe tree allocates as many variables as there are elements in the\ntree, for a total of O(N) allocations, all unnecessary.\n\nThe second problem is that each call to yield for a leaf of the\ntree causes each of the enclosing range loops to receive a value,\nwhich they then immediately pass on to their respective yield\nfunction. This results in a chain of log(N) dynamic yield calls per\nelement, a total of O(N*log N) dynamic calls overall, when only\nO(N) are necessary.\n\nA better implementation strategy for recursive iterators is to\nfirst define the \"every\" operator for your recursive data type,\nwhere every(f) reports whether f(x) is true for every element x in\nthe data type. For our tree, the every function would be:\n\n\tfunc (t *tree) every(f func(int) bool) bool {\n\t\treturn t == nil ||\n\t\t\tt.left.every(f) && f(t.value) && t.right.every(f)\n\t}\n\nThen the iterator can be simply expressed as a trivial wrapper\naround this function:\n\n\tfunc (t *tree) All() iter.Seq[int] {\n\t\treturn func(yield func(int) bool) {\n\t\t\t_ = t.every(yield)\n\t\t}\n\t}\n\nIn effect, tree.All computes whether yield returns true for each\nelement, short-circuiting if it every returns false, then discards\nthe final boolean result.\n\nThis has much better performance characteristics: it makes one\ndynamic call per element of the tree, and it doesn't heap-allocate\nanything. It is also clearer.", + "default": true + }, "shadow": { "type": "boolean", "markdownDescription": "check for possible unintended shadowing of variables\n\nThis analyzer check for shadowed variables.\nA shadowed variable is a variable declared in an inner scope\nwith the same name and type as a variable in an outer scope,\nand where the outer variable is mentioned after the inner one\nis declared.\n\n(This definition can be refined; the module generates too many\nfalse positives and is not yet enabled by default.)\n\nFor example:\n\n\tfunc BadRead(f *os.File, buf []byte) error {\n\t\tvar err error\n\t\tfor {\n\t\t\tn, err := f.Read(buf) // shadows the function variable 'err'\n\t\t\tif err != nil {\n\t\t\t\tbreak // causes return of wrong value\n\t\t\t}\n\t\t\tfoo(buf)\n\t\t}\n\t\treturn err\n\t}", @@ -2396,6 +3181,33 @@ "default": true, "scope": "resource" }, + "ui.diagnostic.annotations": { + "type": "object", + "markdownDescription": "annotations specifies the various kinds of compiler\noptimization details that should be reported as diagnostics\nwhen enabled for a package by the \"Toggle compiler\noptimization details\" (`gopls.gc_details`) command.\n\n(Some users care only about one kind of annotation in their\nprofiling efforts. More importantly, in large packages, the\nnumber of annotations can sometimes overwhelm the user\ninterface and exceed the per-file diagnostic limit.)\n\nTODO(adonovan): rename this field to CompilerOptDetail.\n", + "scope": "resource", + "properties": { + "bounds": { + "type": "boolean", + "markdownDescription": "`\"bounds\"` controls bounds checking diagnostics.\n", + "default": true + }, + "escape": { + "type": "boolean", + "markdownDescription": "`\"escape\"` controls diagnostics about escape choices.\n", + "default": true + }, + "inline": { + "type": "boolean", + "markdownDescription": "`\"inline\"` controls diagnostics about inlining choices.\n", + "default": true + }, + "nil": { + "type": "boolean", + "markdownDescription": "`\"nil\"` controls nil checks.\n", + "default": true + } + } + }, "ui.diagnostic.diagnosticsDelay": { "type": "string", "markdownDescription": "(Advanced) diagnosticsDelay controls the amount of time that gopls waits\nafter the most recent file modification before computing deep diagnostics.\nSimple diagnostics (parsing and type-checking) are always run immediately\non recently modified packages.\n\nThis option must be set to a valid duration string, for example `\"250ms\"`.\n", @@ -2418,7 +3230,13 @@ }, "ui.diagnostic.staticcheck": { "type": "boolean", - "markdownDescription": "(Experimental) staticcheck enables additional analyses from staticcheck.io.\nThese analyses are documented on\n[Staticcheck's website](https://staticcheck.io/docs/checks/).\n", + "markdownDescription": "(Experimental) staticcheck configures the default set of analyses staticcheck.io.\nThese analyses are documented on\n[Staticcheck's website](https://staticcheck.io/docs/checks/).\n\nThe \"staticcheck\" option has three values:\n- false: disable all staticcheck analyzers\n- true: enable all staticcheck analyzers\n- unset: enable a subset of staticcheck analyzers\n selected by gopls maintainers for runtime efficiency\n and analytic precision.\n\nRegardless of this setting, individual analyzers can be\nselectively enabled or disabled using the `analyses` setting.\n", + "default": false, + "scope": "resource" + }, + "ui.diagnostic.staticcheckProvided": { + "type": "boolean", + "markdownDescription": "(Experimental) ", "default": false, "scope": "resource" }, diff --git a/extension/src/goToolsInformation.ts b/extension/src/goToolsInformation.ts index f4c81b363a..2a0ee8b066 100644 --- a/extension/src/goToolsInformation.ts +++ b/extension/src/goToolsInformation.ts @@ -9,7 +9,7 @@ export const allToolsInformation: { [key: string]: Tool } = { name: 'gomodifytags', importPath: 'github.com/fatih/gomodifytags', modulePath: 'github.com/fatih/gomodifytags', - replacedByGopls: true, + replacedByGopls: false, isImportant: false, description: 'Modify tags on structs', defaultVersion: 'v1.17.0' From c5eb5221371ff927679f8ad7d7a63dcd0e2fb367 Mon Sep 17 00:00:00 2001 From: Hongxiang Jiang Date: Wed, 4 Jun 2025 15:57:10 -0400 Subject: [PATCH 31/39] extension/tools: mark gomodifytags replaced by gopls The source of truth of tool config is extension/tools/allTools.ts.in. The x/build triggers re-generate everytime a gopls get released, so the bit got clipped by CL 678735. Change-Id: I94ba9f307b448baa900f7acb633c8e8e130f69ef Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/678916 Reviewed-by: Madeline Kalil kokoro-CI: kokoro LUCI-TryBot-Result: Go LUCI Reviewed-by: Alan Donovan Auto-Submit: Hongxiang Jiang --- extension/src/goToolsInformation.ts | 2 +- extension/tools/allTools.ts.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/src/goToolsInformation.ts b/extension/src/goToolsInformation.ts index 2a0ee8b066..f4c81b363a 100644 --- a/extension/src/goToolsInformation.ts +++ b/extension/src/goToolsInformation.ts @@ -9,7 +9,7 @@ export const allToolsInformation: { [key: string]: Tool } = { name: 'gomodifytags', importPath: 'github.com/fatih/gomodifytags', modulePath: 'github.com/fatih/gomodifytags', - replacedByGopls: false, + replacedByGopls: true, isImportant: false, description: 'Modify tags on structs', defaultVersion: 'v1.17.0' diff --git a/extension/tools/allTools.ts.in b/extension/tools/allTools.ts.in index e97db3a599..186ecdd1bb 100644 --- a/extension/tools/allTools.ts.in +++ b/extension/tools/allTools.ts.in @@ -7,7 +7,7 @@ export const allToolsInformation: { [key: string]: Tool } = { name: 'gomodifytags', importPath: 'github.com/fatih/gomodifytags', modulePath: 'github.com/fatih/gomodifytags', - replacedByGopls: false, + replacedByGopls: true, isImportant: false, description: 'Modify tags on structs', defaultVersion: 'v1.17.0' From 707321cc00017eba82b3f5c1f843a3def3d25af7 Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Thu, 5 Jun 2025 10:14:13 -0700 Subject: [PATCH 32/39] CHANGELOG.md: add release heading for v0.47.3 This is an automated CL which updates the CHANGELOG.md. Change-Id: Ibab5c79cc1b9778491a7829e0296d8d754f65b64 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/679215 Auto-Submit: Gopher Robot kokoro-CI: kokoro Reviewed-by: Madeline Kalil LUCI-TryBot-Result: Go LUCI Reviewed-by: Hongxiang Jiang --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14f263077d..ec25a3e2b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,15 @@ error during command execution when the command result did not include a token. ([Issue 3698](https://github.com/golang/vscode-go/issues/3698)) * Addressed an issue that broke the `Debug Subtest At Cursor` command. ([Issue 3718](https://github.com/golang/vscode-go/issues/3718)) +## v0.47.3 (prerelease) + +Date: 2025-06-05 + +This is the [pre-release version](https://code.visualstudio.com/api/working-with-extensions/publishing-extension#prerelease-extensions) of v0.48. + +**Full Changelog**: https://github.com/golang/vscode-go/compare/v0.46.0-rc.1...v0.47.3 +**Milestone**: https://github.com/golang/vscode-go/issues?q=milestone%3Av0.48.0 + ## v0.47.2 (prerelease) Date: 2025-04-15 From 51b64ed89ae511a01b083f039219204379fe4808 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Tue, 10 Jun 2025 12:32:19 -0400 Subject: [PATCH 33/39] extension/package.json: treat *.s files as Go Assembly, language go.asm This enables "Go to definition" on symbols in Go assembly files. Updates golang/go#71754 Change-Id: I86d08e0cbc2ed91afcfac02c09c0792266ab3b00 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/680515 Auto-Submit: Alan Donovan kokoro-CI: kokoro Reviewed-by: Hongxiang Jiang LUCI-TryBot-Result: Go LUCI --- extension/package.json | 10 ++++++++++ extension/src/goMode.ts | 1 + 2 files changed, 11 insertions(+) diff --git a/extension/package.json b/extension/package.json index 57d9a5e31c..0d82957105 100644 --- a/extension/package.json +++ b/extension/package.json @@ -98,6 +98,7 @@ "onLanguage:go", "onLanguage:go.sum", "onLanguage:gotmpl", + "onLanguage:go.asm", "onDebugInitialConfigurations", "onDebugResolve:go", "onWebviewPanel:welcomeGo" @@ -129,6 +130,15 @@ "Go" ] }, + { + "id": "go.asm", + "extensions": [ + ".s" + ], + "aliases": [ + "Go Assembly" + ] + }, { "id": "go.mod", "filenames": [ diff --git a/extension/src/goMode.ts b/extension/src/goMode.ts index 399258487a..43c3a766c3 100644 --- a/extension/src/goMode.ts +++ b/extension/src/goMode.ts @@ -21,6 +21,7 @@ export function isGoFile(document: vscode.TextDocument): boolean { export const GoDocumentSelector = [ // gopls handles only file URIs. { language: 'go', scheme: 'file' }, + { language: 'go.asm', scheme: 'file' }, { language: 'go.mod', scheme: 'file' }, { language: 'go.sum', scheme: 'file' }, { language: 'go.work', scheme: 'file' }, From ea77a5681128053c56e33e788c5b9f386fca70b0 Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Mon, 16 Jun 2025 14:25:19 -0700 Subject: [PATCH 34/39] extension: update gopls v0.19.0 settings This is an automated CL which updates the gopls version and settings. For golang/go#73965 Change-Id: I47fd42470c9eed7a8f3f97026b023d842081d553 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/682096 Reviewed-by: Alan Donovan kokoro-CI: kokoro Auto-Submit: Gopher Robot LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley --- extension/src/goToolsInformation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/src/goToolsInformation.ts b/extension/src/goToolsInformation.ts index f4c81b363a..76c42ea6d1 100644 --- a/extension/src/goToolsInformation.ts +++ b/extension/src/goToolsInformation.ts @@ -111,8 +111,8 @@ export const allToolsInformation: { [key: string]: Tool } = { description: 'Language Server from Google', usePrereleaseInPreviewMode: true, minimumGoVersion: semver.coerce('1.19'), - latestVersion: semver.parse('v0.18.1'), - latestVersionTimestamp: moment('2025-02-24', 'YYYY-MM-DD') + latestVersion: semver.parse('v0.19.0'), + latestVersionTimestamp: moment('2025-06-10', 'YYYY-MM-DD') }, 'dlv': { name: 'dlv', From b81dc3d8d5745217b739fb3d4b778c38bba7aba7 Mon Sep 17 00:00:00 2001 From: Takuto Nagami Date: Sun, 22 Jun 2025 20:57:00 +0900 Subject: [PATCH 35/39] extension/src/goLint: adding .exe extension to the binary name on Windows This change ensures that the tools with major versions higher than v2 can be properly installed on Windows systems. An error occurred in the binary copying process due to the missing .exe extension. Fixes golang/vscode-go#3777 Change-Id: Iecd1d419bf87be622986ac1d023274e9914992c5 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/683195 Reviewed-by: Madeline Kalil Reviewed-by: Hongxiang Jiang kokoro-CI: kokoro LUCI-TryBot-Result: Go LUCI --- extension/src/goInstallTools.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/extension/src/goInstallTools.ts b/extension/src/goInstallTools.ts index 02499c5afa..00050e4823 100644 --- a/extension/src/goInstallTools.ts +++ b/extension/src/goInstallTools.ts @@ -255,8 +255,10 @@ export async function installTools( // v2, v3... of the tools are installed with the same name as v1, // but must be able to co-exist with other major versions in the GOBIN. // Thus, we install it in a tmp directory and copy it to the GOBIN. - // See detail: golang/vscode-go#3732 - if (tool.name.match('-v\\d+$')) { + // See detail: https://github.com/golang/vscode-go/issues/3732#issuecomment-2752026894 + const isUpgradedMajorVersion = tool.name.match('-v\\d+$'); + + if (isUpgradedMajorVersion) { envForTools['GOBIN'] = path.join(installingPath, 'tmp'); } @@ -268,7 +270,7 @@ export async function installTools( vscode.commands.executeCommand('go.languageserver.restart', RestartReason.INSTALLATION); } - if (tool.name.match('-v\\d+$')) { + if (isUpgradedMajorVersion) { // grep the tool name without version. const toolName = tool.name.match('^(?.+)-v\\d+$')?.groups?.tool; if (!toolName) { @@ -276,7 +278,10 @@ export async function installTools( continue; } - fs.copyFileSync(path.join(installingPath, 'tmp', toolName), path.join(installingPath, tool.name)); + fs.copyFileSync( + path.join(installingPath, 'tmp', correctBinname(toolName)), + path.join(installingPath, correctBinname(tool.name)) + ); fs.rmdirSync(path.join(installingPath, 'tmp'), { recursive: true }); } } From 1729b341d487c97251ec8babee342bd5c5034c17 Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Thu, 19 Jun 2025 08:31:12 -0700 Subject: [PATCH 36/39] extension: update gopls v0.19.1 settings This is an automated CL which updates the gopls version and settings. For golang/go#74297 Change-Id: I57d9229383062ef88fac8752f5c1b2c406a6d80c Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/682835 Reviewed-by: Alan Donovan LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley kokoro-CI: kokoro Auto-Submit: Gopher Robot --- extension/src/goToolsInformation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/src/goToolsInformation.ts b/extension/src/goToolsInformation.ts index 76c42ea6d1..1c1f19d577 100644 --- a/extension/src/goToolsInformation.ts +++ b/extension/src/goToolsInformation.ts @@ -111,8 +111,8 @@ export const allToolsInformation: { [key: string]: Tool } = { description: 'Language Server from Google', usePrereleaseInPreviewMode: true, minimumGoVersion: semver.coerce('1.19'), - latestVersion: semver.parse('v0.19.0'), - latestVersionTimestamp: moment('2025-06-10', 'YYYY-MM-DD') + latestVersion: semver.parse('v0.19.1'), + latestVersionTimestamp: moment('2025-06-18', 'YYYY-MM-DD') }, 'dlv': { name: 'dlv', From d47e01a89b1028c6342f5c8e0bfa5a122e449017 Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Mon, 23 Jun 2025 10:21:02 -0700 Subject: [PATCH 37/39] CHANGELOG.md: add release heading for v0.47.4 This is an automated CL which updates the CHANGELOG.md. Change-Id: I5f0ee5071a96889344846990344735d7b48048d7 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/683415 LUCI-TryBot-Result: Go LUCI Auto-Submit: Gopher Robot Reviewed-by: Dmitri Shuralyov kokoro-CI: kokoro Reviewed-by: Hongxiang Jiang --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec25a3e2b1..148a7523ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,15 @@ error during command execution when the command result did not include a token. ([Issue 3698](https://github.com/golang/vscode-go/issues/3698)) * Addressed an issue that broke the `Debug Subtest At Cursor` command. ([Issue 3718](https://github.com/golang/vscode-go/issues/3718)) +## v0.47.4 (prerelease) + +Date: 2025-06-23 + +This is the [pre-release version](https://code.visualstudio.com/api/working-with-extensions/publishing-extension#prerelease-extensions) of v0.48. + +**Full Changelog**: https://github.com/golang/vscode-go/compare/v0.46.0-rc.1...v0.47.4 +**Milestone**: https://github.com/golang/vscode-go/issues?q=milestone%3Av0.48.0 + ## v0.47.3 (prerelease) Date: 2025-06-05 From 09dcadacf0311d3b519c5a143cdfddb6b7c56359 Mon Sep 17 00:00:00 2001 From: Hongxiang Jiang Date: Fri, 27 Jun 2025 03:41:08 +0000 Subject: [PATCH 38/39] [release-v0.48]extension/test/integration: update debug config for remote mode CL 643280 changed the behavior of debug adapter in remote mode from always "legacy" to the result from command `dlv substitute-path-guess-helper`. This CL calls the method to find the right return value and test the debug adapter with the command return. In master branch, the integration test is running against preview version of extension, so the debug adapter is always "dlv-dap" regardless of the command return. So this test is not captured in the master branch. Change-Id: I5ce6e1040fa16f10b038265d7e0ab7a884a99141 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/684475 kokoro-CI: kokoro Reviewed-by: Robert Findley Reviewed-by: Madeline Kalil LUCI-TryBot-Result: Go LUCI --- extension/src/goDebugConfiguration.ts | 4 +++- extension/test/integration/goDebugConfiguration.test.ts | 9 +++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/extension/src/goDebugConfiguration.ts b/extension/src/goDebugConfiguration.ts index 94c7e12d9c..243a4a29a3 100644 --- a/extension/src/goDebugConfiguration.ts +++ b/extension/src/goDebugConfiguration.ts @@ -387,9 +387,11 @@ export class GoDebugConfigurationProvider implements vscode.DebugConfigurationPr /** * Calls `dlv substitute-path-guess-helper` to get a set of parameters used by Delve to guess the substitutePath configuration after also examining the executable. * + * Exported for testing. + * * See https://github.com/go-delve/delve/blob/d5fb3bee427202f0d4b1683bf743bfd2adb41757/service/debugger/debugger.go#L2466 */ - private async guessSubstitutePath(): Promise { + public async guessSubstitutePath(): Promise { return new Promise((resolve) => { const child = spawn(getBinPath('dlv'), ['substitute-path-guess-helper']); let stdoutData = ''; diff --git a/extension/test/integration/goDebugConfiguration.test.ts b/extension/test/integration/goDebugConfiguration.test.ts index 877e72cf01..6141764f0c 100644 --- a/extension/test/integration/goDebugConfiguration.test.ts +++ b/extension/test/integration/goDebugConfiguration.test.ts @@ -979,7 +979,7 @@ suite('Debug Configuration Default DebugAdapter', () => { assert.strictEqual(resolvedConfig['debugAdapter'], 'dlv-dap'); }); - test("default debugAdapter for remote mode should be 'legacy' when not in Preview mode", async () => { + test('default debugAdapter for remote mode should be determined by `dlv substitute-path-guess-helper`', async () => { const config = { name: 'Attach', type: 'go', @@ -989,9 +989,14 @@ suite('Debug Configuration Default DebugAdapter', () => { cwd: '/path' }; - const want = extensionInfo.isPreview ? 'dlv-dap' : 'legacy'; await debugConfigProvider.resolveDebugConfiguration(undefined, config); const resolvedConfig = config as any; + + const substitutePathGuess = await debugConfigProvider.guessSubstitutePath(); + let want = 'dlv-dap'; + if (substitutePathGuess === null) { + want = 'legacy'; + } assert.strictEqual(resolvedConfig['debugAdapter'], want); }); From 93482a43028147395301d710429902161658a264 Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Thu, 26 Jun 2025 11:54:22 -0700 Subject: [PATCH 39/39] [release-v0.48]extension/package.json: update version to 0.48.0 This is an automated CL which updates the package.json and package-lock.json. Change-Id: Ic886e4815b5f58529f822efad82d808347b4caa0 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/684136 kokoro-CI: kokoro Reviewed-by: Madeline Kalil Reviewed-by: Hongxiang Jiang LUCI-TryBot-Result: Go LUCI Auto-Submit: Hongxiang Jiang --- extension/package-lock.json | 4 ++-- extension/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extension/package-lock.json b/extension/package-lock.json index 8486f764ee..2d885cc49f 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "go", - "version": "0.48.0-dev", + "version": "0.48.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "go", - "version": "0.48.0-dev", + "version": "0.48.0", "license": "MIT", "dependencies": { "diff": "4.0.2", diff --git a/extension/package.json b/extension/package.json index 0d82957105..27f705f17b 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1,7 +1,7 @@ { "name": "go", "displayName": "Go", - "version": "0.48.0-dev", + "version": "0.48.0", "publisher": "golang", "description": "Rich Go language support for Visual Studio Code", "author": { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy