Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ jobs:
runs-on: ubuntu-latest
steps:

- name: Set up Go 1.21
- name: Set up Go 1.23
uses: actions/setup-go@v1
with:
go-version: 1.21
go-version: 1.23

- name: Check out code into the Go module directory
uses: actions/checkout@v2
Expand All @@ -32,4 +32,4 @@ jobs:
run: go install ./...

- name: Self-check
run: go-header $(git ls-files | grep -E '.*\.go$')
run: go-header ./...
11 changes: 5 additions & 6 deletions .go-header.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
values:
regexp:
copyright-holder: Copyright \(c\) {{mod-year-range}} Denis Tingaikin
template: |
{{copyright-holder}}
vars:
copyright_holder: "{{ .MOD_YEAR_RANGE}} Denis Tingaikin"
template: |-
Copyright (c) {{ .copyright_holder }}

SPDX-License-Identifier: Apache-2.0

Expand All @@ -16,4 +15,4 @@ template: |
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
limitations under the License.
68 changes: 58 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
# go-header
[![ci](https://github.com/denis-tingaikin/go-header/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/denis-tingaikin/go-header/actions/workflows/ci.yml)

Go source code linter providing checks for license headers.
Simple go source code linter providing checks for copyrgiht headers.

## Features

| Feature | Status | Details |
|-----------------------------|--------|------------------------------------------|
| ✅ **Copyright Headers** | ✔️ | Supports all standard formats |
| ✅ **Parallel Processing** | ✔️ | Processes files concurrently |
| ✅ **Comment Support** | ✔️ | `//`, `/* */`, `/* * */` |
| ✅ **Go/Analysis** | ✔️ | Native Go tooling integration |
| ✅ **Regex Customization** | ✔️ | User-defined pattern matching |
| ✅ **Automatic Year Checks** | ✔️ | Validates & updates copyright years |
| ✅ **Auto-Fix Files** | ✔️ | In-place header corrections |
| ✅ **Go/Template Support** | ✔️ | *In development* |
| ⏳ **Multi-License Support** | ❌ | Does any one need this? 🤔 |



## Installation

Expand All @@ -10,9 +26,41 @@ For installation you can simply use `go install`.
```bash
go install github.com/denis-tingaikin/go-header/cmd/go-header@latest
```
## Usage

```bash
-V print version and exit
-all
no effect (deprecated)
-c int
display offending line with this many lines of context (default -1)
-config string
path to config file (default ".go-header.yml")
-cpuprofile string
write CPU profile to this file
-debug string
debug flags, any subset of "fpstv"
-diff
with -fix, don't update the files, but print a unified diff
-fix
apply all suggested fixes
-flags
print analyzer flags in JSON
-json
emit JSON output
-memprofile string
write memory profile to this file
-source
no effect (deprecated)
-tags string
no effect (deprecated)
-test
indicates whether test files should be analyzed, too (default true)
-trace string
write trace log to this file
-v no effect (deprecated)
```
## Configuration

To configuring `.go-header.yml` linter you simply need to fill the next fields:

```yaml
Expand All @@ -38,8 +86,8 @@ values:

## Bult-in values

- **MOD-YEAR** - Returns the year when the file was modified.
- **MOD-YEAR-RANGE** - Returns a year-range where the range starts from the year when the file was modified.
- **MOD_YEAR** - Returns the year when the file was modified.
- **MOD_YEAR-RANGE** - Returns a year-range where the range starts from the year when the file was modified.
- **YEAR** - Expects current year. Example header value: `2020`. Example of template using: `{{YEAR}}` or `{{year}}`.
- **YEAR-RANGE** - Expects any valid year interval or current year. Example header value: `2020` or `2000-2020`. Example of template using: `{{year-range}}` or `{{YEAR-RANGE}}`.

Expand All @@ -48,7 +96,7 @@ values:
`go-header` linter expects file paths on input. If you want to run `go-header` only on diff files, then you can use this command:

```bash
go-header $(git diff --name-only | grep -E '.*\.go')
go-header ./...
```

## Setup example
Expand All @@ -59,11 +107,11 @@ Create configuration file `.go-header.yml` in the root of project.

```yaml
---
values:
const:
MY COMPANY: mycompany.com
vars:
DOMAIN: sales|product
MY_COMPANY: {{ .DOMAIN }}.mycompany.com
template: |
{{ MY COMPANY }}
{{ .MY_COMPANY }}
SPDX-License-Identifier: Apache-2.0

Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -80,4 +128,4 @@ template: |
```

### Step 2
You are ready! Execute `go-header ${PATH_TO_FILES}` from the root of the project.
Run `go-header ./...`
164 changes: 164 additions & 0 deletions analysis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Copyright (c) 2020-2025 Denis Tingaikin
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package goheader

import (
"go/ast"
"runtime"
"strings"
"sync"

"golang.org/x/tools/go/analysis"
)

// NewAnalyzer creates new analyzer based on template and goheader values
func NewAnalyzer(c *Config) *analysis.Analyzer {
var initOncer sync.Once
var initErr error
var goheader *Analyzer

return &analysis.Analyzer{
Doc: "the_only_doc",
URL: "https://github.com/denis-tingaikin/go-header",
Name: "goheader",
RunDespiteErrors: true,
Run: func(p *analysis.Pass) (any, error) {
initOncer.Do(func() {
var templ string
var vals map[string]Value

templ, initErr = c.GetTemplate()
if initErr != nil {
return
}

vals, initErr = c.GetValues()
if initErr != nil {
return
}

goheader = New(WithTemplate(templ), WithValues(vals))

})
if initErr != nil {
return nil, initErr
}

var wg sync.WaitGroup

var jobCh = make(chan *ast.File, len(p.Files))

for _, f := range p.Files {
file := f
jobCh <- file
}
close(jobCh)

for range runtime.NumCPU() {
wg.Add(1)
go func() {
defer wg.Done()

for file := range jobCh {
filename := p.Fset.Position(file.Pos()).Filename
if !strings.HasSuffix(filename, ".go") {
continue
}

res := goheader.Analyze(&Target{
File: file,
Path: filename,
})

if res.Err == nil {
continue
}
var line = 1
if hasCGOImport(file) {
line = 4
}

var start = p.Fset.File(file.FileStart).LineStart(line)
var end = res.End - res.Start + start
var endLine = p.Fset.File(file.FileStart).Line(end) + 1
end = p.Fset.File(file.FileStart).LineStart(endLine)

diag := analysis.Diagnostic{
Pos: start,
End: end,
Message: res.Err.Error(),
}

if res.Fix != "" {
diag.SuggestedFixes = []analysis.SuggestedFix{{
TextEdits: []analysis.TextEdit{{
Pos: start,
End: end,
NewText: []byte(res.Fix + "\n"),
}},
}}
}
p.Report(diag)
}
}()
}

wg.Wait()
return nil, nil
},
}
}
func hasCGOImport(file *ast.File) bool {
var unsafeCount int
for _, imp := range file.Imports {
if imp.Path.Value == `"C"` {
return true
}
if imp.Path.Value == `"unsafe"` {
unsafeCount++
}
}
return unsafeCount > 1
}

// NewAnalyzerFromConfigPath creates a new analysis.Analyzer from goheader config file
func NewAnalyzerFromConfigPath(config *string) *analysis.Analyzer {
var goheaderOncer sync.Once
var goheader *analysis.Analyzer

return &analysis.Analyzer{
Doc: "the_only_doc",
URL: "https://github.com/denis-tingaikin/go-header",
Name: "goheader",
RunDespiteErrors: true,
Run: func(p *analysis.Pass) (any, error) {
var err error
goheaderOncer.Do(func() {
var cfg Config
if err = cfg.Parse(*config); err != nil {
return
}
goheader = NewAnalyzer(&cfg)
})

if err != nil {
return nil, err
}
return goheader.Run(p)
},
}
}
Loading