mirror of
https://github.com/golang/go
synced 2024-11-05 11:36:10 -07:00
5dffee2a89
Use format.Source before output. Change-Id: I8504fc80defa77aeaab748b9dc65d6382da500a3 Reviewed-on: https://go-review.googlesource.com/c/tools/+/217177 Reviewed-by: Rebecca Stambler <rstambler@golang.org> Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
245 lines
5.1 KiB
Go
245 lines
5.1 KiB
Go
// Invoke with //go:generate helper/helper -t Server -d protocol/tsserver.go -u lsp -o server_gen.go
|
|
// invoke in internal/lsp
|
|
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()
|
|
return
|
|
}
|
|
// 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 = `
|
|
package lsp
|
|
|
|
// code generated by helper. DO NOT EDIT.
|
|
|
|
import (
|
|
"context"
|
|
|
|
"golang.org/x/tools/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)
|
|
t.Invoke += fmt.Sprintf("%s%s", cm, t.paramnames[i])
|
|
}
|
|
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()
|
|
}
|
|
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)
|
|
}
|
|
}
|