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