www.github.com/golangci/golangci-lint.git@v1.10.1/pkg/golinters/govet.go (about)

     1  package golinters
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"go/ast"
     7  	"go/token"
     8  	"os"
     9  	"os/exec"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/golangci/golangci-lint/pkg/fsutils"
    14  	"github.com/golangci/golangci-lint/pkg/goutils"
    15  	"github.com/golangci/golangci-lint/pkg/lint/linter"
    16  	"github.com/golangci/golangci-lint/pkg/logutils"
    17  	"github.com/golangci/golangci-lint/pkg/result"
    18  	"github.com/golangci/golangci-lint/pkg/timeutils"
    19  	govetAPI "github.com/golangci/govet"
    20  )
    21  
    22  type Govet struct{}
    23  
    24  func (Govet) Name() string {
    25  	return "govet"
    26  }
    27  
    28  func (Govet) Desc() string {
    29  	return "Vet examines Go source code and reports suspicious constructs, " +
    30  		"such as Printf calls whose arguments do not align with the format string"
    31  }
    32  
    33  func (g Govet) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
    34  	var govetIssues []govetAPI.Issue
    35  	var err error
    36  	if lintCtx.Settings().Govet.UseInstalledPackages {
    37  		govetIssues, err = g.runOnInstalledPackages(ctx, lintCtx)
    38  		if err != nil {
    39  			return nil, fmt.Errorf("can't run govet on installed packages: %s", err)
    40  		}
    41  	} else {
    42  		govetIssues, err = g.runOnSourcePackages(ctx, lintCtx)
    43  		if err != nil {
    44  			return nil, fmt.Errorf("can't run govet on source packages: %s", err)
    45  		}
    46  	}
    47  
    48  	if len(govetIssues) == 0 {
    49  		return nil, nil
    50  	}
    51  
    52  	res := make([]result.Issue, 0, len(govetIssues))
    53  	for _, i := range govetIssues {
    54  		res = append(res, result.Issue{
    55  			Pos:        i.Pos,
    56  			Text:       markIdentifiers(i.Message),
    57  			FromLinter: g.Name(),
    58  		})
    59  	}
    60  	return res, nil
    61  }
    62  
    63  func (g Govet) runOnInstalledPackages(ctx context.Context, lintCtx *linter.Context) ([]govetAPI.Issue, error) {
    64  	if err := g.installPackages(ctx, lintCtx); err != nil {
    65  		return nil, fmt.Errorf("can't install packages (it's required for govet): %s", err)
    66  	}
    67  
    68  	// TODO: check .S asm files: govet can do it if pass dirs
    69  	var govetIssues []govetAPI.Issue
    70  	for _, pkg := range lintCtx.PkgProgram.Packages() {
    71  		var astFiles []*ast.File
    72  		var fset *token.FileSet
    73  		for _, fname := range pkg.Files(lintCtx.Cfg.Run.AnalyzeTests) {
    74  			af := lintCtx.ASTCache.Get(fname)
    75  			if af == nil || af.Err != nil {
    76  				return nil, fmt.Errorf("can't get parsed file %q from ast cache: %#v", fname, af)
    77  			}
    78  			astFiles = append(astFiles, af.F)
    79  			fset = af.Fset
    80  		}
    81  		if len(astFiles) == 0 {
    82  			continue
    83  		}
    84  		issues, err := govetAPI.Analyze(astFiles, fset, nil,
    85  			lintCtx.Settings().Govet.CheckShadowing, getPath)
    86  		if err != nil {
    87  			return nil, err
    88  		}
    89  		govetIssues = append(govetIssues, issues...)
    90  	}
    91  
    92  	return govetIssues, nil
    93  }
    94  
    95  func (g Govet) installPackages(ctx context.Context, lintCtx *linter.Context) error {
    96  	inGoRoot, err := goutils.InGoRoot()
    97  	if err != nil {
    98  		return fmt.Errorf("can't check whether we are in $GOROOT: %s", err)
    99  	}
   100  
   101  	if inGoRoot {
   102  		// Go source packages already should be installed into $GOROOT/pkg with go distribution
   103  		lintCtx.Log.Infof("In $GOROOT, don't install packages")
   104  		return nil
   105  	}
   106  
   107  	if err := g.installNonTestPackages(ctx, lintCtx); err != nil {
   108  		return err
   109  	}
   110  
   111  	if err := g.installTestDependencies(ctx, lintCtx); err != nil {
   112  		return err
   113  	}
   114  
   115  	return nil
   116  }
   117  
   118  func (g Govet) installTestDependencies(ctx context.Context, lintCtx *linter.Context) error {
   119  	log := lintCtx.Log
   120  	packages := lintCtx.PkgProgram.Packages()
   121  	var testDirs []string
   122  	for _, pkg := range packages {
   123  		dir := pkg.Dir()
   124  		if dir == "" {
   125  			log.Warnf("Package %#v has empty dir", pkg)
   126  			continue
   127  		}
   128  
   129  		if !strings.HasPrefix(dir, ".") {
   130  			// go install can't work without that
   131  			dir = "./" + dir
   132  		}
   133  
   134  		if len(pkg.TestFiles()) != 0 {
   135  			testDirs = append(testDirs, dir)
   136  		}
   137  	}
   138  
   139  	if len(testDirs) == 0 {
   140  		log.Infof("No test files in packages %#v", packages)
   141  		return nil
   142  	}
   143  
   144  	args := append([]string{"test", "-i"}, testDirs...)
   145  	return runGoCommand(ctx, log, args...)
   146  }
   147  
   148  func (g Govet) installNonTestPackages(ctx context.Context, lintCtx *linter.Context) error {
   149  	log := lintCtx.Log
   150  	packages := lintCtx.PkgProgram.Packages()
   151  	var importPaths []string
   152  	for _, pkg := range packages {
   153  		if pkg.IsTestOnly() {
   154  			// test-only package will be processed by installTestDependencies
   155  			continue
   156  		}
   157  
   158  		dir := pkg.Dir()
   159  		if dir == "" {
   160  			log.Warnf("Package %#v has empty dir", pkg)
   161  			continue
   162  		}
   163  
   164  		if !strings.HasPrefix(dir, ".") {
   165  			// go install can't work without that
   166  			dir = "./" + dir
   167  		}
   168  
   169  		importPaths = append(importPaths, dir)
   170  	}
   171  
   172  	if len(importPaths) == 0 {
   173  		log.Infof("No packages to install, all packages: %#v", packages)
   174  		return nil
   175  	}
   176  
   177  	// we need type information of dependencies of analyzed packages
   178  	// so we pass -i option to install it
   179  	if err := runGoInstall(ctx, log, importPaths, true); err != nil {
   180  		// try without -i option: go < 1.10 doesn't support this option
   181  		// and install dependencies by default.
   182  		return runGoInstall(ctx, log, importPaths, false)
   183  	}
   184  
   185  	return nil
   186  }
   187  
   188  func runGoInstall(ctx context.Context, log logutils.Log, importPaths []string, withIOption bool) error {
   189  	args := []string{"install"}
   190  	if withIOption {
   191  		args = append(args, "-i")
   192  	}
   193  	args = append(args, importPaths...)
   194  
   195  	return runGoCommand(ctx, log, args...)
   196  }
   197  
   198  func runGoCommand(ctx context.Context, log logutils.Log, args ...string) error {
   199  	argsStr := strings.Join(args, " ")
   200  	defer timeutils.Track(time.Now(), log, "go %s", argsStr)
   201  
   202  	cmd := exec.CommandContext(ctx, "go", args...)
   203  	cmd.Env = append([]string{}, os.Environ()...)
   204  	cmd.Env = append(cmd.Env, "GOMAXPROCS=1") // don't consume more than 1 cpu
   205  
   206  	// use .Output but not .Run to capture StdErr in err
   207  	_, err := cmd.Output()
   208  	if err != nil {
   209  		var stderr string
   210  		if ee, ok := err.(*exec.ExitError); ok && ee.Stderr != nil {
   211  			stderr = ": " + string(ee.Stderr)
   212  		}
   213  
   214  		return fmt.Errorf("can't run [go %s]: %s%s", argsStr, err, stderr)
   215  	}
   216  
   217  	return nil
   218  }
   219  
   220  func filterFiles(files []*ast.File, fset *token.FileSet) []*ast.File {
   221  	newFiles := make([]*ast.File, 0, len(files))
   222  	for _, f := range files {
   223  		if !goutils.IsCgoFilename(fset.Position(f.Pos()).Filename) {
   224  			newFiles = append(newFiles, f)
   225  		}
   226  	}
   227  
   228  	return newFiles
   229  }
   230  
   231  func (g Govet) runOnSourcePackages(_ context.Context, lintCtx *linter.Context) ([]govetAPI.Issue, error) {
   232  	// TODO: check .S asm files: govet can do it if pass dirs
   233  	var govetIssues []govetAPI.Issue
   234  	for _, pkg := range lintCtx.Program.InitialPackages() {
   235  		if len(pkg.Files) == 0 {
   236  			continue
   237  		}
   238  
   239  		filteredFiles := filterFiles(pkg.Files, lintCtx.Program.Fset)
   240  		issues, err := govetAPI.Analyze(filteredFiles, lintCtx.Program.Fset, pkg,
   241  			lintCtx.Settings().Govet.CheckShadowing, getPath)
   242  		if err != nil {
   243  			return nil, err
   244  		}
   245  		govetIssues = append(govetIssues, issues...)
   246  	}
   247  
   248  	return govetIssues, nil
   249  }
   250  
   251  func getPath(f *ast.File, fset *token.FileSet) (string, error) {
   252  	return fsutils.ShortestRelPath(fset.Position(f.Pos()).Filename, "")
   253  }