improve typecheck errors parsing

This commit is contained in:
Denis Isaev 2018-11-23 18:23:24 +03:00 committed by Isaev Denis
parent 55a18ae18a
commit dba3907ff3
7 changed files with 72 additions and 42 deletions

View File

@ -88,7 +88,7 @@ func (m Megacheck) Run(ctx context.Context, lintCtx *linter.Context) ([]result.I
var errors []packages.Error var errors []packages.Error
for _, p := range lintCtx.NotCompilingPackages { for _, p := range lintCtx.NotCompilingPackages {
errPkgs = append(errPkgs, p.String()) errPkgs = append(errPkgs, p.String())
errors = append(errors, libpackages.ExtractErrors(p)...) errors = append(errors, libpackages.ExtractErrors(p, lintCtx.ASTCache)...)
} }
warnText := fmt.Sprintf("Can't run megacheck because of compilation errors in packages %s", warnText := fmt.Sprintf("Can't run megacheck because of compilation errors in packages %s",

View File

@ -2,12 +2,7 @@ package golinters
import ( import (
"context" "context"
"fmt"
"go/token"
"strconv"
"strings"
"github.com/pkg/errors"
"golang.org/x/tools/go/packages" "golang.org/x/tools/go/packages"
"github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/lint/linter"
@ -26,44 +21,31 @@ func (TypeCheck) Desc() string {
} }
func (lint TypeCheck) parseError(srcErr packages.Error) (*result.Issue, error) { func (lint TypeCheck) parseError(srcErr packages.Error) (*result.Issue, error) {
// file:line(<optional>:colon) pos, err := libpackages.ParseErrorPosition(srcErr.Pos)
parts := strings.Split(srcErr.Pos, ":")
if len(parts) == 1 {
return nil, errors.New("no colons")
}
file := parts[0]
line, err := strconv.Atoi(parts[1])
if err != nil { if err != nil {
return nil, fmt.Errorf("can't parse line number %q: %s", parts[1], err) return nil, err
}
var column int
if len(parts) == 3 { // no column
column, err = strconv.Atoi(parts[2])
if err != nil {
return nil, errors.Wrapf(err, "failed to parse column from %q", parts[2])
}
} }
return &result.Issue{ return &result.Issue{
Pos: token.Position{ Pos: *pos,
Filename: file,
Line: line,
Column: column,
},
Text: srcErr.Msg, Text: srcErr.Msg,
FromLinter: lint.Name(), FromLinter: lint.Name(),
}, nil }, nil
} }
func (lint TypeCheck) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { func (lint TypeCheck) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
uniqReportedIssues := map[string]bool{}
var res []result.Issue var res []result.Issue
for _, pkg := range lintCtx.NotCompilingPackages { for _, pkg := range lintCtx.NotCompilingPackages {
errors := libpackages.ExtractErrors(pkg) errors := libpackages.ExtractErrors(pkg, lintCtx.ASTCache)
for _, err := range errors { for _, err := range errors {
i, perr := lint.parseError(err) i, perr := lint.parseError(err)
if perr != nil { // failed to parse if perr != nil { // failed to parse
if uniqReportedIssues[err.Msg] {
continue
}
uniqReportedIssues[err.Msg] = true
lintCtx.Log.Errorf("typechecking error: %s", err.Msg) lintCtx.Log.Errorf("typechecking error: %s", err.Msg)
} else { } else {
res = append(res, *i) res = append(res, *i)

View File

@ -334,7 +334,7 @@ func (cl ContextLoader) Load(ctx context.Context, linters []linter.Config) (*lin
} else { } else {
for _, pkg := range pkgs { for _, pkg := range pkgs {
if pkg.IllTyped { if pkg.IllTyped {
cl.log.Infof("Pkg %s errors: %v", pkg.ID, libpackages.ExtractErrors(pkg)) cl.log.Infof("Pkg %s errors: %v", pkg.ID, libpackages.ExtractErrors(pkg, astCache))
} }
} }
} }

38
pkg/packages/errors.go Normal file
View File

@ -0,0 +1,38 @@
package packages
import (
"fmt"
"go/token"
"strconv"
"strings"
"github.com/pkg/errors"
)
func ParseErrorPosition(pos string) (*token.Position, error) {
// file:line(<optional>:colon)
parts := strings.Split(pos, ":")
if len(parts) == 1 {
return nil, errors.New("no colons")
}
file := parts[0]
line, err := strconv.Atoi(parts[1])
if err != nil {
return nil, fmt.Errorf("can't parse line number %q: %s", parts[1], err)
}
var column int
if len(parts) == 3 { // no column
column, err = strconv.Atoi(parts[2])
if err != nil {
return nil, errors.Wrapf(err, "failed to parse column from %q", parts[2])
}
}
return &token.Position{
Filename: file,
Line: line,
Column: column,
}, nil
}

View File

@ -3,10 +3,13 @@ package packages
import ( import (
"fmt" "fmt"
"github.com/golangci/golangci-lint/pkg/lint/astcache"
"golang.org/x/tools/go/packages" "golang.org/x/tools/go/packages"
) )
func ExtractErrors(pkg *packages.Package) []packages.Error { //nolint:gocyclo
func ExtractErrors(pkg *packages.Package, astCache *astcache.Cache) []packages.Error {
errors := extractErrorsImpl(pkg) errors := extractErrorsImpl(pkg)
if len(errors) == 0 { if len(errors) == 0 {
return errors return errors
@ -22,17 +25,18 @@ func ExtractErrors(pkg *packages.Package) []packages.Error {
uniqErrors = append(uniqErrors, err) uniqErrors = append(uniqErrors, err)
} }
if len(pkg.Errors) == 0 && len(pkg.GoFiles) != 0 {
// erorrs were extracted from deps and have at leat one file in package
for i := range uniqErrors {
// change pos to local file to properly process it by processors (properly read line etc)
uniqErrors[i].Msg = fmt.Sprintf("%s: %s", uniqErrors[i].Pos, uniqErrors[i].Msg)
uniqErrors[i].Pos = fmt.Sprintf("%s:1", pkg.GoFiles[0])
}
}
// some errors like "code in directory expects import" don't have Pos, set it here
if len(pkg.GoFiles) != 0 { if len(pkg.GoFiles) != 0 {
// errors were extracted from deps and have at leat one file in package
for i := range uniqErrors {
errPos, parseErr := ParseErrorPosition(uniqErrors[i].Pos)
if parseErr != nil || astCache.Get(errPos.Filename) == nil {
// change pos to local file to properly process it by processors (properly read line etc)
uniqErrors[i].Msg = fmt.Sprintf("%s: %s", uniqErrors[i].Pos, uniqErrors[i].Msg)
uniqErrors[i].Pos = fmt.Sprintf("%s:1", pkg.GoFiles[0])
}
}
// some errors like "code in directory expects import" don't have Pos, set it here
for i := range uniqErrors { for i := range uniqErrors {
err := &uniqErrors[i] err := &uniqErrors[i]
if err.Pos == "" { if err.Pos == "" {

View File

@ -42,6 +42,11 @@ func (p *AutogeneratedExclude) Process(issues []result.Issue) ([]result.Issue, e
} }
func (p *AutogeneratedExclude) shouldPassIssue(i *result.Issue) (bool, error) { func (p *AutogeneratedExclude) shouldPassIssue(i *result.Issue) (bool, error) {
if i.FromLinter == "typecheck" {
// don't hide typechecking errors in generated files: users expect to see why the project isn't compiling
return true, nil
}
fs, err := p.getOrCreateFileSummary(i) fs, err := p.getOrCreateFileSummary(i)
if err != nil { if err != nil {
return false, err return false, err

View File

@ -24,7 +24,8 @@ func TestEmptyDirRun(t *testing.T) {
func TestNotExistingDirRun(t *testing.T) { func TestNotExistingDirRun(t *testing.T) {
testshared.NewLintRunner(t).Run(getTestDataDir("no_such_dir")). testshared.NewLintRunner(t).Run(getTestDataDir("no_such_dir")).
ExpectHasIssue(`cannot find package \"./testdata/no_such_dir\"`) ExpectExitCode(exitcodes.WarningInTest).
ExpectOutputContains(`cannot find package \"./testdata/no_such_dir\"`)
} }
func TestSymlinkLoop(t *testing.T) { func TestSymlinkLoop(t *testing.T) {