diff --git a/README.md b/README.md index bb056c5..70547f5 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ Flags: -c int display offending line with this many lines of context (default -1) -comment-style string - Comment style (line, block) (default "line") + Comment style (line, block, starred-block) (default "line") -copyright-header-matcher string Copyright header matcher regexp (used to detect existence of any copyright header) (default "(?i)copyright") -cpuprofile string @@ -89,7 +89,7 @@ Flags: -diff with -fix, don't update the files, but print a unified diff -exclude string - Paths to exclude (doublestar or r!-prefixed regexp, comma-separated) (default "**/testdata/**") + Paths to exclude (doublestar or r!-prefixed regexp, comma-separated) -fix apply all suggested fixes -flags @@ -190,8 +190,20 @@ a file. The current supported options are: golicenser supports configuring the comment type used for the license headers. The options are: + - `line` - C-style line comments (`// test`). -- `block` - C++-style block comments (`/* test */`) +- `block` - C++-style block comments (`/* test */`). Example: +```go +/* +test +*/ +``` +- `starred-block` - Aligned, starred block comments. Example: +```go +/* + * test + */ +``` ### Matchers diff --git a/analysis.go b/analysis.go index 84b6677..91300f5 100644 --- a/analysis.go +++ b/analysis.go @@ -182,7 +182,7 @@ func (a *analyzer) checkFile(pass *analysis.Pass, file *ast.File) error { } } - if header == "" || !a.headerMatcher.MatchString(header) { + if header == "" || isDirective(header) || !a.headerMatcher.MatchString(header) { // License header is missing, generate a new one. newHeader, err := a.header.Create(filename) if err != nil { @@ -209,8 +209,7 @@ func (a *analyzer) checkFile(pass *analysis.Pass, file *ast.File) error { } if modified { pass.Report(analysis.Diagnostic{ - Pos: headerPos, - End: headerEnd, + Pos: file.Package, Message: "invalid license header", SuggestedFixes: []analysis.SuggestedFix{{ Message: "update license header", @@ -225,3 +224,27 @@ func (a *analyzer) checkFile(pass *analysis.Pass, file *ast.File) error { return nil } + +// isDirective checks if a comment is a directive. +func isDirective(s string) bool { + if len(s) < 3 || s[0] != '/' || s[1] != '/' || s[2] == ' ' { + return false + } + s = s[2:] + + // Match directives in format: "[a-z0-9]+:[a-z0-9]" + colon := strings.Index(s, ":") + if colon <= 0 || colon+1 >= len(s) { + return false + } + for i := range colon + 2 { + if i == colon { + continue + } + b := s[i] + if ('a' > b || b > 'z') && ('0' > b || b > '9') { + return false + } + } + return true +} diff --git a/analysis_test.go b/analysis_test.go index 09ec6bd..2ee2f64 100644 --- a/analysis_test.go +++ b/analysis_test.go @@ -40,7 +40,6 @@ func TestAnalyzer(t *testing.T) { t.Run("simple", func(t *testing.T) { t.Parallel() - cfg := Config{ Header: HeaderOpts{ Template: "Copyright (c) {{.year}} {{.author}}", @@ -193,6 +192,63 @@ func TestAnalyzer(t *testing.T) { _ = analysistest.Run(t, packageDir, a) }) }) + + t.Run("build directive with any matcher", func(t *testing.T) { + t.Parallel() + cfg := Config{ + Header: HeaderOpts{ + Template: "Copyright (c) {{.year}} {{.author}}", + Author: "Test", + YearMode: YearModeThisYear, + }, + CopyrightHeaderMatcher: ".+", + } + a, err := NewAnalyzer(cfg) + if err != nil { + t.Fatalf("NewAnalyzer() err = %v", err) + } + + packageDir := filepath.Join(analysistest.TestData(), "src/builddirective/") + _ = analysistest.Run(t, packageDir, a) + }) + + t.Run("comment style block", func(t *testing.T) { + t.Parallel() + cfg := Config{ + Header: HeaderOpts{ + Template: "Copyright (c) {{.year}} {{.author}}", + Author: "Test", + YearMode: YearModeThisYear, + CommentStyle: CommentStyleBlock, + }, + } + a, err := NewAnalyzer(cfg) + if err != nil { + t.Fatalf("NewAnalyzer() err = %v", err) + } + + packageDir := filepath.Join(analysistest.TestData(), "src/block/") + _ = analysistest.Run(t, packageDir, a) + }) + + t.Run("comment style starred block", func(t *testing.T) { + t.Parallel() + cfg := Config{ + Header: HeaderOpts{ + Template: "Copyright (c) {{.year}} {{.author}}", + Author: "Test", + YearMode: YearModeThisYear, + CommentStyle: CommentStyleStarredBlock, + }, + } + a, err := NewAnalyzer(cfg) + if err != nil { + t.Fatalf("NewAnalyzer() err = %v", err) + } + + packageDir := filepath.Join(analysistest.TestData(), "src/starred-block/") + _ = analysistest.Run(t, packageDir, a) + }) } func TestNewAnalyzer(t *testing.T) { diff --git a/cmd/golicenser/golicenser.go b/cmd/golicenser/golicenser.go index aa2b81c..a57841d 100644 --- a/cmd/golicenser/golicenser.go +++ b/cmd/golicenser/golicenser.go @@ -76,7 +76,7 @@ func init() { flagSet.StringVar(&yearModeStr, "year-mode", golicenser.YearMode(0).String(), "Year formatting mode (preserve, preserve-this-year-range, preserve-modified-range, this-year, last-modified, git-range, git-modified-years)") flagSet.StringVar(&commentStyleStr, "comment-style", golicenser.CommentStyle(0).String(), - "Comment style (line, block)") + "Comment style (line, block, starred-block)") flagSet.StringVar(&exclude, "exclude", "", "Paths to exclude (doublestar or r!-prefixed regexp, comma-separated)") flagSet.IntVar(&maxConcurrent, "max-concurrent", DefaultMaxConcurrent, diff --git a/header.go b/header.go index 7daacfb..f4b103f 100644 --- a/header.go +++ b/header.go @@ -127,9 +127,13 @@ const ( CommentStyleLine CommentStyle = iota // CommentStyleBlock uses C++-style block comments (/* test */). - // I strongly discourage using this as it is more idiomatic to use - // CommentStyleLine. CommentStyleBlock + + // CommentStyleStarredBlock uses aligned, starred block comments, e.g. + // /* + // * Content + // */ + CommentStyleStarredBlock ) // ParseCommentStyle parses a string representation of a comment style. @@ -139,6 +143,8 @@ func ParseCommentStyle(s string) (CommentStyle, error) { return CommentStyleLine, nil case CommentStyleBlock.String(): return CommentStyleBlock, nil + case CommentStyleStarredBlock.String(): + return CommentStyleStarredBlock, nil default: return 0, fmt.Errorf("invalid comment style: %q", s) } @@ -151,23 +157,13 @@ func (cs CommentStyle) String() string { return "line" case CommentStyleBlock: return "block" + case CommentStyleStarredBlock: + return "starred-block" default: return "" } } -// detectCommentStyle attempts to detect the comment style from a comment. -func detectCommentStyle(s string) (CommentStyle, error) { - switch { - case strings.HasPrefix(s, "// "): - return CommentStyleLine, nil - case strings.HasPrefix(s, "/*\n"): - return CommentStyleBlock, nil - default: - return 0, fmt.Errorf("not a comment: %q", s) - } -} - // Render renders the string into a comment. func (cs CommentStyle) Render(s string) string { switch cs { @@ -184,33 +180,90 @@ func (cs CommentStyle) Render(s string) string { return b.String() case CommentStyleBlock: return "/*\n" + s + "\n*/\n" + case CommentStyleStarredBlock: + var b bytes.Buffer + b.WriteString("/*\n") + for _, l := range strings.Split(s, "\n") { + b.WriteString(" *") + if l != "" { + b.WriteRune(' ') + b.WriteString(l) + } + b.WriteRune('\n') + } + b.WriteString(" */\n") + return b.String() default: // Cannot render as a comment. return s } } -// Parse parses the comment and returns the uncommented string. -func (cs CommentStyle) Parse(s string) string { - switch cs { - case CommentStyleLine: +// parseComment parses a comment and returns the comment content and detected +// comment style. An error will be returned if the comment cannot be parsed. +func parseComment(s string) (string, CommentStyle, error) { + s = strings.TrimSpace(s) + if len(s) < 2 { + return "", 0, fmt.Errorf("invalid comment: %q", s) + } + + switch { + case s[0] == '/' && s[1] == '/': var b bytes.Buffer - for i, l := range strings.Split(strings.TrimSuffix(s, "\n"), "\n") { + for i, l := range strings.Split(s, "\n") { if i != 0 { b.WriteRune('\n') } - l = strings.TrimPrefix(l, "//") + l = l[2:] if len(l) > 1 && l[0] == ' ' { l = l[1:] } b.WriteString(l) } - return b.String() - case CommentStyleBlock: - return strings.TrimSuffix(strings.TrimPrefix(s, "/*\n"), "\n*/\n") + return b.String(), CommentStyleLine, nil + case strings.HasPrefix(s, "/*") && strings.HasSuffix(s, "*/"): + s = strings.TrimSpace(s[2 : len(s)-2]) + if len(s) < 2 { + return s, CommentStyleBlock, nil + } + + var b bytes.Buffer + var starred bool + lines := strings.Split(s, "\n") + if l := lines[0]; len(l) > 0 { + if l[0] == '*' { + l = l[1:] + if len(l) > 0 && l[0] == ' ' { + l = l[1:] + } + starred = true + } + b.WriteString(l) + } + + for _, l := range lines[1:] { + b.WriteRune('\n') + if strings.HasPrefix(l, " *") { + starred = true + if len(l) > 2 && l[2] == ' ' { + b.WriteString(l[3:]) + continue + } + b.WriteString(l[2:]) + continue + } + + // Not a starred block comment, fallback to block and just return + // the raw comment content. + return s, CommentStyleBlock, nil + } + + if !starred { + return b.String(), CommentStyleBlock, nil + } + return b.String(), CommentStyleStarredBlock, nil default: - // Cannot parse as a comment. - return s + return "", 0, fmt.Errorf("cannot detect comment type: %q", s) } } @@ -341,9 +394,9 @@ func (h *Header) Create(filename string) (string, error) { // Update updates an existing license header if it matches the func (h *Header) Update(filename, header string) (string, bool, error) { - cs, err := detectCommentStyle(header) - if err == nil { - header = cs.Parse(header) + header, cs, err := parseComment(header) + if err != nil { + return "", false, fmt.Errorf("parse header comment: %w", err) } match := h.matcher.FindStringSubmatch(header) if match == nil { @@ -411,8 +464,7 @@ func (h *Header) Update(filename, header string) (string, bool, error) { if err != nil { return "", false, fmt.Errorf("render header: %w", err) } - modified := newHeader != header || cs != h.commentStyle - + modified := newHeader != header || h.commentStyle != cs return h.commentStyle.Render(newHeader), modified, nil } diff --git a/header_test.go b/header_test.go index dfd0b4a..75dad6b 100644 --- a/header_test.go +++ b/header_test.go @@ -133,61 +133,234 @@ func TestParseCommentStyle(t *testing.T) { } } -func TestCommentStyleRender(t *testing.T) { +func TestParseComment(t *testing.T) { tests := []struct { - name string - in string - want string - style CommentStyle + name string + in string + want string + wantStyle CommentStyle + wantErr bool }{ { - name: "line simple", - in: "Hello world", - want: "// Hello world\n", - style: CommentStyleLine, + name: "line simple", + in: "// Hello world\n", + want: "Hello world", + wantStyle: CommentStyleLine, }, { - name: "line multi-line", - in: "Line 1\nLine 2\nLine 3", - want: "// Line 1\n// Line 2\n// Line 3\n", - style: CommentStyleLine, + name: "line multi-line", + in: "// Line 1\n// Line 2\n// Line 3\n", + want: "Line 1\nLine 2\nLine 3", + wantStyle: CommentStyleLine, }, { - name: "line with blank line", - in: "Line 1\n\nLine 2 after blank", - want: "// Line 1\n//\n// Line 2 after blank\n", - style: CommentStyleLine, + name: "line with blank line", + in: "// Line 1\n//\n// Line 2 after blank\n", + want: "Line 1\n\nLine 2 after blank", + wantStyle: CommentStyleLine, }, { - name: "line with leading space", - in: " Line 1\n Line 2", // one leading space then two spaces - want: "// Line 1\n// Line 2\n", - style: CommentStyleLine, + name: "line with leading space", + in: "// Line 1\n// Line 2\n", + want: " Line 1\n Line 2", // one leading space then two spaces + wantStyle: CommentStyleLine, }, { - name: "block", - in: "Hello world", - want: "/*\nHello world\n*/\n", - style: CommentStyleBlock, + name: "block", + in: "/*\nHello world\n*/\n", + want: "Hello world", + wantStyle: CommentStyleBlock, }, { - name: "block mutiline", - in: "Line 1\nLine 2", - want: "/*\nLine 1\nLine 2\n*/\n", - style: CommentStyleBlock, + name: "block singleline", + in: "/* Hello world */\n", + want: "Hello world", + wantStyle: CommentStyleBlock, + }, + { + name: "block multiline", + in: "/*\nLine 1\nLine 2\n*/\n", + want: "Line 1\nLine 2", + wantStyle: CommentStyleBlock, + }, + { + name: "block singleline no padding", + in: "/*Hello world*/\n", + want: "Hello world", + wantStyle: CommentStyleBlock, + }, + { + name: "starred block", + in: "/*\n * Hello world\n */\n", + want: "Hello world", + wantStyle: CommentStyleStarredBlock, + }, + { + name: "starred block multiline", + in: "/*\n * Line 1\n * Line 2\n */\n", + want: "Line 1\nLine 2", + wantStyle: CommentStyleStarredBlock, + }, + { + name: "starred block no spaces", + in: "/*\n *test\n *test 2\n */\n", + want: "test\ntest 2", + wantStyle: CommentStyleStarredBlock, + }, + { + name: "starred block real", + in: `/* + * Copyright 2013 The Go Authors. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ +`, + want: `Copyright 2013 The Go Authors. All rights reserved. +Use of this source code is governed by a BSD-style +license that can be found in the LICENSE file.`, + wantStyle: CommentStyleStarredBlock, + }, + { + name: "starred block using first line", + in: "/* Test\n * Test 2\n */", + want: "Test\nTest 2", + wantStyle: CommentStyleStarredBlock, + }, + { + name: "invalid block", + in: "/* test", + wantErr: true, + }, + { + name: "random string", + in: "hello world", + wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := tt.style.Render(tt.in); got != tt.want { - t.Errorf("CommentStyle(%+v).Render(%q) = %q, want %q", - tt.style, tt.in, got, tt.want) + got, cs, err := parseComment(tt.in) + if (err != nil) != tt.wantErr { + t.Errorf("parseComment(%q) err = %v, want %v", tt.in, err, tt.wantErr) } + + if got != tt.want { + t.Errorf("parseComment(%q) = %q, want %q", tt.in, got, tt.want) + } + if cs != tt.wantStyle { + t.Errorf("parseComment(%q) style = %s, want %s", tt.in, cs, tt.wantStyle) + } + }) + } +} + +func BenchmarkParseComment(b *testing.B) { + benches := []struct { + name string + header string + }{ + { + name: "line", + header: "// Copyright (c) 2025 Joshua Sing\n", + }, + { + name: "block one line", + header: "/* Copyright (c) 2025 Joshua Sing */\n", + }, + { + name: "block", + header: "/*\nCopyright (c) 2025 Joshua Sing\n*/\n", + }, + { + name: "starred block", + header: "/*\n * Copyright (c) 2025 Joshua Sing\n */\n", + }, + { + name: "line mit", + header: `// Copyright (c) 2025 Joshua Sing +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +`, + }, + { + name: "block mit", + header: `/* +Copyright (c) 2025 Joshua Sing + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +`, + }, + { + name: "starred block mit", + header: `/* + * Copyright (c) 2025 Joshua Sing + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +`, + }, + } + for _, bb := range benches { + b.Run(bb.name, func(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, _, _ = parseComment(bb.header) + } + }) }) } } -func TestCommentStyleParse(t *testing.T) { +func TestCommentStyleRender(t *testing.T) { tests := []struct { name string in string @@ -196,43 +369,62 @@ func TestCommentStyleParse(t *testing.T) { }{ { name: "line simple", - in: "// Hello world\n", - want: "Hello world", + in: "Hello world", + want: "// Hello world\n", style: CommentStyleLine, }, { name: "line multi-line", - in: "// Line 1\n// Line 2\n// Line 3\n", - want: "Line 1\nLine 2\nLine 3", + in: "Line 1\nLine 2\nLine 3", + want: "// Line 1\n// Line 2\n// Line 3\n", style: CommentStyleLine, }, { name: "line with blank line", - in: "// Line 1\n//\n// Line 2 after blank\n", - want: "Line 1\n\nLine 2 after blank", + in: "Line 1\n\nLine 2 after blank", + want: "// Line 1\n//\n// Line 2 after blank\n", style: CommentStyleLine, }, { name: "line with leading space", - in: "// Line 1\n// Line 2\n", - want: " Line 1\n Line 2", // one leading space then two spaces + in: " Line 1\n Line 2", // one leading space then two spaces + want: "// Line 1\n// Line 2\n", style: CommentStyleLine, }, { - in: "/*\nHello world\n*/\n", - want: "Hello world", + name: "block", + in: "Hello world", + want: "/*\nHello world\n*/\n", style: CommentStyleBlock, }, { - in: "/*\nLine 1\nLine 2\n*/\n", - want: "Line 1\nLine 2", + name: "block mutiline", + in: "Line 1\nLine 2", + want: "/*\nLine 1\nLine 2\n*/\n", style: CommentStyleBlock, }, + { + name: "starred block", + in: "Hello world", + want: "/*\n * Hello world\n */\n", + style: CommentStyleStarredBlock, + }, + { + name: "starred block multiline", + in: "Line 1\nLine 2\nLine 3", + want: `/* + * Line 1 + * Line 2 + * Line 3 + */ +`, + style: CommentStyleStarredBlock, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := tt.style.Parse(tt.in); got != tt.want { - t.Errorf("CommentStyle(%+v).Parse(%q) = %q, want %q", + if got := tt.style.Render(tt.in); got != tt.want { + t.Errorf("CommentStyle(%+v).Render(%q) = %q, want %q", tt.style, tt.in, got, tt.want) } }) @@ -679,7 +871,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -`, +*/`, want: `// Copyright (c) 2025 Joshua Sing // // Permission to use, copy, modify, and distribute this software for any @@ -718,6 +910,140 @@ SOFTWARE. } } +func BenchmarkHeaderUpdate(b *testing.B) { + benches := []struct { + name string + opts HeaderOpts + header string + }{ + { + name: "basic", + opts: HeaderOpts{ + Template: "Copyright (c) {{.year}} {{.author}}", + Author: "Joshua Sing", + YearMode: YearModeThisYear, + CommentStyle: CommentStyleLine, + }, + header: "// Copyright (c) 2025 Joshua Sing", + }, + { + name: "basic outdated", + opts: HeaderOpts{ + Template: "Copyright (c) {{.year}} {{.author}}", + Author: "Joshua Sing", + YearMode: YearModeThisYear, + CommentStyle: CommentStyleLine, + }, + header: "// Copyright (c) 2001 Joshua Sing", + }, + { + name: "line mit", + opts: HeaderOpts{ + Template: LicenseMIT, + Author: "Joshua Sing", + YearMode: YearModeThisYear, + CommentStyle: CommentStyleLine, + }, + header: `// Copyright (c) 2025 Joshua Sing +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +`, + }, + { + name: "block mit", + opts: HeaderOpts{ + Template: LicenseMIT, + Author: "Joshua Sing", + YearMode: YearModeThisYear, + CommentStyle: CommentStyleBlock, + }, + header: `/* +Copyright (c) 2025 Joshua Sing + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +`, + }, + { + name: "starred block mit", + opts: HeaderOpts{ + Template: LicenseMIT, + Author: "Joshua Sing", + YearMode: YearModeThisYear, + CommentStyle: CommentStyleBlock, + }, + header: `/* + * Copyright (c) 2025 Joshua Sing + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +`, + }, + } + for _, bb := range benches { + b.Run(bb.name, func(b *testing.B) { + h, err := NewHeader(bb.opts) + if err != nil { + b.Fatalf("NewHeader err = %v", err) + } + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, _, _ = h.Update("test", bb.header) + } + }) + }) + } +} + func TestHeaderMatcher(t *testing.T) { t.Parallel() diff --git a/testdata/src/block/missing.go b/testdata/src/block/missing.go new file mode 100644 index 0000000..149a859 --- /dev/null +++ b/testdata/src/block/missing.go @@ -0,0 +1 @@ +package block // want "missing license header" diff --git a/testdata/src/block/missing.go.golden b/testdata/src/block/missing.go.golden new file mode 100644 index 0000000..06b2faf --- /dev/null +++ b/testdata/src/block/missing.go.golden @@ -0,0 +1,5 @@ +/* +Copyright (c) 2025 Test +*/ + +package block // want "missing license header" diff --git a/testdata/src/block/outdated.go b/testdata/src/block/outdated.go new file mode 100644 index 0000000..ff3592b --- /dev/null +++ b/testdata/src/block/outdated.go @@ -0,0 +1,5 @@ +/* +Copyright (c) 2001 Test +*/ + +package block // want "invalid license header" diff --git a/testdata/src/block/outdated.go.golden b/testdata/src/block/outdated.go.golden new file mode 100644 index 0000000..7486243 --- /dev/null +++ b/testdata/src/block/outdated.go.golden @@ -0,0 +1,5 @@ +/* +Copyright (c) 2025 Test +*/ + +package block // want "invalid license header" diff --git a/testdata/src/differentmatcher/main.go b/testdata/src/differentmatcher/main.go index d296882..fa2f866 100644 --- a/testdata/src/differentmatcher/main.go +++ b/testdata/src/differentmatcher/main.go @@ -1,5 +1,5 @@ -// Copyright (c) 2025 Joshua // want "invalid license header" +// Copyright (c) 2025 Joshua // This header is different, but should be matched by the header matcher and // be replaced. -package differentmatcher +package differentmatcher // want "invalid license header" diff --git a/testdata/src/differentmatcher/main.go.golden b/testdata/src/differentmatcher/main.go.golden index fc2cf4f..c3ff2b9 100644 --- a/testdata/src/differentmatcher/main.go.golden +++ b/testdata/src/differentmatcher/main.go.golden @@ -1,3 +1,3 @@ // Copyright (c) 2025 Test -package differentmatcher +package differentmatcher // want "invalid license header" diff --git a/testdata/src/outdated/main.go b/testdata/src/outdated/main.go index b2e46fc..2a49e14 100644 --- a/testdata/src/outdated/main.go +++ b/testdata/src/outdated/main.go @@ -1,3 +1,3 @@ -// Copyright (c) 2001 Test // want "invalid license header" +// Copyright (c) 2001 Test -package outdated +package outdated // want "invalid license header" diff --git a/testdata/src/outdated/main.go.golden b/testdata/src/outdated/main.go.golden index 3f72b35..5cac3bb 100644 --- a/testdata/src/outdated/main.go.golden +++ b/testdata/src/outdated/main.go.golden @@ -1,3 +1,3 @@ // Copyright (c) 2025 Test -package outdated +package outdated // want "invalid license header" diff --git a/testdata/src/outdated/style.go b/testdata/src/outdated/style.go new file mode 100644 index 0000000..41594b9 --- /dev/null +++ b/testdata/src/outdated/style.go @@ -0,0 +1,3 @@ +/* Copyright (c) 2001 Test */ + +package outdated // want "invalid license header" diff --git a/testdata/src/outdated/style.go.golden b/testdata/src/outdated/style.go.golden new file mode 100644 index 0000000..5cac3bb --- /dev/null +++ b/testdata/src/outdated/style.go.golden @@ -0,0 +1,3 @@ +// Copyright (c) 2025 Test + +package outdated // want "invalid license header" diff --git a/testdata/src/starred-block/missing.go b/testdata/src/starred-block/missing.go new file mode 100644 index 0000000..149a859 --- /dev/null +++ b/testdata/src/starred-block/missing.go @@ -0,0 +1 @@ +package block // want "missing license header" diff --git a/testdata/src/starred-block/missing.go.golden b/testdata/src/starred-block/missing.go.golden new file mode 100644 index 0000000..5ebc49b --- /dev/null +++ b/testdata/src/starred-block/missing.go.golden @@ -0,0 +1,5 @@ +/* + * Copyright (c) 2025 Test + */ + +package block // want "missing license header" diff --git a/testdata/src/starred-block/outdated.go b/testdata/src/starred-block/outdated.go new file mode 100644 index 0000000..46a3c42 --- /dev/null +++ b/testdata/src/starred-block/outdated.go @@ -0,0 +1,5 @@ +/* + * Copyright (c) 2001 Test + */ + +package block // want "invalid license header" diff --git a/testdata/src/starred-block/outdated.go.golden b/testdata/src/starred-block/outdated.go.golden new file mode 100644 index 0000000..98d5e29 --- /dev/null +++ b/testdata/src/starred-block/outdated.go.golden @@ -0,0 +1,5 @@ +/* + * Copyright (c) 2025 Test + */ + +package block // want "invalid license header"