Skip to content

Commit 45de6fd

Browse files
author
golangci
authored
Merge pull request #15 from golangci/feature/check-compilation-errors
Feature/check compilation errors
2 parents 51d178d + b361146 commit 45de6fd

File tree

16 files changed

+206
-59
lines changed

16 files changed

+206
-59
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ golangci-lint linters
166166
- [varcheck](https://github.com/opennota/check): Finds unused global variables and constants
167167
- [ineffassign](https://github.com/gordonklaus/ineffassign): Detects when assignments to existing variables are not used
168168
- [deadcode](https://github.com/remyoudompheng/go-misc/tree/master/deadcode): Finds unused code
169+
- 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).
169170

170171
## Disabled By Default Linters (`-E/--enable`)
171172
- [golint](https://github.com/golang/lint): Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes

pkg/commands/root.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ func (e *Executor) initRoot() {
1717
Long: `Smart, fast linters runner. Run it in cloud for every GitHub pull request on https://golangci.com`,
1818
Run: func(cmd *cobra.Command, args []string) {
1919
if err := cmd.Help(); err != nil {
20-
log.Fatal(err)
20+
logrus.Fatal(err)
2121
}
2222
},
2323
PersistentPreRun: func(cmd *cobra.Command, args []string) {
@@ -33,10 +33,10 @@ func (e *Executor) initRoot() {
3333
if e.cfg.Run.CPUProfilePath != "" {
3434
f, err := os.Create(e.cfg.Run.CPUProfilePath)
3535
if err != nil {
36-
log.Fatal(err)
36+
logrus.Fatal(err)
3737
}
3838
if err := pprof.StartCPUProfile(f); err != nil {
39-
log.Fatal(err)
39+
logrus.Fatal(err)
4040
}
4141
}
4242
},
@@ -47,11 +47,11 @@ func (e *Executor) initRoot() {
4747
if e.cfg.Run.MemProfilePath != "" {
4848
f, err := os.Create(e.cfg.Run.MemProfilePath)
4949
if err != nil {
50-
log.Fatal(err)
50+
logrus.Fatal(err)
5151
}
5252
runtime.GC() // get up-to-date statistics
5353
if err := pprof.WriteHeapProfile(f); err != nil {
54-
log.Fatal("could not write memory profile: ", err)
54+
logrus.Fatal("could not write memory profile: ", err)
5555
}
5656
}
5757

pkg/commands/run.go

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"go/build"
88
"go/token"
9+
"io/ioutil"
910
"log"
1011
"os"
1112
"runtime"
@@ -245,7 +246,26 @@ func (e *Executor) runAnalysis(ctx context.Context, args []string) (<-chan resul
245246
return runner.Run(ctx, linters, lintCtx), nil
246247
}
247248

249+
func setOutputToDevNull() (savedStdout, savedStderr *os.File) {
250+
savedStdout, savedStderr = os.Stdout, os.Stderr
251+
devNull, err := os.Open(os.DevNull)
252+
if err != nil {
253+
logrus.Warnf("can't open null device %q: %s", os.DevNull, err)
254+
return
255+
}
256+
257+
os.Stdout, os.Stderr = devNull, devNull
258+
return
259+
}
260+
248261
func (e *Executor) runAndPrint(ctx context.Context, args []string) error {
262+
// Don't allow linters and loader to print anything
263+
log.SetOutput(ioutil.Discard)
264+
savedStdout, savedStderr := setOutputToDevNull()
265+
defer func() {
266+
os.Stdout, os.Stderr = savedStdout, savedStderr
267+
}()
268+
249269
issues, err := e.runAnalysis(ctx, args)
250270
if err != nil {
251271
return err
@@ -288,11 +308,11 @@ func (e *Executor) executeRun(cmd *cobra.Command, args []string) {
288308
}
289309

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

294314
if err := e.runAndPrint(ctx, args); err != nil {
295-
log.Print(err)
315+
logrus.Warnf("running error: %s", err)
296316
if e.exitCode == 0 {
297317
e.exitCode = exitCodeIfFailure
298318
}
@@ -305,11 +325,11 @@ func (e *Executor) parseConfig(cmd *cobra.Command) {
305325
if err == pflag.ErrHelp {
306326
return
307327
}
308-
log.Fatalf("Can't parse args: %s", err)
328+
logrus.Fatalf("Can't parse args: %s", err)
309329
}
310330

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

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

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

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

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

352372
if err := e.validateConfig(&commandLineConfig); err != nil {
353-
log.Fatal(err)
373+
logrus.Fatal(err)
354374
}
355375
}
356376

pkg/enabled_linters.go

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ func GetAllSupportedLinterConfigs() []LinterConfig {
118118
newLinterConfig(golinters.Goconst{}).WithPresets(PresetStyle).WithSpeed(9),
119119
newLinterConfig(golinters.Deadcode{}).WithFullImport().WithPresets(PresetUnused).WithSpeed(10),
120120
newLinterConfig(golinters.Gocyclo{}).WithPresets(PresetComplexity).WithSpeed(8),
121+
newLinterConfig(golinters.TypeCheck{}).WithFullImport().WithPresets(PresetBugs).WithSpeed(10),
121122

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

129130
if os.Getenv("GOLANGCI_COM_RUN") == "1" {
130131
disabled := map[string]bool{
131-
"gocyclo": true,
132-
"dupl": true,
133-
"maligned": true,
132+
golinters.Gocyclo{}.Name(): true, // annoying
133+
golinters.Dupl{}.Name(): true, // annoying
134+
golinters.Maligned{}.Name(): true, // rarely usable
135+
golinters.TypeCheck{}.Name(): true, // annoying because of different building envs
134136
}
135137
return enableLinterConfigs(lcs, func(lc *LinterConfig) bool {
136138
return !disabled[lc.Linter.Name()]
137139
})
138140
}
139141

140142
enabled := map[string]bool{
141-
"govet": true,
142-
"errcheck": true,
143-
"staticcheck": true,
144-
"unused": true,
145-
"gosimple": true,
146-
"gas": true,
147-
"structcheck": true,
148-
"varcheck": true,
149-
"ineffassign": true,
150-
"deadcode": true,
143+
golinters.Govet{}.Name(): true,
144+
golinters.Errcheck{}.Name(): true,
145+
golinters.Megacheck{StaticcheckEnabled: true}.Name(): true,
146+
golinters.Megacheck{UnusedEnabled: true}.Name(): true,
147+
golinters.Megacheck{GosimpleEnabled: true}.Name(): true,
148+
golinters.Gas{}.Name(): true,
149+
golinters.Structcheck{}.Name(): true,
150+
golinters.Varcheck{}.Name(): true,
151+
golinters.Ineffassign{}.Name(): true,
152+
golinters.Deadcode{}.Name(): true,
153+
golinters.TypeCheck{}.Name(): true,
151154
}
152155
return enableLinterConfigs(lcs, func(lc *LinterConfig) bool {
153156
return enabled[lc.Linter.Name()]

pkg/enabled_linters_test.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ func runGoErrchk(c *exec.Cmd, t *testing.T) {
3131
const testdataDir = "testdata"
3232

3333
var testdataWithIssuesDir = filepath.Join(testdataDir, "with_issues")
34-
var testdataNotCompilingDir = filepath.Join(testdataDir, "not_compiles")
3534

3635
const binName = "golangci-lint"
3736

@@ -72,12 +71,6 @@ func testOneSource(t *testing.T, sourcePath string) {
7271
runGoErrchk(cmd, t)
7372
}
7473

75-
func TestNotCompilingProgram(t *testing.T) {
76-
installBinary(t)
77-
err := exec.Command(binName, "run", "--enable-all", testdataNotCompilingDir).Run()
78-
assert.NoError(t, err)
79-
}
80-
8174
func chdir(b *testing.B, dir string) {
8275
if err := os.Chdir(dir); err != nil {
8376
b.Fatalf("can't chdir to %s: %s", dir, err)

pkg/golinters/golint.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func (g Golint) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, erro
3232
issues = append(issues, i...)
3333
}
3434
if lintErr != nil {
35-
logrus.Warnf("golint: %s", lintErr)
35+
logrus.Infof("golint: %s", lintErr)
3636
}
3737

3838
return issues, nil

pkg/golinters/typecheck.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package golinters
2+
3+
import (
4+
"context"
5+
"go/token"
6+
"strconv"
7+
"strings"
8+
9+
"github.com/golangci/golangci-lint/pkg/result"
10+
)
11+
12+
type TypeCheck struct{}
13+
14+
func (TypeCheck) Name() string {
15+
return "typecheck"
16+
}
17+
18+
func (TypeCheck) Desc() string {
19+
return "Like the front-end of a Go compiler, parses and type-checks Go code"
20+
}
21+
22+
func (lint TypeCheck) parseError(err error) *result.Issue {
23+
// file:line(<optional>:colon): message
24+
parts := strings.Split(err.Error(), ":")
25+
if len(parts) < 3 {
26+
return nil
27+
}
28+
29+
file := parts[0]
30+
line, err := strconv.Atoi(parts[1])
31+
if err != nil {
32+
return nil
33+
}
34+
35+
var column int
36+
var message string
37+
if len(parts) == 3 { // no column
38+
message = parts[2]
39+
} else {
40+
column, err = strconv.Atoi(parts[2])
41+
if err == nil { // column was parsed
42+
message = strings.Join(parts[3:], ":")
43+
} else {
44+
message = strings.Join(parts[2:], ":")
45+
}
46+
}
47+
48+
message = strings.TrimSpace(message)
49+
if message == "" {
50+
return nil
51+
}
52+
53+
return &result.Issue{
54+
Pos: token.Position{
55+
Filename: file,
56+
Line: line,
57+
Column: column,
58+
},
59+
Text: message,
60+
FromLinter: lint.Name(),
61+
}
62+
}
63+
64+
func (lint TypeCheck) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) {
65+
var res []result.Issue
66+
for _, pkg := range lintCtx.Program.InitialPackages() {
67+
for _, err := range pkg.Errors {
68+
i := lint.parseError(err)
69+
if i != nil {
70+
res = append(res, *i)
71+
}
72+
}
73+
}
74+
75+
return res, nil
76+
}

pkg/golinters/typecheck_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package golinters
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestParseError(t *testing.T) {
12+
cases := []struct {
13+
in, out string
14+
good bool
15+
}{
16+
{"f.go:1:2: text", "", true},
17+
{"f.go:1:2: text: with: colons", "", true},
18+
19+
{"f.go:1:2:text wo leading space", "f.go:1:2: text wo leading space", true},
20+
21+
{"f.go:1:2:", "", false},
22+
{"f.go:1:2: ", "", false},
23+
24+
{"f.go:1:2", "f.go:1: 2", true},
25+
{"f.go:1: text no column", "", true},
26+
{"f.go:1: text no column: but with colon", "", true},
27+
{"f.go:1:text no column", "f.go:1: text no column", true},
28+
29+
{"f.go: no line", "", false},
30+
{"f.go: 1: text", "", false},
31+
32+
{"f.go:", "", false},
33+
{"f.go", "", false},
34+
}
35+
36+
lint := TypeCheck{}
37+
for _, c := range cases {
38+
i := lint.parseError(errors.New(c.in))
39+
if !c.good {
40+
assert.Nil(t, i)
41+
continue
42+
}
43+
44+
assert.NotNil(t, i)
45+
46+
pos := fmt.Sprintf("%s:%d", i.FilePath(), i.Line())
47+
if i.Pos.Column != 0 {
48+
pos += fmt.Sprintf(":%d", i.Pos.Column)
49+
}
50+
out := fmt.Sprintf("%s: %s", pos, i.Text)
51+
expOut := c.out
52+
if expOut == "" {
53+
expOut = c.in
54+
}
55+
assert.Equal(t, expOut, out)
56+
57+
assert.Equal(t, "typecheck", i.FromLinter)
58+
}
59+
}

pkg/printers/json.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ func (JSON) Print(issues <-chan result.Issue) (bool, error) {
2222
if err != nil {
2323
return false, err
2424
}
25-
fmt.Fprint(stdOut, string(outputJSON))
25+
fmt.Fprint(StdOut, string(outputJSON))
2626
return len(allIssues) != 0, nil
2727
}

pkg/printers/text.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func (p *Text) Print(issues <-chan result.Issue) (bool, error) {
8888

8989
if issuesN == 0 {
9090
outStr := p.SprintfColored(color.FgGreen, "Congrats! No issues were found.")
91-
fmt.Fprintln(stdOut, outStr)
91+
fmt.Fprintln(StdOut, outStr)
9292
} else {
9393
logrus.Infof("Found %d issues", issuesN)
9494
}
@@ -105,7 +105,7 @@ func (p Text) printIssue(i *result.Issue) {
105105
if i.Pos.Column != 0 {
106106
pos += fmt.Sprintf(":%d", i.Pos.Column)
107107
}
108-
fmt.Fprintf(stdOut, "%s: %s\n", pos, text)
108+
fmt.Fprintf(StdOut, "%s: %s\n", pos, text)
109109
}
110110

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

125125
lineStr = string(bytes.Trim(lines[zeroIndexedLine], "\r"))
126-
fmt.Fprintln(stdOut, lineStr)
126+
fmt.Fprintln(StdOut, lineStr)
127127
}
128128
}
129129

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

149-
fmt.Fprintf(stdOut, "%s%s\n", prefix, p.SprintfColored(color.FgYellow, "^"))
149+
fmt.Fprintf(StdOut, "%s%s\n", prefix, p.SprintfColored(color.FgYellow, "^"))
150150
}

pkg/printers/utils.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ import (
55
"syscall"
66
)
77

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

0 commit comments

Comments
 (0)