@@ -23,7 +23,8 @@ import (
23
23
"text/scanner"
24
24
25
25
"golang.org/x/tools/go/analysis"
26
- "golang.org/x/tools/go/analysis/internal/checker"
26
+ "golang.org/x/tools/go/analysis/checker"
27
+ "golang.org/x/tools/go/analysis/internal"
27
28
"golang.org/x/tools/go/packages"
28
29
"golang.org/x/tools/internal/diff"
29
30
"golang.org/x/tools/internal/testenv"
@@ -137,7 +138,7 @@ type Testing interface {
137
138
// analyzers that offer alternative fixes are advised to put each fix
138
139
// in a separate .go file in the testdata.
139
140
func RunWithSuggestedFixes (t Testing , dir string , a * analysis.Analyzer , patterns ... string ) []* Result {
140
- r := Run (t , dir , a , patterns ... )
141
+ results := Run (t , dir , a , patterns ... )
141
142
142
143
// If the immediate caller of RunWithSuggestedFixes is in
143
144
// x/tools, we apply stricter checks as required by gopls.
@@ -162,7 +163,9 @@ func RunWithSuggestedFixes(t Testing, dir string, a *analysis.Analyzer, patterns
162
163
// Validating the results separately means as long as the two analyses
163
164
// don't produce conflicting suggestions for a single file, everything
164
165
// should match up.
165
- for _ , act := range r {
166
+ for _ , result := range results {
167
+ act := result .Action
168
+
166
169
// file -> message -> edits
167
170
fileEdits := make (map [* token.File ]map [string ][]diff.Edit )
168
171
fileContents := make (map [* token.File ][]byte )
@@ -185,14 +188,14 @@ func RunWithSuggestedFixes(t Testing, dir string, a *analysis.Analyzer, patterns
185
188
if start > end {
186
189
t .Errorf (
187
190
"diagnostic for analysis %v contains Suggested Fix with malformed edit: pos (%v) > end (%v)" ,
188
- act .Pass . Analyzer .Name , start , end )
191
+ act .Analyzer .Name , start , end )
189
192
continue
190
193
}
191
- file , endfile := act .Pass .Fset .File (start ), act .Pass .Fset .File (end )
194
+ file , endfile := act .Package .Fset .File (start ), act .Package .Fset .File (end )
192
195
if file == nil || endfile == nil || file != endfile {
193
196
t .Errorf (
194
197
"diagnostic for analysis %v contains Suggested Fix with malformed spanning files %v and %v" ,
195
- act .Pass . Analyzer .Name , file .Name (), endfile .Name ())
198
+ act .Analyzer .Name , file .Name (), endfile .Name ())
196
199
continue
197
200
}
198
201
if _ , ok := fileContents [file ]; ! ok {
@@ -275,7 +278,7 @@ func RunWithSuggestedFixes(t Testing, dir string, a *analysis.Analyzer, patterns
275
278
}
276
279
}
277
280
}
278
- return r
281
+ return results
279
282
}
280
283
281
284
// applyDiffsAndCompare applies edits to src and compares the results against
@@ -355,24 +358,76 @@ func Run(t Testing, dir string, a *analysis.Analyzer, patterns ...string) []*Res
355
358
return nil
356
359
}
357
360
358
- if err := analysis .Validate ([]* analysis.Analyzer {a }); err != nil {
359
- t .Errorf ("Validate: %v" , err )
361
+ // Print parse and type errors to the test log.
362
+ // (Do not print them to stderr, which would pollute
363
+ // the log in cases where the tests pass.)
364
+ if t , ok := t .(testing.TB ); ok && ! a .RunDespiteErrors {
365
+ packages .Visit (pkgs , nil , func (pkg * packages.Package ) {
366
+ for _ , err := range pkg .Errors {
367
+ t .Log (err )
368
+ }
369
+ })
370
+ }
371
+
372
+ res , err := checker .Analyze ([]* analysis.Analyzer {a }, pkgs , nil )
373
+ if err != nil {
374
+ t .Errorf ("Analyze: %v" , err )
360
375
return nil
361
376
}
362
377
363
- results := checker . TestAnalyzer ( a , pkgs )
364
- for _ , result := range results {
365
- if result .Err != nil {
366
- t .Errorf ("error analyzing %s: %v" , result . Pass , result .Err )
378
+ var results [] * Result
379
+ for _ , act := range res . Roots {
380
+ if act .Err != nil {
381
+ t .Errorf ("error analyzing %s: %v" , act , act .Err )
367
382
} else {
368
- check (t , dir , result . Pass , result . Diagnostics , result . Facts )
383
+ check (t , dir , act )
369
384
}
385
+
386
+ // Compute legacy map of facts relating to this package.
387
+ facts := make (map [types.Object ][]analysis.Fact )
388
+ for _ , objFact := range act .AllObjectFacts () {
389
+ if obj := objFact .Object ; obj .Pkg () == act .Package .Types {
390
+ facts [obj ] = append (facts [obj ], objFact .Fact )
391
+ }
392
+ }
393
+ for _ , pkgFact := range act .AllPackageFacts () {
394
+ if pkgFact .Package == act .Package .Types {
395
+ facts [nil ] = append (facts [nil ], pkgFact .Fact )
396
+ }
397
+ }
398
+
399
+ // Construct the legacy result.
400
+ results = append (results , & Result {
401
+ Pass : internal .Pass (act ),
402
+ Diagnostics : act .Diagnostics ,
403
+ Facts : facts ,
404
+ Result : act .Result ,
405
+ Err : act .Err ,
406
+ Action : act ,
407
+ })
370
408
}
371
409
return results
372
410
}
373
411
374
412
// A Result holds the result of applying an analyzer to a package.
375
- type Result = checker.TestAnalyzerResult
413
+ //
414
+ // Facts contains only facts associated with the package and its objects.
415
+ //
416
+ // This internal type was inadvertently and regrettably exposed
417
+ // through a public type alias. It is essentially redundant with
418
+ // [checker.Action], but must be retained for compatibility. Clients may
419
+ // access the public fields of the Pass but must not invoke any of
420
+ // its "verbs", since the pass is already complete.
421
+ type Result struct {
422
+ Action * checker.Action
423
+
424
+ // legacy fields
425
+ Facts map [types.Object ][]analysis.Fact // nil key => package fact
426
+ Pass * analysis.Pass
427
+ Diagnostics []analysis.Diagnostic // see Action.Diagnostics
428
+ Result any // see Action.Result
429
+ Err error // see Action.Err
430
+ }
376
431
377
432
// loadPackages uses go/packages to load a specified packages (from source, with
378
433
// dependencies) from dir, which is the root of a GOPATH-style project tree.
@@ -421,16 +476,6 @@ func loadPackages(a *analysis.Analyzer, dir string, patterns ...string) ([]*pack
421
476
}
422
477
}
423
478
424
- // Do NOT print errors if the analyzer will continue running.
425
- // It is incredibly confusing for tests to be printing to stderr
426
- // willy-nilly instead of their test logs, especially when the
427
- // errors are expected and are going to be fixed.
428
- if ! a .RunDespiteErrors {
429
- if packages .PrintErrors (pkgs ) > 0 {
430
- return nil , fmt .Errorf ("there were package loading errors (and RunDespiteErrors is false)" )
431
- }
432
- }
433
-
434
479
if len (pkgs ) == 0 {
435
480
return nil , fmt .Errorf ("no packages matched %s" , patterns )
436
481
}
@@ -441,7 +486,7 @@ func loadPackages(a *analysis.Analyzer, dir string, patterns ...string) ([]*pack
441
486
// been run, and verifies that all reported diagnostics and facts match
442
487
// specified by the contents of "// want ..." comments in the package's
443
488
// source files, which must have been parsed with comments enabled.
444
- func check (t Testing , gopath string , pass * analysis. Pass , diagnostics []analysis. Diagnostic , facts map [types. Object ][]analysis. Fact ) {
489
+ func check (t Testing , gopath string , act * checker. Action ) {
445
490
type key struct {
446
491
file string
447
492
line int
@@ -468,7 +513,7 @@ func check(t Testing, gopath string, pass *analysis.Pass, diagnostics []analysis
468
513
}
469
514
470
515
// Extract 'want' comments from parsed Go files.
471
- for _ , f := range pass . Files {
516
+ for _ , f := range act . Package . Syntax {
472
517
for _ , cgroup := range f .Comments {
473
518
for _ , c := range cgroup .List {
474
519
@@ -491,7 +536,7 @@ func check(t Testing, gopath string, pass *analysis.Pass, diagnostics []analysis
491
536
// once outside the loop, but it's
492
537
// incorrect because it can change due
493
538
// to //line directives.
494
- posn := pass .Fset .Position (c .Pos ())
539
+ posn := act . Package .Fset .Position (c .Pos ())
495
540
filename := sanitize (gopath , posn .Filename )
496
541
processComment (filename , posn .Line , text )
497
542
}
@@ -500,7 +545,17 @@ func check(t Testing, gopath string, pass *analysis.Pass, diagnostics []analysis
500
545
501
546
// Extract 'want' comments from non-Go files.
502
547
// TODO(adonovan): we may need to handle //line directives.
503
- for _ , filename := range pass .OtherFiles {
548
+ files := act .Package .OtherFiles
549
+
550
+ // Hack: these two analyzers need to extract expectations from
551
+ // all configurations, so include the files are are usually
552
+ // ignored. (This was previously a hack in the respective
553
+ // analyzers' tests.)
554
+ if act .Analyzer .Name == "buildtag" || act .Analyzer .Name == "directive" {
555
+ files = append (files [:len (files ):len (files )], act .Package .IgnoredFiles ... )
556
+ }
557
+
558
+ for _ , filename := range files {
504
559
data , err := os .ReadFile (filename )
505
560
if err != nil {
506
561
t .Errorf ("can't read '// want' comments from %s: %v" , filename , err )
@@ -553,45 +608,38 @@ func check(t Testing, gopath string, pass *analysis.Pass, diagnostics []analysis
553
608
}
554
609
555
610
// Check the diagnostics match expectations.
556
- for _ , f := range diagnostics {
611
+ for _ , f := range act . Diagnostics {
557
612
// TODO(matloob): Support ranges in analysistest.
558
- posn := pass .Fset .Position (f .Pos )
613
+ posn := act . Package .Fset .Position (f .Pos )
559
614
checkMessage (posn , "diagnostic" , "" , f .Message )
560
615
}
561
616
562
617
// Check the facts match expectations.
563
- // Report errors in lexical order for determinism.
618
+ // We check only facts relating to the current package.
619
+ //
620
+ // We report errors in lexical order for determinism.
564
621
// (It's only deterministic within each file, not across files,
565
622
// because go/packages does not guarantee file.Pos is ascending
566
623
// across the files of a single compilation unit.)
567
- var objects []types.Object
568
- for obj := range facts {
569
- objects = append (objects , obj )
570
- }
571
- sort .Slice (objects , func (i , j int ) bool {
572
- // Package facts compare less than object facts.
573
- ip , jp := objects [i ] == nil , objects [j ] == nil // whether i, j is a package fact
574
- if ip != jp {
575
- return ip && ! jp
576
- }
577
- return objects [i ].Pos () < objects [j ].Pos ()
578
- })
579
- for _ , obj := range objects {
580
- var posn token.Position
581
- var name string
582
- if obj != nil {
583
- // Object facts are reported on the declaring line.
584
- name = obj .Name ()
585
- posn = pass .Fset .Position (obj .Pos ())
586
- } else {
587
- // Package facts are reported at the start of the file.
588
- name = "package"
589
- posn = pass .Fset .Position (pass .Files [0 ].Pos ())
590
- posn .Line = 1
624
+
625
+ // package facts: reported at start of first file
626
+ for _ , pkgFact := range act .AllPackageFacts () {
627
+ if pkgFact .Package == act .Package .Types {
628
+ posn := act .Package .Fset .Position (act .Package .Syntax [0 ].Pos ())
629
+ posn .Line , posn .Column = 1 , 1
630
+ checkMessage (posn , "fact" , "package" , fmt .Sprint (pkgFact ))
591
631
}
632
+ }
592
633
593
- for _ , fact := range facts [obj ] {
594
- checkMessage (posn , "fact" , name , fmt .Sprint (fact ))
634
+ // object facts: reported at line of object declaration
635
+ objFacts := act .AllObjectFacts ()
636
+ sort .Slice (objFacts , func (i , j int ) bool {
637
+ return objFacts [i ].Object .Pos () < objFacts [j ].Object .Pos ()
638
+ })
639
+ for _ , objFact := range objFacts {
640
+ if obj := objFact .Object ; obj .Pkg () == act .Package .Types {
641
+ posn := act .Package .Fset .Position (obj .Pos ())
642
+ checkMessage (posn , "fact" , obj .Name (), fmt .Sprint (objFact .Fact ))
595
643
}
596
644
}
597
645
0 commit comments