Skip to content

Commit

Permalink
feature: golangci-lint adapter (#21)
Browse files Browse the repository at this point in the history
* feature: updated standalone Run for analyzer
* feature: `golangci-lint` issue exporter
  • Loading branch information
butuzov authored May 13, 2023
1 parent 5e4827d commit 8c8321f
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 34 deletions.
45 changes: 15 additions & 30 deletions analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"flag"
"go/ast"
"strings"
"sync"

"github.com/butuzov/mirror/internal/checker"

Expand All @@ -13,30 +12,32 @@ import (
"golang.org/x/tools/go/ast/inspector"
)

type analyzer struct {
withTests bool
withDebug bool

once sync.Once
}

func NewAnalyzer() *analysis.Analyzer {
flags := flags()

a := analyzer{}

return &analysis.Analyzer{
Name: "mirror",
Doc: "reports wrong mirror patterns of bytes/strings usage",
Run: a.run,
Run: run,
Requires: []*analysis.Analyzer{
inspect.Analyzer,
},
Flags: flags,
}
}

func (a *analyzer) run(pass *analysis.Pass) (interface{}, error) {
func run(pass *analysis.Pass) (interface{}, error) {
withTests := pass.Analyzer.Flags.Lookup("with-tests").Value.String() == "true"
// --- Reporting violations via issues ---------------------------------------
for _, violation := range Run(pass, withTests) {
pass.Report(violation.Diagnostic(pass.Fset))
}

return nil, nil
}

func Run(pass *analysis.Pass, withTests bool) []*checker.Violation {
violations := []*checker.Violation{}
// --- Setup -----------------------------------------------------------------

check := checker.New(
Expand All @@ -51,10 +52,6 @@ func (a *analyzer) run(pass *analysis.Pass) (interface{}, error) {
check.Type = checker.WrapType(pass.TypesInfo)
check.Print = checker.WrapPrint(pass.Fset)

violations := []*checker.Violation{}

a.once.Do(a.setup(pass.Analyzer.Flags)) // loading flags info

ins, _ := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
imports := checker.Load(pass.Fset, ins)

Expand All @@ -63,7 +60,7 @@ func (a *analyzer) run(pass *analysis.Pass) (interface{}, error) {
callExpr := n.(*ast.CallExpr)
fileName := pass.Fset.Position(callExpr.Pos()).Filename

if !a.withTests && strings.HasSuffix(fileName, "_test.go") {
if !withTests && strings.HasSuffix(fileName, "_test.go") {
return
}

Expand Down Expand Up @@ -124,19 +121,7 @@ func (a *analyzer) run(pass *analysis.Pass) (interface{}, error) {
}
})

// --- Reporting violations via issues ---------------------------------------
for _, violation := range violations {
pass.Report(violation.Issue(pass.Fset))
}

return nil, nil
}

func (a *analyzer) setup(f flag.FlagSet) func() {
return func() {
a.withTests = f.Lookup("with-tests").Value.String() == "true"
a.withDebug = f.Lookup("with-debug").Value.String() == "true"
}
return violations
}

func flags() flag.FlagSet {
Expand Down
56 changes: 52 additions & 4 deletions internal/checker/violation.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"go/printer"
"go/token"
"path"
"strings"

"golang.org/x/tools/go/analysis"
)
Expand Down Expand Up @@ -100,17 +101,19 @@ func (v *Violation) suggest(fSet *token.FileSet) []byte {
return buf.Bytes()
}

func (v *Violation) Issue(fSet *token.FileSet) analysis.Diagnostic {
func (v *Violation) Diagnostic(fSet *token.FileSet) analysis.Diagnostic {
diagnostic := analysis.Diagnostic{
Pos: v.callExpr.Pos(),
End: v.callExpr.Pos(),
Message: v.Message(),
}

// fmt.Println(string(v.suggest(fSet)))
var buf bytes.Buffer
printer.Fprint(&buf, fSet, v.callExpr)
noNl := bytes.IndexByte(buf.Bytes(), '\n') < 0

// Struct based fix.
if v.Type == Method {
if v.Type == Method && noNl {
diagnostic.SuggestedFixes = []analysis.SuggestedFix{{
Message: "Fix Issue With",
TextEdits: []analysis.TextEdit{{
Expand All @@ -119,8 +122,12 @@ func (v *Violation) Issue(fSet *token.FileSet) analysis.Diagnostic {
}}
}

if v.AltPackage == "" {
v.AltPackage = v.Package
}

// Hooray! we dont need to change package and redo imports.
if v.Type == Function && len(v.AltPackage) == 0 {
if v.Type == Function && v.AltPackage == v.Package && noNl {
diagnostic.SuggestedFixes = []analysis.SuggestedFix{{
Message: "Fix Issue With",
TextEdits: []analysis.TextEdit{{
Expand All @@ -133,3 +140,44 @@ func (v *Violation) Issue(fSet *token.FileSet) analysis.Diagnostic {

return diagnostic
}

type GolangIssue struct {
Start token.Position
End token.Position
Message string
InlineFix string
Original string
}

// GolangCI-lint related diagnostic
func (v *Violation) Issue(pass *analysis.Pass) GolangIssue {
issue := GolangIssue{
Start: pass.Fset.Position(v.callExpr.Pos()),
End: pass.Fset.Position(v.callExpr.End()),
Message: v.Message(),
}

// original expression (useful for debug & requied for replace)
var buf bytes.Buffer
printer.Fprint(&buf, pass.Fset, v.callExpr)
issue.Original = buf.String()

noNl := strings.IndexByte(issue.Original, '\n') < 0

if v.Type == Method && noNl {
fix := v.suggest(pass.Fset)
issue.InlineFix = string(fix)
}

if v.AltPackage == "" {
v.AltPackage = v.Package
}

// Hooray! we don't need to change package and redo imports.
if v.Type == Function && v.AltPackage == v.Package && noNl {
fix := v.suggest(pass.Fset)
issue.InlineFix = string(fix)
}

return issue
}

0 comments on commit 8c8321f

Please sign in to comment.