Skip to content

Commit

Permalink
gopls/internal/protocol: add links to LSP spec
Browse files Browse the repository at this point in the history
This makes it a little easier to click one's way to the
authoritative description.

It isn't perfect; notably, the LSP spec has irregular
anchors (workspace_symbolResolve should be
workspaceSymbol_Resolve).

Change-Id: I62f88b53d2398d777a298ca765f6c71167761e74
Reviewed-on: https://go-review.googlesource.com/c/tools/+/581120
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Peter Weinberger <pjw@google.com>
  • Loading branch information
adonovan committed May 6, 2024
1 parent e2a352c commit 397fef9
Show file tree
Hide file tree
Showing 6 changed files with 1,035 additions and 146 deletions.
57 changes: 20 additions & 37 deletions gopls/internal/protocol/generate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ const vscodeRepo = "https://github.com/microsoft/vscode-languageserver-node"

// lspGitRef names a branch or tag in vscodeRepo.
// It implicitly determines the protocol version of the LSP used by gopls.
// For example, tag release/protocol/3.17.3 of the repo defines protocol version 3.17.0.
// For example, tag release/protocol/3.17.3 of the repo defines
// protocol version 3.17.0 (as declared by the metaData.version field).
// (Point releases are reflected in the git tag version even when they are cosmetic
// and don't change the protocol.)
var lspGitRef = "release/protocol/3.17.6-next.2"
Expand Down Expand Up @@ -116,16 +117,7 @@ func writeclient() {
for _, k := range cfuncs.keys() {
out.WriteString(cfuncs[k])
}

x, err := format.Source(out.Bytes())
if err != nil {
os.WriteFile("/tmp/a.go", out.Bytes(), 0644)
log.Fatalf("tsclient.go: %v", err)
}

if err := os.WriteFile(filepath.Join(*outputdir, "tsclient.go"), x, 0644); err != nil {
log.Fatalf("%v writing tsclient.go", err)
}
formatTo("tsclient.go", out.Bytes())
}

func writeserver() {
Expand Down Expand Up @@ -156,15 +148,7 @@ func serverDispatch(ctx context.Context, server Server, reply jsonrpc2.Replier,
for _, k := range sfuncs.keys() {
out.WriteString(sfuncs[k])
}
x, err := format.Source(out.Bytes())
if err != nil {
os.WriteFile("/tmp/a.go", out.Bytes(), 0644)
log.Fatalf("tsserver.go: %v", err)
}

if err := os.WriteFile(filepath.Join(*outputdir, "tsserver.go"), x, 0644); err != nil {
log.Fatalf("%v writing tsserver.go", err)
}
formatTo("tsserver.go", out.Bytes())
}

func writeprotocol() {
Expand Down Expand Up @@ -197,14 +181,7 @@ func writeprotocol() {
out.WriteString(consts[k])
}
out.WriteString(")\n\n")
x, err := format.Source(out.Bytes())
if err != nil {
os.WriteFile("/tmp/a.go", out.Bytes(), 0644)
log.Fatalf("tsprotocol.go: %v", err)
}
if err := os.WriteFile(filepath.Join(*outputdir, "tsprotocol.go"), x, 0644); err != nil {
log.Fatalf("%v writing tsprotocol.go", err)
}
formatTo("tsprotocol.go", out.Bytes())
}

func writejsons() {
Expand All @@ -228,18 +205,24 @@ func (e UnmarshalError) Error() string {
for _, k := range jsons.keys() {
out.WriteString(jsons[k])
}
x, err := format.Source(out.Bytes())
formatTo("tsjson.go", out.Bytes())
}

// formatTo formats the Go source and writes it to *outputdir/basename.
func formatTo(basename string, src []byte) {
formatted, err := format.Source(src)
if err != nil {
os.WriteFile("/tmp/a.go", out.Bytes(), 0644)
log.Fatalf("tsjson.go: %v", err)
failed := filepath.Join("/tmp", basename+".fail")
os.WriteFile(failed, src, 0644)
log.Fatalf("formatting %s: %v (see %s)", basename, err, failed)
}
if err := os.WriteFile(filepath.Join(*outputdir, "tsjson.go"), x, 0644); err != nil {
log.Fatalf("%v writing tsjson.go", err)
if err := os.WriteFile(filepath.Join(*outputdir, basename), formatted, 0644); err != nil {
log.Fatal(err)
}
}

// create the common file header for the output files
func fileHeader(model Model) string {
func fileHeader(model *Model) string {
fname := filepath.Join(*repodir, ".git", "HEAD")
buf, err := os.ReadFile(fname)
if err != nil {
Expand Down Expand Up @@ -281,14 +264,14 @@ package protocol
model.Version.Version) // 5
}

func parse(fname string) Model {
func parse(fname string) *Model {
buf, err := os.ReadFile(fname)
if err != nil {
log.Fatal(err)
}
buf = addLineNumbers(buf)
var model Model
if err := json.Unmarshal(buf, &model); err != nil {
model := new(Model)
if err := json.Unmarshal(buf, model); err != nil {
log.Fatal(err)
}
return model
Expand Down
46 changes: 32 additions & 14 deletions gopls/internal/protocol/generate/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,19 @@ var (
jsons = make(sortedMap[string])
)

func generateOutput(model Model) {
func generateOutput(model *Model) {
for _, r := range model.Requests {
genDecl(r.Method, r.Params, r.Result, r.Direction)
genCase(r.Method, r.Params, r.Result, r.Direction)
genFunc(r.Method, r.Params, r.Result, r.Direction, false)
genDecl(model, r.Method, r.Params, r.Result, r.Direction)
genCase(model, r.Method, r.Params, r.Result, r.Direction)
genFunc(model, r.Method, r.Params, r.Result, r.Direction, false)
}
for _, n := range model.Notifications {
if n.Method == "$/cancelRequest" {
continue // handled internally by jsonrpc2
}
genDecl(n.Method, n.Params, nil, n.Direction)
genCase(n.Method, n.Params, nil, n.Direction)
genFunc(n.Method, n.Params, nil, n.Direction, true)
genDecl(model, n.Method, n.Params, nil, n.Direction)
genCase(model, n.Method, n.Params, nil, n.Direction)
genFunc(model, n.Method, n.Params, nil, n.Direction, true)
}
genStructs(model)
genAliases(model)
Expand All @@ -49,7 +49,7 @@ func generateOutput(model Model) {
genMarshal()
}

func genDecl(method string, param, result *Type, dir string) {
func genDecl(model *Model, method string, param, result *Type, dir string) {
fname := methodName(method)
p := ""
if notNil(param) {
Expand All @@ -71,7 +71,8 @@ func genDecl(method string, param, result *Type, dir string) {
p = ", *ParamConfiguration"
ret = "([]LSPAny, error)"
}
msg := fmt.Sprintf("\t%s(context.Context%s) %s // %s\n", fname, p, ret, method)
fragment := strings.ReplaceAll(strings.TrimPrefix(method, "$/"), "/", "_")
msg := fmt.Sprintf("\t%s\t%s(context.Context%s) %s\n", lspLink(model, fragment), fname, p, ret)
switch dir {
case "clientToServer":
sdecls[method] = msg
Expand All @@ -85,7 +86,7 @@ func genDecl(method string, param, result *Type, dir string) {
}
}

func genCase(method string, param, result *Type, dir string) {
func genCase(model *Model, method string, param, result *Type, dir string) {
out := new(bytes.Buffer)
fmt.Fprintf(out, "\tcase %q:\n", method)
var p string
Expand Down Expand Up @@ -127,7 +128,7 @@ func genCase(method string, param, result *Type, dir string) {
}
}

func genFunc(method string, param, result *Type, dir string, isnotify bool) {
func genFunc(model *Model, method string, param, result *Type, dir string, isnotify bool) {
out := new(bytes.Buffer)
var p, r string
var goResult string
Expand Down Expand Up @@ -202,7 +203,7 @@ func genFunc(method string, param, result *Type, dir string, isnotify bool) {
}
}

func genStructs(model Model) {
func genStructs(model *Model) {
structures := make(map[string]*Structure) // for expanding Extends
for _, s := range model.Structures {
structures[s.Name] = s
Expand All @@ -215,6 +216,8 @@ func genStructs(model Model) {
// a weird case, and needed only so the generated code contains the old gopls code
nm = "DocumentDiagnosticParams"
}
fmt.Fprintf(out, "//\n")
out.WriteString(lspLink(model, camelCase(s.Name)))
fmt.Fprintf(out, "type %s struct {%s\n", nm, linex(s.Line))
// for gpls compatibilitye, embed most extensions, but expand the rest some day
props := append([]NameType{}, s.Properties...)
Expand Down Expand Up @@ -245,6 +248,19 @@ func genStructs(model Model) {

}

// "FooBar" -> "fooBar"
func camelCase(TitleCased string) string {
return strings.ToLower(TitleCased[:1]) + TitleCased[1:]
}

func lspLink(model *Model, fragment string) string {
// Derive URL version from metaData.version in JSON file.
parts := strings.Split(model.Version.Version, ".") // e.g. "3.17.0"
return fmt.Sprintf("// See https://microsoft.github.io/language-server-protocol/specifications/lsp/%s.%s/specification#%s\n",
parts[0], parts[1], // major.minor
fragment)
}

func genProps(out *bytes.Buffer, props []NameType, name string) {
for _, p := range props {
tp := goplsName(p.Type)
Expand All @@ -263,7 +279,7 @@ func genProps(out *bytes.Buffer, props []NameType, name string) {
}
}

func genAliases(model Model) {
func genAliases(model *Model) {
for _, ta := range model.TypeAliases {
out := new(bytes.Buffer)
generateDoc(out, ta.Documentation)
Expand All @@ -272,6 +288,8 @@ func genAliases(model Model) {
continue // renamed the type, e.g., "DocumentDiagnosticReport", an or-type to "string"
}
tp := goplsName(ta.Type)
fmt.Fprintf(out, "//\n")
out.WriteString(lspLink(model, camelCase(ta.Name)))
fmt.Fprintf(out, "type %s = %s // (alias)\n", nm, tp)
types[nm] = out.String()
}
Expand Down Expand Up @@ -320,7 +338,7 @@ func genGenTypes() {
types[nm] = out.String()
}
}
func genConsts(model Model) {
func genConsts(model *Model) {
for _, e := range model.Enumerations {
out := new(bytes.Buffer)
generateDoc(out, e.Documentation)
Expand Down
2 changes: 1 addition & 1 deletion gopls/internal/protocol/generate/typenames.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
var typeNames = make(map[*Type]string)
var genTypes []*newType

func findTypeNames(model Model) {
func findTypeNames(model *Model) {
for _, s := range model.Structures {
for _, e := range s.Extends {
nameType(e, nil) // all references
Expand Down
60 changes: 40 additions & 20 deletions gopls/internal/protocol/tsclient.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 397fef9

Please sign in to comment.