golang.org/x/tools/gopls@v0.15.3/internal/protocol/generate/output.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  package main
     9  
    10  import (
    11  	"bytes"
    12  	"fmt"
    13  	"log"
    14  	"sort"
    15  	"strings"
    16  )
    17  
    18  var (
    19  	// tsclient.go has 3 sections
    20  	cdecls = make(sortedMap[string])
    21  	ccases = make(sortedMap[string])
    22  	cfuncs = make(sortedMap[string])
    23  	// tsserver.go has 3 sections
    24  	sdecls = make(sortedMap[string])
    25  	scases = make(sortedMap[string])
    26  	sfuncs = make(sortedMap[string])
    27  	// tsprotocol.go has 2 sections
    28  	types  = make(sortedMap[string])
    29  	consts = make(sortedMap[string])
    30  	// tsjson has 1 section
    31  	jsons = make(sortedMap[string])
    32  )
    33  
    34  func generateOutput(model Model) {
    35  	for _, r := range model.Requests {
    36  		genDecl(r.Method, r.Params, r.Result, r.Direction)
    37  		genCase(r.Method, r.Params, r.Result, r.Direction)
    38  		genFunc(r.Method, r.Params, r.Result, r.Direction, false)
    39  	}
    40  	for _, n := range model.Notifications {
    41  		if n.Method == "$/cancelRequest" {
    42  			continue // handled internally by jsonrpc2
    43  		}
    44  		genDecl(n.Method, n.Params, nil, n.Direction)
    45  		genCase(n.Method, n.Params, nil, n.Direction)
    46  		genFunc(n.Method, n.Params, nil, n.Direction, true)
    47  	}
    48  	genStructs(model)
    49  	genAliases(model)
    50  	genGenTypes() // generate the unnamed types
    51  	genConsts(model)
    52  	genMarshal()
    53  }
    54  
    55  func genDecl(method string, param, result *Type, dir string) {
    56  	fname := methodName(method)
    57  	p := ""
    58  	if notNil(param) {
    59  		p = ", *" + goplsName(param)
    60  	}
    61  	ret := "error"
    62  	if notNil(result) {
    63  		tp := goplsName(result)
    64  		if !hasNilValue(tp) {
    65  			tp = "*" + tp
    66  		}
    67  		ret = fmt.Sprintf("(%s, error)", tp)
    68  	}
    69  	// special gopls compatibility case (PJW: still needed?)
    70  	switch method {
    71  	case "workspace/configuration":
    72  		// was And_Param_workspace_configuration, but the type substitution doesn't work,
    73  		// as ParamConfiguration is embedded in And_Param_workspace_configuration
    74  		p = ", *ParamConfiguration"
    75  		ret = "([]LSPAny, error)"
    76  	}
    77  	msg := fmt.Sprintf("\t%s(context.Context%s) %s // %s\n", fname, p, ret, method)
    78  	switch dir {
    79  	case "clientToServer":
    80  		sdecls[method] = msg
    81  	case "serverToClient":
    82  		cdecls[method] = msg
    83  	case "both":
    84  		sdecls[method] = msg
    85  		cdecls[method] = msg
    86  	default:
    87  		log.Fatalf("impossible direction %q", dir)
    88  	}
    89  }
    90  
    91  func genCase(method string, param, result *Type, dir string) {
    92  	out := new(bytes.Buffer)
    93  	fmt.Fprintf(out, "\tcase %q:\n", method)
    94  	var p string
    95  	fname := methodName(method)
    96  	if notNil(param) {
    97  		nm := goplsName(param)
    98  		if method == "workspace/configuration" { // gopls compatibility
    99  			// was And_Param_workspace_configuration, which contains ParamConfiguration
   100  			// so renaming the type leads to circular definitions
   101  			nm = "ParamConfiguration" // gopls compatibility
   102  		}
   103  		fmt.Fprintf(out, "\t\tvar params %s\n", nm)
   104  		fmt.Fprintf(out, "\t\tif err := UnmarshalJSON(r.Params(), &params); err != nil {\n")
   105  		fmt.Fprintf(out, "\t\t\treturn true, sendParseError(ctx, reply, err)\n\t\t}\n")
   106  		p = ", &params"
   107  	}
   108  	if notNil(result) {
   109  		fmt.Fprintf(out, "\t\tresp, err := %%s.%s(ctx%s)\n", fname, p)
   110  		out.WriteString("\t\tif err != nil {\n")
   111  		out.WriteString("\t\t\treturn true, reply(ctx, nil, err)\n")
   112  		out.WriteString("\t\t}\n")
   113  		out.WriteString("\t\treturn true, reply(ctx, resp, nil)\n")
   114  	} else {
   115  		fmt.Fprintf(out, "\t\terr := %%s.%s(ctx%s)\n", fname, p)
   116  		out.WriteString("\t\treturn true, reply(ctx, nil, err)\n")
   117  	}
   118  	out.WriteString("\n")
   119  	msg := out.String()
   120  	switch dir {
   121  	case "clientToServer":
   122  		scases[method] = fmt.Sprintf(msg, "server")
   123  	case "serverToClient":
   124  		ccases[method] = fmt.Sprintf(msg, "client")
   125  	case "both":
   126  		scases[method] = fmt.Sprintf(msg, "server")
   127  		ccases[method] = fmt.Sprintf(msg, "client")
   128  	default:
   129  		log.Fatalf("impossible direction %q", dir)
   130  	}
   131  }
   132  
   133  func genFunc(method string, param, result *Type, dir string, isnotify bool) {
   134  	out := new(bytes.Buffer)
   135  	var p, r string
   136  	var goResult string
   137  	if notNil(param) {
   138  		p = ", params *" + goplsName(param)
   139  	}
   140  	if notNil(result) {
   141  		goResult = goplsName(result)
   142  		if !hasNilValue(goResult) {
   143  			goResult = "*" + goResult
   144  		}
   145  		r = fmt.Sprintf("(%s, error)", goResult)
   146  	} else {
   147  		r = "error"
   148  	}
   149  	// special gopls compatibility case
   150  	switch method {
   151  	case "workspace/configuration":
   152  		// was And_Param_workspace_configuration, but the type substitution doesn't work,
   153  		// as ParamConfiguration is embedded in And_Param_workspace_configuration
   154  		p = ", params *ParamConfiguration"
   155  		r = "([]LSPAny, error)"
   156  		goResult = "[]LSPAny"
   157  	}
   158  	fname := methodName(method)
   159  	fmt.Fprintf(out, "func (s *%%sDispatcher) %s(ctx context.Context%s) %s {\n",
   160  		fname, p, r)
   161  
   162  	if !notNil(result) {
   163  		if isnotify {
   164  			if notNil(param) {
   165  				fmt.Fprintf(out, "\treturn s.sender.Notify(ctx, %q, params)\n", method)
   166  			} else {
   167  				fmt.Fprintf(out, "\treturn s.sender.Notify(ctx, %q, nil)\n", method)
   168  			}
   169  		} else {
   170  			if notNil(param) {
   171  				fmt.Fprintf(out, "\treturn s.sender.Call(ctx, %q, params, nil)\n", method)
   172  			} else {
   173  				fmt.Fprintf(out, "\treturn s.sender.Call(ctx, %q, nil, nil)\n", method)
   174  			}
   175  		}
   176  	} else {
   177  		fmt.Fprintf(out, "\tvar result %s\n", goResult)
   178  		if isnotify {
   179  			if notNil(param) {
   180  				fmt.Fprintf(out, "\ts.sender.Notify(ctx, %q, params)\n", method)
   181  			} else {
   182  				fmt.Fprintf(out, "\t\tif err := s.sender.Notify(ctx, %q, nil); err != nil {\n", method)
   183  			}
   184  		} else {
   185  			if notNil(param) {
   186  				fmt.Fprintf(out, "\t\tif err := s.sender.Call(ctx, %q, params, &result); err != nil {\n", method)
   187  			} else {
   188  				fmt.Fprintf(out, "\t\tif err := s.sender.Call(ctx, %q, nil, &result); err != nil {\n", method)
   189  			}
   190  		}
   191  		fmt.Fprintf(out, "\t\treturn nil, err\n\t}\n\treturn result, nil\n")
   192  	}
   193  	out.WriteString("}\n")
   194  	msg := out.String()
   195  	switch dir {
   196  	case "clientToServer":
   197  		sfuncs[method] = fmt.Sprintf(msg, "server")
   198  	case "serverToClient":
   199  		cfuncs[method] = fmt.Sprintf(msg, "client")
   200  	case "both":
   201  		sfuncs[method] = fmt.Sprintf(msg, "server")
   202  		cfuncs[method] = fmt.Sprintf(msg, "client")
   203  	default:
   204  		log.Fatalf("impossible direction %q", dir)
   205  	}
   206  }
   207  
   208  func genStructs(model Model) {
   209  	structures := make(map[string]*Structure) // for expanding Extends
   210  	for _, s := range model.Structures {
   211  		structures[s.Name] = s
   212  	}
   213  	for _, s := range model.Structures {
   214  		out := new(bytes.Buffer)
   215  		generateDoc(out, s.Documentation)
   216  		nm := goName(s.Name)
   217  		if nm == "string" { // an unacceptable strut name
   218  			// a weird case, and needed only so the generated code contains the old gopls code
   219  			nm = "DocumentDiagnosticParams"
   220  		}
   221  		fmt.Fprintf(out, "type %s struct {%s\n", nm, linex(s.Line))
   222  		// for gpls compatibilitye, embed most extensions, but expand the rest some day
   223  		props := append([]NameType{}, s.Properties...)
   224  		if s.Name == "SymbolInformation" { // but expand this one
   225  			for _, ex := range s.Extends {
   226  				fmt.Fprintf(out, "\t// extends %s\n", ex.Name)
   227  				props = append(props, structures[ex.Name].Properties...)
   228  			}
   229  			genProps(out, props, nm)
   230  		} else {
   231  			genProps(out, props, nm)
   232  			for _, ex := range s.Extends {
   233  				fmt.Fprintf(out, "\t%s\n", goName(ex.Name))
   234  			}
   235  		}
   236  		for _, ex := range s.Mixins {
   237  			fmt.Fprintf(out, "\t%s\n", goName(ex.Name))
   238  		}
   239  		out.WriteString("}\n")
   240  		types[nm] = out.String()
   241  	}
   242  
   243  	// base types
   244  	// (For URI and DocumentURI, see ../uri.go.)
   245  	types["LSPAny"] = "type LSPAny = interface{}\n"
   246  	// A special case, the only previously existing Or type
   247  	types["DocumentDiagnosticReport"] = "type DocumentDiagnosticReport = Or_DocumentDiagnosticReport // (alias) \n"
   248  
   249  }
   250  
   251  func genProps(out *bytes.Buffer, props []NameType, name string) {
   252  	for _, p := range props {
   253  		tp := goplsName(p.Type)
   254  		if newNm, ok := renameProp[prop{name, p.Name}]; ok {
   255  			usedRenameProp[prop{name, p.Name}] = true
   256  			if tp == newNm {
   257  				log.Printf("renameProp useless {%q, %q} for %s", name, p.Name, tp)
   258  			}
   259  			tp = newNm
   260  		}
   261  		// it's a pointer if it is optional, or for gopls compatibility
   262  		opt, star := propStar(name, p, tp)
   263  		json := fmt.Sprintf(" `json:\"%s%s\"`", p.Name, opt)
   264  		generateDoc(out, p.Documentation)
   265  		fmt.Fprintf(out, "\t%s %s%s %s\n", goName(p.Name), star, tp, json)
   266  	}
   267  }
   268  
   269  func genAliases(model Model) {
   270  	for _, ta := range model.TypeAliases {
   271  		out := new(bytes.Buffer)
   272  		generateDoc(out, ta.Documentation)
   273  		nm := goName(ta.Name)
   274  		if nm != ta.Name {
   275  			continue // renamed the type, e.g., "DocumentDiagnosticReport", an or-type to "string"
   276  		}
   277  		tp := goplsName(ta.Type)
   278  		fmt.Fprintf(out, "type %s = %s // (alias)\n", nm, tp)
   279  		types[nm] = out.String()
   280  	}
   281  }
   282  
   283  func genGenTypes() {
   284  	for _, nt := range genTypes {
   285  		out := new(bytes.Buffer)
   286  		nm := goplsName(nt.typ)
   287  		switch nt.kind {
   288  		case "literal":
   289  			fmt.Fprintf(out, "// created for Literal (%s)\n", nt.name)
   290  			fmt.Fprintf(out, "type %s struct {%s\n", nm, linex(nt.line+1))
   291  			genProps(out, nt.properties, nt.name) // systematic name, not gopls name; is this a good choice?
   292  		case "or":
   293  			if !strings.HasPrefix(nm, "Or") {
   294  				// It was replaced by a narrower type defined elsewhere
   295  				continue
   296  			}
   297  			names := []string{}
   298  			for _, t := range nt.items {
   299  				if notNil(t) {
   300  					names = append(names, goplsName(t))
   301  				}
   302  			}
   303  			sort.Strings(names)
   304  			fmt.Fprintf(out, "// created for Or %v\n", names)
   305  			fmt.Fprintf(out, "type %s struct {%s\n", nm, linex(nt.line+1))
   306  			fmt.Fprintf(out, "\tValue interface{} `json:\"value\"`\n")
   307  		case "and":
   308  			fmt.Fprintf(out, "// created for And\n")
   309  			fmt.Fprintf(out, "type %s struct {%s\n", nm, linex(nt.line+1))
   310  			for _, x := range nt.items {
   311  				nm := goplsName(x)
   312  				fmt.Fprintf(out, "\t%s\n", nm)
   313  			}
   314  		case "tuple": // there's only this one
   315  			nt.name = "UIntCommaUInt"
   316  			fmt.Fprintf(out, "//created for Tuple\ntype %s struct {%s\n", nm, linex(nt.line+1))
   317  			fmt.Fprintf(out, "\tFld0 uint32 `json:\"fld0\"`\n")
   318  			fmt.Fprintf(out, "\tFld1 uint32 `json:\"fld1\"`\n")
   319  		default:
   320  			log.Fatalf("%s not handled", nt.kind)
   321  		}
   322  		out.WriteString("}\n")
   323  		types[nm] = out.String()
   324  	}
   325  }
   326  func genConsts(model Model) {
   327  	for _, e := range model.Enumerations {
   328  		out := new(bytes.Buffer)
   329  		generateDoc(out, e.Documentation)
   330  		tp := goplsName(e.Type)
   331  		nm := goName(e.Name)
   332  		fmt.Fprintf(out, "type %s %s%s\n", nm, tp, linex(e.Line))
   333  		types[nm] = out.String()
   334  		vals := new(bytes.Buffer)
   335  		generateDoc(vals, e.Documentation)
   336  		for _, v := range e.Values {
   337  			generateDoc(vals, v.Documentation)
   338  			nm := goName(v.Name)
   339  			more, ok := disambiguate[e.Name]
   340  			if ok {
   341  				usedDisambiguate[e.Name] = true
   342  				nm = more.prefix + nm + more.suffix
   343  				nm = goName(nm) // stringType
   344  			}
   345  			var val string
   346  			switch v := v.Value.(type) {
   347  			case string:
   348  				val = fmt.Sprintf("%q", v)
   349  			case float64:
   350  				val = fmt.Sprintf("%d", int(v))
   351  			default:
   352  				log.Fatalf("impossible type %T", v)
   353  			}
   354  			fmt.Fprintf(vals, "\t%s %s = %s%s\n", nm, e.Name, val, linex(v.Line))
   355  		}
   356  		consts[nm] = vals.String()
   357  	}
   358  }
   359  func genMarshal() {
   360  	for _, nt := range genTypes {
   361  		nm := goplsName(nt.typ)
   362  		if !strings.HasPrefix(nm, "Or") {
   363  			continue
   364  		}
   365  		names := []string{}
   366  		for _, t := range nt.items {
   367  			if notNil(t) {
   368  				names = append(names, goplsName(t))
   369  			}
   370  		}
   371  		sort.Strings(names)
   372  		var buf bytes.Buffer
   373  		fmt.Fprintf(&buf, "func (t %s) MarshalJSON() ([]byte, error) {\n", nm)
   374  		buf.WriteString("\tswitch x := t.Value.(type){\n")
   375  		for _, nmx := range names {
   376  			fmt.Fprintf(&buf, "\tcase %s:\n", nmx)
   377  			fmt.Fprintf(&buf, "\t\treturn json.Marshal(x)\n")
   378  		}
   379  		buf.WriteString("\tcase nil:\n\t\treturn []byte(\"null\"), nil\n\t}\n")
   380  		fmt.Fprintf(&buf, "\treturn nil, fmt.Errorf(\"type %%T not one of %v\", t)\n", names)
   381  		buf.WriteString("}\n\n")
   382  
   383  		fmt.Fprintf(&buf, "func (t *%s) UnmarshalJSON(x []byte) error {\n", nm)
   384  		buf.WriteString("\tif string(x) == \"null\" {\n\t\tt.Value = nil\n\t\t\treturn nil\n\t}\n")
   385  		for i, nmx := range names {
   386  			fmt.Fprintf(&buf, "\tvar h%d %s\n", i, nmx)
   387  			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)
   388  		}
   389  		fmt.Fprintf(&buf, "return &UnmarshalError{\"unmarshal failed to match one of %v\"}", names)
   390  		buf.WriteString("}\n\n")
   391  		jsons[nm] = buf.String()
   392  	}
   393  }
   394  
   395  func linex(n int) string {
   396  	if *lineNumbers {
   397  		return fmt.Sprintf(" // line %d", n)
   398  	}
   399  	return ""
   400  }
   401  
   402  func goplsName(t *Type) string {
   403  	nm := typeNames[t]
   404  	// translate systematic name to gopls name
   405  	if newNm, ok := goplsType[nm]; ok {
   406  		usedGoplsType[nm] = true
   407  		nm = newNm
   408  	}
   409  	return nm
   410  }
   411  
   412  func notNil(t *Type) bool { // shutdwon is the special case that needs this
   413  	return t != nil && (t.Kind != "base" || t.Name != "null")
   414  }
   415  
   416  func hasNilValue(t string) bool {
   417  	// this may be unreliable, and need a supplementary table
   418  	if strings.HasPrefix(t, "[]") || strings.HasPrefix(t, "*") {
   419  		return true
   420  	}
   421  	if t == "interface{}" || t == "any" {
   422  		return true
   423  	}
   424  	// that's all the cases that occur currently
   425  	return false
   426  }