blob: d9722902ca9d6520c61359d985d75d35454f447e [file] [log] [blame]
// 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 gen is used to generate command bindings from the gopls command
// interface.
package gen
import (
"bytes"
"fmt"
"go/types"
"log"
"text/template"
"golang.org/x/tools/gopls/internal/protocol/command/commandmeta"
"golang.org/x/tools/internal/imports"
)
const src = `// Copyright 2024 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.
// Don't include this file during code generation, or it will break the build
// if existing interface methods have been modified.
//go:build !generate
// +build !generate
// Code generated by gen.go from gopls/internal/protocol/command. DO NOT EDIT.
package command
import (
{{range $k, $v := .Imports -}}
"{{$k}}"
{{end}}
)
// Symbolic names for gopls commands, corresponding to methods of [Interface].
//
// The string value is used in the Command field of protocol.Command.
// These commands may be obtained from a CodeLens or CodeAction request
// and executed by an ExecuteCommand request.
const (
{{- range .Commands}}
{{.MethodName}} Command = "{{.Name}}"
{{- end}}
)
var Commands = []Command {
{{- range .Commands}}
{{.MethodName}},
{{- end}}
}
func Dispatch(ctx context.Context, params *protocol.ExecuteCommandParams, s Interface) (interface{}, error) {
switch Command(params.Command) {
{{- range .Commands}}
case {{.MethodName}}:
{{- if .Args -}}
{{- range $i, $v := .Args}}
var a{{$i}} {{typeString $v.Type}}
{{- end}}
if err := UnmarshalArgs(params.Arguments{{range $i, $v := .Args}}, &a{{$i}}{{end}}); err != nil {
return nil, err
}
{{end -}}
return {{if not .Result}}nil, {{end}}s.{{.MethodName}}(ctx{{range $i, $v := .Args}}, a{{$i}}{{end}})
{{- end}}
}
return nil, fmt.Errorf("unsupported command %q", params.Command)
}
{{- range .Commands}}
{{if fallible .Args}}
func New{{.MethodName}}Command(title string, {{range $i, $v := .Args}}{{if $i}}, {{end}}a{{$i}} {{typeString $v.Type}}{{end}}) (*protocol.Command, error) {
args, err := MarshalArgs({{range $i, $v := .Args}}{{if $i}}, {{end}}a{{$i}}{{end}})
if err != nil {
return nil, err
}
return &protocol.Command{
Title: title,
Command: {{.MethodName}}.String(),
Arguments: args,
}, nil
}
{{else}}
func New{{.MethodName}}Command(title string, {{range $i, $v := .Args}}{{if $i}}, {{end}}a{{$i}} {{typeString $v.Type}}{{end}}) *protocol.Command {
return &protocol.Command{
Title: title,
Command: {{.MethodName}}.String(),
Arguments: MustMarshalArgs({{range $i, $v := .Args}}{{if $i}}, {{end}}a{{$i}}{{end}}),
}
}
{{end}}
{{end}}
`
type data struct {
Imports map[string]bool
Commands []*commandmeta.Command
}
// Generate computes the new contents of ../command_gen.go from a
// static analysis of the command.Interface type.
func Generate() ([]byte, error) {
cmds, err := commandmeta.Load()
if err != nil {
return nil, fmt.Errorf("loading command data: %v", err)
}
const thispkg = "golang.org/x/tools/gopls/internal/protocol/command"
qf := func(p *types.Package) string {
if p.Path() == thispkg {
return ""
}
return p.Name()
}
tmpl, err := template.New("").Funcs(template.FuncMap{
"typeString": func(t types.Type) string {
return types.TypeString(t, qf)
},
"fallible": func(args []*commandmeta.Field) bool {
var fallible func(types.Type) bool
fallible = func(t types.Type) bool {
switch t := t.Underlying().(type) {
case *types.Basic:
return false
case *types.Slice:
return fallible(t.Elem())
case *types.Struct:
for i := 0; i < t.NumFields(); i++ {
if fallible(t.Field(i).Type()) {
return true
}
}
return false
}
// Assume all other types are fallible for now:
log.Println("Command.Args has fallible type", t)
return true
}
for _, arg := range args {
if fallible(arg.Type) {
return true
}
}
return false
},
}).Parse(src)
if err != nil {
return nil, err
}
d := data{
Commands: cmds,
Imports: map[string]bool{
"context": true,
"fmt": true,
"golang.org/x/tools/gopls/internal/protocol": true,
},
}
for _, c := range d.Commands {
for _, arg := range c.Args {
pth := pkgPath(arg.Type)
if pth != "" && pth != thispkg {
d.Imports[pth] = true
}
}
if c.Result != nil {
pth := pkgPath(c.Result.Type)
if pth != "" && pth != thispkg {
d.Imports[pth] = true
}
}
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, d); err != nil {
return nil, fmt.Errorf("executing: %v", err)
}
opts := &imports.Options{
AllErrors: true,
FormatOnly: true,
Comments: true,
}
content, err := imports.Process("", buf.Bytes(), opts)
if err != nil {
return nil, fmt.Errorf("goimports: %v", err)
}
return content, nil
}
func pkgPath(t types.Type) string {
type hasTypeName interface { // *Named or *Alias (or *TypeParam)
Obj() *types.TypeName
}
if t, ok := t.(hasTypeName); ok {
if pkg := t.Obj().Pkg(); pkg != nil {
return pkg.Path()
}
}
return ""
}