mirror of
https://github.com/golang/go
synced 2024-11-18 12:14:42 -07:00
internal/lsp: rework snapshots and cache FileHandles per-snapshot
This change does not complete the work to handle snapshots correctly, but it does implement the behavior of re-building the snapshot on each file invalidation. It also moves to the approach of caching the FileHandles on the snapshot, rather than in the goFile object, which is now not necessary. Finally, this change shifts the logic of metadata invalidation into the content invalidation step, so there is less logic to decide if we should re-load a package or not. Change-Id: I18387c385fb070da4db1302bf97035ce6328b5c3 Reviewed-on: https://go-review.googlesource.com/c/tools/+/197799 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
parent
8b695b21ef
commit
57610eddc9
63
internal/lsp/cache/check.go
vendored
63
internal/lsp/cache/check.go
vendored
@ -23,9 +23,9 @@ import (
|
||||
)
|
||||
|
||||
type importer struct {
|
||||
view *view
|
||||
ctx context.Context
|
||||
config *packages.Config
|
||||
snapshot *snapshot
|
||||
ctx context.Context
|
||||
config *packages.Config
|
||||
|
||||
// seen maintains the set of previously imported packages.
|
||||
// If we have seen a package that is already in this map, we have a circular import.
|
||||
@ -62,7 +62,7 @@ type checkPackageHandle struct {
|
||||
config *packages.Config
|
||||
}
|
||||
|
||||
func (cph *checkPackageHandle) mode() source.ParseMode {
|
||||
func (cph *checkPackageHandle) Mode() source.ParseMode {
|
||||
if len(cph.files) == 0 {
|
||||
return -1
|
||||
}
|
||||
@ -83,19 +83,15 @@ type checkPackageData struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (pkg *pkg) GetImport(ctx context.Context, pkgPath string) (source.Package, error) {
|
||||
if imp := pkg.imports[packagePath(pkgPath)]; imp != nil {
|
||||
return imp, nil
|
||||
}
|
||||
// Don't return a nil pointer because that still satisfies the interface.
|
||||
return nil, errors.Errorf("no imported package for %s", pkgPath)
|
||||
}
|
||||
|
||||
// checkPackageHandle returns a source.CheckPackageHandle for a given package and config.
|
||||
func (imp *importer) checkPackageHandle(ctx context.Context, m *metadata) (*checkPackageHandle, error) {
|
||||
func (imp *importer) checkPackageHandle(ctx context.Context, id packageID, s *snapshot) (*checkPackageHandle, error) {
|
||||
m := s.getMetadata(id)
|
||||
if m == nil {
|
||||
return nil, errors.Errorf("no metadata for %s", id)
|
||||
}
|
||||
phs, err := imp.parseGoHandles(ctx, m)
|
||||
if err != nil {
|
||||
log.Error(ctx, "no ParseGoHandles", err, telemetry.Package.Of(m.id))
|
||||
log.Error(ctx, "no ParseGoHandles", err, telemetry.Package.Of(id))
|
||||
return nil, err
|
||||
}
|
||||
cph := &checkPackageHandle{
|
||||
@ -104,12 +100,19 @@ func (imp *importer) checkPackageHandle(ctx context.Context, m *metadata) (*chec
|
||||
config: imp.config,
|
||||
imports: make(map[packagePath]*checkPackageHandle),
|
||||
}
|
||||
h := imp.view.session.cache.store.Bind(cph.key(), func(ctx context.Context) interface{} {
|
||||
h := imp.snapshot.view.session.cache.store.Bind(cph.key(), func(ctx context.Context) interface{} {
|
||||
data := &checkPackageData{}
|
||||
data.pkg, data.err = imp.typeCheck(ctx, cph, m)
|
||||
return data
|
||||
})
|
||||
|
||||
cph.handle = h
|
||||
|
||||
// Cache the CheckPackageHandle in the snapshot.
|
||||
for _, ph := range cph.files {
|
||||
uri := ph.File().Identity().URI
|
||||
s.addPackage(uri, cph)
|
||||
}
|
||||
return cph, nil
|
||||
}
|
||||
|
||||
@ -190,16 +193,16 @@ func (cph *checkPackageHandle) key() checkPackageKey {
|
||||
func (imp *importer) parseGoHandles(ctx context.Context, m *metadata) ([]source.ParseGoHandle, error) {
|
||||
phs := make([]source.ParseGoHandle, 0, len(m.files))
|
||||
for _, uri := range m.files {
|
||||
f, err := imp.view.GetFile(ctx, uri)
|
||||
f, err := imp.snapshot.view.GetFile(ctx, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fh := f.Handle(ctx)
|
||||
fh := imp.snapshot.Handle(ctx, f)
|
||||
mode := source.ParseExported
|
||||
if imp.topLevelPackageID == m.id {
|
||||
mode = source.ParseFull
|
||||
}
|
||||
phs = append(phs, imp.view.session.cache.ParseGoHandle(fh, mode))
|
||||
phs = append(phs, imp.snapshot.view.session.cache.ParseGoHandle(fh, mode))
|
||||
}
|
||||
return phs, nil
|
||||
}
|
||||
@ -236,7 +239,7 @@ func (imp *importer) typeCheck(ctx context.Context, cph *checkPackageHandle, m *
|
||||
defer done()
|
||||
|
||||
pkg := &pkg{
|
||||
view: imp.view,
|
||||
view: imp.snapshot.view,
|
||||
id: m.id,
|
||||
pkgPath: m.pkgPath,
|
||||
files: cph.Files(),
|
||||
@ -259,13 +262,17 @@ func (imp *importer) typeCheck(ctx context.Context, cph *checkPackageHandle, m *
|
||||
}
|
||||
// Set imports of package to correspond to cached packages.
|
||||
cimp := imp.child(ctx, pkg, cph)
|
||||
for _, child := range m.children {
|
||||
childHandle, err := cimp.checkPackageHandle(ctx, child)
|
||||
if err != nil {
|
||||
log.Error(ctx, "no check package handle", err, telemetry.Package.Of(child.id))
|
||||
for _, depID := range m.deps {
|
||||
dep := imp.snapshot.getMetadata(depID)
|
||||
if dep == nil {
|
||||
continue
|
||||
}
|
||||
cph.imports[child.pkgPath] = childHandle
|
||||
depHandle, err := cimp.checkPackageHandle(ctx, depID, imp.snapshot)
|
||||
if err != nil {
|
||||
log.Error(ctx, "no check package handle", err, telemetry.Package.Of(depID))
|
||||
continue
|
||||
}
|
||||
cph.imports[dep.pkgPath] = depHandle
|
||||
}
|
||||
var (
|
||||
files = make([]*ast.File, len(pkg.files))
|
||||
@ -284,7 +291,7 @@ func (imp *importer) typeCheck(ctx context.Context, cph *checkPackageHandle, m *
|
||||
|
||||
for _, err := range parseErrors {
|
||||
if err != nil {
|
||||
imp.view.session.cache.appendPkgError(pkg, err)
|
||||
imp.snapshot.view.session.cache.appendPkgError(pkg, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -308,11 +315,11 @@ func (imp *importer) typeCheck(ctx context.Context, cph *checkPackageHandle, m *
|
||||
|
||||
cfg := &types.Config{
|
||||
Error: func(err error) {
|
||||
imp.view.session.cache.appendPkgError(pkg, err)
|
||||
imp.snapshot.view.session.cache.appendPkgError(pkg, err)
|
||||
},
|
||||
Importer: cimp,
|
||||
}
|
||||
check := types.NewChecker(cfg, imp.view.session.cache.FileSet(), pkg.types, pkg.typesInfo)
|
||||
check := types.NewChecker(cfg, imp.snapshot.view.session.cache.FileSet(), pkg.types, pkg.typesInfo)
|
||||
|
||||
// Type checking errors are handled via the config, so ignore them here.
|
||||
_ = check.Files(files)
|
||||
@ -328,7 +335,7 @@ func (imp *importer) child(ctx context.Context, pkg *pkg, cph *checkPackageHandl
|
||||
}
|
||||
seen[pkg.id] = struct{}{}
|
||||
return &importer{
|
||||
view: imp.view,
|
||||
snapshot: imp.snapshot,
|
||||
ctx: ctx,
|
||||
config: imp.config,
|
||||
seen: seen,
|
||||
|
20
internal/lsp/cache/file.go
vendored
20
internal/lsp/cache/file.go
vendored
@ -5,11 +5,9 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go/token"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
"golang.org/x/tools/internal/span"
|
||||
@ -31,9 +29,6 @@ type fileBase struct {
|
||||
kind source.FileKind
|
||||
|
||||
view *view
|
||||
|
||||
handleMu sync.Mutex
|
||||
handle source.FileHandle
|
||||
}
|
||||
|
||||
func basename(filename string) string {
|
||||
@ -44,6 +39,10 @@ func (f *fileBase) URI() span.URI {
|
||||
return f.uris[0]
|
||||
}
|
||||
|
||||
func (f *fileBase) Kind() source.FileKind {
|
||||
return f.kind
|
||||
}
|
||||
|
||||
func (f *fileBase) filename() string {
|
||||
return f.fname
|
||||
}
|
||||
@ -53,17 +52,6 @@ func (f *fileBase) View() source.View {
|
||||
return f.view
|
||||
}
|
||||
|
||||
// Content returns a handle for the contents of the file.
|
||||
func (f *fileBase) Handle(ctx context.Context) source.FileHandle {
|
||||
f.handleMu.Lock()
|
||||
defer f.handleMu.Unlock()
|
||||
|
||||
if f.handle == nil {
|
||||
f.handle = f.view.session.GetFile(f.URI(), f.kind)
|
||||
}
|
||||
return f.handle
|
||||
}
|
||||
|
||||
func (f *fileBase) FileSet() *token.FileSet {
|
||||
return f.view.Session().Cache().FileSet()
|
||||
}
|
||||
|
171
internal/lsp/cache/gofile.go
vendored
171
internal/lsp/cache/gofile.go
vendored
@ -6,8 +6,6 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go/ast"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
"golang.org/x/tools/internal/lsp/telemetry"
|
||||
@ -15,68 +13,112 @@ import (
|
||||
errors "golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// goFile holds all of the information we know about a Go file.
|
||||
type goFile struct {
|
||||
fileBase
|
||||
func (v *view) CheckPackageHandles(ctx context.Context, f source.File) (source.Snapshot, []source.CheckPackageHandle, error) {
|
||||
// Get the snapshot that will be used for type-checking.
|
||||
s := v.getSnapshot()
|
||||
|
||||
// mu protects all mutable state of the Go file,
|
||||
// which can be modified during type-checking.
|
||||
mu sync.Mutex
|
||||
|
||||
imports []*ast.ImportSpec
|
||||
cphs, err := s.checkPackageHandles(ctx, f)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return s, cphs, nil
|
||||
}
|
||||
|
||||
type packageKey struct {
|
||||
id packageID
|
||||
mode source.ParseMode
|
||||
}
|
||||
|
||||
func (f *goFile) CheckPackageHandles(ctx context.Context) (results []source.CheckPackageHandle, err error) {
|
||||
func (s *snapshot) checkPackageHandles(ctx context.Context, f source.File) ([]source.CheckPackageHandle, error) {
|
||||
ctx = telemetry.File.With(ctx, f.URI())
|
||||
fh := f.Handle(ctx)
|
||||
|
||||
var dirty bool
|
||||
meta, pkgs := f.view.getSnapshot(f.URI())
|
||||
if len(meta) == 0 {
|
||||
dirty = true
|
||||
}
|
||||
for _, m := range meta {
|
||||
if len(m.missingDeps) != 0 {
|
||||
dirty = true
|
||||
}
|
||||
}
|
||||
for _, cph := range pkgs {
|
||||
// If we're explicitly checking if a file needs to be type-checked,
|
||||
// we need it to be fully parsed.
|
||||
if cph.mode() != source.ParseFull {
|
||||
continue
|
||||
}
|
||||
// Check if there is a fully-parsed package to which this file belongs.
|
||||
for _, file := range cph.Files() {
|
||||
if file.File().Identity() == fh.Identity() {
|
||||
results = append(results, cph)
|
||||
}
|
||||
}
|
||||
}
|
||||
if dirty || len(results) == 0 {
|
||||
cphs, err := f.view.loadParseTypecheck(ctx, f, fh)
|
||||
fh := s.Handle(ctx, f)
|
||||
|
||||
// Determine if we need to type-check the package.
|
||||
m, cphs, load, check := s.shouldCheck(fh)
|
||||
cfg := s.view.Config(ctx)
|
||||
|
||||
// We may need to re-load package metadata.
|
||||
// We only need to this if it has been invalidated, and is therefore unvailable.
|
||||
if load {
|
||||
var err error
|
||||
m, err = s.load(ctx, f.URI(), cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cphs == nil {
|
||||
return results, nil
|
||||
// If load has explicitly returned nil metadata and no error,
|
||||
// it means that we should not re-type-check the packages.
|
||||
if m == nil {
|
||||
return cphs, nil
|
||||
}
|
||||
results = cphs
|
||||
}
|
||||
if len(results) == 0 {
|
||||
if check {
|
||||
var results []source.CheckPackageHandle
|
||||
for _, m := range m {
|
||||
imp := &importer{
|
||||
config: cfg,
|
||||
seen: make(map[packageID]struct{}),
|
||||
topLevelPackageID: m.id,
|
||||
snapshot: s,
|
||||
}
|
||||
cph, err := imp.checkPackageHandle(ctx, m.id, s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, cph)
|
||||
}
|
||||
cphs = results
|
||||
}
|
||||
if len(cphs) == 0 {
|
||||
return nil, errors.Errorf("no CheckPackageHandles for %s", f.URI())
|
||||
}
|
||||
return results, nil
|
||||
return cphs, nil
|
||||
}
|
||||
|
||||
func (v *view) GetActiveReverseDeps(ctx context.Context, uri span.URI) (results []source.CheckPackageHandle) {
|
||||
func (s *snapshot) shouldCheck(fh source.FileHandle) (m []*metadata, cphs []source.CheckPackageHandle, load, check bool) {
|
||||
// Get the metadata for the given file.
|
||||
m = s.getMetadataForURI(fh.Identity().URI)
|
||||
|
||||
// If there is no metadata for the package, we definitely need to type-check again.
|
||||
if len(m) == 0 {
|
||||
return nil, nil, true, true
|
||||
}
|
||||
|
||||
// If the metadata for the package had missing dependencies,
|
||||
// we _may_ need to re-check. If the missing dependencies haven't changed
|
||||
// since previous load, we will not check again.
|
||||
for _, m := range m {
|
||||
if len(m.missingDeps) != 0 {
|
||||
load = true
|
||||
check = true
|
||||
}
|
||||
}
|
||||
// We expect to see a checked package for each package ID,
|
||||
// and it should be parsed in full mode.
|
||||
var (
|
||||
rdeps = v.reverseDependencies(ctx, uri)
|
||||
expected = len(m)
|
||||
cachedCPHs = s.getPackages(fh.Identity().URI)
|
||||
)
|
||||
if len(cachedCPHs) < expected {
|
||||
return m, nil, load, true
|
||||
}
|
||||
for _, cph := range cachedCPHs {
|
||||
// The package may have been checked in the exported mode.
|
||||
if cph.Mode() != source.ParseFull {
|
||||
continue
|
||||
}
|
||||
// Confirm that the file belongs to this package.
|
||||
for _, file := range cph.Files() {
|
||||
if file.File().Identity() == fh.Identity() {
|
||||
cphs = append(cphs, cph)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(cphs) < expected {
|
||||
return m, nil, load, true
|
||||
}
|
||||
return m, cphs, load, check
|
||||
}
|
||||
|
||||
func (v *view) GetActiveReverseDeps(ctx context.Context, f source.File) (results []source.CheckPackageHandle) {
|
||||
var (
|
||||
s = v.getSnapshot()
|
||||
rdeps = transitiveReverseDependencies(ctx, f.URI(), s)
|
||||
files = v.openFiles(ctx, rdeps)
|
||||
seen = make(map[span.URI]struct{})
|
||||
)
|
||||
@ -84,11 +126,7 @@ func (v *view) GetActiveReverseDeps(ctx context.Context, uri span.URI) (results
|
||||
if _, ok := seen[f.URI()]; ok {
|
||||
continue
|
||||
}
|
||||
gof, ok := f.(source.GoFile)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
cphs, err := gof.CheckPackageHandles(ctx)
|
||||
cphs, err := s.checkPackageHandles(ctx, f)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@ -100,3 +138,28 @@ func (v *view) GetActiveReverseDeps(ctx context.Context, uri span.URI) (results
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func transitiveReverseDependencies(ctx context.Context, uri span.URI, s *snapshot) (result []span.URI) {
|
||||
var (
|
||||
seen = make(map[packageID]struct{})
|
||||
uris = make(map[span.URI]struct{})
|
||||
topLevelURIs = make(map[span.URI]struct{})
|
||||
)
|
||||
|
||||
metadata := s.getMetadataForURI(uri)
|
||||
|
||||
for _, m := range metadata {
|
||||
for _, uri := range m.files {
|
||||
topLevelURIs[uri] = struct{}{}
|
||||
}
|
||||
s.reverseDependencies(m.id, uris, seen)
|
||||
}
|
||||
// Filter out the URIs that belong to the original package.
|
||||
for uri := range uris {
|
||||
if _, ok := topLevelURIs[uri]; ok {
|
||||
continue
|
||||
}
|
||||
result = append(result, uri)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
271
internal/lsp/cache/load.go
vendored
271
internal/lsp/cache/load.go
vendored
@ -7,7 +7,7 @@ package cache
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
@ -19,122 +19,37 @@ import (
|
||||
errors "golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func (v *view) loadParseTypecheck(ctx context.Context, f *goFile, fh source.FileHandle) ([]source.CheckPackageHandle, error) {
|
||||
ctx, done := trace.StartSpan(ctx, "cache.view.loadParseTypeCheck", telemetry.URI.Of(f.URI()))
|
||||
defer done()
|
||||
|
||||
meta, err := v.load(ctx, f, fh)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If load has explicitly returns nil metadata and no error,
|
||||
// it means that we should not re-typecheck the packages.
|
||||
if meta == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var (
|
||||
cphs []*checkPackageHandle
|
||||
results []source.CheckPackageHandle
|
||||
)
|
||||
for _, m := range meta {
|
||||
imp := &importer{
|
||||
view: v,
|
||||
config: v.Config(ctx),
|
||||
seen: make(map[packageID]struct{}),
|
||||
topLevelPackageID: m.id,
|
||||
}
|
||||
cph, err := imp.checkPackageHandle(ctx, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, ph := range cph.files {
|
||||
if err := v.cachePerFile(ctx, ph); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
cphs = append(cphs, cph)
|
||||
results = append(results, cph)
|
||||
}
|
||||
// Cache the package type information for the top-level package.
|
||||
v.updatePackages(cphs)
|
||||
return results, nil
|
||||
type packageKey struct {
|
||||
mode source.ParseMode
|
||||
id packageID
|
||||
}
|
||||
|
||||
func (v *view) cachePerFile(ctx context.Context, ph source.ParseGoHandle) error {
|
||||
file, _, _, err := ph.Parse(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := v.GetFile(ctx, ph.File().Identity().URI)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gof, ok := f.(*goFile)
|
||||
if !ok {
|
||||
return errors.Errorf("%s is not a Go file", ph.File().Identity().URI)
|
||||
}
|
||||
gof.mu.Lock()
|
||||
gof.imports = file.Imports
|
||||
gof.mu.Unlock()
|
||||
return nil
|
||||
type metadata struct {
|
||||
id packageID
|
||||
pkgPath packagePath
|
||||
name string
|
||||
files []span.URI
|
||||
typesSizes types.Sizes
|
||||
errors []packages.Error
|
||||
deps []packageID
|
||||
missingDeps map[packagePath]struct{}
|
||||
}
|
||||
|
||||
func (view *view) load(ctx context.Context, f *goFile, fh source.FileHandle) ([]*metadata, error) {
|
||||
ctx, done := trace.StartSpan(ctx, "cache.view.load", telemetry.URI.Of(f.URI()))
|
||||
func (s *snapshot) load(ctx context.Context, uri span.URI, cfg *packages.Config) ([]*metadata, error) {
|
||||
ctx, done := trace.StartSpan(ctx, "cache.view.load", telemetry.URI.Of(uri))
|
||||
defer done()
|
||||
|
||||
// Get the metadata for the file.
|
||||
meta, err := view.checkMetadata(ctx, f, fh)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(meta) == 0 {
|
||||
return nil, fmt.Errorf("no package metadata found for %s", f.URI())
|
||||
}
|
||||
return meta, nil
|
||||
}
|
||||
|
||||
// checkMetadata determines if we should run go/packages.Load for this file.
|
||||
// If yes, update the metadata for the file and its package.
|
||||
func (v *view) checkMetadata(ctx context.Context, f *goFile, fh source.FileHandle) ([]*metadata, error) {
|
||||
var shouldRunGopackages bool
|
||||
|
||||
m := v.getMetadata(fh.Identity().URI)
|
||||
if len(m) == 0 {
|
||||
shouldRunGopackages = true
|
||||
}
|
||||
// Get file content in case we don't already have it.
|
||||
parsed, _, _, err := v.session.cache.ParseGoHandle(fh, source.ParseHeader).Parse(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Check if we need to re-run go/packages before loading the package.
|
||||
shouldRunGopackages = shouldRunGopackages || v.shouldRunGopackages(ctx, f, parsed, m)
|
||||
|
||||
// The package metadata is correct as-is, so just return it.
|
||||
if !shouldRunGopackages {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Don't bother running go/packages if the context has been canceled.
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
ctx, done := trace.StartSpan(ctx, "packages.Load", telemetry.File.Of(f.filename()))
|
||||
defer done()
|
||||
|
||||
pkgs, err := packages.Load(v.Config(ctx), fmt.Sprintf("file=%s", f.filename()))
|
||||
pkgs, err := packages.Load(cfg, fmt.Sprintf("file=%s", uri.Filename()))
|
||||
log.Print(ctx, "go/packages.Load", tag.Of("packages", len(pkgs)))
|
||||
|
||||
if len(pkgs) == 0 {
|
||||
if err == nil {
|
||||
err = errors.Errorf("go/packages.Load: no packages found for %s", f.filename())
|
||||
err = errors.Errorf("go/packages.Load: no packages found for %s", uri)
|
||||
}
|
||||
// Return this error as a diagnostic to the user.
|
||||
return nil, err
|
||||
}
|
||||
m, prevMissingImports, err := v.updateMetadata(ctx, f.URI(), pkgs)
|
||||
m, prevMissingImports, err := s.updateMetadata(ctx, uri, pkgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -145,36 +60,6 @@ func (v *view) checkMetadata(ctx context.Context, f *goFile, fh source.FileHandl
|
||||
return meta, nil
|
||||
}
|
||||
|
||||
// shouldRunGopackages reparses a file's package and import declarations to
|
||||
// determine if they have changed.
|
||||
// It assumes that the caller holds the lock on the f.mu lock.
|
||||
func (v *view) shouldRunGopackages(ctx context.Context, f *goFile, file *ast.File, metadata []*metadata) bool {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
// Check if the package's name has changed, by checking if this is a filename
|
||||
// we already know about, and if so, check if its package name has changed.
|
||||
for _, m := range metadata {
|
||||
for _, uri := range m.files {
|
||||
if span.CompareURI(uri, f.URI()) == 0 {
|
||||
if m.name != file.Name.Name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the package's imports have changed, re-run `go list`.
|
||||
if len(f.imports) != len(file.Imports) {
|
||||
return true
|
||||
}
|
||||
for i, importSpec := range f.imports {
|
||||
if importSpec.Path.Value != file.Imports[i].Path.Value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func validateMetadata(ctx context.Context, metadata []*metadata, prevMissingImports map[packageID]map[packagePath]struct{}) ([]*metadata, error) {
|
||||
// If we saw incorrect metadata for this package previously, don't both rechecking it.
|
||||
for _, m := range metadata {
|
||||
@ -207,3 +92,123 @@ func sameSet(x, y map[packagePath]struct{}) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// shouldLoad reparses a file's package and import declarations to
|
||||
// determine if they have changed.
|
||||
func (c *cache) shouldLoad(ctx context.Context, s *snapshot, originalFH, currentFH source.FileHandle) bool {
|
||||
if originalFH == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// Get the original parsed file in order to check package name and imports.
|
||||
original, _, _, err := c.ParseGoHandle(originalFH, source.ParseHeader).Parse(ctx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Get the current parsed file in order to check package name and imports.
|
||||
current, _, _, err := c.ParseGoHandle(currentFH, source.ParseHeader).Parse(ctx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if the package's metadata has changed. The cases handled are:
|
||||
//
|
||||
// 1. A package's name has changed
|
||||
// 2. A file's imports have changed
|
||||
//
|
||||
if original.Name.Name != current.Name.Name {
|
||||
return true
|
||||
}
|
||||
// If the package's imports have changed, re-run `go list`.
|
||||
if len(original.Imports) != len(current.Imports) {
|
||||
return true
|
||||
}
|
||||
for i, importSpec := range original.Imports {
|
||||
// TODO: Handle the case where the imports have just been re-ordered.
|
||||
if importSpec.Path.Value != current.Imports[i].Path.Value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *snapshot) updateMetadata(ctx context.Context, uri span.URI, pkgs []*packages.Package) ([]*metadata, map[packageID]map[packagePath]struct{}, error) {
|
||||
// Clear metadata since we are re-running go/packages.
|
||||
prevMissingImports := make(map[packageID]map[packagePath]struct{})
|
||||
m := s.getMetadataForURI(uri)
|
||||
|
||||
for _, m := range m {
|
||||
if len(m.missingDeps) > 0 {
|
||||
prevMissingImports[m.id] = m.missingDeps
|
||||
}
|
||||
}
|
||||
|
||||
var results []*metadata
|
||||
for _, pkg := range pkgs {
|
||||
log.Print(ctx, "go/packages.Load", tag.Of("package", pkg.PkgPath), tag.Of("files", pkg.CompiledGoFiles))
|
||||
|
||||
// Set the metadata for this package.
|
||||
if err := s.updateImports(ctx, packagePath(pkg.PkgPath), pkg); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
m := s.getMetadata(packageID(pkg.ID))
|
||||
if m != nil {
|
||||
results = append(results, m)
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild the import graph when the metadata is updated.
|
||||
s.clearAndRebuildImportGraph()
|
||||
|
||||
if len(results) == 0 {
|
||||
return nil, nil, errors.Errorf("no metadata for %s", uri)
|
||||
}
|
||||
return results, prevMissingImports, nil
|
||||
}
|
||||
|
||||
func (s *snapshot) updateImports(ctx context.Context, pkgPath packagePath, pkg *packages.Package) error {
|
||||
// Recreate the metadata rather than reusing it to avoid locking.
|
||||
m := &metadata{
|
||||
id: packageID(pkg.ID),
|
||||
pkgPath: pkgPath,
|
||||
name: pkg.Name,
|
||||
typesSizes: pkg.TypesSizes,
|
||||
errors: pkg.Errors,
|
||||
}
|
||||
for _, filename := range pkg.CompiledGoFiles {
|
||||
uri := span.FileURI(filename)
|
||||
m.files = append(m.files, uri)
|
||||
|
||||
s.addID(uri, m.id)
|
||||
}
|
||||
|
||||
// Add the metadata to the cache.
|
||||
s.setMetadata(m)
|
||||
|
||||
for importPath, importPkg := range pkg.Imports {
|
||||
importPkgPath := packagePath(importPath)
|
||||
importID := packageID(importPkg.ID)
|
||||
|
||||
if importPkgPath == pkgPath {
|
||||
return errors.Errorf("cycle detected in %s", importPath)
|
||||
}
|
||||
m.deps = append(m.deps, importID)
|
||||
|
||||
// Don't remember any imports with significant errors.
|
||||
if importPkgPath != "unsafe" && len(importPkg.CompiledGoFiles) == 0 {
|
||||
if m.missingDeps == nil {
|
||||
m.missingDeps = make(map[packagePath]struct{})
|
||||
}
|
||||
m.missingDeps[importPkgPath] = struct{}{}
|
||||
continue
|
||||
}
|
||||
dep := s.getMetadata(importID)
|
||||
if dep == nil {
|
||||
if err := s.updateImports(ctx, importPkgPath, importPkg); err != nil {
|
||||
log.Error(ctx, "error in dependency", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
8
internal/lsp/cache/pkg.go
vendored
8
internal/lsp/cache/pkg.go
vendored
@ -191,6 +191,14 @@ func (pkg *pkg) IsIllTyped() bool {
|
||||
return pkg.types == nil || pkg.typesInfo == nil || pkg.typesSizes == nil
|
||||
}
|
||||
|
||||
func (pkg *pkg) GetImport(ctx context.Context, pkgPath string) (source.Package, error) {
|
||||
if imp := pkg.imports[packagePath(pkgPath)]; imp != nil {
|
||||
return imp, nil
|
||||
}
|
||||
// Don't return a nil pointer because that still satisfies the interface.
|
||||
return nil, errors.Errorf("no imported package for %s", pkgPath)
|
||||
}
|
||||
|
||||
func (pkg *pkg) SetDiagnostics(a *analysis.Analyzer, diags []source.Diagnostic) {
|
||||
pkg.diagMu.Lock()
|
||||
defer pkg.diagMu.Unlock()
|
||||
|
2
internal/lsp/cache/session.go
vendored
2
internal/lsp/cache/session.go
vendored
@ -102,10 +102,12 @@ func (s *session) NewView(ctx context.Context, name string, folder span.URI, opt
|
||||
packages: make(map[span.URI]map[packageKey]*checkPackageHandle),
|
||||
ids: make(map[span.URI][]packageID),
|
||||
metadata: make(map[packageID]*metadata),
|
||||
files: make(map[span.URI]source.FileHandle),
|
||||
},
|
||||
ignoredURIs: make(map[span.URI]struct{}),
|
||||
builtin: &builtinPkg{},
|
||||
}
|
||||
v.snapshot.view = v
|
||||
v.analyzers = UpdateAnalyzers(v, defaultAnalyzers)
|
||||
// Preemptively build the builtin package,
|
||||
// so we immediately add builtin.go to the list of ignored files.
|
||||
|
442
internal/lsp/cache/snapshot.go
vendored
442
internal/lsp/cache/snapshot.go
vendored
@ -2,286 +2,171 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go/types"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
"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"
|
||||
)
|
||||
|
||||
type snapshot struct {
|
||||
id uint64
|
||||
id uint64
|
||||
view *view
|
||||
|
||||
packages map[span.URI]map[packageKey]*checkPackageHandle
|
||||
ids map[span.URI][]packageID
|
||||
mu sync.Mutex
|
||||
|
||||
// ids maps file URIs to package IDs.
|
||||
// It may be invalidated on calls to go/packages.
|
||||
ids map[span.URI][]packageID
|
||||
|
||||
// metadata maps file IDs to their associated metadata.
|
||||
// It may invalidated on calls to go/packages.
|
||||
metadata map[packageID]*metadata
|
||||
|
||||
// importedBy maps package IDs to the list of packages that import them.
|
||||
importedBy map[packageID][]packageID
|
||||
|
||||
// files maps file URIs to their corresponding FileHandles.
|
||||
// It may invalidated when a file's content changes.
|
||||
files map[span.URI]source.FileHandle
|
||||
|
||||
// packages maps a file URI to a set of CheckPackageHandles to which that file belongs.
|
||||
// It may be invalidated when a file's content changes.
|
||||
packages map[span.URI]map[packageKey]*checkPackageHandle
|
||||
}
|
||||
|
||||
type metadata struct {
|
||||
id packageID
|
||||
pkgPath packagePath
|
||||
name string
|
||||
files []span.URI
|
||||
typesSizes types.Sizes
|
||||
parents map[packageID]bool
|
||||
children map[packageID]*metadata
|
||||
errors []packages.Error
|
||||
missingDeps map[packagePath]struct{}
|
||||
}
|
||||
func (s *snapshot) getImportedBy(id packageID) []packageID {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
func (v *view) getSnapshot(uri span.URI) ([]*metadata, []*checkPackageHandle) {
|
||||
v.snapshotMu.Lock()
|
||||
defer v.snapshotMu.Unlock()
|
||||
|
||||
var m []*metadata
|
||||
for _, id := range v.snapshot.ids[uri] {
|
||||
m = append(m, v.snapshot.metadata[id])
|
||||
// If we haven't rebuilt the import graph since creating the snapshot.
|
||||
if len(s.importedBy) == 0 {
|
||||
s.rebuildImportGraph()
|
||||
}
|
||||
var cphs []*checkPackageHandle
|
||||
for _, cph := range v.snapshot.packages[uri] {
|
||||
|
||||
return s.importedBy[id]
|
||||
}
|
||||
|
||||
func (s *snapshot) addPackage(uri span.URI, cph *checkPackageHandle) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
pkgs, ok := s.packages[uri]
|
||||
if !ok {
|
||||
pkgs = make(map[packageKey]*checkPackageHandle)
|
||||
s.packages[uri] = pkgs
|
||||
}
|
||||
// TODO: Check that there isn't already something set here.
|
||||
// This can't be done until we fix the cache keys for CheckPackageHandles.
|
||||
pkgs[packageKey{
|
||||
id: cph.m.id,
|
||||
mode: cph.Mode(),
|
||||
}] = cph
|
||||
}
|
||||
|
||||
func (s *snapshot) getPackages(uri span.URI) (cphs []source.CheckPackageHandle) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
for _, cph := range s.packages[uri] {
|
||||
cphs = append(cphs, cph)
|
||||
}
|
||||
return m, cphs
|
||||
return cphs
|
||||
}
|
||||
|
||||
func (v *view) getMetadata(uri span.URI) []*metadata {
|
||||
v.snapshotMu.Lock()
|
||||
defer v.snapshotMu.Unlock()
|
||||
func (s *snapshot) getMetadataForURI(uri span.URI) (metadata []*metadata) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
var m []*metadata
|
||||
for _, id := range v.snapshot.ids[uri] {
|
||||
m = append(m, v.snapshot.metadata[id])
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (v *view) getPackages(uri span.URI) map[packageKey]*checkPackageHandle {
|
||||
v.snapshotMu.Lock()
|
||||
defer v.snapshotMu.Unlock()
|
||||
|
||||
return v.snapshot.packages[uri]
|
||||
}
|
||||
|
||||
func (v *view) updateMetadata(ctx context.Context, uri span.URI, pkgs []*packages.Package) ([]*metadata, map[packageID]map[packagePath]struct{}, error) {
|
||||
v.snapshotMu.Lock()
|
||||
defer v.snapshotMu.Unlock()
|
||||
|
||||
// Clear metadata since we are re-running go/packages.
|
||||
prevMissingImports := make(map[packageID]map[packagePath]struct{})
|
||||
for _, id := range v.snapshot.ids[uri] {
|
||||
if m, ok := v.snapshot.metadata[id]; ok && len(m.missingDeps) > 0 {
|
||||
prevMissingImports[id] = m.missingDeps
|
||||
for _, id := range s.ids[uri] {
|
||||
if m, ok := s.metadata[id]; ok {
|
||||
metadata = append(metadata, m)
|
||||
}
|
||||
}
|
||||
without := make(map[span.URI]struct{})
|
||||
for _, id := range v.snapshot.ids[uri] {
|
||||
v.remove(id, without, map[packageID]struct{}{})
|
||||
}
|
||||
v.snapshot = v.snapshot.cloneMetadata(without)
|
||||
|
||||
var results []*metadata
|
||||
for _, pkg := range pkgs {
|
||||
log.Print(ctx, "go/packages.Load", tag.Of("package", pkg.PkgPath), tag.Of("files", pkg.CompiledGoFiles))
|
||||
|
||||
// Build the import graph for this package.
|
||||
if err := v.updateImportGraph(ctx, &importGraph{
|
||||
pkgPath: packagePath(pkg.PkgPath),
|
||||
pkg: pkg,
|
||||
parent: nil,
|
||||
}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
results = append(results, v.snapshot.metadata[packageID(pkg.ID)])
|
||||
}
|
||||
if len(results) == 0 {
|
||||
return nil, nil, errors.Errorf("no metadata for %s", uri)
|
||||
}
|
||||
return results, prevMissingImports, nil
|
||||
return metadata
|
||||
}
|
||||
|
||||
type importGraph struct {
|
||||
pkgPath packagePath
|
||||
pkg *packages.Package
|
||||
parent *metadata
|
||||
func (s *snapshot) setMetadata(m *metadata) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
s.metadata[m.id] = m
|
||||
}
|
||||
|
||||
func (v *view) updateImportGraph(ctx context.Context, g *importGraph) error {
|
||||
// Recreate the metadata rather than reusing it to avoid locking.
|
||||
m := &metadata{
|
||||
id: packageID(g.pkg.ID),
|
||||
pkgPath: g.pkgPath,
|
||||
name: g.pkg.Name,
|
||||
typesSizes: g.pkg.TypesSizes,
|
||||
errors: g.pkg.Errors,
|
||||
}
|
||||
for _, filename := range g.pkg.CompiledGoFiles {
|
||||
uri := span.FileURI(filename)
|
||||
v.snapshot.ids[uri] = append(v.snapshot.ids[uri], m.id)
|
||||
m.files = append(m.files, uri)
|
||||
}
|
||||
// Preserve the import graph.
|
||||
if original, ok := v.snapshot.metadata[m.id]; ok {
|
||||
m.children = original.children
|
||||
m.parents = original.parents
|
||||
}
|
||||
if m.children == nil {
|
||||
m.children = make(map[packageID]*metadata)
|
||||
}
|
||||
if m.parents == nil {
|
||||
m.parents = make(map[packageID]bool)
|
||||
}
|
||||
func (s *snapshot) getMetadata(id packageID) *metadata {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
// Add the metadata to the cache.
|
||||
v.snapshot.metadata[m.id] = m
|
||||
|
||||
// Connect the import graph.
|
||||
if g.parent != nil {
|
||||
m.parents[g.parent.id] = true
|
||||
g.parent.children[m.id] = m
|
||||
}
|
||||
for importPath, importPkg := range g.pkg.Imports {
|
||||
importPkgPath := packagePath(importPath)
|
||||
if importPkgPath == g.pkgPath {
|
||||
return errors.Errorf("cycle detected in %s", importPath)
|
||||
}
|
||||
// Don't remember any imports with significant errors.
|
||||
if importPkgPath != "unsafe" && len(importPkg.CompiledGoFiles) == 0 {
|
||||
if m.missingDeps == nil {
|
||||
m.missingDeps = make(map[packagePath]struct{})
|
||||
}
|
||||
m.missingDeps[importPkgPath] = struct{}{}
|
||||
continue
|
||||
}
|
||||
if _, ok := m.children[packageID(importPkg.ID)]; !ok {
|
||||
if err := v.updateImportGraph(ctx, &importGraph{
|
||||
pkgPath: importPkgPath,
|
||||
pkg: importPkg,
|
||||
parent: m,
|
||||
}); err != nil {
|
||||
log.Error(ctx, "error in dependency", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Clear out any imports that have been removed since the package was last loaded.
|
||||
for importID := range m.children {
|
||||
child, ok := v.snapshot.metadata[importID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
importPath := string(child.pkgPath)
|
||||
if _, ok := g.pkg.Imports[importPath]; ok {
|
||||
continue
|
||||
}
|
||||
delete(m.children, importID)
|
||||
delete(child.parents, m.id)
|
||||
}
|
||||
return nil
|
||||
return s.metadata[id]
|
||||
}
|
||||
|
||||
func (v *view) updatePackages(cphs []*checkPackageHandle) {
|
||||
v.snapshotMu.Lock()
|
||||
defer v.snapshotMu.Unlock()
|
||||
func (s *snapshot) addID(uri span.URI, id packageID) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
for _, cph := range cphs {
|
||||
for _, ph := range cph.files {
|
||||
uri := ph.File().Identity().URI
|
||||
if _, ok := v.snapshot.packages[uri]; !ok {
|
||||
v.snapshot.packages[uri] = make(map[packageKey]*checkPackageHandle)
|
||||
}
|
||||
v.snapshot.packages[uri][packageKey{
|
||||
id: cph.m.id,
|
||||
mode: ph.Mode(),
|
||||
}] = cph
|
||||
}
|
||||
}
|
||||
s.ids[uri] = append(s.ids[uri], id)
|
||||
}
|
||||
|
||||
// invalidateContent invalidates the content of a Go file,
|
||||
// including any position and type information that depends on it.
|
||||
func (v *view) invalidateContent(ctx context.Context, f *goFile) {
|
||||
f.handleMu.Lock()
|
||||
defer f.handleMu.Unlock()
|
||||
func (s *snapshot) getIDs(uri span.URI) []packageID {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
without := make(map[span.URI]struct{})
|
||||
|
||||
// Remove the package and all of its reverse dependencies from the cache.
|
||||
v.snapshotMu.Lock()
|
||||
defer v.snapshotMu.Unlock()
|
||||
|
||||
for _, id := range v.snapshot.ids[f.URI()] {
|
||||
f.view.remove(id, without, map[packageID]struct{}{})
|
||||
}
|
||||
v.snapshot = v.snapshot.clonePackages(without)
|
||||
f.handle = nil
|
||||
return s.ids[uri]
|
||||
}
|
||||
|
||||
// invalidateMeta invalidates package metadata for all files in f's
|
||||
// package. This forces f's package's metadata to be reloaded next
|
||||
// time the package is checked.
|
||||
func (v *view) invalidateMetadata(uri span.URI) {
|
||||
v.snapshotMu.Lock()
|
||||
defer v.snapshotMu.Unlock()
|
||||
func (s *snapshot) getFile(uri span.URI) source.FileHandle {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
without := make(map[span.URI]struct{})
|
||||
|
||||
for _, id := range v.snapshot.ids[uri] {
|
||||
v.remove(id, without, map[packageID]struct{}{})
|
||||
}
|
||||
v.snapshot = v.snapshot.cloneMetadata(without)
|
||||
return s.files[uri]
|
||||
}
|
||||
|
||||
// remove invalidates a package and its reverse dependencies in the view's
|
||||
// package cache. It is assumed that the caller has locked both the mutexes
|
||||
// of both the mcache and the pcache.
|
||||
func (v *view) remove(id packageID, toDelete map[span.URI]struct{}, seen map[packageID]struct{}) {
|
||||
if _, ok := seen[id]; ok {
|
||||
return
|
||||
}
|
||||
m, ok := v.snapshot.metadata[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
for parentID := range m.parents {
|
||||
v.remove(parentID, toDelete, seen)
|
||||
}
|
||||
for _, uri := range m.files {
|
||||
toDelete[uri] = struct{}{}
|
||||
func (s *snapshot) Handle(ctx context.Context, f source.File) source.FileHandle {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if _, ok := s.files[f.URI()]; !ok {
|
||||
s.files[f.URI()] = s.view.session.GetFile(f.URI(), f.Kind())
|
||||
}
|
||||
return s.files[f.URI()]
|
||||
}
|
||||
|
||||
func (s *snapshot) clonePackages(without map[span.URI]struct{}) *snapshot {
|
||||
func (s *snapshot) clone(withoutURI span.URI, withoutTypes, withoutMetadata map[span.URI]struct{}) *snapshot {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
result := &snapshot{
|
||||
id: s.id + 1,
|
||||
packages: make(map[span.URI]map[packageKey]*checkPackageHandle),
|
||||
ids: s.ids,
|
||||
metadata: s.metadata,
|
||||
id: s.id + 1,
|
||||
view: s.view,
|
||||
packages: make(map[span.URI]map[packageKey]*checkPackageHandle),
|
||||
ids: make(map[span.URI][]packageID),
|
||||
metadata: make(map[packageID]*metadata),
|
||||
importedBy: make(map[packageID][]packageID),
|
||||
files: make(map[span.URI]source.FileHandle),
|
||||
}
|
||||
// Copy all of the FileHandles except for the one that was invalidated.
|
||||
for k, v := range s.files {
|
||||
if k == withoutURI {
|
||||
continue
|
||||
}
|
||||
result.files[k] = v
|
||||
}
|
||||
for k, v := range s.packages {
|
||||
if _, ok := without[k]; ok {
|
||||
continue
|
||||
if withoutTypes != nil {
|
||||
if _, ok := withoutTypes[k]; ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
result.packages[k] = v
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *snapshot) cloneMetadata(without map[span.URI]struct{}) *snapshot {
|
||||
result := &snapshot{
|
||||
id: s.id + 1,
|
||||
packages: s.packages,
|
||||
ids: make(map[span.URI][]packageID),
|
||||
metadata: make(map[packageID]*metadata),
|
||||
}
|
||||
withoutIDs := make(map[packageID]struct{})
|
||||
for k, ids := range s.ids {
|
||||
if _, ok := without[k]; ok {
|
||||
for _, id := range ids {
|
||||
withoutIDs[id] = struct{}{}
|
||||
if withoutMetadata != nil {
|
||||
if _, ok := withoutMetadata[k]; ok {
|
||||
for _, id := range ids {
|
||||
withoutIDs[id] = struct{}{}
|
||||
}
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
result.ids[k] = ids
|
||||
}
|
||||
@ -294,34 +179,91 @@ func (s *snapshot) cloneMetadata(without map[span.URI]struct{}) *snapshot {
|
||||
return result
|
||||
}
|
||||
|
||||
func (v *view) reverseDependencies(ctx context.Context, uri span.URI) map[span.URI]struct{} {
|
||||
seen := make(map[packageID]struct{})
|
||||
uris := make(map[span.URI]struct{})
|
||||
// invalidateContent invalidates the content of a Go file,
|
||||
// including any position and type information that depends on it.
|
||||
func (v *view) invalidateContent(ctx context.Context, uri span.URI, kind source.FileKind) {
|
||||
withoutTypes := make(map[span.URI]struct{})
|
||||
withoutMetadata := make(map[span.URI]struct{})
|
||||
|
||||
// This should be the only time we hold the view's snapshot lock for any period of time.
|
||||
v.snapshotMu.Lock()
|
||||
defer v.snapshotMu.Unlock()
|
||||
|
||||
for _, id := range v.snapshot.ids[uri] {
|
||||
v.rdeps(id, seen, uris, id)
|
||||
ids := v.snapshot.getIDs(uri)
|
||||
|
||||
// Remove the package and all of its reverse dependencies from the cache.
|
||||
for _, id := range ids {
|
||||
v.snapshot.reverseDependencies(id, withoutTypes, map[packageID]struct{}{})
|
||||
}
|
||||
return uris
|
||||
|
||||
// Get the original FileHandle for the URI, if it exists.
|
||||
originalFH := v.snapshot.getFile(uri)
|
||||
|
||||
// Get the current FileHandle for the URI.
|
||||
currentFH := v.session.GetFile(uri, kind)
|
||||
|
||||
// Check if the file's package name or imports have changed,
|
||||
// and if so, invalidate metadata.
|
||||
if v.session.cache.shouldLoad(ctx, v.snapshot, originalFH, currentFH) {
|
||||
withoutMetadata = withoutTypes
|
||||
|
||||
// TODO: If a package's name has changed,
|
||||
// we should invalidate the metadata for the new package name (if it exists).
|
||||
}
|
||||
|
||||
v.snapshot = v.snapshot.clone(uri, withoutTypes, withoutMetadata)
|
||||
}
|
||||
|
||||
func (v *view) rdeps(topID packageID, seen map[packageID]struct{}, results map[span.URI]struct{}, id packageID) {
|
||||
// invalidateMetadata invalidates package metadata for all files in f's
|
||||
// package. This forces f's package's metadata to be reloaded next
|
||||
// time the package is checked.
|
||||
//
|
||||
// TODO: This function shouldn't be necessary.
|
||||
// We should be able to handle its use cases more efficiently.
|
||||
func (v *view) invalidateMetadata(uri span.URI) {
|
||||
v.snapshotMu.Lock()
|
||||
defer v.snapshotMu.Unlock()
|
||||
|
||||
withoutMetadata := make(map[span.URI]struct{})
|
||||
for _, id := range v.snapshot.getIDs(uri) {
|
||||
v.snapshot.reverseDependencies(id, withoutMetadata, map[packageID]struct{}{})
|
||||
}
|
||||
v.snapshot = v.snapshot.clone(uri, nil, withoutMetadata)
|
||||
}
|
||||
|
||||
// reverseDependencies populates the uris map with file URIs belonging to the
|
||||
// provided package and its transitive reverse dependencies.
|
||||
func (s *snapshot) reverseDependencies(id packageID, uris map[span.URI]struct{}, seen map[packageID]struct{}) {
|
||||
if _, ok := seen[id]; ok {
|
||||
return
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
m, ok := v.snapshot.metadata[id]
|
||||
if !ok {
|
||||
m := s.getMetadata(id)
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
if id != topID {
|
||||
for _, uri := range m.files {
|
||||
results[uri] = struct{}{}
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
importedBy := s.getImportedBy(id)
|
||||
for _, parentID := range importedBy {
|
||||
s.reverseDependencies(parentID, uris, seen)
|
||||
}
|
||||
for parentID := range m.parents {
|
||||
v.rdeps(topID, seen, results, parentID)
|
||||
for _, uri := range m.files {
|
||||
uris[uri] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *snapshot) clearAndRebuildImportGraph() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
// Completely invalidate the original map.
|
||||
s.importedBy = make(map[packageID][]packageID)
|
||||
s.rebuildImportGraph()
|
||||
}
|
||||
|
||||
func (s *snapshot) rebuildImportGraph() {
|
||||
for id, m := range s.metadata {
|
||||
for _, importID := range m.deps {
|
||||
s.importedBy[importID] = append(s.importedBy[importID], id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
68
internal/lsp/cache/view.go
vendored
68
internal/lsp/cache/view.go
vendored
@ -288,6 +288,17 @@ func (v *view) BuiltinPackage() source.BuiltinPackage {
|
||||
return v.builtin
|
||||
}
|
||||
|
||||
func (v *view) Snapshot() source.Snapshot {
|
||||
return v.getSnapshot()
|
||||
}
|
||||
|
||||
func (v *view) getSnapshot() *snapshot {
|
||||
v.snapshotMu.Lock()
|
||||
defer v.snapshotMu.Unlock()
|
||||
|
||||
return v.snapshot
|
||||
}
|
||||
|
||||
// SetContent sets the overlay contents for a file.
|
||||
func (v *view) SetContent(ctx context.Context, uri span.URI, content []byte) (bool, error) {
|
||||
v.mu.Lock()
|
||||
@ -298,11 +309,12 @@ func (v *view) SetContent(ctx context.Context, uri span.URI, content []byte) (bo
|
||||
v.cancel()
|
||||
v.backgroundCtx, v.cancel = context.WithCancel(v.baseCtx)
|
||||
|
||||
if !v.Ignore(uri) {
|
||||
kind := source.DetectLanguage("", uri.Filename())
|
||||
return v.session.SetOverlay(uri, kind, content), nil
|
||||
if v.Ignore(uri) {
|
||||
return false, nil
|
||||
}
|
||||
return false, nil
|
||||
|
||||
kind := source.DetectLanguage("", uri.Filename())
|
||||
return v.session.SetOverlay(uri, kind, content), nil
|
||||
}
|
||||
|
||||
// FindFile returns the file if the given URI is already a part of the view.
|
||||
@ -329,46 +341,20 @@ func (v *view) GetFile(ctx context.Context, uri span.URI) (source.File, error) {
|
||||
|
||||
// getFile is the unlocked internal implementation of GetFile.
|
||||
func (v *view) getFile(ctx context.Context, uri span.URI, kind source.FileKind) (viewFile, error) {
|
||||
if f, err := v.findFile(uri); err != nil {
|
||||
f, err := v.findFile(uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if f != nil {
|
||||
return f, nil
|
||||
}
|
||||
var f viewFile
|
||||
switch kind {
|
||||
case source.Mod:
|
||||
f = &modFile{
|
||||
fileBase: fileBase{
|
||||
view: v,
|
||||
fname: uri.Filename(),
|
||||
kind: source.Mod,
|
||||
},
|
||||
}
|
||||
case source.Sum:
|
||||
f = &sumFile{
|
||||
fileBase: fileBase{
|
||||
view: v,
|
||||
fname: uri.Filename(),
|
||||
kind: source.Sum,
|
||||
},
|
||||
}
|
||||
default:
|
||||
// Assume that all other files are Go files, regardless of extension.
|
||||
f = &goFile{
|
||||
fileBase: fileBase{
|
||||
view: v,
|
||||
fname: uri.Filename(),
|
||||
kind: source.Go,
|
||||
},
|
||||
}
|
||||
v.session.filesWatchMap.Watch(uri, func() {
|
||||
gof, ok := f.(*goFile)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
v.invalidateContent(ctx, gof)
|
||||
})
|
||||
f = &fileBase{
|
||||
view: v,
|
||||
fname: uri.Filename(),
|
||||
kind: source.Go,
|
||||
}
|
||||
v.session.filesWatchMap.Watch(uri, func() {
|
||||
v.invalidateContent(ctx, uri, kind)
|
||||
})
|
||||
v.mapFile(uri, f)
|
||||
return f, nil
|
||||
}
|
||||
@ -425,11 +411,11 @@ func (v *view) mapFile(uri span.URI, f viewFile) {
|
||||
}
|
||||
}
|
||||
|
||||
func (v *view) openFiles(ctx context.Context, uris map[span.URI]struct{}) (results []source.File) {
|
||||
func (v *view) openFiles(ctx context.Context, uris []span.URI) (results []source.File) {
|
||||
v.mu.Lock()
|
||||
defer v.mu.Unlock()
|
||||
|
||||
for uri := range uris {
|
||||
for _, uri := range uris {
|
||||
// Call unlocked version of getFile since we hold the lock on the view.
|
||||
if f, err := v.getFile(ctx, uri, source.Go); err == nil && v.session.IsOpen(uri) {
|
||||
results = append(results, f)
|
||||
|
@ -27,8 +27,10 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fh := view.Snapshot().Handle(ctx, f)
|
||||
|
||||
// Determine the supported actions for this file kind.
|
||||
fileKind := f.Handle(ctx).Identity().Kind
|
||||
fileKind := fh.Identity().Kind
|
||||
supportedCodeActions, ok := view.Options().SupportedCodeActions[fileKind]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no supported code actions for %v file kind", fileKind)
|
||||
@ -67,17 +69,13 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara
|
||||
},
|
||||
})
|
||||
case source.Go:
|
||||
gof, ok := f.(source.GoFile)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("%s is not a Go file", f.URI())
|
||||
}
|
||||
edits, editsPerFix, err := source.AllImportsFixes(ctx, view, gof)
|
||||
edits, editsPerFix, err := source.AllImportsFixes(ctx, view, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if diagnostics := params.Context.Diagnostics; wanted[protocol.QuickFix] && len(diagnostics) > 0 {
|
||||
// First, add the quick fixes reported by go/analysis.
|
||||
qf, err := quickFixes(ctx, view, gof, diagnostics)
|
||||
qf, err := quickFixes(ctx, view, f, diagnostics)
|
||||
if err != nil {
|
||||
log.Error(ctx, "quick fixes failed", err, telemetry.File.Of(uri))
|
||||
}
|
||||
@ -207,9 +205,9 @@ func importDiagnostics(fix *imports.ImportFix, diagnostics []protocol.Diagnostic
|
||||
return results
|
||||
}
|
||||
|
||||
func quickFixes(ctx context.Context, view source.View, gof source.GoFile, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) {
|
||||
func quickFixes(ctx context.Context, view source.View, f source.File, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) {
|
||||
var codeActions []protocol.CodeAction
|
||||
cphs, err := gof.CheckPackageHandles(ctx)
|
||||
_, cphs, err := view.CheckPackageHandles(ctx, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -22,7 +22,8 @@ func (s *Server) executeCommand(ctx context.Context, params *protocol.ExecuteCom
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := f.(source.ModFile); !ok {
|
||||
fh := view.Snapshot().Handle(ctx, f)
|
||||
if fh.Identity().Kind != source.Mod {
|
||||
return nil, errors.Errorf("%s is not a mod file", uri)
|
||||
}
|
||||
// Run go.mod tidy on the view.
|
||||
|
@ -20,7 +20,7 @@ func (s *Server) completion(ctx context.Context, params *protocol.CompletionPara
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.session.ViewOf(uri)
|
||||
options := view.Options()
|
||||
f, err := getGoFile(ctx, view, uri)
|
||||
f, err := view.GetFile(ctx, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
func (s *Server) definition(ctx context.Context, params *protocol.DefinitionParams) ([]protocol.Location, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.session.ViewOf(uri)
|
||||
f, err := getGoFile(ctx, view, uri)
|
||||
f, err := view.GetFile(ctx, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -38,7 +38,7 @@ func (s *Server) definition(ctx context.Context, params *protocol.DefinitionPara
|
||||
func (s *Server) typeDefinition(ctx context.Context, params *protocol.TypeDefinitionParams) ([]protocol.Location, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.session.ViewOf(uri)
|
||||
f, err := getGoFile(ctx, view, uri)
|
||||
f, err := view.GetFile(ctx, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
func (s *Server) diagnostics(view source.View, uri span.URI) error {
|
||||
@ -28,12 +27,7 @@ func (s *Server) diagnostics(view source.View, uri span.URI) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// For non-Go files, don't return any diagnostics.
|
||||
gof, ok := f.(source.GoFile)
|
||||
if !ok {
|
||||
return errors.Errorf("%s is not a Go file", f.URI())
|
||||
}
|
||||
reports, warningMsg, err := source.Diagnostics(ctx, view, gof, view.Options().DisabledAnalyses)
|
||||
reports, warningMsg, err := source.Diagnostics(ctx, view, f, view.Options().DisabledAnalyses)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
func (s *Server) foldingRange(ctx context.Context, params *protocol.FoldingRangeParams) ([]protocol.FoldingRange, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.session.ViewOf(uri)
|
||||
f, err := getGoFile(ctx, view, uri)
|
||||
f, err := view.GetFile(ctx, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
func (s *Server) hover(ctx context.Context, params *protocol.HoverParams) (*protocol.Hover, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.session.ViewOf(uri)
|
||||
f, err := getGoFile(ctx, view, uri)
|
||||
f, err := view.GetFile(ctx, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -23,11 +23,11 @@ import (
|
||||
func (s *Server) documentLink(ctx context.Context, params *protocol.DocumentLinkParams) ([]protocol.DocumentLink, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.session.ViewOf(uri)
|
||||
f, err := getGoFile(ctx, view, uri)
|
||||
f, err := view.GetFile(ctx, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fh := f.Handle(ctx)
|
||||
fh := view.Snapshot().Handle(ctx, f)
|
||||
file, m, _, err := view.Session().Cache().ParseGoHandle(fh, source.ParseFull).Parse(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -76,11 +76,7 @@ func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []source.Diagnosti
|
||||
if err != nil {
|
||||
t.Fatalf("no file for %s: %v", f, err)
|
||||
}
|
||||
gof, ok := f.(source.GoFile)
|
||||
if !ok {
|
||||
t.Fatalf("%s is not a Go file: %v", uri, err)
|
||||
}
|
||||
results, _, err := source.Diagnostics(r.ctx, v, gof, nil)
|
||||
results, _, err := source.Diagnostics(r.ctx, v, f, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -323,7 +319,7 @@ func (r *runner) SuggestedFix(t *testing.T, spn span.Span) {
|
||||
uri := spn.URI()
|
||||
filename := uri.Filename()
|
||||
view := r.server.session.ViewOf(uri)
|
||||
f, err := getGoFile(r.ctx, view, uri)
|
||||
f, err := view.GetFile(r.ctx, uri)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import (
|
||||
func (s *Server) references(ctx context.Context, params *protocol.ReferenceParams) ([]protocol.Location, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.session.ViewOf(uri)
|
||||
f, err := getGoFile(ctx, view, uri)
|
||||
f, err := view.GetFile(ctx, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
func (s *Server) rename(ctx context.Context, params *protocol.RenameParams) (*protocol.WorkspaceEdit, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.session.ViewOf(uri)
|
||||
f, err := getGoFile(ctx, view, uri)
|
||||
f, err := view.GetFile(ctx, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -38,7 +38,7 @@ func (s *Server) rename(ctx context.Context, params *protocol.RenameParams) (*pr
|
||||
func (s *Server) prepareRename(ctx context.Context, params *protocol.PrepareRenameParams) (*protocol.Range, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.session.ViewOf(uri)
|
||||
f, err := getGoFile(ctx, view, uri)
|
||||
f, err := view.GetFile(ctx, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package lsp implements LSP for gopls.
|
||||
package lsp
|
||||
|
||||
import (
|
||||
|
@ -17,7 +17,7 @@ import (
|
||||
func (s *Server) signatureHelp(ctx context.Context, params *protocol.SignatureHelpParams) (*protocol.SignatureHelp, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.session.ViewOf(uri)
|
||||
f, err := getGoFile(ctx, view, uri)
|
||||
f, err := view.GetFile(ctx, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -126,7 +126,8 @@ func (ipm insensitivePrefixMatcher) Score(candidateLabel string) float32 {
|
||||
|
||||
// completer contains the necessary information for a single completion request.
|
||||
type completer struct {
|
||||
pkg Package
|
||||
snapshot Snapshot
|
||||
pkg Package
|
||||
|
||||
qf types.Qualifier
|
||||
opts CompletionOptions
|
||||
@ -376,13 +377,13 @@ func (e ErrIsDefinition) Error() string {
|
||||
// The selection is computed based on the preceding identifier and can be used by
|
||||
// the client to score the quality of the completion. For instance, some clients
|
||||
// may tolerate imperfect matches as valid completion results, since users may make typos.
|
||||
func Completion(ctx context.Context, view View, f GoFile, pos protocol.Position, opts CompletionOptions) ([]CompletionItem, *Selection, error) {
|
||||
func Completion(ctx context.Context, view View, f File, pos protocol.Position, opts CompletionOptions) ([]CompletionItem, *Selection, error) {
|
||||
ctx, done := trace.StartSpan(ctx, "source.Completion")
|
||||
defer done()
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
cphs, err := f.CheckPackageHandles(ctx)
|
||||
snapshot, cphs, err := view.CheckPackageHandles(ctx, f)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -427,6 +428,7 @@ func Completion(ctx context.Context, view View, f GoFile, pos protocol.Position,
|
||||
clInfo := enclosingCompositeLiteral(path, rng.Start, pkg.GetTypesInfo())
|
||||
c := &completer{
|
||||
pkg: pkg,
|
||||
snapshot: snapshot,
|
||||
qf: qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo()),
|
||||
view: view,
|
||||
ctx: ctx,
|
||||
|
@ -133,7 +133,7 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
|
||||
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, pkg, file, obj.Pos())
|
||||
ident, err := findIdentifier(c.ctx, c.view, c.snapshot, pkg, file, obj.Pos())
|
||||
if err != nil {
|
||||
return CompletionItem{}, err
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
type Diagnostic struct {
|
||||
@ -38,11 +37,11 @@ const (
|
||||
SeverityError
|
||||
)
|
||||
|
||||
func Diagnostics(ctx context.Context, view View, f GoFile, disabledAnalyses map[string]struct{}) (map[span.URI][]Diagnostic, string, error) {
|
||||
func Diagnostics(ctx context.Context, view View, f File, disabledAnalyses map[string]struct{}) (map[span.URI][]Diagnostic, string, error) {
|
||||
ctx, done := trace.StartSpan(ctx, "source.Diagnostics", telemetry.File.Of(f.URI()))
|
||||
defer done()
|
||||
|
||||
cphs, err := f.CheckPackageHandles(ctx)
|
||||
_, cphs, err := view.CheckPackageHandles(ctx, f)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@ -85,7 +84,7 @@ func Diagnostics(ctx context.Context, view View, f GoFile, disabledAnalyses map[
|
||||
}
|
||||
}
|
||||
// Updates to the diagnostics for this package may need to be propagated.
|
||||
revDeps := view.GetActiveReverseDeps(ctx, f.URI())
|
||||
revDeps := view.GetActiveReverseDeps(ctx, f)
|
||||
for _, cph := range revDeps {
|
||||
pkg, err := cph.Check(ctx)
|
||||
if err != nil {
|
||||
@ -215,13 +214,9 @@ func toDiagnostic(ctx context.Context, view View, diag analysis.Diagnostic, cate
|
||||
if err != nil {
|
||||
return Diagnostic{}, err
|
||||
}
|
||||
gof, ok := f.(GoFile)
|
||||
if !ok {
|
||||
return Diagnostic{}, errors.Errorf("%s is not a Go file", f.URI())
|
||||
}
|
||||
// If the package has changed since these diagnostics were computed,
|
||||
// this may be incorrect. Should the package be associated with the diagnostic?
|
||||
cphs, err := gof.CheckPackageHandles(ctx)
|
||||
_, cphs, err := view.CheckPackageHandles(ctx, f)
|
||||
if err != nil {
|
||||
return Diagnostic{}, err
|
||||
}
|
||||
|
@ -16,10 +16,12 @@ type FoldingRangeInfo struct {
|
||||
}
|
||||
|
||||
// FoldingRange gets all of the folding range for f.
|
||||
func FoldingRange(ctx context.Context, view View, f GoFile, lineFoldingOnly bool) (ranges []*FoldingRangeInfo, err error) {
|
||||
func FoldingRange(ctx context.Context, view View, f File, lineFoldingOnly bool) (ranges []*FoldingRangeInfo, err error) {
|
||||
// TODO(suzmue): consider limiting the number of folding ranges returned, and
|
||||
// implement a way to prioritize folding ranges in that case.
|
||||
ph := view.Session().Cache().ParseGoHandle(f.Handle(ctx), ParseFull)
|
||||
s := view.Snapshot()
|
||||
fh := s.Handle(ctx, f)
|
||||
ph := view.Session().Cache().ParseGoHandle(fh, ParseFull)
|
||||
file, m, _, err := ph.Parse(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -24,11 +24,7 @@ func Format(ctx context.Context, view View, f File) ([]protocol.TextEdit, error)
|
||||
ctx, done := trace.StartSpan(ctx, "source.Format")
|
||||
defer done()
|
||||
|
||||
gof, ok := f.(GoFile)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("formatting is not supported for non-Go files")
|
||||
}
|
||||
cphs, err := gof.CheckPackageHandles(ctx)
|
||||
snapshot, cphs, err := view.CheckPackageHandles(ctx, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -55,7 +51,7 @@ func Format(ctx context.Context, view View, f File) ([]protocol.TextEdit, error)
|
||||
// have any parse errors and can still be formatted. Using format.Node
|
||||
// on an ast with errors may result in code being added or removed.
|
||||
// Attempt to format the source of this file instead.
|
||||
formatted, err := formatSource(ctx, f)
|
||||
formatted, err := formatSource(ctx, snapshot, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -75,10 +71,11 @@ func Format(ctx context.Context, view View, f File) ([]protocol.TextEdit, error)
|
||||
return computeTextEdits(ctx, ph.File(), m, buf.String())
|
||||
}
|
||||
|
||||
func formatSource(ctx context.Context, file File) ([]byte, error) {
|
||||
func formatSource(ctx context.Context, s Snapshot, f File) ([]byte, error) {
|
||||
ctx, done := trace.StartSpan(ctx, "source.formatSource")
|
||||
defer done()
|
||||
data, _, err := file.Handle(ctx).Read(ctx)
|
||||
|
||||
data, _, err := s.Handle(ctx, f).Read(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -86,11 +83,11 @@ func formatSource(ctx context.Context, file File) ([]byte, error) {
|
||||
}
|
||||
|
||||
// Imports formats a file using the goimports tool.
|
||||
func Imports(ctx context.Context, view View, f GoFile, rng span.Range) ([]protocol.TextEdit, error) {
|
||||
func Imports(ctx context.Context, view View, f File, rng span.Range) ([]protocol.TextEdit, error) {
|
||||
ctx, done := trace.StartSpan(ctx, "source.Imports")
|
||||
defer done()
|
||||
|
||||
cphs, err := f.CheckPackageHandles(ctx)
|
||||
_, cphs, err := view.CheckPackageHandles(ctx, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -149,11 +146,11 @@ type ImportFix struct {
|
||||
// In addition to returning the result of applying all edits,
|
||||
// it returns a list of fixes that could be applied to the file, with the
|
||||
// corresponding TextEdits that would be needed to apply that fix.
|
||||
func AllImportsFixes(ctx context.Context, view View, f GoFile) (edits []protocol.TextEdit, editsPerFix []*ImportFix, err error) {
|
||||
func AllImportsFixes(ctx context.Context, view View, f File) (edits []protocol.TextEdit, editsPerFix []*ImportFix, err error) {
|
||||
ctx, done := trace.StartSpan(ctx, "source.AllImportsFixes")
|
||||
defer done()
|
||||
|
||||
cphs, err := f.CheckPackageHandles(ctx)
|
||||
_, cphs, err := view.CheckPackageHandles(ctx, f)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -23,7 +23,8 @@ func Highlight(ctx context.Context, view View, uri span.URI, pos protocol.Positi
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ph := view.Session().Cache().ParseGoHandle(f.Handle(ctx), ParseFull)
|
||||
fh := view.Snapshot().Handle(ctx, f)
|
||||
ph := view.Session().Cache().ParseGoHandle(fh, ParseFull)
|
||||
file, m, _, err := ph.Parse(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -20,9 +20,10 @@ import (
|
||||
|
||||
// IdentifierInfo holds information about an identifier in Go source.
|
||||
type IdentifierInfo struct {
|
||||
Name string
|
||||
View View
|
||||
File ParseGoHandle
|
||||
Name string
|
||||
View View
|
||||
snapshot Snapshot
|
||||
File ParseGoHandle
|
||||
mappedRange
|
||||
|
||||
Type struct {
|
||||
@ -47,8 +48,8 @@ type Declaration struct {
|
||||
|
||||
// Identifier returns identifier information for a position
|
||||
// in a file, accounting for a potentially incomplete selector.
|
||||
func Identifier(ctx context.Context, view View, f GoFile, pos protocol.Position) (*IdentifierInfo, error) {
|
||||
cphs, err := f.CheckPackageHandles(ctx)
|
||||
func Identifier(ctx context.Context, view View, f File, pos protocol.Position) (*IdentifierInfo, error) {
|
||||
snapshot, cphs, err := view.CheckPackageHandles(ctx, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -73,17 +74,17 @@ func Identifier(ctx context.Context, view View, f GoFile, pos protocol.Position)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return findIdentifier(ctx, view, pkg, file, rng.Start)
|
||||
return findIdentifier(ctx, view, snapshot, pkg, file, rng.Start)
|
||||
}
|
||||
|
||||
func findIdentifier(ctx context.Context, view View, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) {
|
||||
if result, err := identifier(ctx, view, pkg, file, pos); err != nil || result != nil {
|
||||
func findIdentifier(ctx context.Context, view View, snapshot Snapshot, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) {
|
||||
if result, err := identifier(ctx, view, snapshot, pkg, file, pos); err != nil || result != nil {
|
||||
return result, err
|
||||
}
|
||||
// If the position is not an identifier but immediately follows
|
||||
// an identifier or selector period (as is common when
|
||||
// requesting a completion), use the path to the preceding node.
|
||||
ident, err := identifier(ctx, view, pkg, file, pos-1)
|
||||
ident, err := identifier(ctx, view, snapshot, pkg, file, pos-1)
|
||||
if ident == nil && err == nil {
|
||||
err = errors.New("no identifier found")
|
||||
}
|
||||
@ -91,14 +92,14 @@ func findIdentifier(ctx context.Context, view View, pkg Package, file *ast.File,
|
||||
}
|
||||
|
||||
// identifier checks a single position for a potential identifier.
|
||||
func identifier(ctx context.Context, view View, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) {
|
||||
func identifier(ctx context.Context, view View, snapshot Snapshot, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) {
|
||||
ctx, done := trace.StartSpan(ctx, "source.identifier")
|
||||
defer done()
|
||||
|
||||
var err error
|
||||
|
||||
// Handle import specs separately, as there is no formal position for a package declaration.
|
||||
if result, err := importSpec(ctx, view, file, pkg, pos); result != nil || err != nil {
|
||||
if result, err := importSpec(ctx, view, snapshot, file, pkg, pos); result != nil || err != nil {
|
||||
return result, err
|
||||
}
|
||||
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
|
||||
@ -113,11 +114,12 @@ func identifier(ctx context.Context, view View, pkg Package, file *ast.File, pos
|
||||
}
|
||||
}
|
||||
result := &IdentifierInfo{
|
||||
View: view,
|
||||
File: ph,
|
||||
qf: qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo()),
|
||||
pkg: pkg,
|
||||
ident: searchForIdent(path[0]),
|
||||
View: view,
|
||||
snapshot: snapshot,
|
||||
File: ph,
|
||||
qf: qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo()),
|
||||
pkg: pkg,
|
||||
ident: searchForIdent(path[0]),
|
||||
}
|
||||
// No identifier at the given position.
|
||||
if result.ident == nil {
|
||||
@ -273,7 +275,7 @@ func objToNode(ctx context.Context, view View, pkg Package, obj types.Object) (a
|
||||
}
|
||||
|
||||
// importSpec handles positions inside of an *ast.ImportSpec.
|
||||
func importSpec(ctx context.Context, view View, file *ast.File, pkg Package, pos token.Pos) (*IdentifierInfo, error) {
|
||||
func importSpec(ctx context.Context, view View, snapshot Snapshot, file *ast.File, pkg Package, pos token.Pos) (*IdentifierInfo, error) {
|
||||
var imp *ast.ImportSpec
|
||||
for _, spec := range file.Imports {
|
||||
if spec.Path.Pos() <= pos && pos < spec.Path.End() {
|
||||
@ -295,10 +297,11 @@ func importSpec(ctx context.Context, view View, file *ast.File, pkg Package, pos
|
||||
}
|
||||
}
|
||||
result := &IdentifierInfo{
|
||||
View: view,
|
||||
File: ph,
|
||||
Name: importPath,
|
||||
pkg: pkg,
|
||||
View: view,
|
||||
snapshot: snapshot,
|
||||
File: ph,
|
||||
Name: importPath,
|
||||
pkg: pkg,
|
||||
}
|
||||
if result.mappedRange, err = posToMappedRange(ctx, view, pkg, imp.Path.Pos(), imp.Path.End()); err != nil {
|
||||
return nil, err
|
||||
|
@ -41,7 +41,7 @@ type PrepareItem struct {
|
||||
Text string
|
||||
}
|
||||
|
||||
func PrepareRename(ctx context.Context, view View, f GoFile, pos protocol.Position) (*PrepareItem, error) {
|
||||
func PrepareRename(ctx context.Context, view View, f File, pos protocol.Position) (*PrepareItem, error) {
|
||||
ctx, done := trace.StartSpan(ctx, "source.PrepareRename")
|
||||
defer done()
|
||||
|
||||
@ -151,7 +151,7 @@ func (i *IdentifierInfo) Rename(ctx context.Context, view View, newName string)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fh := f.Handle(ctx)
|
||||
fh := i.snapshot.Handle(ctx, f)
|
||||
data, _, err := fh.Read(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -227,6 +227,7 @@ func getPkgNameIdentifier(ctx context.Context, ident *IdentifierInfo, pkgName *t
|
||||
return &IdentifierInfo{
|
||||
Name: pkgName.Name(),
|
||||
View: ident.View,
|
||||
snapshot: ident.snapshot,
|
||||
mappedRange: decl.mappedRange,
|
||||
File: ident.File,
|
||||
Declaration: decl,
|
||||
|
@ -27,11 +27,11 @@ type ParameterInformation struct {
|
||||
Label string
|
||||
}
|
||||
|
||||
func SignatureHelp(ctx context.Context, view View, f GoFile, pos protocol.Position) (*SignatureInformation, error) {
|
||||
func SignatureHelp(ctx context.Context, view View, f File, pos protocol.Position) (*SignatureInformation, error) {
|
||||
ctx, done := trace.StartSpan(ctx, "source.SignatureHelp")
|
||||
defer done()
|
||||
|
||||
cphs, err := f.CheckPackageHandles(ctx)
|
||||
_, cphs, err := view.CheckPackageHandles(ctx, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -94,7 +94,7 @@ FindCall:
|
||||
|
||||
// Handle builtin functions separately.
|
||||
if obj, ok := obj.(*types.Builtin); ok {
|
||||
return builtinSignature(ctx, f.View(), callExpr, obj.Name(), rng.Start)
|
||||
return builtinSignature(ctx, view, callExpr, obj.Name(), rng.Start)
|
||||
}
|
||||
|
||||
// Get the type information for the function being called.
|
||||
@ -118,7 +118,7 @@ FindCall:
|
||||
comment *ast.CommentGroup
|
||||
)
|
||||
if obj != nil {
|
||||
node, err := objToNode(ctx, f.View(), pkg, obj)
|
||||
node, err := objToNode(ctx, view, pkg, obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []source.Diagnosti
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
results, _, err := source.Diagnostics(r.ctx, r.view, f.(source.GoFile), nil)
|
||||
results, _, err := source.Diagnostics(r.ctx, r.view, f, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -243,7 +243,7 @@ func (r *runner) callCompletion(t *testing.T, src span.Span, options source.Comp
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
list, surrounding, err := source.Completion(r.ctx, r.view, f.(source.GoFile), protocol.Position{
|
||||
list, surrounding, err := source.Completion(r.ctx, r.view, f, protocol.Position{
|
||||
Line: float64(src.Start().Line() - 1),
|
||||
Character: float64(src.Start().Column() - 1),
|
||||
}, options)
|
||||
@ -284,14 +284,15 @@ func (r *runner) FoldingRange(t *testing.T, spn span.Span) {
|
||||
if err != nil {
|
||||
t.Fatalf("failed for %v: %v", spn, err)
|
||||
}
|
||||
data, _, err := f.Handle(r.ctx).Read(r.ctx)
|
||||
fh := r.view.Snapshot().Handle(r.ctx, f)
|
||||
data, _, err := fh.Read(r.ctx)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Test all folding ranges.
|
||||
ranges, err := source.FoldingRange(r.ctx, r.view, f.(source.GoFile), false)
|
||||
ranges, err := source.FoldingRange(r.ctx, r.view, f, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
@ -299,7 +300,7 @@ func (r *runner) FoldingRange(t *testing.T, spn span.Span) {
|
||||
r.foldingRanges(t, "foldingRange", uri, string(data), ranges)
|
||||
|
||||
// Test folding ranges with lineFoldingOnly
|
||||
ranges, err = source.FoldingRange(r.ctx, r.view, f.(source.GoFile), true)
|
||||
ranges, err = source.FoldingRange(r.ctx, r.view, f, true)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
@ -434,7 +435,8 @@ func (r *runner) Format(t *testing.T, spn span.Span) {
|
||||
}
|
||||
return
|
||||
}
|
||||
data, _, err := f.Handle(ctx).Read(ctx)
|
||||
fh := r.view.Snapshot().Handle(r.ctx, f)
|
||||
data, _, err := fh.Read(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -465,7 +467,7 @@ func (r *runner) Import(t *testing.T, spn span.Span) {
|
||||
if err != nil {
|
||||
t.Fatalf("failed for %v: %v", spn, err)
|
||||
}
|
||||
fh := f.Handle(ctx)
|
||||
fh := r.view.Snapshot().Handle(r.ctx, f)
|
||||
tok, err := r.view.Session().Cache().TokenHandle(fh).Token(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -474,7 +476,7 @@ func (r *runner) Import(t *testing.T, spn span.Span) {
|
||||
if err != nil {
|
||||
t.Fatalf("failed for %v: %v", spn, err)
|
||||
}
|
||||
edits, err := source.Imports(ctx, r.view, f.(source.GoFile), rng)
|
||||
edits, err := source.Imports(ctx, r.view, f, rng)
|
||||
if err != nil {
|
||||
if goimported != "" {
|
||||
t.Error(err)
|
||||
@ -512,7 +514,7 @@ func (r *runner) Definition(t *testing.T, spn span.Span, d tests.Definition) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ident, err := source.Identifier(ctx, r.view, f.(source.GoFile), srcRng.Start)
|
||||
ident, err := source.Identifier(ctx, r.view, f, srcRng.Start)
|
||||
if err != nil {
|
||||
t.Fatalf("failed for %v: %v", d.Src, err)
|
||||
}
|
||||
@ -590,7 +592,7 @@ func (r *runner) Reference(t *testing.T, src span.Span, itemList []span.Span) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ident, err := source.Identifier(ctx, r.view, f.(source.GoFile), srcRng.Start)
|
||||
ident, err := source.Identifier(ctx, r.view, f, srcRng.Start)
|
||||
if err != nil {
|
||||
t.Fatalf("failed for %v: %v", src, err)
|
||||
}
|
||||
@ -637,7 +639,7 @@ func (r *runner) Rename(t *testing.T, spn span.Span, newText string) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ident, err := source.Identifier(r.ctx, r.view, f.(source.GoFile), srcRng.Start)
|
||||
ident, err := source.Identifier(r.ctx, r.view, f, srcRng.Start)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
@ -659,7 +661,7 @@ func (r *runner) Rename(t *testing.T, spn span.Span, newText string) {
|
||||
if err != nil {
|
||||
t.Fatalf("failed for %v: %v", spn, err)
|
||||
}
|
||||
fh := f.Handle(ctx)
|
||||
fh := r.view.Snapshot().Handle(r.ctx, f)
|
||||
data, _, err := fh.Read(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -726,7 +728,7 @@ func (r *runner) PrepareRename(t *testing.T, src span.Span, want *source.Prepare
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Find the identifier at the position.
|
||||
item, err := source.PrepareRename(ctx, r.view, f.(source.GoFile), srcRng.Start)
|
||||
item, err := source.PrepareRename(ctx, r.view, f, srcRng.Start)
|
||||
if err != nil {
|
||||
if want.Text != "" { // expected an ident.
|
||||
t.Errorf("prepare rename failed for %v: got error: %v", src, err)
|
||||
@ -754,7 +756,7 @@ func (r *runner) Symbol(t *testing.T, uri span.URI, expectedSymbols []protocol.D
|
||||
if err != nil {
|
||||
t.Fatalf("failed for %v: %v", uri, err)
|
||||
}
|
||||
symbols, err := source.DocumentSymbols(ctx, r.view, f.(source.GoFile))
|
||||
symbols, err := source.DocumentSymbols(ctx, r.view, f)
|
||||
if err != nil {
|
||||
t.Errorf("symbols failed for %s: %v", uri, err)
|
||||
}
|
||||
@ -820,7 +822,7 @@ func (r *runner) SignatureHelp(t *testing.T, spn span.Span, expectedSignature *s
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gotSignature, err := source.SignatureHelp(ctx, r.view, f.(source.GoFile), rng.Start)
|
||||
gotSignature, err := source.SignatureHelp(ctx, r.view, f, rng.Start)
|
||||
if err != nil {
|
||||
// Only fail if we got an error we did not expect.
|
||||
if expectedSignature != nil {
|
||||
|
@ -14,11 +14,11 @@ import (
|
||||
"golang.org/x/tools/internal/telemetry/trace"
|
||||
)
|
||||
|
||||
func DocumentSymbols(ctx context.Context, view View, f GoFile) ([]protocol.DocumentSymbol, error) {
|
||||
func DocumentSymbols(ctx context.Context, view View, f File) ([]protocol.DocumentSymbol, error) {
|
||||
ctx, done := trace.StartSpan(ctx, "source.DocumentSymbols")
|
||||
defer done()
|
||||
|
||||
cphs, err := f.CheckPackageHandles(ctx)
|
||||
_, cphs, err := view.CheckPackageHandles(ctx, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ func IsGenerated(ctx context.Context, view View, uri span.URI) bool {
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
ph := view.Session().Cache().ParseGoHandle(f.Handle(ctx), ParseHeader)
|
||||
ph := view.Session().Cache().ParseGoHandle(view.Snapshot().Handle(ctx, f), ParseHeader)
|
||||
parsed, _, _, err := ph.Parse(ctx)
|
||||
if err != nil {
|
||||
return false
|
||||
|
@ -117,6 +117,10 @@ type CheckPackageHandle interface {
|
||||
// Config is the *packages.Config that the package metadata was loaded with.
|
||||
Config() *packages.Config
|
||||
|
||||
// Mode returns the ParseMode for all of the files in the CheckPackageHandle.
|
||||
// The files should always have the same parse mode.
|
||||
Mode() ParseMode
|
||||
|
||||
// Check returns the type-checked Package for the CheckPackageHandle.
|
||||
Check(ctx context.Context) (Package, error)
|
||||
|
||||
@ -240,6 +244,7 @@ type View interface {
|
||||
// Ignore returns true if this file should be ignored by this view.
|
||||
Ignore(span.URI) bool
|
||||
|
||||
// Config returns the configuration for the view.
|
||||
Config(ctx context.Context) *packages.Config
|
||||
|
||||
// RunProcessEnvFunc runs fn with the process env for this view inserted into opts.
|
||||
@ -257,33 +262,28 @@ type View interface {
|
||||
// Analyzers returns the set of Analyzers active for this view.
|
||||
Analyzers() []*analysis.Analyzer
|
||||
|
||||
// CheckPackageHandles returns the CheckPackageHandles for the packages
|
||||
// that this file belongs to.
|
||||
CheckPackageHandles(ctx context.Context, f File) (Snapshot, []CheckPackageHandle, error)
|
||||
|
||||
// GetActiveReverseDeps returns the active files belonging to the reverse
|
||||
// dependencies of this file's package.
|
||||
GetActiveReverseDeps(ctx context.Context, uri span.URI) []CheckPackageHandle
|
||||
GetActiveReverseDeps(ctx context.Context, f File) []CheckPackageHandle
|
||||
|
||||
// Snapshot returns the current snapshot for the view.
|
||||
Snapshot() Snapshot
|
||||
}
|
||||
|
||||
// Snapshot represents the current state for the given view.
|
||||
type Snapshot interface {
|
||||
// Handle returns the FileHandle for the given file.
|
||||
Handle(ctx context.Context, f File) FileHandle
|
||||
}
|
||||
|
||||
// File represents a source file of any type.
|
||||
type File interface {
|
||||
URI() span.URI
|
||||
View() View
|
||||
Handle(ctx context.Context) FileHandle
|
||||
}
|
||||
|
||||
// GoFile represents a Go source file that has been type-checked.
|
||||
type GoFile interface {
|
||||
File
|
||||
|
||||
// GetCheckPackageHandles returns the CheckPackageHandles for the packages
|
||||
// that this file belongs to.
|
||||
CheckPackageHandles(ctx context.Context) ([]CheckPackageHandle, error)
|
||||
}
|
||||
|
||||
type ModFile interface {
|
||||
File
|
||||
}
|
||||
|
||||
type SumFile interface {
|
||||
File
|
||||
Kind() FileKind
|
||||
}
|
||||
|
||||
// Package represents a Go package that has been type-checked. It maintains
|
||||
|
@ -19,7 +19,7 @@ func (s *Server) documentSymbol(ctx context.Context, params *protocol.DocumentSy
|
||||
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.session.ViewOf(uri)
|
||||
f, err := getGoFile(ctx, view, uri)
|
||||
f, err := view.GetFile(ctx, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ func (s *Server) didSave(ctx context.Context, params *protocol.DidSaveTextDocume
|
||||
|
||||
func (s *Server) didClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
ctx = telemetry.File.With(ctx, uri)
|
||||
ctx = telemetry.URI.With(ctx, uri)
|
||||
s.session.DidClose(uri)
|
||||
view := s.session.ViewOf(uri)
|
||||
if _, err := view.SetContent(ctx, uri, nil); err != nil {
|
||||
@ -154,18 +154,11 @@ func (s *Server) didClose(ctx context.Context, params *protocol.DidCloseTextDocu
|
||||
// clear out all diagnostics for the package.
|
||||
f, err := view.GetFile(ctx, uri)
|
||||
if err != nil {
|
||||
log.Error(ctx, "no file for %s: %v", err, telemetry.File)
|
||||
return nil
|
||||
log.Error(ctx, "no file", err, telemetry.URI)
|
||||
}
|
||||
// For non-Go files, don't return any diagnostics.
|
||||
gof, ok := f.(source.GoFile)
|
||||
if !ok {
|
||||
log.Error(ctx, "closing a non-Go file, no diagnostics to clear", nil, telemetry.File)
|
||||
return nil
|
||||
}
|
||||
cphs, err := gof.CheckPackageHandles(ctx)
|
||||
_, cphs, err := view.CheckPackageHandles(ctx, f)
|
||||
if err != nil {
|
||||
log.Error(ctx, "no CheckPackageHandles", err, telemetry.URI.Of(gof.URI()))
|
||||
log.Error(ctx, "no CheckPackageHandles", err, telemetry.URI.Of(uri))
|
||||
return nil
|
||||
}
|
||||
for _, cph := range cphs {
|
||||
|
@ -1,25 +0,0 @@
|
||||
// Copyright 2019 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"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
"golang.org/x/tools/internal/span"
|
||||
errors "golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func getGoFile(ctx context.Context, view source.View, uri span.URI) (source.GoFile, error) {
|
||||
f, err := view.GetFile(ctx, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gof, ok := f.(source.GoFile)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("%s is not a Go file", uri)
|
||||
}
|
||||
return gof, nil
|
||||
}
|
@ -27,10 +27,10 @@ func (s *Server) didChangeWatchedFiles(ctx context.Context, params *protocol.Did
|
||||
ctx := telemetry.File.With(ctx, uri)
|
||||
|
||||
for _, view := range s.session.Views() {
|
||||
gof, _ := view.FindFile(ctx, uri).(source.GoFile)
|
||||
f := view.FindFile(ctx, uri)
|
||||
|
||||
// If we have never seen this file before, there is nothing to do.
|
||||
if gof == nil {
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -53,14 +53,14 @@ func (s *Server) didChangeWatchedFiles(ctx context.Context, params *protocol.Did
|
||||
case protocol.Deleted:
|
||||
log.Print(ctx, "watched file deleted", telemetry.File)
|
||||
|
||||
cphs, err := gof.CheckPackageHandles(ctx)
|
||||
_, cphs, err := view.CheckPackageHandles(ctx, f)
|
||||
if err != nil {
|
||||
log.Error(ctx, "didChangeWatchedFiles: GetPackage", err, telemetry.File)
|
||||
continue
|
||||
}
|
||||
// Find a different file in the same package we can use to trigger diagnostics.
|
||||
// TODO(rstambler): Allow diagnostics to be called per-package to avoid this.
|
||||
var otherFile source.GoFile
|
||||
var otherFile source.File
|
||||
sort.Slice(cphs, func(i, j int) bool {
|
||||
return len(cphs[i].Files()) > len(cphs[j].Files())
|
||||
})
|
||||
@ -69,10 +69,10 @@ func (s *Server) didChangeWatchedFiles(ctx context.Context, params *protocol.Did
|
||||
continue
|
||||
}
|
||||
ident := ph.File().Identity()
|
||||
if ident.URI == gof.URI() {
|
||||
if ident.URI == f.URI() {
|
||||
continue
|
||||
}
|
||||
otherFile, _ = view.FindFile(ctx, ident.URI).(source.GoFile)
|
||||
otherFile := view.FindFile(ctx, ident.URI)
|
||||
if otherFile != nil {
|
||||
break
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user