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 }