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:
pjw 2022-10-14 09:26:03 -04:00 коммит произвёл Peter Weinberger
Родитель 121f889bbc
Коммит 3e1371fd13
11 изменённых файлов: 847 добавлений и 0 удалений

Просмотреть файл

@ -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)
}