mirror of
https://github.com/golang/go
synced 2024-11-18 18:44:42 -07:00
49b8ac185c
Currently there is no need for this because the file contents are part of the file handle. This change is in preparation for an impending improvement that tweaks the source code during the parse stage to fix certain kind of terminal parse errors. Any code that wants to use an *ast.File or *token.File in conjunction with the file contents needs access to the doctored source code so things line up. Change-Id: I59d83d3d6150aa1264761aa2c1f6c1269075a2ce Reviewed-on: https://go-review.googlesource.com/c/tools/+/218979 Run-TryBot: Muir Manders <muir@mnd.rs> Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
146 lines
4.1 KiB
Go
146 lines
4.1 KiB
Go
// Copyright 2018 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 lsp
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
"net/url"
|
|
"regexp"
|
|
"strconv"
|
|
"sync"
|
|
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
"golang.org/x/tools/internal/lsp/source"
|
|
"golang.org/x/tools/internal/span"
|
|
"golang.org/x/tools/internal/telemetry/log"
|
|
)
|
|
|
|
func (s *Server) documentLink(ctx context.Context, params *protocol.DocumentLinkParams) ([]protocol.DocumentLink, error) {
|
|
uri := span.NewURI(params.TextDocument.URI)
|
|
view, err := s.session.ViewOf(uri)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fh, err := view.Snapshot().GetFile(uri)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// TODO(golang/go#36501): Support document links for go.mod files.
|
|
if fh.Identity().Kind == source.Mod {
|
|
return nil, nil
|
|
}
|
|
file, _, m, _, err := view.Session().Cache().ParseGoHandle(fh, source.ParseFull).Parse(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var links []protocol.DocumentLink
|
|
ast.Inspect(file, func(node ast.Node) bool {
|
|
switch n := node.(type) {
|
|
case *ast.ImportSpec:
|
|
// For import specs, provide a link to a documentation website, like https://pkg.go.dev.
|
|
if target, err := strconv.Unquote(n.Path.Value); err == nil {
|
|
target = fmt.Sprintf("https://%s/%s", view.Options().LinkTarget, target)
|
|
|
|
// Account for the quotation marks in the positions.
|
|
start, end := n.Path.Pos()+1, n.Path.End()-1
|
|
if l, err := toProtocolLink(view, m, target, start, end); err == nil {
|
|
links = append(links, l)
|
|
} else {
|
|
log.Error(ctx, "failed to create protocol link", err)
|
|
}
|
|
}
|
|
return false
|
|
case *ast.BasicLit:
|
|
// Look for links in string literals.
|
|
if n.Kind == token.STRING {
|
|
links = append(links, findLinksInString(ctx, view, n.Value, n.Pos(), m)...)
|
|
}
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
// Look for links in comments.
|
|
for _, commentGroup := range file.Comments {
|
|
for _, comment := range commentGroup.List {
|
|
links = append(links, findLinksInString(ctx, view, comment.Text, comment.Pos(), m)...)
|
|
}
|
|
}
|
|
return links, nil
|
|
}
|
|
|
|
func findLinksInString(ctx context.Context, view source.View, src string, pos token.Pos, m *protocol.ColumnMapper) []protocol.DocumentLink {
|
|
var links []protocol.DocumentLink
|
|
for _, index := range view.Options().URLRegexp.FindAllIndex([]byte(src), -1) {
|
|
start, end := index[0], index[1]
|
|
startPos := token.Pos(int(pos) + start)
|
|
endPos := token.Pos(int(pos) + end)
|
|
url, err := url.Parse(src[start:end])
|
|
if err != nil {
|
|
log.Error(ctx, "failed to parse matching URL", err)
|
|
continue
|
|
}
|
|
// If the URL has no scheme, use https.
|
|
if url.Scheme == "" {
|
|
url.Scheme = "https"
|
|
}
|
|
l, err := toProtocolLink(view, m, url.String(), startPos, endPos)
|
|
if err != nil {
|
|
log.Error(ctx, "failed to create protocol link", err)
|
|
continue
|
|
}
|
|
links = append(links, l)
|
|
}
|
|
// Handle golang/go#1234-style links.
|
|
r := getIssueRegexp()
|
|
for _, index := range r.FindAllIndex([]byte(src), -1) {
|
|
start, end := index[0], index[1]
|
|
startPos := token.Pos(int(pos) + start)
|
|
endPos := token.Pos(int(pos) + end)
|
|
matches := r.FindStringSubmatch(src)
|
|
if len(matches) < 4 {
|
|
continue
|
|
}
|
|
org, repo, number := matches[1], matches[2], matches[3]
|
|
target := fmt.Sprintf("https://github.com/%s/%s/issues/%s", org, repo, number)
|
|
l, err := toProtocolLink(view, m, target, startPos, endPos)
|
|
if err != nil {
|
|
log.Error(ctx, "failed to create protocol link", err)
|
|
continue
|
|
}
|
|
links = append(links, l)
|
|
}
|
|
return links
|
|
}
|
|
|
|
func getIssueRegexp() *regexp.Regexp {
|
|
once.Do(func() {
|
|
issueRegexp = regexp.MustCompile(`(\w+)/([\w-]+)#([0-9]+)`)
|
|
})
|
|
return issueRegexp
|
|
}
|
|
|
|
var (
|
|
once sync.Once
|
|
issueRegexp *regexp.Regexp
|
|
)
|
|
|
|
func toProtocolLink(view source.View, m *protocol.ColumnMapper, target string, start, end token.Pos) (protocol.DocumentLink, error) {
|
|
spn, err := span.NewRange(view.Session().Cache().FileSet(), start, end).Span()
|
|
if err != nil {
|
|
return protocol.DocumentLink{}, err
|
|
}
|
|
rng, err := m.Range(spn)
|
|
if err != nil {
|
|
return protocol.DocumentLink{}, err
|
|
}
|
|
return protocol.DocumentLink{
|
|
Range: rng,
|
|
Target: target,
|
|
}, nil
|
|
}
|