@@ -11,94 +11,148 @@ package checker_test
1111import (
1212 "fmt"
1313 "log"
14+ "maps"
15+ "os"
1416
1517 "golang.org/x/tools/go/analysis"
1618 "golang.org/x/tools/go/analysis/checker"
1719 "golang.org/x/tools/go/packages"
20+ "golang.org/x/tools/txtar"
1821)
1922
23+ const testdata = `
24+ -- go.mod --
25+ module example.com
26+ go 1.21
27+
28+ -- a/a.go --
29+ package a
30+
31+ import _ "example.com/b"
32+ import _ "example.com/c"
33+
34+ func A1()
35+ func A2()
36+ func A3()
37+
38+ -- b/b.go --
39+ package b
40+
41+ func B1()
42+ func B2()
43+
44+ -- c/c.go --
45+ package c
46+
47+ import _ "example.com/d"
48+
49+ func C1()
50+
51+ -- d/d.go --
52+ package d
53+
54+ func D1()
55+ func D2()
56+ func D3()
57+ func D4()
58+ `
59+
2060func Example () {
21- // Load packages: just this one.
61+ // Extract a tree of Go source files.
62+ // (Avoid the standard library as it is always evolving.)
63+ dir , err := os .MkdirTemp ("" , "" )
64+ if err != nil {
65+ log .Fatal (err )
66+ }
67+ fs , err := txtar .FS (txtar .Parse ([]byte (testdata )))
68+ if err != nil {
69+ log .Fatal (err )
70+ }
71+ if err := os .CopyFS (dir , fs ); err != nil {
72+ log .Fatal (err )
73+ }
74+
75+ // Load packages: example.com/a + dependencies
2276 //
77+ cfg := & packages.Config {Mode : packages .LoadAllSyntax , Dir : dir }
78+ initial , err := packages .Load (cfg , "example.com/a" )
79+ if err != nil {
80+ log .Fatal (err ) // failure to enumerate packages
81+ }
82+
2383 // There may be parse or type errors among the
2484 // initial packages or their dependencies,
2585 // but the analysis driver can handle faulty inputs,
2686 // as can some analyzers.
27- cfg := & packages.Config {Mode : packages .LoadAllSyntax }
28- initial , err := packages .Load (cfg , "." )
29- if err != nil {
30- log .Fatal (err ) // failure to enumerate packages
31- }
87+ packages .PrintErrors (initial )
88+
3289 if len (initial ) == 0 {
3390 log .Fatalf ("no initial packages" )
3491 }
3592
36- // Run analyzers (just one) on packages.
37- analyzers := []* analysis.Analyzer {minmaxpkg }
93+ // Run analyzers (just one) on example.com packages.
94+ analyzers := []* analysis.Analyzer {pkgdecls }
3895 graph , err := checker .Analyze (analyzers , initial , nil )
3996 if err != nil {
4097 log .Fatal (err )
4198 }
4299
43- // Print information about the results of each
44- // analysis action, including all dependencies.
100+ // Inspect the result of each analysis action,
101+ // including those for all dependencies.
45102 //
46- // Clients using Go 1.23 can say:
47- // for act := range graph.All() { ... }
48- graph .All ()(func (act * checker.Action ) bool {
49- // Print information about the Action, e.g.
50- //
51- // act.String()
52- // act.Result
53- // act.Err
54- // act.Diagnostics
55- //
56- // (We don't actually print anything here
57- // as the output would vary over time,
58- // which is unsuitable for a test.)
59- return true
60- })
61-
62- // Print the minmaxpkg package fact computed for this package.
103+ // A realistic client would use Result, Err, Diagnostics,
104+ // but for test stability, we just print the action string
105+ // ("analyzer@package").
106+ for act := range graph .All () {
107+ fmt .Println ("printing" , act )
108+ }
109+
110+ // Print the package fact for the sole initial package.
63111 root := graph .Roots [0 ]
64- fact := new (minmaxpkgFact )
112+ fact := new (pkgdeclsFact )
65113 if root .PackageFact (root .Package .Types , fact ) {
66- fmt .Printf ("min=%s max=%s" , fact .min , fact .max )
114+ for k , v := range fact .numdecls {
115+ fmt .Printf ("%s:\t %d decls\n " , k , v )
116+ }
67117 }
68- // Output:
69- // min=bufio max=unsafe
118+
119+ // Unordered Output:
120+ // printing pkgdecls@example.com/a
121+ // printing pkgdecls@example.com/b
122+ // printing pkgdecls@example.com/c
123+ // printing pkgdecls@example.com/d
124+ // example.com/a: 3 decls
125+ // example.com/b: 2 decls
126+ // example.com/c: 1 decls
127+ // example.com/d: 4 decls
70128}
71129
72- // minmaxpkg is a trivial example analyzer that uses package facts to
130+ // pkgdecls is a trivial example analyzer that uses package facts to
73131// compute information from the entire dependency graph.
74- var minmaxpkg = & analysis.Analyzer {
75- Name : "minmaxpkg " ,
76- Doc : "Finds the min- and max-named packages among our dependencies ." ,
132+ var pkgdecls = & analysis.Analyzer {
133+ Name : "pkgdecls " ,
134+ Doc : "Computes a package fact mapping each package to its number of declarations ." ,
77135 Run : run ,
78- FactTypes : []analysis.Fact {(* minmaxpkgFact )(nil )},
136+ FactTypes : []analysis.Fact {(* pkgdeclsFact )(nil )},
79137}
80138
81- // A package fact that records the alphabetically min and max-named
82- // packages among the dependencies of this package.
83- // (This property was chosen because it is relatively stable
84- // as the codebase evolves, avoiding frequent test breakage.)
85- type minmaxpkgFact struct { min , max string }
139+ type pkgdeclsFact struct { numdecls map [string ]int }
86140
87- func (* minmaxpkgFact ) AFact () {}
141+ func (* pkgdeclsFact ) AFact () {}
88142
89143func run (pass * analysis.Pass ) (any , error ) {
90- // Compute the min and max of the facts from our direct imports.
91- f := & minmaxpkgFact {min : pass .Pkg .Path (), max : pass .Pkg .Path ()}
144+ numdecls := map [string ]int {
145+ pass .Pkg .Path (): pass .Pkg .Scope ().Len (),
146+ }
147+
148+ // Compute the union across all dependencies.
92149 for _ , imp := range pass .Pkg .Imports () {
93- if f2 := new (minmaxpkgFact ); pass .ImportPackageFact (imp , f2 ) {
94- if f2 .min < f .min {
95- f .min = f2 .min
96- }
97- if f2 .max > f .max {
98- f .max = f2 .max
99- }
150+ if depFact := new (pkgdeclsFact ); pass .ImportPackageFact (imp , depFact ) {
151+ maps .Copy (numdecls , depFact .numdecls )
100152 }
101153 }
102- pass .ExportPackageFact (f )
154+
155+ pass .ExportPackageFact (& pkgdeclsFact {numdecls })
156+
103157 return nil , nil
104158}
0 commit comments