aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/ashanbrown
diff options
context:
space:
mode:
authorTaras Madan <tarasmadan@google.com>2023-02-22 22:16:50 +0100
committerTaras Madan <tarasmadan@google.com>2023-02-24 12:47:23 +0100
commit4165372ec8fd142475a4e35fd0cf4f8042132208 (patch)
tree21cd62211b4dd80bee469054c5b65db77342333c /vendor/github.com/ashanbrown
parent2b3ed821a493b8936c8bacfa6f8b4f1c90a00855 (diff)
dependencies: update
set go min requirements to 1.19 update dependencies update vendor
Diffstat (limited to 'vendor/github.com/ashanbrown')
-rw-r--r--vendor/github.com/ashanbrown/forbidigo/forbidigo/forbidigo.go190
-rw-r--r--vendor/github.com/ashanbrown/forbidigo/forbidigo/patterns.go106
2 files changed, 270 insertions, 26 deletions
diff --git a/vendor/github.com/ashanbrown/forbidigo/forbidigo/forbidigo.go b/vendor/github.com/ashanbrown/forbidigo/forbidigo/forbidigo.go
index 17740faa7..9b3765405 100644
--- a/vendor/github.com/ashanbrown/forbidigo/forbidigo/forbidigo.go
+++ b/vendor/github.com/ashanbrown/forbidigo/forbidigo/forbidigo.go
@@ -7,6 +7,7 @@ import (
"go/ast"
"go/printer"
"go/token"
+ "go/types"
"log"
"regexp"
"strings"
@@ -16,6 +17,7 @@ import (
type Issue interface {
Details() string
+ Pos() token.Pos
Position() token.Position
String() string
}
@@ -23,6 +25,7 @@ type Issue interface {
type UsedIssue struct {
identifier string
pattern string
+ pos token.Pos
position token.Position
customMsg string
}
@@ -39,6 +42,10 @@ func (a UsedIssue) Position() token.Position {
return a.position
}
+func (a UsedIssue) Pos() token.Pos {
+ return a.pos
+}
+
func (a UsedIssue) String() string { return toString(a) }
func toString(i UsedIssue) string {
@@ -91,19 +98,43 @@ type visitor struct {
linter *Linter
comments []*ast.CommentGroup
- fset *token.FileSet
- issues []Issue
+ runConfig RunConfig
+ issues []Issue
}
+// Deprecated: Run was the original entrypoint before RunWithConfig was introduced to support
+// additional match patterns that need additional information.
func (l *Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) {
- var issues []Issue //nolint:prealloc // we don't know how many there will be
+ return l.RunWithConfig(RunConfig{Fset: fset}, nodes...)
+}
+
+// RunConfig provides information that the linter needs for different kinds
+// of match patterns. Ideally, all fields should get set. More fields may get
+// added in the future as needed.
+type RunConfig struct {
+ // FSet is required.
+ Fset *token.FileSet
+
+ // TypesInfo is needed for expanding source code expressions.
+ // Nil disables that step, i.e. patterns match the literal source code.
+ TypesInfo *types.Info
+
+ // DebugLog is used to print debug messages. May be nil.
+ DebugLog func(format string, args ...interface{})
+}
+
+func (l *Linter) RunWithConfig(config RunConfig, nodes ...ast.Node) ([]Issue, error) {
+ if config.DebugLog == nil {
+ config.DebugLog = func(format string, args ...interface{}) {}
+ }
+ var issues []Issue
for _, node := range nodes {
var comments []*ast.CommentGroup
isTestFile := false
isWholeFileExample := false
if file, ok := node.(*ast.File); ok {
comments = file.Comments
- fileName := fset.Position(file.Pos()).Filename
+ fileName := config.Fset.Position(file.Pos()).Filename
isTestFile = strings.HasSuffix(fileName, "_test.go")
// From https://blog.golang.org/examples, a "whole file example" is:
@@ -139,7 +170,7 @@ func (l *Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) {
cfg: l.cfg,
isTestFile: isTestFile,
linter: l,
- fset: fset,
+ runConfig: config,
comments: comments,
}
ast.Walk(&visitor, node)
@@ -157,40 +188,169 @@ func (v *visitor) Visit(node ast.Node) ast.Visitor {
return nil
}
return v
+ // The following two are handled below.
case *ast.SelectorExpr:
case *ast.Ident:
+ // Everything else isn't.
default:
return v
}
+
+ // The text as it appears in the source is always used because issues
+ // use that. It's used for matching unless usage of type information
+ // is enabled.
+ srcText := v.textFor(node)
+ matchTexts, pkgText := v.expandMatchText(node, srcText)
+ v.runConfig.DebugLog("%s: match %v, package %q", v.runConfig.Fset.Position(node.Pos()), matchTexts, pkgText)
for _, p := range v.linter.patterns {
- if p.pattern.MatchString(v.textFor(node)) && !v.permit(node) {
+ if p.matches(matchTexts) &&
+ (p.Package == "" || p.pkgRe.MatchString(pkgText)) &&
+ !v.permit(node) {
v.issues = append(v.issues, UsedIssue{
- identifier: v.textFor(node),
- pattern: p.pattern.String(),
- position: v.fset.Position(node.Pos()),
- customMsg: p.msg,
+ identifier: srcText, // Always report the expression as it appears in the source code.
+ pattern: p.re.String(),
+ pos: node.Pos(),
+ position: v.runConfig.Fset.Position(node.Pos()),
+ customMsg: p.Msg,
})
}
}
return nil
}
+// textFor returns the expression as it appears in the source code (for
+// example, <importname>.<function name>).
func (v *visitor) textFor(node ast.Node) string {
buf := new(bytes.Buffer)
- if err := printer.Fprint(buf, v.fset, node); err != nil {
- log.Fatalf("ERROR: unable to print node at %s: %s", v.fset.Position(node.Pos()), err)
+ if err := printer.Fprint(buf, v.runConfig.Fset, node); err != nil {
+ log.Fatalf("ERROR: unable to print node at %s: %s", v.runConfig.Fset.Position(node.Pos()), err)
}
return buf.String()
}
+// expandMatchText expands the selector in a selector expression to the full package
+// name and (for variables) the type:
+//
+// - example.com/some/pkg.Function
+// - example.com/some/pkg.CustomType.Method
+//
+// It updates the text to match against and fills the package string if possible,
+// otherwise it just returns.
+func (v *visitor) expandMatchText(node ast.Node, srcText string) (matchTexts []string, pkgText string) {
+ // The text to match against is the literal source code if we cannot
+ // come up with something different.
+ matchText := srcText
+
+ if v.runConfig.TypesInfo == nil {
+ return []string{matchText}, pkgText
+ }
+
+ location := v.runConfig.Fset.Position(node.Pos())
+
+ switch node := node.(type) {
+ case *ast.Ident:
+ object, ok := v.runConfig.TypesInfo.Uses[node]
+ if !ok {
+ // No information about the identifier. Should
+ // not happen, but perhaps there were compile
+ // errors?
+ v.runConfig.DebugLog("%s: unknown identifier %q", location, srcText)
+ return []string{matchText}, pkgText
+ }
+ if pkg := object.Pkg(); pkg != nil {
+ pkgText = pkg.Path()
+ v.runConfig.DebugLog("%s: identifier: %q -> %q in package %q", location, srcText, matchText, pkgText)
+ // match either with or without package name
+ return []string{pkg.Name() + "." + srcText, srcText}, pkgText
+ } else {
+ v.runConfig.DebugLog("%s: identifier: %q -> %q without package", location, srcText, matchText)
+ }
+ return []string{matchText}, pkgText
+ case *ast.SelectorExpr:
+ selector := node.X
+ field := node.Sel.Name
+
+ // If we are lucky, the entire selector expression has a known
+ // type. We don't care about the value.
+ selectorText := v.textFor(node)
+ if typeAndValue, ok := v.runConfig.TypesInfo.Types[selector]; ok {
+ m, p, ok := pkgFromType(typeAndValue.Type)
+ if !ok {
+ v.runConfig.DebugLog("%s: selector %q with supported type %T", location, selectorText, typeAndValue.Type)
+ }
+ matchText = m + "." + field
+ pkgText = p
+ v.runConfig.DebugLog("%s: selector %q with supported type %q: %q -> %q, package %q", location, selectorText, typeAndValue.Type.String(), srcText, matchText, pkgText)
+ return []string{matchText}, pkgText
+ }
+ // Some expressions need special treatment.
+ switch selector := selector.(type) {
+ case *ast.Ident:
+ object, ok := v.runConfig.TypesInfo.Uses[selector]
+ if !ok {
+ // No information about the identifier. Should
+ // not happen, but perhaps there were compile
+ // errors?
+ v.runConfig.DebugLog("%s: unknown selector identifier %q", location, selectorText)
+ return []string{matchText}, pkgText
+ }
+ switch object := object.(type) {
+ case *types.PkgName:
+ pkgText = object.Imported().Path()
+ matchText = object.Imported().Name() + "." + field
+ v.runConfig.DebugLog("%s: selector %q is package: %q -> %q, package %q", location, selectorText, srcText, matchText, pkgText)
+ return []string{matchText}, pkgText
+ case *types.Var:
+ m, p, ok := pkgFromType(object.Type())
+ if !ok {
+ v.runConfig.DebugLog("%s: selector %q is variable with unsupported type %T", location, selectorText, object.Type())
+ }
+ matchText = m + "." + field
+ pkgText = p
+ v.runConfig.DebugLog("%s: selector %q is variable of type %q: %q -> %q, package %q", location, selectorText, object.Type().String(), srcText, matchText, pkgText)
+ default:
+ // Something else?
+ v.runConfig.DebugLog("%s: selector %q is identifier with unsupported type %T", location, selectorText, object)
+ }
+ default:
+ v.runConfig.DebugLog("%s: selector %q of unsupported type %T", location, selectorText, selector)
+ }
+ return []string{matchText}, pkgText
+ default:
+ v.runConfig.DebugLog("%s: unsupported type %T", location, node)
+ return []string{matchText}, pkgText
+ }
+}
+
+// pkgFromType tries to determine `<package name>.<type name>` and the full
+// package path. This only needs to work for types of a selector in a selector
+// expression.
+func pkgFromType(t types.Type) (typeStr, pkgStr string, ok bool) {
+ if ptr, ok := t.(*types.Pointer); ok {
+ t = ptr.Elem()
+ }
+
+ switch t := t.(type) {
+ case *types.Named:
+ obj := t.Obj()
+ pkg := obj.Pkg()
+ if pkg == nil {
+ return "", "", false
+ }
+ return pkg.Name() + "." + obj.Name(), pkg.Path(), true
+ default:
+ return "", "", false
+ }
+}
+
func (v *visitor) permit(node ast.Node) bool {
if v.cfg.IgnorePermitDirectives {
return false
}
- nodePos := v.fset.Position(node.Pos())
- var nolint = regexp.MustCompile(fmt.Sprintf(`^//\s?permit:%s\b`, regexp.QuoteMeta(v.textFor(node))))
+ nodePos := v.runConfig.Fset.Position(node.Pos())
+ nolint := regexp.MustCompile(fmt.Sprintf(`^//\s?permit:%s\b`, regexp.QuoteMeta(v.textFor(node))))
for _, c := range v.comments {
- commentPos := v.fset.Position(c.Pos())
+ commentPos := v.runConfig.Fset.Position(c.Pos())
if commentPos.Line == nodePos.Line && len(c.List) > 0 && nolint.MatchString(c.List[0].Text) {
return true
}
diff --git a/vendor/github.com/ashanbrown/forbidigo/forbidigo/patterns.go b/vendor/github.com/ashanbrown/forbidigo/forbidigo/patterns.go
index c23648822..2692dcd24 100644
--- a/vendor/github.com/ashanbrown/forbidigo/forbidigo/patterns.go
+++ b/vendor/github.com/ashanbrown/forbidigo/forbidigo/patterns.go
@@ -5,39 +5,123 @@ import (
"regexp"
"regexp/syntax"
"strings"
+
+ "gopkg.in/yaml.v2"
)
+// pattern matches code that is not supposed to be used.
type pattern struct {
- pattern *regexp.Regexp
- msg string
+ re, pkgRe *regexp.Regexp
+
+ // Pattern is the regular expression string that is used for matching.
+ // It gets matched against the literal source code text or the expanded
+ // text, depending on the mode in which the analyzer runs.
+ Pattern string `yaml:"p"`
+
+ // Package is a regular expression for the full package path of
+ // an imported item. Ignored unless the analyzer is configured to
+ // determine that information.
+ Package string `yaml:"pkg,omitempty"`
+
+ // Msg gets printed in addition to the normal message if a match is
+ // found.
+ Msg string `yaml:"msg,omitempty"`
+}
+
+// A yamlPattern pattern in a YAML string may be represented either by a string
+// (the traditional regular expression syntax) or a struct (for more complex
+// patterns).
+type yamlPattern pattern
+
+func (p *yamlPattern) UnmarshalYAML(unmarshal func(interface{}) error) error {
+ // Try struct first. It's unlikely that a regular expression string
+ // is valid YAML for a struct.
+ var ptrn pattern
+ if err := unmarshal(&ptrn); err != nil {
+ errStr := err.Error()
+ // Didn't work, try plain string.
+ var ptrn string
+ if err := unmarshal(&ptrn); err != nil {
+ return fmt.Errorf("pattern is neither a regular expression string (%s) nor a Pattern struct (%s)", err.Error(), errStr)
+ }
+ p.Pattern = ptrn
+ } else {
+ *p = yamlPattern(ptrn)
+ }
+ return ((*pattern)(p)).validate()
}
+var _ yaml.Unmarshaler = &yamlPattern{}
+
+// parse accepts a regular expression or, if the string starts with { or contains a line break, a
+// JSON or YAML representation of a Pattern.
func parse(ptrn string) (*pattern, error) {
- ptrnRe, err := regexp.Compile(ptrn)
+ pattern := &pattern{}
+
+ if strings.HasPrefix(strings.TrimSpace(ptrn), "{") ||
+ strings.Contains(ptrn, "\n") {
+ // Embedded JSON or YAML. We can decode both with the YAML decoder.
+ if err := yaml.UnmarshalStrict([]byte(ptrn), pattern); err != nil {
+ return nil, fmt.Errorf("parsing as JSON or YAML failed: %v", err)
+ }
+ } else {
+ pattern.Pattern = ptrn
+ }
+
+ if err := pattern.validate(); err != nil {
+ return nil, err
+ }
+ return pattern, nil
+}
+
+func (p *pattern) validate() error {
+ ptrnRe, err := regexp.Compile(p.Pattern)
if err != nil {
- return nil, fmt.Errorf("unable to compile pattern `%s`: %s", ptrn, err)
+ return fmt.Errorf("unable to compile source code pattern `%s`: %s", p.Pattern, err)
}
- re, err := syntax.Parse(ptrn, syntax.Perl)
+ re, err := syntax.Parse(p.Pattern, syntax.Perl)
if err != nil {
- return nil, fmt.Errorf("unable to parse pattern `%s`: %s", ptrn, err)
+ return fmt.Errorf("unable to parse source code pattern `%s`: %s", p.Pattern, err)
}
msg := extractComment(re)
- return &pattern{pattern: ptrnRe, msg: msg}, nil
+ if msg != "" {
+ p.Msg = msg
+ }
+ p.re = ptrnRe
+
+ if p.Package != "" {
+ pkgRe, err := regexp.Compile(p.Package)
+ if err != nil {
+ return fmt.Errorf("unable to compile package pattern `%s`: %s", p.Package, err)
+ }
+ p.pkgRe = pkgRe
+ }
+
+ return nil
+}
+
+func (p *pattern) matches(matchTexts []string) bool {
+ for _, text := range matchTexts {
+ if p.re.MatchString(text) {
+ return true
+ }
+ }
+ return false
}
// Traverse the leaf submatches in the regex tree and extract a comment, if any
// is present.
func extractComment(re *syntax.Regexp) string {
for _, sub := range re.Sub {
+ subStr := sub.String()
+ if strings.HasPrefix(subStr, "#") {
+ return strings.TrimSpace(strings.TrimPrefix(sub.String(), "#"))
+ }
if len(sub.Sub) > 0 {
if comment := extractComment(sub); comment != "" {
return comment
}
}
- subStr := sub.String()
- if strings.HasPrefix(subStr, "#") {
- return strings.TrimSpace(strings.TrimPrefix(subStr, "#"))
- }
}
return ""
}