зеркало из https://github.com/golang/tools.git
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 <rfindley@google.com> Reviewed-by: Alan Donovan <adonovan@google.com> gopls-CI: kokoro <noreply+kokoro@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Peter Weinberger <pjw@google.com>
This commit is contained in:
Родитель
121f889bbc
Коммит
3e1371fd13
|
@ -0,0 +1,10 @@
|
||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.19
|
||||||
|
// +build go1.19
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// compare the generated files in two directories
|
|
@ -0,0 +1,104 @@
|
||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.19
|
||||||
|
// +build go1.19
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// various data tables
|
||||||
|
|
||||||
|
// methodNames is a map from the method to the name of the function that handles it
|
||||||
|
var methodNames = map[string]string{
|
||||||
|
"$/cancelRequest": "CancelRequest",
|
||||||
|
"$/logTrace": "LogTrace",
|
||||||
|
"$/progress": "Progress",
|
||||||
|
"$/setTrace": "SetTrace",
|
||||||
|
"callHierarchy/incomingCalls": "IncomingCalls",
|
||||||
|
"callHierarchy/outgoingCalls": "OutgoingCalls",
|
||||||
|
"client/registerCapability": "RegisterCapability",
|
||||||
|
"client/unregisterCapability": "UnregisterCapability",
|
||||||
|
"codeAction/resolve": "ResolveCodeAction",
|
||||||
|
"codeLens/resolve": "ResolveCodeLens",
|
||||||
|
"completionItem/resolve": "ResolveCompletionItem",
|
||||||
|
"documentLink/resolve": "ResolveDocumentLink",
|
||||||
|
"exit": "Exit",
|
||||||
|
"initialize": "Initialize",
|
||||||
|
"initialized": "Initialized",
|
||||||
|
"inlayHint/resolve": "Resolve",
|
||||||
|
"notebookDocument/didChange": "DidChangeNotebookDocument",
|
||||||
|
"notebookDocument/didClose": "DidCloseNotebookDocument",
|
||||||
|
"notebookDocument/didOpen": "DidOpenNotebookDocument",
|
||||||
|
"notebookDocument/didSave": "DidSaveNotebookDocument",
|
||||||
|
"shutdown": "Shutdown",
|
||||||
|
"telemetry/event": "Event",
|
||||||
|
"textDocument/codeAction": "CodeAction",
|
||||||
|
"textDocument/codeLens": "CodeLens",
|
||||||
|
"textDocument/colorPresentation": "ColorPresentation",
|
||||||
|
"textDocument/completion": "Completion",
|
||||||
|
"textDocument/declaration": "Declaration",
|
||||||
|
"textDocument/definition": "Definition",
|
||||||
|
"textDocument/diagnostic": "Diagnostic",
|
||||||
|
"textDocument/didChange": "DidChange",
|
||||||
|
"textDocument/didClose": "DidClose",
|
||||||
|
"textDocument/didOpen": "DidOpen",
|
||||||
|
"textDocument/didSave": "DidSave",
|
||||||
|
"textDocument/documentColor": "DocumentColor",
|
||||||
|
"textDocument/documentHighlight": "DocumentHighlight",
|
||||||
|
"textDocument/documentLink": "DocumentLink",
|
||||||
|
"textDocument/documentSymbol": "DocumentSymbol",
|
||||||
|
"textDocument/foldingRange": "FoldingRange",
|
||||||
|
"textDocument/formatting": "Formatting",
|
||||||
|
"textDocument/hover": "Hover",
|
||||||
|
"textDocument/implementation": "Implementation",
|
||||||
|
"textDocument/inlayHint": "InlayHint",
|
||||||
|
"textDocument/inlineValue": "InlineValue",
|
||||||
|
"textDocument/linkedEditingRange": "LinkedEditingRange",
|
||||||
|
"textDocument/moniker": "Moniker",
|
||||||
|
"textDocument/onTypeFormatting": "OnTypeFormatting",
|
||||||
|
"textDocument/prepareCallHierarchy": "PrepareCallHierarchy",
|
||||||
|
"textDocument/prepareRename": "PrepareRename",
|
||||||
|
"textDocument/prepareTypeHierarchy": "PrepareTypeHierarchy",
|
||||||
|
"textDocument/publishDiagnostics": "PublishDiagnostics",
|
||||||
|
"textDocument/rangeFormatting": "RangeFormatting",
|
||||||
|
"textDocument/references": "References",
|
||||||
|
"textDocument/rename": "Rename",
|
||||||
|
"textDocument/selectionRange": "SelectionRange",
|
||||||
|
"textDocument/semanticTokens/full": "SemanticTokensFull",
|
||||||
|
"textDocument/semanticTokens/full/delta": "SemanticTokensFullDelta",
|
||||||
|
"textDocument/semanticTokens/range": "SemanticTokensRange",
|
||||||
|
"textDocument/signatureHelp": "SignatureHelp",
|
||||||
|
"textDocument/typeDefinition": "TypeDefinition",
|
||||||
|
"textDocument/willSave": "WillSave",
|
||||||
|
"textDocument/willSaveWaitUntil": "WillSaveWaitUntil",
|
||||||
|
"typeHierarchy/subtypes": "Subtypes",
|
||||||
|
"typeHierarchy/supertypes": "Supertypes",
|
||||||
|
"window/logMessage": "LogMessage",
|
||||||
|
"window/showDocument": "ShowDocument",
|
||||||
|
"window/showMessage": "ShowMessage",
|
||||||
|
"window/showMessageRequest": "ShowMessageRequest",
|
||||||
|
"window/workDoneProgress/cancel": "WorkDoneProgressCancel",
|
||||||
|
"window/workDoneProgress/create": "WorkDoneProgressCreate",
|
||||||
|
"workspace/applyEdit": "ApplyEdit",
|
||||||
|
"workspace/codeLens/refresh": "CodeLensRefresh",
|
||||||
|
"workspace/configuration": "Configuration",
|
||||||
|
"workspace/diagnostic": "DiagnosticWorkspace",
|
||||||
|
"workspace/diagnostic/refresh": "DiagnosticRefresh",
|
||||||
|
"workspace/didChangeConfiguration": "DidChangeConfiguration",
|
||||||
|
"workspace/didChangeWatchedFiles": "DidChangeWatchedFiles",
|
||||||
|
"workspace/didChangeWorkspaceFolders": "DidChangeWorkspaceFolders",
|
||||||
|
"workspace/didCreateFiles": "DidCreateFiles",
|
||||||
|
"workspace/didDeleteFiles": "DidDeleteFiles",
|
||||||
|
"workspace/didRenameFiles": "DidRenameFiles",
|
||||||
|
"workspace/executeCommand": "ExecuteCommand",
|
||||||
|
"workspace/inlayHint/refresh": "InlayHintRefresh",
|
||||||
|
"workspace/inlineValue/refresh": "InlineValueRefresh",
|
||||||
|
"workspace/semanticTokens/refresh": "SemanticTokensRefresh",
|
||||||
|
"workspace/symbol": "Symbol",
|
||||||
|
"workspace/willCreateFiles": "WillCreateFiles",
|
||||||
|
"workspace/willDeleteFiles": "WillDeleteFiles",
|
||||||
|
"workspace/willRenameFiles": "WillRenameFiles",
|
||||||
|
"workspace/workspaceFolders": "WorkspaceFolders",
|
||||||
|
"workspaceSymbol/resolve": "ResolveWorkspaceSymbol",
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.19
|
||||||
|
// +build go1.19
|
||||||
|
|
||||||
|
/*
|
||||||
|
GenLSP generates the files tsprotocol.go, tsclient.go,
|
||||||
|
tsserver.go, tsjson.go that support the language server protocol
|
||||||
|
for gopls.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
go run . [flags]
|
||||||
|
|
||||||
|
The flags are:
|
||||||
|
|
||||||
|
-d <directory name>
|
||||||
|
The directory containing the vscode-languageserver-node repository.
|
||||||
|
(git clone https://github.com/microsoft/vscode-languageserver-node.git).
|
||||||
|
If not specified, the default is $HOME/vscode-languageserver-node.
|
||||||
|
|
||||||
|
-o <directory name>
|
||||||
|
The directory to write the generated files to. It must exist.
|
||||||
|
The default is "gen".
|
||||||
|
|
||||||
|
-c <directory name>
|
||||||
|
Compare the generated files to the files in the specified directory.
|
||||||
|
If this flag is not specified, no comparison is done.
|
||||||
|
*/
|
||||||
|
package main
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.19
|
||||||
|
// +build go1.19
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// generate the Go code
|
|
@ -0,0 +1,93 @@
|
||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.19
|
||||||
|
// +build go1.19
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// git clone https://github.com/microsoft/vscode-languageserver-node.git
|
||||||
|
repodir = flag.String("d", "", "directory of vscode-languageserver-node")
|
||||||
|
outputdir = flag.String("o", "gen", "output directory")
|
||||||
|
cmpolder = flag.String("c", "", "directory of older generated code")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.SetFlags(log.Lshortfile) // log file name and line number, not time
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *repodir == "" {
|
||||||
|
*repodir = fmt.Sprintf("%s/vscode-languageserver-node", os.Getenv("HOME"))
|
||||||
|
}
|
||||||
|
spec := parse(*repodir)
|
||||||
|
|
||||||
|
// index the information in the specification
|
||||||
|
spec.indexRPCInfo() // messages
|
||||||
|
spec.indexDefInfo() // named types
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *spec) indexRPCInfo() {
|
||||||
|
for _, r := range s.model.Requests {
|
||||||
|
r := r
|
||||||
|
s.byMethod[r.Method] = &r
|
||||||
|
}
|
||||||
|
for _, n := range s.model.Notifications {
|
||||||
|
n := n
|
||||||
|
if n.Method == "$/cancelRequest" {
|
||||||
|
// viewed as too confusing to generate
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s.byMethod[n.Method] = &n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *spec) indexDefInfo() {
|
||||||
|
for _, s := range sp.model.Structures {
|
||||||
|
s := s
|
||||||
|
sp.byName[s.Name] = &s
|
||||||
|
}
|
||||||
|
for _, e := range sp.model.Enumerations {
|
||||||
|
e := e
|
||||||
|
sp.byName[e.Name] = &e
|
||||||
|
}
|
||||||
|
for _, ta := range sp.model.TypeAliases {
|
||||||
|
ta := ta
|
||||||
|
sp.byName[ta.Name] = &ta
|
||||||
|
}
|
||||||
|
|
||||||
|
// some Structure and TypeAlias names need to be changed for Go
|
||||||
|
// so byName contains the name used in the .json file, and
|
||||||
|
// the Name field contains the Go version of the name.
|
||||||
|
v := sp.model.Structures
|
||||||
|
for i, s := range v {
|
||||||
|
switch s.Name {
|
||||||
|
case "_InitializeParams": // _ is not upper case
|
||||||
|
v[i].Name = "XInitializeParams"
|
||||||
|
case "ConfigurationParams": // gopls compatibility
|
||||||
|
v[i].Name = "ParamConfiguration"
|
||||||
|
case "InitializeParams": // gopls compatibility
|
||||||
|
v[i].Name = "ParamInitialize"
|
||||||
|
case "PreviousResultId": // Go naming convention
|
||||||
|
v[i].Name = "PreviousResultID"
|
||||||
|
case "WorkspaceFoldersServerCapabilities": // gopls compatibility
|
||||||
|
v[i].Name = "WorkspaceFolders5Gn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w := sp.model.TypeAliases
|
||||||
|
for i, t := range w {
|
||||||
|
switch t.Name {
|
||||||
|
case "PrepareRenameResult": // gopls compatibility
|
||||||
|
w[i].Name = "PrepareRename2Gn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.19
|
||||||
|
// +build go1.19
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// this is not a test, but an easy way to invoke the debugger
|
||||||
|
func TestAll(t *testing.T) {
|
||||||
|
t.Skip("run by hand")
|
||||||
|
log.SetFlags(log.Lshortfile)
|
||||||
|
main()
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is not a test, but an easy way to invoke the debugger
|
||||||
|
func TestCompare(t *testing.T) {
|
||||||
|
t.Skip("run by hand")
|
||||||
|
log.SetFlags(log.Lshortfile)
|
||||||
|
*cmpolder = "../lsp/gen" // instead use a directory containing the older generated files
|
||||||
|
main()
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that the parsed file includes all the information
|
||||||
|
// from the json file. This test will fail if the spec
|
||||||
|
// introduces new fields. (one can test this test by
|
||||||
|
// commenting out some special handling in parse.go.)
|
||||||
|
func TestParseContents(t *testing.T) {
|
||||||
|
t.Skip("run by hand")
|
||||||
|
log.SetFlags(log.Lshortfile)
|
||||||
|
|
||||||
|
// compute our parse of the specification
|
||||||
|
dir := os.Getenv("HOME") + "/vscode-languageserver-node"
|
||||||
|
v := parse(dir)
|
||||||
|
out, err := json.Marshal(v.model)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var our interface{}
|
||||||
|
if err := json.Unmarshal(out, &our); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// process the json file
|
||||||
|
fname := dir + "/protocol/metaModel.json"
|
||||||
|
buf, err := os.ReadFile(fname)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not read metaModel.json: %v", err)
|
||||||
|
}
|
||||||
|
var raw interface{}
|
||||||
|
if err := json.Unmarshal(buf, &raw); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert to strings showing the fields
|
||||||
|
them := flatten(raw)
|
||||||
|
us := flatten(our)
|
||||||
|
|
||||||
|
// everything in them should be in us
|
||||||
|
lesser := make(sortedMap[bool])
|
||||||
|
for _, s := range them {
|
||||||
|
lesser[s] = true
|
||||||
|
}
|
||||||
|
greater := make(sortedMap[bool]) // set of fields we have
|
||||||
|
for _, s := range us {
|
||||||
|
greater[s] = true
|
||||||
|
}
|
||||||
|
for _, k := range lesser.keys() { // set if fields they have
|
||||||
|
if !greater[k] {
|
||||||
|
t.Errorf("missing %s", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// flatten(nil) = "nil"
|
||||||
|
// flatten(v string) = fmt.Sprintf("%q", v)
|
||||||
|
// flatten(v float64)= fmt.Sprintf("%g", v)
|
||||||
|
// flatten(v bool) = fmt.Sprintf("%v", v)
|
||||||
|
// flatten(v []any) = []string{"[0]"flatten(v[0]), "[1]"flatten(v[1]), ...}
|
||||||
|
// flatten(v map[string]any) = {"key1": flatten(v["key1"]), "key2": flatten(v["key2"]), ...}
|
||||||
|
func flatten(x any) []string {
|
||||||
|
switch v := x.(type) {
|
||||||
|
case nil:
|
||||||
|
return []string{"nil"}
|
||||||
|
case string:
|
||||||
|
return []string{fmt.Sprintf("%q", v)}
|
||||||
|
case float64:
|
||||||
|
return []string{fmt.Sprintf("%g", v)}
|
||||||
|
case bool:
|
||||||
|
return []string{fmt.Sprintf("%v", v)}
|
||||||
|
case []any:
|
||||||
|
var ans []string
|
||||||
|
for i, x := range v {
|
||||||
|
idx := fmt.Sprintf("[%.3d]", i)
|
||||||
|
for _, s := range flatten(x) {
|
||||||
|
ans = append(ans, idx+s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ans
|
||||||
|
case map[string]any:
|
||||||
|
var ans []string
|
||||||
|
for k, x := range v {
|
||||||
|
idx := fmt.Sprintf("%q:", k)
|
||||||
|
for _, s := range flatten(x) {
|
||||||
|
ans = append(ans, idx+s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ans
|
||||||
|
default:
|
||||||
|
log.Fatalf("unexpected type %T", x)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.19
|
||||||
|
// +build go1.19
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// assign names to types. many types come with names, but names
|
||||||
|
// have to be provided for "or", "and", "tuple", and "literal" types.
|
||||||
|
// Only one tuple type occurs, so it poses no problem. Otherwise
|
||||||
|
// the name cannot depend on the ordering of the components, as permuting
|
||||||
|
// them doesn't change the type. One possibility is to build the name
|
||||||
|
// of the type out of the names of its components, done in an
|
||||||
|
// earlier version of this code, but rejected by code reviewers.
|
||||||
|
// (the name would change if the components changed.)
|
||||||
|
// An alternate is to use the definition context, which is what is done here
|
||||||
|
// and works for the existing code. However, it cannot work in general.
|
||||||
|
// (This easiest case is an "or" type with two "literal" components.
|
||||||
|
// The components will get the same name, as their definition contexts
|
||||||
|
// are identical.) spec.byName contains enough information to detect
|
||||||
|
// such cases. (Note that sometimes giving the same name to different
|
||||||
|
// types is correct, for instance when they involve stringLiterals.)
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// stacks contain information about the ancestry of a type
|
||||||
|
// (spaces and initial capital letters are treated specially in stack.name())
|
||||||
|
type stack []string
|
||||||
|
|
||||||
|
func (s stack) push(v string) stack {
|
||||||
|
return append(s, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s stack) pop() {
|
||||||
|
s = s[:len(s)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate a type name from the stack that contains its ancestry
|
||||||
|
//
|
||||||
|
// For instance, ["Result textDocument/implementation"] becomes "_textDocument_implementation"
|
||||||
|
// which, after being returned, becomes "Or_textDocument_implementation",
|
||||||
|
// which will become "[]Location" eventually (for gopls compatibility).
|
||||||
|
func (s stack) name(prefix string) string {
|
||||||
|
var nm string
|
||||||
|
var seen int
|
||||||
|
// use the most recent 2 entries, if there are 2,
|
||||||
|
// or just the only one.
|
||||||
|
for i := len(s) - 1; i >= 0 && seen < 2; i-- {
|
||||||
|
x := s[i]
|
||||||
|
if x[0] <= 'Z' && x[0] >= 'A' {
|
||||||
|
// it may contain a message
|
||||||
|
if idx := strings.Index(x, " "); idx >= 0 {
|
||||||
|
x = prefix + strings.Replace(x[idx+1:], "/", "_", -1)
|
||||||
|
}
|
||||||
|
nm += x
|
||||||
|
seen++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nm
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.19
|
||||||
|
// +build go1.19
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// Write the output
|
|
@ -0,0 +1,174 @@
|
||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.19
|
||||||
|
// +build go1.19
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// a spec contains the specification of the protocol, and derived information.
|
||||||
|
type spec struct {
|
||||||
|
model *Model
|
||||||
|
|
||||||
|
// combined Requests and Notifications, indexed by method (e.g., "textDocument/didOpen")
|
||||||
|
byMethod sortedMap[Message]
|
||||||
|
|
||||||
|
// Structures, Enumerations, and TypeAliases, indexed by name used in
|
||||||
|
// the .json specification file
|
||||||
|
// (Some Structure and Enumeration names need to be changed for Go,
|
||||||
|
// such as _Initialize)
|
||||||
|
byName sortedMap[Defined]
|
||||||
|
|
||||||
|
// computed type information
|
||||||
|
nameToTypes sortedMap[[]*Type] // all the uses of a type name
|
||||||
|
|
||||||
|
// remember which types are in a union type
|
||||||
|
orTypes sortedMap[sortedMap[bool]]
|
||||||
|
|
||||||
|
// information about the version of vscode-languageclient-node
|
||||||
|
githash string
|
||||||
|
modTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the specification file and return a spec.
|
||||||
|
// (TestParseContents checks that the parse gets all the fields of the specification)
|
||||||
|
func parse(dir string) *spec {
|
||||||
|
fname := filepath.Join(dir, "protocol", "metaModel.json")
|
||||||
|
buf, err := os.ReadFile(fname)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not read metaModel.json: %v", err)
|
||||||
|
}
|
||||||
|
// line numbers in the .json file occur as comments in tsprotocol.go
|
||||||
|
newbuf := addLineNumbers(buf)
|
||||||
|
var v Model
|
||||||
|
if err := json.Unmarshal(newbuf, &v); err != nil {
|
||||||
|
log.Fatalf("could not unmarshal metaModel.json: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ans := &spec{
|
||||||
|
model: &v,
|
||||||
|
byMethod: make(sortedMap[Message]),
|
||||||
|
byName: make(sortedMap[Defined]),
|
||||||
|
nameToTypes: make(sortedMap[[]*Type]),
|
||||||
|
orTypes: make(sortedMap[sortedMap[bool]]),
|
||||||
|
}
|
||||||
|
ans.githash, ans.modTime = gitInfo(dir)
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
|
||||||
|
// gitInfo returns the git hash and modtime of the repository.
|
||||||
|
func gitInfo(dir string) (string, time.Time) {
|
||||||
|
fname := dir + "/.git/HEAD"
|
||||||
|
buf, err := os.ReadFile(fname)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
buf = bytes.TrimSpace(buf)
|
||||||
|
var githash string
|
||||||
|
if len(buf) == 40 {
|
||||||
|
githash = string(buf[:40])
|
||||||
|
} else if bytes.HasPrefix(buf, []byte("ref: ")) {
|
||||||
|
fname = dir + "/.git/" + string(buf[5:])
|
||||||
|
buf, err = os.ReadFile(fname)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
githash = string(buf[:40])
|
||||||
|
} else {
|
||||||
|
log.Fatalf("githash cannot be recovered from %s", fname)
|
||||||
|
}
|
||||||
|
loadTime := time.Now()
|
||||||
|
return githash, loadTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// addLineNumbers adds a "line" field to each object in the JSON.
|
||||||
|
func addLineNumbers(buf []byte) []byte {
|
||||||
|
var ans []byte
|
||||||
|
// In the specification .json file, the delimiter '{' is
|
||||||
|
// always followed by a newline. There are other {s embedded in strings.
|
||||||
|
// json.Token does not return \n, or :, or , so using it would
|
||||||
|
// require parsing the json to reconstruct the missing information.
|
||||||
|
for linecnt, i := 1, 0; i < len(buf); i++ {
|
||||||
|
ans = append(ans, buf[i])
|
||||||
|
switch buf[i] {
|
||||||
|
case '{':
|
||||||
|
if buf[i+1] == '\n' {
|
||||||
|
ans = append(ans, fmt.Sprintf(`"line": %d, `, linecnt)...)
|
||||||
|
// warning: this would fail if the spec file had
|
||||||
|
// `"value": {\n}`, but it does not, as comma is a separator.
|
||||||
|
}
|
||||||
|
case '\n':
|
||||||
|
linecnt++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type.Value has to be treated specially for literals and maps
|
||||||
|
func (t *Type) UnmarshalJSON(data []byte) error {
|
||||||
|
// First unmarshal only the unambiguous fields.
|
||||||
|
var x struct {
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
Items []*Type `json:"items"`
|
||||||
|
Element *Type `json:"element"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Key *Type `json:"key"`
|
||||||
|
Value any `json:"value"`
|
||||||
|
Line int `json:"line"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &x); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*t = Type{
|
||||||
|
Kind: x.Kind,
|
||||||
|
Items: x.Items,
|
||||||
|
Element: x.Element,
|
||||||
|
Name: x.Name,
|
||||||
|
Value: x.Value,
|
||||||
|
Line: x.Line,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then unmarshal the 'value' field based on the kind.
|
||||||
|
// This depends on Unmarshal ignoring fields it doesn't know about.
|
||||||
|
switch x.Kind {
|
||||||
|
case "map":
|
||||||
|
var x struct {
|
||||||
|
Key *Type `json:"key"`
|
||||||
|
Value *Type `json:"value"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &x); err != nil {
|
||||||
|
return fmt.Errorf("Type.kind=map: %v", err)
|
||||||
|
}
|
||||||
|
t.Key = x.Key
|
||||||
|
t.Value = x.Value
|
||||||
|
|
||||||
|
case "literal":
|
||||||
|
var z struct {
|
||||||
|
Value ParseLiteral `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, &z); err != nil {
|
||||||
|
return fmt.Errorf("Type.kind=literal: %v", err)
|
||||||
|
}
|
||||||
|
t.Value = z.Value
|
||||||
|
|
||||||
|
case "base", "reference", "array", "and", "or", "tuple",
|
||||||
|
"stringLiteral":
|
||||||
|
// nop. never seen integerLiteral or booleanLiteral.
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("cannot decode Type.kind %q: %s", x.Kind, data)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,173 @@
|
||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.19
|
||||||
|
// +build go1.19
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "sort"
|
||||||
|
|
||||||
|
// Model contains the parsed version of the spec
|
||||||
|
type Model struct {
|
||||||
|
Version Metadata `json:"metaData"`
|
||||||
|
Requests []Request `json:"requests"`
|
||||||
|
Notifications []Notification `json:"notifications"`
|
||||||
|
Structures []Structure `json:"structures"`
|
||||||
|
Enumerations []Enumeration `json:"enumerations"`
|
||||||
|
TypeAliases []TypeAlias `json:"typeAliases"`
|
||||||
|
Line int `json:"line"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata is information about the version of the spec
|
||||||
|
type Metadata struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Line int `json:"line"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Request is the parsed version of an LSP request
|
||||||
|
type Request struct {
|
||||||
|
Documentation string `json:"documentation"`
|
||||||
|
ErrorData *Type `json:"errorData"`
|
||||||
|
Direction string `json:"messageDirection"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Params *Type `json:"params"`
|
||||||
|
PartialResult *Type `json:"partialResult"`
|
||||||
|
Proposed bool `json:"proposed"`
|
||||||
|
RegistrationMethod string `json:"registrationMethod"`
|
||||||
|
RegistrationOptions *Type `json:"registrationOptions"`
|
||||||
|
Result *Type `json:"result"`
|
||||||
|
Since string `json:"since"`
|
||||||
|
Line int `json:"line"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Notificatin is the parsed version of an LSP notification
|
||||||
|
type Notification struct {
|
||||||
|
Documentation string `json:"documentation"`
|
||||||
|
Direction string `json:"messageDirection"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Params *Type `json:"params"`
|
||||||
|
Proposed bool `json:"proposed"`
|
||||||
|
RegistrationMethod string `json:"registrationMethod"`
|
||||||
|
RegistrationOptions *Type `json:"registrationOptions"`
|
||||||
|
Since string `json:"since"`
|
||||||
|
Line int `json:"line"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Structure is the parsed version of an LSP structure from the spec
|
||||||
|
type Structure struct {
|
||||||
|
Documentation string `json:"documentation"`
|
||||||
|
Extends []*Type `json:"extends"`
|
||||||
|
Mixins []*Type `json:"mixins"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Properties []NameType `json:"properties"`
|
||||||
|
Proposed bool `json:"proposed"`
|
||||||
|
Since string `json:"since"`
|
||||||
|
Line int `json:"line"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// An enumeration is the parsed version of an LSP enumeration from the spec
|
||||||
|
type Enumeration struct {
|
||||||
|
Documentation string `json:"documentation"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Proposed bool `json:"proposed"`
|
||||||
|
Since string `json:"since"`
|
||||||
|
SupportsCustomValues bool `json:"supportsCustomValues"`
|
||||||
|
Type *Type `json:"type"`
|
||||||
|
Values []NameValue `json:"values"`
|
||||||
|
Line int `json:"line"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// A TypeAlias is the parsed version of an LSP type alias from the spec
|
||||||
|
type TypeAlias struct {
|
||||||
|
Documentation string `json:"documentation"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Proposed bool `json:"proposed"`
|
||||||
|
Since string `json:"since"`
|
||||||
|
Type *Type `json:"type"`
|
||||||
|
Line int `json:"line"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// A NameValue describes an enumeration constant
|
||||||
|
type NameValue struct {
|
||||||
|
Documentation string `json:"documentation"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Proposed bool `json:"proposed"`
|
||||||
|
Since string `json:"since"`
|
||||||
|
Value any `json:"value"` // number or string
|
||||||
|
Line int `json:"line"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// common to Request and Notification
|
||||||
|
type Message interface {
|
||||||
|
direction() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Request) direction() string {
|
||||||
|
return r.Direction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n Notification) direction() string {
|
||||||
|
return n.Direction
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Defined is one of Structure, Enumeration, TypeAlias, for type checking
|
||||||
|
type Defined interface {
|
||||||
|
tag()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Structure) tag() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Enumeration) tag() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ta TypeAlias) tag() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Type is the parsed version of an LSP type from the spec,
|
||||||
|
// or a Type the code constructs
|
||||||
|
type Type struct {
|
||||||
|
Kind string `json:"kind"` // -- which kind goes with which field --
|
||||||
|
Items []*Type `json:"items"` // "and", "or", "tuple"
|
||||||
|
Element *Type `json:"element"` // "array"
|
||||||
|
Name string `json:"name"` // "base", "reference"
|
||||||
|
Key *Type `json:"key"` // "map"
|
||||||
|
Value any `json:"value"` // "map", "stringLiteral", "literal"
|
||||||
|
// used to tie generated code to the specification
|
||||||
|
Line int `json:"line"`
|
||||||
|
|
||||||
|
name string // these are generated names, like Uint32
|
||||||
|
typeName string // these are actual type names, like uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsedLiteral is Type.Value when Type.Kind is "literal"
|
||||||
|
type ParseLiteral struct {
|
||||||
|
Properties `json:"properties"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// A NameType represents the name and type of a structure element
|
||||||
|
type NameType struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type *Type `json:"type"`
|
||||||
|
Optional bool `json:"optional"`
|
||||||
|
Documentation string `json:"documentation"`
|
||||||
|
Since string `json:"since"`
|
||||||
|
Proposed bool `json:"proposed"`
|
||||||
|
Line int `json:"line"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Properties are the collection of structure elements
|
||||||
|
type Properties []NameType
|
||||||
|
|
||||||
|
type sortedMap[T any] map[string]T
|
||||||
|
|
||||||
|
func (s sortedMap[T]) keys() []string {
|
||||||
|
var keys []string
|
||||||
|
for k := range s {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return keys
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.19
|
||||||
|
// +build go1.19
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// goName returns the Go version of a name.
|
||||||
|
func goName(s string) string {
|
||||||
|
if s == "" {
|
||||||
|
return s // doesn't happen
|
||||||
|
}
|
||||||
|
s = strings.ToUpper(s[:1]) + s[1:]
|
||||||
|
if rest := strings.TrimSuffix(s, "Uri"); rest != s {
|
||||||
|
s = rest + "URI"
|
||||||
|
}
|
||||||
|
if rest := strings.TrimSuffix(s, "Id"); rest != s {
|
||||||
|
s = rest + "ID"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// the common header for all generated files
|
||||||
|
func (s *spec) createHeader() string {
|
||||||
|
format := `// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Code generated for LSP. DO NOT EDIT.
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
// Code generated from version %s of protocol/metaModel.json.
|
||||||
|
// git hash %s (as of %s)
|
||||||
|
|
||||||
|
`
|
||||||
|
hdr := fmt.Sprintf(format, s.model.Version.Version, s.githash, s.modTime.Format(time.ANSIC))
|
||||||
|
return hdr
|
||||||
|
}
|
||||||
|
|
||||||
|
// useful in debugging
|
||||||
|
func here() {
|
||||||
|
_, f, l, _ := runtime.Caller(1)
|
||||||
|
log.Printf("here: %s:%d", f, l)
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче