blob: 84ac7aa7ba400cbaffd4e111693209b983cdae64 [file] [log] [blame]
// Copyright 2020 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.
// The helper command generates the declaration of the concrete
// 'server' type that implements the abstract Server interface defined
// in protocol/tsserver.go (which is itself generated from the LSP
// protocol).
//
// To run, invoke "go generate" in the parent (lsp) directory.
//
// TODO(adonovan): merge this into the main LSP generator.
package main
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"log"
"os"
"sort"
"strings"
"text/template"
)
var (
typ = flag.String("t", "Server", "generate code for this type")
def = flag.String("d", "", "the file the type is defined in") // this relies on punning
use = flag.String("u", "", "look for uses in this package")
out = flag.String("o", "", "where to write the generated file")
)
func main() {
log.SetFlags(log.Lshortfile)
flag.Parse()
if *typ == "" || *def == "" || *use == "" || *out == "" {
flag.PrintDefaults()
os.Exit(1)
}
// read the type definition and see what methods we're looking for
doTypes()
// parse the package and see which methods are defined
doUses()
output()
}
// replace "\\\n" with nothing before using
var tmpl = `// Copyright 2021 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.
package lsp
// code generated by helper. DO NOT EDIT.
import (
"context"
"golang.org/x/tools/gopls/internal/lsp/protocol"
)
{{range $key, $v := .Stuff}}
func (s *{{$.Type}}) {{$v.Name}}({{.Param}}) {{.Result}} {
{{if ne .Found ""}} return s.{{.Internal}}({{.Invoke}})\
{{else}}return {{if lt 1 (len .Results)}}nil, {{end}}notImplemented("{{.Name}}"){{end}}
}
{{end}}
`
func output() {
// put in empty param names as needed
for _, t := range types {
if t.paramnames == nil {
t.paramnames = make([]string, len(t.paramtypes))
}
for i, p := range t.paramtypes {
cm := ""
if i > 0 {
cm = ", "
}
t.Param += fmt.Sprintf("%s%s %s", cm, t.paramnames[i], p)
this := t.paramnames[i]
if this == "_" {
this = "nil"
}
t.Invoke += fmt.Sprintf("%s%s", cm, this)
}
if len(t.Results) > 1 {
t.Result = "("
}
for i, r := range t.Results {
cm := ""
if i > 0 {
cm = ", "
}
t.Result += fmt.Sprintf("%s%s", cm, r)
}
if len(t.Results) > 1 {
t.Result += ")"
}
}
fd, err := os.Create(*out)
if err != nil {
log.Fatal(err)
}
t, err := template.New("foo").Parse(tmpl)
if err != nil {
log.Fatal(err)
}
type par struct {
Type string
Stuff []*Function
}
p := par{*typ, types}
if false { // debugging the template
t.Execute(os.Stderr, &p)
}
buf := bytes.NewBuffer(nil)
err = t.Execute(buf, &p)
if err != nil {
log.Fatal(err)
}
ans, err := format.Source(bytes.Replace(buf.Bytes(), []byte("\\\n"), []byte{}, -1))
if err != nil {
log.Fatal(err)
}
fd.Write(ans)
}
func doUses() {
fset := token.NewFileSet()
pkgs, err := parser.ParseDir(fset, *use, nil, 0)
if err != nil {
log.Fatalf("%q:%v", *use, err)
}
pkg := pkgs["lsp"] // CHECK
files := pkg.Files
for fname, f := range files {
for _, d := range f.Decls {
fd, ok := d.(*ast.FuncDecl)
if !ok {
continue
}
nm := fd.Name.String()
if ast.IsExported(nm) {
// we're looking for things like didChange
continue
}
if fx, ok := byname[nm]; ok {
if fx.Found != "" {
log.Fatalf("found %s in %s and %s", fx.Internal, fx.Found, fname)
}
fx.Found = fname
// and the Paramnames
ft := fd.Type
for _, f := range ft.Params.List {
nm := ""
if len(f.Names) > 0 {
nm = f.Names[0].String()
if nm == "_" {
nm = "_gen"
}
}
fx.paramnames = append(fx.paramnames, nm)
}
}
}
}
if false {
for i, f := range types {
log.Printf("%d %s %s", i, f.Internal, f.Found)
}
}
}
type Function struct {
Name string
Internal string // first letter lower case
paramtypes []string
paramnames []string
Results []string
Param string
Result string // do it in code, easier than in a template
Invoke string
Found string // file it was found in
}
var types []*Function
var byname = map[string]*Function{} // internal names
func doTypes() {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, *def, nil, 0)
if err != nil {
log.Fatal(err)
}
fd, err := os.Create("/tmp/ast")
if err != nil {
log.Fatal(err)
}
ast.Fprint(fd, fset, f, ast.NotNilFilter)
ast.Inspect(f, inter)
sort.Slice(types, func(i, j int) bool { return types[i].Name < types[j].Name })
if false {
for i, f := range types {
log.Printf("%d %s(%v) %v", i, f.Name, f.paramtypes, f.Results)
}
}
}
func inter(n ast.Node) bool {
x, ok := n.(*ast.TypeSpec)
if !ok || x.Name.Name != *typ {
return true
}
m := x.Type.(*ast.InterfaceType).Methods.List
for _, fld := range m {
fn := fld.Type.(*ast.FuncType)
p := fn.Params.List
r := fn.Results.List
fx := &Function{
Name: fld.Names[0].String(),
}
fx.Internal = strings.ToLower(fx.Name[:1]) + fx.Name[1:]
for _, f := range p {
fx.paramtypes = append(fx.paramtypes, whatis(f.Type))
}
for _, f := range r {
fx.Results = append(fx.Results, whatis(f.Type))
}
types = append(types, fx)
byname[fx.Internal] = fx
}
return false
}
func whatis(x ast.Expr) string {
switch n := x.(type) {
case *ast.SelectorExpr:
return whatis(n.X) + "." + n.Sel.String()
case *ast.StarExpr:
return "*" + whatis(n.X)
case *ast.Ident:
if ast.IsExported(n.Name) {
// these are from package protocol
return "protocol." + n.Name
}
return n.Name
case *ast.ArrayType:
return "[]" + whatis(n.Elt)
case *ast.InterfaceType:
return "interface{}"
default:
log.Fatalf("Fatal %T", x)
return fmt.Sprintf("%T", x)
}
}