Skip to content

Commit

Permalink
license: Add -ignore, MockGen, tests
Browse files Browse the repository at this point in the history
This is an AI-started copy and paste from similar code in the `inconsistentReceiverName` vet check. (I'm intentionally not factoring it as a `util/` extension at this time.)

It enables calls like this:
```sh
mattermost-govet -license -license.ignore=server/manifest.go ./...
```

It also extends the existing ignore checks to include the `MockGen` header, and introduces test coverage for all.
  • Loading branch information
lieut-data committed Jan 16, 2025
1 parent 065c05c commit a4a63e1
Show file tree
Hide file tree
Showing 50 changed files with 544 additions and 40 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ require (
github.com/getkin/kin-openapi v0.61.0
github.com/pkg/errors v0.9.1
github.com/sajari/fuzzy v1.0.0
github.com/stretchr/testify v1.10.0 // indirect
golang.org/x/tools v0.23.0
)
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,17 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY=
github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
Expand Down Expand Up @@ -105,3 +113,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
121 changes: 81 additions & 40 deletions license/license.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,81 +22,122 @@ var EEAnalyzer = &analysis.Analyzer{
Run: run,
}

var sourceAvailablePackagePrefixRe = regexp.MustCompile("^github.com/mattermost/mattermost/server/v[0-9]+/enterprise")
var (
ignoreFilesPattern string
sourceAvailablePackagePrefixRe = regexp.MustCompile("^github.com/mattermost/mattermost/server/v[0-9]+/enterprise")
)

const (
defaultLicenseLine1 = "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved."
defaultLicenseLine2 = "// See LICENSE.txt for license information."
enterpriseLicenseLine2 = "// See ENTERPRISE-LICENSE.txt and SOURCE-CODE-LICENSE.txt for license information."
sourceAvailableLicenseLine2 = "// See LICENSE.enterprise for license information."
)

var buildHeaders = []string{
"//go:generate",
"//go:build",
}

var generatedHeaders = []string{
"// Code generated by mockery",
"// Code generated by MockGen. DO NOT EDIT.",
"by go-bindata DO NOT EDIT. (@generated)",
}

func init() {
Analyzer.Flags.StringVar(&ignoreFilesPattern, "ignore", "", "Comma separated list of files to ignore")
EEAnalyzer.Flags.StringVar(&ignoreFilesPattern, "ignore", "", "Comma separated list of files to ignore")
}

func run(pass *analysis.Pass) (interface{}, error) {
const mockeryHeader = "// Code generated by mockery"
const goGenerateHeader = "//go:generate"
const bindataHeader = "by go-bindata DO NOT EDIT. (@generated)"
const buildTag = "// +build"
var ignoreFiles []string
if ignoreFilesPattern != "" {
ignoreFiles = strings.Split(ignoreFilesPattern, ",")
}

expectedLine1 := defaultLicenseLine1
expectedLine2 := defaultLicenseLine2

const licenseLine1 = "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved."
const defaultLicenseLine2 = "// See LICENSE.txt for license information."
const enterpriseLicenseLine2 = "// See ENTERPRISE-LICENSE.txt and SOURCE-CODE-LICENSE.txt for license information."
const sourceAvailableLicenseLine2 = "// See LICENSE.enterprise for license information."
if pass.Analyzer.Name == "enterpriseLicense" {
// Closed source enterprise license
expectedLine2 = enterpriseLicenseLine2
} else if sourceAvailablePackagePrefixRe.MatchString(pass.Pkg.Path()) {
// Source available license
expectedLine2 = sourceAvailableLicenseLine2
}

nextFile:
for _, file := range pass.Files {
licenseLine2 := defaultLicenseLine2
if pass.Analyzer.Name == "enterpriseLicense" {
// Closed source enterprise license
licenseLine2 = enterpriseLicenseLine2
} else if sourceAvailablePackagePrefixRe.MatchString(pass.Pkg.Path()) {
// Source available license
licenseLine2 = sourceAvailableLicenseLine2
fileObj := pass.Fset.File(file.Pos())

// Ignore files specified via -ignore
if fileIsIgnored(fileObj.Name(), ignoreFiles) {
continue nextFile
}

// Ignore file copied from enterprise
fileObj := pass.Fset.File(file.Pos())
// Ignore file copied from enterprise (this can later be passed via `-ignore`)
if strings.HasSuffix(fileObj.Name(), "imports/imports.go") {
continue
continue nextFile
}

if len(file.Comments) == 0 {
pass.Reportf(file.Pos(), "License not found")
continue
continue nextFile
}

if len(file.Comments[0].List) == 0 {
pass.Reportf(file.Pos(), "License not found or wrong")
continue
}

if strings.HasPrefix(file.Comments[0].List[0].Text, mockeryHeader) {
continue
continue nextFile
}

if strings.HasSuffix(file.Comments[0].List[0].Text, bindataHeader) {
continue
// Ignore known generated files.
for _, header := range generatedHeaders {
if strings.Contains(file.Comments[0].List[0].Text, header) {
continue nextFile
}
}

// Allow build directives to precede license directives.
commentGroup := 0
if strings.HasPrefix(file.Comments[0].List[0].Text, goGenerateHeader) || strings.HasPrefix(file.Comments[0].List[0].Text, buildTag) {
if len(file.Comments[0].List) > 1 {
pass.Reportf(file.Pos(), "Must be an empty line between the build directive and the license")
continue
for _, buildHeader := range buildHeaders {
if strings.HasPrefix(file.Comments[0].List[0].Text, buildHeader) {
if len(file.Comments[0].List) > 1 {
pass.Reportf(file.Pos(), "Must be an empty line between the build directive and the license")
continue nextFile
}
commentGroup++
}
commentGroup++
}

if len(file.Comments) < commentGroup+1 {
pass.Reportf(file.Pos(), "License not found")
continue
continue nextFile
}

if len(file.Comments[commentGroup].List) < 2 {
pass.Reportf(file.Pos(), "License not found or wrong")
continue
continue nextFile
}

if file.Comments[commentGroup].List[0].Text != licenseLine1 {
pass.Reportf(file.Comments[0].List[0].Pos(), "License wrong:\n\tseen:\n\t%s\n\t%s\n\n\texpected:\n\t%s\n\t%s", file.Comments[0].List[0].Text, file.Comments[0].List[1].Text, licenseLine1, licenseLine2)
continue
if file.Comments[commentGroup].List[0].Text != expectedLine1 {
pass.Reportf(file.Comments[commentGroup].List[0].Pos(), "License copywright wrong, expected: %s", expectedLine1)
continue nextFile
}

if file.Comments[commentGroup].List[1].Text != licenseLine2 {
pass.Reportf(file.Comments[0].List[1].Pos(), "License wrong:\n\tseen:\n\t%s\n\t%s\n\n\texpected:\n\t%s\n\t%s", file.Comments[0].List[0].Text, file.Comments[0].List[1].Text, licenseLine1, licenseLine2)
continue
if file.Comments[commentGroup].List[1].Text != expectedLine2 {
pass.Reportf(file.Comments[commentGroup].List[1].Pos(), "License reference wrong, expected %s", expectedLine2)
continue nextFile
}
}
return nil, nil
}

func fileIsIgnored(file string, ignoreFiles []string) bool {
for _, f := range ignoreFiles {
if strings.HasSuffix(file, f) {
return true
}
}
return false
}
199 changes: 199 additions & 0 deletions license/license_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

package license_test

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/mattermost/mattermost-govet/v2/license"
"golang.org/x/tools/go/analysis/analysistest"
)

func TestStandard(t *testing.T) {
t.Run("ignored", func(t *testing.T) {
t.Run("specified file", func(t *testing.T) {
testdata := analysistest.TestData()
require.NoError(t, license.Analyzer.Flags.Set("ignore", "ignored1.go,ignored2.go"))
analysistest.Run(t, testdata, license.Analyzer, "standard/ignored_files")
})

t.Run("mockery", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.Analyzer, "standard/ignored/mockery")
})

t.Run("mockgen", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.Analyzer, "standard/ignored/mockgen")
})

t.Run("go-bindata", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.Analyzer, "standard/ignored/go-bin-data")
})
})

t.Run("missing license", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.Analyzer, "standard/missing")
})

t.Run("valid license", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.Analyzer, "standard/valid")
})

t.Run("invalid copyright on line 1", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.Analyzer, "standard/invalid_copyright")
})

t.Run("invalid reference on line 2", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.Analyzer, "standard/invalid_reference")
})

t.Run("build directives", func(t *testing.T) {
t.Run("go:generate with valid license", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.Analyzer, "standard/build/gogenerate")
})

t.Run("go:build with valid license", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.Analyzer, "standard/build/buildtag")
})

t.Run("directive with valid license but without newline", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.Analyzer, "standard/build/withoutnewline")
})
})
}

func TestEnterprise(t *testing.T) {
t.Run("ignored", func(t *testing.T) {
t.Run("specified file", func(t *testing.T) {
testdata := analysistest.TestData()
require.NoError(t, license.EEAnalyzer.Flags.Set("ignore", "ignored1.go,ignored2.go"))
analysistest.Run(t, testdata, license.EEAnalyzer, "enterprise/ignored_files")
})

t.Run("mockery", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.EEAnalyzer, "enterprise/ignored/mockery")
})

t.Run("mockgen", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.EEAnalyzer, "enterprise/ignored/mockgen")
})

t.Run("go-bindata", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.EEAnalyzer, "enterprise/ignored/go-bin-data")
})
})

t.Run("missing license", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.EEAnalyzer, "enterprise/missing")
})

t.Run("valid license", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.EEAnalyzer, "enterprise/valid")
})

t.Run("invalid copyright on line 1", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.EEAnalyzer, "enterprise/invalid_copyright")
})

t.Run("invalid reference on line 2", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.EEAnalyzer, "enterprise/invalid_reference")
})

t.Run("build directives", func(t *testing.T) {
t.Run("go:generate with valid license", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.EEAnalyzer, "enterprise/build/gogenerate")
})

t.Run("go:build with valid license", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.EEAnalyzer, "enterprise/build/buildtag")
})

t.Run("directive with valid license but without newline", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.EEAnalyzer, "enterprise/build/withoutnewline")
})
})
}

func TestSourceAvailable(t *testing.T) {
t.Run("ignored", func(t *testing.T) {
t.Run("specified file", func(t *testing.T) {
testdata := analysistest.TestData()
require.NoError(t, license.Analyzer.Flags.Set("ignore", "ignored1.go,ignored2.go"))
analysistest.Run(t, testdata, license.Analyzer, "github.com/mattermost/mattermost/server/v8/enterprise/ignored_files")
})

t.Run("mockery", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.Analyzer, "github.com/mattermost/mattermost/server/v8/enterprise/ignored/mockery")
})

t.Run("mockgen", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.Analyzer, "github.com/mattermost/mattermost/server/v8/enterprise/ignored/mockgen")
})

t.Run("go-bindata", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.Analyzer, "github.com/mattermost/mattermost/server/v8/enterprise/ignored/go-bin-data")
})
})

t.Run("missing license", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.Analyzer, "github.com/mattermost/mattermost/server/v8/enterprise/missing")
})

t.Run("valid license", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.Analyzer, "github.com/mattermost/mattermost/server/v8/enterprise/valid")
})

t.Run("invalid copyright on line 1", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.Analyzer, "github.com/mattermost/mattermost/server/v8/enterprise/invalid_copyright")
})

t.Run("invalid reference on line 2", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.Analyzer, "github.com/mattermost/mattermost/server/v8/enterprise/invalid_reference")
})

t.Run("build directives", func(t *testing.T) {
t.Run("go:generate with valid license", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.Analyzer, "github.com/mattermost/mattermost/server/v8/enterprise/build/gogenerate")
})

t.Run("go:build with valid license", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.Analyzer, "github.com/mattermost/mattermost/server/v8/enterprise/build/buildtag")
})

t.Run("directive with valid license but without newline", func(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, license.Analyzer, "github.com/mattermost/mattermost/server/v8/enterprise/build/withoutnewline")
})
})
}
Loading

0 comments on commit a4a63e1

Please sign in to comment.