[gopls-release-branch.0.6] all: merge master into gopls-release-branch.0.6
0e232fa9 gopls: add scheme to CodeDescription.href
2363391a all: go fmt ./...
b3696403 all: fmt tests with new gofmt
f6e04434 go/analysis: add unusedwrite pass
bdaa8bfb go/gcexportdata: warn that {Read,Write}Bundle are experimental
f4301d9e internal/imports: update stdlib index for 1.16
f3748ed8 internal/lsp/source: filter out comparable from completion results
f47cb783 go/analysis/passes/buildtag: update check for //go:build
06713c25 go/loader: fix race
1f00549a internal/regtest: fix regtests for the dev.typeparams Go branch
4b197900 txtar: minor fix in unit test input
9eb35354 internal/lsp: 'go get' packages instead of modules
67e49ef2 go/internal/cgo: set pkgdir with -srcdir instead of CWD
19ff21fb go/analysis/unitchecker: tell the user how to list the flags and analyzers
d5b83329 internal/lsp/command: rename package generate to gen
4534fc34 go/internal/gcimporter: reference golang/go#44339 in TODO
35839b70 go/internal/gcimporter: fix tests on darwin
a1db63cc go/internal/gcimporter: add "bundled" export data formats
b79f76fe go/internal/gcimporter: fix reexporting compiler data
7fde01ff go/internal/gcimporter: refactor IExportData tests
6055ccf0 go/internal/gcimporter: simplify IExportData API
1e7abacf internal/lsp: refactor go command error handling
ffc20750 internal/lsp: fix nil pointer in hover when (types.Object).Pkg() is nil
fca89925 internal/lsp: handle nil pointer with import shortcut = link
5848b84f internal/typesinternal: sync error codes with go1.16
3a5a0519 go/analysis/passes: add sigchanyzer Analyzer
Change-Id: I1d4a41669e2f8e8115fb5d62cc25a62f7c755ba2
diff --git a/cmd/bundle/main.go b/cmd/bundle/main.go
index 8e5ad2c..fd8b0e5 100644
--- a/cmd/bundle/main.go
+++ b/cmd/bundle/main.go
@@ -228,6 +228,7 @@
var out bytes.Buffer
if buildTags != "" {
+ fmt.Fprintf(&out, "//go:build %s\n", buildTags)
fmt.Fprintf(&out, "// +build %s\n\n", buildTags)
}
diff --git a/cmd/bundle/testdata/out.golden b/cmd/bundle/testdata/out.golden
index ed18e3d..a8f0cfe 100644
--- a/cmd/bundle/testdata/out.golden
+++ b/cmd/bundle/testdata/out.golden
@@ -1,3 +1,4 @@
+//go:build tag
// +build tag
// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.
diff --git a/cmd/callgraph/main_test.go b/cmd/callgraph/main_test.go
index 6aeae6f..f486def 100644
--- a/cmd/callgraph/main_test.go
+++ b/cmd/callgraph/main_test.go
@@ -4,8 +4,8 @@
// No testdata on Android.
-// +build !android
-// +build go1.11
+//go:build !android && go1.11
+// +build !android,go1.11
package main
diff --git a/cmd/cover/cover_test.go b/cmd/cover/cover_test.go
index 54d3465..228c911 100644
--- a/cmd/cover/cover_test.go
+++ b/cmd/cover/cover_test.go
@@ -4,6 +4,7 @@
// No testdata on Android.
+//go:build !android
// +build !android
package main_test
diff --git a/cmd/fiximports/main_test.go b/cmd/fiximports/main_test.go
index 2e011d3..9d2c94c 100644
--- a/cmd/fiximports/main_test.go
+++ b/cmd/fiximports/main_test.go
@@ -4,6 +4,7 @@
// No testdata on Android.
+//go:build !android
// +build !android
package main
diff --git a/cmd/getgo/download.go b/cmd/getgo/download.go
index 383cb3d..1731131 100644
--- a/cmd/getgo/download.go
+++ b/cmd/getgo/download.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !plan9
// +build !plan9
package main
diff --git a/cmd/getgo/download_test.go b/cmd/getgo/download_test.go
index 5a5cc29..76cd96c 100644
--- a/cmd/getgo/download_test.go
+++ b/cmd/getgo/download_test.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !plan9
// +build !plan9
package main
diff --git a/cmd/getgo/main.go b/cmd/getgo/main.go
index 417e860..441fd89 100644
--- a/cmd/getgo/main.go
+++ b/cmd/getgo/main.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !plan9
// +build !plan9
// The getgo command installs Go to the user's system.
diff --git a/cmd/getgo/main_test.go b/cmd/getgo/main_test.go
index e430895..0c0e8b9 100644
--- a/cmd/getgo/main_test.go
+++ b/cmd/getgo/main_test.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !plan9
// +build !plan9
package main
diff --git a/cmd/getgo/path.go b/cmd/getgo/path.go
index 52e0462..f1799a8 100644
--- a/cmd/getgo/path.go
+++ b/cmd/getgo/path.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !plan9
// +build !plan9
package main
diff --git a/cmd/getgo/path_test.go b/cmd/getgo/path_test.go
index 5355c6e..2249c54 100644
--- a/cmd/getgo/path_test.go
+++ b/cmd/getgo/path_test.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !plan9
// +build !plan9
package main
diff --git a/cmd/getgo/steps.go b/cmd/getgo/steps.go
index e505f5a..fe69aa6 100644
--- a/cmd/getgo/steps.go
+++ b/cmd/getgo/steps.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !plan9
// +build !plan9
package main
diff --git a/cmd/getgo/system.go b/cmd/getgo/system.go
index 232ca36..3449c9c 100644
--- a/cmd/getgo/system.go
+++ b/cmd/getgo/system.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !plan9
// +build !plan9
package main
diff --git a/cmd/getgo/system_unix.go b/cmd/getgo/system_unix.go
index adc3e55..09606f8 100644
--- a/cmd/getgo/system_unix.go
+++ b/cmd/getgo/system_unix.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build aix || darwin || dragonfly || freebsd || linux || nacl || netbsd || openbsd || solaris
// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd solaris
package main
diff --git a/cmd/getgo/system_windows.go b/cmd/getgo/system_windows.go
index d8a6191..5b1e247 100644
--- a/cmd/getgo/system_windows.go
+++ b/cmd/getgo/system_windows.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build windows
// +build windows
package main
diff --git a/cmd/godex/isAlias18.go b/cmd/godex/isAlias18.go
index cab1292..431602b 100644
--- a/cmd/godex/isAlias18.go
+++ b/cmd/godex/isAlias18.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !go1.9
// +build !go1.9
package main
diff --git a/cmd/godex/isAlias19.go b/cmd/godex/isAlias19.go
index 6ebdd42..e588911 100644
--- a/cmd/godex/isAlias19.go
+++ b/cmd/godex/isAlias19.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.9
// +build go1.9
package main
diff --git a/cmd/goimports/goimports_gc.go b/cmd/goimports/goimports_gc.go
index 21d867e..190a565 100644
--- a/cmd/goimports/goimports_gc.go
+++ b/cmd/goimports/goimports_gc.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build gc
// +build gc
package main
diff --git a/cmd/goimports/goimports_not_gc.go b/cmd/goimports/goimports_not_gc.go
index f5531ce..344fe75 100644
--- a/cmd/goimports/goimports_not_gc.go
+++ b/cmd/goimports/goimports_not_gc.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !gc
// +build !gc
package main
diff --git a/cmd/gotype/sizesFor18.go b/cmd/gotype/sizesFor18.go
index 94e8176..39e3d9f 100644
--- a/cmd/gotype/sizesFor18.go
+++ b/cmd/gotype/sizesFor18.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !go1.9
// +build !go1.9
// This file contains a copy of the implementation of types.SizesFor
diff --git a/cmd/gotype/sizesFor19.go b/cmd/gotype/sizesFor19.go
index 9e0b481..34181c8 100644
--- a/cmd/gotype/sizesFor19.go
+++ b/cmd/gotype/sizesFor19.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.9
// +build go1.9
package main
diff --git a/cmd/guru/isAlias18.go b/cmd/guru/isAlias18.go
index 6a2a4c0..6d91017 100644
--- a/cmd/guru/isAlias18.go
+++ b/cmd/guru/isAlias18.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !go1.9
// +build !go1.9
package main
diff --git a/cmd/guru/isAlias19.go b/cmd/guru/isAlias19.go
index 25096ab..4d63679 100644
--- a/cmd/guru/isAlias19.go
+++ b/cmd/guru/isAlias19.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.9
// +build go1.9
package main
diff --git a/cmd/splitdwarf/splitdwarf.go b/cmd/splitdwarf/splitdwarf.go
index 44e7a7a..a13b9f3 100644
--- a/cmd/splitdwarf/splitdwarf.go
+++ b/cmd/splitdwarf/splitdwarf.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !js && !nacl && !plan9 && !solaris && !windows
// +build !js,!nacl,!plan9,!solaris,!windows
/*
diff --git a/cmd/stress/stress.go b/cmd/stress/stress.go
index 4ff6cf3..9ba6ef3 100644
--- a/cmd/stress/stress.go
+++ b/cmd/stress/stress.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !plan9
// +build !plan9
// The stress utility is intended for catching sporadic failures.
diff --git a/cmd/stringer/endtoend_test.go b/cmd/stringer/endtoend_test.go
index af106b5..3b0b39d 100644
--- a/cmd/stringer/endtoend_test.go
+++ b/cmd/stringer/endtoend_test.go
@@ -4,6 +4,7 @@
// go command is not available on android
+//go:build !android
// +build !android
package main
diff --git a/container/intsets/popcnt_amd64.go b/container/intsets/popcnt_amd64.go
index 99ea813..25c02f4 100644
--- a/container/intsets/popcnt_amd64.go
+++ b/container/intsets/popcnt_amd64.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build amd64 && !appengine && !gccgo
// +build amd64,!appengine,!gccgo
package intsets
diff --git a/container/intsets/popcnt_gccgo.go b/container/intsets/popcnt_gccgo.go
index 3fc5e85..5e1efcf 100644
--- a/container/intsets/popcnt_gccgo.go
+++ b/container/intsets/popcnt_gccgo.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build gccgo
// +build gccgo
package intsets
diff --git a/container/intsets/popcnt_generic.go b/container/intsets/popcnt_generic.go
index 3985a1d..caffedc 100644
--- a/container/intsets/popcnt_generic.go
+++ b/container/intsets/popcnt_generic.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build (!amd64 || appengine) && !gccgo
// +build !amd64 appengine
// +build !gccgo
diff --git a/go/analysis/analysistest/analysistest.go b/go/analysis/analysistest/analysistest.go
index 5e99afe..8447244 100644
--- a/go/analysis/analysistest/analysistest.go
+++ b/go/analysis/analysistest/analysistest.go
@@ -354,13 +354,13 @@
// Any comment starting with "want" is treated
// as an expectation, even without following whitespace.
if rest := strings.TrimPrefix(text, "want"); rest != text {
- expects, err := parseExpectations(rest)
+ lineDelta, expects, err := parseExpectations(rest)
if err != nil {
t.Errorf("%s:%d: in 'want' comment: %s", filename, linenum, err)
return
}
if expects != nil {
- want[key{filename, linenum}] = expects
+ want[key{filename, linenum + lineDelta}] = expects
}
}
}
@@ -526,13 +526,13 @@
// parseExpectations parses the content of a "// want ..." comment
// and returns the expectations, a mixture of diagnostics ("rx") and
// facts (name:"rx").
-func parseExpectations(text string) ([]expectation, error) {
+func parseExpectations(text string) (lineDelta int, expects []expectation, err error) {
var scanErr string
sc := new(scanner.Scanner).Init(strings.NewReader(text))
sc.Error = func(s *scanner.Scanner, msg string) {
scanErr = msg // e.g. bad string escape
}
- sc.Mode = scanner.ScanIdents | scanner.ScanStrings | scanner.ScanRawStrings
+ sc.Mode = scanner.ScanIdents | scanner.ScanStrings | scanner.ScanRawStrings | scanner.ScanInts
scanRegexp := func(tok rune) (*regexp.Regexp, error) {
if tok != scanner.String && tok != scanner.RawString {
@@ -543,14 +543,19 @@
return regexp.Compile(pattern)
}
- var expects []expectation
for {
tok := sc.Scan()
switch tok {
+ case '+':
+ tok = sc.Scan()
+ if tok != scanner.Int {
+ return 0, nil, fmt.Errorf("got +%s, want +Int", scanner.TokenString(tok))
+ }
+ lineDelta, _ = strconv.Atoi(sc.TokenText())
case scanner.String, scanner.RawString:
rx, err := scanRegexp(tok)
if err != nil {
- return nil, err
+ return 0, nil, err
}
expects = append(expects, expectation{"diagnostic", "", rx})
@@ -558,24 +563,24 @@
name := sc.TokenText()
tok = sc.Scan()
if tok != ':' {
- return nil, fmt.Errorf("got %s after %s, want ':'",
+ return 0, nil, fmt.Errorf("got %s after %s, want ':'",
scanner.TokenString(tok), name)
}
tok = sc.Scan()
rx, err := scanRegexp(tok)
if err != nil {
- return nil, err
+ return 0, nil, err
}
expects = append(expects, expectation{"fact", name, rx})
case scanner.EOF:
if scanErr != "" {
- return nil, fmt.Errorf("%s", scanErr)
+ return 0, nil, fmt.Errorf("%s", scanErr)
}
- return expects, nil
+ return lineDelta, expects, nil
default:
- return nil, fmt.Errorf("unexpected %s", scanner.TokenString(tok))
+ return 0, nil, fmt.Errorf("unexpected %s", scanner.TokenString(tok))
}
}
}
diff --git a/go/analysis/multichecker/multichecker_test.go b/go/analysis/multichecker/multichecker_test.go
index c7a2c46..07bf977 100644
--- a/go/analysis/multichecker/multichecker_test.go
+++ b/go/analysis/multichecker/multichecker_test.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.12
// +build go1.12
package multichecker_test
diff --git a/go/analysis/passes/buildtag/buildtag.go b/go/analysis/passes/buildtag/buildtag.go
index 841b928..c4407ad 100644
--- a/go/analysis/passes/buildtag/buildtag.go
+++ b/go/analysis/passes/buildtag/buildtag.go
@@ -2,14 +2,17 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.16
+// +build go1.16
+
// Package buildtag defines an Analyzer that checks build tags.
package buildtag
import (
- "bytes"
- "fmt"
"go/ast"
+ "go/build/constraint"
"go/parser"
+ "go/token"
"strings"
"unicode"
@@ -52,118 +55,313 @@
}
func checkGoFile(pass *analysis.Pass, f *ast.File) {
- pastCutoff := false
+ var check checker
+ check.init(pass)
+ defer check.finish()
+
for _, group := range f.Comments {
// A +build comment is ignored after or adjoining the package declaration.
if group.End()+1 >= f.Package {
- pastCutoff = true
+ check.plusBuildOK = false
}
-
- // "+build" is ignored within or after a /*...*/ comment.
- if !strings.HasPrefix(group.List[0].Text, "//") {
- pastCutoff = true
- continue
+ // A //go:build comment is ignored after the package declaration
+ // (but adjoining it is OK, in contrast to +build comments).
+ if group.Pos() >= f.Package {
+ check.goBuildOK = false
}
// Check each line of a //-comment.
for _, c := range group.List {
- if !strings.Contains(c.Text, "+build") {
- continue
+ // "+build" is ignored within or after a /*...*/ comment.
+ if !strings.HasPrefix(c.Text, "//") {
+ check.plusBuildOK = false
}
- if err := checkLine(c.Text, pastCutoff); err != nil {
- pass.Reportf(c.Pos(), "%s", err)
- }
+ check.comment(c.Slash, c.Text)
}
}
}
func checkOtherFile(pass *analysis.Pass, filename string) error {
+ var check checker
+ check.init(pass)
+ defer check.finish()
+
+ // We cannot use the Go parser, since this may not be a Go source file.
+ // Read the raw bytes instead.
content, tf, err := analysisutil.ReadFile(pass.Fset, filename)
if err != nil {
return err
}
- // We must look at the raw lines, as build tags may appear in non-Go
- // files such as assembly files.
- lines := bytes.SplitAfter(content, nl)
+ check.file(token.Pos(tf.Base()), string(content))
+ return nil
+}
+type checker struct {
+ pass *analysis.Pass
+ plusBuildOK bool // "+build" lines still OK
+ goBuildOK bool // "go:build" lines still OK
+ crossCheck bool // cross-check go:build and +build lines when done reading file
+ inStar bool // currently in a /* */ comment
+ goBuildPos token.Pos // position of first go:build line found
+ plusBuildPos token.Pos // position of first "+build" line found
+ goBuild constraint.Expr // go:build constraint found
+ plusBuild constraint.Expr // AND of +build constraints found
+}
+
+func (check *checker) init(pass *analysis.Pass) {
+ check.pass = pass
+ check.goBuildOK = true
+ check.plusBuildOK = true
+ check.crossCheck = true
+}
+
+func (check *checker) file(pos token.Pos, text string) {
// Determine cutpoint where +build comments are no longer valid.
// They are valid in leading // comments in the file followed by
// a blank line.
//
// This must be done as a separate pass because of the
// requirement that the comment be followed by a blank line.
- var cutoff int
- for i, line := range lines {
- line = bytes.TrimSpace(line)
- if !bytes.HasPrefix(line, slashSlash) {
- if len(line) > 0 {
- break
+ var plusBuildCutoff int
+ fullText := text
+ for text != "" {
+ i := strings.Index(text, "\n")
+ if i < 0 {
+ i = len(text)
+ } else {
+ i++
+ }
+ offset := len(fullText) - len(text)
+ line := text[:i]
+ text = text[i:]
+ line = strings.TrimSpace(line)
+ if !strings.HasPrefix(line, "//") && line != "" {
+ break
+ }
+ if line == "" {
+ plusBuildCutoff = offset
+ }
+ }
+
+ // Process each line.
+ // Must stop once we hit goBuildOK == false
+ text = fullText
+ check.inStar = false
+ for text != "" {
+ i := strings.Index(text, "\n")
+ if i < 0 {
+ i = len(text)
+ } else {
+ i++
+ }
+ offset := len(fullText) - len(text)
+ line := text[:i]
+ text = text[i:]
+ check.plusBuildOK = offset < plusBuildCutoff
+
+ if strings.HasPrefix(line, "//") {
+ check.comment(pos+token.Pos(offset), line)
+ continue
+ }
+
+ // Keep looking for the point at which //go:build comments
+ // stop being allowed. Skip over, cut out any /* */ comments.
+ for {
+ line = strings.TrimSpace(line)
+ if check.inStar {
+ i := strings.Index(line, "*/")
+ if i < 0 {
+ line = ""
+ break
+ }
+ line = line[i+len("*/"):]
+ check.inStar = false
+ continue
}
- cutoff = i
+ if strings.HasPrefix(line, "/*") {
+ check.inStar = true
+ line = line[len("/*"):]
+ continue
+ }
+ break
+ }
+ if line != "" {
+ // Found non-comment non-blank line.
+ // Ends space for valid //go:build comments,
+ // but also ends the fraction of the file we can
+ // reliably parse. From this point on we might
+ // incorrectly flag "comments" inside multiline
+ // string constants or anything else (this might
+ // not even be a Go program). So stop.
+ break
}
}
-
- for i, line := range lines {
- line = bytes.TrimSpace(line)
- if !bytes.HasPrefix(line, slashSlash) {
- continue
- }
- if !bytes.Contains(line, []byte("+build")) {
- continue
- }
- if err := checkLine(string(line), i >= cutoff); err != nil {
- pass.Reportf(analysisutil.LineStart(tf, i+1), "%s", err)
- continue
- }
- }
- return nil
}
-// checkLine checks a line that starts with "//" and contains "+build".
-func checkLine(line string, pastCutoff bool) error {
- line = strings.TrimPrefix(line, "//")
- line = strings.TrimSpace(line)
+func (check *checker) comment(pos token.Pos, text string) {
+ if strings.HasPrefix(text, "//") {
+ if strings.Contains(text, "+build") {
+ check.plusBuildLine(pos, text)
+ }
+ if strings.Contains(text, "//go:build") {
+ check.goBuildLine(pos, text)
+ }
+ }
+ if strings.HasPrefix(text, "/*") {
+ if i := strings.Index(text, "\n"); i >= 0 {
+ // multiline /* */ comment - process interior lines
+ check.inStar = true
+ i++
+ pos += token.Pos(i)
+ text = text[i:]
+ for text != "" {
+ i := strings.Index(text, "\n")
+ if i < 0 {
+ i = len(text)
+ } else {
+ i++
+ }
+ line := text[:i]
+ if strings.HasPrefix(line, "//") {
+ check.comment(pos, line)
+ }
+ pos += token.Pos(i)
+ text = text[i:]
+ }
+ check.inStar = false
+ }
+ }
+}
- if strings.HasPrefix(line, "+build") {
- fields := strings.Fields(line)
- if fields[0] != "+build" {
- // Comment is something like +buildasdf not +build.
- return fmt.Errorf("possible malformed +build comment")
+func (check *checker) goBuildLine(pos token.Pos, line string) {
+ if !constraint.IsGoBuild(line) {
+ if !strings.HasPrefix(line, "//go:build") && constraint.IsGoBuild("//"+strings.TrimSpace(line[len("//"):])) {
+ check.pass.Reportf(pos, "malformed //go:build line (space between // and go:build)")
}
- if pastCutoff {
- return fmt.Errorf("+build comment must appear before package clause and be followed by a blank line")
- }
- if err := checkArguments(fields); err != nil {
- return err
- }
+ return
+ }
+ if !check.goBuildOK || check.inStar {
+ check.pass.Reportf(pos, "misplaced //go:build comment")
+ check.crossCheck = false
+ return
+ }
+
+ if check.goBuildPos == token.NoPos {
+ check.goBuildPos = pos
} else {
- // Comment with +build but not at beginning.
- if !pastCutoff {
- return fmt.Errorf("possible malformed +build comment")
- }
+ check.pass.Reportf(pos, "unexpected extra //go:build line")
+ check.crossCheck = false
}
- return nil
+
+ // testing hack: stop at // ERROR
+ if i := strings.Index(line, " // ERROR "); i >= 0 {
+ line = line[:i]
+ }
+
+ x, err := constraint.Parse(line)
+ if err != nil {
+ check.pass.Reportf(pos, "%v", err)
+ check.crossCheck = false
+ return
+ }
+
+ if check.goBuild == nil {
+ check.goBuild = x
+ }
}
-func checkArguments(fields []string) error {
+func (check *checker) plusBuildLine(pos token.Pos, line string) {
+ line = strings.TrimSpace(line)
+ if !constraint.IsPlusBuild(line) {
+ // Comment with +build but not at beginning.
+ // Only report early in file.
+ if check.plusBuildOK && !strings.HasPrefix(line, "// want") {
+ check.pass.Reportf(pos, "possible malformed +build comment")
+ }
+ return
+ }
+ if !check.plusBuildOK { // inStar implies !plusBuildOK
+ check.pass.Reportf(pos, "misplaced +build comment")
+ check.crossCheck = false
+ }
+
+ if check.plusBuildPos == token.NoPos {
+ check.plusBuildPos = pos
+ }
+
+ // testing hack: stop at // ERROR
+ if i := strings.Index(line, " // ERROR "); i >= 0 {
+ line = line[:i]
+ }
+
+ fields := strings.Fields(line[len("//"):])
+ // IsPlusBuildConstraint check above implies fields[0] == "+build"
for _, arg := range fields[1:] {
for _, elem := range strings.Split(arg, ",") {
if strings.HasPrefix(elem, "!!") {
- return fmt.Errorf("invalid double negative in build constraint: %s", arg)
+ check.pass.Reportf(pos, "invalid double negative in build constraint: %s", arg)
+ check.crossCheck = false
+ continue
}
elem = strings.TrimPrefix(elem, "!")
for _, c := range elem {
if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
- return fmt.Errorf("invalid non-alphanumeric build constraint: %s", arg)
+ check.pass.Reportf(pos, "invalid non-alphanumeric build constraint: %s", arg)
+ check.crossCheck = false
+ break
}
}
}
}
- return nil
+
+ if check.crossCheck {
+ y, err := constraint.Parse(line)
+ if err != nil {
+ // Should never happen - constraint.Parse never rejects a // +build line.
+ // Also, we just checked the syntax above.
+ // Even so, report.
+ check.pass.Reportf(pos, "%v", err)
+ check.crossCheck = false
+ return
+ }
+ if check.plusBuild == nil {
+ check.plusBuild = y
+ } else {
+ check.plusBuild = &constraint.AndExpr{X: check.plusBuild, Y: y}
+ }
+ }
}
-var (
- nl = []byte("\n")
- slashSlash = []byte("//")
-)
+func (check *checker) finish() {
+ if !check.crossCheck || check.plusBuildPos == token.NoPos || check.goBuildPos == token.NoPos {
+ return
+ }
+
+ // Have both //go:build and // +build,
+ // with no errors found (crossCheck still true).
+ // Check they match.
+ var want constraint.Expr
+ lines, err := constraint.PlusBuildLines(check.goBuild)
+ if err != nil {
+ check.pass.Reportf(check.goBuildPos, "%v", err)
+ return
+ }
+ for _, line := range lines {
+ y, err := constraint.Parse(line)
+ if err != nil {
+ // Definitely should not happen, but not the user's fault.
+ // Do not report.
+ return
+ }
+ if want == nil {
+ want = y
+ } else {
+ want = &constraint.AndExpr{X: want, Y: y}
+ }
+ }
+ if want.String() != check.plusBuild.String() {
+ check.pass.Reportf(check.plusBuildPos, "+build lines do not match //go:build condition")
+ return
+ }
+}
diff --git a/go/analysis/passes/buildtag/buildtag_old.go b/go/analysis/passes/buildtag/buildtag_old.go
new file mode 100644
index 0000000..e923492
--- /dev/null
+++ b/go/analysis/passes/buildtag/buildtag_old.go
@@ -0,0 +1,174 @@
+// Copyright 2013 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.
+
+// TODO(rsc): Delete this file once Go 1.17 comes out and we can retire Go 1.15 support.
+
+//go:build !go1.16
+// +build !go1.16
+
+// Package buildtag defines an Analyzer that checks build tags.
+package buildtag
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "strings"
+ "unicode"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
+)
+
+const Doc = "check that +build tags are well-formed and correctly located"
+
+var Analyzer = &analysis.Analyzer{
+ Name: "buildtag",
+ Doc: Doc,
+ Run: runBuildTag,
+}
+
+func runBuildTag(pass *analysis.Pass) (interface{}, error) {
+ for _, f := range pass.Files {
+ checkGoFile(pass, f)
+ }
+ for _, name := range pass.OtherFiles {
+ if err := checkOtherFile(pass, name); err != nil {
+ return nil, err
+ }
+ }
+ for _, name := range pass.IgnoredFiles {
+ if strings.HasSuffix(name, ".go") {
+ f, err := parser.ParseFile(pass.Fset, name, nil, parser.ParseComments)
+ if err != nil {
+ // Not valid Go source code - not our job to diagnose, so ignore.
+ return nil, nil
+ }
+ checkGoFile(pass, f)
+ } else {
+ if err := checkOtherFile(pass, name); err != nil {
+ return nil, err
+ }
+ }
+ }
+ return nil, nil
+}
+
+func checkGoFile(pass *analysis.Pass, f *ast.File) {
+ pastCutoff := false
+ for _, group := range f.Comments {
+ // A +build comment is ignored after or adjoining the package declaration.
+ if group.End()+1 >= f.Package {
+ pastCutoff = true
+ }
+
+ // "+build" is ignored within or after a /*...*/ comment.
+ if !strings.HasPrefix(group.List[0].Text, "//") {
+ pastCutoff = true
+ continue
+ }
+
+ // Check each line of a //-comment.
+ for _, c := range group.List {
+ if !strings.Contains(c.Text, "+build") {
+ continue
+ }
+ if err := checkLine(c.Text, pastCutoff); err != nil {
+ pass.Reportf(c.Pos(), "%s", err)
+ }
+ }
+ }
+}
+
+func checkOtherFile(pass *analysis.Pass, filename string) error {
+ content, tf, err := analysisutil.ReadFile(pass.Fset, filename)
+ if err != nil {
+ return err
+ }
+
+ // We must look at the raw lines, as build tags may appear in non-Go
+ // files such as assembly files.
+ lines := bytes.SplitAfter(content, nl)
+
+ // Determine cutpoint where +build comments are no longer valid.
+ // They are valid in leading // comments in the file followed by
+ // a blank line.
+ //
+ // This must be done as a separate pass because of the
+ // requirement that the comment be followed by a blank line.
+ var cutoff int
+ for i, line := range lines {
+ line = bytes.TrimSpace(line)
+ if !bytes.HasPrefix(line, slashSlash) {
+ if len(line) > 0 {
+ break
+ }
+ cutoff = i
+ }
+ }
+
+ for i, line := range lines {
+ line = bytes.TrimSpace(line)
+ if !bytes.HasPrefix(line, slashSlash) {
+ continue
+ }
+ if !bytes.Contains(line, []byte("+build")) {
+ continue
+ }
+ if err := checkLine(string(line), i >= cutoff); err != nil {
+ pass.Reportf(analysisutil.LineStart(tf, i+1), "%s", err)
+ continue
+ }
+ }
+ return nil
+}
+
+// checkLine checks a line that starts with "//" and contains "+build".
+func checkLine(line string, pastCutoff bool) error {
+ line = strings.TrimPrefix(line, "//")
+ line = strings.TrimSpace(line)
+
+ if strings.HasPrefix(line, "+build") {
+ fields := strings.Fields(line)
+ if fields[0] != "+build" {
+ // Comment is something like +buildasdf not +build.
+ return fmt.Errorf("possible malformed +build comment")
+ }
+ if pastCutoff {
+ return fmt.Errorf("+build comment must appear before package clause and be followed by a blank line")
+ }
+ if err := checkArguments(fields); err != nil {
+ return err
+ }
+ } else {
+ // Comment with +build but not at beginning.
+ if !pastCutoff {
+ return fmt.Errorf("possible malformed +build comment")
+ }
+ }
+ return nil
+}
+
+func checkArguments(fields []string) error {
+ for _, arg := range fields[1:] {
+ for _, elem := range strings.Split(arg, ",") {
+ if strings.HasPrefix(elem, "!!") {
+ return fmt.Errorf("invalid double negative in build constraint: %s", arg)
+ }
+ elem = strings.TrimPrefix(elem, "!")
+ for _, c := range elem {
+ if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
+ return fmt.Errorf("invalid non-alphanumeric build constraint: %s", arg)
+ }
+ }
+ }
+ }
+ return nil
+}
+
+var (
+ nl = []byte("\n")
+ slashSlash = []byte("//")
+)
diff --git a/go/analysis/passes/buildtag/buildtag_test.go b/go/analysis/passes/buildtag/buildtag_test.go
index 110343c..163e8e3 100644
--- a/go/analysis/passes/buildtag/buildtag_test.go
+++ b/go/analysis/passes/buildtag/buildtag_test.go
@@ -5,6 +5,8 @@
package buildtag_test
import (
+ "runtime"
+ "strings"
"testing"
"golang.org/x/tools/go/analysis"
@@ -13,6 +15,9 @@
)
func Test(t *testing.T) {
+ if strings.HasPrefix(runtime.Version(), "go1.") && runtime.Version() < "go1.16" {
+ t.Skipf("skipping on %v", runtime.Version())
+ }
analyzer := *buildtag.Analyzer
analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
defer func() {
diff --git a/go/analysis/passes/buildtag/testdata/src/a/buildtag.go b/go/analysis/passes/buildtag/testdata/src/a/buildtag.go
index dcc980c..5bc5d3c 100644
--- a/go/analysis/passes/buildtag/testdata/src/a/buildtag.go
+++ b/go/analysis/passes/buildtag/testdata/src/a/buildtag.go
@@ -4,15 +4,19 @@
// This file contains tests for the buildtag checker.
-// +builder // want `possible malformed \+build comment`
-// +build !ignore
+// want +1 `possible malformed \+build comment`
+// +builder
+// +build ignore
// Mention +build // want `possible malformed \+build comment`
-// +build nospace // want "build comment must appear before package clause and be followed by a blank line"
+// want +1 `misplaced \+build comment`
+// +build nospace
+//go:build ok
package a
-// +build toolate // want "build comment must appear before package clause and be followed by a blank line$"
+// want +1 `misplaced \+build comment`
+// +build toolate
var _ = 3
diff --git a/go/analysis/passes/buildtag/testdata/src/a/buildtag2.go b/go/analysis/passes/buildtag/testdata/src/a/buildtag2.go
index 3b71ca5..453cbea 100644
--- a/go/analysis/passes/buildtag/testdata/src/a/buildtag2.go
+++ b/go/analysis/passes/buildtag/testdata/src/a/buildtag2.go
@@ -6,4 +6,12 @@
package a
-// +build toolate // want "build comment must appear before package clause and be followed by a blank line$"
+// want +1 `misplaced \+build comment`
+// +build toolate
+
+// want +1 `misplaced //go:build comment`
+//go:build toolate
+
+var _ = `
+// +build notacomment
+`
diff --git a/go/analysis/passes/buildtag/testdata/src/a/buildtag3.go b/go/analysis/passes/buildtag/testdata/src/a/buildtag3.go
new file mode 100644
index 0000000..0e81c49
--- /dev/null
+++ b/go/analysis/passes/buildtag/testdata/src/a/buildtag3.go
@@ -0,0 +1,14 @@
+// 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.
+
+// want +3 `[+]build lines do not match //go:build condition`
+
+//go:build good
+// +build bad
+
+package a
+
+var _ = `
+// +build notacomment
+`
diff --git a/go/analysis/passes/buildtag/testdata/src/a/buildtag4.go b/go/analysis/passes/buildtag/testdata/src/a/buildtag4.go
new file mode 100644
index 0000000..2651130
--- /dev/null
+++ b/go/analysis/passes/buildtag/testdata/src/a/buildtag4.go
@@ -0,0 +1,9 @@
+// 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.
+
+//go:build !(bad || worse)
+// +build !bad
+// +build !worse
+
+package a
diff --git a/go/analysis/passes/buildtag/testdata/src/a/buildtag5.go b/go/analysis/passes/buildtag/testdata/src/a/buildtag5.go
new file mode 100644
index 0000000..bd5e039
--- /dev/null
+++ b/go/analysis/passes/buildtag/testdata/src/a/buildtag5.go
@@ -0,0 +1,11 @@
+// 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.
+
+//go:build !(bad || worse)
+// +build !bad,!worse
+
+package a
+
+//want +1 `misplaced \+build comment`
+// +build other
diff --git a/go/analysis/passes/buildtag/testdata/src/a/buildtag6.s b/go/analysis/passes/buildtag/testdata/src/a/buildtag6.s
new file mode 100644
index 0000000..40fe14c
--- /dev/null
+++ b/go/analysis/passes/buildtag/testdata/src/a/buildtag6.s
@@ -0,0 +1,9 @@
+// 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.
+
+#include "go_asm.h"
+
+// ok because we cannot parse assembly files.
+// +build no
+
diff --git a/go/analysis/passes/buildtag/testdata/src/a/buildtag7.s b/go/analysis/passes/buildtag/testdata/src/a/buildtag7.s
new file mode 100644
index 0000000..b622d48
--- /dev/null
+++ b/go/analysis/passes/buildtag/testdata/src/a/buildtag7.s
@@ -0,0 +1,11 @@
+// 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.
+
+// +build ignore
+
+#include "go_asm.h"
+
+// ok because we cannot parse assembly files
+// the assembler would complain if we did assemble this file.
+//go:build no
diff --git a/go/analysis/passes/buildtag/testdata/src/a/buildtag8.s b/go/analysis/passes/buildtag/testdata/src/a/buildtag8.s
new file mode 100644
index 0000000..2f4edd3
--- /dev/null
+++ b/go/analysis/passes/buildtag/testdata/src/a/buildtag8.s
@@ -0,0 +1,14 @@
+// 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.
+
+// want +3 `\+build lines do not match //go:build condition`
+
+//go:build something
+// +build ignore
+
+#include "go_asm.h"
+
+// ok because we cannot parse assembly files
+// the assembler would complain if we did assemble this file.
+//go:build no
diff --git a/go/analysis/passes/errorsas/errorsas_test.go b/go/analysis/passes/errorsas/errorsas_test.go
index 19e783e..5ef8668 100644
--- a/go/analysis/passes/errorsas/errorsas_test.go
+++ b/go/analysis/passes/errorsas/errorsas_test.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.13
// +build go1.13
package errorsas_test
diff --git a/go/analysis/passes/sigchanyzer/sigchanyzer.go b/go/analysis/passes/sigchanyzer/sigchanyzer.go
new file mode 100644
index 0000000..3d89061
--- /dev/null
+++ b/go/analysis/passes/sigchanyzer/sigchanyzer.go
@@ -0,0 +1,129 @@
+// 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 sigchanyzer defines an Analyzer that detects
+// misuse of unbuffered signal as argument to signal.Notify.
+package sigchanyzer
+
+import (
+ "bytes"
+ "go/ast"
+ "go/format"
+ "go/token"
+ "go/types"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/inspect"
+ "golang.org/x/tools/go/ast/inspector"
+)
+
+const Doc = `check for unbuffered channel of os.Signal
+
+This checker reports call expression of the form signal.Notify(c <-chan os.Signal, sig ...os.Signal),
+where c is an unbuffered channel, which can be at risk of missing the signal.`
+
+// Analyzer describes sigchanyzer analysis function detector.
+var Analyzer = &analysis.Analyzer{
+ Name: "sigchanyzer",
+ Doc: Doc,
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+ Run: run,
+}
+
+func run(pass *analysis.Pass) (interface{}, error) {
+ inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+
+ nodeFilter := []ast.Node{
+ (*ast.CallExpr)(nil),
+ }
+ inspect.Preorder(nodeFilter, func(n ast.Node) {
+ call := n.(*ast.CallExpr)
+ if !isSignalNotify(pass.TypesInfo, call) {
+ return
+ }
+ var chanDecl *ast.CallExpr
+ switch arg := call.Args[0].(type) {
+ case *ast.Ident:
+ if decl, ok := findDecl(arg).(*ast.CallExpr); ok {
+ chanDecl = decl
+ }
+ case *ast.CallExpr:
+ chanDecl = arg
+ }
+ if chanDecl == nil || len(chanDecl.Args) != 1 {
+ return
+ }
+ chanDecl.Args = append(chanDecl.Args, &ast.BasicLit{
+ Kind: token.INT,
+ Value: "1",
+ })
+ var buf bytes.Buffer
+ if err := format.Node(&buf, token.NewFileSet(), chanDecl); err != nil {
+ return
+ }
+ pass.Report(analysis.Diagnostic{
+ Pos: call.Pos(),
+ End: call.End(),
+ Message: "misuse of unbuffered os.Signal channel as argument to signal.Notify",
+ SuggestedFixes: []analysis.SuggestedFix{{
+ Message: "Change to buffer channel",
+ TextEdits: []analysis.TextEdit{{
+ Pos: chanDecl.Pos(),
+ End: chanDecl.End(),
+ NewText: buf.Bytes(),
+ }},
+ }},
+ })
+ })
+ return nil, nil
+}
+
+func isSignalNotify(info *types.Info, call *ast.CallExpr) bool {
+ check := func(id *ast.Ident) bool {
+ obj := info.ObjectOf(id)
+ return obj.Name() == "Notify" && obj.Pkg().Path() == "os/signal"
+ }
+ switch fun := call.Fun.(type) {
+ case *ast.SelectorExpr:
+ return check(fun.Sel)
+ case *ast.Ident:
+ if fun, ok := findDecl(fun).(*ast.SelectorExpr); ok {
+ return check(fun.Sel)
+ }
+ return false
+ default:
+ return false
+ }
+}
+
+func findDecl(arg *ast.Ident) ast.Node {
+ if arg.Obj == nil {
+ return nil
+ }
+ switch as := arg.Obj.Decl.(type) {
+ case *ast.AssignStmt:
+ if len(as.Lhs) != len(as.Rhs) {
+ return nil
+ }
+ for i, lhs := range as.Lhs {
+ lid, ok := lhs.(*ast.Ident)
+ if !ok {
+ continue
+ }
+ if lid.Obj == arg.Obj {
+ return as.Rhs[i]
+ }
+ }
+ case *ast.ValueSpec:
+ if len(as.Names) != len(as.Values) {
+ return nil
+ }
+ for i, name := range as.Names {
+ if name.Obj == arg.Obj {
+ return as.Values[i]
+ }
+ }
+ }
+ return nil
+}
diff --git a/go/analysis/passes/sigchanyzer/sigchanyzer_test.go b/go/analysis/passes/sigchanyzer/sigchanyzer_test.go
new file mode 100644
index 0000000..50b3f4b
--- /dev/null
+++ b/go/analysis/passes/sigchanyzer/sigchanyzer_test.go
@@ -0,0 +1,17 @@
+// 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 sigchanyzer_test
+
+import (
+ "testing"
+
+ "golang.org/x/tools/go/analysis/analysistest"
+ "golang.org/x/tools/go/analysis/passes/sigchanyzer"
+)
+
+func Test(t *testing.T) {
+ testdata := analysistest.TestData()
+ analysistest.RunWithSuggestedFixes(t, testdata, sigchanyzer.Analyzer, "a")
+}
diff --git a/go/analysis/passes/sigchanyzer/testdata/src/a/a.go b/go/analysis/passes/sigchanyzer/testdata/src/a/a.go
new file mode 100644
index 0000000..277bf20
--- /dev/null
+++ b/go/analysis/passes/sigchanyzer/testdata/src/a/a.go
@@ -0,0 +1,38 @@
+package p
+
+import (
+ "os"
+ ao "os"
+ "os/signal"
+)
+
+var c = make(chan os.Signal)
+var d = make(chan os.Signal)
+
+func f() {
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt) // ok
+ _ = <-c
+}
+
+func g() {
+ c := make(chan os.Signal)
+ signal.Notify(c, os.Interrupt) // want "misuse of unbuffered os.Signal channel as argument to signal.Notify"
+ _ = <-c
+}
+
+func h() {
+ c := make(chan ao.Signal)
+ signal.Notify(c, os.Interrupt) // want "misuse of unbuffered os.Signal channel as argument to signal.Notify"
+ _ = <-c
+}
+
+func i() {
+ signal.Notify(d, os.Interrupt) // want "misuse of unbuffered os.Signal channel as argument to signal.Notify"
+}
+
+func j() {
+ c := make(chan os.Signal)
+ f := signal.Notify
+ f(c, os.Interrupt) // want "misuse of unbuffered os.Signal channel as argument to signal.Notify"
+}
diff --git a/go/analysis/passes/sigchanyzer/testdata/src/a/a.go.golden b/go/analysis/passes/sigchanyzer/testdata/src/a/a.go.golden
new file mode 100644
index 0000000..e3702d7
--- /dev/null
+++ b/go/analysis/passes/sigchanyzer/testdata/src/a/a.go.golden
@@ -0,0 +1,38 @@
+package p
+
+import (
+ "os"
+ ao "os"
+ "os/signal"
+)
+
+var c = make(chan os.Signal)
+var d = make(chan os.Signal, 1)
+
+func f() {
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt) // ok
+ _ = <-c
+}
+
+func g() {
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt) // want "misuse of unbuffered os.Signal channel as argument to signal.Notify"
+ _ = <-c
+}
+
+func h() {
+ c := make(chan ao.Signal, 1)
+ signal.Notify(c, os.Interrupt) // want "misuse of unbuffered os.Signal channel as argument to signal.Notify"
+ _ = <-c
+}
+
+func i() {
+ signal.Notify(d, os.Interrupt) // want "misuse of unbuffered os.Signal channel as argument to signal.Notify"
+}
+
+func j() {
+ c := make(chan os.Signal, 1)
+ f := signal.Notify
+ f(c, os.Interrupt) // want "misuse of unbuffered os.Signal channel as argument to signal.Notify"
+}
diff --git a/go/analysis/passes/unusedwrite/testdata/src/a/unusedwrite.go b/go/analysis/passes/unusedwrite/testdata/src/a/unusedwrite.go
new file mode 100644
index 0000000..7e43ee4
--- /dev/null
+++ b/go/analysis/passes/unusedwrite/testdata/src/a/unusedwrite.go
@@ -0,0 +1,75 @@
+package a
+
+type T1 struct{ x int }
+
+type T2 struct {
+ x int
+ y int
+}
+
+type T3 struct{ y *T1 }
+
+func BadWrites() {
+ // Test struct field writes.
+ var s1 T1
+ s1.x = 10 // want "unused write to field x"
+
+ // Test array writes.
+ var s2 [10]int
+ s2[1] = 10 // want "unused write to array index 1:int"
+
+ // Test range variables of struct type.
+ s3 := []T1{T1{x: 100}}
+ for i, v := range s3 {
+ v.x = i // want "unused write to field x"
+ }
+
+ // Test the case where a different field is read after the write.
+ s4 := []T2{T2{x: 1, y: 2}}
+ for i, v := range s4 {
+ v.x = i // want "unused write to field x"
+ _ = v.y
+ }
+}
+
+func (t T1) BadValueReceiverWrite(v T2) {
+ t.x = 10 // want "unused write to field x"
+ v.y = 20 // want "unused write to field y"
+}
+
+func GoodWrites(m map[int]int) {
+ // A map is copied by reference such that a write will affect the original map.
+ m[1] = 10
+
+ // Test struct field writes.
+ var s1 T1
+ s1.x = 10
+ print(s1.x)
+
+ // Test array writes.
+ var s2 [10]int
+ s2[1] = 10
+ // Current the checker doesn't distinguish index 1 and index 2.
+ _ = s2[2]
+
+ // Test range variables of struct type.
+ s3 := []T1{T1{x: 100}}
+ for i, v := range s3 { // v is a copy
+ v.x = i
+ _ = v.x // still a usage
+ }
+
+ // Test an object with multiple fields.
+ o := &T2{x: 10, y: 20}
+ print(o)
+
+ // Test an object of embedded struct/pointer type.
+ t1 := &T1{x: 10}
+ t2 := &T3{y: t1}
+ print(t2)
+}
+
+func (t *T1) GoodPointerReceiverWrite(v *T2) {
+ t.x = 10
+ v.y = 20
+}
diff --git a/go/analysis/passes/unusedwrite/unusedwrite.go b/go/analysis/passes/unusedwrite/unusedwrite.go
new file mode 100644
index 0000000..37a0e78
--- /dev/null
+++ b/go/analysis/passes/unusedwrite/unusedwrite.go
@@ -0,0 +1,184 @@
+// 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 unusedwrite checks for unused writes to the elements of a struct or array object.
+package unusedwrite
+
+import (
+ "fmt"
+ "go/types"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/buildssa"
+ "golang.org/x/tools/go/ssa"
+)
+
+// Doc is a documentation string.
+const Doc = `checks for unused writes
+
+The analyzer reports instances of writes to struct fields and
+arrays that are never read. Specifically, when a struct object
+or an array is copied, its elements are copied implicitly by
+the compiler, and any element write to this copy does nothing
+with the original object.
+
+For example:
+
+ type T struct { x int }
+ func f(input []T) {
+ for i, v := range input { // v is a copy
+ v.x = i // unused write to field x
+ }
+ }
+
+Another example is about non-pointer receiver:
+
+ type T struct { x int }
+ func (t T) f() { // t is a copy
+ t.x = i // unused write to field x
+ }
+`
+
+// Analyzer reports instances of writes to struct fields and arrays
+//that are never read.
+var Analyzer = &analysis.Analyzer{
+ Name: "unusedwrite",
+ Doc: Doc,
+ Requires: []*analysis.Analyzer{buildssa.Analyzer},
+ Run: run,
+}
+
+func run(pass *analysis.Pass) (interface{}, error) {
+ // Check the writes to struct and array objects.
+ checkStore := func(store *ssa.Store) {
+ // Consider field/index writes to an object whose elements are copied and not shared.
+ // MapUpdate is excluded since only the reference of the map is copied.
+ switch addr := store.Addr.(type) {
+ case *ssa.FieldAddr:
+ if isDeadStore(store, addr.X, addr) {
+ // Report the bug.
+ pass.Reportf(store.Pos(),
+ "unused write to field %s",
+ getFieldName(addr.X.Type(), addr.Field))
+ }
+ case *ssa.IndexAddr:
+ if isDeadStore(store, addr.X, addr) {
+ // Report the bug.
+ pass.Reportf(store.Pos(),
+ "unused write to array index %s", addr.Index)
+ }
+ }
+ }
+
+ ssainput := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
+ for _, fn := range ssainput.SrcFuncs {
+ // Visit each block. No need to visit fn.Recover.
+ for _, blk := range fn.Blocks {
+ for _, instr := range blk.Instrs {
+ // Identify writes.
+ if store, ok := instr.(*ssa.Store); ok {
+ checkStore(store)
+ }
+ }
+ }
+ }
+ return nil, nil
+}
+
+// isDeadStore determines whether a field/index write to an object is dead.
+// Argument "obj" is the object, and "addr" is the instruction fetching the field/index.
+func isDeadStore(store *ssa.Store, obj ssa.Value, addr ssa.Instruction) bool {
+ // Consider only struct or array objects.
+ if !hasStructOrArrayType(obj) {
+ return false
+ }
+ // Check liveness: if the value is used later, then don't report the write.
+ for _, ref := range *obj.Referrers() {
+ if ref == store || ref == addr {
+ continue
+ }
+ switch ins := ref.(type) {
+ case ssa.CallInstruction:
+ return false
+ case *ssa.FieldAddr:
+ // Check whether the same field is used.
+ if ins.X == obj {
+ if faddr, ok := addr.(*ssa.FieldAddr); ok {
+ if faddr.Field == ins.Field {
+ return false
+ }
+ }
+ }
+ // Otherwise another field is used, and this usage doesn't count.
+ continue
+ case *ssa.IndexAddr:
+ if ins.X == obj {
+ return false
+ }
+ continue // Otherwise another object is used
+ case *ssa.Lookup:
+ if ins.X == obj {
+ return false
+ }
+ continue // Otherwise another object is used
+ case *ssa.Store:
+ if ins.Val == obj {
+ return false
+ }
+ continue // Otherwise other object is stored
+ default: // consider live if the object is used in any other instruction
+ return false
+ }
+ }
+ return true
+}
+
+// isStructOrArray returns whether the underlying type is struct or array.
+func isStructOrArray(tp types.Type) bool {
+ if named, ok := tp.(*types.Named); ok {
+ tp = named.Underlying()
+ }
+ switch tp.(type) {
+ case *types.Array:
+ return true
+ case *types.Struct:
+ return true
+ }
+ return false
+}
+
+// hasStructOrArrayType returns whether a value is of struct or array type.
+func hasStructOrArrayType(v ssa.Value) bool {
+ if instr, ok := v.(ssa.Instruction); ok {
+ if alloc, ok := instr.(*ssa.Alloc); ok {
+ // Check the element type of an allocated register (which always has pointer type)
+ // e.g., for
+ // func (t T) f() { ...}
+ // the receiver object is of type *T:
+ // t0 = local T (t) *T
+ if tp, ok := alloc.Type().(*types.Pointer); ok {
+ return isStructOrArray(tp.Elem())
+ }
+ return false
+ }
+ }
+ return isStructOrArray(v.Type())
+}
+
+// getFieldName returns the name of a field in a struct.
+// It the field is not found, then it returns the string format of the index.
+//
+// For example, for struct T {x int, y int), getFieldName(*T, 1) returns "y".
+func getFieldName(tp types.Type, index int) string {
+ if pt, ok := tp.(*types.Pointer); ok {
+ tp = pt.Elem()
+ }
+ if named, ok := tp.(*types.Named); ok {
+ tp = named.Underlying()
+ }
+ if stp, ok := tp.(*types.Struct); ok {
+ return stp.Field(index).Name()
+ }
+ return fmt.Sprintf("%d", index)
+}
diff --git a/go/analysis/passes/unusedwrite/unusedwrite_test.go b/go/analysis/passes/unusedwrite/unusedwrite_test.go
new file mode 100644
index 0000000..9658849
--- /dev/null
+++ b/go/analysis/passes/unusedwrite/unusedwrite_test.go
@@ -0,0 +1,17 @@
+// 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 unusedwrite_test
+
+import (
+ "testing"
+
+ "golang.org/x/tools/go/analysis/analysistest"
+ "golang.org/x/tools/go/analysis/passes/unusedwrite"
+)
+
+func Test(t *testing.T) {
+ testdata := analysistest.TestData()
+ analysistest.Run(t, testdata, unusedwrite.Analyzer, "a")
+}
diff --git a/go/analysis/unitchecker/main.go b/go/analysis/unitchecker/main.go
index 7fa7c85..23acb7e 100644
--- a/go/analysis/unitchecker/main.go
+++ b/go/analysis/unitchecker/main.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build ignore
// +build ignore
// This file provides an example command for static checkers
diff --git a/go/analysis/unitchecker/unitchecker.go b/go/analysis/unitchecker/unitchecker.go
index 713e138..5424489 100644
--- a/go/analysis/unitchecker/unitchecker.go
+++ b/go/analysis/unitchecker/unitchecker.go
@@ -97,7 +97,7 @@
Usage of %[1]s:
%.16[1]s unit.cfg # execute analysis specified by config file
- %.16[1]s help # general help
+ %.16[1]s help # general help, including listing analyzers and flags
%.16[1]s help name # help on specific analyzer and its flags
`, progname)
os.Exit(1)
diff --git a/go/analysis/unitchecker/unitchecker112.go b/go/analysis/unitchecker/unitchecker112.go
index 9051456..3180f4a 100644
--- a/go/analysis/unitchecker/unitchecker112.go
+++ b/go/analysis/unitchecker/unitchecker112.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.12
// +build go1.12
package unitchecker
diff --git a/go/analysis/unitchecker/unitchecker_test.go b/go/analysis/unitchecker/unitchecker_test.go
index 4a302ff..7e5b848 100644
--- a/go/analysis/unitchecker/unitchecker_test.go
+++ b/go/analysis/unitchecker/unitchecker_test.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.12
// +build go1.12
package unitchecker_test
diff --git a/go/buildutil/allpackages_test.go b/go/buildutil/allpackages_test.go
index ccdc31b..1aa194d 100644
--- a/go/buildutil/allpackages_test.go
+++ b/go/buildutil/allpackages_test.go
@@ -4,6 +4,7 @@
// Incomplete source tree on Android.
+//go:build !android
// +build !android
package buildutil_test
diff --git a/go/callgraph/cha/cha_test.go b/go/callgraph/cha/cha_test.go
index cb2d585..3dc0314 100644
--- a/go/callgraph/cha/cha_test.go
+++ b/go/callgraph/cha/cha_test.go
@@ -4,6 +4,7 @@
// No testdata on Android.
+//go:build !android
// +build !android
package cha_test
diff --git a/go/callgraph/rta/rta_test.go b/go/callgraph/rta/rta_test.go
index 28a00b3..9ae1bdf 100644
--- a/go/callgraph/rta/rta_test.go
+++ b/go/callgraph/rta/rta_test.go
@@ -4,6 +4,7 @@
// No testdata on Android.
+//go:build !android
// +build !android
package rta_test
diff --git a/go/gcexportdata/example_test.go b/go/gcexportdata/example_test.go
index a50bc40..fda3f60 100644
--- a/go/gcexportdata/example_test.go
+++ b/go/gcexportdata/example_test.go
@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build go1.7
-// +build gc
+//go:build go1.7 && gc
+// +build go1.7,gc
package gcexportdata_test
diff --git a/go/gcexportdata/gcexportdata.go b/go/gcexportdata/gcexportdata.go
index f8363d8..fc8beea 100644
--- a/go/gcexportdata/gcexportdata.go
+++ b/go/gcexportdata/gcexportdata.go
@@ -100,10 +100,34 @@
// Write writes encoded type information for the specified package to out.
// The FileSet provides file position information for named objects.
func Write(out io.Writer, fset *token.FileSet, pkg *types.Package) error {
- b, err := gcimporter.IExportData(fset, pkg)
- if err != nil {
+ if _, err := io.WriteString(out, "i"); err != nil {
return err
}
- _, err = out.Write(b)
- return err
+ return gcimporter.IExportData(out, fset, pkg)
+}
+
+// ReadBundle reads an export bundle from in, decodes it, and returns type
+// information for the packages.
+// File position information is added to fset.
+//
+// ReadBundle may inspect and add to the imports map to ensure that references
+// within the export bundle to other packages are consistent.
+//
+// On return, the state of the reader is undefined.
+//
+// Experimental: This API is experimental and may change in the future.
+func ReadBundle(in io.Reader, fset *token.FileSet, imports map[string]*types.Package) ([]*types.Package, error) {
+ data, err := ioutil.ReadAll(in)
+ if err != nil {
+ return nil, fmt.Errorf("reading export bundle: %v", err)
+ }
+ return gcimporter.IImportBundle(fset, imports, data)
+}
+
+// WriteBundle writes encoded type information for the specified packages to out.
+// The FileSet provides file position information for named objects.
+//
+// Experimental: This API is experimental and may change in the future.
+func WriteBundle(out io.Writer, fset *token.FileSet, pkgs []*types.Package) error {
+ return gcimporter.IExportBundle(out, fset, pkgs)
}
diff --git a/go/gcexportdata/main.go b/go/gcexportdata/main.go
index 2713dce..e9df4e9 100644
--- a/go/gcexportdata/main.go
+++ b/go/gcexportdata/main.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build ignore
// +build ignore
// The gcexportdata command is a diagnostic tool that displays the
diff --git a/go/internal/cgo/cgo.go b/go/internal/cgo/cgo.go
index 9772504..d9074ea 100644
--- a/go/internal/cgo/cgo.go
+++ b/go/internal/cgo/cgo.go
@@ -57,13 +57,14 @@
"go/build"
"go/parser"
"go/token"
- exec "golang.org/x/sys/execabs"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
"strings"
+
+ exec "golang.org/x/sys/execabs"
)
// ProcessFiles invokes the cgo preprocessor on bp.CgoFiles, parses
@@ -159,14 +160,13 @@
}
args := stringList(
- "go", "tool", "cgo", "-objdir", tmpdir, cgoflags, "--",
+ "go", "tool", "cgo", "-srcdir", pkgdir, "-objdir", tmpdir, cgoflags, "--",
cgoCPPFLAGS, cgoexeCFLAGS, cgoFiles,
)
if false {
- log.Printf("Running cgo for package %q: %s (dir=%s)", bp.ImportPath, args, pkgdir)
+ log.Printf("Running cgo for package %q: %s", bp.ImportPath, args)
}
cmd := exec.Command(args[0], args[1:]...)
- cmd.Dir = pkgdir
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
diff --git a/go/internal/gccgoimporter/newInterface10.go b/go/internal/gccgoimporter/newInterface10.go
index 9a108d4..1b449ef 100644
--- a/go/internal/gccgoimporter/newInterface10.go
+++ b/go/internal/gccgoimporter/newInterface10.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !go1.11
// +build !go1.11
package gccgoimporter
diff --git a/go/internal/gccgoimporter/newInterface11.go b/go/internal/gccgoimporter/newInterface11.go
index 1636610..631546e 100644
--- a/go/internal/gccgoimporter/newInterface11.go
+++ b/go/internal/gccgoimporter/newInterface11.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.11
// +build go1.11
package gccgoimporter
diff --git a/go/internal/gcimporter/bexport_test.go b/go/internal/gcimporter/bexport_test.go
index de7b921..702278e 100644
--- a/go/internal/gcimporter/bexport_test.go
+++ b/go/internal/gcimporter/bexport_test.go
@@ -12,6 +12,7 @@
"go/parser"
"go/token"
"go/types"
+ "path/filepath"
"reflect"
"runtime"
"strings"
@@ -117,7 +118,8 @@
func fileLine(fset *token.FileSet, obj types.Object) string {
posn := fset.Position(obj.Pos())
- return fmt.Sprintf("%s:%d", posn.Filename, posn.Line)
+ filename := filepath.Clean(strings.ReplaceAll(posn.Filename, "$GOROOT", runtime.GOROOT()))
+ return fmt.Sprintf("%s:%d", filename, posn.Line)
}
// equalObj reports how x and y differ. They are assumed to belong to
diff --git a/go/internal/gcimporter/iexport.go b/go/internal/gcimporter/iexport.go
index 4be32a2..d2fc8b6 100644
--- a/go/internal/gcimporter/iexport.go
+++ b/go/internal/gcimporter/iexport.go
@@ -25,12 +25,25 @@
// 0: Go1.11 encoding
const iexportVersion = 0
-// IExportData returns the binary export data for pkg.
+// Current bundled export format version. Increase with each format change.
+// 0: initial implementation
+const bundleVersion = 0
+
+// IExportData writes indexed export data for pkg to out.
//
// If no file set is provided, position info will be missing.
// The package path of the top-level package will not be recorded,
// so that calls to IImportData can override with a provided package path.
-func IExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) {
+func IExportData(out io.Writer, fset *token.FileSet, pkg *types.Package) error {
+ return iexportCommon(out, fset, false, []*types.Package{pkg})
+}
+
+// IExportBundle writes an indexed export bundle for pkgs to out.
+func IExportBundle(out io.Writer, fset *token.FileSet, pkgs []*types.Package) error {
+ return iexportCommon(out, fset, true, pkgs)
+}
+
+func iexportCommon(out io.Writer, fset *token.FileSet, bundle bool, pkgs []*types.Package) (err error) {
defer func() {
if e := recover(); e != nil {
if ierr, ok := e.(internalError); ok {
@@ -43,13 +56,14 @@
}()
p := iexporter{
- out: bytes.NewBuffer(nil),
fset: fset,
allPkgs: map[*types.Package]bool{},
stringIndex: map[string]uint64{},
declIndex: map[types.Object]uint64{},
typIndex: map[types.Type]uint64{},
- localpkg: pkg,
+ }
+ if !bundle {
+ p.localpkg = pkgs[0]
}
for i, pt := range predeclared() {
@@ -60,10 +74,20 @@
}
// Initialize work queue with exported declarations.
- scope := pkg.Scope()
- for _, name := range scope.Names() {
- if ast.IsExported(name) {
- p.pushDecl(scope.Lookup(name))
+ for _, pkg := range pkgs {
+ scope := pkg.Scope()
+ for _, name := range scope.Names() {
+ if ast.IsExported(name) {
+ p.pushDecl(scope.Lookup(name))
+ }
+ }
+
+ if bundle {
+ // Ensure pkg and its imports are included in the index.
+ p.allPkgs[pkg] = true
+ for _, imp := range pkg.Imports() {
+ p.allPkgs[imp] = true
+ }
}
}
@@ -76,21 +100,35 @@
dataLen := uint64(p.data0.Len())
w := p.newWriter()
w.writeIndex(p.declIndex)
+
+ if bundle {
+ w.uint64(uint64(len(pkgs)))
+ for _, pkg := range pkgs {
+ w.pkg(pkg)
+ imps := pkg.Imports()
+ w.uint64(uint64(len(imps)))
+ for _, imp := range imps {
+ w.pkg(imp)
+ }
+ }
+ }
w.flush()
// Assemble header.
var hdr intWriter
- hdr.WriteByte('i')
+ if bundle {
+ hdr.uint64(bundleVersion)
+ }
hdr.uint64(iexportVersion)
hdr.uint64(uint64(p.strings.Len()))
hdr.uint64(dataLen)
// Flush output.
- io.Copy(p.out, &hdr)
- io.Copy(p.out, &p.strings)
- io.Copy(p.out, &p.data0)
+ io.Copy(out, &hdr)
+ io.Copy(out, &p.strings)
+ io.Copy(out, &p.data0)
- return p.out.Bytes(), nil
+ return nil
}
// writeIndex writes out an object index. mainIndex indicates whether
@@ -104,7 +142,9 @@
// For the main index, make sure to include every package that
// we reference, even if we're not exporting (or reexporting)
// any symbols from it.
- pkgObjs[w.p.localpkg] = nil
+ if w.p.localpkg != nil {
+ pkgObjs[w.p.localpkg] = nil
+ }
for pkg := range w.p.allPkgs {
pkgObjs[pkg] = nil
}
@@ -474,10 +514,10 @@
func (w *exportWriter) value(typ types.Type, v constant.Value) {
w.typ(typ, nil)
- switch v.Kind() {
- case constant.Bool:
+ switch b := typ.Underlying().(*types.Basic); b.Info() & types.IsConstType {
+ case types.IsBoolean:
w.bool(constant.BoolVal(v))
- case constant.Int:
+ case types.IsInteger:
var i big.Int
if i64, exact := constant.Int64Val(v); exact {
i.SetInt64(i64)
@@ -487,25 +527,27 @@
i.SetString(v.ExactString(), 10)
}
w.mpint(&i, typ)
- case constant.Float:
+ case types.IsFloat:
f := constantToFloat(v)
w.mpfloat(f, typ)
- case constant.Complex:
+ case types.IsComplex:
w.mpfloat(constantToFloat(constant.Real(v)), typ)
w.mpfloat(constantToFloat(constant.Imag(v)), typ)
- case constant.String:
+ case types.IsString:
w.string(constant.StringVal(v))
- case constant.Unknown:
- // package contains type errors
default:
- panic(internalErrorf("unexpected value %v (%T)", v, v))
+ if b.Kind() == types.Invalid {
+ // package contains type errors
+ break
+ }
+ panic(internalErrorf("unexpected type %v (%v)", typ, typ.Underlying()))
}
}
// constantToFloat converts a constant.Value with kind constant.Float to a
// big.Float.
func constantToFloat(x constant.Value) *big.Float {
- assert(x.Kind() == constant.Float)
+ x = constant.ToFloat(x)
// Use the same floating-point precision (512) as cmd/compile
// (see Mpprec in cmd/compile/internal/gc/mpfloat.go).
const mpprec = 512
diff --git a/go/internal/gcimporter/iexport_test.go b/go/internal/gcimporter/iexport_test.go
index 5024570..5385011 100644
--- a/go/internal/gcimporter/iexport_test.go
+++ b/go/internal/gcimporter/iexport_test.go
@@ -4,11 +4,14 @@
// This is a copy of bexport_test.go for iexport.go.
+//go:build go1.11
// +build go1.11
package gcimporter_test
import (
+ "bufio"
+ "bytes"
"fmt"
"go/ast"
"go/build"
@@ -16,7 +19,9 @@
"go/parser"
"go/token"
"go/types"
+ "io/ioutil"
"math/big"
+ "os"
"reflect"
"runtime"
"sort"
@@ -28,6 +33,35 @@
"golang.org/x/tools/go/loader"
)
+func readExportFile(filename string) ([]byte, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ buf := bufio.NewReader(f)
+ if _, err := gcimporter.FindExportData(buf); err != nil {
+ return nil, err
+ }
+
+ if ch, err := buf.ReadByte(); err != nil {
+ return nil, err
+ } else if ch != 'i' {
+ return nil, fmt.Errorf("unexpected byte: %v", ch)
+ }
+
+ return ioutil.ReadAll(buf)
+}
+
+func iexport(fset *token.FileSet, pkg *types.Package) ([]byte, error) {
+ var buf bytes.Buffer
+ if err := gcimporter.IExportData(&buf, fset, pkg); err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), nil
+}
+
func TestIExportData_stdlib(t *testing.T) {
if runtime.Compiler == "gccgo" {
t.Skip("gccgo standard library is inaccessible")
@@ -45,6 +79,9 @@
conf := loader.Config{
Build: &ctxt,
AllowErrors: true,
+ TypeChecker: types.Config{
+ Sizes: types.SizesFor(ctxt.Compiler, ctxt.GOARCH),
+ },
}
for _, path := range buildutil.AllPackages(conf.Build) {
conf.Import(path)
@@ -72,63 +109,87 @@
}
var sorted []*types.Package
- for pkg := range prog.AllPackages {
- sorted = append(sorted, pkg)
+ for pkg, info := range prog.AllPackages {
+ if info.Files != nil { // non-empty directory
+ sorted = append(sorted, pkg)
+ }
}
sort.Slice(sorted, func(i, j int) bool {
return sorted[i].Path() < sorted[j].Path()
})
for _, pkg := range sorted {
- info := prog.AllPackages[pkg]
- if info.Files == nil {
- continue // empty directory
- }
- exportdata, err := gcimporter.IExportData(conf.Fset, pkg)
- if err != nil {
- t.Fatal(err)
- }
- if exportdata[0] == 'i' {
- exportdata = exportdata[1:] // trim the 'i' in the header
+ if exportdata, err := iexport(conf.Fset, pkg); err != nil {
+ t.Error(err)
} else {
- t.Fatalf("unexpected first character of export data: %v", exportdata[0])
+ testPkgData(t, conf.Fset, pkg, exportdata)
}
- imports := make(map[string]*types.Package)
- fset2 := token.NewFileSet()
- n, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg.Path())
- if err != nil {
- t.Errorf("IImportData(%s): %v", pkg.Path(), err)
+ if pkg.Name() == "main" || pkg.Name() == "haserrors" {
+ // skip; no export data
+ } else if bp, err := ctxt.Import(pkg.Path(), "", build.FindOnly); err != nil {
+ t.Log("warning:", err)
+ } else if exportdata, err := readExportFile(bp.PkgObj); err != nil {
+ t.Log("warning:", err)
+ } else {
+ testPkgData(t, conf.Fset, pkg, exportdata)
+ }
+ }
+
+ var bundle bytes.Buffer
+ if err := gcimporter.IExportBundle(&bundle, conf.Fset, sorted); err != nil {
+ t.Fatal(err)
+ }
+ fset2 := token.NewFileSet()
+ imports := make(map[string]*types.Package)
+ pkgs2, err := gcimporter.IImportBundle(fset2, imports, bundle.Bytes())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for i, pkg := range sorted {
+ testPkg(t, conf.Fset, pkg, fset2, pkgs2[i])
+ }
+}
+
+func testPkgData(t *testing.T, fset *token.FileSet, pkg *types.Package, exportdata []byte) {
+ imports := make(map[string]*types.Package)
+ fset2 := token.NewFileSet()
+ _, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg.Path())
+ if err != nil {
+ t.Errorf("IImportData(%s): %v", pkg.Path(), err)
+ }
+
+ testPkg(t, fset, pkg, fset2, pkg2)
+}
+
+func testPkg(t *testing.T, fset *token.FileSet, pkg *types.Package, fset2 *token.FileSet, pkg2 *types.Package) {
+ if _, err := iexport(fset2, pkg2); err != nil {
+ t.Errorf("reexport %q: %v", pkg.Path(), err)
+ }
+
+ // Compare the packages' corresponding members.
+ for _, name := range pkg.Scope().Names() {
+ if !ast.IsExported(name) {
continue
}
- if n != len(exportdata) {
- t.Errorf("IImportData(%s) decoded %d bytes, want %d",
- pkg.Path(), n, len(exportdata))
+ obj1 := pkg.Scope().Lookup(name)
+ obj2 := pkg2.Scope().Lookup(name)
+ if obj2 == nil {
+ t.Errorf("%s.%s not found, want %s", pkg.Path(), name, obj1)
+ continue
}
- // Compare the packages' corresponding members.
- for _, name := range pkg.Scope().Names() {
- if !ast.IsExported(name) {
- continue
- }
- obj1 := pkg.Scope().Lookup(name)
- obj2 := pkg2.Scope().Lookup(name)
- if obj2 == nil {
- t.Fatalf("%s.%s not found, want %s", pkg.Path(), name, obj1)
- continue
- }
+ fl1 := fileLine(fset, obj1)
+ fl2 := fileLine(fset2, obj2)
+ if fl1 != fl2 {
+ t.Errorf("%s.%s: got posn %s, want %s",
+ pkg.Path(), name, fl2, fl1)
+ }
- fl1 := fileLine(conf.Fset, obj1)
- fl2 := fileLine(fset2, obj2)
- if fl1 != fl2 {
- t.Errorf("%s.%s: got posn %s, want %s",
- pkg.Path(), name, fl2, fl1)
- }
-
- if err := cmpObj(obj1, obj2); err != nil {
- t.Errorf("%s.%s: %s\ngot: %s\nwant: %s",
- pkg.Path(), name, err, obj2, obj1)
- }
+ if err := cmpObj(obj1, obj2); err != nil {
+ t.Errorf("%s.%s: %s\ngot: %s\nwant: %s",
+ pkg.Path(), name, err, obj2, obj1)
}
}
}
@@ -151,15 +212,10 @@
}
// export
- exportdata, err := gcimporter.IExportData(fset1, pkg)
+ exportdata, err := iexport(fset1, pkg)
if err != nil {
t.Fatal(err)
}
- if exportdata[0] == 'i' {
- exportdata = exportdata[1:] // trim the 'i' in the header
- } else {
- t.Fatalf("unexpected first character of export data: %v", exportdata[0])
- }
// import
imports := make(map[string]*types.Package)
@@ -199,15 +255,10 @@
// export
// use a nil fileset here to confirm that it doesn't panic
- exportdata, err := gcimporter.IExportData(nil, pkg1)
+ exportdata, err := iexport(nil, pkg1)
if err != nil {
t.Fatal(err)
}
- if exportdata[0] == 'i' {
- exportdata = exportdata[1:] // trim the 'i' in the header
- } else {
- t.Fatalf("unexpected first character of export data: %v", exportdata[0])
- }
// import
imports := make(map[string]*types.Package)
diff --git a/go/internal/gcimporter/iimport.go b/go/internal/gcimporter/iimport.go
index a31a880..b236deb 100644
--- a/go/internal/gcimporter/iimport.go
+++ b/go/internal/gcimporter/iimport.go
@@ -59,10 +59,23 @@
)
// IImportData imports a package from the serialized package data
-// and returns the number of bytes consumed and a reference to the package.
+// and returns 0 and a reference to the package.
// If the export data version is not recognized or the format is otherwise
// compromised, an error is returned.
-func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) {
+func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (int, *types.Package, error) {
+ pkgs, err := iimportCommon(fset, imports, data, false, path)
+ if err != nil {
+ return 0, nil, err
+ }
+ return 0, pkgs[0], nil
+}
+
+// IImportBundle imports a set of packages from the serialized package bundle.
+func IImportBundle(fset *token.FileSet, imports map[string]*types.Package, data []byte) ([]*types.Package, error) {
+ return iimportCommon(fset, imports, data, true, "")
+}
+
+func iimportCommon(fset *token.FileSet, imports map[string]*types.Package, data []byte, bundle bool, path string) (pkgs []*types.Package, err error) {
const currentVersion = 1
version := int64(-1)
defer func() {
@@ -77,6 +90,15 @@
r := &intReader{bytes.NewReader(data), path}
+ if bundle {
+ bundleVersion := r.uint64()
+ switch bundleVersion {
+ case bundleVersion:
+ default:
+ errorf("unknown bundle format version %d", bundleVersion)
+ }
+ }
+
version = int64(r.uint64())
switch version {
case currentVersion, 0:
@@ -143,39 +165,58 @@
p.pkgIndex[pkg] = nameIndex
pkgList[i] = pkg
}
- if len(pkgList) == 0 {
- errorf("no packages found for %s", path)
- panic("unreachable")
+
+ if bundle {
+ pkgs = make([]*types.Package, r.uint64())
+ for i := range pkgs {
+ pkg := p.pkgAt(r.uint64())
+ imps := make([]*types.Package, r.uint64())
+ for j := range imps {
+ imps[j] = p.pkgAt(r.uint64())
+ }
+ pkg.SetImports(imps)
+ pkgs[i] = pkg
+ }
+ } else {
+ if len(pkgList) == 0 {
+ errorf("no packages found for %s", path)
+ panic("unreachable")
+ }
+ pkgs = pkgList[:1]
+
+ // record all referenced packages as imports
+ list := append(([]*types.Package)(nil), pkgList[1:]...)
+ sort.Sort(byPath(list))
+ pkgs[0].SetImports(list)
}
- p.ipkg = pkgList[0]
- names := make([]string, 0, len(p.pkgIndex[p.ipkg]))
- for name := range p.pkgIndex[p.ipkg] {
- names = append(names, name)
- }
- sort.Strings(names)
- for _, name := range names {
- p.doDecl(p.ipkg, name)
+
+ for _, pkg := range pkgs {
+ if pkg.Complete() {
+ continue
+ }
+
+ names := make([]string, 0, len(p.pkgIndex[pkg]))
+ for name := range p.pkgIndex[pkg] {
+ names = append(names, name)
+ }
+ sort.Strings(names)
+ for _, name := range names {
+ p.doDecl(pkg, name)
+ }
+
+ // package was imported completely and without errors
+ pkg.MarkComplete()
}
for _, typ := range p.interfaceList {
typ.Complete()
}
- // record all referenced packages as imports
- list := append(([]*types.Package)(nil), pkgList[1:]...)
- sort.Sort(byPath(list))
- p.ipkg.SetImports(list)
-
- // package was imported completely and without errors
- p.ipkg.MarkComplete()
-
- consumed, _ := r.Seek(0, io.SeekCurrent)
- return int(consumed), p.ipkg, nil
+ return pkgs, nil
}
type iimporter struct {
ipath string
- ipkg *types.Package
version int
stringData []byte
@@ -227,9 +268,6 @@
return pkg
}
path := p.stringAt(off)
- if path == p.ipath {
- return p.ipkg
- }
errorf("missing package %q in %q", path, p.ipath)
return nil
}
diff --git a/go/internal/gcimporter/israce_test.go b/go/internal/gcimporter/israce_test.go
index af8e52b..885ba1c 100644
--- a/go/internal/gcimporter/israce_test.go
+++ b/go/internal/gcimporter/israce_test.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build race
// +build race
package gcimporter_test
diff --git a/go/internal/gcimporter/newInterface10.go b/go/internal/gcimporter/newInterface10.go
index 463f252..8b163e3 100644
--- a/go/internal/gcimporter/newInterface10.go
+++ b/go/internal/gcimporter/newInterface10.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !go1.11
// +build !go1.11
package gcimporter
diff --git a/go/internal/gcimporter/newInterface11.go b/go/internal/gcimporter/newInterface11.go
index ab28b95..49984f4 100644
--- a/go/internal/gcimporter/newInterface11.go
+++ b/go/internal/gcimporter/newInterface11.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.11
// +build go1.11
package gcimporter
diff --git a/go/loader/loader.go b/go/loader/loader.go
index bc12ca3..508a1fd 100644
--- a/go/loader/loader.go
+++ b/go/loader/loader.go
@@ -869,21 +869,6 @@
// caused these imports.
//
func (imp *importer) importAll(fromPath, fromDir string, imports map[string]bool, mode build.ImportMode) (infos []*PackageInfo, errors []importError) {
- // TODO(adonovan): opt: do the loop in parallel once
- // findPackage is non-blocking.
- var pending []*importInfo
- for importPath := range imports {
- bp, err := imp.findPackage(importPath, fromDir, mode)
- if err != nil {
- errors = append(errors, importError{
- path: importPath,
- err: err,
- })
- continue
- }
- pending = append(pending, imp.startLoad(bp))
- }
-
if fromPath != "" {
// We're loading a set of imports.
//
@@ -895,29 +880,36 @@
deps = make(map[string]bool)
imp.graph[fromPath] = deps
}
- for _, ii := range pending {
- deps[ii.path] = true
+ for importPath := range imports {
+ deps[importPath] = true
}
imp.graphMu.Unlock()
}
- for _, ii := range pending {
+ var pending []*importInfo
+ for importPath := range imports {
if fromPath != "" {
- if cycle := imp.findPath(ii.path, fromPath); cycle != nil {
- // Cycle-forming import: we must not await its
- // completion since it would deadlock.
- //
- // We don't record the error in ii since
- // the error is really associated with the
- // cycle-forming edge, not the package itself.
- // (Also it would complicate the
- // invariants of importPath completion.)
+ if cycle := imp.findPath(importPath, fromPath); cycle != nil {
+ // Cycle-forming import: we must not check it
+ // since it would deadlock.
if trace {
fmt.Fprintf(os.Stderr, "import cycle: %q\n", cycle)
}
continue
}
}
+ bp, err := imp.findPackage(importPath, fromDir, mode)
+ if err != nil {
+ errors = append(errors, importError{
+ path: importPath,
+ err: err,
+ })
+ continue
+ }
+ pending = append(pending, imp.startLoad(bp))
+ }
+
+ for _, ii := range pending {
ii.awaitCompletion()
infos = append(infos, ii.info)
}
diff --git a/go/loader/loader_test.go b/go/loader/loader_test.go
index e68405a..e39653c 100644
--- a/go/loader/loader_test.go
+++ b/go/loader/loader_test.go
@@ -4,6 +4,7 @@
// No testdata on Android.
+//go:build !android
// +build !android
package loader_test
diff --git a/go/packages/packagestest/modules_111.go b/go/packages/packagestest/modules_111.go
index 61fa969..4b976f6 100644
--- a/go/packages/packagestest/modules_111.go
+++ b/go/packages/packagestest/modules_111.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.11
// +build go1.11
package packagestest
diff --git a/go/pointer/pointer_test.go b/go/pointer/pointer_test.go
index 07a241b..2f6e069 100644
--- a/go/pointer/pointer_test.go
+++ b/go/pointer/pointer_test.go
@@ -4,6 +4,7 @@
// No testdata on Android.
+//go:build !android
// +build !android
package pointer_test
diff --git a/go/pointer/stdlib_test.go b/go/pointer/stdlib_test.go
index d3ba721..2d5097f 100644
--- a/go/pointer/stdlib_test.go
+++ b/go/pointer/stdlib_test.go
@@ -4,6 +4,7 @@
// Incomplete source tree on Android.
+//go:build !android
// +build !android
package pointer
diff --git a/go/ssa/identical.go b/go/ssa/identical.go
index f3cc8ac..e802696 100644
--- a/go/ssa/identical.go
+++ b/go/ssa/identical.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.8
// +build go1.8
package ssa
diff --git a/go/ssa/identical_17.go b/go/ssa/identical_17.go
index faa124f..575aa5d 100644
--- a/go/ssa/identical_17.go
+++ b/go/ssa/identical_17.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !go1.8
// +build !go1.8
package ssa
diff --git a/go/ssa/identical_test.go b/go/ssa/identical_test.go
index 2fd4ae9..25484a5 100644
--- a/go/ssa/identical_test.go
+++ b/go/ssa/identical_test.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.8
// +build go1.8
package ssa_test
diff --git a/go/ssa/ssautil/switch_test.go b/go/ssa/ssautil/switch_test.go
index a47dbef..bad8bdd 100644
--- a/go/ssa/ssautil/switch_test.go
+++ b/go/ssa/ssautil/switch_test.go
@@ -4,6 +4,7 @@
// No testdata on Android.
+//go:build !android
// +build !android
package ssautil_test
diff --git a/go/ssa/stdlib_test.go b/go/ssa/stdlib_test.go
index 41b87ff..1c358b0 100644
--- a/go/ssa/stdlib_test.go
+++ b/go/ssa/stdlib_test.go
@@ -4,6 +4,7 @@
// Incomplete source tree on Android.
+//go:build !android
// +build !android
package ssa_test
diff --git a/godoc/godoc17_test.go b/godoc/godoc17_test.go
index d153991..82e23e6 100644
--- a/godoc/godoc17_test.go
+++ b/godoc/godoc17_test.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.7
// +build go1.7
package godoc
diff --git a/godoc/static/makestatic.go b/godoc/static/makestatic.go
index 0f910f0..ef7b904 100644
--- a/godoc/static/makestatic.go
+++ b/godoc/static/makestatic.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build ignore
// +build ignore
// Command makestatic writes the generated file buffer to "static.go".
diff --git a/godoc/vfs/fs.go b/godoc/vfs/fs.go
index b033666..f12d653 100644
--- a/godoc/vfs/fs.go
+++ b/godoc/vfs/fs.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.16
// +build go1.16
package vfs
diff --git a/gopls/go.mod b/gopls/go.mod
index 2bf051d..a38cac5 100644
--- a/gopls/go.mod
+++ b/gopls/go.mod
@@ -14,3 +14,5 @@
mvdan.cc/gofumpt v0.1.0
mvdan.cc/xurls/v2 v2.2.0
)
+
+replace golang.org/x/tools => ../
diff --git a/gopls/internal/hooks/analysis.go b/gopls/internal/hooks/analysis.go
index 6bab2be..23d4ab6 100644
--- a/gopls/internal/hooks/analysis.go
+++ b/gopls/internal/hooks/analysis.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.15
// +build go1.15
package hooks
diff --git a/gopls/internal/hooks/analysis_115.go b/gopls/internal/hooks/analysis_115.go
index ffb01a4..187e522 100644
--- a/gopls/internal/hooks/analysis_115.go
+++ b/gopls/internal/hooks/analysis_115.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !go1.15
// +build !go1.15
package hooks
diff --git a/gopls/internal/regtest/diagnostics/diagnostics_test.go b/gopls/internal/regtest/diagnostics/diagnostics_test.go
index 3a73152..84b8b3d 100644
--- a/gopls/internal/regtest/diagnostics/diagnostics_test.go
+++ b/gopls/internal/regtest/diagnostics/diagnostics_test.go
@@ -16,7 +16,6 @@
"golang.org/x/tools/internal/lsp"
"golang.org/x/tools/internal/lsp/fake"
"golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/tests"
"golang.org/x/tools/internal/testenv"
)
@@ -526,7 +525,6 @@
`
Run(t, generated, func(t *testing.T, env *Env) {
env.OpenFile("main.go")
- original := env.ReadWorkspaceFile("main.go")
var d protocol.PublishDiagnosticsParams
env.Await(
OnceMet(
@@ -534,12 +532,8 @@
ReadDiagnostics("main.go", &d),
),
)
- // Apply fixes and save the buffer.
- env.ApplyQuickFixes("main.go", d.Diagnostics)
- env.SaveBuffer("main.go")
- fixed := env.ReadWorkspaceFile("main.go")
- if original != fixed {
- t.Fatalf("generated file was changed by quick fixes:\n%s", tests.Diff(t, original, fixed))
+ if fixes := env.GetQuickFixes("main.go", d.Diagnostics); len(fixes) != 0 {
+ t.Errorf("got quick fixes %v, wanted none", fixes)
}
})
}
diff --git a/gopls/internal/regtest/expectation.go b/gopls/internal/regtest/expectation.go
index e520099..02e00dd 100644
--- a/gopls/internal/regtest/expectation.go
+++ b/gopls/internal/regtest/expectation.go
@@ -12,6 +12,7 @@
"golang.org/x/tools/internal/lsp"
"golang.org/x/tools/internal/lsp/fake"
"golang.org/x/tools/internal/lsp/protocol"
+ "golang.org/x/tools/internal/testenv"
)
// An Expectation asserts that the state of the editor at a point in time
@@ -580,3 +581,16 @@
func NoDiagnosticWithMessage(name, msg string) DiagnosticExpectation {
return DiagnosticExpectation{path: name, message: msg, present: false}
}
+
+// GoSum asserts that a "go.sum is out of sync" diagnostic for the given module
+// (as formatted in a go.mod file, e.g. "example.com v1.0.0") is present.
+func (e *Env) GoSumDiagnostic(name, module string) Expectation {
+ e.T.Helper()
+ // In 1.16, go.sum diagnostics should appear on the relevant module. Earlier
+ // errors have no information and appear on the module declaration.
+ if testenv.Go1Point() >= 16 {
+ return e.DiagnosticAtRegexpWithMessage(name, module, "go.sum is out of sync")
+ } else {
+ return e.DiagnosticAtRegexpWithMessage(name, `module`, "go.sum is out of sync")
+ }
+}
diff --git a/gopls/internal/regtest/misc/generate_test.go b/gopls/internal/regtest/misc/generate_test.go
index 6987924..a7631d9 100644
--- a/gopls/internal/regtest/misc/generate_test.go
+++ b/gopls/internal/regtest/misc/generate_test.go
@@ -4,6 +4,7 @@
// TODO(rfindley): figure out why go generate fails on android builders.
+//go:build !android
// +build !android
package misc
diff --git a/gopls/internal/regtest/modfile/modfile_test.go b/gopls/internal/regtest/modfile/modfile_test.go
index cf7dad1..33b65fe 100644
--- a/gopls/internal/regtest/modfile/modfile_test.go
+++ b/gopls/internal/regtest/modfile/modfile_test.go
@@ -548,6 +548,7 @@
d := protocol.PublishDiagnosticsParams{}
env.Await(
OnceMet(
+ // Make sure the diagnostic mentions the new version -- the old diagnostic is in the same place.
env.DiagnosticAtRegexpWithMessage("a/go.mod", "example.com v1.2.3", "example.com@v1.2.3"),
ReadDiagnostics("a/go.mod", &d),
),
@@ -822,7 +823,7 @@
env.OpenFile("go.mod")
env.Await(
OnceMet(
- DiagnosticAt("go.mod", 0, 0),
+ env.GoSumDiagnostic("go.mod", `example.com v1.2.3`),
ReadDiagnostics("go.mod", d),
),
)
@@ -1001,9 +1002,7 @@
}
func TestSumUpdateQuickFix(t *testing.T) {
- // Error messages changed in 1.16 that changed the diagnostic positions.
- testenv.NeedsGo1Point(t, 16)
-
+ testenv.NeedsGo1Point(t, 14)
const mod = `
-- go.mod --
module mod.com
@@ -1030,22 +1029,14 @@
Modes(Singleton),
).Run(t, mod, func(t *testing.T, env *Env) {
env.OpenFile("go.mod")
- pos := env.RegexpSearch("go.mod", "example.com")
params := &protocol.PublishDiagnosticsParams{}
env.Await(
OnceMet(
- env.DiagnosticAtRegexp("go.mod", "example.com"),
+ env.GoSumDiagnostic("go.mod", "example.com"),
ReadDiagnostics("go.mod", params),
),
)
- var diagnostic protocol.Diagnostic
- for _, d := range params.Diagnostics {
- if d.Range.Start.Line == uint32(pos.Line) {
- diagnostic = d
- break
- }
- }
- env.ApplyQuickFixes("go.mod", []protocol.Diagnostic{diagnostic})
+ env.ApplyQuickFixes("go.mod", params.Diagnostics)
const want = `example.com v1.2.3 h1:Yryq11hF02fEf2JlOS2eph+ICE2/ceevGV3C9dl5V/c=
example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo=
`
diff --git a/gopls/internal/regtest/workspace/workspace_test.go b/gopls/internal/regtest/workspace/workspace_test.go
index 8712784..f0128d8 100644
--- a/gopls/internal/regtest/workspace/workspace_test.go
+++ b/gopls/internal/regtest/workspace/workspace_test.go
@@ -818,25 +818,25 @@
Modes(Experimental),
).Run(t, mod, func(t *testing.T, env *Env) {
params := &protocol.PublishDiagnosticsParams{}
- env.OpenFile("a/go.mod")
+ env.OpenFile("b/go.mod")
env.Await(
- ReadDiagnostics("a/go.mod", params),
+ OnceMet(
+ env.GoSumDiagnostic("b/go.mod", `example.com v1.2.3`),
+ ReadDiagnostics("b/go.mod", params),
+ ),
)
for _, d := range params.Diagnostics {
- if d.Message != `go.sum is out of sync with go.mod. Please update it by applying the quick fix.` {
+ if !strings.Contains(d.Message, "go.sum is out of sync") {
continue
}
- actions, err := env.Editor.GetQuickFixes(env.Ctx, "a/go.mod", nil, []protocol.Diagnostic{d})
- if err != nil {
- t.Fatal(err)
- }
+ actions := env.GetQuickFixes("b/go.mod", []protocol.Diagnostic{d})
if len(actions) != 2 {
t.Fatalf("expected 2 code actions, got %v", len(actions))
}
- env.ApplyQuickFixes("a/go.mod", []protocol.Diagnostic{d})
+ env.ApplyQuickFixes("b/go.mod", []protocol.Diagnostic{d})
}
env.Await(
- EmptyDiagnostics("a/go.mod"),
+ EmptyDiagnostics("b/go.mod"),
)
})
}
diff --git a/gopls/internal/regtest/wrappers.go b/gopls/internal/regtest/wrappers.go
index fa9367e..c77de5b 100644
--- a/gopls/internal/regtest/wrappers.go
+++ b/gopls/internal/regtest/wrappers.go
@@ -200,6 +200,16 @@
}
}
+// GetQuickFixes returns the available quick fix code actions.
+func (e *Env) GetQuickFixes(path string, diagnostics []protocol.Diagnostic) []protocol.CodeAction {
+ e.T.Helper()
+ actions, err := e.Editor.GetQuickFixes(e.Ctx, path, nil, diagnostics)
+ if err != nil {
+ e.T.Fatal(err)
+ }
+ return actions
+}
+
// Hover in the editor, calling t.Fatal on any error.
func (e *Env) Hover(name string, pos fake.Pos) (*protocol.MarkupContent, fake.Pos) {
e.T.Helper()
@@ -265,6 +275,8 @@
}
}
+// DumpGoSum prints the correct go.sum contents for dir in txtar format,
+// for use in creating regtests.
func (e *Env) DumpGoSum(dir string) {
e.T.Helper()
diff --git a/internal/fastwalk/fastwalk_dirent_fileno.go b/internal/fastwalk/fastwalk_dirent_fileno.go
index ccffec5..d58595d 100644
--- a/internal/fastwalk/fastwalk_dirent_fileno.go
+++ b/internal/fastwalk/fastwalk_dirent_fileno.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build freebsd || openbsd || netbsd
// +build freebsd openbsd netbsd
package fastwalk
diff --git a/internal/fastwalk/fastwalk_dirent_ino.go b/internal/fastwalk/fastwalk_dirent_ino.go
index ab7fbc0..ea02b9e 100644
--- a/internal/fastwalk/fastwalk_dirent_ino.go
+++ b/internal/fastwalk/fastwalk_dirent_ino.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build (linux || darwin) && !appengine
// +build linux darwin
// +build !appengine
diff --git a/internal/fastwalk/fastwalk_dirent_namlen_bsd.go b/internal/fastwalk/fastwalk_dirent_namlen_bsd.go
index a3b26a7..d5c9c32 100644
--- a/internal/fastwalk/fastwalk_dirent_namlen_bsd.go
+++ b/internal/fastwalk/fastwalk_dirent_namlen_bsd.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build darwin || freebsd || openbsd || netbsd
// +build darwin freebsd openbsd netbsd
package fastwalk
diff --git a/internal/fastwalk/fastwalk_dirent_namlen_linux.go b/internal/fastwalk/fastwalk_dirent_namlen_linux.go
index e880d35..c82e57d 100644
--- a/internal/fastwalk/fastwalk_dirent_namlen_linux.go
+++ b/internal/fastwalk/fastwalk_dirent_namlen_linux.go
@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build linux
-// +build !appengine
+//go:build linux && !appengine
+// +build linux,!appengine
package fastwalk
diff --git a/internal/fastwalk/fastwalk_portable.go b/internal/fastwalk/fastwalk_portable.go
index b0d6327..085d311 100644
--- a/internal/fastwalk/fastwalk_portable.go
+++ b/internal/fastwalk/fastwalk_portable.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build appengine || (!linux && !darwin && !freebsd && !openbsd && !netbsd)
// +build appengine !linux,!darwin,!freebsd,!openbsd,!netbsd
package fastwalk
diff --git a/internal/fastwalk/fastwalk_unix.go b/internal/fastwalk/fastwalk_unix.go
index 5901a8f..e4edb5c 100644
--- a/internal/fastwalk/fastwalk_unix.go
+++ b/internal/fastwalk/fastwalk_unix.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build (linux || darwin || freebsd || openbsd || netbsd) && !appengine
// +build linux darwin freebsd openbsd netbsd
// +build !appengine
diff --git a/internal/imports/mkindex.go b/internal/imports/mkindex.go
index ef8c0d2..36a532b 100644
--- a/internal/imports/mkindex.go
+++ b/internal/imports/mkindex.go
@@ -1,3 +1,4 @@
+//go:build ignore
// +build ignore
// Copyright 2013 The Go Authors. All rights reserved.
diff --git a/internal/imports/mkstdlib.go b/internal/imports/mkstdlib.go
index cf0fc49..f5ea292 100644
--- a/internal/imports/mkstdlib.go
+++ b/internal/imports/mkstdlib.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build ignore
// +build ignore
// mkstdlib generates the zstdlib.go file, containing the Go standard
@@ -14,7 +15,6 @@
"bytes"
"fmt"
"go/format"
- exec "golang.org/x/sys/execabs"
"io"
"io/ioutil"
"log"
@@ -23,6 +23,8 @@
"regexp"
"runtime"
"sort"
+
+ exec "golang.org/x/sys/execabs"
)
func mustOpen(name string) io.Reader {
@@ -66,6 +68,7 @@
mustOpen(api("go1.13.txt")),
mustOpen(api("go1.14.txt")),
mustOpen(api("go1.15.txt")),
+ mustOpen(api("go1.16.txt")),
// The API of the syscall/js package needs to be computed explicitly,
// because it's not included in the GOROOT/api/go1.*.txt files at this time.
diff --git a/internal/imports/zstdlib.go b/internal/imports/zstdlib.go
index 7b573b9..ccdd4e0 100644
--- a/internal/imports/zstdlib.go
+++ b/internal/imports/zstdlib.go
@@ -974,13 +974,29 @@
"DF_STATIC_TLS",
"DF_SYMBOLIC",
"DF_TEXTREL",
+ "DT_ADDRRNGHI",
+ "DT_ADDRRNGLO",
+ "DT_AUDIT",
+ "DT_AUXILIARY",
"DT_BIND_NOW",
+ "DT_CHECKSUM",
+ "DT_CONFIG",
"DT_DEBUG",
+ "DT_DEPAUDIT",
"DT_ENCODING",
+ "DT_FEATURE",
+ "DT_FILTER",
"DT_FINI",
"DT_FINI_ARRAY",
"DT_FINI_ARRAYSZ",
"DT_FLAGS",
+ "DT_FLAGS_1",
+ "DT_GNU_CONFLICT",
+ "DT_GNU_CONFLICTSZ",
+ "DT_GNU_HASH",
+ "DT_GNU_LIBLIST",
+ "DT_GNU_LIBLISTSZ",
+ "DT_GNU_PRELINKED",
"DT_HASH",
"DT_HIOS",
"DT_HIPROC",
@@ -990,28 +1006,100 @@
"DT_JMPREL",
"DT_LOOS",
"DT_LOPROC",
+ "DT_MIPS_AUX_DYNAMIC",
+ "DT_MIPS_BASE_ADDRESS",
+ "DT_MIPS_COMPACT_SIZE",
+ "DT_MIPS_CONFLICT",
+ "DT_MIPS_CONFLICTNO",
+ "DT_MIPS_CXX_FLAGS",
+ "DT_MIPS_DELTA_CLASS",
+ "DT_MIPS_DELTA_CLASSSYM",
+ "DT_MIPS_DELTA_CLASSSYM_NO",
+ "DT_MIPS_DELTA_CLASS_NO",
+ "DT_MIPS_DELTA_INSTANCE",
+ "DT_MIPS_DELTA_INSTANCE_NO",
+ "DT_MIPS_DELTA_RELOC",
+ "DT_MIPS_DELTA_RELOC_NO",
+ "DT_MIPS_DELTA_SYM",
+ "DT_MIPS_DELTA_SYM_NO",
+ "DT_MIPS_DYNSTR_ALIGN",
+ "DT_MIPS_FLAGS",
+ "DT_MIPS_GOTSYM",
+ "DT_MIPS_GP_VALUE",
+ "DT_MIPS_HIDDEN_GOTIDX",
+ "DT_MIPS_HIPAGENO",
+ "DT_MIPS_ICHECKSUM",
+ "DT_MIPS_INTERFACE",
+ "DT_MIPS_INTERFACE_SIZE",
+ "DT_MIPS_IVERSION",
+ "DT_MIPS_LIBLIST",
+ "DT_MIPS_LIBLISTNO",
+ "DT_MIPS_LOCALPAGE_GOTIDX",
+ "DT_MIPS_LOCAL_GOTIDX",
+ "DT_MIPS_LOCAL_GOTNO",
+ "DT_MIPS_MSYM",
+ "DT_MIPS_OPTIONS",
+ "DT_MIPS_PERF_SUFFIX",
+ "DT_MIPS_PIXIE_INIT",
+ "DT_MIPS_PLTGOT",
+ "DT_MIPS_PROTECTED_GOTIDX",
+ "DT_MIPS_RLD_MAP",
+ "DT_MIPS_RLD_MAP_REL",
+ "DT_MIPS_RLD_TEXT_RESOLVE_ADDR",
+ "DT_MIPS_RLD_VERSION",
+ "DT_MIPS_RWPLT",
+ "DT_MIPS_SYMBOL_LIB",
+ "DT_MIPS_SYMTABNO",
+ "DT_MIPS_TIME_STAMP",
+ "DT_MIPS_UNREFEXTNO",
+ "DT_MOVEENT",
+ "DT_MOVESZ",
+ "DT_MOVETAB",
"DT_NEEDED",
"DT_NULL",
"DT_PLTGOT",
+ "DT_PLTPAD",
+ "DT_PLTPADSZ",
"DT_PLTREL",
"DT_PLTRELSZ",
+ "DT_POSFLAG_1",
+ "DT_PPC64_GLINK",
+ "DT_PPC64_OPD",
+ "DT_PPC64_OPDSZ",
+ "DT_PPC64_OPT",
+ "DT_PPC_GOT",
+ "DT_PPC_OPT",
"DT_PREINIT_ARRAY",
"DT_PREINIT_ARRAYSZ",
"DT_REL",
"DT_RELA",
+ "DT_RELACOUNT",
"DT_RELAENT",
"DT_RELASZ",
+ "DT_RELCOUNT",
"DT_RELENT",
"DT_RELSZ",
"DT_RPATH",
"DT_RUNPATH",
"DT_SONAME",
+ "DT_SPARC_REGISTER",
"DT_STRSZ",
"DT_STRTAB",
"DT_SYMBOLIC",
"DT_SYMENT",
+ "DT_SYMINENT",
+ "DT_SYMINFO",
+ "DT_SYMINSZ",
"DT_SYMTAB",
+ "DT_SYMTAB_SHNDX",
"DT_TEXTREL",
+ "DT_TLSDESC_GOT",
+ "DT_TLSDESC_PLT",
+ "DT_USED",
+ "DT_VALRNGHI",
+ "DT_VALRNGLO",
+ "DT_VERDEF",
+ "DT_VERDEFNUM",
"DT_VERNEED",
"DT_VERNEEDNUM",
"DT_VERSYM",
@@ -1271,17 +1359,38 @@
"PF_R",
"PF_W",
"PF_X",
+ "PT_AARCH64_ARCHEXT",
+ "PT_AARCH64_UNWIND",
+ "PT_ARM_ARCHEXT",
+ "PT_ARM_EXIDX",
"PT_DYNAMIC",
+ "PT_GNU_EH_FRAME",
+ "PT_GNU_MBIND_HI",
+ "PT_GNU_MBIND_LO",
+ "PT_GNU_PROPERTY",
+ "PT_GNU_RELRO",
+ "PT_GNU_STACK",
"PT_HIOS",
"PT_HIPROC",
"PT_INTERP",
"PT_LOAD",
"PT_LOOS",
"PT_LOPROC",
+ "PT_MIPS_ABIFLAGS",
+ "PT_MIPS_OPTIONS",
+ "PT_MIPS_REGINFO",
+ "PT_MIPS_RTPROC",
"PT_NOTE",
"PT_NULL",
+ "PT_OPENBSD_BOOTDATA",
+ "PT_OPENBSD_RANDOMIZE",
+ "PT_OPENBSD_WXNEEDED",
+ "PT_PAX_FLAGS",
"PT_PHDR",
+ "PT_S390_PGSTE",
"PT_SHLIB",
+ "PT_SUNWSTACK",
+ "PT_SUNW_EH_FRAME",
"PT_TLS",
"Prog",
"Prog32",
@@ -2445,6 +2554,9 @@
"SectionHeader",
"Sym",
},
+ "embed": []string{
+ "FS",
+ },
"encoding": []string{
"BinaryMarshaler",
"BinaryUnmarshaler",
@@ -2680,6 +2792,7 @@
"FlagSet",
"Float64",
"Float64Var",
+ "Func",
"Getter",
"Int",
"Int64",
@@ -2853,6 +2966,18 @@
"Package",
"ToolDir",
},
+ "go/build/constraint": []string{
+ "AndExpr",
+ "Expr",
+ "IsGoBuild",
+ "IsPlusBuild",
+ "NotExpr",
+ "OrExpr",
+ "Parse",
+ "PlusBuildLines",
+ "SyntaxError",
+ "TagExpr",
+ },
"go/constant": []string{
"BinaryOp",
"BitLen",
@@ -3273,6 +3398,7 @@
"Must",
"New",
"OK",
+ "ParseFS",
"ParseFiles",
"ParseGlob",
"Srcset",
@@ -3432,6 +3558,7 @@
"Copy",
"CopyBuffer",
"CopyN",
+ "Discard",
"EOF",
"ErrClosedPipe",
"ErrNoProgress",
@@ -3443,12 +3570,15 @@
"MultiReader",
"MultiWriter",
"NewSectionReader",
+ "NopCloser",
"Pipe",
"PipeReader",
"PipeWriter",
+ "ReadAll",
"ReadAtLeast",
"ReadCloser",
"ReadFull",
+ "ReadSeekCloser",
"ReadSeeker",
"ReadWriteCloser",
"ReadWriteSeeker",
@@ -3472,6 +3602,49 @@
"WriterAt",
"WriterTo",
},
+ "io/fs": []string{
+ "DirEntry",
+ "ErrClosed",
+ "ErrExist",
+ "ErrInvalid",
+ "ErrNotExist",
+ "ErrPermission",
+ "FS",
+ "File",
+ "FileInfo",
+ "FileMode",
+ "Glob",
+ "GlobFS",
+ "ModeAppend",
+ "ModeCharDevice",
+ "ModeDevice",
+ "ModeDir",
+ "ModeExclusive",
+ "ModeIrregular",
+ "ModeNamedPipe",
+ "ModePerm",
+ "ModeSetgid",
+ "ModeSetuid",
+ "ModeSocket",
+ "ModeSticky",
+ "ModeSymlink",
+ "ModeTemporary",
+ "ModeType",
+ "PathError",
+ "ReadDir",
+ "ReadDirFS",
+ "ReadDirFile",
+ "ReadFile",
+ "ReadFileFS",
+ "SkipDir",
+ "Stat",
+ "StatFS",
+ "Sub",
+ "SubFS",
+ "ValidPath",
+ "WalkDir",
+ "WalkDirFunc",
+ },
"io/ioutil": []string{
"Discard",
"NopCloser",
@@ -3483,6 +3656,7 @@
"WriteFile",
},
"log": []string{
+ "Default",
"Fatal",
"Fatalf",
"Fatalln",
@@ -3819,6 +3993,7 @@
"DialUDP",
"DialUnix",
"Dialer",
+ "ErrClosed",
"ErrWriteToConnected",
"Error",
"FileConn",
@@ -3938,6 +4113,7 @@
"ErrUseLastResponse",
"ErrWriteAfterFlush",
"Error",
+ "FS",
"File",
"FileServer",
"FileSystem",
@@ -4234,7 +4410,10 @@
"Chtimes",
"Clearenv",
"Create",
+ "CreateTemp",
"DevNull",
+ "DirEntry",
+ "DirFS",
"Environ",
"ErrClosed",
"ErrDeadlineExceeded",
@@ -4243,6 +4422,7 @@
"ErrNoDeadline",
"ErrNotExist",
"ErrPermission",
+ "ErrProcessDone",
"Executable",
"Exit",
"Expand",
@@ -4276,6 +4456,7 @@
"Lstat",
"Mkdir",
"MkdirAll",
+ "MkdirTemp",
"ModeAppend",
"ModeCharDevice",
"ModeDevice",
@@ -4310,6 +4491,8 @@
"ProcAttr",
"Process",
"ProcessState",
+ "ReadDir",
+ "ReadFile",
"Readlink",
"Remove",
"RemoveAll",
@@ -4333,6 +4516,7 @@
"UserCacheDir",
"UserConfigDir",
"UserHomeDir",
+ "WriteFile",
},
"os/exec": []string{
"Cmd",
@@ -4347,6 +4531,7 @@
"Ignore",
"Ignored",
"Notify",
+ "NotifyContext",
"Reset",
"Stop",
},
@@ -4397,6 +4582,7 @@
"ToSlash",
"VolumeName",
"Walk",
+ "WalkDir",
"WalkFunc",
},
"plugin": []string{
@@ -4629,6 +4815,19 @@
"Stack",
"WriteHeapDump",
},
+ "runtime/metrics": []string{
+ "All",
+ "Description",
+ "Float64Histogram",
+ "KindBad",
+ "KindFloat64",
+ "KindFloat64Histogram",
+ "KindUint64",
+ "Read",
+ "Sample",
+ "Value",
+ "ValueKind",
+ },
"runtime/pprof": []string{
"Do",
"ForLabels",
@@ -5012,6 +5211,8 @@
"AddrinfoW",
"Adjtime",
"Adjtimex",
+ "AllThreadsSyscall",
+ "AllThreadsSyscall6",
"AttachLsf",
"B0",
"B1000000",
@@ -10010,13 +10211,20 @@
"TB",
"Verbose",
},
+ "testing/fstest": []string{
+ "MapFS",
+ "MapFile",
+ "TestFS",
+ },
"testing/iotest": []string{
"DataErrReader",
+ "ErrReader",
"ErrTimeout",
"HalfReader",
"NewReadLogger",
"NewWriteLogger",
"OneByteReader",
+ "TestReader",
"TimeoutReader",
"TruncateWriter",
},
@@ -10076,6 +10284,7 @@
"JSEscaper",
"Must",
"New",
+ "ParseFS",
"ParseFiles",
"ParseGlob",
"Template",
@@ -10087,12 +10296,14 @@
"BranchNode",
"ChainNode",
"CommandNode",
+ "CommentNode",
"DotNode",
"FieldNode",
"IdentifierNode",
"IfNode",
"IsEmptyTree",
"ListNode",
+ "Mode",
"New",
"NewIdentifier",
"NilNode",
@@ -10101,6 +10312,7 @@
"NodeBool",
"NodeChain",
"NodeCommand",
+ "NodeComment",
"NodeDot",
"NodeField",
"NodeIdentifier",
@@ -10118,6 +10330,7 @@
"NodeWith",
"NumberNode",
"Parse",
+ "ParseComments",
"PipeNode",
"Pos",
"RangeNode",
@@ -10230,6 +10443,7 @@
"Chakma",
"Cham",
"Cherokee",
+ "Chorasmian",
"Co",
"Common",
"Coptic",
@@ -10243,6 +10457,7 @@
"Devanagari",
"Diacritic",
"Digit",
+ "Dives_Akuru",
"Dogra",
"Duployan",
"Egyptian_Hieroglyphs",
@@ -10300,6 +10515,7 @@
"Katakana",
"Kayah_Li",
"Kharoshthi",
+ "Khitan_Small_Script",
"Khmer",
"Khojki",
"Khudawadi",
@@ -10472,6 +10688,7 @@
"Wancho",
"Warang_Citi",
"White_Space",
+ "Yezidi",
"Yi",
"Z",
"Zanabazar_Square",
diff --git a/internal/lsp/cache/errors.go b/internal/lsp/cache/errors.go
index 529a420..148fbda 100644
--- a/internal/lsp/cache/errors.go
+++ b/internal/lsp/cache/errors.go
@@ -14,6 +14,8 @@
"strconv"
"strings"
+ errors "golang.org/x/xerrors"
+
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/analysisinternal"
@@ -23,7 +25,6 @@
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/typesinternal"
- errors "golang.org/x/xerrors"
)
func sourceDiagnostics(ctx context.Context, snapshot *snapshot, pkg *pkg, severity protocol.DiagnosticSeverity, e interface{}) ([]*source.Diagnostic, error) {
@@ -167,7 +168,7 @@
func typesCodeHref(snapshot *snapshot, code typesinternal.ErrorCode) string {
target := snapshot.View().Options().LinkTarget
- return fmt.Sprintf("%s/golang.org/x/tools/internal/typesinternal#%s", target, code.String())
+ return fmt.Sprintf("https://%s/golang.org/x/tools/internal/typesinternal#%s", target, code.String())
}
func suggestedAnalysisFixes(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic) ([]source.SuggestedFix, error) {
diff --git a/internal/lsp/cache/mod.go b/internal/lsp/cache/mod.go
index 9112bfd..2e1917a 100644
--- a/internal/lsp/cache/mod.go
+++ b/internal/lsp/cache/mod.go
@@ -60,13 +60,26 @@
Converter: span.NewContentConverter(modFH.URI().Filename(), contents),
Content: contents,
}
- file, err := modfile.Parse(modFH.URI().Filename(), contents, nil)
-
+ file, parseErr := modfile.Parse(modFH.URI().Filename(), contents, nil)
// Attempt to convert the error to a standardized parse error.
var parseErrors []*source.Diagnostic
- if err != nil {
- if parseErr := extractErrorWithPosition(ctx, err.Error(), s); parseErr != nil {
- parseErrors = []*source.Diagnostic{parseErr}
+ if parseErr != nil {
+ mfErrList, ok := parseErr.(modfile.ErrorList)
+ if !ok {
+ return &parseModData{err: fmt.Errorf("unexpected parse error type %v", parseErr)}
+ }
+ for _, mfErr := range mfErrList {
+ rng, err := rangeFromPositions(m, mfErr.Pos, mfErr.Pos)
+ if err != nil {
+ return &parseModData{err: err}
+ }
+ parseErrors = []*source.Diagnostic{{
+ URI: modFH.URI(),
+ Range: rng,
+ Severity: protocol.SeverityError,
+ Source: source.ParseError,
+ Message: mfErr.Err.Error(),
+ }}
}
}
return &parseModData{
@@ -76,7 +89,7 @@
File: file,
ParseErrors: parseErrors,
},
- err: err,
+ err: parseErr,
}
}, nil)
@@ -214,46 +227,67 @@
// extractGoCommandError tries to parse errors that come from the go command
// and shape them into go.mod diagnostics.
-func (s *snapshot) extractGoCommandErrors(ctx context.Context, snapshot source.Snapshot, goCmdError string) []*source.Diagnostic {
- var srcErrs []*source.Diagnostic
- if srcErr := s.parseModError(ctx, goCmdError); srcErr != nil {
- srcErrs = append(srcErrs, srcErr...)
+func (s *snapshot) extractGoCommandErrors(ctx context.Context, goCmdError string) ([]*source.Diagnostic, error) {
+ diagLocations := map[*source.ParsedModule]span.Span{}
+ backupDiagLocations := map[*source.ParsedModule]span.Span{}
+
+ // The go command emits parse errors for completely invalid go.mod files.
+ // Those are reported by our own diagnostics and can be ignored here.
+ // As of writing, we are not aware of any other errors that include
+ // file/position information, so don't even try to find it.
+ if strings.Contains(goCmdError, "errors parsing go.mod") {
+ return nil, nil
}
- if srcErr := extractErrorWithPosition(ctx, goCmdError, s); srcErr != nil {
- srcErrs = append(srcErrs, srcErr)
- } else {
- for _, uri := range s.ModFiles() {
- fh, err := s.GetFile(ctx, uri)
- if err != nil {
- continue
- }
- if srcErr := s.matchErrorToModule(ctx, fh, goCmdError); srcErr != nil {
- srcErrs = append(srcErrs, srcErr)
- }
+
+ // Match the error against all the mod files in the workspace.
+ for _, uri := range s.ModFiles() {
+ fh, err := s.GetFile(ctx, uri)
+ if err != nil {
+ return nil, err
+ }
+ pm, err := s.ParseMod(ctx, fh)
+ if err != nil {
+ return nil, err
+ }
+ spn, found, err := s.matchErrorToModule(ctx, pm, goCmdError)
+ if err != nil {
+ return nil, err
+ }
+ if found {
+ diagLocations[pm] = spn
+ } else {
+ backupDiagLocations[pm] = spn
}
}
- return srcErrs
+
+ // If we didn't find any good matches, assign diagnostics to all go.mod files.
+ if len(diagLocations) == 0 {
+ diagLocations = backupDiagLocations
+ }
+
+ var srcErrs []*source.Diagnostic
+ for pm, spn := range diagLocations {
+ diag, err := s.goCommandDiagnostic(pm, spn, goCmdError)
+ if err != nil {
+ return nil, err
+ }
+ srcErrs = append(srcErrs, diag)
+ }
+ return srcErrs, nil
}
var moduleVersionInErrorRe = regexp.MustCompile(`[:\s]([+-._~0-9A-Za-z]+)@([+-._~0-9A-Za-z]+)[:\s]`)
-// matchErrorToModule attempts to match module version in error messages.
+// matchErrorToModule matches a go command error message to a go.mod file.
// Some examples:
//
// example.com@v1.2.2: reading example.com/@v/v1.2.2.mod: no such file or directory
// go: github.com/cockroachdb/apd/v2@v2.0.72: reading github.com/cockroachdb/apd/go.mod at revision v2.0.72: unknown revision v2.0.72
// go: example.com@v1.2.3 requires\n\trandom.org@v1.2.3: parsing go.mod:\n\tmodule declares its path as: bob.org\n\tbut was required as: random.org
//
-// We search for module@version, starting from the end to find the most
-// relevant module, e.g. random.org@v1.2.3 above. Then we associate the error
-// with a directive that references any of the modules mentioned.
-func (s *snapshot) matchErrorToModule(ctx context.Context, fh source.FileHandle, goCmdError string) *source.Diagnostic {
- pm, err := s.ParseMod(ctx, fh)
- if err != nil {
- return nil
- }
-
- var innermost *module.Version
+// It returns the location of a reference to the one of the modules and true
+// if one exists. If none is found it returns a fallback location and false.
+func (s *snapshot) matchErrorToModule(ctx context.Context, pm *source.ParsedModule, goCmdError string) (span.Span, bool, error) {
var reference *modfile.Line
matches := moduleVersionInErrorRe.FindAllStringSubmatch(goCmdError, -1)
@@ -267,9 +301,6 @@
if err := module.Check(ver.Path, ver.Version); err != nil {
continue
}
- if innermost == nil {
- innermost = &ver
- }
reference = findModuleReference(pm.File, ver)
if reference != nil {
break
@@ -278,52 +309,112 @@
if reference == nil {
// No match for the module path was found in the go.mod file.
- // Show the error on the module declaration, if one exists.
+ // Show the error on the module declaration, if one exists, or
+ // just the first line of the file.
if pm.File.Module == nil {
- return nil
+ return span.New(pm.URI, span.NewPoint(1, 1, 0), span.Point{}), false, nil
}
- reference = pm.File.Module.Syntax
+ spn, err := spanFromPositions(pm.Mapper, pm.File.Module.Syntax.Start, pm.File.Module.Syntax.End)
+ return spn, false, err
}
- rng, err := rangeFromPositions(pm.Mapper, reference.Start, reference.End)
+ spn, err := spanFromPositions(pm.Mapper, reference.Start, reference.End)
+ return spn, true, err
+}
+
+// goCommandDiagnostic creates a diagnostic for a given go command error.
+func (s *snapshot) goCommandDiagnostic(pm *source.ParsedModule, spn span.Span, goCmdError string) (*source.Diagnostic, error) {
+ rng, err := pm.Mapper.Range(spn)
if err != nil {
- return nil
+ return nil, err
}
- disabledByGOPROXY := strings.Contains(goCmdError, "disabled by GOPROXY=off")
- shouldAddDep := strings.Contains(goCmdError, "to add it")
- if innermost != nil && (disabledByGOPROXY || shouldAddDep) {
+
+ matches := moduleVersionInErrorRe.FindAllStringSubmatch(goCmdError, -1)
+ var innermost *module.Version
+ for i := len(matches) - 1; i >= 0; i-- {
+ ver := module.Version{Path: matches[i][1], Version: matches[i][2]}
+ // Any module versions that come from the workspace module should not
+ // be shown to the user.
+ if source.IsWorkspaceModuleVersion(ver.Version) {
+ continue
+ }
+ if err := module.Check(ver.Path, ver.Version); err != nil {
+ continue
+ }
+ innermost = &ver
+ break
+ }
+
+ switch {
+ case strings.Contains(goCmdError, "inconsistent vendoring"):
+ cmd, err := command.NewVendorCommand("Run go mod vendor", command.URIArg{URI: protocol.URIFromSpanURI(pm.URI)})
+ if err != nil {
+ return nil, err
+ }
+ return &source.Diagnostic{
+ URI: pm.URI,
+ Range: rng,
+ Severity: protocol.SeverityError,
+ Source: source.ListError,
+ Message: `Inconsistent vendoring detected. Please re-run "go mod vendor".
+See https://github.com/golang/go/issues/39164 for more detail on this issue.`,
+ SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd)},
+ }, nil
+
+ case strings.Contains(goCmdError, "updates to go.sum needed"), strings.Contains(goCmdError, "missing go.sum entry"):
+ var args []protocol.DocumentURI
+ for _, uri := range s.ModFiles() {
+ args = append(args, protocol.URIFromSpanURI(uri))
+ }
+ tidyCmd, err := command.NewTidyCommand("Run go mod tidy", command.URIArgs{URIs: args})
+ if err != nil {
+ return nil, err
+ }
+ updateCmd, err := command.NewUpdateGoSumCommand("Update go.sum", command.URIArgs{URIs: args})
+ if err != nil {
+ return nil, err
+ }
+ msg := "go.sum is out of sync with go.mod. Please update it by applying the quick fix."
+ if innermost != nil {
+ msg = fmt.Sprintf("go.sum is out of sync with go.mod: entry for %v is missing. Please updating it by applying the quick fix.", innermost)
+ }
+ return &source.Diagnostic{
+ URI: pm.URI,
+ Range: rng,
+ Severity: protocol.SeverityError,
+ Source: source.ListError,
+ Message: msg,
+ SuggestedFixes: []source.SuggestedFix{
+ source.SuggestedFixFromCommand(tidyCmd),
+ source.SuggestedFixFromCommand(updateCmd),
+ },
+ }, nil
+ case strings.Contains(goCmdError, "disabled by GOPROXY=off") && innermost != nil:
title := fmt.Sprintf("Download %v@%v", innermost.Path, innermost.Version)
cmd, err := command.NewAddDependencyCommand(title, command.DependencyArgs{
- URI: protocol.URIFromSpanURI(fh.URI()),
+ URI: protocol.URIFromSpanURI(pm.URI),
AddRequire: false,
GoCmdArgs: []string{fmt.Sprintf("%v@%v", innermost.Path, innermost.Version)},
})
if err != nil {
- return nil
- }
- msg := goCmdError
- if disabledByGOPROXY {
- msg = fmt.Sprintf("%v@%v has not been downloaded", innermost.Path, innermost.Version)
+ return nil, err
}
return &source.Diagnostic{
- URI: fh.URI(),
+ URI: pm.URI,
Range: rng,
Severity: protocol.SeverityError,
- Message: msg,
+ Message: fmt.Sprintf("%v@%v has not been downloaded", innermost.Path, innermost.Version),
Source: source.ListError,
SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd)},
- }
- }
- diagSource := source.ListError
- if fh != nil {
- diagSource = source.ParseError
- }
- return &source.Diagnostic{
- URI: fh.URI(),
- Range: rng,
- Severity: protocol.SeverityError,
- Source: diagSource,
- Message: goCmdError,
+ }, nil
+ default:
+ return &source.Diagnostic{
+ URI: pm.URI,
+ Range: rng,
+ Severity: protocol.SeverityError,
+ Source: source.ListError,
+ Message: goCmdError,
+ }, nil
}
}
@@ -345,56 +436,3 @@
}
return nil
}
-
-// errorPositionRe matches errors messages of the form <filename>:<line>:<col>,
-// where the <col> is optional.
-var errorPositionRe = regexp.MustCompile(`(?P<pos>.*:([\d]+)(:([\d]+))?): (?P<msg>.+)`)
-
-// extractErrorWithPosition returns a structured error with position
-// information for the given unstructured error. If a file handle is provided,
-// the error position will be on that file. This is useful for parse errors,
-// where we already know the file with the error.
-func extractErrorWithPosition(ctx context.Context, goCmdError string, src source.FileSource) *source.Diagnostic {
- matches := errorPositionRe.FindStringSubmatch(strings.TrimSpace(goCmdError))
- if len(matches) == 0 {
- return nil
- }
- var pos, msg string
- for i, name := range errorPositionRe.SubexpNames() {
- if name == "pos" {
- pos = matches[i]
- }
- if name == "msg" {
- msg = matches[i]
- }
- }
- spn := span.Parse(pos)
- fh, err := src.GetFile(ctx, spn.URI())
- if err != nil {
- return nil
- }
- content, err := fh.Read()
- if err != nil {
- return nil
- }
- m := &protocol.ColumnMapper{
- URI: spn.URI(),
- Converter: span.NewContentConverter(spn.URI().Filename(), content),
- Content: content,
- }
- rng, err := m.Range(spn)
- if err != nil {
- return nil
- }
- diagSource := source.ListError
- if fh != nil {
- diagSource = source.ParseError
- }
- return &source.Diagnostic{
- URI: spn.URI(),
- Range: rng,
- Severity: protocol.SeverityError,
- Source: diagSource,
- Message: msg,
- }
-}
diff --git a/internal/lsp/cache/mod_tidy.go b/internal/lsp/cache/mod_tidy.go
index 1b7ef52..6891a39 100644
--- a/internal/lsp/cache/mod_tidy.go
+++ b/internal/lsp/cache/mod_tidy.go
@@ -151,74 +151,6 @@
return mth.tidy(ctx, s)
}
-func (s *snapshot) parseModError(ctx context.Context, errText string) []*source.Diagnostic {
- // Match on common error messages. This is really hacky, but I'm not sure
- // of any better way. This can be removed when golang/go#39164 is resolved.
- isInconsistentVendor := strings.Contains(errText, "inconsistent vendoring")
- isGoSumUpdates := strings.Contains(errText, "updates to go.sum needed") || strings.Contains(errText, "missing go.sum entry")
-
- if !isInconsistentVendor && !isGoSumUpdates {
- return nil
- }
-
- switch {
- case isInconsistentVendor:
- uri := s.ModFiles()[0]
- args := command.URIArg{URI: protocol.URIFromSpanURI(uri)}
- rng, err := s.uriToModDecl(ctx, uri)
- if err != nil {
- return nil
- }
- cmd, err := command.NewVendorCommand("Run go mod vendor", args)
- if err != nil {
- return nil
- }
- return []*source.Diagnostic{{
- URI: uri,
- Range: rng,
- Severity: protocol.SeverityError,
- Source: source.ListError,
- Message: `Inconsistent vendoring detected. Please re-run "go mod vendor".
-See https://github.com/golang/go/issues/39164 for more detail on this issue.`,
- SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd)},
- }}
-
- case isGoSumUpdates:
- var args []protocol.DocumentURI
- for _, uri := range s.ModFiles() {
- args = append(args, protocol.URIFromSpanURI(uri))
- }
- var diagnostics []*source.Diagnostic
- for _, uri := range s.ModFiles() {
- rng, err := s.uriToModDecl(ctx, uri)
- if err != nil {
- return nil
- }
- tidyCmd, err := command.NewTidyCommand("Run go mod tidy", command.URIArgs{URIs: args})
- if err != nil {
- return nil
- }
- updateCmd, err := command.NewUpdateGoSumCommand("Update go.sum", command.URIArgs{URIs: args})
- if err != nil {
- return nil
- }
- diagnostics = append(diagnostics, &source.Diagnostic{
- URI: uri,
- Range: rng,
- Severity: protocol.SeverityError,
- Source: source.ListError,
- Message: `go.sum is out of sync with go.mod. Please update it by applying the quick fix.`,
- SuggestedFixes: []source.SuggestedFix{
- source.SuggestedFixFromCommand(tidyCmd),
- source.SuggestedFixFromCommand(updateCmd),
- },
- })
- }
- return diagnostics
- default:
- return nil
- }
-}
func (s *snapshot) uriToModDecl(ctx context.Context, uri span.URI) (protocol.Range, error) {
fh, err := s.GetFile(ctx, uri)
@@ -546,6 +478,14 @@
}
func rangeFromPositions(m *protocol.ColumnMapper, s, e modfile.Position) (protocol.Range, error) {
+ spn, err := spanFromPositions(m, s, e)
+ if err != nil {
+ return protocol.Range{}, err
+ }
+ return m.Range(spn)
+}
+
+func spanFromPositions(m *protocol.ColumnMapper, s, e modfile.Position) (span.Span, error) {
toPoint := func(offset int) (span.Point, error) {
l, c, err := m.Converter.ToPosition(offset)
if err != nil {
@@ -555,11 +495,11 @@
}
start, err := toPoint(s.Byte)
if err != nil {
- return protocol.Range{}, err
+ return span.Span{}, err
}
end, err := toPoint(e.Byte)
if err != nil {
- return protocol.Range{}, err
+ return span.Span{}, err
}
- return m.Range(span.New(m.URI, start, end))
+ return span.New(m.URI, start, end), nil
}
diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go
index 0f44cab..ddfefc7 100644
--- a/internal/lsp/cache/snapshot.go
+++ b/internal/lsp/cache/snapshot.go
@@ -1022,22 +1022,22 @@
}
func (s *snapshot) awaitLoaded(ctx context.Context) error {
- err := s.awaitLoadedAllErrors(ctx)
+ loadErr := s.awaitLoadedAllErrors(ctx)
// If we still have absolutely no metadata, check if the view failed to
// initialize and return any errors.
// TODO(rstambler): Should we clear the error after we return it?
s.mu.Lock()
defer s.mu.Unlock()
- if len(s.metadata) == 0 {
- return err
+ if len(s.metadata) == 0 && loadErr != nil {
+ return loadErr.MainError
}
return nil
}
func (s *snapshot) GetCriticalError(ctx context.Context) *source.CriticalError {
loadErr := s.awaitLoadedAllErrors(ctx)
- if errors.Is(loadErr, context.Canceled) {
+ if loadErr != nil && errors.Is(loadErr.MainError, context.Canceled) {
return nil
}
@@ -1060,14 +1060,10 @@
return nil
}
- if strings.Contains(loadErr.Error(), "cannot find main module") {
+ if strings.Contains(loadErr.MainError.Error(), "cannot find main module") {
return s.workspaceLayoutError(ctx)
}
- criticalErr := &source.CriticalError{
- MainError: loadErr,
- DiagList: s.extractGoCommandErrors(ctx, s, loadErr.Error()),
- }
- return criticalErr
+ return loadErr
}
const adHocPackagesWarning = `You are outside of a module and outside of $GOPATH/src.
@@ -1095,27 +1091,32 @@
return false
}
-func (s *snapshot) awaitLoadedAllErrors(ctx context.Context) error {
+func (s *snapshot) awaitLoadedAllErrors(ctx context.Context) *source.CriticalError {
// Do not return results until the snapshot's view has been initialized.
s.AwaitInitialized(ctx)
if ctx.Err() != nil {
- return ctx.Err()
+ return &source.CriticalError{MainError: ctx.Err()}
}
if err := s.reloadWorkspace(ctx); err != nil {
- return err
+ diags, _ := s.extractGoCommandErrors(ctx, err.Error())
+ return &source.CriticalError{
+ MainError: err,
+ DiagList: diags,
+ }
}
if err := s.reloadOrphanedFiles(ctx); err != nil {
- return err
+ diags, _ := s.extractGoCommandErrors(ctx, err.Error())
+ return &source.CriticalError{
+ MainError: err,
+ DiagList: diags,
+ }
}
// TODO(rstambler): Should we be more careful about returning the
// initialization error? Is it possible for the initialization error to be
// corrected without a successful reinitialization?
- if s.initializedErr == nil {
- return nil
- }
- return s.initializedErr.MainError
+ return s.initializedErr
}
func (s *snapshot) AwaitInitialized(ctx context.Context) {
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index e71a84c..64a9e7e 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -573,15 +573,15 @@
}
if err != nil {
event.Error(ctx, "initial workspace load failed", err)
- if modDiagnostics != nil {
- s.initializedErr = &source.CriticalError{
- MainError: errors.Errorf("errors loading modules: %v: %w", err, modDiagnostics),
- DiagList: modDiagnostics,
- }
- } else {
- s.initializedErr = &source.CriticalError{
- MainError: err,
- }
+ extractedDiags, _ := s.extractGoCommandErrors(ctx, err.Error())
+ s.initializedErr = &source.CriticalError{
+ MainError: err,
+ DiagList: append(modDiagnostics, extractedDiags...),
+ }
+ } else if len(modDiagnostics) != 0 {
+ s.initializedErr = &source.CriticalError{
+ MainError: fmt.Errorf("error loading module names"),
+ DiagList: modDiagnostics,
}
} else {
// Clear out the initialization error, in case it had been set
diff --git a/internal/lsp/cache/view_test.go b/internal/lsp/cache/view_test.go
index fb788f4..cb57182 100644
--- a/internal/lsp/cache/view_test.go
+++ b/internal/lsp/cache/view_test.go
@@ -8,12 +8,9 @@
"io/ioutil"
"os"
"path/filepath"
- "strings"
"testing"
- "golang.org/x/mod/modfile"
"golang.org/x/tools/internal/lsp/fake"
- "golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span"
)
@@ -106,88 +103,6 @@
}
}
-// This tests the logic used to extract positions from parse and other Go
-// command errors.
-func TestExtractPositionFromError(t *testing.T) {
- workspace := `
--- a/go.mod --
-modul a.com
--- b/go.mod --
-module b.com
-
-go 1.12.hello
--- c/go.mod --
-module c.com
-
-require a.com master
-`
- dir, err := fake.Tempdir(workspace)
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(dir)
-
- tests := []struct {
- filename string
- wantRng protocol.Range
- }{
- {
- filename: "a/go.mod",
- wantRng: protocol.Range{},
- },
- {
- filename: "b/go.mod",
- wantRng: protocol.Range{
- Start: protocol.Position{Line: 2},
- End: protocol.Position{Line: 2},
- },
- },
- {
- filename: "c/go.mod",
- wantRng: protocol.Range{
- Start: protocol.Position{Line: 2},
- End: protocol.Position{Line: 2},
- },
- },
- }
- for _, test := range tests {
- ctx := context.Background()
- rel := fake.RelativeTo(dir)
- uri := span.URIFromPath(rel.AbsPath(test.filename))
- if source.DetectLanguage("", uri.Filename()) != source.Mod {
- t.Fatalf("expected only go.mod files")
- }
- // Try directly parsing the given, invalid go.mod file. Then, extract a
- // position from the error message.
- src := &osFileSource{}
- modFH, err := src.GetFile(ctx, uri)
- if err != nil {
- t.Fatal(err)
- }
- content, err := modFH.Read()
- if err != nil {
- t.Fatal(err)
- }
- _, parseErr := modfile.Parse(uri.Filename(), content, nil)
- if parseErr == nil {
- t.Fatalf("%s: expected an unparseable go.mod file", uri.Filename())
- }
- srcErr := extractErrorWithPosition(ctx, parseErr.Error(), src)
- if srcErr == nil {
- t.Fatalf("unable to extract positions from %v", parseErr.Error())
- }
- if srcErr.URI != uri {
- t.Errorf("unexpected URI: got %s, wanted %s", srcErr.URI, uri)
- }
- if protocol.CompareRange(test.wantRng, srcErr.Range) != 0 {
- t.Errorf("unexpected range: got %s, wanted %s", srcErr.Range, test.wantRng)
- }
- if !strings.HasSuffix(parseErr.Error(), srcErr.Message) {
- t.Errorf("unexpected message: got %s, wanted %s", srcErr.Message, parseErr)
- }
- }
-}
-
func TestInVendor(t *testing.T) {
for _, tt := range []struct {
path string
diff --git a/internal/lsp/command/command_gen.go b/internal/lsp/command/command_gen.go
index aca505c..1871c9d 100644
--- a/internal/lsp/command/command_gen.go
+++ b/internal/lsp/command/command_gen.go
@@ -4,6 +4,7 @@
// Don't include this file during code generation, or it will break the build
// if existing interface methods have been modified.
+//go:build !generate
// +build !generate
package command
diff --git a/internal/lsp/command/generate/generate.go b/internal/lsp/command/gen/gen.go
similarity index 96%
rename from internal/lsp/command/generate/generate.go
rename to internal/lsp/command/gen/gen.go
index d76ff6f..3934f1a 100644
--- a/internal/lsp/command/generate/generate.go
+++ b/internal/lsp/command/gen/gen.go
@@ -2,9 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// Package generate is used to generate command bindings from the gopls command
+// Package gen is used to generate command bindings from the gopls command
// interface.
-package generate
+package gen
import (
"bytes"
@@ -22,6 +22,7 @@
// Don't include this file during code generation, or it will break the build
// if existing interface methods have been modified.
+//go:build !generate
// +build !generate
package command
diff --git a/internal/lsp/command/generate.go b/internal/lsp/command/generate.go
index 49d72b5..14628c7 100644
--- a/internal/lsp/command/generate.go
+++ b/internal/lsp/command/generate.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build ignore
// +build ignore
package main
@@ -11,11 +12,11 @@
"io/ioutil"
"os"
- "golang.org/x/tools/internal/lsp/command/generate"
+ "golang.org/x/tools/internal/lsp/command/gen"
)
func main() {
- content, err := generate.Generate()
+ content, err := gen.Generate()
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
diff --git a/internal/lsp/command/interface_test.go b/internal/lsp/command/interface_test.go
index d58545e..9ea30b4 100644
--- a/internal/lsp/command/interface_test.go
+++ b/internal/lsp/command/interface_test.go
@@ -9,7 +9,7 @@
"io/ioutil"
"testing"
- "golang.org/x/tools/internal/lsp/command/generate"
+ "golang.org/x/tools/internal/lsp/command/gen"
"golang.org/x/tools/internal/testenv"
)
@@ -21,7 +21,7 @@
t.Fatal(err)
}
- generated, err := generate.Generate()
+ generated, err := gen.Generate()
if err != nil {
t.Fatal(err)
}
diff --git a/internal/lsp/fake/editor.go b/internal/lsp/fake/editor.go
index 96410d9..58a13ec 100644
--- a/internal/lsp/fake/editor.go
+++ b/internal/lsp/fake/editor.go
@@ -747,17 +747,26 @@
// OrganizeImports requests and performs the source.organizeImports codeAction.
func (e *Editor) OrganizeImports(ctx context.Context, path string) error {
- return e.codeAction(ctx, path, nil, nil, protocol.SourceOrganizeImports)
+ _, err := e.codeAction(ctx, path, nil, nil, protocol.SourceOrganizeImports)
+ return err
}
// RefactorRewrite requests and performs the source.refactorRewrite codeAction.
func (e *Editor) RefactorRewrite(ctx context.Context, path string, rng *protocol.Range) error {
- return e.codeAction(ctx, path, rng, nil, protocol.RefactorRewrite)
+ applied, err := e.codeAction(ctx, path, rng, nil, protocol.RefactorRewrite)
+ if applied == 0 {
+ return errors.Errorf("no refactorings were applied")
+ }
+ return err
}
// ApplyQuickFixes requests and performs the quickfix codeAction.
func (e *Editor) ApplyQuickFixes(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic) error {
- return e.codeAction(ctx, path, rng, diagnostics, protocol.QuickFix, protocol.SourceFixAll)
+ applied, err := e.codeAction(ctx, path, rng, diagnostics, protocol.QuickFix, protocol.SourceFixAll)
+ if applied == 0 {
+ return errors.Errorf("no quick fixes were applied")
+ }
+ return err
}
// GetQuickFixes returns the available quick fix code actions.
@@ -765,14 +774,15 @@
return e.getCodeActions(ctx, path, rng, diagnostics, protocol.QuickFix, protocol.SourceFixAll)
}
-func (e *Editor) codeAction(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic, only ...protocol.CodeActionKind) error {
+func (e *Editor) codeAction(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic, only ...protocol.CodeActionKind) (int, error) {
actions, err := e.getCodeActions(ctx, path, rng, diagnostics, only...)
if err != nil {
- return err
+ return 0, err
}
+ applied := 0
for _, action := range actions {
if action.Title == "" {
- return errors.Errorf("empty title for code action")
+ return 0, errors.Errorf("empty title for code action")
}
var match bool
for _, o := range only {
@@ -784,6 +794,7 @@
if !match {
continue
}
+ applied++
for _, change := range action.Edit.DocumentChanges {
path := e.sandbox.Workdir.URIToPath(change.TextDocument.URI)
if int32(e.buffers[path].version) != change.TextDocument.Version {
@@ -792,7 +803,7 @@
}
edits := convertEdits(change.Edits)
if err := e.EditBuffer(ctx, path, edits); err != nil {
- return errors.Errorf("editing buffer %q: %w", path, err)
+ return 0, errors.Errorf("editing buffer %q: %w", path, err)
}
}
// Execute any commands. The specification says that commands are
@@ -802,15 +813,15 @@
Command: action.Command.Command,
Arguments: action.Command.Arguments,
}); err != nil {
- return err
+ return 0, err
}
}
// Some commands may edit files on disk.
if err := e.sandbox.Workdir.CheckForFileChanges(ctx); err != nil {
- return err
+ return 0, err
}
}
- return nil
+ return applied, nil
}
func (e *Editor) getCodeActions(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic, only ...protocol.CodeActionKind) ([]protocol.CodeAction, error) {
diff --git a/internal/lsp/lsprpc/autostart_posix.go b/internal/lsp/lsprpc/autostart_posix.go
index 9ad3f1d..45089b8 100644
--- a/internal/lsp/lsprpc/autostart_posix.go
+++ b/internal/lsp/lsprpc/autostart_posix.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package lsprpc
diff --git a/internal/span/token111.go b/internal/span/token111.go
index bf7a540..c41e94b 100644
--- a/internal/span/token111.go
+++ b/internal/span/token111.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !go1.12
// +build !go1.12
package span
diff --git a/internal/span/token112.go b/internal/span/token112.go
index 017aec9..4c4dea1 100644
--- a/internal/span/token112.go
+++ b/internal/span/token112.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.12
// +build go1.12
package span
diff --git a/internal/span/uri_test.go b/internal/span/uri_test.go
index 260f948..bcbad87 100644
--- a/internal/span/uri_test.go
+++ b/internal/span/uri_test.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !windows
// +build !windows
package span_test
diff --git a/internal/span/uri_windows_test.go b/internal/span/uri_windows_test.go
index 2a2632e..e50b58f 100644
--- a/internal/span/uri_windows_test.go
+++ b/internal/span/uri_windows_test.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build windows
// +build windows
package span_test
diff --git a/internal/testenv/testenv_112.go b/internal/testenv/testenv_112.go
index b25846c..4b6e57d 100644
--- a/internal/testenv/testenv_112.go
+++ b/internal/testenv/testenv_112.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.12
// +build go1.12
package testenv
diff --git a/playground/socket/socket.go b/playground/socket/socket.go
index 5e385eb..cdc6653 100644
--- a/playground/socket/socket.go
+++ b/playground/socket/socket.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !appengine
// +build !appengine
// Package socket implements an WebSocket-based playground backend.
diff --git a/refactor/eg/eg_test.go b/refactor/eg/eg_test.go
index 158f909..a788361 100644
--- a/refactor/eg/eg_test.go
+++ b/refactor/eg/eg_test.go
@@ -4,6 +4,7 @@
// No testdata on Android.
+//go:build !android
// +build !android
package eg_test
diff --git a/refactor/eg/testdata/A.template b/refactor/eg/testdata/A.template
index f611961..6a23f12 100644
--- a/refactor/eg/testdata/A.template
+++ b/refactor/eg/testdata/A.template
@@ -1,5 +1,3 @@
-// +build ignore
-
package template
// Basic test of type-aware expression refactoring.
diff --git a/refactor/eg/testdata/A1.go b/refactor/eg/testdata/A1.go
index 9e65eb3..c64fd80 100644
--- a/refactor/eg/testdata/A1.go
+++ b/refactor/eg/testdata/A1.go
@@ -1,5 +1,3 @@
-// +build ignore
-
package A1
import (
diff --git a/refactor/eg/testdata/A1.golden b/refactor/eg/testdata/A1.golden
index 7eb2934..a8aeb06 100644
--- a/refactor/eg/testdata/A1.golden
+++ b/refactor/eg/testdata/A1.golden
@@ -1,5 +1,3 @@
-// +build ignore
-
package A1
import (
diff --git a/refactor/eg/testdata/A2.go b/refactor/eg/testdata/A2.go
index 3ae29ad..2fab790 100644
--- a/refactor/eg/testdata/A2.go
+++ b/refactor/eg/testdata/A2.go
@@ -1,5 +1,3 @@
-// +build ignore
-
package A2
// This refactoring causes addition of "errors" import.
diff --git a/refactor/eg/testdata/A2.golden b/refactor/eg/testdata/A2.golden
index b6e3a6d..0e4ca44 100644
--- a/refactor/eg/testdata/A2.golden
+++ b/refactor/eg/testdata/A2.golden
@@ -1,5 +1,3 @@
-// +build ignore
-
package A2
// This refactoring causes addition of "errors" import.
diff --git a/refactor/eg/testdata/B1.go b/refactor/eg/testdata/B1.go
index 8b52546..1e09c90 100644
--- a/refactor/eg/testdata/B1.go
+++ b/refactor/eg/testdata/B1.go
@@ -1,5 +1,3 @@
-// +build ignore
-
package B1
import "time"
diff --git a/refactor/eg/testdata/B1.golden b/refactor/eg/testdata/B1.golden
index 4d4da21..b2ed30b 100644
--- a/refactor/eg/testdata/B1.golden
+++ b/refactor/eg/testdata/B1.golden
@@ -1,5 +1,3 @@
-// +build ignore
-
package B1
import "time"
diff --git a/refactor/eg/testdata/C1.go b/refactor/eg/testdata/C1.go
index 523b388..fb565a3 100644
--- a/refactor/eg/testdata/C1.go
+++ b/refactor/eg/testdata/C1.go
@@ -1,5 +1,3 @@
-// +build ignore
-
package C1
import "strings"
diff --git a/refactor/eg/testdata/C1.golden b/refactor/eg/testdata/C1.golden
index ae7759d..d3b0b71 100644
--- a/refactor/eg/testdata/C1.golden
+++ b/refactor/eg/testdata/C1.golden
@@ -1,5 +1,3 @@
-// +build ignore
-
package C1
import "strings"
diff --git a/refactor/eg/testdata/D1.go b/refactor/eg/testdata/D1.go
index ae0a806..03a434c 100644
--- a/refactor/eg/testdata/D1.go
+++ b/refactor/eg/testdata/D1.go
@@ -1,5 +1,3 @@
-// +build ignore
-
package D1
import "fmt"
diff --git a/refactor/eg/testdata/D1.golden b/refactor/eg/testdata/D1.golden
index 2932652..88d4a9e 100644
--- a/refactor/eg/testdata/D1.golden
+++ b/refactor/eg/testdata/D1.golden
@@ -1,5 +1,3 @@
-// +build ignore
-
package D1
import "fmt"
diff --git a/refactor/eg/testdata/E1.go b/refactor/eg/testdata/E1.go
index 3ea1793..54054c8 100644
--- a/refactor/eg/testdata/E1.go
+++ b/refactor/eg/testdata/E1.go
@@ -1,5 +1,3 @@
-// +build ignore
-
package E1
import "log"
diff --git a/refactor/eg/testdata/E1.golden b/refactor/eg/testdata/E1.golden
index 796364f..ec10b41 100644
--- a/refactor/eg/testdata/E1.golden
+++ b/refactor/eg/testdata/E1.golden
@@ -1,5 +1,3 @@
-// +build ignore
-
package E1
import (
diff --git a/refactor/eg/testdata/F.template b/refactor/eg/testdata/F.template
index 21e1bd2..df73beb 100644
--- a/refactor/eg/testdata/F.template
+++ b/refactor/eg/testdata/F.template
@@ -1,8 +1,8 @@
package templates
-// Test
+// Test
import "sync"
func before(s sync.RWMutex) { s.Lock() }
-func after(s sync.RWMutex) { s.RLock() }
\ No newline at end of file
+func after(s sync.RWMutex) { s.RLock() }
diff --git a/refactor/eg/testdata/F1.go b/refactor/eg/testdata/F1.go
index 2258abd..da9c9de 100644
--- a/refactor/eg/testdata/F1.go
+++ b/refactor/eg/testdata/F1.go
@@ -1,5 +1,3 @@
-// +build ignore
-
package F1
import "sync"
diff --git a/refactor/eg/testdata/F1.golden b/refactor/eg/testdata/F1.golden
index 5ffda69..ea5d0cd 100644
--- a/refactor/eg/testdata/F1.golden
+++ b/refactor/eg/testdata/F1.golden
@@ -1,5 +1,3 @@
-// +build ignore
-
package F1
import "sync"
diff --git a/refactor/eg/testdata/G.template b/refactor/eg/testdata/G.template
index 69d84fe..ab368ce 100644
--- a/refactor/eg/testdata/G.template
+++ b/refactor/eg/testdata/G.template
@@ -7,4 +7,3 @@
func before(from, to token.Pos) ast.BadExpr { return ast.BadExpr{From: from, To: to} }
func after(from, to token.Pos) ast.BadExpr { return ast.BadExpr{from, to} }
-
\ No newline at end of file
diff --git a/refactor/eg/testdata/G1.go b/refactor/eg/testdata/G1.go
index 07aaff9..0fb9ab9 100644
--- a/refactor/eg/testdata/G1.go
+++ b/refactor/eg/testdata/G1.go
@@ -1,5 +1,3 @@
-// +build ignore
-
package G1
import "go/ast"
diff --git a/refactor/eg/testdata/G1.golden b/refactor/eg/testdata/G1.golden
index c93c53f..ba3704c 100644
--- a/refactor/eg/testdata/G1.golden
+++ b/refactor/eg/testdata/G1.golden
@@ -1,5 +1,3 @@
-// +build ignore
-
package G1
import "go/ast"
diff --git a/refactor/eg/testdata/H1.go b/refactor/eg/testdata/H1.go
index ef4291c..e151ac8 100644
--- a/refactor/eg/testdata/H1.go
+++ b/refactor/eg/testdata/H1.go
@@ -1,5 +1,3 @@
-// +build ignore
-
package H1
import "go/ast"
diff --git a/refactor/eg/testdata/H1.golden b/refactor/eg/testdata/H1.golden
index a1e5961..da2658a 100644
--- a/refactor/eg/testdata/H1.golden
+++ b/refactor/eg/testdata/H1.golden
@@ -1,5 +1,3 @@
-// +build ignore
-
package H1
import "go/ast"
diff --git a/refactor/eg/testdata/I.template b/refactor/eg/testdata/I.template
index d03e774..b8e8f93 100644
--- a/refactor/eg/testdata/I.template
+++ b/refactor/eg/testdata/I.template
@@ -1,5 +1,3 @@
-// +build ignore
-
package templates
import (
diff --git a/refactor/eg/testdata/I1.go b/refactor/eg/testdata/I1.go
index d1762eb..ef3fe8b 100644
--- a/refactor/eg/testdata/I1.go
+++ b/refactor/eg/testdata/I1.go
@@ -1,5 +1,3 @@
-// +build ignore
-
package I1
import "fmt"
diff --git a/refactor/eg/testdata/I1.golden b/refactor/eg/testdata/I1.golden
index f33b3e1..d0246ae 100644
--- a/refactor/eg/testdata/I1.golden
+++ b/refactor/eg/testdata/I1.golden
@@ -1,5 +1,3 @@
-// +build ignore
-
package I1
import (
diff --git a/refactor/eg/testdata/J.template b/refactor/eg/testdata/J.template
index 6f82cdf..b3b1f18 100644
--- a/refactor/eg/testdata/J.template
+++ b/refactor/eg/testdata/J.template
@@ -1,5 +1,3 @@
-// +build ignore
-
package templates
import ()
diff --git a/refactor/eg/testdata/J1.go b/refactor/eg/testdata/J1.go
index 2fbeee8..532ca13 100644
--- a/refactor/eg/testdata/J1.go
+++ b/refactor/eg/testdata/J1.go
@@ -1,5 +1,3 @@
-// +build ignore
-
package I1
import "fmt"
diff --git a/refactor/eg/testdata/J1.golden b/refactor/eg/testdata/J1.golden
index bb2f11c..911ef87 100644
--- a/refactor/eg/testdata/J1.golden
+++ b/refactor/eg/testdata/J1.golden
@@ -1,5 +1,3 @@
-// +build ignore
-
package I1
import "fmt"
diff --git a/refactor/importgraph/graph_test.go b/refactor/importgraph/graph_test.go
index e342323..2ab54e2 100644
--- a/refactor/importgraph/graph_test.go
+++ b/refactor/importgraph/graph_test.go
@@ -4,6 +4,7 @@
// Incomplete std lib sources on Android.
+//go:build !android
// +build !android
package importgraph_test
diff --git a/txtar/archive_test.go b/txtar/archive_test.go
index 1f79f8d..7ac5ee9 100644
--- a/txtar/archive_test.go
+++ b/txtar/archive_test.go
@@ -70,7 +70,7 @@
{"file1", []byte("File 1 text.\n-- foo ---\nMore file 1 text.\n")},
{"file 2", []byte("File 2 text.\n")},
{"empty", []byte{}},
- {"noNL", []byte("hello world\n")},
+ {"noNL", []byte("hello world")},
},
},
wanted: `comment1
@@ -90,7 +90,7 @@
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Format(tt.input)
- if !reflect.DeepEqual(string(result), tt.wanted) {
+ if string(result) != tt.wanted {
t.Errorf("Wrong output. \nGot:\n%s\nWant:\n%s\n", string(result), tt.wanted)
}
})