diff --git a/.conform.yaml b/.conform.yaml index a0609f20..74811fea 100644 --- a/.conform.yaml +++ b/.conform.yaml @@ -33,6 +33,7 @@ policies: excludeSuffixes: - .pb.go - .pb.gw.go + allowPrecedingComments: true header: | // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/README.md b/README.md index 251c4879..4eb68b2c 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ policies: - .ext excludeSuffixes: - .exclude-ext-prefix.ext + allowPrecedingComments: false header: | This is the contents of a license header. ``` diff --git a/cmd/conform/serve.go b/cmd/conform/serve.go index 4b811591..793504a0 100644 --- a/cmd/conform/serve.go +++ b/cmd/conform/serve.go @@ -50,8 +50,8 @@ var serveCmd = &cobra.Command{ return } - //nolint: errcheck - defer os.RemoveAll(dir) + + defer os.RemoveAll(dir) //nolint:errcheck if err = os.MkdirAll(filepath.Join(dir, "github"), 0o700); err != nil { log.Printf("failed to create github directory: %+v\n", err) diff --git a/go.mod b/go.mod index 3a98768a..57ae1336 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,7 @@ module github.com/talos-systems/conform +go 1.13 + require ( github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect github.com/deckarep/golang-set v1.7.1 // indirect @@ -18,11 +20,10 @@ require ( github.com/pkg/errors v0.9.1 github.com/spf13/cobra v0.0.3 github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/testify v1.7.0 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c gonum.org/v1/gonum v0.6.1 // indirect gopkg.in/jdkato/prose.v2 v2.0.0-20180825173540-767a23049b9e gopkg.in/neurosnap/sentences.v1 v1.0.6 // indirect gopkg.in/yaml.v2 v2.3.0 ) - -go 1.13 diff --git a/internal/enforcer/enforcer.go b/internal/enforcer/enforcer.go index ed304873..8c8ba310 100644 --- a/internal/enforcer/enforcer.go +++ b/internal/enforcer/enforcer.go @@ -112,8 +112,7 @@ func (c *Conform) Enforce(setters ...policy.Option) error { } } - //nolint: errcheck - w.Flush() + w.Flush() //nolint:errcheck if !pass { return errors.New("1 or more policy failed") diff --git a/internal/policy/commit/check_jira_test.go b/internal/policy/commit/check_jira_test.go index 0596d9e0..ecdb44a8 100644 --- a/internal/policy/commit/check_jira_test.go +++ b/internal/policy/commit/check_jira_test.go @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -//nolint: testpackage +//nolint:testpackage package commit import ( diff --git a/internal/policy/commit/commit_test.go b/internal/policy/commit/commit_test.go index 102732cd..0ba4f3a8 100644 --- a/internal/policy/commit/commit_test.go +++ b/internal/policy/commit/commit_test.go @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -//nolint: testpackage +//nolint:testpackage package commit import ( diff --git a/internal/policy/license/license.go b/internal/policy/license/license.go index aa8f4455..9f9a9d9f 100644 --- a/internal/policy/license/license.go +++ b/internal/policy/license/license.go @@ -6,6 +6,7 @@ package license import ( + "bufio" "bytes" "fmt" "io/ioutil" @@ -33,6 +34,10 @@ type License struct { // ExcludeSuffixes is the Suffixes used to find files that the license policy // should not be applied to. ExcludeSuffixes []string `mapstructure:"excludeSuffixes"` + // AllowPrecedingComments, when enabled, allows blank lines and `//` and `#` line comments + // before the license header. Useful for code generators that put build constraints or + // "DO NOT EDIT" lines before the license. + AllowPrecedingComments bool `mapstructure:"allowPrecedingComments"` // Header is the contents of the license header. Header string `mapstructure:"header"` } @@ -95,7 +100,7 @@ func (l License) ValidateLicenseHeader() policy.Check { return check } - value := []byte(l.Header) + value := []byte(strings.TrimSpace(l.Header)) err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { if err != nil { @@ -124,18 +129,15 @@ func (l License) ValidateLicenseHeader() policy.Check { // Check files matching the included suffixes. for _, suffix := range l.IncludeSuffixes { if strings.HasSuffix(info.Name(), suffix) { - var contents []byte - if contents, err = ioutil.ReadFile(path); err != nil { - check.errors = append(check.errors, errors.Errorf("Failed to open %s", path)) - - return nil //nolint:nilerr + if l.AllowPrecedingComments { + err = validateFileWithPrecedingComments(path, value) + } else { + err = validateFile(path, value) } - if bytes.HasPrefix(contents, value) { - continue + if err != nil { + check.errors = append(check.errors, err) } - - check.errors = append(check.errors, errors.Errorf("File %s does not contain a license header", info.Name())) } } } @@ -148,3 +150,53 @@ func (l License) ValidateLicenseHeader() policy.Check { return check } + +func validateFile(path string, value []byte) error { + contents, err := ioutil.ReadFile(path) + if err != nil { + return errors.Errorf("Failed to read %s: %s", path, err) + } + + if bytes.HasPrefix(contents, value) { + return nil + } + + return errors.Errorf("File %s does not contain a license header", path) +} + +func validateFileWithPrecedingComments(path string, value []byte) error { + f, err := os.Open(path) + if err != nil { + return errors.Errorf("Failed to open %s: %s", path, err) + } + defer f.Close() //nolint:errcheck + + var contents []byte + + // read lines until the first non-comment line + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + + comment := line == "" + comment = comment || strings.HasPrefix(line, "//") + comment = comment || strings.HasPrefix(line, "#") + + if !comment { + break + } + + contents = append(contents, scanner.Bytes()...) + contents = append(contents, '\n') + } + + if err := scanner.Err(); err != nil { + return errors.Errorf("Failed to check file %s: %s", path, err) + } + + if bytes.Contains(contents, value) { + return nil + } + + return errors.Errorf("File %s does not contain a license header", path) +} diff --git a/internal/policy/license/license_test.go b/internal/policy/license/license_test.go new file mode 100644 index 00000000..33536920 --- /dev/null +++ b/internal/policy/license/license_test.go @@ -0,0 +1,43 @@ +//go:build !some_test_tag +// +build !some_test_tag + +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package license_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/talos-systems/conform/internal/policy/license" +) + +func TestLicense(t *testing.T) { + const header = ` +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/.` + + t.Run("Default", func(t *testing.T) { + l := license.License{ + IncludeSuffixes: []string{".go"}, + AllowPrecedingComments: false, + Header: header, + } + check := l.ValidateLicenseHeader() + assert.Equal(t, "Found 1 files without license header", check.Message()) + }) + + t.Run("AllowPrecedingComments", func(t *testing.T) { + l := license.License{ + IncludeSuffixes: []string{".go"}, + AllowPrecedingComments: true, + Header: header, + } + check := l.ValidateLicenseHeader() + assert.Equal(t, "All files have a valid license header", check.Message()) + }) +}