github.com/ferretdb/golangci-lint@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 }