|  | // Copyright 2021 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" | 
|  | "crypto/sha256" | 
|  | "fmt" | 
|  | "go/parser" | 
|  | "go/token" | 
|  | "runtime" | 
|  |  | 
|  | "golang.org/x/sync/errgroup" | 
|  | "golang.org/x/tools/gopls/internal/cache/metadata" | 
|  | "golang.org/x/tools/gopls/internal/cache/parsego" | 
|  | "golang.org/x/tools/gopls/internal/cache/symbols" | 
|  | "golang.org/x/tools/gopls/internal/file" | 
|  | "golang.org/x/tools/gopls/internal/filecache" | 
|  | "golang.org/x/tools/gopls/internal/protocol" | 
|  | "golang.org/x/tools/gopls/internal/util/bug" | 
|  | "golang.org/x/tools/internal/event" | 
|  | ) | 
|  |  | 
|  | // Symbols extracts and returns symbol information for every file contained in | 
|  | // a loaded package. It awaits snapshot loading. | 
|  | // | 
|  | // If workspaceOnly is set, this only includes symbols from files in a | 
|  | // workspace package. Otherwise, it returns symbols from all loaded packages. | 
|  | func (s *Snapshot) Symbols(ctx context.Context, ids ...PackageID) ([]*symbols.Package, error) { | 
|  | meta := s.MetadataGraph() | 
|  |  | 
|  | res := make([]*symbols.Package, len(ids)) | 
|  | var g errgroup.Group | 
|  | g.SetLimit(runtime.GOMAXPROCS(-1)) // symbolizing is cpu bound | 
|  | for i, id := range ids { | 
|  | g.Go(func() error { | 
|  | mp := meta.Packages[id] | 
|  | if mp == nil { | 
|  | return bug.Errorf("missing metadata for %q", id) | 
|  | } | 
|  |  | 
|  | key, fhs, err := symbolKey(ctx, mp, s) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | if data, err := filecache.Get(symbolsKind, key); err == nil { | 
|  | res[i] = symbols.Decode(data) | 
|  | return nil | 
|  | } else if err != filecache.ErrNotFound { | 
|  | bug.Reportf("internal error reading symbol data: %v", err) | 
|  | } | 
|  |  | 
|  | pgfs, err := s.view.parseCache.parseFiles(ctx, token.NewFileSet(), parsego.Full&^parser.ParseComments, false, fhs...) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | pkg := symbols.New(pgfs) | 
|  |  | 
|  | // Store the resulting data in the cache. | 
|  | go func() { | 
|  | data := pkg.Encode() | 
|  | if err := filecache.Set(symbolsKind, key, data); err != nil { | 
|  | event.Error(ctx, fmt.Sprintf("storing symbol data for %s", id), err) | 
|  | } | 
|  | }() | 
|  |  | 
|  | res[i] = pkg | 
|  | return nil | 
|  | }) | 
|  | } | 
|  |  | 
|  | return res, g.Wait() | 
|  | } | 
|  |  | 
|  | func symbolKey(ctx context.Context, mp *metadata.Package, fs file.Source) (file.Hash, []file.Handle, error) { | 
|  | seen := make(map[protocol.DocumentURI]bool) | 
|  | var fhs []file.Handle | 
|  | for _, list := range [][]protocol.DocumentURI{mp.GoFiles, mp.CompiledGoFiles} { | 
|  | for _, uri := range list { | 
|  | if !seen[uri] { | 
|  | seen[uri] = true | 
|  | fh, err := fs.ReadFile(ctx, uri) | 
|  | if err != nil { | 
|  | return file.Hash{}, nil, err // context cancelled | 
|  | } | 
|  | fhs = append(fhs, fh) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | hasher := sha256.New() | 
|  | fmt.Fprintf(hasher, "symbols: %s\n", mp.PkgPath) | 
|  | fmt.Fprintf(hasher, "files: %d\n", len(fhs)) | 
|  | for _, fh := range fhs { | 
|  | fmt.Fprintln(hasher, fh.Identity()) | 
|  | } | 
|  | var hash file.Hash | 
|  | hasher.Sum(hash[:0]) | 
|  | return hash, fhs, nil | 
|  | } |