blob: 18dd6ea3f7c5db1576f8fd07d527fec631bde261 [file] [log] [blame]
// 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(), &params); err != nil {\n")
fmt.Fprintf(out, "\t\t\treturn true, sendParseError(ctx, reply, err)\n\t\t}\n")
p = ", &params"
}
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 &UnmarshalError{\"unmarshal failed to match one of %v\"}", 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
}