Skip to content

Commit

Permalink
fix: rework linter, so it receives a list of regular expressions
Browse files Browse the repository at this point in the history
Previously there was overlapping with regular expressions, as comma is
used in quantificators.

BREAKING CHANGE:
Patterns now passed as a number of `i` flags for include and `e` flags
for exclude patterns.
  • Loading branch information
xobotyi committed Apr 10, 2022
1 parent c73fb34 commit bee026a
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 51 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.idea
vendor
vendor
*.exe
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ go get -u github.com/GaijinEntertainment/go-exhaustruct/cmd/exhaustruct
exhaustruct [-flag] [package]
Flags:
-include string
Comma separated list of regular expressions to match struct packages and names
-exclude string
Comma separated list of regular expressions to exclude struct packages and names
-i value
Regular expression to match struct packages and names, can receive multiple flags
-e value
Regular expression to exclude struct packages and names, can receive multiple flags
```

### Example
Expand Down
3 changes: 2 additions & 1 deletion cmd/exhaustruct/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ import (

func main() {
flag.Bool("unsafeptr", false, "")
singlechecker.Main(analyzer.Analyzer)

singlechecker.Main(analyzer.MustNewAnalyzer([]string{}, []string{}))
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/GaijinEntertainment/go-exhaustruct

go 1.17
go 1.18

require golang.org/x/tools v0.1.10

Expand Down
115 changes: 77 additions & 38 deletions pkg/analyzer/analyzer.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package analyzer

import (
"errors"
"flag"
"fmt"
"go/ast"
"go/types"
"regexp"
Expand All @@ -12,49 +14,65 @@ import (
"golang.org/x/tools/go/ast/inspector"
)

//nolint:gochecknoglobals
var Analyzer = &analysis.Analyzer{
Name: "exhaustruct",
Doc: "Checks if all structure fields are initialized",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
}

//nolint:gochecknoglobals
var (
IncludePatternsString string
ExcludePatternsString string
ErrEmptyPattern = errors.New("pattern can't be empty")
)

func newFlagSet() flag.FlagSet {
type analyzer struct {
include PatternsList
exclude PatternsList
}

// MustNewAnalyzer returns a go/analysis-compatible analyzer.
// -i arguments adds include patterns
// -e arguments adds exclude patterns
func MustNewAnalyzer(include []string, exclude []string) *analysis.Analyzer {
a := analyzer{
include: mustNewPatternsList(include),
exclude: mustNewPatternsList(exclude),
}

return &analysis.Analyzer{
Name: "exhaustruct",
Doc: "Checks if all structure fields are initialized",
Run: a.run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: a.newFlagSet(),
}
}

func (a *analyzer) newFlagSet() flag.FlagSet {
fs := flag.NewFlagSet("exhaustruct flags", flag.PanicOnError)

fs.StringVar(&IncludePatternsString, "include", "", "Comma separated list of regular expressions to match struct packages and names") //nolint:lll
fs.StringVar(&ExcludePatternsString, "exclude", "", "Comma separated list of regular expressions to exclude struct packages and names") //nolint:lll
fs.Var(
&reListVar{values: &a.include},
"i",
"Regular expression to match struct packages and names, can receive multiple flags",
)
fs.Var(
&reListVar{values: &a.exclude},
"e",
"Regular expression to exclude struct packages and names, can receive multiple flags",
)

return *fs
}

func run(pass *analysis.Pass) (interface{}, error) {
include := mustNewPatternsList(IncludePatternsString)
exclude := mustNewPatternsList(ExcludePatternsString)

//nolint:forcetypeassert
insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
func (a *analyzer) run(pass *analysis.Pass) (interface{}, error) {
insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) //nolint:forcetypeassert

nodeTypes := []ast.Node{
(*ast.CompositeLit)(nil),
(*ast.ReturnStmt)(nil),
}

insp.Preorder(nodeTypes, newVisitor(pass, include, exclude))
insp.Preorder(nodeTypes, a.newVisitor(pass))

return nil, nil //nolint:nilnil
}

//nolint:gocognit,funlen,cyclop
func newVisitor(pass *analysis.Pass, include PatternsList, exclude PatternsList) func(node ast.Node) {
//nolint:funlen,cyclop
func (a *analyzer) newVisitor(pass *analysis.Pass) func(node ast.Node) {
var ret *ast.ReturnStmt

return func(node ast.Node) {
Expand Down Expand Up @@ -87,16 +105,14 @@ func newVisitor(pass *analysis.Pass, include PatternsList, exclude PatternsList)
return
}

if len(exclude) > 0 {
if exclude.MatchesAny(typ.String()) {
if len(a.include) > 0 {
if !a.include.MatchesAny(typ.String()) {
return
}
}

if len(include) > 0 {
if !include.MatchesAny(typ.String()) {
return
}
if a.exclude.MatchesAny(typ.String()) {
return
}

if len(lit.Elts) == 0 && ret != nil {
Expand Down Expand Up @@ -233,13 +249,17 @@ func (l PatternsList) MatchesAny(str string) bool {
return false
}

// mustNewPatternsList parses comma separated regexp string to a slice of
// compiled regular expressions.
func mustNewPatternsList(in string) (list PatternsList) {
for _, chunk := range strings.FieldsFunc(in, patternsSlitFn) {
re, err := regexp.Compile(chunk)
// mustNewPatternsList parses slice of strings to a slice of compiled regular
// expressions.
func mustNewPatternsList(in []string) (list PatternsList) {
for _, reStr := range in {
if reStr == "" {
panic(ErrEmptyPattern)
}

re, err := regexp.Compile(reStr)
if err != nil {
panic(err)
panic(fmt.Errorf("unable to compile %s as regular expression: %w", reStr, err))
}

list = append(list, re)
Expand All @@ -248,6 +268,25 @@ func mustNewPatternsList(in string) (list PatternsList) {
return list
}

func patternsSlitFn(r rune) bool {
return r == ','
type reListVar struct {
values *PatternsList
}

func (v *reListVar) Set(value string) error {
if value == "" {
return ErrEmptyPattern
}

re, err := regexp.Compile(value)
if err != nil {
return fmt.Errorf("unable to compile %s as regular expression: %w", value, err)
}

*v.values = append(*v.values, re)

return nil
}

func (v *reListVar) String() string {
return ""
}
19 changes: 13 additions & 6 deletions pkg/analyzer/analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ func TestAll(t *testing.T) {
}

testdata := filepath.Join(filepath.Dir(filepath.Dir(wd)), "testdata")
analyzer.IncludePatternsString = ".*\\.Test,.*\\.Test2,.*\\.Embedded,.*\\.External"
analyzer.ExcludePatternsString = ".*Excluded$"
analysistest.Run(t, testdata, analyzer.Analyzer, "s")

a := analyzer.MustNewAnalyzer(
[]string{".*\\.Test", ".*\\.Test2", ".*\\.Embedded", ".*\\.External"},
[]string{".*Excluded$"},
)

analysistest.Run(t, testdata, a, "s")
}

func BenchmarkAll(b *testing.B) {
Expand All @@ -31,10 +35,13 @@ func BenchmarkAll(b *testing.B) {
}

testdata := filepath.Join(filepath.Dir(filepath.Dir(wd)), "testdata")
analyzer.IncludePatternsString = ".*\\.Test,.*\\.Test2,.*\\.Embedded,.*\\.External"
analyzer.ExcludePatternsString = ".*Excluded$"

a := analyzer.MustNewAnalyzer(
[]string{".*\\.Test", ".*\\.Test2", ".*\\.Embedded", ".*\\.External"},
[]string{".*Excluded$"},
)

for i := 0; i < b.N; i++ {
analysistest.Run(b, testdata, analyzer.Analyzer, "s")
analysistest.Run(b, testdata, a, "s")
}
}

0 comments on commit bee026a

Please sign in to comment.