github.com/nozzle/golangci-lint@v1.49.0-nz3/pkg/golinters/depguard.go (about) 1 package golinters 2 3 import ( 4 "fmt" 5 "path/filepath" 6 "regexp" 7 "strings" 8 "sync" 9 10 "github.com/OpenPeeDeeP/depguard" 11 "golang.org/x/tools/go/analysis" 12 "golang.org/x/tools/go/loader" //nolint:staticcheck // require changes in github.com/OpenPeeDeeP/depguard 13 14 "github.com/golangci/golangci-lint/pkg/config" 15 "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" 16 "github.com/golangci/golangci-lint/pkg/lint/linter" 17 "github.com/golangci/golangci-lint/pkg/result" 18 ) 19 20 const depguardName = "depguard" 21 22 func NewDepguard(settings *config.DepGuardSettings) *goanalysis.Linter { 23 var mu sync.Mutex 24 var resIssues []goanalysis.Issue 25 26 analyzer := &analysis.Analyzer{ 27 Name: depguardName, 28 Doc: goanalysis.TheOnlyanalyzerDoc, 29 Run: goanalysis.DummyRun, 30 } 31 32 return goanalysis.NewLinter( 33 depguardName, 34 "Go linter that checks if package imports are in a list of acceptable packages", 35 []*analysis.Analyzer{analyzer}, 36 nil, 37 ).WithContextSetter(func(lintCtx *linter.Context) { 38 dg, err := newDepGuard(settings) 39 40 analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { 41 if err != nil { 42 return nil, err 43 } 44 45 issues, errRun := dg.run(pass) 46 if errRun != nil { 47 return nil, errRun 48 } 49 50 mu.Lock() 51 resIssues = append(resIssues, issues...) 52 mu.Unlock() 53 54 return nil, nil 55 } 56 }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { 57 return resIssues 58 }).WithLoadMode(goanalysis.LoadModeSyntax) 59 } 60 61 type depGuard struct { 62 loadConfig *loader.Config 63 guardians []*guardian 64 } 65 66 func newDepGuard(settings *config.DepGuardSettings) (*depGuard, error) { 67 ps, err := newGuardian(settings) 68 if err != nil { 69 return nil, err 70 } 71 72 d := &depGuard{ 73 loadConfig: &loader.Config{ 74 Cwd: "", // fallbacked to os.Getcwd 75 Build: nil, // fallbacked to build.Default 76 }, 77 guardians: []*guardian{ps}, 78 } 79 80 for _, additional := range settings.AdditionalGuards { 81 add := additional 82 ps, err = newGuardian(&add) 83 if err != nil { 84 return nil, err 85 } 86 87 d.guardians = append(d.guardians, ps) 88 } 89 90 return d, nil 91 } 92 93 func (d depGuard) run(pass *analysis.Pass) ([]goanalysis.Issue, error) { 94 prog := goanalysis.MakeFakeLoaderProgram(pass) 95 96 var resIssues []goanalysis.Issue 97 for _, g := range d.guardians { 98 issues, errRun := g.run(d.loadConfig, prog, pass) 99 if errRun != nil { 100 return nil, errRun 101 } 102 103 resIssues = append(resIssues, issues...) 104 } 105 106 return resIssues, nil 107 } 108 109 var separatorToReplace = regexp.QuoteMeta(string(filepath.Separator)) 110 111 // normalizePathInRegex normalizes path in regular expressions. 112 // noop on Unix. 113 // This replacing should be safe because "/" are disallowed in Windows 114 // https://docs.microsoft.com/windows/win32/fileio/naming-a-file 115 func normalizePathInRegex(path string) string { 116 return strings.ReplaceAll(path, "/", separatorToReplace) 117 } 118 119 type guardian struct { 120 *depguard.Depguard 121 pkgsWithErrorMessage map[string]string 122 } 123 124 func newGuardian(settings *config.DepGuardSettings) (*guardian, error) { 125 var ignoreFileRules []string 126 for _, rule := range settings.IgnoreFileRules { 127 ignoreFileRules = append(ignoreFileRules, normalizePathInRegex(rule)) 128 } 129 130 dg := &depguard.Depguard{ 131 Packages: settings.Packages, 132 IncludeGoRoot: settings.IncludeGoRoot, 133 IgnoreFileRules: ignoreFileRules, 134 } 135 136 var err error 137 dg.ListType, err = getDepGuardListType(settings.ListType) 138 if err != nil { 139 return nil, err 140 } 141 142 // if the list type was a denylist the packages with error messages should be included in the denylist package list 143 if dg.ListType == depguard.LTBlacklist { 144 noMessagePackages := make(map[string]bool) 145 for _, pkg := range dg.Packages { 146 noMessagePackages[pkg] = true 147 } 148 149 for pkg := range settings.PackagesWithErrorMessage { 150 if _, ok := noMessagePackages[pkg]; !ok { 151 dg.Packages = append(dg.Packages, pkg) 152 } 153 } 154 } 155 156 return &guardian{ 157 Depguard: dg, 158 pkgsWithErrorMessage: settings.PackagesWithErrorMessage, 159 }, nil 160 } 161 162 func (g guardian) run(loadConfig *loader.Config, prog *loader.Program, pass *analysis.Pass) ([]goanalysis.Issue, error) { 163 issues, err := g.Run(loadConfig, prog) 164 if err != nil { 165 return nil, err 166 } 167 168 res := make([]goanalysis.Issue, 0, len(issues)) 169 170 for _, issue := range issues { 171 res = append(res, 172 goanalysis.NewIssue(&result.Issue{ 173 Pos: issue.Position, 174 Text: g.createMsg(issue.PackageName), 175 FromLinter: depguardName, 176 }, pass), 177 ) 178 } 179 180 return res, nil 181 } 182 183 func (g guardian) createMsg(pkgName string) string { 184 msgSuffix := "is in the denylist" 185 if g.ListType == depguard.LTWhitelist { 186 msgSuffix = "is not in the allowlist" 187 } 188 189 var userSuppliedMsgSuffix string 190 if g.pkgsWithErrorMessage != nil { 191 userSuppliedMsgSuffix = g.pkgsWithErrorMessage[pkgName] 192 if userSuppliedMsgSuffix != "" { 193 userSuppliedMsgSuffix = ": " + userSuppliedMsgSuffix 194 } 195 } 196 197 return fmt.Sprintf("%s %s%s", formatCode(pkgName, nil), msgSuffix, userSuppliedMsgSuffix) 198 } 199 200 func getDepGuardListType(listType string) (depguard.ListType, error) { 201 if listType == "" { 202 return depguard.LTBlacklist, nil 203 } 204 205 listT, found := depguard.StringToListType[strings.ToLower(listType)] 206 if !found { 207 return depguard.LTBlacklist, fmt.Errorf("unsure what list type %s is", listType) 208 } 209 210 return listT, nil 211 }