Skip to content

Commit

Permalink
go/analysis: Add modules to Pass
Browse files Browse the repository at this point in the history
Adds optional Module information to Pass. Module contains
the go module information for the package of the Pass.
It contains the module's path, the module version, and the
GoVersion of the module.

Updates packages.PrintErrors to additionally print module
errors.

Fixes golang/go#66315

Change-Id: I7005b8e2f6290f16416c2438af0e6de2940ba9fc
Reviewed-on: https://go-review.googlesource.com/c/tools/+/577996
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
  • Loading branch information
timothy-king committed Jul 31, 2024
1 parent 55d718e commit ead76ab
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 1 deletion.
9 changes: 9 additions & 0 deletions go/analysis/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ type Pass struct {
TypesSizes types.Sizes // function for computing sizes of types
TypeErrors []types.Error // type errors (only if Analyzer.RunDespiteErrors)

Module *Module // the package's enclosing module (possibly nil in some drivers)

// Report reports a Diagnostic, a finding about a specific location
// in the analyzed source code such as a potential mistake.
// It may be called by the Run function.
Expand Down Expand Up @@ -238,3 +240,10 @@ func (pass *Pass) String() string {
type Fact interface {
AFact() // dummy method to avoid type errors
}

// A Module describes the module to which a package belongs.
type Module struct {
Path string // module path
Version string // module version ("" if unknown, such as for workspace modules)
GoVersion string // go version used in module (e.g. "go1.22.0")
}
58 changes: 58 additions & 0 deletions go/analysis/analysistest/analysistest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/findcall"
"golang.org/x/tools/internal/testenv"
"golang.org/x/tools/internal/testfiles"
"golang.org/x/tools/txtar"
)

func init() {
Expand Down Expand Up @@ -202,6 +204,62 @@ func F() {} // want "say hello"`,
analysistest.RunWithSuggestedFixes(t, dir, noend, "a")
}

func TestModule(t *testing.T) {
const content = `
Test that analysis.pass.Module is populated.
-- go.mod --
module golang.org/fake/mod
go 1.21
require golang.org/xyz/fake v0.12.34
-- mod.go --
// We expect a module.Path and a module.GoVersion, but an empty module.Version.
package mod // want "golang.org/fake/mod,,1.21"
import "golang.org/xyz/fake/ver"
var _ ver.T
-- vendor/modules.txt --
# golang.org/xyz/fake v0.12.34
## explicit; go 1.18
golang.org/xyz/fake/ver
-- vendor/golang.org/xyz/fake/ver/ver.go --
// This package is vendored so that we can populate a non-empty
// Pass.Module.Version is in a test.
package ver //want "golang.org/xyz/fake,v0.12.34,1.18"
type T string
`
fs, err := txtar.FS(txtar.Parse([]byte(content)))
if err != nil {
t.Fatal(err)
}
dir := testfiles.CopyToTmp(t, fs)

filever := &analysis.Analyzer{
Name: "mod",
Doc: "reports module information",
Run: func(pass *analysis.Pass) (any, error) {
msg := "no module info"
if m := pass.Module; m != nil {
msg = fmt.Sprintf("%s,%s,%s", m.Path, m.Version, m.GoVersion)
}
for _, file := range pass.Files {
pass.Reportf(file.Package, "%s", msg)
}
return nil, nil
},
}
analysistest.Run(t, dir, filever, "golang.org/fake/mod", "golang.org/xyz/fake/ver")
}

type errorfunc func(string)

func (f errorfunc) Errorf(format string, args ...interface{}) {
Expand Down
10 changes: 9 additions & 1 deletion go/analysis/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func Run(args []string, analyzers []*analysis.Analyzer) (exitcode int) {
}

pkgsExitCode := 0
// Print package errors regardless of RunDespiteErrors.
// Print package and module errors regardless of RunDespiteErrors.
// Do not exit if there are errors, yet.
if n := packages.PrintErrors(initial); n > 0 {
pkgsExitCode = 1
Expand Down Expand Up @@ -720,6 +720,13 @@ func (act *action) execOnce() {
}
}

module := &analysis.Module{} // possibly empty (non nil) in go/analysis drivers.
if mod := act.pkg.Module; mod != nil {
module.Path = mod.Path
module.Version = mod.Version
module.GoVersion = mod.GoVersion
}

// Run the analysis.
pass := &analysis.Pass{
Analyzer: act.a,
Expand All @@ -731,6 +738,7 @@ func (act *action) execOnce() {
TypesInfo: act.pkg.TypesInfo,
TypesSizes: act.pkg.TypesSizes,
TypeErrors: act.pkg.TypeErrors,
Module: module,

ResultOf: inputs,
Report: func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) },
Expand Down
4 changes: 4 additions & 0 deletions go/analysis/unitchecker/separate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"io"
"os"
"path/filepath"
"sort"
"strings"
"sync/atomic"
"testing"
Expand Down Expand Up @@ -167,6 +168,8 @@ func MyPrintf(format string, args ...any) {
if v := pkg.Module.GoVersion; v != "" {
cfg.GoVersion = "go" + v
}
cfg.ModulePath = pkg.Module.Path
cfg.ModuleVersion = pkg.Module.Version
}

// Write the JSON configuration message to a file.
Expand Down Expand Up @@ -220,6 +223,7 @@ func MyPrintf(format string, args ...any) {
// from separate analysis of "main", "lib", and "fmt":

const want = `/main/main.go:6:2: [printf] separate/lib.MyPrintf format %s has arg 123 of wrong type int`
sort.Strings(allDiagnostics)
if got := strings.Join(allDiagnostics, "\n"); got != want {
t.Errorf("Got: %s\nWant: %s", got, want)
}
Expand Down
9 changes: 9 additions & 0 deletions go/analysis/unitchecker/unitchecker.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ type Config struct {
GoFiles []string
NonGoFiles []string
IgnoredFiles []string
ModulePath string // module path
ModuleVersion string // module version
ImportMap map[string]string // maps import path to package path
PackageFile map[string]string // maps package path to file of type information
Standard map[string]bool // package belongs to standard library
Expand Down Expand Up @@ -359,6 +361,12 @@ func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]re
factFilter[reflect.TypeOf(f)] = true
}

module := &analysis.Module{
Path: cfg.ModulePath,
Version: cfg.ModuleVersion,
GoVersion: cfg.GoVersion,
}

pass := &analysis.Pass{
Analyzer: a,
Fset: fset,
Expand All @@ -377,6 +385,7 @@ func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]re
ImportPackageFact: facts.ImportPackageFact,
ExportPackageFact: facts.ExportPackageFact,
AllPackageFacts: func() []analysis.PackageFact { return facts.AllPackageFacts(factFilter) },
Module: module,
}
pass.ReadFile = analysisinternal.MakeReadFile(pass)

Expand Down
9 changes: 9 additions & 0 deletions go/packages/visit.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,20 @@ func Visit(pkgs []*Package, pre func(*Package) bool, post func(*Package)) {
// PrintErrors returns the number of errors printed.
func PrintErrors(pkgs []*Package) int {
var n int
errModules := make(map[*Module]bool)
Visit(pkgs, nil, func(pkg *Package) {
for _, err := range pkg.Errors {
fmt.Fprintln(os.Stderr, err)
n++
}

// Print pkg.Module.Error once if present.
mod := pkg.Module
if mod != nil && mod.Error != nil && !errModules[mod] {
errModules[mod] = true
fmt.Fprintln(os.Stderr, mod.Error.Err)
n++
}
})
return n
}

0 comments on commit ead76ab

Please sign in to comment.