From 8c8321f58f55a47bd701798840e9346ce64eae81 Mon Sep 17 00:00:00 2001 From: Oleg Butuzov Date: Sat, 13 May 2023 14:29:20 +0300 Subject: [PATCH] feature: golangci-lint adapter (#21) * feature: updated standalone Run for analyzer * feature: `golangci-lint` issue exporter --- analyzer.go | 45 ++++++++++------------------ internal/checker/violation.go | 56 ++++++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 34 deletions(-) diff --git a/analyzer.go b/analyzer.go index 53a991c..025c2b2 100644 --- a/analyzer.go +++ b/analyzer.go @@ -4,7 +4,6 @@ import ( "flag" "go/ast" "strings" - "sync" "github.com/butuzov/mirror/internal/checker" @@ -13,22 +12,13 @@ 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, }, @@ -36,7 +26,18 @@ func NewAnalyzer() *analysis.Analyzer { } } -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( @@ -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) @@ -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 } @@ -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 { diff --git a/internal/checker/violation.go b/internal/checker/violation.go index 570678f..db113a6 100644 --- a/internal/checker/violation.go +++ b/internal/checker/violation.go @@ -7,6 +7,7 @@ import ( "go/printer" "go/token" "path" + "strings" "golang.org/x/tools/go/analysis" ) @@ -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{{ @@ -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{{ @@ -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 +}