Skip to content

Commit 3e1371f

Browse files
committed
gopls/internal: start on LSP stub generator in Go.
This is the first in a series of CLs implementing the new stub generator. The code is intended to reproduce exactly the current state of the generated code. This CL has the final file layout, but primarily consists of the parsing of the specification. The LSP maintainers now provide a .json file describing the messages and types used in the protocol. The new code in this CL, written in Go, parses this file and generates Go definitions. The tests need to be run by hand because the metaModel.json file is not available to the presubmit tests. Related golang/go#52969 Change-Id: Id2fc58c973a92c39ba98c936f2af03b1c40ada44 Reviewed-on: https://go-review.googlesource.com/c/tools/+/443055 Reviewed-by: Robert Findley <[email protected]> Reviewed-by: Alan Donovan <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Peter Weinberger <[email protected]>
1 parent 121f889 commit 3e1371f

File tree

11 files changed

+847
-0
lines changed

11 files changed

+847
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.19
6+
// +build go1.19
7+
8+
package main
9+
10+
// compare the generated files in two directories
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.19
6+
// +build go1.19
7+
8+
package main
9+
10+
// various data tables
11+
12+
// methodNames is a map from the method to the name of the function that handles it
13+
var methodNames = map[string]string{
14+
"$/cancelRequest": "CancelRequest",
15+
"$/logTrace": "LogTrace",
16+
"$/progress": "Progress",
17+
"$/setTrace": "SetTrace",
18+
"callHierarchy/incomingCalls": "IncomingCalls",
19+
"callHierarchy/outgoingCalls": "OutgoingCalls",
20+
"client/registerCapability": "RegisterCapability",
21+
"client/unregisterCapability": "UnregisterCapability",
22+
"codeAction/resolve": "ResolveCodeAction",
23+
"codeLens/resolve": "ResolveCodeLens",
24+
"completionItem/resolve": "ResolveCompletionItem",
25+
"documentLink/resolve": "ResolveDocumentLink",
26+
"exit": "Exit",
27+
"initialize": "Initialize",
28+
"initialized": "Initialized",
29+
"inlayHint/resolve": "Resolve",
30+
"notebookDocument/didChange": "DidChangeNotebookDocument",
31+
"notebookDocument/didClose": "DidCloseNotebookDocument",
32+
"notebookDocument/didOpen": "DidOpenNotebookDocument",
33+
"notebookDocument/didSave": "DidSaveNotebookDocument",
34+
"shutdown": "Shutdown",
35+
"telemetry/event": "Event",
36+
"textDocument/codeAction": "CodeAction",
37+
"textDocument/codeLens": "CodeLens",
38+
"textDocument/colorPresentation": "ColorPresentation",
39+
"textDocument/completion": "Completion",
40+
"textDocument/declaration": "Declaration",
41+
"textDocument/definition": "Definition",
42+
"textDocument/diagnostic": "Diagnostic",
43+
"textDocument/didChange": "DidChange",
44+
"textDocument/didClose": "DidClose",
45+
"textDocument/didOpen": "DidOpen",
46+
"textDocument/didSave": "DidSave",
47+
"textDocument/documentColor": "DocumentColor",
48+
"textDocument/documentHighlight": "DocumentHighlight",
49+
"textDocument/documentLink": "DocumentLink",
50+
"textDocument/documentSymbol": "DocumentSymbol",
51+
"textDocument/foldingRange": "FoldingRange",
52+
"textDocument/formatting": "Formatting",
53+
"textDocument/hover": "Hover",
54+
"textDocument/implementation": "Implementation",
55+
"textDocument/inlayHint": "InlayHint",
56+
"textDocument/inlineValue": "InlineValue",
57+
"textDocument/linkedEditingRange": "LinkedEditingRange",
58+
"textDocument/moniker": "Moniker",
59+
"textDocument/onTypeFormatting": "OnTypeFormatting",
60+
"textDocument/prepareCallHierarchy": "PrepareCallHierarchy",
61+
"textDocument/prepareRename": "PrepareRename",
62+
"textDocument/prepareTypeHierarchy": "PrepareTypeHierarchy",
63+
"textDocument/publishDiagnostics": "PublishDiagnostics",
64+
"textDocument/rangeFormatting": "RangeFormatting",
65+
"textDocument/references": "References",
66+
"textDocument/rename": "Rename",
67+
"textDocument/selectionRange": "SelectionRange",
68+
"textDocument/semanticTokens/full": "SemanticTokensFull",
69+
"textDocument/semanticTokens/full/delta": "SemanticTokensFullDelta",
70+
"textDocument/semanticTokens/range": "SemanticTokensRange",
71+
"textDocument/signatureHelp": "SignatureHelp",
72+
"textDocument/typeDefinition": "TypeDefinition",
73+
"textDocument/willSave": "WillSave",
74+
"textDocument/willSaveWaitUntil": "WillSaveWaitUntil",
75+
"typeHierarchy/subtypes": "Subtypes",
76+
"typeHierarchy/supertypes": "Supertypes",
77+
"window/logMessage": "LogMessage",
78+
"window/showDocument": "ShowDocument",
79+
"window/showMessage": "ShowMessage",
80+
"window/showMessageRequest": "ShowMessageRequest",
81+
"window/workDoneProgress/cancel": "WorkDoneProgressCancel",
82+
"window/workDoneProgress/create": "WorkDoneProgressCreate",
83+
"workspace/applyEdit": "ApplyEdit",
84+
"workspace/codeLens/refresh": "CodeLensRefresh",
85+
"workspace/configuration": "Configuration",
86+
"workspace/diagnostic": "DiagnosticWorkspace",
87+
"workspace/diagnostic/refresh": "DiagnosticRefresh",
88+
"workspace/didChangeConfiguration": "DidChangeConfiguration",
89+
"workspace/didChangeWatchedFiles": "DidChangeWatchedFiles",
90+
"workspace/didChangeWorkspaceFolders": "DidChangeWorkspaceFolders",
91+
"workspace/didCreateFiles": "DidCreateFiles",
92+
"workspace/didDeleteFiles": "DidDeleteFiles",
93+
"workspace/didRenameFiles": "DidRenameFiles",
94+
"workspace/executeCommand": "ExecuteCommand",
95+
"workspace/inlayHint/refresh": "InlayHintRefresh",
96+
"workspace/inlineValue/refresh": "InlineValueRefresh",
97+
"workspace/semanticTokens/refresh": "SemanticTokensRefresh",
98+
"workspace/symbol": "Symbol",
99+
"workspace/willCreateFiles": "WillCreateFiles",
100+
"workspace/willDeleteFiles": "WillDeleteFiles",
101+
"workspace/willRenameFiles": "WillRenameFiles",
102+
"workspace/workspaceFolders": "WorkspaceFolders",
103+
"workspaceSymbol/resolve": "ResolveWorkspaceSymbol",
104+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.19
6+
// +build go1.19
7+
8+
/*
9+
GenLSP generates the files tsprotocol.go, tsclient.go,
10+
tsserver.go, tsjson.go that support the language server protocol
11+
for gopls.
12+
13+
Usage:
14+
15+
go run . [flags]
16+
17+
The flags are:
18+
19+
-d <directory name>
20+
The directory containing the vscode-languageserver-node repository.
21+
(git clone https://github.com/microsoft/vscode-languageserver-node.git).
22+
If not specified, the default is $HOME/vscode-languageserver-node.
23+
24+
-o <directory name>
25+
The directory to write the generated files to. It must exist.
26+
The default is "gen".
27+
28+
-c <directory name>
29+
Compare the generated files to the files in the specified directory.
30+
If this flag is not specified, no comparison is done.
31+
*/
32+
package main
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.19
6+
// +build go1.19
7+
8+
package main
9+
10+
// generate the Go code
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.19
6+
// +build go1.19
7+
8+
package main
9+
10+
import (
11+
"flag"
12+
"fmt"
13+
"log"
14+
"os"
15+
)
16+
17+
var (
18+
// git clone https://github.com/microsoft/vscode-languageserver-node.git
19+
repodir = flag.String("d", "", "directory of vscode-languageserver-node")
20+
outputdir = flag.String("o", "gen", "output directory")
21+
cmpolder = flag.String("c", "", "directory of older generated code")
22+
)
23+
24+
func main() {
25+
log.SetFlags(log.Lshortfile) // log file name and line number, not time
26+
flag.Parse()
27+
28+
if *repodir == "" {
29+
*repodir = fmt.Sprintf("%s/vscode-languageserver-node", os.Getenv("HOME"))
30+
}
31+
spec := parse(*repodir)
32+
33+
// index the information in the specification
34+
spec.indexRPCInfo() // messages
35+
spec.indexDefInfo() // named types
36+
37+
}
38+
39+
func (s *spec) indexRPCInfo() {
40+
for _, r := range s.model.Requests {
41+
r := r
42+
s.byMethod[r.Method] = &r
43+
}
44+
for _, n := range s.model.Notifications {
45+
n := n
46+
if n.Method == "$/cancelRequest" {
47+
// viewed as too confusing to generate
48+
continue
49+
}
50+
s.byMethod[n.Method] = &n
51+
}
52+
}
53+
54+
func (sp *spec) indexDefInfo() {
55+
for _, s := range sp.model.Structures {
56+
s := s
57+
sp.byName[s.Name] = &s
58+
}
59+
for _, e := range sp.model.Enumerations {
60+
e := e
61+
sp.byName[e.Name] = &e
62+
}
63+
for _, ta := range sp.model.TypeAliases {
64+
ta := ta
65+
sp.byName[ta.Name] = &ta
66+
}
67+
68+
// some Structure and TypeAlias names need to be changed for Go
69+
// so byName contains the name used in the .json file, and
70+
// the Name field contains the Go version of the name.
71+
v := sp.model.Structures
72+
for i, s := range v {
73+
switch s.Name {
74+
case "_InitializeParams": // _ is not upper case
75+
v[i].Name = "XInitializeParams"
76+
case "ConfigurationParams": // gopls compatibility
77+
v[i].Name = "ParamConfiguration"
78+
case "InitializeParams": // gopls compatibility
79+
v[i].Name = "ParamInitialize"
80+
case "PreviousResultId": // Go naming convention
81+
v[i].Name = "PreviousResultID"
82+
case "WorkspaceFoldersServerCapabilities": // gopls compatibility
83+
v[i].Name = "WorkspaceFolders5Gn"
84+
}
85+
}
86+
w := sp.model.TypeAliases
87+
for i, t := range w {
88+
switch t.Name {
89+
case "PrepareRenameResult": // gopls compatibility
90+
w[i].Name = "PrepareRename2Gn"
91+
}
92+
}
93+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.19
6+
// +build go1.19
7+
8+
package main
9+
10+
import (
11+
"encoding/json"
12+
"fmt"
13+
"log"
14+
"os"
15+
"testing"
16+
)
17+
18+
// this is not a test, but an easy way to invoke the debugger
19+
func TestAll(t *testing.T) {
20+
t.Skip("run by hand")
21+
log.SetFlags(log.Lshortfile)
22+
main()
23+
}
24+
25+
// this is not a test, but an easy way to invoke the debugger
26+
func TestCompare(t *testing.T) {
27+
t.Skip("run by hand")
28+
log.SetFlags(log.Lshortfile)
29+
*cmpolder = "../lsp/gen" // instead use a directory containing the older generated files
30+
main()
31+
}
32+
33+
// check that the parsed file includes all the information
34+
// from the json file. This test will fail if the spec
35+
// introduces new fields. (one can test this test by
36+
// commenting out some special handling in parse.go.)
37+
func TestParseContents(t *testing.T) {
38+
t.Skip("run by hand")
39+
log.SetFlags(log.Lshortfile)
40+
41+
// compute our parse of the specification
42+
dir := os.Getenv("HOME") + "/vscode-languageserver-node"
43+
v := parse(dir)
44+
out, err := json.Marshal(v.model)
45+
if err != nil {
46+
t.Fatal(err)
47+
}
48+
var our interface{}
49+
if err := json.Unmarshal(out, &our); err != nil {
50+
t.Fatal(err)
51+
}
52+
53+
// process the json file
54+
fname := dir + "/protocol/metaModel.json"
55+
buf, err := os.ReadFile(fname)
56+
if err != nil {
57+
t.Fatalf("could not read metaModel.json: %v", err)
58+
}
59+
var raw interface{}
60+
if err := json.Unmarshal(buf, &raw); err != nil {
61+
t.Fatal(err)
62+
}
63+
64+
// convert to strings showing the fields
65+
them := flatten(raw)
66+
us := flatten(our)
67+
68+
// everything in them should be in us
69+
lesser := make(sortedMap[bool])
70+
for _, s := range them {
71+
lesser[s] = true
72+
}
73+
greater := make(sortedMap[bool]) // set of fields we have
74+
for _, s := range us {
75+
greater[s] = true
76+
}
77+
for _, k := range lesser.keys() { // set if fields they have
78+
if !greater[k] {
79+
t.Errorf("missing %s", k)
80+
}
81+
}
82+
}
83+
84+
// flatten(nil) = "nil"
85+
// flatten(v string) = fmt.Sprintf("%q", v)
86+
// flatten(v float64)= fmt.Sprintf("%g", v)
87+
// flatten(v bool) = fmt.Sprintf("%v", v)
88+
// flatten(v []any) = []string{"[0]"flatten(v[0]), "[1]"flatten(v[1]), ...}
89+
// flatten(v map[string]any) = {"key1": flatten(v["key1"]), "key2": flatten(v["key2"]), ...}
90+
func flatten(x any) []string {
91+
switch v := x.(type) {
92+
case nil:
93+
return []string{"nil"}
94+
case string:
95+
return []string{fmt.Sprintf("%q", v)}
96+
case float64:
97+
return []string{fmt.Sprintf("%g", v)}
98+
case bool:
99+
return []string{fmt.Sprintf("%v", v)}
100+
case []any:
101+
var ans []string
102+
for i, x := range v {
103+
idx := fmt.Sprintf("[%.3d]", i)
104+
for _, s := range flatten(x) {
105+
ans = append(ans, idx+s)
106+
}
107+
}
108+
return ans
109+
case map[string]any:
110+
var ans []string
111+
for k, x := range v {
112+
idx := fmt.Sprintf("%q:", k)
113+
for _, s := range flatten(x) {
114+
ans = append(ans, idx+s)
115+
}
116+
}
117+
return ans
118+
default:
119+
log.Fatalf("unexpected type %T", x)
120+
return nil
121+
}
122+
}

0 commit comments

Comments
 (0)