Skip to content

Commit b3c629f

Browse files
authored
Merge pull request #2 from GPTI314/claude/review-enhance-optimization-017dXcB4PEKc8xx7PtGYq5nm
Claude/review enhance optimization 017d xc b4 pe kc8xx7 pt g yq5nm
2 parents d83ccda + de666ed commit b3c629f

File tree

3 files changed

+194
-20
lines changed

3 files changed

+194
-20
lines changed

extension/src/goMain.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,29 @@ interface ExtensionTestAPI {
8383
globalState: vscode.Memento;
8484
}
8585

86+
/**
87+
* Extension activation entry point called by VS Code when the Go extension loads.
88+
* This is the main initialization function that sets up all Go development features.
89+
*
90+
* Activation sequence:
91+
* 1. Initialize global and workspace state
92+
* 2. Configure GOROOT and environment variables
93+
* 3. Build and install vscgo helper tool
94+
* 4. Register all extension commands
95+
* 5. Set up language features (code lens, formatting, testing, etc.)
96+
* 6. Start gopls language server (if enabled)
97+
* 7. Install missing/outdated Go tools
98+
* 8. Configure telemetry and surveys
99+
*
100+
* @param ctx - VS Code extension context providing subscriptions, storage, and paths
101+
* @returns ExtensionAPI for production use or ExtensionTestAPI for testing
102+
*
103+
* @example
104+
* // Called automatically by VS Code, not by user code
105+
* // Returns API that can be accessed by other extensions via:
106+
* const goExt = vscode.extensions.getExtension('golang.go');
107+
* const api = await goExt?.activate();
108+
*/
86109
export async function activate(ctx: vscode.ExtensionContext): Promise<ExtensionAPI | ExtensionTestAPI | undefined> {
87110
if (process.env['VSCODE_GO_IN_TEST'] === '1') {
88111
// TODO: VSCODE_GO_IN_TEST was introduced long before we learned about
@@ -240,6 +263,26 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<ExtensionA
240263
return extensionAPI;
241264
}
242265

266+
/**
267+
* Extension deactivation called by VS Code when the extension is being unloaded.
268+
* Performs cleanup to ensure no resources are leaked and all background processes are stopped.
269+
*
270+
* Cleanup operations (all run in parallel):
271+
* - Stop gopls language server and wait for graceful shutdown
272+
* - Cancel any running test sessions
273+
* - Kill any active pprof profiling processes
274+
* - Clean up temporary directories and files
275+
* - Dispose status bar items
276+
* - Flush and dispose telemetry reporter
277+
*
278+
* @returns Promise that resolves when all cleanup is complete
279+
*
280+
* @example
281+
* // Called automatically by VS Code when:
282+
* // - Extension is disabled
283+
* // - Extension is being updated
284+
* // - VS Code is shutting down
285+
*/
243286
export function deactivate() {
244287
return Promise.all([
245288
goCtx.languageClient?.stop(),

extension/src/language/goLanguageServer.ts

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export interface LanguageServerConfig {
8585
modtime?: Date;
8686
enabled: boolean;
8787
flags: string[];
88-
env: any;
88+
env: NodeJS.ProcessEnv;
8989
features: {
9090
// A custom formatter can be configured to run instead of gopls.
9191
// This is enabled when the user has configured a specific format
@@ -95,6 +95,12 @@ export interface LanguageServerConfig {
9595
checkForUpdates: string;
9696
}
9797

98+
/**
99+
* Represents a configuration object for gopls or Go settings.
100+
* This is a flexible object that can contain any configuration key-value pairs.
101+
*/
102+
export type ConfigurationObject = { [key: string]: unknown };
103+
98104
export interface ServerInfo {
99105
Name: string;
100106
Version?: string;
@@ -382,8 +388,27 @@ type VulncheckEvent = {
382388
message?: string;
383389
};
384390

385-
// buildLanguageClient returns a language client built using the given language server config.
386-
// The returned language client need to be started before use.
391+
/**
392+
* Builds a VS Code Language Server Protocol (LSP) client for gopls.
393+
* The returned client is configured but not started - caller must call .start() on it.
394+
*
395+
* This function sets up:
396+
* - Output channels for server logs and traces
397+
* - Language server executable options (path, flags, environment)
398+
* - Client options (document selectors, middleware, synchronization)
399+
* - Progress handlers for gopls operations
400+
* - Command execution middleware
401+
* - Configuration synchronization with gopls
402+
*
403+
* @param goCtx - Go extension context containing output channels and state
404+
* @param cfg - Language server configuration with path, flags, and features
405+
* @returns Configured GoLanguageClient instance ready to be started
406+
*
407+
* @example
408+
* const cfg = await buildLanguageServerConfig(getGoConfig());
409+
* const client = await buildLanguageClient(goCtx, cfg);
410+
* await client.start(); // Start the language server
411+
*/
387412
export async function buildLanguageClient(
388413
goCtx: GoExtensionContext,
389414
cfg: LanguageServerConfig
@@ -408,7 +433,7 @@ export async function buildLanguageClient(
408433

409434
// TODO(hxjiang): deprecate special handling for async call gopls.run_govulncheck.
410435
let govulncheckTerminal: IProgressTerminal | undefined;
411-
const pendingVulncheckProgressToken = new Map<ProgressToken, any>();
436+
const pendingVulncheckProgressToken = new Map<ProgressToken, { URI: string }>();
412437
const onDidChangeVulncheckResultEmitter = new vscode.EventEmitter<VulncheckEvent>();
413438

414439
// VSCode-Go prepares the information needed to start the language server.
@@ -850,12 +875,12 @@ export async function buildLanguageClient(
850875
// and selects only those the user explicitly specifies in their settings.
851876
// This returns a new object created based on the filtered properties of workspaceConfig.
852877
// Exported for testing.
853-
export function filterGoplsDefaultConfigValues(workspaceConfig: any, resource?: vscode.Uri): any {
878+
export function filterGoplsDefaultConfigValues(workspaceConfig: ConfigurationObject, resource?: vscode.Uri): ConfigurationObject {
854879
if (!workspaceConfig) {
855880
workspaceConfig = {};
856881
}
857882
const cfg = getGoplsConfig(resource);
858-
const filtered = {} as { [key: string]: any };
883+
const filtered: ConfigurationObject = {};
859884
for (const [key, value] of Object.entries(workspaceConfig)) {
860885
if (typeof value === 'function') {
861886
continue;
@@ -888,7 +913,7 @@ export function filterGoplsDefaultConfigValues(workspaceConfig: any, resource?:
888913
// - go.buildTags and go.buildFlags are passed as gopls.build.buildFlags
889914
// if goplsWorkspaceConfig doesn't explicitly set it yet.
890915
// Exported for testing.
891-
export function passGoConfigToGoplsConfigValues(goplsWorkspaceConfig: any, goWorkspaceConfig: any): any {
916+
export function passGoConfigToGoplsConfigValues(goplsWorkspaceConfig: ConfigurationObject, goWorkspaceConfig: ConfigurationObject): ConfigurationObject {
892917
if (!goplsWorkspaceConfig) {
893918
goplsWorkspaceConfig = {};
894919
}
@@ -913,10 +938,10 @@ export function passGoConfigToGoplsConfigValues(goplsWorkspaceConfig: any, goWor
913938
// If this is for the nightly extension, we also request to activate features under experiments.
914939
async function adjustGoplsWorkspaceConfiguration(
915940
cfg: LanguageServerConfig,
916-
workspaceConfig: any,
941+
workspaceConfig: ConfigurationObject,
917942
section?: string,
918943
resource?: vscode.Uri
919-
): Promise<any> {
944+
): Promise<ConfigurationObject> {
920945
// We process only gopls config
921946
if (section !== 'gopls') {
922947
return workspaceConfig;
@@ -940,7 +965,7 @@ async function adjustGoplsWorkspaceConfiguration(
940965
return workspaceConfig;
941966
}
942967

943-
async function passInlayHintConfigToGopls(cfg: LanguageServerConfig, goplsConfig: any, goConfig: any) {
968+
async function passInlayHintConfigToGopls(cfg: LanguageServerConfig, goplsConfig: ConfigurationObject, goConfig: vscode.WorkspaceConfiguration): Promise<ConfigurationObject> {
944969
const goplsVersion = await getLocalGoplsVersion(cfg);
945970
if (!goplsVersion) return goplsConfig ?? {};
946971
const version = semver.parse(goplsVersion.version);
@@ -953,7 +978,7 @@ async function passInlayHintConfigToGopls(cfg: LanguageServerConfig, goplsConfig
953978
return goplsConfig;
954979
}
955980

956-
async function passVulncheckConfigToGopls(cfg: LanguageServerConfig, goplsConfig: any, goConfig: any) {
981+
async function passVulncheckConfigToGopls(cfg: LanguageServerConfig, goplsConfig: ConfigurationObject, goConfig: vscode.WorkspaceConfiguration): Promise<ConfigurationObject> {
957982
const goplsVersion = await getLocalGoplsVersion(cfg);
958983
if (!goplsVersion) return goplsConfig ?? {};
959984
const version = semver.parse(goplsVersion.version);
@@ -966,7 +991,7 @@ async function passVulncheckConfigToGopls(cfg: LanguageServerConfig, goplsConfig
966991
return goplsConfig;
967992
}
968993

969-
async function passLinkifyShowMessageToGopls(cfg: LanguageServerConfig, goplsConfig: any) {
994+
async function passLinkifyShowMessageToGopls(cfg: LanguageServerConfig, goplsConfig: ConfigurationObject): Promise<ConfigurationObject> {
970995
goplsConfig = goplsConfig ?? {};
971996

972997
const goplsVersion = await getLocalGoplsVersion(cfg);
@@ -1025,6 +1050,29 @@ function createBenchmarkCodeLens(lens: vscode.CodeLens): vscode.CodeLens[] {
10251050
];
10261051
}
10271052

1053+
/**
1054+
* Builds the configuration object for the Go language server (gopls).
1055+
* This function locates gopls, validates it exists, and constructs the config needed to start it.
1056+
*
1057+
* Configuration includes:
1058+
* - Server executable path (from go.languageServerPath setting or auto-detected)
1059+
* - Server command-line flags (from go.languageServerFlags)
1060+
* - Environment variables for gopls process
1061+
* - Custom formatter (if go.formatTool is set to override gopls formatting)
1062+
* - Update check preferences (from go.toolsManagement.checkForUpdates)
1063+
* - Server version and modification time (for tracking updates)
1064+
*
1065+
* @param goConfig - VS Code workspace configuration for the Go extension
1066+
* @returns LanguageServerConfig object, with `enabled: false` if gopls is disabled or not found
1067+
*
1068+
* @example
1069+
* const goConfig = getGoConfig();
1070+
* const cfg = await buildLanguageServerConfig(goConfig);
1071+
* if (cfg.enabled) {
1072+
* console.log(`Found gopls at: ${cfg.path}`);
1073+
* const client = await buildLanguageClient(goCtx, cfg);
1074+
* }
1075+
*/
10281076
export async function buildLanguageServerConfig(
10291077
goConfig: vscode.WorkspaceConfiguration
10301078
): Promise<LanguageServerConfig> {

extension/src/util.ts

Lines changed: 91 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ import {
2727
} from './utils/pathUtils';
2828
import { killProcessTree } from './utils/processUtils';
2929

30+
/**
31+
* Represents a command defined in the extension's package.json.
32+
* Used for command palette and keybindings.
33+
*/
34+
export interface ExtensionCommand {
35+
command: string;
36+
title: string;
37+
category?: string;
38+
}
39+
3040
export class GoVersion {
3141
public sv?: semver.SemVer;
3242
// Go version tags are not following the strict semver format
@@ -298,14 +308,48 @@ function resolveToolsGopath(): string {
298308
return toolsGopathForWorkspace;
299309
}
300310

301-
// getBinPath returns the path to the tool.
311+
/**
312+
* Returns the absolute path to a Go tool's executable.
313+
* This is the primary function used throughout the extension to locate Go tools.
314+
*
315+
* Search order:
316+
* 1. Alternate tools configured in go.alternateTools
317+
* 2. GOBIN environment variable
318+
* 3. Configured toolsGopath (go.toolsGopath setting)
319+
* 4. Current workspace GOPATH
320+
* 5. GOROOT/bin (for go, gofmt, godoc)
321+
* 6. PATH environment variable
322+
* 7. Default system locations (e.g., /usr/local/go/bin)
323+
*
324+
* @param tool - Name of the tool (e.g., 'gopls', 'dlv', 'staticcheck')
325+
* @param useCache - Whether to use cached tool paths (default: true)
326+
* @returns Absolute path to the tool executable, or just the tool name if not found
327+
*
328+
* @example
329+
* const goplsPath = getBinPath('gopls'); // Returns '/Users/me/go/bin/gopls'
330+
* const goPath = getBinPath('go'); // Returns '/usr/local/go/bin/go'
331+
*/
302332
export function getBinPath(tool: string, useCache = true): string {
303333
const r = getBinPathWithExplanation(tool, useCache);
304334
return r.binPath;
305335
}
306336

307-
// getBinPathWithExplanation returns the path to the tool, and the explanation on why
308-
// the path was chosen. See getBinPathWithPreferredGopathGorootWithExplanation for details.
337+
/**
338+
* Returns the absolute path to a Go tool's executable along with an explanation
339+
* of where the path was found. Useful for diagnostics and troubleshooting.
340+
*
341+
* @param tool - Name of the tool (e.g., 'gopls', 'dlv', 'staticcheck')
342+
* @param useCache - Whether to use cached tool paths (default: true)
343+
* @param uri - Optional workspace URI for multi-root workspace support
344+
* @returns Object containing the tool path and optional explanation
345+
* - binPath: Absolute path to the tool executable
346+
* - why: Optional explanation ('gobin', 'gopath', 'goroot', 'path', 'alternateTool', 'cached', 'default')
347+
*
348+
* @example
349+
* const result = getBinPathWithExplanation('gopls');
350+
* console.log(`Found gopls at ${result.binPath} (source: ${result.why})`);
351+
* // Output: "Found gopls at /Users/me/go/bin/gopls (source: gobin)"
352+
*/
309353
export function getBinPathWithExplanation(
310354
tool: string,
311355
useCache = true,
@@ -332,6 +376,19 @@ export function getBinPathWithExplanation(
332376
);
333377
}
334378

379+
/**
380+
* Serializes a VS Code document into the archive format expected by Go tools.
381+
* This format is used to pass modified/unsaved file contents to tools like gopls and goimports.
382+
*
383+
* Format: `filename\nbytelength\ncontents`
384+
*
385+
* @param document - The VS Code text document to serialize
386+
* @returns Serialized document in archive format
387+
*
388+
* @example
389+
* const archive = getFileArchive(document);
390+
* // Returns: "/path/to/file.go\n1234\npackage main..."
391+
*/
335392
export function getFileArchive(document: vscode.TextDocument): string {
336393
const fileContents = document.getText();
337394
return document.fileName + '\n' + Buffer.byteLength(fileContents, 'utf8') + '\n' + fileContents;
@@ -407,14 +464,18 @@ export function getModuleCache(): string | undefined {
407464
}
408465
}
409466

410-
export function getExtensionCommands(): any[] {
467+
/**
468+
* Returns all commands defined in the extension's package.json.
469+
* @returns Array of extension commands (excluding 'go.show.commands')
470+
*/
471+
export function getExtensionCommands(): ExtensionCommand[] {
411472
const pkgJSON = vscode.extensions.getExtension(extensionId)?.packageJSON;
412473
if (!pkgJSON.contributes || !pkgJSON.contributes.commands) {
413474
return [];
414475
}
415-
const extensionCommands: any[] = vscode.extensions
476+
const extensionCommands: ExtensionCommand[] = vscode.extensions
416477
.getExtension(extensionId)
417-
?.packageJSON.contributes.commands.filter((x: any) => x.command !== 'go.show.commands');
478+
?.packageJSON.contributes.commands.filter((x: ExtensionCommand) => x.command !== 'go.show.commands');
418479
return extensionCommands;
419480
}
420481

@@ -530,7 +591,7 @@ export function runTool(
530591
severity: string,
531592
useStdErr: boolean,
532593
toolName: string,
533-
env: any,
594+
env: NodeJS.ProcessEnv,
534595
printUnexpectedOutput: boolean,
535596
token?: vscode.CancellationToken
536597
): Promise<ICheckResult[]> {
@@ -557,7 +618,7 @@ export function runTool(
557618
return new Promise((resolve, reject) => {
558619
p = cp.execFile(cmd, args, { env, cwd }, (err, stdout, stderr) => {
559620
try {
560-
if (err && (<any>err).code === 'ENOENT') {
621+
if (err && (err as NodeJS.ErrnoException).code === 'ENOENT') {
561622
// Since the tool is run on save which can be frequent
562623
// we avoid sending explicit notification if tool is missing
563624
console.log(`Cannot find ${toolName ? toolName : 'go'}`);
@@ -626,6 +687,28 @@ export function runTool(
626687
});
627688
}
628689

690+
/**
691+
* Converts tool output errors into VS Code diagnostics and displays them in the Problems panel.
692+
* This is the central function for surfacing errors from Go tools (gopls, staticcheck, golint, etc.)
693+
* to the VS Code UI.
694+
*
695+
* Features:
696+
* - Clears existing diagnostics before adding new ones
697+
* - Maps error positions to VS Code ranges using token-based column calculation
698+
* - Groups diagnostics by file for efficient display
699+
* - Handles both open and closed files
700+
* - Filters out diagnostics that overlap with gopls diagnostics (if gopls is enabled)
701+
*
702+
* @param goCtx - Go extension context for accessing configuration
703+
* @param document - Optional document that triggered the check (for optimization)
704+
* @param errors - Array of check results from Go tools
705+
* @param diagnosticCollection - VS Code diagnostic collection to update (e.g., 'go-lint', 'go-vet')
706+
* @param diagnosticSource - Optional source label for the diagnostics (e.g., 'staticcheck', 'golint')
707+
*
708+
* @example
709+
* const errors = await runGoVet(document);
710+
* handleDiagnosticErrors(goCtx, document, errors, vetDiagnosticCollection, 'go-vet');
711+
*/
629712
export function handleDiagnosticErrors(
630713
goCtx: GoExtensionContext,
631714
document: vscode.TextDocument | undefined,

0 commit comments

Comments
 (0)