// 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 cache import ( "context" "fmt" "go/ast" "golang.org/x/tools/go/packages" "golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/lsp/telemetry" "golang.org/x/tools/internal/span" "golang.org/x/tools/internal/telemetry/log" "golang.org/x/tools/internal/telemetry/tag" "golang.org/x/tools/internal/telemetry/trace" 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 } 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 } 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 } 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())) 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())) 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()) } // Return this error as a diagnostic to the user. return nil, err } return v.updateMetadata(ctx, f.URI(), pkgs) } // 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 { // 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 }