Skip to content

Commit 01b566a

Browse files
Add go-header linter (#1181)
* add go-header linter * apply review comments: add goheader example into .golangci.example.yml * apply review comments: correctly handle multiline comments
1 parent b22e3f1 commit 01b566a

File tree

9 files changed

+139
-1
lines changed

9 files changed

+139
-1
lines changed

.golangci.example.yml

+27
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,33 @@ linters-settings:
157157
gofmt:
158158
# simplify code: gofmt with `-s` option, true by default
159159
simplify: true
160+
goheader:
161+
values:
162+
const:
163+
# define here const type values in format k:v, for example:
164+
# YEAR: 2020
165+
# COMPANY: MY COMPANY
166+
regexp:
167+
# define here regexp type values, for example
168+
# AUTHOR: .*@mycompany\.com
169+
template:
170+
# put here copyright header template for source code files, for example:
171+
# {{ AUTHOR }} {{ COMPANY }} {{ YEAR }}
172+
# SPDX-License-Identifier: Apache-2.0
173+
#
174+
# Licensed under the Apache License, Version 2.0 (the "License");
175+
# you may not use this file except in compliance with the License.
176+
# You may obtain a copy of the License at:
177+
#
178+
# http://www.apache.org/licenses/LICENSE-2.0
179+
#
180+
# Unless required by applicable law or agreed to in writing, software
181+
# distributed under the License is distributed on an "AS IS" BASIS,
182+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
183+
# See the License for the specific language governing permissions and
184+
# limitations under the License.
185+
template-path:
186+
# also as alternative of directive 'template' you may put the path to file with the template source
160187
goimports:
161188
# put imports beginning with prefix after 3rd-party packages;
162189
# it's a comma-separated list of prefixes

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5
77
github.com/OpenPeeDeeP/depguard v1.0.1
88
github.com/bombsimon/wsl/v3 v3.1.0
9+
github.com/denis-tingajkin/go-header v0.3.1
910
github.com/fatih/color v1.9.0
1011
github.com/go-critic/go-critic v0.4.3
1112
github.com/go-lintpack/lintpack v0.5.2

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
4949
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5050
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5151
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
52+
github.com/denis-tingajkin/go-header v0.3.1 h1:ymEpSiFjeItCy1FOP+x0M2KdCELdEAHUsNa8F+hHc6w=
53+
github.com/denis-tingajkin/go-header v0.3.1/go.mod h1:sq/2IxMhaZX+RRcgHfCRx/m0M5na0fBt4/CRe7Lrji0=
5254
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
5355
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
5456
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=

pkg/config/config.go

+7
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ type LintersSettings struct {
239239
Dogsled DogsledSettings
240240
Gocognit GocognitSettings
241241
Godot GodotSettings
242+
Goheader GoHeaderSettings
242243
Testpackage TestpackageSettings
243244
Nestif NestifSettings
244245
NoLintLint NoLintLintSettings
@@ -247,6 +248,12 @@ type LintersSettings struct {
247248
Custom map[string]CustomLinterSettings
248249
}
249250

251+
type GoHeaderSettings struct {
252+
Values map[string]map[string]string `mapstructure:"values"`
253+
Template string `mapstructure:"template"`
254+
TemplatePath string `mapstructure:"template-path"`
255+
}
256+
250257
type GovetSettings struct {
251258
CheckShadowing bool `mapstructure:"check-shadowing"`
252259
Settings map[string]map[string]interface{}

pkg/golinters/goheader.go

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package golinters
2+
3+
import (
4+
"go/token"
5+
"sync"
6+
7+
goheader "github.com/denis-tingajkin/go-header"
8+
"golang.org/x/tools/go/analysis"
9+
10+
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
11+
"github.com/golangci/golangci-lint/pkg/lint/linter"
12+
"github.com/golangci/golangci-lint/pkg/result"
13+
)
14+
15+
const goHeaderName = "goheader"
16+
17+
func NewGoHeader() *goanalysis.Linter {
18+
var mu sync.Mutex
19+
var issues []goanalysis.Issue
20+
21+
analyzer := &analysis.Analyzer{
22+
Name: goHeaderName,
23+
Doc: goanalysis.TheOnlyanalyzerDoc,
24+
}
25+
return goanalysis.NewLinter(
26+
goHeaderName,
27+
"Checks is file header matches to pattern",
28+
[]*analysis.Analyzer{analyzer},
29+
nil,
30+
).WithContextSetter(func(lintCtx *linter.Context) {
31+
cfg := lintCtx.Cfg.LintersSettings.Goheader
32+
c := &goheader.Configuration{
33+
Values: cfg.Values,
34+
Template: cfg.Template,
35+
TemplatePath: cfg.TemplatePath,
36+
}
37+
analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
38+
if c.TemplatePath == "" && c.Template == "" {
39+
// User did not pass template, so then do not run go-header linter
40+
return nil, nil
41+
}
42+
template, err := c.GetTemplate()
43+
if err != nil {
44+
return nil, err
45+
}
46+
values, err := c.GetValues()
47+
if err != nil {
48+
return nil, err
49+
}
50+
a := goheader.New(goheader.WithTemplate(template), goheader.WithValues(values))
51+
var res []goanalysis.Issue
52+
for _, file := range pass.Files {
53+
i := a.Analyze(file)
54+
issue := result.Issue{
55+
Pos: token.Position{
56+
Line: i.Location().Line + 1,
57+
Column: i.Location().Position,
58+
Filename: pass.Fset.Position(file.Pos()).Filename,
59+
},
60+
Text: i.Message(),
61+
FromLinter: goHeaderName,
62+
}
63+
res = append(res, goanalysis.NewIssue(&issue, pass))
64+
}
65+
if len(res) == 0 {
66+
return nil, nil
67+
}
68+
69+
mu.Lock()
70+
issues = append(issues, res...)
71+
mu.Unlock()
72+
73+
return nil, nil
74+
}
75+
}).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
76+
return issues
77+
}).WithLoadMode(goanalysis.LoadModeSyntax)
78+
}

pkg/lint/lintersdb/manager.go

+4
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
202202
WithPresets(linter.PresetFormatting).
203203
WithAutoFix().
204204
WithURL("https://godoc.org/golang.org/x/tools/cmd/goimports"),
205+
linter.NewConfig(golinters.NewGoHeader()).
206+
WithPresets(linter.PresetStyle).
207+
WithLoadForGoAnalysis().
208+
WithURL("https://github.com/denis-tingajkin/go-header"),
205209
linter.NewConfig(golinters.NewMaligned()).
206210
WithLoadForGoAnalysis().
207211
WithPresets(linter.PresetPerformance).

test/linters_test.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ func buildConfigFromShortRepr(t *testing.T, repr string, config map[string]inter
173173
lastObj[keyParts[len(keyParts)-1]] = kv[1]
174174
}
175175

176+
func skipMultilineComment(scanner *bufio.Scanner) {
177+
for line := scanner.Text(); !strings.Contains(line, "*/") && scanner.Scan(); {
178+
line = scanner.Text()
179+
}
180+
}
181+
176182
func extractRunContextFromComments(t *testing.T, sourcePath string) *runContext {
177183
f, err := os.Open(sourcePath)
178184
assert.NoError(t, err)
@@ -183,10 +189,13 @@ func extractRunContextFromComments(t *testing.T, sourcePath string) *runContext
183189
scanner := bufio.NewScanner(f)
184190
for scanner.Scan() {
185191
line := scanner.Text()
192+
if strings.HasPrefix(line, "/*") {
193+
skipMultilineComment(scanner)
194+
continue
195+
}
186196
if !strings.HasPrefix(line, "//") {
187197
return rc
188198
}
189-
190199
line = strings.TrimPrefix(line, "//")
191200
if strings.HasPrefix(line, "args: ") {
192201
assert.Nil(t, rc.args)

test/testdata/configs/go-header.yml

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
linters-settings:
2+
goheader:
3+
template: MY {{title}}
4+
values:
5+
const:
6+
title: TITLE.

test/testdata/go-header.go

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/*MY TITLE!*/ // ERROR "Expected:TITLE., Actual: TITLE!"
2+
//args: -Egoheader
3+
//config_path: testdata/configs/go-header.yml
4+
package testdata

0 commit comments

Comments
 (0)