github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/analysis/internal/checker/checker_test.go (about)

     1  // Copyright 2019 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package checker_test
     6  
     7  import (
     8  	"fmt"
     9  	"go/ast"
    10  	"io/ioutil"
    11  	"path/filepath"
    12  	"testing"
    13  
    14  	"golang.org/x/tools/go/analysis"
    15  	"golang.org/x/tools/go/analysis/analysistest"
    16  	"golang.org/x/tools/go/analysis/internal/checker"
    17  	"golang.org/x/tools/go/analysis/passes/inspect"
    18  	"golang.org/x/tools/go/ast/inspector"
    19  	"golang.org/x/tools/internal/testenv"
    20  )
    21  
    22  func TestApplyFixes(t *testing.T) {
    23  	testenv.NeedsGoPackages(t)
    24  
    25  	files := map[string]string{
    26  		"rename/test.go": `package rename
    27  
    28  func Foo() {
    29  	bar := 12
    30  	_ = bar
    31  }
    32  
    33  // the end
    34  `}
    35  	want := `package rename
    36  
    37  func Foo() {
    38  	baz := 12
    39  	_ = baz
    40  }
    41  
    42  // the end
    43  `
    44  
    45  	testdata, cleanup, err := analysistest.WriteFiles(files)
    46  	if err != nil {
    47  		t.Fatal(err)
    48  	}
    49  	path := filepath.Join(testdata, "src/rename/test.go")
    50  	checker.Fix = true
    51  	checker.Run([]string{"file=" + path}, []*analysis.Analyzer{analyzer})
    52  
    53  	contents, err := ioutil.ReadFile(path)
    54  	if err != nil {
    55  		t.Fatal(err)
    56  	}
    57  
    58  	got := string(contents)
    59  	if got != want {
    60  		t.Errorf("contents of rewritten file\ngot: %s\nwant: %s", got, want)
    61  	}
    62  
    63  	defer cleanup()
    64  }
    65  
    66  var analyzer = &analysis.Analyzer{
    67  	Name:     "rename",
    68  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    69  	Run:      run,
    70  }
    71  
    72  var other = &analysis.Analyzer{ // like analyzer but with a different Name.
    73  	Name:     "other",
    74  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    75  	Run:      run,
    76  }
    77  
    78  func run(pass *analysis.Pass) (interface{}, error) {
    79  	const (
    80  		from      = "bar"
    81  		to        = "baz"
    82  		conflict  = "conflict"  // add conflicting edits to package conflict.
    83  		duplicate = "duplicate" // add duplicate edits to package conflict.
    84  		other     = "other"     // add conflicting edits to package other from different analyzers.
    85  	)
    86  
    87  	if pass.Analyzer.Name == other {
    88  		if pass.Pkg.Name() != other {
    89  			return nil, nil // only apply Analyzer other to packages named other
    90  		}
    91  	}
    92  
    93  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    94  	nodeFilter := []ast.Node{(*ast.Ident)(nil)}
    95  	inspect.Preorder(nodeFilter, func(n ast.Node) {
    96  		ident := n.(*ast.Ident)
    97  		if ident.Name == from {
    98  			msg := fmt.Sprintf("renaming %q to %q", from, to)
    99  			edits := []analysis.TextEdit{
   100  				{Pos: ident.Pos(), End: ident.End(), NewText: []byte(to)},
   101  			}
   102  			switch pass.Pkg.Name() {
   103  			case conflict:
   104  				edits = append(edits, []analysis.TextEdit{
   105  					{Pos: ident.Pos() - 1, End: ident.End(), NewText: []byte(to)},
   106  					{Pos: ident.Pos(), End: ident.End() - 1, NewText: []byte(to)},
   107  					{Pos: ident.Pos(), End: ident.End(), NewText: []byte("lorem ipsum")},
   108  				}...)
   109  			case duplicate:
   110  				edits = append(edits, edits...)
   111  			case other:
   112  				if pass.Analyzer.Name == other {
   113  					edits[0].Pos = edits[0].Pos + 1 // shift by one to mismatch analyzer and other
   114  				}
   115  			}
   116  			pass.Report(analysis.Diagnostic{
   117  				Pos:            ident.Pos(),
   118  				End:            ident.End(),
   119  				Message:        msg,
   120  				SuggestedFixes: []analysis.SuggestedFix{{Message: msg, TextEdits: edits}}})
   121  		}
   122  	})
   123  
   124  	return nil, nil
   125  }
   126  
   127  func TestRunDespiteErrors(t *testing.T) {
   128  	testenv.NeedsGoPackages(t)
   129  
   130  	files := map[string]string{
   131  		"rderr/test.go": `package rderr
   132  
   133  // Foo deliberately has a type error
   134  func Foo(s string) int {
   135  	return s + 1
   136  }
   137  `}
   138  
   139  	testdata, cleanup, err := analysistest.WriteFiles(files)
   140  	if err != nil {
   141  		t.Fatal(err)
   142  	}
   143  	path := filepath.Join(testdata, "src/rderr/test.go")
   144  
   145  	// A no-op analyzer that should finish regardless of
   146  	// parse or type errors in the code.
   147  	noop := &analysis.Analyzer{
   148  		Name:     "noop",
   149  		Requires: []*analysis.Analyzer{inspect.Analyzer},
   150  		Run: func(pass *analysis.Pass) (interface{}, error) {
   151  			return nil, nil
   152  		},
   153  		RunDespiteErrors: true,
   154  	}
   155  
   156  	// A no-op analyzer that should finish regardless of
   157  	// parse or type errors in the code.
   158  	noopWithFact := &analysis.Analyzer{
   159  		Name:     "noopfact",
   160  		Requires: []*analysis.Analyzer{inspect.Analyzer},
   161  		Run: func(pass *analysis.Pass) (interface{}, error) {
   162  			return nil, nil
   163  		},
   164  		RunDespiteErrors: true,
   165  		FactTypes:        []analysis.Fact{&EmptyFact{}},
   166  	}
   167  
   168  	for _, test := range []struct {
   169  		name      string
   170  		pattern   []string
   171  		analyzers []*analysis.Analyzer
   172  		code      int
   173  	}{
   174  		// parse/type errors
   175  		{name: "skip-error", pattern: []string{"file=" + path}, analyzers: []*analysis.Analyzer{analyzer}, code: 1},
   176  		// RunDespiteErrors allows a driver to run an Analyzer even after parse/type errors.
   177  		//
   178  		// The noop analyzer doesn't use facts, so the driver loads only the root
   179  		// package from source. For the rest, it asks 'go list' for export data,
   180  		// which fails because the compiler encounters the type error.  Since the
   181  		// errors come from 'go list', the driver doesn't run the analyzer.
   182  		{name: "despite-error", pattern: []string{"file=" + path}, analyzers: []*analysis.Analyzer{noop}, code: 1},
   183  		// The noopfact analyzer does use facts, so the driver loads source for
   184  		// all dependencies, does type checking itself, recognizes the error as a
   185  		// type error, and runs the analyzer.
   186  		{name: "despite-error-fact", pattern: []string{"file=" + path}, analyzers: []*analysis.Analyzer{noopWithFact}, code: 0},
   187  		// combination of parse/type errors and no errors
   188  		{name: "despite-error-and-no-error", pattern: []string{"file=" + path, "sort"}, analyzers: []*analysis.Analyzer{analyzer, noop}, code: 1},
   189  		// non-existing package error
   190  		{name: "no-package", pattern: []string{"xyz"}, analyzers: []*analysis.Analyzer{analyzer}, code: 1},
   191  		{name: "no-package-despite-error", pattern: []string{"abc"}, analyzers: []*analysis.Analyzer{noop}, code: 1},
   192  		{name: "no-multi-package-despite-error", pattern: []string{"xyz", "abc"}, analyzers: []*analysis.Analyzer{noop}, code: 1},
   193  		// combination of type/parsing and different errors
   194  		{name: "different-errors", pattern: []string{"file=" + path, "xyz"}, analyzers: []*analysis.Analyzer{analyzer, noop}, code: 1},
   195  		// non existing dir error
   196  		{name: "no-match-dir", pattern: []string{"file=non/existing/dir"}, analyzers: []*analysis.Analyzer{analyzer, noop}, code: 1},
   197  		// no errors
   198  		{name: "no-errors", pattern: []string{"sort"}, analyzers: []*analysis.Analyzer{analyzer, noop}, code: 0},
   199  	} {
   200  		if test.name == "despite-error" && testenv.Go1Point() < 20 {
   201  			// The behavior in the comment on the despite-error test only occurs for Go 1.20+.
   202  			continue
   203  		}
   204  		if got := checker.Run(test.pattern, test.analyzers); got != test.code {
   205  			t.Errorf("got incorrect exit code %d for test %s; want %d", got, test.name, test.code)
   206  		}
   207  	}
   208  
   209  	defer cleanup()
   210  }
   211  
   212  type EmptyFact struct{}
   213  
   214  func (f *EmptyFact) AFact() {}