Skip to content

Commit 28136c8

Browse files
committed
src/goOutline: use language server to get document symbols
This change replaces the use of go-outline with gopls when available. This is done by fixing up the results from gopls to resemble those returned by go-outline to avoid having to handle these document symbols differently in various places. Also sets the testing gopls version to pre release. Updates #1020 Change-Id: Ib3ce4fe04c3ffe9428f6701ffaa24dd8444781c0 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/384574 Trust: Suzy Mueller <[email protected]> Run-TryBot: Suzy Mueller <[email protected]> Trust: Hyang-Ah Hana Kim <[email protected]> Reviewed-by: Hyang-Ah Hana Kim <[email protected]> TryBot-Result: kokoro <[email protected]>
1 parent af9afa3 commit 28136c8

File tree

6 files changed

+90
-17
lines changed

6 files changed

+90
-17
lines changed

docs/settings.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ Example Usage:
730730
| `nilfunc` | check for useless comparisons between functions and nil <br/> A useless comparison is one like f == nil as opposed to f() == nil. <br/> Default: `true` |
731731
| `nilness` | check for redundant or impossible nil comparisons <br/> 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 <br/> <pre>if r := recover(); r != nil {</pre><br/> This check reports conditions such as: <br/> <pre>if f == nil { // impossible condition (f is a function)<br/>}</pre><br/> and: <br/> <pre>p := &v<br/>...<br/>if p != nil { // tautological condition<br/>}</pre><br/> and: <br/> <pre>if p == nil {<br/> print(*p) // nil dereference<br/>}</pre><br/> and: <br/> <pre>if p == nil {<br/> panic(p)<br/>}</pre><br/> <br/> Default: `false` |
732732
| `nonewvars` | suggested fixes for "no new vars on left side of :=" <br/> This checker provides suggested fixes for type errors of the type "no new vars on left side of :=". For example: <pre>z := 1<br/>z := 2</pre>will turn into <pre>z := 1<br/>z = 2</pre><br/> <br/> Default: `true` |
733-
| `noresultvalues` | suggested fixes for "no result values expected" <br/> This checker provides suggested fixes for type errors of the type "no result values expected". For example: <pre>func z() { return nil }</pre>will turn into <pre>func z() { return }</pre><br/> <br/> Default: `true` |
733+
| `noresultvalues` | suggested fixes for unexpected return values <br/> This checker provides suggested fixes for type errors of the type "no result values expected" or "too many return values". For example: <pre>func z() { return nil }</pre>will turn into <pre>func z() { return }</pre><br/> <br/> Default: `true` |
734734
| `printf` | check consistency of Printf format strings and arguments <br/> The check applies to known functions (for example, those in package fmt) as well as any detected wrappers of known functions. <br/> A function that wants to avail itself of printf checking but is not found by this analyzer's heuristics (for example, due to use of dynamic calls) can insert a bogus call: <br/> <pre>if false {<br/> _ = fmt.Sprintf(format, args...) // enable printf checking<br/>}</pre><br/> The -funcs flag specifies a comma-separated list of names of additional known formatting functions or methods. If the name contains a period, it must denote a specific function using one of the following forms: <br/> <pre>dir/pkg.Function<br/>dir/pkg.Type.Method<br/>(*dir/pkg.Type).Method</pre><br/> Otherwise the name is interpreted as a case-insensitive unqualified identifier such as "errorf". Either way, if a listed name ends in f, the function is assumed to be Printf-like, taking a format string before the argument list. Otherwise it is assumed to be Print-like, taking a list of arguments with no format string. <br/> <br/> Default: `true` |
735735
| `shadow` | check for possible unintended shadowing of variables <br/> 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. <br/> (This definition can be refined; the module generates too many false positives and is not yet enabled by default.) <br/> For example: <br/> <pre>func BadRead(f *os.File, buf []byte) error {<br/> var err error<br/> for {<br/> n, err := f.Read(buf) // shadows the function variable 'err'<br/> if err != nil {<br/> break // causes return of wrong value<br/> }<br/> foo(buf)<br/> }<br/> return err<br/>}</pre><br/> <br/> Default: `false` |
736736
| `shift` | check for shifts that equal or exceed the width of the integer <br/> Default: `true` |
@@ -840,7 +840,7 @@ Default: `"Both"`
840840
<br/>
841841
Allowed Options: `CaseInsensitive`, `CaseSensitive`, `FastFuzzy`, `Fuzzy`
842842

843-
Default: `"Fuzzy"`
843+
Default: `"FastFuzzy"`
844844
### `ui.navigation.symbolStyle`
845845

846846
(Advanced) symbolStyle controls how symbols are qualified in symbol responses.

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -2272,7 +2272,7 @@
22722272
},
22732273
"noresultvalues": {
22742274
"type": "boolean",
2275-
"markdownDescription": "suggested fixes for \"no result values expected\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"no result values expected\". For example:\n\tfunc z() { return nil }\nwill turn into\n\tfunc z() { return }\n",
2275+
"markdownDescription": "suggested fixes for unexpected return values\n\nThis checker provides suggested fixes for type errors of the\ntype \"no result values expected\" or \"too many return values\".\nFor example:\n\tfunc z() { return nil }\nwill turn into\n\tfunc z() { return }\n",
22762276
"default": true
22772277
},
22782278
"printf": {
@@ -2485,7 +2485,7 @@
24852485
"",
24862486
""
24872487
],
2488-
"default": "Fuzzy",
2488+
"default": "FastFuzzy",
24892489
"scope": "resource"
24902490
},
24912491
"ui.navigation.symbolStyle": {

src/goOutline.ts

+68-2
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@
77
'use strict';
88

99
import cp = require('child_process');
10+
import semver = require('semver');
1011
import vscode = require('vscode');
12+
import { ExecuteCommandParams, ExecuteCommandRequest } from 'vscode-languageserver-protocol';
1113
import { getGoConfig } from './config';
1214
import { toolExecutionEnvironment } from './goEnv';
1315
import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools';
16+
import { getLocalGoplsVersion, languageClient, latestConfig } from './goLanguageServer';
1417
import { getBinPath, getFileArchive, makeMemoizedByteOffsetConverter } from './util';
1518
import { killProcess } from './utils/processUtils';
1619

@@ -192,14 +195,63 @@ function convertToCodeSymbols(
192195
export class GoDocumentSymbolProvider implements vscode.DocumentSymbolProvider {
193196
constructor(private includeImports?: boolean) {}
194197

195-
public provideDocumentSymbols(
198+
public async provideDocumentSymbols(
196199
document: vscode.TextDocument,
197200
token: vscode.CancellationToken
198-
): Thenable<vscode.DocumentSymbol[]> {
201+
): Promise<vscode.DocumentSymbol[]> {
199202
if (typeof this.includeImports !== 'boolean') {
200203
const gotoSymbolConfig = getGoConfig(document.uri)['gotoSymbol'];
201204
this.includeImports = gotoSymbolConfig ? gotoSymbolConfig['includeImports'] : false;
202205
}
206+
207+
// TODO(suzmue): Check the commands available instead of the version.
208+
const goplsVersion = await getLocalGoplsVersion(latestConfig);
209+
const sv = semver.parse(goplsVersion, true);
210+
if (languageClient && (goplsVersion === '(devel)' || semver.gt(sv, 'v0.7.6'))) {
211+
const symbols: vscode.DocumentSymbol[] = await vscode.commands.executeCommand(
212+
'vscode.executeDocumentSymbolProvider',
213+
document.uri
214+
);
215+
if (!symbols || symbols.length === 0) {
216+
return [];
217+
}
218+
219+
// Stitch the results together to make the results look like
220+
// go-outline.
221+
// TODO(suzmue): use regexp to find the package declaration.
222+
const packageSymbol = new vscode.DocumentSymbol(
223+
'unknown',
224+
'package',
225+
vscode.SymbolKind.Package,
226+
new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0)),
227+
new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0))
228+
);
229+
packageSymbol.children = symbols;
230+
if (this.includeImports) {
231+
try {
232+
const imports = await listImports(document);
233+
imports?.forEach((value) => {
234+
packageSymbol.children.unshift(
235+
new vscode.DocumentSymbol(
236+
value.Path,
237+
'import',
238+
vscode.SymbolKind.Namespace,
239+
new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0)),
240+
new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0))
241+
)
242+
);
243+
});
244+
} catch (e) {
245+
// Fall back to use go-outline.
246+
return this.runGoOutline(document, token);
247+
}
248+
}
249+
return [packageSymbol];
250+
}
251+
return this.runGoOutline(document, token);
252+
}
253+
254+
private runGoOutline(document: vscode.TextDocument, token: vscode.CancellationToken) {
203255
const options: GoOutlineOptions = {
204256
fileName: document.fileName,
205257
document,
@@ -208,3 +260,17 @@ export class GoDocumentSymbolProvider implements vscode.DocumentSymbolProvider {
208260
return documentSymbols(options, token);
209261
}
210262
}
263+
264+
async function listImports(document: vscode.TextDocument): Promise<{ Path: string; Name: string }[]> {
265+
const uri = languageClient.code2ProtocolConverter.asTextDocumentIdentifier(document).uri;
266+
const params: ExecuteCommandParams = {
267+
command: 'gopls.list_imports',
268+
arguments: [
269+
{
270+
URI: uri
271+
}
272+
]
273+
};
274+
const resp = await languageClient.sendRequest(ExecuteCommandRequest.type, params);
275+
return resp.Imports;
276+
}

src/goTest/resolve.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { getCurrentGoPath } from '../util';
2424
import { getGoConfig } from '../config';
2525
import { dispose, disposeIfEmpty, FileSystem, GoTest, GoTestKind, isInTest, Workspace } from './utils';
2626
import { walk, WalkStop } from './walk';
27+
import { importsTestify } from '../testUtils';
2728

2829
export type ProvideSymbols = (doc: TextDocument, token: CancellationToken) => Thenable<DocumentSymbol[]>;
2930

@@ -202,11 +203,7 @@ export class GoTestResolver {
202203
const seen = new Set<string>();
203204
const item = await this.getFile(doc.uri);
204205
const symbols = await this.provideDocumentSymbols(doc, null);
205-
const testify = symbols.some((s) =>
206-
s.children.some(
207-
(sym) => sym.kind === SymbolKind.Namespace && sym.name === '"github.com/stretchr/testify/suite"'
208-
)
209-
);
206+
const testify = importsTestify(symbols);
210207
for (const symbol of symbols) {
211208
await this.processSymbol(doc, item, seen, testify, symbol);
212209
}
@@ -410,7 +407,7 @@ export class GoTestResolver {
410407
}
411408

412409
// Recursively process symbols that are nested
413-
if (symbol.kind !== SymbolKind.Function) {
410+
if (symbol.kind !== SymbolKind.Function && symbol.kind !== SymbolKind.Method) {
414411
for (const sym of symbol.children) await this.processSymbol(doc, file, seen, importsTestify, sym);
415412
return;
416413
}

src/testUtils.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,10 @@ export async function getTestFunctions(
154154
return;
155155
}
156156
const children = symbol.children;
157-
const testify = children.some(
158-
(sym) => sym.kind === vscode.SymbolKind.Namespace && sym.name === '"github.com/stretchr/testify/suite"'
159-
);
157+
const testify = importsTestify(symbols);
160158
return children.filter(
161159
(sym) =>
162-
sym.kind === vscode.SymbolKind.Function &&
160+
(sym.kind === vscode.SymbolKind.Function || sym.kind === vscode.SymbolKind.Method) &&
163161
(testFuncRegex.test(sym.name) || fuzzFuncRegx.test(sym.name) || (testify && testMethodRegex.test(sym.name)))
164162
);
165163
}
@@ -632,3 +630,15 @@ function removeRunFlag(flags: string[]): void {
632630
flags.splice(index, 2);
633631
}
634632
}
633+
634+
export function importsTestify(syms: vscode.DocumentSymbol[]): boolean {
635+
if (!syms || syms.length === 0 || !syms[0]) {
636+
return false;
637+
}
638+
const children = syms[0].children;
639+
return children.some(
640+
(sym) =>
641+
sym.kind === vscode.SymbolKind.Namespace &&
642+
(sym.name === '"github.com/stretchr/testify/suite"' || sym.name === 'github.com/stretchr/testify/suite')
643+
);
644+
}

tools/installtools/main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ var tools = []struct {
1717
dest string
1818
}{
1919
// TODO: auto-generate based on allTools.ts.in.
20-
{"golang.org/x/tools/gopls", "", ""},
20+
{"golang.org/x/tools/gopls", "v0.8.0-pre.1", ""}, // TODO: make this not hardcoded
2121
{"github.com/acroca/go-symbols", "", ""},
2222
{"github.com/cweill/gotests/gotests", "", ""},
2323
{"github.com/davidrjenni/reftools/cmd/fillstruct", "", ""},

0 commit comments

Comments
 (0)