Skip to content

Commit ed0b551

Browse files
committed
Fix linting of preprocessed files
Preprocessed files like .qtpl.go quicktemplate Go files can have //line directives. They map to a source .qtpl file. This commit fixes linting of such files: 1. don't fail on AST cache loading 2. output Go filename not .qtpl or similar Also, here we update golint to the upstream version. Relates: #316, #466, #467, #468
1 parent eed661b commit ed0b551

File tree

17 files changed

+274
-43
lines changed

17 files changed

+274
-43
lines changed

.travis.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ language: go
33
go:
44
- 1.11.x
55
- 1.12.x
6+
7+
before_script:
8+
- go get github.com/valyala/quicktemplate
9+
610
script: make check_generated test
711

812
after_success:

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ require (
2020
github.com/golangci/gofmt v0.0.0-20181105071733-0b8337e80d98
2121
github.com/golangci/gosec v0.0.0-20180901114220-66fb7fc33547
2222
github.com/golangci/ineffassign v0.0.0-20180808204949-2ee8f2867dde
23-
github.com/golangci/lint-1 v0.0.0-20180610141402-4bf9709227d1
23+
github.com/golangci/lint-1 v0.0.0-20180610141402-ee948d087217
2424
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca
2525
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770
2626
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21

go.sum

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ github.com/golangci/gosec v0.0.0-20180901114220-66fb7fc33547 h1:qMomh8bv+kDazm1d
6060
github.com/golangci/gosec v0.0.0-20180901114220-66fb7fc33547/go.mod h1:0qUabqiIQgfmlAmulqxyiGkkyF6/tOGSnY2cnPVwrzU=
6161
github.com/golangci/ineffassign v0.0.0-20180808204949-2ee8f2867dde h1:qEGp3ZF1Qw6TkbWKn6GdJ12Ssu/CpJBaBcJ4hrUjrSo=
6262
github.com/golangci/ineffassign v0.0.0-20180808204949-2ee8f2867dde/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU=
63-
github.com/golangci/lint-1 v0.0.0-20180610141402-4bf9709227d1 h1:PHK2kIh21Zt4IcG0bBRzQwEDVKF64LnkoSXnm8lfJUk=
64-
github.com/golangci/lint-1 v0.0.0-20180610141402-4bf9709227d1/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
63+
github.com/golangci/lint-1 v0.0.0-20180610141402-ee948d087217 h1:r7vyX+SN24x6+5AnpnrRn/bdwBb7U+McZqCHOVtXDuk=
64+
github.com/golangci/lint-1 v0.0.0-20180610141402-ee948d087217/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
6565
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA=
6666
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=
6767
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770 h1:EL/O5HGrF7Jaq0yNhBLucz9hTuRzj2LdwGBOaENgxIk=
@@ -171,6 +171,7 @@ golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGm
171171
golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
172172
golang.org/x/tools v0.0.0-20181205014116-22934f0fdb62/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
173173
golang.org/x/tools v0.0.0-20190121143147-24cd39ecf745/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
174+
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
174175
golang.org/x/tools v0.0.0-20190420000508-685fecacd0a0 h1:pa1CyBALPFjblgkNQp7T7gEcFcG/GOG5Ck8IcnSVWGs=
175176
golang.org/x/tools v0.0.0-20190420000508-685fecacd0a0/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
176177
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=

pkg/lint/astcache/astcache.go

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"go/parser"
66
"go/token"
77
"path/filepath"
8-
"time"
98

109
"golang.org/x/tools/go/packages"
1110

@@ -107,34 +106,37 @@ func LoadFromPackages(pkgs []*packages.Package, log logutils.Log) (*Cache, error
107106
return c, nil
108107
}
109108

110-
func (c *Cache) loadFromPackage(pkg *packages.Package) {
111-
if len(pkg.Syntax) == 0 || len(pkg.GoFiles) != len(pkg.CompiledGoFiles) {
112-
// len(pkg.Syntax) == 0 if only filenames are loaded
113-
// lengths aren't equal if there are preprocessed files (cgo)
114-
startedAt := time.Now()
115-
116-
// can't use pkg.Fset: it will overwrite offsets by preprocessed files
117-
fset := token.NewFileSet()
118-
for _, f := range pkg.GoFiles {
119-
c.parseFile(f, fset)
120-
}
109+
func (c *Cache) extractFilenamesForAstFile(fset *token.FileSet, f *ast.File) []string {
110+
var ret []string
121111

122-
c.log.Infof("Parsed AST of all pkg.GoFiles: %s for %s", pkg.GoFiles, time.Since(startedAt))
123-
return
112+
// false ignores //line comments: name can be incorrect for generated files with //line directives
113+
// mapping e.g. from .rl to .go files.
114+
pos := fset.PositionFor(f.Pos(), false)
115+
if pos.Filename != "" {
116+
ret = append(ret, pos.Filename)
124117
}
125118

119+
return ret
120+
}
121+
122+
func (c *Cache) loadFromPackage(pkg *packages.Package) {
126123
for _, f := range pkg.Syntax {
127-
pos := pkg.Fset.Position(f.Pos())
128-
if pos.Filename == "" {
129-
continue
124+
for _, filename := range c.extractFilenamesForAstFile(pkg.Fset, f) {
125+
filePath := c.normalizeFilename(filename)
126+
c.m[filePath] = &File{
127+
F: f,
128+
Fset: pkg.Fset,
129+
Name: filePath,
130+
}
130131
}
132+
}
131133

132-
filePath := c.normalizeFilename(pos.Filename)
133-
134-
c.m[filePath] = &File{
135-
F: f,
136-
Fset: pkg.Fset,
137-
Name: filePath,
134+
// some Go files sometimes aren't present in pkg.Syntax
135+
fset := token.NewFileSet() // can't use pkg.Fset: it will overwrite offsets by preprocessed files
136+
for _, filePath := range pkg.GoFiles {
137+
filePath = c.normalizeFilename(filePath)
138+
if c.m[filePath] == nil {
139+
c.parseFile(filePath, fset)
138140
}
139141
}
140142
}

pkg/lint/runner.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ func NewRunner(astCache *astcache.Cache, cfg *config.Config, log logutils.Log, g
6767

6868
return &Runner{
6969
Processors: []processors.Processor{
70-
processors.NewPathPrettifier(), // must be before diff, nolint and exclude autogenerated processor at least
7170
processors.NewCgo(goenv),
71+
processors.NewFilenameUnadjuster(astCache, log.Child("filename_unadjuster")), // must go after Cgo
72+
processors.NewPathPrettifier(), // must be before diff, nolint and exclude autogenerated processor at least
7273
skipFilesProcessor,
7374
skipDirsProcessor, // must be after path prettifier
7475

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package processors
2+
3+
import (
4+
"go/token"
5+
"path/filepath"
6+
"strings"
7+
8+
"github.com/golangci/golangci-lint/pkg/logutils"
9+
10+
"github.com/golangci/golangci-lint/pkg/lint/astcache"
11+
12+
"github.com/golangci/golangci-lint/pkg/result"
13+
)
14+
15+
type posMapper func(pos token.Position) token.Position
16+
17+
// FilenameUnadjuster is needed because a lot of linters use fset.Position(f.Pos())
18+
// to get filename. And they return adjusted filename (e.g. *.qtpl) for an issue. We need
19+
// restore real .go filename to properly output it, parse it, etc.
20+
type FilenameUnadjuster struct {
21+
m map[string]posMapper // map from adjusted filename to position mapper: adjusted -> unadjusted position
22+
log logutils.Log
23+
}
24+
25+
var _ Processor = FilenameUnadjuster{}
26+
27+
func NewFilenameUnadjuster(cache *astcache.Cache, log logutils.Log) *FilenameUnadjuster {
28+
m := map[string]posMapper{}
29+
for _, f := range cache.GetAllValidFiles() {
30+
adjustedFilename := f.Fset.PositionFor(f.F.Pos(), true).Filename
31+
if adjustedFilename == "" {
32+
continue
33+
}
34+
unadjustedFilename := f.Fset.PositionFor(f.F.Pos(), false).Filename
35+
if unadjustedFilename == "" || unadjustedFilename == adjustedFilename {
36+
continue
37+
}
38+
if !strings.HasSuffix(unadjustedFilename, ".go") {
39+
continue // file.go -> /caches/cgo-xxx
40+
}
41+
42+
f := f
43+
m[adjustedFilename] = func(adjustedPos token.Position) token.Position {
44+
tokenFile := f.Fset.File(f.F.Pos())
45+
if tokenFile == nil {
46+
log.Warnf("Failed to get token file for %s", adjustedFilename)
47+
return adjustedPos
48+
}
49+
return f.Fset.PositionFor(tokenFile.Pos(adjustedPos.Offset), false)
50+
}
51+
}
52+
53+
return &FilenameUnadjuster{
54+
m: m,
55+
log: log,
56+
}
57+
}
58+
59+
func (p FilenameUnadjuster) Name() string {
60+
return "filename_unadjuster"
61+
}
62+
63+
func (p FilenameUnadjuster) Process(issues []result.Issue) ([]result.Issue, error) {
64+
return transformIssues(issues, func(i *result.Issue) *result.Issue {
65+
issueFilePath := i.FilePath()
66+
if !filepath.IsAbs(i.FilePath()) {
67+
absPath, err := filepath.Abs(i.FilePath())
68+
if err != nil {
69+
p.log.Warnf("failed to build abs path for %q: %s", i.FilePath(), err)
70+
return i
71+
}
72+
issueFilePath = absPath
73+
}
74+
75+
mapper := p.m[issueFilePath]
76+
if mapper == nil {
77+
return i
78+
}
79+
80+
newI := *i
81+
newI.Pos = mapper(i.Pos)
82+
p.log.Infof("Unadjusted from %v to %v", i.Pos, newI.Pos)
83+
return &newI
84+
}), nil
85+
}
86+
87+
func (FilenameUnadjuster) Finish() {}

pkg/result/processors/nolint.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"sort"
88
"strings"
99

10+
"github.com/pkg/errors"
11+
1012
"github.com/golangci/golangci-lint/pkg/lint/astcache"
1113
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
1214
"github.com/golangci/golangci-lint/pkg/logutils"
@@ -88,8 +90,12 @@ func (p *Nolint) getOrCreateFileData(i *result.Issue) (*fileData, error) {
8890
}
8991

9092
file := p.astCache.Get(i.FilePath())
91-
if file == nil || file.Err != nil {
92-
return nil, fmt.Errorf("can't parse file %s: %v, astcache is %v", i.FilePath(), file, p.astCache.ParsedFilenames())
93+
if file == nil {
94+
return nil, fmt.Errorf("no file %s in ast cache %v",
95+
i.FilePath(), p.astCache.ParsedFilenames())
96+
}
97+
if file.Err != nil {
98+
return nil, errors.Wrapf(file.Err, "can't parse file %s", i.FilePath())
9399
}
94100

95101
fd.ignoredRanges = p.buildIgnoredRangesForFile(file.F, file.Fset, i.FilePath())

test/run_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package test
22

33
import (
44
"path/filepath"
5+
"strings"
56
"testing"
67

78
"github.com/stretchr/testify/assert"
@@ -67,6 +68,30 @@ func TestGovetCustomFormatter(t *testing.T) {
6768
testshared.NewLintRunner(t).Run(getTestDataDir("govet_custom_formatter")).ExpectNoIssues()
6869
}
6970

71+
func TestLineDirectiveProcessedFilesLiteLoading(t *testing.T) {
72+
r := testshared.NewLintRunner(t).Run("--print-issued-lines=false", "--no-config",
73+
"--exclude-use-default=false", "-Egolint", getTestDataDir("quicktemplate"))
74+
75+
output := strings.Join([]string{
76+
"testdata/quicktemplate/hello.qtpl.go:26:1: exported function `StreamHello` should have comment or be unexported (golint)",
77+
"testdata/quicktemplate/hello.qtpl.go:50:1: exported function `Hello` should have comment or be unexported (golint)",
78+
"testdata/quicktemplate/hello.qtpl.go:39:1: exported function `WriteHello` should have comment or be unexported (golint)",
79+
}, "\n")
80+
r.ExpectExitCode(exitcodes.IssuesFound).ExpectOutputEq(output + "\n")
81+
}
82+
83+
func TestLineDirectiveProcessedFilesFullLoading(t *testing.T) {
84+
r := testshared.NewLintRunner(t).Run("--print-issued-lines=false", "--no-config",
85+
"--exclude-use-default=false", "-Egolint,govet", getTestDataDir("quicktemplate"))
86+
87+
output := strings.Join([]string{
88+
"testdata/quicktemplate/hello.qtpl.go:26:1: exported function `StreamHello` should have comment or be unexported (golint)",
89+
"testdata/quicktemplate/hello.qtpl.go:50:1: exported function `Hello` should have comment or be unexported (golint)",
90+
"testdata/quicktemplate/hello.qtpl.go:39:1: exported function `WriteHello` should have comment or be unexported (golint)",
91+
}, "\n")
92+
r.ExpectExitCode(exitcodes.IssuesFound).ExpectOutputEq(output + "\n")
93+
}
94+
7095
func TestSkippedDirsNoMatchArg(t *testing.T) {
7196
dir := getTestDataDir("skipdirs", "skip_me", "nested")
7297
r := testshared.NewLintRunner(t).Run("--print-issued-lines=false", "--no-config", "--skip-dirs", dir, "-Egolint", dir)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
All text outside function templates is treated as comments,
2+
i.e. it is just ignored by quicktemplate compiler (`qtc`). It is for humans.
3+
4+
Hello is a simple template function.
5+
{% func Hello(name string) %}
6+
Hello, {%s name %}!
7+
{% endfunc %}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// This file is automatically generated by qtc from "hello.qtpl".
2+
// See https://github.com/valyala/quicktemplate for details.
3+
4+
// All text outside function templates is treated as comments,
5+
// i.e. it is just ignored by quicktemplate compiler (`qtc`). It is for humans.
6+
//
7+
// Hello is a simple template function.
8+
9+
//line hello.qtpl:5
10+
package quicktemplate
11+
12+
//line hello.qtpl:5
13+
import (
14+
qtio422016 "io"
15+
16+
qt422016 "github.com/valyala/quicktemplate"
17+
)
18+
19+
//line hello.qtpl:5
20+
var (
21+
_ = qtio422016.Copy
22+
_ = qt422016.AcquireByteBuffer
23+
)
24+
25+
//line hello.qtpl:5
26+
func StreamHello(qw422016 *qt422016.Writer, name string) {
27+
//line hello.qtpl:5
28+
qw422016.N().S(`
29+
Hello, `)
30+
//line hello.qtpl:6
31+
qw422016.E().S(name)
32+
//line hello.qtpl:6
33+
qw422016.N().S(`!
34+
`)
35+
//line hello.qtpl:7
36+
}
37+
38+
//line hello.qtpl:7
39+
func WriteHello(qq422016 qtio422016.Writer, name string) {
40+
//line hello.qtpl:7
41+
qw422016 := qt422016.AcquireWriter(qq422016)
42+
//line hello.qtpl:7
43+
StreamHello(qw422016, name)
44+
//line hello.qtpl:7
45+
qt422016.ReleaseWriter(qw422016)
46+
//line hello.qtpl:7
47+
}
48+
49+
//line hello.qtpl:7
50+
func Hello(name string) string {
51+
//line hello.qtpl:7
52+
qb422016 := qt422016.AcquireByteBuffer()
53+
//line hello.qtpl:7
54+
WriteHello(qb422016, name)
55+
//line hello.qtpl:7
56+
qs422016 := string(qb422016.B)
57+
//line hello.qtpl:7
58+
qt422016.ReleaseByteBuffer(qb422016)
59+
//line hello.qtpl:7
60+
return qs422016
61+
//line hello.qtpl:7
62+
}

vendor/github.com/golangci/lint-1/.travis.yml

Lines changed: 4 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/golangci/lint-1/CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/golangci/lint-1/README.md

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/golangci/lint-1/go.mod

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/golangci/lint-1/go.sum

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)