Skip to content

Commit 6180759

Browse files
authored
header: rename MatchTemplate to Matcher (#5)
1 parent d0e0e75 commit 6180759

File tree

8 files changed

+131
-71
lines changed

8 files changed

+131
-71
lines changed

README.md

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ A fast, easy-to-use, license header linter for Go.
2424
| Git support || Limited¹ |||
2525
| Exclude patterns | ✅ (regexp & doublestar) ||||
2626
| Uses Go `text/template` |||||
27-
| Customise existing matcher |||||
27+
| Customise header matcher |||||
2828
| Supports non-Go files | Planned ||||
2929
| Supported by golangci-lint | Planned ||||
3030

@@ -196,12 +196,15 @@ golicenser supports configuring the comment type used for the license headers. T
196196
golicenser allows changing the regexp matchers used to detect existing license headers, and to tell when they should be
197197
replaced.
198198

199-
#### Match template
199+
#### Matcher
200200

201-
The "match template" is a regexp expression used to match the license headers used by the project. Headers matched by
202-
this matcher will be updated or replaced.
201+
The matcher is a regexp expression used to match the license headers generated by golicenser (or otherwise used by the
202+
project). Matched headers are updated or replaced by golicenser.
203203

204-
By default, the license header template will be used.
204+
By default, the regexp-escaped value of license header template will be used.
205+
206+
In some cases it may be easier to provide the plain license header here, as such it is possible to set `MatcherEscape` (
207+
or `-matcher-escape`) to always regexp-escape the matcher value.
205208

206209
##### Variables
207210

@@ -212,13 +215,14 @@ year changes).
212215
- `filename` - `.+`
213216
- `year` - `(\d{4})|(\d{4})-(\d{4])|(\d{4})(?:, (\d{4}))+` - Matches a single year, year range or listed years.
214217

215-
#### Match header
218+
#### Copyright header matcher
216219

217-
The "match header" is used to detect **any** copyright header. By default, `(?i)copyright` is used, however this can be
218-
customised in order to prevent false-positives.
220+
The copyright header matcher is used to detect **any** copyright header. By default, `(?i)copyright` is used, however
221+
this can be customised in order to prevent false-positives.
219222

220-
If a copyright header exists and does not match the [match template](#match-template), then a license header will not be
221-
created.
223+
If a copyright header exists and does not match the [header matcher](#matcher), then a license header will not be
224+
created. If a file starts with a comment that does not match the copyright header matcher, then a new license header
225+
will be generated.
222226

223227
## Contributing
224228

analysis.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@ import (
3636
const (
3737
analyzerName = "golicenser"
3838

39-
// DefaultMatchHeaderRegexp is the default regexp used to detect license
40-
// headers. This will match any header containing "copyright".
41-
DefaultMatchHeaderRegexp = "(?i)copyright"
39+
// DefaultCopyrightHeaderMatcher is the default regexp used to detect the
40+
// existence of any copyright header This will match any header containing
41+
// "copyright".
42+
DefaultCopyrightHeaderMatcher = "(?i)copyright"
4243
)
4344

4445
var (
@@ -56,9 +57,9 @@ var (
5657
type Config struct {
5758
Header HeaderOpts
5859

59-
Exclude []string
60-
MaxConcurrent int
61-
MatchHeaderRegexp string
60+
Exclude []string
61+
MaxConcurrent int
62+
CopyrightHeaderMatcher string
6263
}
6364

6465
// NewAnalyzer creates a golicenser analyzer.
@@ -92,8 +93,8 @@ func newAnalyzer(cfg Config) (*analyzer, error) {
9293
if cfg.MaxConcurrent < 1 {
9394
cfg.MaxConcurrent = DefaultMaxConcurrent
9495
}
95-
if cfg.MatchHeaderRegexp == "" {
96-
cfg.MatchHeaderRegexp = DefaultMatchHeaderRegexp
96+
if cfg.CopyrightHeaderMatcher == "" {
97+
cfg.CopyrightHeaderMatcher = DefaultCopyrightHeaderMatcher
9798
}
9899
if cfg.Exclude == nil {
99100
cfg.Exclude = DefaultExcludes
@@ -102,7 +103,7 @@ func newAnalyzer(cfg Config) (*analyzer, error) {
102103
a := &analyzer{cfg: cfg}
103104

104105
var err error
105-
a.headerMatcher, err = regexp.Compile(a.cfg.MatchHeaderRegexp)
106+
a.headerMatcher, err = regexp.Compile(a.cfg.CopyrightHeaderMatcher)
106107
if err != nil {
107108
return nil, fmt.Errorf("compile match header regexp: %w", err)
108109
}

analysis_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,51 @@ func TestAnalyzer(t *testing.T) {
7878
_ = analysistest.RunWithSuggestedFixes(t, packageDir, a)
7979
})
8080
})
81+
82+
t.Run("with matcher", func(t *testing.T) {
83+
cfg := Config{
84+
Header: HeaderOpts{
85+
Template: "Copyright (c) {{.year}} {{.author}}",
86+
Author: "Test",
87+
Matcher: "Copyright \\(c\\) {{.year}} Joshua",
88+
YearMode: YearModeThisYear,
89+
},
90+
Exclude: []string{},
91+
}
92+
a, err := NewAnalyzer(cfg)
93+
if err != nil {
94+
t.Fatalf("NewAnalyzer() err = %v", err)
95+
}
96+
97+
// differentmatcher contains a header that will be matched by Matcher
98+
// but is different from the Template.
99+
t.Run("differentmatcher", func(t *testing.T) {
100+
packageDir := filepath.Join(analysistest.TestData(), "src/differentmatcher/")
101+
_ = analysistest.Run(t, packageDir, a)
102+
})
103+
})
104+
105+
t.Run("with escaped matcher", func(t *testing.T) {
106+
cfg := Config{
107+
Header: HeaderOpts{
108+
Template: "Copyright (c) {{.year}} {{.author}}",
109+
Author: "Test",
110+
Matcher: "Copyright (c) {{.year}} Joshua",
111+
MatcherEscape: true,
112+
YearMode: YearModeThisYear,
113+
},
114+
Exclude: []string{},
115+
}
116+
a, err := NewAnalyzer(cfg)
117+
if err != nil {
118+
t.Fatalf("NewAnalyzer() err = %v", err)
119+
}
120+
121+
// differentmatcher contains a header that will be matched with the
122+
// escaped Matcher but is different from the Template.
123+
t.Run("differentmatcher", func(t *testing.T) {
124+
packageDir := filepath.Join(analysistest.TestData(), "src/differentmatcher/")
125+
_ = analysistest.Run(t, packageDir, a)
126+
})
127+
})
81128
}

cmd/golicenser/golicenser.go

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -36,31 +36,31 @@ import (
3636
var flagSet flag.FlagSet
3737

3838
var (
39-
template string
40-
templateFile string
41-
matchTemplate string
42-
matchTemplateFile string
43-
matchTemplateEscape bool
44-
author string
45-
authorRegexp string
46-
variables string
47-
yearModeStr string
48-
commentStyleStr string
49-
exclude string
50-
maxConcurrent int
51-
matchHeaderRegexp string
39+
template string
40+
templateFile string
41+
matcher string
42+
matcherFile string
43+
matcherEscape bool
44+
author string
45+
authorRegexp string
46+
variables string
47+
yearModeStr string
48+
commentStyleStr string
49+
exclude string
50+
maxConcurrent int
51+
copyrightHeaderMatcher string
5252
)
5353

5454
func init() {
5555
flagSet.StringVar(&template, "tmpl", "", "License header template")
5656
flagSet.StringVar(&templateFile, "tmpl-file", "license_header.txt",
5757
"License header template file")
58-
flagSet.StringVar(&matchTemplate, "match-tmpl", "",
59-
"Match license header template")
60-
flagSet.StringVar(&matchTemplateFile, "match-tmpl-file", "",
61-
"Match license header template file (used to detect existing license headers which may be updated)")
62-
flagSet.BoolVar(&matchTemplateEscape, "match-tmpl-escape", false,
63-
"Whether to regexp-escape the match-tmpl")
58+
flagSet.StringVar(&matcher, "matcher", "",
59+
"License header matcher (This is template, when executed it must become valid regexp)")
60+
flagSet.StringVar(&matcherFile, "matcher-file", "",
61+
"License header matcher file)")
62+
flagSet.BoolVar(&matcherEscape, "matcher-escape", false,
63+
"Whether to regexp-escape the matcher")
6464
flagSet.StringVar(&author, "author", "", "Copyright author")
6565
flagSet.StringVar(&authorRegexp, "author-regexp", "",
6666
"Regexp to match copyright author (default: match author)")
@@ -73,8 +73,8 @@ func init() {
7373
"Paths to exclude (doublestar or r!-prefixed regexp, comma-separated)")
7474
flagSet.IntVar(&maxConcurrent, "max-concurrent", golicenser.DefaultMaxConcurrent,
7575
"Maximum concurrent processes to use when processing files")
76-
flagSet.StringVar(&matchHeaderRegexp, "match-header-regexp", golicenser.DefaultMatchHeaderRegexp,
77-
"Match header regexp (used to detect any copyright headers)")
76+
flagSet.StringVar(&copyrightHeaderMatcher, "copyright-header-matcher", golicenser.DefaultCopyrightHeaderMatcher,
77+
"Copyright header matcher regexp (used to detect existence of any copyright header)")
7878
}
7979

8080
// TODO(joshuasing): There has to be a better way of doing this...
@@ -97,16 +97,16 @@ var analyzer = &analysis.Analyzer{
9797
template = tm
9898
}
9999
}
100-
if matchTemplate == "" && matchTemplateFile != "" {
100+
if matcher == "" && matcherFile != "" {
101101
//nolint:gosec // Reading user-defined file.
102-
b, err := os.ReadFile(matchTemplateFile)
102+
b, err := os.ReadFile(matcherFile)
103103
if err != nil {
104104
log.Fatal("read match template file: ", err)
105105
}
106-
matchTemplate = string(b)
106+
matcher = string(b)
107107
} else {
108-
if tm, ok := golicenser.TemplateBySPDX(matchTemplate); ok {
109-
matchTemplate = tm
108+
if tm, ok := golicenser.TemplateBySPDX(matcher); ok {
109+
matcher = tm
110110
}
111111
}
112112

@@ -136,18 +136,18 @@ var analyzer = &analysis.Analyzer{
136136

137137
a, err := golicenser.NewAnalyzer(golicenser.Config{
138138
Header: golicenser.HeaderOpts{
139-
Template: template,
140-
MatchTemplate: matchTemplate,
141-
MatchTemplateEscape: matchTemplateEscape,
142-
Author: author,
143-
AuthorRegexp: authorRegexp,
144-
Variables: vars,
145-
YearMode: yearMode,
146-
CommentStyle: commentStyle,
139+
Template: template,
140+
Matcher: matcher,
141+
MatcherEscape: matcherEscape,
142+
Author: author,
143+
AuthorRegexp: authorRegexp,
144+
Variables: vars,
145+
YearMode: yearMode,
146+
CommentStyle: commentStyle,
147147
},
148-
Exclude: strings.Split(exclude, ","),
149-
MaxConcurrent: maxConcurrent,
150-
MatchHeaderRegexp: matchHeaderRegexp,
148+
Exclude: strings.Split(exclude, ","),
149+
MaxConcurrent: maxConcurrent,
150+
CopyrightHeaderMatcher: copyrightHeaderMatcher,
151151
})
152152
if err != nil {
153153
log.Fatal(err)

header.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -232,14 +232,14 @@ var tmplFuncMap = template.FuncMap{
232232

233233
// HeaderOpts are the options for creating a license header.
234234
type HeaderOpts struct {
235-
Template string
236-
MatchTemplate string
237-
MatchTemplateEscape bool
238-
Author string
239-
AuthorRegexp string
240-
Variables map[string]any
241-
YearMode YearMode
242-
CommentStyle CommentStyle
235+
Template string
236+
Matcher string
237+
MatcherEscape bool
238+
Author string
239+
AuthorRegexp string
240+
Variables map[string]any
241+
YearMode YearMode
242+
CommentStyle CommentStyle
243243
}
244244

245245
// NewHeader creates a new header with the given options.
@@ -280,13 +280,13 @@ func NewHeader(opts HeaderOpts) (*Header, error) {
280280
}
281281

282282
var matcher *regexp.Regexp
283-
if opts.MatchTemplate != "" {
283+
if opts.Matcher != "" {
284284
mt, err := template.New("").Funcs(tmplFuncMap).
285-
Option("missingkey=error").Parse(opts.MatchTemplate)
285+
Option("missingkey=error").Parse(opts.Matcher)
286286
if err != nil {
287287
return nil, fmt.Errorf("new match template: %w", err)
288288
}
289-
matcher, err = headerMatcher(mt, opts.MatchTemplateEscape, authorRegexp, opts.Variables)
289+
matcher, err = headerMatcher(mt, opts.MatcherEscape, authorRegexp, opts.Variables)
290290
if err != nil {
291291
return nil, fmt.Errorf("create header matcher (with match template): %w", err)
292292
}

header_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -281,10 +281,10 @@ func TestHeaderUpdate(t *testing.T) {
281281
{
282282
name: "change MIT to OpenBSD",
283283
header: HeaderOpts{
284-
Template: LicenseOpenBSD,
285-
MatchTemplate: LicenseMIT,
286-
MatchTemplateEscape: true,
287-
Author: "Joshua Sing",
284+
Template: LicenseOpenBSD,
285+
Matcher: LicenseMIT,
286+
MatcherEscape: true,
287+
Author: "Joshua Sing",
288288
},
289289
wantModified: true,
290290
existing: `// Copyright (c) 2025 Joshua Sing
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright (c) 2025 Joshua // want "invalid license header"
2+
// This header is different, but should be matched by the header matcher and
3+
// be replaced.
4+
5+
package differentmatcher
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Copyright (c) 2025 Test
2+
3+
package differentmatcher

0 commit comments

Comments
 (0)