golang.org/x/tools/gopls@v0.15.3/internal/protocol/generate/main.go (about)

     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  // The generate command generates Go declarations from VSCode's
     9  // description of the Language Server Protocol.
    10  //
    11  // To run it, type 'go generate' in the parent (protocol) directory.
    12  package main
    13  
    14  // see https://github.com/golang/go/issues/61217 for discussion of an issue
    15  
    16  import (
    17  	"bytes"
    18  	"encoding/json"
    19  	"flag"
    20  	"fmt"
    21  	"go/format"
    22  	"log"
    23  	"os"
    24  	"os/exec"
    25  	"path/filepath"
    26  	"strings"
    27  )
    28  
    29  const vscodeRepo = "https://github.com/microsoft/vscode-languageserver-node"
    30  
    31  // lspGitRef names a branch or tag in vscodeRepo.
    32  // It implicitly determines the protocol version of the LSP used by gopls.
    33  // For example, tag release/protocol/3.17.3 of the repo defines protocol version 3.17.0.
    34  // (Point releases are reflected in the git tag version even when they are cosmetic
    35  // and don't change the protocol.)
    36  var lspGitRef = "release/protocol/3.17.6-next.1"
    37  
    38  var (
    39  	repodir   = flag.String("d", "", "directory containing clone of "+vscodeRepo)
    40  	outputdir = flag.String("o", ".", "output directory")
    41  	// PJW: not for real code
    42  	cmpdir      = flag.String("c", "", "directory of earlier code")
    43  	doboth      = flag.String("b", "", "generate and compare")
    44  	lineNumbers = flag.Bool("l", false, "add line numbers to generated output")
    45  )
    46  
    47  func main() {
    48  	log.SetFlags(log.Lshortfile) // log file name and line number, not time
    49  	flag.Parse()
    50  
    51  	processinline()
    52  }
    53  
    54  func processinline() {
    55  	// A local repository may be specified during debugging.
    56  	// The default behavior is to download the canonical version.
    57  	if *repodir == "" {
    58  		tmpdir, err := os.MkdirTemp("", "")
    59  		if err != nil {
    60  			log.Fatal(err)
    61  		}
    62  		defer os.RemoveAll(tmpdir) // ignore error
    63  
    64  		// Clone the repository.
    65  		cmd := exec.Command("git", "clone", "--quiet", "--depth=1", "-c", "advice.detachedHead=false", vscodeRepo, "--branch="+lspGitRef, "--single-branch", tmpdir)
    66  		cmd.Stdout = os.Stderr
    67  		cmd.Stderr = os.Stderr
    68  		if err := cmd.Run(); err != nil {
    69  			log.Fatal(err)
    70  		}
    71  
    72  		*repodir = tmpdir
    73  	} else {
    74  		lspGitRef = fmt.Sprintf("(not git, local dir %s)", *repodir)
    75  	}
    76  
    77  	model := parse(filepath.Join(*repodir, "protocol/metaModel.json"))
    78  
    79  	findTypeNames(model)
    80  	generateOutput(model)
    81  
    82  	fileHdr = fileHeader(model)
    83  
    84  	// write the files
    85  	writeclient()
    86  	writeserver()
    87  	writeprotocol()
    88  	writejsons()
    89  
    90  	checkTables()
    91  }
    92  
    93  // common file header for output files
    94  var fileHdr string
    95  
    96  func writeclient() {
    97  	out := new(bytes.Buffer)
    98  	fmt.Fprintln(out, fileHdr)
    99  	out.WriteString(
   100  		`import (
   101  	"context"
   102  
   103  	"golang.org/x/tools/internal/jsonrpc2"
   104  )
   105  `)
   106  	out.WriteString("type Client interface {\n")
   107  	for _, k := range cdecls.keys() {
   108  		out.WriteString(cdecls[k])
   109  	}
   110  	out.WriteString("}\n\n")
   111  	out.WriteString(`func clientDispatch(ctx context.Context, client Client, reply jsonrpc2.Replier, r jsonrpc2.Request) (bool, error) {
   112  	defer recoverHandlerPanic(r.Method())
   113  	switch r.Method() {
   114  `)
   115  	for _, k := range ccases.keys() {
   116  		out.WriteString(ccases[k])
   117  	}
   118  	out.WriteString(("\tdefault:\n\t\treturn false, nil\n\t}\n}\n\n"))
   119  	for _, k := range cfuncs.keys() {
   120  		out.WriteString(cfuncs[k])
   121  	}
   122  
   123  	x, err := format.Source(out.Bytes())
   124  	if err != nil {
   125  		os.WriteFile("/tmp/a.go", out.Bytes(), 0644)
   126  		log.Fatalf("tsclient.go: %v", err)
   127  	}
   128  
   129  	if err := os.WriteFile(filepath.Join(*outputdir, "tsclient.go"), x, 0644); err != nil {
   130  		log.Fatalf("%v writing tsclient.go", err)
   131  	}
   132  }
   133  
   134  func writeserver() {
   135  	out := new(bytes.Buffer)
   136  	fmt.Fprintln(out, fileHdr)
   137  	out.WriteString(
   138  		`import (
   139  	"context"
   140  
   141  	"golang.org/x/tools/internal/jsonrpc2"
   142  )
   143  `)
   144  	out.WriteString("type Server interface {\n")
   145  	for _, k := range sdecls.keys() {
   146  		out.WriteString(sdecls[k])
   147  	}
   148  	out.WriteString(`
   149  }
   150  
   151  func serverDispatch(ctx context.Context, server Server, reply jsonrpc2.Replier, r jsonrpc2.Request) (bool, error) {
   152  	defer recoverHandlerPanic(r.Method())
   153  	switch r.Method() {
   154  `)
   155  	for _, k := range scases.keys() {
   156  		out.WriteString(scases[k])
   157  	}
   158  	out.WriteString(("\tdefault:\n\t\treturn false, nil\n\t}\n}\n\n"))
   159  	for _, k := range sfuncs.keys() {
   160  		out.WriteString(sfuncs[k])
   161  	}
   162  	x, err := format.Source(out.Bytes())
   163  	if err != nil {
   164  		os.WriteFile("/tmp/a.go", out.Bytes(), 0644)
   165  		log.Fatalf("tsserver.go: %v", err)
   166  	}
   167  
   168  	if err := os.WriteFile(filepath.Join(*outputdir, "tsserver.go"), x, 0644); err != nil {
   169  		log.Fatalf("%v writing tsserver.go", err)
   170  	}
   171  }
   172  
   173  func writeprotocol() {
   174  	out := new(bytes.Buffer)
   175  	fmt.Fprintln(out, fileHdr)
   176  	out.WriteString("import \"encoding/json\"\n\n")
   177  
   178  	// The followiing are unneeded, but make the new code a superset of the old
   179  	hack := func(newer, existing string) {
   180  		if _, ok := types[existing]; !ok {
   181  			log.Fatalf("types[%q] not found", existing)
   182  		}
   183  		types[newer] = strings.Replace(types[existing], existing, newer, 1)
   184  	}
   185  	hack("ConfigurationParams", "ParamConfiguration")
   186  	hack("InitializeParams", "ParamInitialize")
   187  	hack("PreviousResultId", "PreviousResultID")
   188  	hack("WorkspaceFoldersServerCapabilities", "WorkspaceFolders5Gn")
   189  	hack("_InitializeParams", "XInitializeParams")
   190  
   191  	for _, k := range types.keys() {
   192  		if k == "WatchKind" {
   193  			types[k] = "type WatchKind = uint32" // strict gopls compatibility needs the '='
   194  		}
   195  		out.WriteString(types[k])
   196  	}
   197  
   198  	out.WriteString("\nconst (\n")
   199  	for _, k := range consts.keys() {
   200  		out.WriteString(consts[k])
   201  	}
   202  	out.WriteString(")\n\n")
   203  	x, err := format.Source(out.Bytes())
   204  	if err != nil {
   205  		os.WriteFile("/tmp/a.go", out.Bytes(), 0644)
   206  		log.Fatalf("tsprotocol.go: %v", err)
   207  	}
   208  	if err := os.WriteFile(filepath.Join(*outputdir, "tsprotocol.go"), x, 0644); err != nil {
   209  		log.Fatalf("%v writing tsprotocol.go", err)
   210  	}
   211  }
   212  
   213  func writejsons() {
   214  	out := new(bytes.Buffer)
   215  	fmt.Fprintln(out, fileHdr)
   216  	out.WriteString("import \"encoding/json\"\n\n")
   217  	out.WriteString("import \"fmt\"\n")
   218  
   219  	out.WriteString(`
   220  // UnmarshalError indicates that a JSON value did not conform to
   221  // one of the expected cases of an LSP union type.
   222  type UnmarshalError struct {
   223  	msg string
   224  }
   225  
   226  func (e UnmarshalError) Error() string {
   227  	return e.msg
   228  }
   229  `)
   230  
   231  	for _, k := range jsons.keys() {
   232  		out.WriteString(jsons[k])
   233  	}
   234  	x, err := format.Source(out.Bytes())
   235  	if err != nil {
   236  		os.WriteFile("/tmp/a.go", out.Bytes(), 0644)
   237  		log.Fatalf("tsjson.go: %v", err)
   238  	}
   239  	if err := os.WriteFile(filepath.Join(*outputdir, "tsjson.go"), x, 0644); err != nil {
   240  		log.Fatalf("%v writing tsjson.go", err)
   241  	}
   242  }
   243  
   244  // create the common file header for the output files
   245  func fileHeader(model Model) string {
   246  	fname := filepath.Join(*repodir, ".git", "HEAD")
   247  	buf, err := os.ReadFile(fname)
   248  	if err != nil {
   249  		log.Fatal(err)
   250  	}
   251  	buf = bytes.TrimSpace(buf)
   252  	var githash string
   253  	if len(buf) == 40 {
   254  		githash = string(buf[:40])
   255  	} else if bytes.HasPrefix(buf, []byte("ref: ")) {
   256  		fname = filepath.Join(*repodir, ".git", string(buf[5:]))
   257  		buf, err = os.ReadFile(fname)
   258  		if err != nil {
   259  			log.Fatal(err)
   260  		}
   261  		githash = string(buf[:40])
   262  	} else {
   263  		log.Fatalf("githash cannot be recovered from %s", fname)
   264  	}
   265  
   266  	format := `// Copyright 2023 The Go Authors. All rights reserved.
   267  // Use of this source code is governed by a BSD-style
   268  // license that can be found in the LICENSE file.
   269  
   270  // Code generated for LSP. DO NOT EDIT.
   271  
   272  package protocol
   273  
   274  // Code generated from %[1]s at ref %[2]s (hash %[3]s).
   275  // %[4]s/blob/%[2]s/%[1]s
   276  // LSP metaData.version = %[5]s.
   277  
   278  `
   279  	return fmt.Sprintf(format,
   280  		"protocol/metaModel.json", // 1
   281  		lspGitRef,                 // 2
   282  		githash,                   // 3
   283  		vscodeRepo,                // 4
   284  		model.Version.Version)     // 5
   285  }
   286  
   287  func parse(fname string) Model {
   288  	buf, err := os.ReadFile(fname)
   289  	if err != nil {
   290  		log.Fatal(err)
   291  	}
   292  	buf = addLineNumbers(buf)
   293  	var model Model
   294  	if err := json.Unmarshal(buf, &model); err != nil {
   295  		log.Fatal(err)
   296  	}
   297  	return model
   298  }
   299  
   300  // Type.Value has to be treated specially for literals and maps
   301  func (t *Type) UnmarshalJSON(data []byte) error {
   302  	// First unmarshal only the unambiguous fields.
   303  	var x struct {
   304  		Kind    string  `json:"kind"`
   305  		Items   []*Type `json:"items"`
   306  		Element *Type   `json:"element"`
   307  		Name    string  `json:"name"`
   308  		Key     *Type   `json:"key"`
   309  		Value   any     `json:"value"`
   310  		Line    int     `json:"line"`
   311  	}
   312  	if err := json.Unmarshal(data, &x); err != nil {
   313  		return err
   314  	}
   315  	*t = Type{
   316  		Kind:    x.Kind,
   317  		Items:   x.Items,
   318  		Element: x.Element,
   319  		Name:    x.Name,
   320  		Value:   x.Value,
   321  		Line:    x.Line,
   322  	}
   323  
   324  	// Then unmarshal the 'value' field based on the kind.
   325  	// This depends on Unmarshal ignoring fields it doesn't know about.
   326  	switch x.Kind {
   327  	case "map":
   328  		var x struct {
   329  			Key   *Type `json:"key"`
   330  			Value *Type `json:"value"`
   331  		}
   332  		if err := json.Unmarshal(data, &x); err != nil {
   333  			return fmt.Errorf("Type.kind=map: %v", err)
   334  		}
   335  		t.Key = x.Key
   336  		t.Value = x.Value
   337  
   338  	case "literal":
   339  		var z struct {
   340  			Value ParseLiteral `json:"value"`
   341  		}
   342  
   343  		if err := json.Unmarshal(data, &z); err != nil {
   344  			return fmt.Errorf("Type.kind=literal: %v", err)
   345  		}
   346  		t.Value = z.Value
   347  
   348  	case "base", "reference", "array", "and", "or", "tuple",
   349  		"stringLiteral":
   350  		// no-op. never seen integerLiteral or booleanLiteral.
   351  
   352  	default:
   353  		return fmt.Errorf("cannot decode Type.kind %q: %s", x.Kind, data)
   354  	}
   355  	return nil
   356  }
   357  
   358  // which table entries were not used
   359  func checkTables() {
   360  	for k := range disambiguate {
   361  		if !usedDisambiguate[k] {
   362  			log.Printf("disambiguate[%v] unused", k)
   363  		}
   364  	}
   365  	for k := range renameProp {
   366  		if !usedRenameProp[k] {
   367  			log.Printf("renameProp {%q, %q} unused", k[0], k[1])
   368  		}
   369  	}
   370  	for k := range goplsStar {
   371  		if !usedGoplsStar[k] {
   372  			log.Printf("goplsStar {%q, %q} unused", k[0], k[1])
   373  		}
   374  	}
   375  	for k := range goplsType {
   376  		if !usedGoplsType[k] {
   377  			log.Printf("unused goplsType[%q]->%s", k, goplsType[k])
   378  		}
   379  	}
   380  }