1// Copyright 2021 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// Package gen is used to generate command bindings from the gopls command
6// interface.
7package gen
8
9import (
10	"bytes"
11	"fmt"
12	"go/types"
13	"text/template"
14
15	"golang.org/x/tools/internal/imports"
16	"golang.org/x/tools/internal/lsp/command/commandmeta"
17)
18
19const src = `// Copyright 2021 The Go Authors. All rights reserved.
20// Use of this source code is governed by a BSD-style
21// license that can be found in the LICENSE file.
22
23// Don't include this file during code generation, or it will break the build
24// if existing interface methods have been modified.
25//go:build !generate
26// +build !generate
27
28package command
29
30// Code generated by generate.go. DO NOT EDIT.
31
32import (
33	{{range $k, $v := .Imports -}}
34	"{{$k}}"
35	{{end}}
36)
37
38const (
39{{- range .Commands}}
40	{{.MethodName}} Command = "{{.Name}}"
41{{- end}}
42)
43
44var Commands = []Command {
45{{- range .Commands}}
46	{{.MethodName}},
47{{- end}}
48}
49
50func Dispatch(ctx context.Context, params *protocol.ExecuteCommandParams, s Interface) (interface{}, error) {
51	switch params.Command {
52	{{- range .Commands}}
53	case "{{.ID}}":
54		{{- if .Args -}}
55			{{- range $i, $v := .Args}}
56		var a{{$i}} {{typeString $v.Type}}
57			{{- end}}
58		if err := UnmarshalArgs(params.Arguments{{range $i, $v := .Args}}, &a{{$i}}{{end}}); err != nil {
59			return nil, err
60		}
61		{{end -}}
62		return {{if not .Result}}nil, {{end}}s.{{.MethodName}}(ctx{{range $i, $v := .Args}}, a{{$i}}{{end}})
63	{{- end}}
64	}
65	return nil, fmt.Errorf("unsupported command %q", params.Command)
66}
67{{- range .Commands}}
68
69func New{{.MethodName}}Command(title string, {{range $i, $v := .Args}}{{if $i}}, {{end}}a{{$i}} {{typeString $v.Type}}{{end}}) (protocol.Command, error) {
70	args, err := MarshalArgs({{range $i, $v := .Args}}{{if $i}}, {{end}}a{{$i}}{{end}})
71	if err != nil {
72		return protocol.Command{}, err
73	}
74	return protocol.Command{
75		Title: title,
76		Command: "{{.ID}}",
77		Arguments: args,
78	}, nil
79}
80{{end}}
81`
82
83type data struct {
84	Imports  map[string]bool
85	Commands []*commandmeta.Command
86}
87
88func Generate() ([]byte, error) {
89	pkg, cmds, err := commandmeta.Load()
90	if err != nil {
91		return nil, fmt.Errorf("loading command data: %v", err)
92	}
93	qf := func(p *types.Package) string {
94		if p == pkg.Types {
95			return ""
96		}
97		return p.Name()
98	}
99	tmpl, err := template.New("").Funcs(template.FuncMap{
100		"typeString": func(t types.Type) string {
101			return types.TypeString(t, qf)
102		},
103	}).Parse(src)
104	if err != nil {
105		return nil, err
106	}
107	d := data{
108		Commands: cmds,
109		Imports: map[string]bool{
110			"context": true,
111			"fmt":     true,
112			"golang.org/x/tools/internal/lsp/protocol": true,
113		},
114	}
115	const thispkg = "golang.org/x/tools/internal/lsp/command"
116	for _, c := range d.Commands {
117		for _, arg := range c.Args {
118			pth := pkgPath(arg.Type)
119			if pth != "" && pth != thispkg {
120				d.Imports[pth] = true
121			}
122		}
123		pth := pkgPath(c.Result)
124		if pth != "" && pth != thispkg {
125			d.Imports[pth] = true
126		}
127	}
128
129	var buf bytes.Buffer
130	if err := tmpl.Execute(&buf, d); err != nil {
131		return nil, fmt.Errorf("executing: %v", err)
132	}
133
134	opts := &imports.Options{
135		AllErrors:  true,
136		FormatOnly: true,
137		Comments:   true,
138	}
139	content, err := imports.Process("", buf.Bytes(), opts)
140	if err != nil {
141		return nil, fmt.Errorf("goimports: %v", err)
142	}
143	return content, nil
144}
145
146func pkgPath(t types.Type) string {
147	if n, ok := t.(*types.Named); ok {
148		if pkg := n.Obj().Pkg(); pkg != nil {
149			return pkg.Path()
150		}
151	}
152	return ""
153}
154