Skip to content

#12: check compilation errors #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ golangci-lint linters
- [varcheck](https://github.com/opennota/check): Finds unused global variables and constants
- [ineffassign](https://github.com/gordonklaus/ineffassign): Detects when assignments to existing variables are not used
- [deadcode](https://github.com/remyoudompheng/go-misc/tree/master/deadcode): Finds unused code
- typecheck: Like the front-end of a Go compiler, parses and type-checks Go code. Similar to [gotype](https://godoc.org/golang.org/x/tools/cmd/gotype).

## Disabled By Default Linters (`-E/--enable`)
- [golint](https://github.com/golang/lint): Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes
Expand Down
10 changes: 5 additions & 5 deletions pkg/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func (e *Executor) initRoot() {
Long: `Smart, fast linters runner. Run it in cloud for every GitHub pull request on https://golangci.com`,
Run: func(cmd *cobra.Command, args []string) {
if err := cmd.Help(); err != nil {
log.Fatal(err)
logrus.Fatal(err)
}
},
PersistentPreRun: func(cmd *cobra.Command, args []string) {
Expand All @@ -33,10 +33,10 @@ func (e *Executor) initRoot() {
if e.cfg.Run.CPUProfilePath != "" {
f, err := os.Create(e.cfg.Run.CPUProfilePath)
if err != nil {
log.Fatal(err)
logrus.Fatal(err)
}
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal(err)
logrus.Fatal(err)
}
}
},
Expand All @@ -47,11 +47,11 @@ func (e *Executor) initRoot() {
if e.cfg.Run.MemProfilePath != "" {
f, err := os.Create(e.cfg.Run.MemProfilePath)
if err != nil {
log.Fatal(err)
logrus.Fatal(err)
}
runtime.GC() // get up-to-date statistics
if err := pprof.WriteHeapProfile(f); err != nil {
log.Fatal("could not write memory profile: ", err)
logrus.Fatal("could not write memory profile: ", err)
}
}

Expand Down
36 changes: 28 additions & 8 deletions pkg/commands/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"go/build"
"go/token"
"io/ioutil"
"log"
"os"
"runtime"
Expand Down Expand Up @@ -245,7 +246,26 @@ func (e *Executor) runAnalysis(ctx context.Context, args []string) (<-chan resul
return runner.Run(ctx, linters, lintCtx), nil
}

func setOutputToDevNull() (savedStdout, savedStderr *os.File) {
savedStdout, savedStderr = os.Stdout, os.Stderr
devNull, err := os.Open(os.DevNull)
if err != nil {
logrus.Warnf("can't open null device %q: %s", os.DevNull, err)
return
}

os.Stdout, os.Stderr = devNull, devNull
return
}

func (e *Executor) runAndPrint(ctx context.Context, args []string) error {
// Don't allow linters and loader to print anything
log.SetOutput(ioutil.Discard)
savedStdout, savedStderr := setOutputToDevNull()
defer func() {
os.Stdout, os.Stderr = savedStdout, savedStderr
}()

issues, err := e.runAnalysis(ctx, args)
if err != nil {
return err
Expand Down Expand Up @@ -288,11 +308,11 @@ func (e *Executor) executeRun(cmd *cobra.Command, args []string) {
}

if e.cfg.Output.PrintWelcomeMessage {
fmt.Println("Run this tool in cloud on every github pull request in https://golangci.com for free (public repos)")
fmt.Fprintln(printers.StdOut, "Run this tool in cloud on every github pull request in https://golangci.com for free (public repos)")
}

if err := e.runAndPrint(ctx, args); err != nil {
log.Print(err)
logrus.Warnf("running error: %s", err)
if e.exitCode == 0 {
e.exitCode = exitCodeIfFailure
}
Expand All @@ -305,11 +325,11 @@ func (e *Executor) parseConfig(cmd *cobra.Command) {
if err == pflag.ErrHelp {
return
}
log.Fatalf("Can't parse args: %s", err)
logrus.Fatalf("Can't parse args: %s", err)
}

if err := viper.BindPFlags(cmd.Flags()); err != nil {
log.Fatalf("Can't bind cobra's flags to viper: %s", err)
logrus.Fatalf("Can't bind cobra's flags to viper: %s", err)
}

viper.SetEnvPrefix("GOLANGCI")
Expand All @@ -318,7 +338,7 @@ func (e *Executor) parseConfig(cmd *cobra.Command) {

configFile := e.cfg.Run.Config
if e.cfg.Run.NoConfig && configFile != "" {
log.Fatal("can't combine option --config and --no-config")
logrus.Fatal("can't combine option --config and --no-config")
}

if e.cfg.Run.NoConfig {
Expand All @@ -342,15 +362,15 @@ func (e *Executor) parseConfigImpl() {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
return
}
log.Fatalf("Can't read viper config: %s", err)
logrus.Fatalf("Can't read viper config: %s", err)
}

if err := viper.Unmarshal(&e.cfg); err != nil {
log.Fatalf("Can't unmarshal config by viper: %s", err)
logrus.Fatalf("Can't unmarshal config by viper: %s", err)
}

if err := e.validateConfig(&commandLineConfig); err != nil {
log.Fatal(err)
logrus.Fatal(err)
}
}

Expand Down
29 changes: 16 additions & 13 deletions pkg/enabled_linters.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func GetAllSupportedLinterConfigs() []LinterConfig {
newLinterConfig(golinters.Goconst{}).WithPresets(PresetStyle).WithSpeed(9),
newLinterConfig(golinters.Deadcode{}).WithFullImport().WithPresets(PresetUnused).WithSpeed(10),
newLinterConfig(golinters.Gocyclo{}).WithPresets(PresetComplexity).WithSpeed(8),
newLinterConfig(golinters.TypeCheck{}).WithFullImport().WithPresets(PresetBugs).WithSpeed(10),

newLinterConfig(golinters.Gofmt{}).WithPresets(PresetFormatting).WithSpeed(7),
newLinterConfig(golinters.Gofmt{UseGoimports: true}).WithPresets(PresetFormatting).WithSpeed(5),
Expand All @@ -128,26 +129,28 @@ func GetAllSupportedLinterConfigs() []LinterConfig {

if os.Getenv("GOLANGCI_COM_RUN") == "1" {
disabled := map[string]bool{
"gocyclo": true,
"dupl": true,
"maligned": true,
golinters.Gocyclo{}.Name(): true, // annoying
golinters.Dupl{}.Name(): true, // annoying
golinters.Maligned{}.Name(): true, // rarely usable
golinters.TypeCheck{}.Name(): true, // annoying because of different building envs
}
return enableLinterConfigs(lcs, func(lc *LinterConfig) bool {
return !disabled[lc.Linter.Name()]
})
}

enabled := map[string]bool{
"govet": true,
"errcheck": true,
"staticcheck": true,
"unused": true,
"gosimple": true,
"gas": true,
"structcheck": true,
"varcheck": true,
"ineffassign": true,
"deadcode": true,
golinters.Govet{}.Name(): true,
golinters.Errcheck{}.Name(): true,
golinters.Megacheck{StaticcheckEnabled: true}.Name(): true,
golinters.Megacheck{UnusedEnabled: true}.Name(): true,
golinters.Megacheck{GosimpleEnabled: true}.Name(): true,
golinters.Gas{}.Name(): true,
golinters.Structcheck{}.Name(): true,
golinters.Varcheck{}.Name(): true,
golinters.Ineffassign{}.Name(): true,
golinters.Deadcode{}.Name(): true,
golinters.TypeCheck{}.Name(): true,
}
return enableLinterConfigs(lcs, func(lc *LinterConfig) bool {
return enabled[lc.Linter.Name()]
Expand Down
7 changes: 0 additions & 7 deletions pkg/enabled_linters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ func runGoErrchk(c *exec.Cmd, t *testing.T) {
const testdataDir = "testdata"

var testdataWithIssuesDir = filepath.Join(testdataDir, "with_issues")
var testdataNotCompilingDir = filepath.Join(testdataDir, "not_compiles")

const binName = "golangci-lint"

Expand Down Expand Up @@ -72,12 +71,6 @@ func testOneSource(t *testing.T, sourcePath string) {
runGoErrchk(cmd, t)
}

func TestNotCompilingProgram(t *testing.T) {
installBinary(t)
err := exec.Command(binName, "run", "--enable-all", testdataNotCompilingDir).Run()
assert.NoError(t, err)
}

func chdir(b *testing.B, dir string) {
if err := os.Chdir(dir); err != nil {
b.Fatalf("can't chdir to %s: %s", dir, err)
Expand Down
2 changes: 1 addition & 1 deletion pkg/golinters/golint.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (g Golint) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, erro
issues = append(issues, i...)
}
if lintErr != nil {
logrus.Warnf("golint: %s", lintErr)
logrus.Infof("golint: %s", lintErr)
}

return issues, nil
Expand Down
76 changes: 76 additions & 0 deletions pkg/golinters/typecheck.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package golinters

import (
"context"
"go/token"
"strconv"
"strings"

"github.com/golangci/golangci-lint/pkg/result"
)

type TypeCheck struct{}

func (TypeCheck) Name() string {
return "typecheck"
}

func (TypeCheck) Desc() string {
return "Like the front-end of a Go compiler, parses and type-checks Go code"
}

func (lint TypeCheck) parseError(err error) *result.Issue {
// file:line(<optional>:colon): message
parts := strings.Split(err.Error(), ":")
if len(parts) < 3 {
return nil
}

file := parts[0]
line, err := strconv.Atoi(parts[1])
if err != nil {
return nil
}

var column int
var message string
if len(parts) == 3 { // no column
message = parts[2]
} else {
column, err = strconv.Atoi(parts[2])
if err == nil { // column was parsed
message = strings.Join(parts[3:], ":")
} else {
message = strings.Join(parts[2:], ":")
}
}

message = strings.TrimSpace(message)
if message == "" {
return nil
}

return &result.Issue{
Pos: token.Position{
Filename: file,
Line: line,
Column: column,
},
Text: message,
FromLinter: lint.Name(),
}
}

func (lint TypeCheck) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) {
var res []result.Issue
for _, pkg := range lintCtx.Program.InitialPackages() {
for _, err := range pkg.Errors {
i := lint.parseError(err)
if i != nil {
res = append(res, *i)
}
}
}

return res, nil
}
59 changes: 59 additions & 0 deletions pkg/golinters/typecheck_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package golinters

import (
"errors"
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func TestParseError(t *testing.T) {
cases := []struct {
in, out string
good bool
}{
{"f.go:1:2: text", "", true},
{"f.go:1:2: text: with: colons", "", true},

{"f.go:1:2:text wo leading space", "f.go:1:2: text wo leading space", true},

{"f.go:1:2:", "", false},
{"f.go:1:2: ", "", false},

{"f.go:1:2", "f.go:1: 2", true},
{"f.go:1: text no column", "", true},
{"f.go:1: text no column: but with colon", "", true},
{"f.go:1:text no column", "f.go:1: text no column", true},

{"f.go: no line", "", false},
{"f.go: 1: text", "", false},

{"f.go:", "", false},
{"f.go", "", false},
}

lint := TypeCheck{}
for _, c := range cases {
i := lint.parseError(errors.New(c.in))
if !c.good {
assert.Nil(t, i)
continue
}

assert.NotNil(t, i)

pos := fmt.Sprintf("%s:%d", i.FilePath(), i.Line())
if i.Pos.Column != 0 {
pos += fmt.Sprintf(":%d", i.Pos.Column)
}
out := fmt.Sprintf("%s: %s", pos, i.Text)
expOut := c.out
if expOut == "" {
expOut = c.in
}
assert.Equal(t, expOut, out)

assert.Equal(t, "typecheck", i.FromLinter)
}
}
2 changes: 1 addition & 1 deletion pkg/printers/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ func (JSON) Print(issues <-chan result.Issue) (bool, error) {
if err != nil {
return false, err
}
fmt.Fprint(stdOut, string(outputJSON))
fmt.Fprint(StdOut, string(outputJSON))
return len(allIssues) != 0, nil
}
8 changes: 4 additions & 4 deletions pkg/printers/text.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func (p *Text) Print(issues <-chan result.Issue) (bool, error) {

if issuesN == 0 {
outStr := p.SprintfColored(color.FgGreen, "Congrats! No issues were found.")
fmt.Fprintln(stdOut, outStr)
fmt.Fprintln(StdOut, outStr)
} else {
logrus.Infof("Found %d issues", issuesN)
}
Expand All @@ -105,7 +105,7 @@ func (p Text) printIssue(i *result.Issue) {
if i.Pos.Column != 0 {
pos += fmt.Sprintf(":%d", i.Pos.Column)
}
fmt.Fprintf(stdOut, "%s: %s\n", pos, text)
fmt.Fprintf(StdOut, "%s: %s\n", pos, text)
}

func (p Text) printIssuedLines(i *result.Issue, lines linesCache) {
Expand All @@ -123,7 +123,7 @@ func (p Text) printIssuedLines(i *result.Issue, lines linesCache) {
}

lineStr = string(bytes.Trim(lines[zeroIndexedLine], "\r"))
fmt.Fprintln(stdOut, lineStr)
fmt.Fprintln(StdOut, lineStr)
}
}

Expand All @@ -146,5 +146,5 @@ func (p Text) printUnderLinePointer(i *result.Issue, line string) {
prefix += strings.Repeat(" ", spacesCount)
}

fmt.Fprintf(stdOut, "%s%s\n", prefix, p.SprintfColored(color.FgYellow, "^"))
fmt.Fprintf(StdOut, "%s%s\n", prefix, p.SprintfColored(color.FgYellow, "^"))
}
2 changes: 1 addition & 1 deletion pkg/printers/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ import (
"syscall"
)

var stdOut = os.NewFile(uintptr(syscall.Stdout), "/dev/stdout") // was set to /dev/null
var StdOut = os.NewFile(uintptr(syscall.Stdout), "/dev/stdout") // was set to /dev/null
Loading