.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.1-0.20210319172145-bda8f5cee399 / internal / lsp / command / commandmeta / meta.go
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 commandmeta provides metadata about LSP commands, by analyzing the
6 // command.Interface type.
7 package commandmeta
8
9 import (
10         "fmt"
11         "go/ast"
12         "go/token"
13         "go/types"
14         "reflect"
15         "strings"
16         "unicode"
17
18         "golang.org/x/tools/go/ast/astutil"
19         "golang.org/x/tools/go/packages"
20         "golang.org/x/tools/internal/lsp/command"
21 )
22
23 type Command struct {
24         MethodName string
25         Name       string
26         // TODO(rFindley): I think Title can actually be eliminated. In all cases
27         // where we use it, there is probably a more appropriate contextual title.
28         Title  string
29         Doc    string
30         Args   []*Field
31         Result types.Type
32 }
33
34 func (c *Command) ID() string {
35         return command.ID(c.Name)
36 }
37
38 type Field struct {
39         Name    string
40         Doc     string
41         JSONTag string
42         Type    types.Type
43         // In some circumstances, we may want to recursively load additional field
44         // descriptors for fields of struct types, documenting their internals.
45         Fields []*Field
46 }
47
48 func Load() (*packages.Package, []*Command, error) {
49         pkgs, err := packages.Load(
50                 &packages.Config{
51                         Mode:       packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax | packages.NeedImports | packages.NeedDeps,
52                         BuildFlags: []string{"-tags=generate"},
53                 },
54                 "golang.org/x/tools/internal/lsp/command",
55         )
56         if err != nil {
57                 return nil, nil, fmt.Errorf("packages.Load: %v", err)
58         }
59         pkg := pkgs[0]
60         if len(pkg.Errors) > 0 {
61                 return pkg, nil, pkg.Errors[0]
62         }
63
64         // For a bit of type safety, use reflection to get the interface name within
65         // the package scope.
66         it := reflect.TypeOf((*command.Interface)(nil)).Elem()
67         obj := pkg.Types.Scope().Lookup(it.Name()).Type().Underlying().(*types.Interface)
68
69         // Load command metadata corresponding to each interface method.
70         var commands []*Command
71         loader := fieldLoader{make(map[types.Object]*Field)}
72         for i := 0; i < obj.NumMethods(); i++ {
73                 m := obj.Method(i)
74                 c, err := loader.loadMethod(pkg, m)
75                 if err != nil {
76                         return nil, nil, fmt.Errorf("loading %s: %v", m.Name(), err)
77                 }
78                 commands = append(commands, c)
79         }
80         return pkg, commands, nil
81 }
82
83 // fieldLoader loads field information, memoizing results to prevent infinite
84 // recursion.
85 type fieldLoader struct {
86         loaded map[types.Object]*Field
87 }
88
89 var universeError = types.Universe.Lookup("error").Type()
90
91 func (l *fieldLoader) loadMethod(pkg *packages.Package, m *types.Func) (*Command, error) {
92         node, err := findField(pkg, m.Pos())
93         if err != nil {
94                 return nil, err
95         }
96         title, doc := splitDoc(node.Doc.Text())
97         c := &Command{
98                 MethodName: m.Name(),
99                 Name:       lspName(m.Name()),
100                 Doc:        doc,
101                 Title:      title,
102         }
103         sig := m.Type().Underlying().(*types.Signature)
104         rlen := sig.Results().Len()
105         if rlen > 2 || rlen == 0 {
106                 return nil, fmt.Errorf("must have 1 or 2 returns, got %d", rlen)
107         }
108         finalResult := sig.Results().At(rlen - 1)
109         if !types.Identical(finalResult.Type(), universeError) {
110                 return nil, fmt.Errorf("final return must be error")
111         }
112         if rlen == 2 {
113                 c.Result = sig.Results().At(0).Type()
114         }
115         ftype := node.Type.(*ast.FuncType)
116         if sig.Params().Len() != ftype.Params.NumFields() {
117                 panic("bug: mismatching method params")
118         }
119         for i, p := range ftype.Params.List {
120                 pt := sig.Params().At(i)
121                 fld, err := l.loadField(pkg, p, pt, "")
122                 if err != nil {
123                         return nil, err
124                 }
125                 if i == 0 {
126                         // Lazy check that the first argument is a context. We could relax this,
127                         // but then the generated code gets more complicated.
128                         if named, ok := fld.Type.(*types.Named); !ok || named.Obj().Name() != "Context" || named.Obj().Pkg().Path() != "context" {
129                                 return nil, fmt.Errorf("first method parameter must be context.Context")
130                         }
131                         // Skip the context argument, as it is implied.
132                         continue
133                 }
134                 c.Args = append(c.Args, fld)
135         }
136         return c, nil
137 }
138
139 func (l *fieldLoader) loadField(pkg *packages.Package, node *ast.Field, obj *types.Var, tag string) (*Field, error) {
140         if existing, ok := l.loaded[obj]; ok {
141                 return existing, nil
142         }
143         fld := &Field{
144                 Name:    obj.Name(),
145                 Doc:     strings.TrimSpace(node.Doc.Text()),
146                 Type:    obj.Type(),
147                 JSONTag: reflect.StructTag(tag).Get("json"),
148         }
149         under := fld.Type.Underlying()
150         if p, ok := under.(*types.Pointer); ok {
151                 under = p.Elem()
152         }
153         if s, ok := under.(*types.Struct); ok {
154                 for i := 0; i < s.NumFields(); i++ {
155                         obj2 := s.Field(i)
156                         pkg2 := pkg
157                         if obj2.Pkg() != pkg2.Types {
158                                 pkg2, ok = pkg.Imports[obj2.Pkg().Path()]
159                                 if !ok {
160                                         return nil, fmt.Errorf("missing import for %q: %q", pkg.ID, obj2.Pkg().Path())
161                                 }
162                         }
163                         node2, err := findField(pkg2, obj2.Pos())
164                         if err != nil {
165                                 return nil, err
166                         }
167                         tag := s.Tag(i)
168                         structField, err := l.loadField(pkg2, node2, obj2, tag)
169                         if err != nil {
170                                 return nil, err
171                         }
172                         fld.Fields = append(fld.Fields, structField)
173                 }
174         }
175         return fld, nil
176 }
177
178 // splitDoc parses a command doc string to separate the title from normal
179 // documentation.
180 //
181 // The doc comment should be of the form: "MethodName: Title\nDocumentation"
182 func splitDoc(text string) (title, doc string) {
183         docParts := strings.SplitN(text, "\n", 2)
184         titleParts := strings.SplitN(docParts[0], ":", 2)
185         if len(titleParts) > 1 {
186                 title = strings.TrimSpace(titleParts[1])
187         }
188         if len(docParts) > 1 {
189                 doc = strings.TrimSpace(docParts[1])
190         }
191         return title, doc
192 }
193
194 // lspName returns the normalized command name to use in the LSP.
195 func lspName(methodName string) string {
196         words := splitCamel(methodName)
197         for i := range words {
198                 words[i] = strings.ToLower(words[i])
199         }
200         return strings.Join(words, "_")
201 }
202
203 // splitCamel splits s into words, according to camel-case word boundaries.
204 // Initialisms are grouped as a single word.
205 //
206 // For example:
207 //  "RunTests" -> []string{"Run", "Tests"}
208 //  "GCDetails" -> []string{"GC", "Details"}
209 func splitCamel(s string) []string {
210         var words []string
211         for len(s) > 0 {
212                 last := strings.LastIndexFunc(s, unicode.IsUpper)
213                 if last < 0 {
214                         last = 0
215                 }
216                 if last == len(s)-1 {
217                         // Group initialisms as a single word.
218                         last = 1 + strings.LastIndexFunc(s[:last], func(r rune) bool { return !unicode.IsUpper(r) })
219                 }
220                 words = append(words, s[last:])
221                 s = s[:last]
222         }
223         for i := 0; i < len(words)/2; i++ {
224                 j := len(words) - i - 1
225                 words[i], words[j] = words[j], words[i]
226         }
227         return words
228 }
229
230 // findField finds the struct field or interface method positioned at pos,
231 // within the AST.
232 func findField(pkg *packages.Package, pos token.Pos) (*ast.Field, error) {
233         fset := pkg.Fset
234         var file *ast.File
235         for _, f := range pkg.Syntax {
236                 if fset.Position(f.Pos()).Filename == fset.Position(pos).Filename {
237                         file = f
238                         break
239                 }
240         }
241         if file == nil {
242                 return nil, fmt.Errorf("no file for pos %v", pos)
243         }
244         path, _ := astutil.PathEnclosingInterval(file, pos, pos)
245         // This is fragile, but in the cases we care about, the field will be in
246         // path[1].
247         return path[1].(*ast.Field), nil
248 }