+++ /dev/null
-// Copyright 2020 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 source
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "strings"
-
- "golang.org/x/tools/internal/gocommand"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
-)
-
-func GCOptimizationDetails(ctx context.Context, snapshot Snapshot, pkgDir span.URI) (map[VersionedFileIdentity][]*Diagnostic, error) {
- outDir := filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.details", os.Getpid()))
-
- if err := os.MkdirAll(outDir, 0700); err != nil {
- return nil, err
- }
- tmpFile, err := ioutil.TempFile(os.TempDir(), "gopls-x")
- if err != nil {
- return nil, err
- }
- defer os.Remove(tmpFile.Name())
-
- outDirURI := span.URIFromPath(outDir)
- // GC details doesn't handle Windows URIs in the form of "file:///C:/...",
- // so rewrite them to "file://C:/...". See golang/go#41614.
- if !strings.HasPrefix(outDir, "/") {
- outDirURI = span.URI(strings.Replace(string(outDirURI), "file:///", "file://", 1))
- }
- inv := &gocommand.Invocation{
- Verb: "build",
- Args: []string{
- fmt.Sprintf("-gcflags=-json=0,%s", outDirURI),
- fmt.Sprintf("-o=%s", tmpFile.Name()),
- ".",
- },
- WorkingDir: pkgDir.Filename(),
- }
- _, err = snapshot.RunGoCommandDirect(ctx, Normal, inv)
- if err != nil {
- return nil, err
- }
- files, err := findJSONFiles(outDir)
- if err != nil {
- return nil, err
- }
- reports := make(map[VersionedFileIdentity][]*Diagnostic)
- opts := snapshot.View().Options()
- var parseError error
- for _, fn := range files {
- uri, diagnostics, err := parseDetailsFile(fn, opts)
- if err != nil {
- // expect errors for all the files, save 1
- parseError = err
- }
- fh := snapshot.FindFile(uri)
- if fh == nil {
- continue
- }
- if pkgDir.Filename() != filepath.Dir(fh.URI().Filename()) {
- // https://github.com/golang/go/issues/42198
- // sometimes the detail diagnostics generated for files
- // outside the package can never be taken back.
- continue
- }
- reports[fh.VersionedFileIdentity()] = diagnostics
- }
- return reports, parseError
-}
-
-func parseDetailsFile(filename string, options *Options) (span.URI, []*Diagnostic, error) {
- buf, err := ioutil.ReadFile(filename)
- if err != nil {
- return "", nil, err
- }
- var (
- uri span.URI
- i int
- diagnostics []*Diagnostic
- )
- type metadata struct {
- File string `json:"file,omitempty"`
- }
- for dec := json.NewDecoder(bytes.NewReader(buf)); dec.More(); {
- // The first element always contains metadata.
- if i == 0 {
- i++
- m := new(metadata)
- if err := dec.Decode(m); err != nil {
- return "", nil, err
- }
- if !strings.HasSuffix(m.File, ".go") {
- continue // <autogenerated>
- }
- uri = span.URIFromPath(m.File)
- continue
- }
- d := new(protocol.Diagnostic)
- if err := dec.Decode(d); err != nil {
- return "", nil, err
- }
- msg := d.Code.(string)
- if msg != "" {
- msg = fmt.Sprintf("%s(%s)", msg, d.Message)
- }
- if skipDiagnostic(msg, d.Source, options) {
- continue
- }
- var related []RelatedInformation
- for _, ri := range d.RelatedInformation {
- related = append(related, RelatedInformation{
- URI: ri.Location.URI.SpanURI(),
- Range: zeroIndexedRange(ri.Location.Range),
- Message: ri.Message,
- })
- }
- diagnostic := &Diagnostic{
- Range: zeroIndexedRange(d.Range),
- Message: msg,
- Severity: d.Severity,
- Source: d.Source,
- Tags: d.Tags,
- Related: related,
- }
- diagnostics = append(diagnostics, diagnostic)
- i++
- }
- return uri, diagnostics, nil
-}
-
-// skipDiagnostic reports whether a given diagnostic should be shown to the end
-// user, given the current options.
-func skipDiagnostic(msg, source string, o *Options) bool {
- if source != "go compiler" {
- return false
- }
- switch {
- case o.Annotations["noInline"]:
- return strings.HasPrefix(msg, "canInline") ||
- strings.HasPrefix(msg, "cannotInline") ||
- strings.HasPrefix(msg, "inlineCall")
- case o.Annotations["noEscape"]:
- return strings.HasPrefix(msg, "escape") || msg == "leak"
- case o.Annotations["noNilcheck"]:
- return strings.HasPrefix(msg, "nilcheck")
- case o.Annotations["noBounds"]:
- return strings.HasPrefix(msg, "isInBounds") ||
- strings.HasPrefix(msg, "isSliceInBounds")
- }
- return false
-}
-
-// The range produced by the compiler is 1-indexed, so subtract range by 1.
-func zeroIndexedRange(rng protocol.Range) protocol.Range {
- return protocol.Range{
- Start: protocol.Position{
- Line: rng.Start.Line - 1,
- Character: rng.Start.Character - 1,
- },
- End: protocol.Position{
- Line: rng.End.Line - 1,
- Character: rng.End.Character - 1,
- },
- }
-}
-
-func findJSONFiles(dir string) ([]string, error) {
- ans := []string{}
- f := func(path string, fi os.FileInfo, _ error) error {
- if fi.IsDir() {
- return nil
- }
- if strings.HasSuffix(path, ".json") {
- ans = append(ans, path)
- }
- return nil
- }
- err := filepath.Walk(dir, f)
- return ans, err
-}