golang.org/x/tools/gopls@v0.15.3/internal/template/implementations.go (about) 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 template 6 7 import ( 8 "context" 9 "fmt" 10 "regexp" 11 "strconv" 12 "time" 13 14 "golang.org/x/tools/gopls/internal/cache" 15 "golang.org/x/tools/gopls/internal/file" 16 "golang.org/x/tools/gopls/internal/protocol" 17 "golang.org/x/tools/gopls/internal/protocol/semtok" 18 ) 19 20 // line number (1-based) and message 21 var errRe = regexp.MustCompile(`template.*:(\d+): (.*)`) 22 23 // Diagnostics returns parse errors. There is only one per file. 24 // The errors are not always helpful. For instance { {end}} 25 // will likely point to the end of the file. 26 func Diagnostics(snapshot *cache.Snapshot) map[protocol.DocumentURI][]*cache.Diagnostic { 27 diags := make(map[protocol.DocumentURI][]*cache.Diagnostic) 28 for uri, fh := range snapshot.Templates() { 29 diags[uri] = diagnoseOne(fh) 30 } 31 return diags 32 } 33 34 func diagnoseOne(fh file.Handle) []*cache.Diagnostic { 35 // no need for skipTemplate check, as Diagnose is called on the 36 // snapshot's template files 37 buf, err := fh.Content() 38 if err != nil { 39 // Is a Diagnostic with no Range useful? event.Error also? 40 msg := fmt.Sprintf("failed to read %s (%v)", fh.URI().Path(), err) 41 d := cache.Diagnostic{Message: msg, Severity: protocol.SeverityError, URI: fh.URI(), 42 Source: cache.TemplateError} 43 return []*cache.Diagnostic{&d} 44 } 45 p := parseBuffer(buf) 46 if p.ParseErr == nil { 47 return nil 48 } 49 unknownError := func(msg string) []*cache.Diagnostic { 50 s := fmt.Sprintf("malformed template error %q: %s", p.ParseErr.Error(), msg) 51 d := cache.Diagnostic{ 52 Message: s, Severity: protocol.SeverityError, Range: p.Range(p.nls[0], 1), 53 URI: fh.URI(), Source: cache.TemplateError} 54 return []*cache.Diagnostic{&d} 55 } 56 // errors look like `template: :40: unexpected "}" in operand` 57 // so the string needs to be parsed 58 matches := errRe.FindStringSubmatch(p.ParseErr.Error()) 59 if len(matches) != 3 { 60 msg := fmt.Sprintf("expected 3 matches, got %d (%v)", len(matches), matches) 61 return unknownError(msg) 62 } 63 lineno, err := strconv.Atoi(matches[1]) 64 if err != nil { 65 msg := fmt.Sprintf("couldn't convert %q to int, %v", matches[1], err) 66 return unknownError(msg) 67 } 68 msg := matches[2] 69 d := cache.Diagnostic{Message: msg, Severity: protocol.SeverityError, 70 Source: cache.TemplateError} 71 start := p.nls[lineno-1] 72 if lineno < len(p.nls) { 73 size := p.nls[lineno] - start 74 d.Range = p.Range(start, size) 75 } else { 76 d.Range = p.Range(start, 1) 77 } 78 return []*cache.Diagnostic{&d} 79 } 80 81 // Definition finds the definitions of the symbol at loc. It 82 // does not understand scoping (if any) in templates. This code is 83 // for definitions, type definitions, and implementations. 84 // Results only for variables and templates. 85 func Definition(snapshot *cache.Snapshot, fh file.Handle, loc protocol.Position) ([]protocol.Location, error) { 86 x, _, err := symAtPosition(fh, loc) 87 if err != nil { 88 return nil, err 89 } 90 sym := x.name 91 ans := []protocol.Location{} 92 // PJW: this is probably a pattern to abstract 93 a := New(snapshot.Templates()) 94 for k, p := range a.files { 95 for _, s := range p.symbols { 96 if !s.vardef || s.name != sym { 97 continue 98 } 99 ans = append(ans, protocol.Location{URI: k, Range: p.Range(s.start, s.length)}) 100 } 101 } 102 return ans, nil 103 } 104 105 func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) (*protocol.Hover, error) { 106 sym, p, err := symAtPosition(fh, position) 107 if sym == nil || err != nil { 108 return nil, err 109 } 110 ans := protocol.Hover{Range: p.Range(sym.start, sym.length), Contents: protocol.MarkupContent{Kind: protocol.Markdown}} 111 switch sym.kind { 112 case protocol.Function: 113 ans.Contents.Value = fmt.Sprintf("function: %s", sym.name) 114 case protocol.Variable: 115 ans.Contents.Value = fmt.Sprintf("variable: %s", sym.name) 116 case protocol.Constant: 117 ans.Contents.Value = fmt.Sprintf("constant %s", sym.name) 118 case protocol.Method: // field or method 119 ans.Contents.Value = fmt.Sprintf("%s: field or method", sym.name) 120 case protocol.Package: // template use, template def (PJW: do we want two?) 121 ans.Contents.Value = fmt.Sprintf("template %s\n(add definition)", sym.name) 122 case protocol.Namespace: 123 ans.Contents.Value = fmt.Sprintf("template %s defined", sym.name) 124 case protocol.Number: 125 ans.Contents.Value = "number" 126 case protocol.String: 127 ans.Contents.Value = "string" 128 case protocol.Boolean: 129 ans.Contents.Value = "boolean" 130 default: 131 ans.Contents.Value = fmt.Sprintf("oops, sym=%#v", sym) 132 } 133 return &ans, nil 134 } 135 136 func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, params *protocol.ReferenceParams) ([]protocol.Location, error) { 137 sym, _, err := symAtPosition(fh, params.Position) 138 if sym == nil || err != nil || sym.name == "" { 139 return nil, err 140 } 141 ans := []protocol.Location{} 142 143 a := New(snapshot.Templates()) 144 for k, p := range a.files { 145 for _, s := range p.symbols { 146 if s.name != sym.name { 147 continue 148 } 149 if s.vardef && !params.Context.IncludeDeclaration { 150 continue 151 } 152 ans = append(ans, protocol.Location{URI: k, Range: p.Range(s.start, s.length)}) 153 } 154 } 155 // do these need to be sorted? (a.files is a map) 156 return ans, nil 157 } 158 159 func SemanticTokens(ctx context.Context, snapshot *cache.Snapshot, spn protocol.DocumentURI) (*protocol.SemanticTokens, error) { 160 fh, err := snapshot.ReadFile(ctx, spn) 161 if err != nil { 162 return nil, err 163 } 164 buf, err := fh.Content() 165 if err != nil { 166 return nil, err 167 } 168 p := parseBuffer(buf) 169 170 var items []semtok.Token 171 add := func(line, start, len uint32) { 172 if len == 0 { 173 return // vscode doesn't like 0-length Tokens 174 } 175 // TODO(adonovan): don't ignore the rng restriction, if any. 176 items = append(items, semtok.Token{ 177 Line: line, 178 Start: start, 179 Len: len, 180 Type: semtok.TokMacro, 181 }) 182 } 183 184 for _, t := range p.Tokens() { 185 if t.Multiline { 186 la, ca := p.LineCol(t.Start) 187 lb, cb := p.LineCol(t.End) 188 add(la, ca, p.RuneCount(la, ca, 0)) 189 for l := la + 1; l < lb; l++ { 190 add(l, 0, p.RuneCount(l, 0, 0)) 191 } 192 add(lb, 0, p.RuneCount(lb, 0, cb)) 193 continue 194 } 195 sz, err := p.TokenSize(t) 196 if err != nil { 197 return nil, err 198 } 199 line, col := p.LineCol(t.Start) 200 add(line, col, uint32(sz)) 201 } 202 const noStrings = false 203 const noNumbers = false 204 ans := &protocol.SemanticTokens{ 205 Data: semtok.Encode( 206 items, 207 noStrings, 208 noNumbers, 209 snapshot.Options().SemanticTypes, 210 snapshot.Options().SemanticMods), 211 // for small cache, some day. for now, the LSP client ignores this 212 // (that is, when the LSP client starts returning these, we can cache) 213 ResultID: fmt.Sprintf("%v", time.Now()), 214 } 215 return ans, nil 216 } 217 218 // still need to do rename, etc