internal/lsp: reduce usage of column mapper

A mapper is always uniquely tied to a file at a specific version, so
just build it when we get a new *ast.File. We build the mapper using the
*token.File associated with the particular *ast.File, which is why there
is one per ParseGoHandle instead of FileHandle.

Change-Id: Ida40981ef91f6133cdd07e9793337fcd67510fba
Reviewed-on: https://go-review.googlesource.com/c/tools/+/194517
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
Rebecca Stambler 2019-09-10 00:36:39 -04:00
parent b31ee645dd
commit 5edc6aefed
22 changed files with 209 additions and 187 deletions

View File

@ -258,7 +258,7 @@ func (imp *importer) typeCheck(ctx context.Context, cph *checkPackageHandle, m *
go func(i int, ph source.ParseGoHandle) {
defer wg.Done()
files[i], parseErrors[i] = ph.Parse(ctx)
files[i], _, parseErrors[i] = ph.Parse(ctx)
}(i, ph)
}
wg.Wait()
@ -350,7 +350,7 @@ func (imp *importer) cachePerFile(ctx context.Context, gof *goFile, ph source.Pa
}
gof.pkgs[cph.m.id] = cph
file, err := ph.Parse(ctx)
file, _, err := ph.Parse(ctx)
if file == nil {
return errors.Errorf("no AST for %s: %v", ph.File().Identity().URI, err)
}

View File

@ -190,7 +190,7 @@ func (v *view) shouldRunGopackages(ctx context.Context, f *goFile, fh source.Fil
return true
}
// Get file content in case we don't already have it.
parsed, err := v.session.cache.ParseGoHandle(fh, source.ParseHeader).Parse(ctx)
parsed, _, err := v.session.cache.ParseGoHandle(fh, source.ParseHeader).Parse(ctx)
if err == context.Canceled {
log.Error(ctx, "parsing file header", err, tag.Of("file", f.URI()))
return false

View File

@ -13,9 +13,11 @@ import (
"go/token"
"reflect"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/lsp/telemetry"
"golang.org/x/tools/internal/memoize"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/telemetry/log"
"golang.org/x/tools/internal/telemetry/trace"
errors "golang.org/x/xerrors"
@ -39,8 +41,9 @@ type parseGoHandle struct {
type parseGoData struct {
memoize.NoCopy
ast *ast.File
err error
ast *ast.File
mapper *protocol.ColumnMapper
err error
}
func (c *cache) ParseGoHandle(fh source.FileHandle, mode source.ParseMode) source.ParseGoHandle {
@ -51,6 +54,17 @@ func (c *cache) ParseGoHandle(fh source.FileHandle, mode source.ParseMode) sourc
h := c.store.Bind(key, func(ctx context.Context) interface{} {
data := &parseGoData{}
data.ast, data.err = parseGo(ctx, c, fh, mode)
tok := c.FileSet().File(data.ast.Pos())
if tok == nil {
return data
}
uri := fh.Identity().URI
content, _, err := fh.Read(ctx)
if err != nil {
data.err = err
return data
}
data.mapper = newColumnMapper(uri, c.FileSet(), tok, content)
return data
})
return &parseGoHandle{
@ -59,6 +73,13 @@ func (c *cache) ParseGoHandle(fh source.FileHandle, mode source.ParseMode) sourc
mode: mode,
}
}
func newColumnMapper(uri span.URI, fset *token.FileSet, tok *token.File, content []byte) *protocol.ColumnMapper {
return &protocol.ColumnMapper{
URI: uri,
Converter: span.NewTokenConverter(fset, tok),
Content: content,
}
}
func (h *parseGoHandle) File() source.FileHandle {
return h.file
@ -68,22 +89,22 @@ func (h *parseGoHandle) Mode() source.ParseMode {
return h.mode
}
func (h *parseGoHandle) Parse(ctx context.Context) (*ast.File, error) {
func (h *parseGoHandle) Parse(ctx context.Context) (*ast.File, *protocol.ColumnMapper, error) {
v := h.handle.Get(ctx)
if v == nil {
return nil, ctx.Err()
return nil, nil, ctx.Err()
}
data := v.(*parseGoData)
return data.ast, data.err
return data.ast, data.mapper, data.err
}
func (h *parseGoHandle) Cached(ctx context.Context) (*ast.File, error) {
func (h *parseGoHandle) Cached(ctx context.Context) (*ast.File, *protocol.ColumnMapper, error) {
v := h.handle.Cached()
if v == nil {
return nil, errors.Errorf("no cached value for %s", h.file.Identity().URI)
return nil, nil, errors.Errorf("no cached value for %s", h.file.Identity().URI)
}
data := v.(*parseGoData)
return data.ast, data.err
return data.ast, data.mapper, data.err
}
func hashParseKey(ph source.ParseGoHandle) string {

View File

@ -7,7 +7,6 @@ package cache
import (
"context"
"go/ast"
"go/token"
"go/types"
"sort"
"sync"
@ -155,7 +154,7 @@ func (pkg *pkg) GetHandles() []source.ParseGoHandle {
func (pkg *pkg) GetSyntax(ctx context.Context) []*ast.File {
var syntax []*ast.File
for _, ph := range pkg.files {
file, _ := ph.Cached(ctx)
file, _, _ := ph.Cached(ctx)
if file != nil {
syntax = append(syntax, file)
}
@ -203,7 +202,7 @@ func (pkg *pkg) GetDiagnostics() []source.Diagnostic {
return diags
}
func (p *pkg) FindFile(ctx context.Context, uri span.URI, pos token.Pos) (source.ParseGoHandle, *ast.File, source.Package, error) {
func (p *pkg) FindFile(ctx context.Context, uri span.URI) (source.ParseGoHandle, *ast.File, source.Package, error) {
queue := []*pkg{p}
seen := make(map[string]bool)
@ -214,13 +213,11 @@ func (p *pkg) FindFile(ctx context.Context, uri span.URI, pos token.Pos) (source
for _, ph := range pkg.files {
if ph.File().Identity().URI == uri {
file, err := ph.Cached(ctx)
file, _, err := ph.Cached(ctx)
if file == nil {
return nil, nil, nil, err
}
if file.Pos() <= pos && pos <= file.End() {
return ph, file, pkg, nil
}
return ph, file, pkg, nil
}
}
for _, dep := range pkg.imports {

View File

@ -8,7 +8,6 @@ import (
"context"
"fmt"
"go/ast"
"go/parser"
"go/token"
"go/types"
"os"
@ -310,8 +309,11 @@ func (v *view) buildBuiltinPkg(ctx context.Context) {
pkg := pkgs[0]
files := make(map[string]*ast.File)
for _, filename := range pkg.GoFiles {
file, err := parser.ParseFile(cfg.Fset, filename, nil, parser.ParseComments)
if err != nil {
fh := v.session.GetFile(span.FileURI(filename))
ph := v.session.cache.ParseGoHandle(fh, source.ParseFull)
file, _, err := ph.Parse(ctx)
if file == nil {
log.Error(ctx, "failed to parse builtin", err, telemetry.File.Of(filename))
v.builtinPkg, _ = ast.NewPackage(cfg.Fset, nil, nil, nil)
return
}

View File

@ -344,7 +344,12 @@ func (c *cmdClient) getFile(ctx context.Context, uri span.URI) *cmdFile {
}
f := c.fset.AddFile(fname, -1, len(content))
f.SetLinesForContent(content)
file.mapper = protocol.NewColumnMapper(uri, fname, c.fset, f, content)
converter := span.NewContentConverter(fname, content)
file.mapper = &protocol.ColumnMapper{
URI: uri,
Converter: converter,
Content: content,
}
}
return file
}

View File

@ -28,17 +28,10 @@ func (s *Server) documentLink(ctx context.Context, params *protocol.DocumentLink
return nil, err
}
fh := f.Handle(ctx)
data, _, err := fh.Read(ctx)
if err != nil {
return nil, err
}
file, err := view.Session().Cache().ParseGoHandle(fh, source.ParseFull).Parse(ctx)
file, m, err := view.Session().Cache().ParseGoHandle(fh, source.ParseFull).Parse(ctx)
if file == nil {
return nil, err
}
tok := view.Session().Cache().FileSet().File(file.Pos())
m := protocol.NewColumnMapper(f.URI(), f.URI().Filename(), view.Session().Cache().FileSet(), tok, data)
var links []protocol.DocumentLink
ast.Inspect(file, func(node ast.Node) bool {
switch n := node.(type) {

View File

@ -352,7 +352,7 @@ func (r *runner) FoldingRange(t *testing.T, data tests.FoldingRanges) {
}
func (r *runner) foldingRanges(t *testing.T, prefix string, uri span.URI, ranges []protocol.FoldingRange) {
m, err := r.mapper(uri)
m, err := r.data.Mapper(uri)
if err != nil {
t.Fatal(err)
}
@ -484,7 +484,7 @@ func (r *runner) Format(t *testing.T, data tests.Formats) {
}
continue
}
m, err := r.mapper(uri)
m, err := r.data.Mapper(uri)
if err != nil {
t.Fatal(err)
}
@ -520,7 +520,7 @@ func (r *runner) Import(t *testing.T, data tests.Imports) {
}
continue
}
m, err := r.mapper(uri)
m, err := r.data.Mapper(uri)
if err != nil {
t.Fatal(err)
}
@ -572,7 +572,7 @@ func (r *runner) SuggestedFix(t *testing.T, data tests.SuggestedFixes) {
}
continue
}
m, err := r.mapper(f.URI())
m, err := r.data.Mapper(f.URI())
if err != nil {
t.Fatal(err)
}
@ -595,7 +595,7 @@ func (r *runner) SuggestedFix(t *testing.T, data tests.SuggestedFixes) {
func (r *runner) Definition(t *testing.T, data tests.Definitions) {
for _, d := range data {
sm, err := r.mapper(d.Src.URI())
sm, err := r.data.Mapper(d.Src.URI())
if err != nil {
t.Fatal(err)
}
@ -648,7 +648,7 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) {
}
} else if !d.OnlyHover {
locURI := span.NewURI(locs[0].URI)
lm, err := r.mapper(locURI)
lm, err := r.data.Mapper(locURI)
if err != nil {
t.Fatal(err)
}
@ -665,7 +665,7 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) {
func (r *runner) Highlight(t *testing.T, data tests.Highlights) {
for name, locations := range data {
m, err := r.mapper(locations[0].URI())
m, err := r.data.Mapper(locations[0].URI())
if err != nil {
t.Fatal(err)
}
@ -701,7 +701,7 @@ func (r *runner) Highlight(t *testing.T, data tests.Highlights) {
func (r *runner) Reference(t *testing.T, data tests.References) {
for src, itemList := range data {
sm, err := r.mapper(src.URI())
sm, err := r.data.Mapper(src.URI())
if err != nil {
t.Fatal(err)
}
@ -712,7 +712,7 @@ func (r *runner) Reference(t *testing.T, data tests.References) {
want := make(map[protocol.Location]bool)
for _, pos := range itemList {
m, err := r.mapper(pos.URI())
m, err := r.data.Mapper(pos.URI())
if err != nil {
t.Fatal(err)
}
@ -749,7 +749,7 @@ func (r *runner) Rename(t *testing.T, data tests.Renames) {
uri := spn.URI()
filename := uri.Filename()
sm, err := r.mapper(uri)
sm, err := r.data.Mapper(uri)
if err != nil {
t.Fatal(err)
}
@ -777,7 +777,7 @@ func (r *runner) Rename(t *testing.T, data tests.Renames) {
var res []string
for uri, edits := range *workspaceEdits.Changes {
m, err := r.mapper(span.URI(uri))
m, err := r.data.Mapper(span.URI(uri))
if err != nil {
t.Fatal(err)
}
@ -813,7 +813,7 @@ func (r *runner) Rename(t *testing.T, data tests.Renames) {
func (r *runner) PrepareRename(t *testing.T, data tests.PrepareRenames) {
for src, want := range data {
m, err := r.mapper(src.URI())
m, err := r.data.Mapper(src.URI())
if err != nil {
t.Fatal(err)
}
@ -928,7 +928,7 @@ func summarizeSymbols(t *testing.T, i int, want []protocol.DocumentSymbol, got [
func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) {
for spn, expectedSignatures := range data {
m, err := r.mapper(spn.URI())
m, err := r.data.Mapper(spn.URI())
if err != nil {
t.Fatal(err)
}
@ -960,6 +960,9 @@ func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) {
}
continue
}
if gotSignatures == nil {
t.Fatalf("expected %v, got nil", expectedSignatures)
}
if diff := diffSignatures(spn, expectedSignatures, gotSignatures); diff != "" {
t.Error(diff)
}
@ -1003,7 +1006,7 @@ func diffSignatures(spn span.Span, want *source.SignatureInformation, got *proto
func (r *runner) Link(t *testing.T, data tests.Links) {
for uri, wantLinks := range data {
m, err := r.mapper(uri)
m, err := r.data.Mapper(uri)
if err != nil {
t.Fatal(err)
}
@ -1054,27 +1057,6 @@ func (r *runner) Link(t *testing.T, data tests.Links) {
}
}
func (r *runner) mapper(uri span.URI) (*protocol.ColumnMapper, error) {
filename := uri.Filename()
fset := r.data.Exported.ExpectFileSet
var f *token.File
fset.Iterate(func(check *token.File) bool {
if check.Name() == filename {
f = check
return false
}
return true
})
if f == nil {
return nil, fmt.Errorf("no token.File for %s", uri)
}
content, err := r.data.Exported.FileContents(f.Name())
if err != nil {
return nil, err
}
return protocol.NewColumnMapper(uri, filename, fset, f, content), nil
}
func TestBytesOffset(t *testing.T) {
tests := []struct {
text string
@ -1102,7 +1084,13 @@ func TestBytesOffset(t *testing.T) {
fset := token.NewFileSet()
f := fset.AddFile(fname, -1, len(test.text))
f.SetLinesForContent([]byte(test.text))
mapper := protocol.NewColumnMapper(span.FileURI(fname), fname, fset, f, []byte(test.text))
uri := span.FileURI(fname)
converter := span.NewContentConverter(fname, []byte(test.text))
mapper := &protocol.ColumnMapper{
URI: uri,
Converter: converter,
Content: []byte(test.text),
}
got, err := mapper.Point(test.pos)
if err != nil && test.want != -1 {
t.Errorf("unexpected error: %v", err)

View File

@ -8,7 +8,6 @@ package protocol
import (
"fmt"
"go/token"
"golang.org/x/tools/internal/span"
errors "golang.org/x/xerrors"
@ -24,20 +23,6 @@ func NewURI(uri span.URI) string {
return string(uri)
}
func NewColumnMapper(uri span.URI, filename string, fset *token.FileSet, f *token.File, content []byte) *ColumnMapper {
var converter *span.TokenConverter
if f == nil {
converter = span.NewContentConverter(filename, content)
} else {
converter = span.NewTokenConverter(fset, f)
}
return &ColumnMapper{
URI: uri,
Converter: converter,
Content: content,
}
}
func (m *ColumnMapper) Location(s span.Span) (Location, error) {
rng, err := m.Range(s)
if err != nil {

View File

@ -388,20 +388,10 @@ func Completion(ctx context.Context, view View, f GoFile, pos protocol.Position,
ph = h
}
}
file, err := ph.Cached(ctx)
file, m, err := ph.Cached(ctx)
if file == nil {
return nil, nil, err
}
data, _, err := ph.File().Read(ctx)
if err != nil {
return nil, nil, err
}
fset := view.Session().Cache().FileSet()
tok := fset.File(file.Pos())
if tok == nil {
return nil, nil, errors.Errorf("no token.File for %s", f.URI())
}
m := protocol.NewColumnMapper(f.URI(), f.URI().Filename(), fset, tok, data)
spn, err := m.PointSpan(pos)
if err != nil {
return nil, nil, err

View File

@ -18,6 +18,7 @@ import (
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/telemetry/log"
"golang.org/x/tools/internal/telemetry/tag"
errors "golang.org/x/xerrors"
)
// formatCompletion creates a completion item for a given candidate.
@ -124,10 +125,13 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
}
uri := span.FileURI(pos.Filename)
_, file, pkg, err := c.pkg.FindFile(c.ctx, uri, obj.Pos())
_, file, pkg, err := c.pkg.FindFile(c.ctx, uri)
if err != nil {
return CompletionItem{}, err
}
if !(file.Pos() <= obj.Pos() && obj.Pos() <= file.End()) {
return CompletionItem{}, errors.Errorf("no file for %s", obj.Name())
}
ident, err := findIdentifier(c.ctx, c.view, []Package{pkg}, file, obj.Pos())
if err != nil {
return CompletionItem{}, err

View File

@ -190,29 +190,22 @@ func spanToRange(ctx context.Context, view View, pkg Package, spn span.Span, isT
var (
fh FileHandle
file *ast.File
m *protocol.ColumnMapper
err error
)
for _, ph := range pkg.GetHandles() {
if ph.File().Identity().URI == spn.URI() {
fh = ph.File()
file, err = ph.Cached(ctx)
file, m, err = ph.Cached(ctx)
}
}
if file == nil {
return protocol.Range{}, err
}
fset := view.Session().Cache().FileSet()
tok := fset.File(file.Pos())
if tok == nil {
return protocol.Range{}, errors.Errorf("no token.File for %s", spn.URI())
}
data, _, err := fh.Read(ctx)
if err != nil {
return protocol.Range{}, err
}
uri := fh.Identity().URI
m := protocol.NewColumnMapper(uri, uri.Filename(), fset, tok, data)
// Try to get a range for the diagnostic.
// TODO: Don't just limit ranges to type errors.
if spn.IsPoint() && isTypeError {

View File

@ -20,16 +20,11 @@ func FoldingRange(ctx context.Context, view View, f GoFile, lineFoldingOnly bool
// TODO(suzmue): consider limiting the number of folding ranges returned, and
// implement a way to prioritize folding ranges in that case.
fh := f.Handle(ctx)
file, err := view.Session().Cache().ParseGoHandle(fh, ParseFull).Parse(ctx)
ph := view.Session().Cache().ParseGoHandle(fh, ParseFull)
file, m, err := ph.Parse(ctx)
if err != nil {
return nil, err
}
data, _, err := fh.Read(ctx)
if err != nil {
return nil, err
}
fset := view.Session().Cache().FileSet()
m := protocol.NewColumnMapper(f.URI(), f.URI().Filename(), fset, fset.File(file.Pos()), data)
// Get folding ranges for comments separately as they are not walked by ast.Inspect.
ranges = append(ranges, commentsFoldingRange(view, m, file)...)

View File

@ -8,9 +8,7 @@ package source
import (
"bytes"
"context"
"go/ast"
"go/format"
"go/token"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/imports"
@ -34,12 +32,16 @@ func Format(ctx context.Context, view View, f File) ([]protocol.TextEdit, error)
if err != nil {
return nil, err
}
var file *ast.File
for _, ph := range pkg.GetHandles() {
if ph.File().Identity().URI == f.URI() {
file, err = ph.Cached(ctx)
var ph ParseGoHandle
for _, h := range pkg.GetHandles() {
if h.File().Identity().URI == f.URI() {
ph = h
}
}
if ph == nil {
return nil, err
}
file, m, err := ph.Parse(ctx)
if file == nil {
return nil, err
}
@ -52,7 +54,7 @@ func Format(ctx context.Context, view View, f File) ([]protocol.TextEdit, error)
if err != nil {
return nil, err
}
return computeTextEdits(ctx, view.Session().Cache().FileSet(), f, string(formatted))
return computeTextEdits(ctx, ph.File(), m, string(formatted))
}
fset := view.Session().Cache().FileSet()
@ -65,7 +67,7 @@ func Format(ctx context.Context, view View, f File) ([]protocol.TextEdit, error)
if err := format.Node(buf, fset, file); err != nil {
return nil, err
}
return computeTextEdits(ctx, view.Session().Cache().FileSet(), f, buf.String())
return computeTextEdits(ctx, ph.File(), m, buf.String())
}
func formatSource(ctx context.Context, file File) ([]byte, error) {
@ -82,10 +84,6 @@ func formatSource(ctx context.Context, file File) ([]byte, error) {
func Imports(ctx context.Context, view View, f GoFile, rng span.Range) ([]protocol.TextEdit, error) {
ctx, done := trace.StartSpan(ctx, "source.Imports")
defer done()
data, _, err := f.Handle(ctx).Read(ctx)
if err != nil {
return nil, err
}
pkg, err := f.GetPackage(ctx)
if err != nil {
return nil, err
@ -93,7 +91,15 @@ func Imports(ctx context.Context, view View, f GoFile, rng span.Range) ([]protoc
if hasListErrors(pkg.GetErrors()) {
return nil, errors.Errorf("%s has list errors, not running goimports", f.URI())
}
var ph ParseGoHandle
for _, h := range pkg.GetHandles() {
if h.File().Identity().URI == f.URI() {
ph = h
}
}
if ph == nil {
return nil, err
}
options := &imports.Options{
// Defaults.
AllErrors: true,
@ -105,14 +111,22 @@ func Imports(ctx context.Context, view View, f GoFile, rng span.Range) ([]protoc
}
var formatted []byte
importFn := func(opts *imports.Options) error {
formatted, err = imports.Process(f.URI().Filename(), data, opts)
data, _, err := ph.File().Read(ctx)
if err != nil {
return err
}
formatted, err = imports.Process(ph.File().Identity().URI.Filename(), data, opts)
return err
}
err = view.RunProcessEnvFunc(ctx, importFn, options)
if err != nil {
return nil, err
}
return computeTextEdits(ctx, view.Session().Cache().FileSet(), f, string(formatted))
_, m, err := ph.Parse(ctx)
if m == nil {
return nil, err
}
return computeTextEdits(ctx, ph.File(), m, string(formatted))
}
type ImportFix struct {
@ -132,11 +146,6 @@ func AllImportsFixes(ctx context.Context, view View, f File) (edits []protocol.T
if !ok {
return nil, nil, errors.Errorf("no imports fixes for non-Go files: %v", err)
}
data, _, err := f.Handle(ctx).Read(ctx)
if err != nil {
return nil, nil, err
}
pkg, err := gof.GetPackage(ctx)
if err != nil {
return nil, nil, err
@ -154,6 +163,19 @@ func AllImportsFixes(ctx context.Context, view View, f File) (edits []protocol.T
TabWidth: 8,
}
importFn := func(opts *imports.Options) error {
var ph ParseGoHandle
for _, h := range pkg.GetHandles() {
if h.File().Identity().URI == f.URI() {
ph = h
}
}
if ph == nil {
return errors.Errorf("no ParseGoHandle for %s", f.URI())
}
data, _, err := ph.File().Read(ctx)
if err != nil {
return err
}
fixes, err := imports.FixImports(f.URI().Filename(), data, opts)
if err != nil {
return err
@ -163,7 +185,11 @@ func AllImportsFixes(ctx context.Context, view View, f File) (edits []protocol.T
if err != nil {
return err
}
edits, err = computeTextEdits(ctx, view.Session().Cache().FileSet(), f, string(formatted))
_, m, err := ph.Parse(ctx)
if m == nil {
return err
}
edits, err = computeTextEdits(ctx, ph.File(), m, string(formatted))
if err != nil {
return err
}
@ -174,7 +200,7 @@ func AllImportsFixes(ctx context.Context, view View, f File) (edits []protocol.T
if err != nil {
return err
}
edits, err := computeTextEdits(ctx, view.Session().Cache().FileSet(), f, string(formatted))
edits, err := computeTextEdits(ctx, ph.File(), m, string(formatted))
if err != nil {
return err
}
@ -242,19 +268,16 @@ func hasListErrors(errors []packages.Error) bool {
return false
}
func computeTextEdits(ctx context.Context, fset *token.FileSet, f File, formatted string) ([]protocol.TextEdit, error) {
func computeTextEdits(ctx context.Context, fh FileHandle, m *protocol.ColumnMapper, formatted string) ([]protocol.TextEdit, error) {
ctx, done := trace.StartSpan(ctx, "source.computeTextEdits")
defer done()
data, _, err := f.Handle(ctx).Read(ctx)
data, _, err := fh.Read(ctx)
if err != nil {
return nil, err
}
edits := diff.ComputeEdits(f.URI(), string(data), formatted)
m := protocol.NewColumnMapper(f.URI(), f.URI().Filename(), fset, nil, data)
edits := diff.ComputeEdits(fh.Identity().URI, string(data), formatted)
return ToProtocolEdits(m, edits)
}
func ToProtocolEdits(m *protocol.ColumnMapper, edits []diff.TextEdit) ([]protocol.TextEdit, error) {

View File

@ -226,10 +226,13 @@ func hasErrorType(obj types.Object) bool {
func objToNode(ctx context.Context, view View, pkg Package, obj types.Object) (ast.Decl, error) {
uri := span.FileURI(view.Session().Cache().FileSet().Position(obj.Pos()).Filename)
_, declAST, _, err := pkg.FindFile(ctx, uri, obj.Pos())
_, declAST, _, err := pkg.FindFile(ctx, uri)
if declAST == nil {
return nil, err
}
if !(declAST.Pos() <= obj.Pos() && obj.Pos() <= declAST.End()) {
return nil, errors.Errorf("no file for %s", obj.Name())
}
path, _ := astutil.PathEnclosingInterval(declAST, obj.Pos(), obj.Pos())
if path == nil {
return nil, errors.Errorf("no path for object %v", obj.Name())

View File

@ -178,7 +178,7 @@ func (i *IdentifierInfo) getPkgName(ctx context.Context) (*IdentifierInfo, error
}
for _, ph := range pkg.GetHandles() {
if ph.File().Identity().URI == i.File.File().Identity().URI {
file, err = ph.Cached(ctx)
file, _, err = ph.Cached(ctx)
}
}
if file == nil {

View File

@ -488,10 +488,12 @@ func (r *runner) Format(t *testing.T, data tests.Formats) {
}
data, _, err := f.Handle(ctx).Read(ctx)
if err != nil {
t.Error(err)
continue
t.Fatal(err)
}
m, err := r.data.Mapper(f.URI())
if err != nil {
t.Fatal(err)
}
m := protocol.NewColumnMapper(uri, filename, r.view.Session().Cache().FileSet(), nil, data)
diffEdits, err := source.FromProtocolEdits(m, edits)
if err != nil {
t.Error(err)
@ -535,10 +537,12 @@ func (r *runner) Import(t *testing.T, data tests.Imports) {
}
data, _, err := fh.Read(ctx)
if err != nil {
t.Error(err)
continue
t.Fatal(err)
}
m, err := r.data.Mapper(fh.Identity().URI)
if err != nil {
t.Fatal(err)
}
m := protocol.NewColumnMapper(uri, filename, r.view.Session().Cache().FileSet(), nil, data)
diffEdits, err := source.FromProtocolEdits(m, edits)
if err != nil {
t.Error(err)
@ -717,12 +721,15 @@ func (r *runner) Rename(t *testing.T, data tests.Renames) {
if err != nil {
t.Fatalf("failed for %v: %v", spn, err)
}
data, _, err := f.Handle(ctx).Read(ctx)
fh := f.Handle(ctx)
data, _, err := fh.Read(ctx)
if err != nil {
t.Error(err)
continue
t.Fatal(err)
}
m, err := r.data.Mapper(fh.Identity().URI)
if err != nil {
t.Fatal(err)
}
m := protocol.NewColumnMapper(f.URI(), f.URI().Filename(), r.data.Exported.ExpectFileSet, nil, data)
filename := filepath.Base(editSpn.Filename())
diffEdits, err := source.FromProtocolEdits(m, edits)
if err != nil {
@ -922,13 +929,12 @@ func (r *runner) Link(t *testing.T, data tests.Links) {
// This is a pure LSP feature, no source level functionality to be tested.
}
func spanToRange(data *tests.Data, span span.Span) (*protocol.ColumnMapper, protocol.Range, error) {
contents, err := data.Exported.FileContents(span.URI().Filename())
func spanToRange(data *tests.Data, spn span.Span) (*protocol.ColumnMapper, protocol.Range, error) {
m, err := data.Mapper(spn.URI())
if err != nil {
return nil, protocol.Range{}, err
}
m := protocol.NewColumnMapper(span.URI(), span.URI().Filename(), data.Exported.ExpectFileSet, nil, contents)
srcRng, err := m.Range(span)
srcRng, err := m.Range(spn)
if err != nil {
return nil, protocol.Range{}, err
}

View File

@ -25,7 +25,7 @@ func DocumentSymbols(ctx context.Context, view View, f GoFile) ([]protocol.Docum
var file *ast.File
for _, ph := range pkg.GetHandles() {
if ph.File().Identity().URI == f.URI() {
file, err = ph.Cached(ctx)
file, _, err = ph.Cached(ctx)
}
}
if file == nil {

View File

@ -51,7 +51,7 @@ func (s mappedRange) URI() span.URI {
return s.m.URI
}
// bestCheckPackageHandle picks the "narrowest" package for a given file.
// bestPackage picks the "narrowest" package for a given file.
//
// By "narrowest" package, we mean the package with the fewest number of files
// that includes the given file. This solves the problem of test variants,
@ -126,20 +126,11 @@ func pkgToMapper(ctx context.Context, view View, pkg Package, uri span.URI) (*as
if ph == nil {
return nil, nil, errors.Errorf("no ParseGoHandle for %s", uri)
}
file, err := ph.Cached(ctx)
file, m, err := ph.Cached(ctx)
if file == nil {
return nil, nil, err
}
data, _, err := ph.File().Read(ctx)
if err != nil {
return nil, nil, err
}
fset := view.Session().Cache().FileSet()
tok := fset.File(file.Pos())
if tok == nil {
return nil, nil, errors.Errorf("no token.File for %s", uri)
}
return file, protocol.NewColumnMapper(uri, uri.Filename(), fset, tok, data), nil
return file, m, nil
}
func builtinFileToMapper(ctx context.Context, view View, f GoFile, file *ast.File) (*ast.File, *protocol.ColumnMapper, error) {
@ -148,12 +139,13 @@ func builtinFileToMapper(ctx context.Context, view View, f GoFile, file *ast.Fil
if err != nil {
return nil, nil, err
}
fset := view.Session().Cache().FileSet()
tok := fset.File(file.Pos())
if tok == nil {
return nil, nil, errors.Errorf("no token.File for %s", f.URI())
converter := span.NewContentConverter(fh.Identity().URI.Filename(), data)
m := &protocol.ColumnMapper{
URI: fh.Identity().URI,
Content: data,
Converter: converter,
}
return nil, protocol.NewColumnMapper(f.URI(), f.URI().Filename(), fset, tok, data), nil
return nil, m, nil
}
func IsGenerated(ctx context.Context, view View, uri span.URI) bool {
@ -162,7 +154,7 @@ func IsGenerated(ctx context.Context, view View, uri span.URI) bool {
return false
}
ph := view.Session().Cache().ParseGoHandle(f.Handle(ctx), ParseHeader)
parsed, err := ph.Parse(ctx)
parsed, _, err := ph.Parse(ctx)
if parsed == nil {
return false
}

View File

@ -80,10 +80,10 @@ type ParseGoHandle interface {
// Parse returns the parsed AST for the file.
// If the file is not available, returns nil and an error.
Parse(ctx context.Context) (*ast.File, error)
Parse(ctx context.Context) (*ast.File, *protocol.ColumnMapper, error)
// Cached returns the AST for this handle, if it has already been stored.
Cached(ctx context.Context) (*ast.File, error)
Cached(ctx context.Context) (*ast.File, *protocol.ColumnMapper, error)
}
// ParseMode controls the content of the AST produced when parsing a source file.
@ -321,5 +321,5 @@ type Package interface {
// FindFile returns the AST and type information for a file that may
// belong to or be part of a dependency of the given package.
FindFile(ctx context.Context, uri span.URI, pos token.Pos) (ParseGoHandle, *ast.File, Package, error)
FindFile(ctx context.Context, uri span.URI) (ParseGoHandle, *ast.File, Package, error)
}

View File

@ -15,6 +15,7 @@ import (
"path/filepath"
"sort"
"strings"
"sync"
"testing"
"golang.org/x/tools/go/expect"
@ -99,6 +100,9 @@ type Data struct {
fragments map[string]string
dir string
golden map[string]*Golden
mappersMu sync.Mutex
mappers map[span.URI]*protocol.ColumnMapper
}
type Tests interface {
@ -184,6 +188,7 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
dir: dir,
fragments: map[string]string{},
golden: map[string]*Golden{},
mappers: map[span.URI]*protocol.ColumnMapper{},
}
files := packagestest.MustCopyFileTree(dir)
@ -426,6 +431,25 @@ func Run(t *testing.T, tests Tests, data *Data) {
}
}
func (data *Data) Mapper(uri span.URI) (*protocol.ColumnMapper, error) {
data.mappersMu.Lock()
defer data.mappersMu.Unlock()
if _, ok := data.mappers[uri]; !ok {
content, err := data.Exported.FileContents(uri.Filename())
if err != nil {
return nil, err
}
converter := span.NewContentConverter(uri.Filename(), content)
data.mappers[uri] = &protocol.ColumnMapper{
URI: uri,
Converter: converter,
Content: content,
}
}
return data.mappers[uri], nil
}
func (data *Data) Golden(tag string, target string, update func() ([]byte, error)) []byte {
data.t.Helper()
fragment, found := data.fragments[target]
@ -672,20 +696,18 @@ func (data *Data) collectPrepareRenames(src span.Span, rng span.Range, placehold
// make the range just be the start.
rng = span.NewRange(rng.FileSet, rng.Start, rng.Start)
}
contents, err := data.Exported.FileContents(src.URI().Filename())
m, err := data.Mapper(src.URI())
if err != nil {
return
data.t.Fatal(err)
}
m := protocol.NewColumnMapper(src.URI(), src.URI().Filename(), data.Exported.ExpectFileSet, nil, contents)
// Convert range to span and then to protocol.Range.
spn, err := rng.Span()
if err != nil {
return
data.t.Fatal(err)
}
prng, err := m.Range(spn)
if err != nil {
return
data.t.Fatal(err)
}
data.PrepareRenames[src] = &source.PrepareItem{
Range: prng,
@ -694,14 +716,13 @@ func (data *Data) collectPrepareRenames(src span.Span, rng span.Range, placehold
}
func (data *Data) collectSymbols(name string, spn span.Span, kind string, parentName string) {
contents, err := data.Exported.FileContents(spn.URI().Filename())
m, err := data.Mapper(spn.URI())
if err != nil {
return
data.t.Fatal(err)
}
m := protocol.NewColumnMapper(spn.URI(), spn.URI().Filename(), data.Exported.ExpectFileSet, nil, contents)
rng, err := m.Range(spn)
if err != nil {
return
data.t.Fatal(err)
}
sym := protocol.DocumentSymbol{
Name: name,

View File

@ -109,10 +109,14 @@ func (s *Server) applyChanges(ctx context.Context, uri span.URI, changes []proto
if err != nil {
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "file not found (%v)", err)
}
fset := s.session.Cache().FileSet()
for _, change := range changes {
// Update column mapper along with the content.
m := protocol.NewColumnMapper(uri, uri.Filename(), fset, nil, content)
converter := span.NewContentConverter(uri.Filename(), content)
m := &protocol.ColumnMapper{
URI: uri,
Converter: converter,
Content: content,
}
spn, err := m.RangeSpan(*change.Range)
if err != nil {