| // 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" |
| "fmt" |
| "log" |
| "sort" |
| "strings" |
| ) |
| |
| var ( |
| // tsclient.go has 3 sections |
| cdecls = make(sortedMap[string]) |
| ccases = make(sortedMap[string]) |
| cfuncs = make(sortedMap[string]) |
| // tsserver.go has 3 sections |
| sdecls = make(sortedMap[string]) |
| scases = make(sortedMap[string]) |
| sfuncs = make(sortedMap[string]) |
| // tsprotocol.go has 2 sections |
| types = make(sortedMap[string]) |
| consts = make(sortedMap[string]) |
| // tsjson has 1 section |
| jsons = make(sortedMap[string]) |
| ) |
| |
| 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) |
| } |
| 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) |
| } |
| genStructs(model) |
| genAliases(model) |
| genGenTypes() // generate the unnamed types |
| genConsts(model) |
| genMarshal() |
| } |
| |
| func genDecl(method string, param, result *Type, dir string) { |
| fname := methodNames[method] |
| p := "" |
| if notNil(param) { |
| p = ", *" + goplsName(param) |
| } |
| ret := "error" |
| if notNil(result) { |
| tp := goplsName(result) |
| if !hasNilValue(tp) { |
| tp = "*" + tp |
| } |
| ret = fmt.Sprintf("(%s, error)", tp) |
| } |
| // special gopls compatibility case (PJW: still needed?) |
| switch method { |
| case "workspace/configuration": |
| // was And_Param_workspace_configuration, but the type substitution doesn't work, |
| // as ParamConfiguration is embedded in And_Param_workspace_configuration |
| p = ", *ParamConfiguration" |
| ret = "([]LSPAny, error)" |
| } |
| msg := fmt.Sprintf("\t%s(context.Context%s) %s // %s\n", fname, p, ret, method) |
| switch dir { |
| case "clientToServer": |
| sdecls[method] = msg |
| case "serverToClient": |
| cdecls[method] = msg |
| case "both": |
| sdecls[method] = msg |
| cdecls[method] = msg |
| default: |
| log.Fatalf("impossible direction %q", dir) |
| } |
| } |
| |
| func genCase(method string, param, result *Type, dir string) { |
| out := new(bytes.Buffer) |
| fmt.Fprintf(out, "\tcase %q:\n", method) |
| var p string |
| fname := methodNames[method] |
| if notNil(param) { |
| nm := goplsName(param) |
| if method == "workspace/configuration" { // gopls compatibility |
| // was And_Param_workspace_configuration, which contains ParamConfiguration |
| // so renaming the type leads to circular definitions |
| nm = "ParamConfiguration" // gopls compatibility |
| } |
| fmt.Fprintf(out, "\t\tvar params %s\n", nm) |
| fmt.Fprintf(out, "\t\tif err := json.Unmarshal(r.Params(), ¶ms); err != nil {\n") |
| fmt.Fprintf(out, "\t\t\treturn true, sendParseError(ctx, reply, err)\n\t\t}\n") |
| p = ", ¶ms" |
| } |
| if notNil(result) { |
| fmt.Fprintf(out, "\t\tresp, err := %%s.%s(ctx%s)\n", fname, p) |
| out.WriteString("\t\tif err != nil {\n") |
| out.WriteString("\t\t\treturn true, reply(ctx, nil, err)\n") |
| out.WriteString("\t\t}\n") |
| out.WriteString("\t\treturn true, reply(ctx, resp, nil)\n") |
| } else { |
| fmt.Fprintf(out, "\t\terr := %%s.%s(ctx%s)\n", fname, p) |
| out.WriteString("\t\treturn true, reply(ctx, nil, err)\n") |
| } |
| msg := out.String() |
| switch dir { |
| case "clientToServer": |
| scases[method] = fmt.Sprintf(msg, "server") |
| case "serverToClient": |
| ccases[method] = fmt.Sprintf(msg, "client") |
| case "both": |
| scases[method] = fmt.Sprintf(msg, "server") |
| ccases[method] = fmt.Sprintf(msg, "client") |
| default: |
| log.Fatalf("impossible direction %q", dir) |
| } |
| } |
| |
| func genFunc(method string, param, result *Type, dir string, isnotify bool) { |
| out := new(bytes.Buffer) |
| var p, r string |
| var goResult string |
| if notNil(param) { |
| p = ", params *" + goplsName(param) |
| } |
| if notNil(result) { |
| goResult = goplsName(result) |
| if !hasNilValue(goResult) { |
| goResult = "*" + goResult |
| } |
| r = fmt.Sprintf("(%s, error)", goResult) |
| } else { |
| r = "error" |
| } |
| // special gopls compatibility case |
| switch method { |
| case "workspace/configuration": |
| // was And_Param_workspace_configuration, but the type substitution doesn't work, |
| // as ParamConfiguration is embedded in And_Param_workspace_configuration |
| p = ", params *ParamConfiguration" |
| r = "([]LSPAny, error)" |
| goResult = "[]LSPAny" |
| } |
| fname := methodNames[method] |
| fmt.Fprintf(out, "func (s *%%sDispatcher) %s(ctx context.Context%s) %s {\n", |
| fname, p, r) |
| |
| if !notNil(result) { |
| if isnotify { |
| if notNil(param) { |
| fmt.Fprintf(out, "\treturn s.sender.Notify(ctx, %q, params)\n", method) |
| } else { |
| fmt.Fprintf(out, "\treturn s.sender.Notify(ctx, %q, nil)\n", method) |
| } |
| } else { |
| if notNil(param) { |
| fmt.Fprintf(out, "\treturn s.sender.Call(ctx, %q, params, nil)\n", method) |
| } else { |
| fmt.Fprintf(out, "\treturn s.sender.Call(ctx, %q, nil, nil)\n", method) |
| } |
| } |
| } else { |
| fmt.Fprintf(out, "\tvar result %s\n", goResult) |
| if isnotify { |
| if notNil(param) { |
| fmt.Fprintf(out, "\ts.sender.Notify(ctx, %q, params)\n", method) |
| } else { |
| fmt.Fprintf(out, "\t\tif err := s.sender.Notify(ctx, %q, nil); err != nil {\n", method) |
| } |
| } else { |
| if notNil(param) { |
| fmt.Fprintf(out, "\t\tif err := s.sender.Call(ctx, %q, params, &result); err != nil {\n", method) |
| } else { |
| fmt.Fprintf(out, "\t\tif err := s.sender.Call(ctx, %q, nil, &result); err != nil {\n", method) |
| } |
| } |
| fmt.Fprintf(out, "\t\treturn nil, err\n\t}\n\treturn result, nil\n") |
| } |
| out.WriteString("}\n") |
| msg := out.String() |
| switch dir { |
| case "clientToServer": |
| sfuncs[method] = fmt.Sprintf(msg, "server") |
| case "serverToClient": |
| cfuncs[method] = fmt.Sprintf(msg, "client") |
| case "both": |
| sfuncs[method] = fmt.Sprintf(msg, "server") |
| cfuncs[method] = fmt.Sprintf(msg, "client") |
| default: |
| log.Fatalf("impossible direction %q", dir) |
| } |
| } |
| |
| func genStructs(model Model) { |
| structures := make(map[string]*Structure) // for expanding Extends |
| for _, s := range model.Structures { |
| structures[s.Name] = s |
| } |
| for _, s := range model.Structures { |
| out := new(bytes.Buffer) |
| generateDoc(out, s.Documentation) |
| nm := goName(s.Name) |
| if nm == "string" { // an unacceptable strut name |
| // a weird case, and needed only so the generated code contains the old gopls code |
| nm = "DocumentDiagnosticParams" |
| } |
| fmt.Fprintf(out, "type %s struct { // line %d\n", nm, s.Line) |
| // for gpls compatibilitye, embed most extensions, but expand the rest some day |
| props := append([]NameType{}, s.Properties...) |
| if s.Name == "SymbolInformation" { // but expand this one |
| for _, ex := range s.Extends { |
| fmt.Fprintf(out, "\t// extends %s\n", ex.Name) |
| props = append(props, structures[ex.Name].Properties...) |
| } |
| genProps(out, props, nm) |
| } else { |
| genProps(out, props, nm) |
| for _, ex := range s.Extends { |
| fmt.Fprintf(out, "\t%s\n", goName(ex.Name)) |
| } |
| } |
| for _, ex := range s.Mixins { |
| fmt.Fprintf(out, "\t%s\n", goName(ex.Name)) |
| } |
| out.WriteString("}\n") |
| types[nm] = out.String() |
| } |
| // base types |
| types["DocumentURI"] = "type DocumentURI string\n" |
| types["URI"] = "type URI = string\n" |
| |
| types["LSPAny"] = "type LSPAny = interface{}\n" |
| // A special case, the only previously existing Or type |
| types["DocumentDiagnosticReport"] = "type DocumentDiagnosticReport = Or_DocumentDiagnosticReport // (alias) line 13909\n" |
| |
| } |
| |
| func genProps(out *bytes.Buffer, props []NameType, name string) { |
| for _, p := range props { |
| tp := goplsName(p.Type) |
| if newNm, ok := renameProp[prop{name, p.Name}]; ok { |
| usedRenameProp[prop{name, p.Name}] = true |
| if tp == newNm { |
| log.Printf("renameProp useless {%q, %q} for %s", name, p.Name, tp) |
| } |
| tp = newNm |
| } |
| // it's a pointer if it is optional, or for gopls compatibility |
| opt, star := propStar(name, p, tp) |
| json := fmt.Sprintf(" `json:\"%s%s\"`", p.Name, opt) |
| generateDoc(out, p.Documentation) |
| fmt.Fprintf(out, "\t%s %s%s %s\n", goName(p.Name), star, tp, json) |
| } |
| } |
| |
| func genAliases(model Model) { |
| for _, ta := range model.TypeAliases { |
| out := new(bytes.Buffer) |
| generateDoc(out, ta.Documentation) |
| nm := goName(ta.Name) |
| if nm != ta.Name { |
| continue // renamed the type, e.g., "DocumentDiagnosticReport", an or-type to "string" |
| } |
| tp := goplsName(ta.Type) |
| fmt.Fprintf(out, "type %s = %s // (alias) line %d\n", nm, tp, ta.Line) |
| types[nm] = out.String() |
| } |
| } |
| |
| func genGenTypes() { |
| for _, nt := range genTypes { |
| out := new(bytes.Buffer) |
| nm := goplsName(nt.typ) |
| switch nt.kind { |
| case "literal": |
| fmt.Fprintf(out, "// created for Literal (%s)\n", nt.name) |
| fmt.Fprintf(out, "type %s struct { // line %d\n", nm, nt.line+1) |
| genProps(out, nt.properties, nt.name) // systematic name, not gopls name; is this a good choice? |
| case "or": |
| if !strings.HasPrefix(nm, "Or") { |
| // It was replaced by a narrower type defined elsewhere |
| continue |
| } |
| names := []string{} |
| for _, t := range nt.items { |
| if notNil(t) { |
| names = append(names, goplsName(t)) |
| } |
| } |
| sort.Strings(names) |
| fmt.Fprintf(out, "// created for Or %v\n", names) |
| fmt.Fprintf(out, "type %s struct { // line %d\n", nm, nt.line+1) |
| fmt.Fprintf(out, "\tValue interface{} `json:\"value\"`\n") |
| case "and": |
| fmt.Fprintf(out, "// created for And\n") |
| fmt.Fprintf(out, "type %s struct { // line %d\n", nm, nt.line+1) |
| for _, x := range nt.items { |
| nm := goplsName(x) |
| fmt.Fprintf(out, "\t%s\n", nm) |
| } |
| case "tuple": // there's only this one |
| nt.name = "UIntCommaUInt" |
| fmt.Fprintf(out, "//created for Tuple\ntype %s struct { // line %d\n", nm, nt.line+1) |
| fmt.Fprintf(out, "\tFld0 uint32 `json:\"fld0\"`\n") |
| fmt.Fprintf(out, "\tFld1 uint32 `json:\"fld1\"`\n") |
| default: |
| log.Fatalf("%s not handled", nt.kind) |
| } |
| out.WriteString("}\n") |
| types[nm] = out.String() |
| } |
| } |
| func genConsts(model Model) { |
| for _, e := range model.Enumerations { |
| out := new(bytes.Buffer) |
| generateDoc(out, e.Documentation) |
| tp := goplsName(e.Type) |
| nm := goName(e.Name) |
| fmt.Fprintf(out, "type %s %s // line %d\n", nm, tp, e.Line) |
| types[nm] = out.String() |
| vals := new(bytes.Buffer) |
| generateDoc(vals, e.Documentation) |
| for _, v := range e.Values { |
| generateDoc(vals, v.Documentation) |
| nm := goName(v.Name) |
| more, ok := disambiguate[e.Name] |
| if ok { |
| usedDisambiguate[e.Name] = true |
| nm = more.prefix + nm + more.suffix |
| nm = goName(nm) // stringType |
| } |
| var val string |
| switch v := v.Value.(type) { |
| case string: |
| val = fmt.Sprintf("%q", v) |
| case float64: |
| val = fmt.Sprintf("%d", int(v)) |
| default: |
| log.Fatalf("impossible type %T", v) |
| } |
| fmt.Fprintf(vals, "\t%s %s = %s // line %d\n", nm, e.Name, val, v.Line) |
| } |
| consts[nm] = vals.String() |
| } |
| } |
| func genMarshal() { |
| for _, nt := range genTypes { |
| nm := goplsName(nt.typ) |
| if !strings.HasPrefix(nm, "Or") { |
| continue |
| } |
| names := []string{} |
| for _, t := range nt.items { |
| if notNil(t) { |
| names = append(names, goplsName(t)) |
| } |
| } |
| sort.Strings(names) |
| var buf bytes.Buffer |
| fmt.Fprintf(&buf, "// from line %d\n", nt.line) |
| fmt.Fprintf(&buf, "func (t %s) MarshalJSON() ([]byte, error) {\n", nm) |
| buf.WriteString("\tswitch x := t.Value.(type){\n") |
| for _, nmx := range names { |
| fmt.Fprintf(&buf, "\tcase %s:\n", nmx) |
| fmt.Fprintf(&buf, "\t\treturn json.Marshal(x)\n") |
| } |
| buf.WriteString("\tcase nil:\n\t\treturn []byte(\"null\"), nil\n\t}\n") |
| fmt.Fprintf(&buf, "\treturn nil, fmt.Errorf(\"type %%T not one of %v\", t)\n", names) |
| buf.WriteString("}\n\n") |
| |
| fmt.Fprintf(&buf, "func (t *%s) UnmarshalJSON(x []byte) error {\n", nm) |
| buf.WriteString("\tif string(x) == \"null\" {\n\t\tt.Value = nil\n\t\t\treturn nil\n\t}\n") |
| for i, nmx := range names { |
| fmt.Fprintf(&buf, "\tvar h%d %s\n", i, nmx) |
| fmt.Fprintf(&buf, "\tif err := json.Unmarshal(x, &h%d); err == nil {\n\t\tt.Value = h%d\n\t\t\treturn nil\n\t\t}\n", i, i) |
| } |
| fmt.Fprintf(&buf, "return errors.New(\"unmarshal failed to match one of %v\")\n", names) |
| buf.WriteString("}\n\n") |
| jsons[nm] = buf.String() |
| } |
| } |
| |
| func goplsName(t *Type) string { |
| nm := typeNames[t] |
| // translate systematic name to gopls name |
| if newNm, ok := goplsType[nm]; ok { |
| usedGoplsType[nm] = true |
| nm = newNm |
| } |
| return nm |
| } |
| |
| func notNil(t *Type) bool { // shutdwon is the special case that needs this |
| return t != nil && (t.Kind != "base" || t.Name != "null") |
| } |
| |
| func hasNilValue(t string) bool { |
| // this may be unreliable, and need a supplementary table |
| if strings.HasPrefix(t, "[]") || strings.HasPrefix(t, "*") { |
| return true |
| } |
| if t == "interface{}" || t == "any" { |
| return true |
| } |
| // that's all the cases that occur currently |
| return false |
| } |