From f80b873ba8dd59329478ca00ecffa157ebdff1fe Mon Sep 17 00:00:00 2001 From: Ryan Currah Date: Sat, 19 Dec 2020 23:07:08 -0500 Subject: [PATCH] Check for local replace directives (#6) * feat: now can raise lint issues on local replace directives * fix: do not equal fold when comparing module names because they are case sensitive * refactor(log): removed logging from library code because libs should not log just return errors --- .gomodguard.yaml => _example/.gomodguard.yaml | 11 +- _example/blocked_example.go | 7 + _example/go.mod | 12 ++ _example/go.sum | 30 ++++ cmd.go | 7 +- go.mod | 1 - go.sum | 8 +- gomodguard.go | 153 ++++++++---------- gomodguard_test.go | 94 ++++++----- 9 files changed, 179 insertions(+), 144 deletions(-) rename .gomodguard.yaml => _example/.gomodguard.yaml (77%) create mode 100644 _example/go.mod create mode 100644 _example/go.sum diff --git a/.gomodguard.yaml b/_example/.gomodguard.yaml similarity index 77% rename from .gomodguard.yaml rename to _example/.gomodguard.yaml index 38a2f0b..8581f90 100644 --- a/.gomodguard.yaml +++ b/_example/.gomodguard.yaml @@ -3,6 +3,7 @@ allowed: - gopkg.in/yaml.v2 - github.com/go-xmlfmt/xmlfmt - github.com/Masterminds/semver + - github.com/ryancurrah/gomodguard domains: # List of allowed module domains - golang.org @@ -12,16 +13,14 @@ blocked: recommendations: # Recommended modules that should be used instead (Optional) - golang.org/x/mod reason: "`mod` is the official go.mod parser library." # Reason why the recommended module should be used (Optional) - - github.com/mitchellh/go-homedir: + - github.com/gofrs/uuid: recommendations: - github.com/ryancurrah/gomodguard - reason: "testing if the current/linted module is not blocked when it is recommended" - - github.com/phayes/checkstyle: - recommendations: - - github.com/someother/module - reason: "testing if module is blocked with recommendation" + reason: "testing if module is not blocked when it is recommended." versions: - github.com/mitchellh/go-homedir: version: "<= 1.1.0" reason: "testing if blocked version constraint works." + + local_replace_directives: true diff --git a/_example/blocked_example.go b/_example/blocked_example.go index 6d2974d..13862d2 100644 --- a/_example/blocked_example.go +++ b/_example/blocked_example.go @@ -4,6 +4,8 @@ import ( "io/ioutil" "github.com/gofrs/uuid" + "github.com/mitchellh/go-homedir" + "github.com/ryancurrah/gomodguard" module "github.com/uudashr/go-module" ) @@ -21,4 +23,9 @@ func aBlockedImport() { // nolint: deadcode,unused _ = mod _ = uuid.Must(uuid.NewV4()) + + var blockedModule gomodguard.BlockedModule + blockedModule.Set("some.com/module/name") + + _, _ = homedir.Expand("~/something") } diff --git a/_example/go.mod b/_example/go.mod new file mode 100644 index 0000000..026b69d --- /dev/null +++ b/_example/go.mod @@ -0,0 +1,12 @@ +module github.com/ryancurrah/example + +go 1.15 + +require ( + github.com/gofrs/uuid v3.3.0+incompatible + github.com/mitchellh/go-homedir v1.1.0 + github.com/ryancurrah/gomodguard v1.1.0 + github.com/uudashr/go-module v0.0.0-20200529023307-c90a4239ad70 +) + +replace github.com/ryancurrah/gomodguard => ../ diff --git a/_example/go.sum b/_example/go.sum new file mode 100644 index 0000000..4b9e223 --- /dev/null +++ b/_example/go.sum @@ -0,0 +1,30 @@ +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo= +github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= +github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84= +github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d h1:CdDQnGF8Nq9ocOS/xlSptM1N3BbrA6/kmaep5ggwaIA= +github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= +github.com/uudashr/go-module v0.0.0-20200529023307-c90a4239ad70 h1:t/4GlAfaNAVbh8GqZmHl96pFwlaw7+DAwp2OjUMOxgw= +github.com/uudashr/go-module v0.0.0-20200529023307-c90a4239ad70/go.mod h1:P6Nk1sQWL6jcdBIxnLVlqCsOl0arao7gg7sPoM6gx4A= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/cmd.go b/cmd.go index 652e61f..89a22ae 100644 --- a/cmd.go +++ b/cmd.go @@ -82,11 +82,16 @@ func Run() int { filteredFiles := GetFilteredFiles(cwd, noTest, args) - processor, err := NewProcessor(*config, logger) + processor, err := NewProcessor(config) if err != nil { logger.Fatalf("error: %s", err) } + logger.Printf("info: allowed modules, %+v", config.Allowed.Modules) + logger.Printf("info: allowed module domains, %+v", config.Allowed.Domains) + logger.Printf("info: blocked modules, %+v", config.Blocked.Modules.Get()) + logger.Printf("info: blocked modules with version constraints, %+v", config.Blocked.Versions.Get()) + results := processor.ProcessFiles(filteredFiles) if report == "checkstyle" { diff --git a/go.mod b/go.mod index 1dd686a..1f17483 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b github.com/mitchellh/go-homedir v1.1.0 github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d - github.com/pkg/errors v0.9.1 golang.org/x/mod v0.4.0 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index e310310..ccbc5e0 100644 --- a/go.sum +++ b/go.sum @@ -6,12 +6,9 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d h1:CdDQnGF8Nq9ocOS/xlSptM1N3BbrA6/kmaep5ggwaIA= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -25,6 +22,5 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbO golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/gomodguard.go b/gomodguard.go index 1646773..3f2cc0d 100644 --- a/gomodguard.go +++ b/gomodguard.go @@ -7,7 +7,6 @@ import ( "go/parser" "go/token" "io/ioutil" - "log" "os" "os/exec" "strings" @@ -24,30 +23,20 @@ const ( ) var ( - blockReasonNotInAllowedList = "import of package `%s` is blocked because the module is not in the allowed modules list." - blockReasonInBlockedList = "import of package `%s` is blocked because the module is in the blocked modules list." + blockReasonNotInAllowedList = "import of package `%s` is blocked because the module is not in the allowed modules list." + blockReasonInBlockedList = "import of package `%s` is blocked because the module is in the blocked modules list." + blockReasonHasLocalReplaceDirective = "import of package `%s` is blocked because the module has a local replace directive." ) // BlockedVersion has a version constraint a reason why the the module version is blocked. type BlockedVersion struct { - Version string `yaml:"version"` - Reason string `yaml:"reason"` - lintedModuleVersion string `yaml:"-"` + Version string `yaml:"version"` + Reason string `yaml:"reason"` } -// Set required values for performing checks. This must be ran before running anything else. -func (r *BlockedVersion) Set(lintedModuleVersion string) { - r.lintedModuleVersion = lintedModuleVersion -} - -// IsAllowed returns true if the blocked module is allowed. You must Set() values first. -func (r *BlockedVersion) IsAllowed() bool { - return !r.isLintedModuleVersionBlocked() -} - -// isLintedModuleVersionBlocked returns true if version constraint specified and the -// linted module version meets the constraint. -func (r *BlockedVersion) isLintedModuleVersionBlocked() bool { +// IsLintedModuleVersionBlocked returns true if a version constraint is specified and the +// linted module version matches the constraint. +func (r *BlockedVersion) IsLintedModuleVersionBlocked(lintedModuleVersion string) bool { if r.Version == "" { return false } @@ -57,26 +46,28 @@ func (r *BlockedVersion) isLintedModuleVersionBlocked() bool { return false } - version, err := semver.NewVersion(strings.TrimLeft(r.lintedModuleVersion, "v")) + version, err := semver.NewVersion(lintedModuleVersion) if err != nil { return false } - return constraint.Check(version) + meet := constraint.Check(version) + + return meet } // Message returns the reason why the module version is blocked. -func (r *BlockedVersion) Message() string { +func (r *BlockedVersion) Message(lintedModuleVersion string) string { msg := "" - // Add version contraint to message - msg += fmt.Sprintf("version `%s` is blocked because it does not meet the version constraint `%s`.", r.lintedModuleVersion, r.Version) + // Add version contraint to message. + msg += fmt.Sprintf("version `%s` is blocked because it does not meet the version constraint `%s`.", lintedModuleVersion, r.Version) if r.Reason == "" { return msg } - // Add reason to message + // Add reason to message. msg += fmt.Sprintf(" %s.", strings.TrimRight(r.Reason, ".")) return msg @@ -84,32 +75,22 @@ func (r *BlockedVersion) Message() string { // BlockedModule has alternative modules to use and a reason why the module is blocked. type BlockedModule struct { - Recommendations []string `yaml:"recommendations"` - Reason string `yaml:"reason"` - currentModuleName string `yaml:"-"` + Recommendations []string `yaml:"recommendations"` + Reason string `yaml:"reason"` } -// Set required values for performing checks. This must be ran before running anything else. -func (r *BlockedModule) Set(currentModuleName string) { - r.currentModuleName = currentModuleName -} - -// IsAllowed returns true if the blocked module is allowed. You must Set() values first. -func (r *BlockedModule) IsAllowed() bool { - // If the current go.mod file being linted is a recommended module of a - // blocked module and it imports that blocked module, do not set as blocked. - // This could mean that the linted module is a wrapper for that blocked module. - return r.isCurrentModuleARecommendation() -} - -// isCurrentModuleARecommendation returns true if the current module is in the Recommendations list. -func (r *BlockedModule) isCurrentModuleARecommendation() bool { +// IsCurrentModuleARecommendation returns true if the current module is in the Recommendations list. +// +// If the current go.mod file being linted is a recommended module of a +// blocked module and it imports that blocked module, do not set as blocked. +// This could mean that the linted module is a wrapper for that blocked module. +func (r *BlockedModule) IsCurrentModuleARecommendation(currentModuleName string) bool { if r == nil { return false } for n := range r.Recommendations { - if strings.TrimSpace(r.currentModuleName) == strings.TrimSpace(r.Recommendations[n]) { + if strings.TrimSpace(currentModuleName) == strings.TrimSpace(r.Recommendations[n]) { return true } } @@ -177,11 +158,10 @@ func (b BlockedVersions) Get() []string { } // GetBlockReason returns a block version if one is set for the provided linted module name. -func (b BlockedVersions) GetBlockReason(lintedModuleName, lintedModuleVersion string) *BlockedVersion { +func (b BlockedVersions) GetBlockReason(lintedModuleName string) *BlockedVersion { for _, blockedModule := range b { for blockedModuleName, blockedVersion := range blockedModule { - if strings.EqualFold(strings.TrimSpace(lintedModuleName), strings.TrimSpace(blockedModuleName)) { - blockedVersion.Set(lintedModuleVersion) + if strings.TrimSpace(lintedModuleName) == strings.TrimSpace(blockedModuleName) { return &blockedVersion } } @@ -208,11 +188,10 @@ func (b BlockedModules) Get() []string { } // GetBlockReason returns a block module if one is set for the provided linted module name. -func (b BlockedModules) GetBlockReason(currentModuleName, lintedModuleName string) *BlockedModule { +func (b BlockedModules) GetBlockReason(lintedModuleName string) *BlockedModule { for _, blockedModule := range b { for blockedModuleName, blockedModule := range blockedModule { - if strings.EqualFold(strings.TrimSpace(lintedModuleName), strings.TrimSpace(blockedModuleName)) { - blockedModule.Set(currentModuleName) + if strings.TrimSpace(lintedModuleName) == strings.TrimSpace(blockedModuleName) { return &blockedModule } } @@ -234,7 +213,7 @@ func (a *Allowed) IsAllowedModule(moduleName string) bool { allowedModules := a.Modules for i := range allowedModules { - if strings.EqualFold(strings.TrimSpace(moduleName), strings.TrimSpace(allowedModules[i])) { + if strings.TrimSpace(moduleName) == strings.TrimSpace(allowedModules[i]) { return true } } @@ -259,8 +238,9 @@ func (a *Allowed) IsAllowedModuleDomain(moduleName string) bool { // Blocked is a list of modules that are // blocked and not to be used. type Blocked struct { - Modules BlockedModules `yaml:"modules"` - Versions BlockedVersions `yaml:"versions"` + Modules BlockedModules `yaml:"modules"` + Versions BlockedVersions `yaml:"versions"` + LocalReplaceDirectives bool `yaml:"local_replace_directives"` } // Configuration of gomodguard allow and block lists. @@ -285,38 +265,31 @@ func (r *Result) String() string { // Processor processes Go files. type Processor struct { - Config Configuration - Logger *log.Logger + Config *Configuration Modfile *modfile.File blockedModulesFromModFile map[string][]string Result []Result } // NewProcessor will create a Processor to lint blocked packages. -func NewProcessor(config Configuration, logger *log.Logger) (*Processor, error) { +func NewProcessor(config *Configuration) (*Processor, error) { goModFileBytes, err := loadGoModFile() if err != nil { return nil, fmt.Errorf(errReadingGoModFile, goModFilename, err) } - mfile, err := modfile.Parse(goModFilename, goModFileBytes, nil) + modFile, err := modfile.Parse(goModFilename, goModFileBytes, nil) if err != nil { return nil, fmt.Errorf(errParsingGoModFile, goModFilename, err) } - logger.Printf("info: allowed modules, %+v", config.Allowed.Modules) - logger.Printf("info: allowed module domains, %+v", config.Allowed.Domains) - logger.Printf("info: blocked modules, %+v", config.Blocked.Modules.Get()) - logger.Printf("info: blocked modules with version constraints, %+v", config.Blocked.Versions.Get()) - p := &Processor{ Config: config, - Logger: logger, - Modfile: mfile, + Modfile: modFile, Result: []Result{}, } - p.SetBlockedModulesFromModFile() + p.SetBlockedModules() return p, nil } @@ -324,19 +297,6 @@ func NewProcessor(config Configuration, logger *log.Logger) (*Processor, error) // ProcessFiles takes a string slice with file names (full paths) // and lints them. func (p *Processor) ProcessFiles(filenames []string) []Result { - pluralModuleMsg := "s" - if len(p.blockedModulesFromModFile) == 1 { - pluralModuleMsg = "" - } - - blockedModules := make([]string, 0, len(p.blockedModulesFromModFile)) - for blockedModuleName := range p.blockedModulesFromModFile { - blockedModules = append(blockedModules, blockedModuleName) - } - - p.Logger.Printf("info: found %d blocked module%s in %s: %+v", - len(p.blockedModulesFromModFile), pluralModuleMsg, goModFilename, blockedModules) - for _, filename := range filenames { data, err := ioutil.ReadFile(filename) if err != nil { @@ -396,16 +356,20 @@ func (p *Processor) addError(fileset *token.FileSet, pos token.Pos, reason strin }) } -// SetBlockedModulesFromModFile determines which modules are blocked by reading -// the go.mod file and comparing the require modules to the allowed modules. -func (p *Processor) SetBlockedModulesFromModFile() { +// SetBlockedModules determines and sets which modules are blocked by reading +// the go.mod file of the module that is being linted. +// +// It works by iterating over the dependant modules specified in the require +// directive, checking if the module domain or full name is in the allowed list. +func (p *Processor) SetBlockedModules() { //nolint:gocognit blockedModules := make(map[string][]string, len(p.Modfile.Require)) currentModuleName := p.Modfile.Module.Mod.Path lintedModules := p.Modfile.Require + replacedModules := p.Modfile.Replace for i := range lintedModules { if lintedModules[i].Indirect { - continue + continue // Do not lint indirect modules. } lintedModuleName := strings.TrimSpace(lintedModules[i].Mod.Path) @@ -424,20 +388,35 @@ func (p *Processor) SetBlockedModulesFromModFile() { isAllowed = false } - blockModuleReason := p.Config.Blocked.Modules.GetBlockReason(currentModuleName, lintedModuleName) - blockVersionReason := p.Config.Blocked.Versions.GetBlockReason(lintedModuleName, lintedModuleVersion) + blockModuleReason := p.Config.Blocked.Modules.GetBlockReason(lintedModuleName) + blockVersionReason := p.Config.Blocked.Versions.GetBlockReason(lintedModuleName) if !isAllowed && blockModuleReason == nil && blockVersionReason == nil { blockedModules[lintedModuleName] = append(blockedModules[lintedModuleName], blockReasonNotInAllowedList) continue } - if blockModuleReason != nil && !blockModuleReason.IsAllowed() { + if blockModuleReason != nil && !blockModuleReason.IsCurrentModuleARecommendation(currentModuleName) { blockedModules[lintedModuleName] = append(blockedModules[lintedModuleName], fmt.Sprintf("%s %s", blockReasonInBlockedList, blockModuleReason.Message())) } - if blockVersionReason != nil && !blockVersionReason.IsAllowed() { - blockedModules[lintedModuleName] = append(blockedModules[lintedModuleName], fmt.Sprintf("%s %s", blockReasonInBlockedList, blockVersionReason.Message())) + if blockVersionReason != nil && blockVersionReason.IsLintedModuleVersionBlocked(lintedModuleVersion) { + blockedModules[lintedModuleName] = append(blockedModules[lintedModuleName], fmt.Sprintf("%s %s", blockReasonInBlockedList, blockVersionReason.Message(lintedModuleVersion))) + } + } + + // Replace directives with local paths are blocked. + // Filesystem paths found in "replace" directives are represented by a path with an empty version. + // https://github.com/golang/mod/blob/bc388b264a244501debfb9caea700c6dcaff10e2/module/module.go#L122-L124 + if p.Config.Blocked.LocalReplaceDirectives { + for i := range replacedModules { + replacedModuleOldName := strings.TrimSpace(replacedModules[i].Old.Path) + replacedModuleNewName := strings.TrimSpace(replacedModules[i].New.Path) + replacedModuleNewVersion := strings.TrimSpace(replacedModules[i].New.Version) + + if replacedModuleNewName != "" && replacedModuleNewVersion == "" { + blockedModules[replacedModuleOldName] = append(blockedModules[replacedModuleOldName], blockReasonHasLocalReplaceDirective) + } } } diff --git a/gomodguard_test.go b/gomodguard_test.go index 9745c62..7c45839 100644 --- a/gomodguard_test.go +++ b/gomodguard_test.go @@ -2,7 +2,7 @@ package gomodguard_test import ( - "log" + "fmt" "os" "reflect" "strings" @@ -11,6 +11,33 @@ import ( "github.com/ryancurrah/gomodguard" ) +var ( + config *gomodguard.Configuration + cwd string +) + +func TestMain(m *testing.M) { + err := os.Chdir("_example") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + cwd, err = os.Getwd() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + config, err = gomodguard.GetConfig(".gomodguard.yaml") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + os.Exit(m.Run()) +} + func TestBlockedModuleIsAllowed(t *testing.T) { var tests = []struct { testName string @@ -34,8 +61,7 @@ func TestBlockedModuleIsAllowed(t *testing.T) { for _, tt := range tests { t.Run(tt.testName, func(t *testing.T) { - tt.blockedModule.Set(tt.currentModuleName) - isAllowed := tt.blockedModule.IsAllowed() + isAllowed := tt.blockedModule.IsCurrentModuleARecommendation(tt.currentModuleName) if isAllowed != tt.wantIsAllowed { t.Errorf("got '%v' want '%v'", isAllowed, tt.wantIsAllowed) } @@ -76,7 +102,6 @@ func TestBlockedModuleMessage(t *testing.T) { for _, tt := range tests { t.Run(tt.testName, func(t *testing.T) { - tt.blockedModule.Set(tt.currentModuleName) message := tt.blockedModule.Message() if !strings.EqualFold(message, tt.wantMessage) { t.Errorf("got '%s' want '%s'", message, tt.wantMessage) @@ -162,8 +187,7 @@ func TestBlockedVersionMessage(t *testing.T) { for _, tt := range tests { t.Run(tt.testName, func(t *testing.T) { - tt.blockedVersion.Set(tt.lintedModuleVersion) - message := tt.blockedVersion.Message() + message := tt.blockedVersion.Message(tt.lintedModuleVersion) if !strings.EqualFold(message, tt.wantMessage) { t.Errorf("got '%s' want '%s'", message, tt.wantMessage) } @@ -197,10 +221,9 @@ func TestBlockedModulesGetBlockedModule(t *testing.T) { for _, tt := range tests { t.Run(tt.testName, func(t *testing.T) { - blockedModule := tt.blockedModules.GetBlockReason(tt.currentModuleName, tt.lintedModuleName) - blockedModule.Set(tt.currentModuleName) - if blockedModule.IsAllowed() != tt.wantIsAllowed { - t.Errorf("got '%+v' want '%+v'", blockedModule.IsAllowed(), tt.wantIsAllowed) + blockedModule := tt.blockedModules.GetBlockReason(tt.lintedModuleName) + if blockedModule.IsCurrentModuleARecommendation(tt.currentModuleName) != tt.wantIsAllowed { + t.Errorf("got '%+v' want '%+v'", blockedModule.IsCurrentModuleARecommendation(tt.currentModuleName), tt.wantIsAllowed) } }) } @@ -292,24 +315,14 @@ func TestResultString(t *testing.T) { } func TestProcessorNewProcessor(t *testing.T) { - config, logger, _, err := setup() - if err != nil { - t.Error(err) - } - - _, err = gomodguard.NewProcessor(*config, logger) + _, err := gomodguard.NewProcessor(config) if err != nil { t.Error(err) } } func TestProcessorProcessFiles(t *testing.T) { - config, logger, cwd, err := setup() - if err != nil { - t.Error(err) - } - - processor, err := gomodguard.NewProcessor(*config, logger) + processor, err := gomodguard.NewProcessor(config) if err != nil { t.Error(err) } @@ -322,48 +335,43 @@ func TestProcessorProcessFiles(t *testing.T) { wantReason string }{ { - "process module blocked because of recommendation", - gomodguard.Processor{Config: *config, Logger: logger, Modfile: processor.Modfile, Result: []gomodguard.Result{}}, - "cmd.go:14:1 import of package `github.com/phayes/checkstyle` is blocked because the module is in the blocked modules list. `github.com/someother/module` is a recommended module. testing if module is blocked with recommendation.", + "module blocked because of recommendation", + gomodguard.Processor{Config: config, Modfile: processor.Modfile, Result: []gomodguard.Result{}}, + "blocked_example.go:9:1 import of package `github.com/uudashr/go-module` is blocked because the module is in the blocked modules list. `golang.org/x/mod` is a recommended module. `mod` is the official go.mod parser library.", + }, + { + "module blocked because of version constraint", + gomodguard.Processor{Config: config, Modfile: processor.Modfile, Result: []gomodguard.Result{}}, + "blocked_example.go:7:1 import of package `github.com/mitchellh/go-homedir` is blocked because the module is in the blocked modules list. version `v1.1.0` is blocked because it does not meet the version constraint `<= 1.1.0`. testing if blocked version constraint works.", }, { - "process module blocked because of version constraint", - gomodguard.Processor{Config: *config, Logger: logger, Modfile: processor.Modfile, Result: []gomodguard.Result{}}, - "cmd.go:13:1 import of package `github.com/mitchellh/go-homedir` is blocked because the module is in the blocked modules list. version `v1.1.0` is blocked because it does not meet the version constraint `<= 1.1.0`. testing if blocked version constraint works.", + "module blocked because of local replace directive", + gomodguard.Processor{Config: config, Modfile: processor.Modfile, Result: []gomodguard.Result{}}, + "blocked_example.go:8:1 import of package `github.com/ryancurrah/gomodguard` is blocked because the module has a local replace directive.", }, } for _, tt := range tests { t.Run(tt.testName, func(t *testing.T) { - tt.processor.SetBlockedModulesFromModFile() + tt.processor.SetBlockedModules() results := tt.processor.ProcessFiles(filteredFiles) if len(results) == 0 { t.Fatal("result should be greater than zero") } foundWantReason := false + allReasons := make([]string, 0, len(results)) for _, result := range results { + allReasons = append(allReasons, result.String()) + if strings.EqualFold(result.String(), tt.wantReason) { foundWantReason = true } } if !foundWantReason { - t.Errorf("got '%+v' want '%s'", results, tt.wantReason) + t.Errorf("got '%+v' want '%s'", allReasons, tt.wantReason) } }) } } - -func setup() (*gomodguard.Configuration, *log.Logger, string, error) { - config, err := gomodguard.GetConfig(".gomodguard.yaml") - if err != nil { - return nil, nil, "", err - } - - logger := log.New(os.Stderr, "", 0) - - cwd, _ := os.Getwd() - - return config, logger, cwd, nil -}