github.com/mattbailey/reviewdog@v0.10.0/reviewdog.go (about) 1 package reviewdog 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "os" 9 10 "github.com/reviewdog/reviewdog/diff" 11 ) 12 13 // Reviewdog represents review dog application which parses result of compiler 14 // or linter, get diff and filter the results by diff, and report filtered 15 // results. 16 type Reviewdog struct { 17 toolname string 18 p Parser 19 c CommentService 20 d DiffService 21 filterMode FilterMode 22 failOnError bool 23 } 24 25 // NewReviewdog returns a new Reviewdog. 26 func NewReviewdog(toolname string, p Parser, c CommentService, d DiffService, filterMode FilterMode, failOnError bool) *Reviewdog { 27 return &Reviewdog{p: p, c: c, d: d, toolname: toolname, filterMode: filterMode, failOnError: failOnError} 28 } 29 30 // RunFromResult creates a new Reviewdog and runs it with check results. 31 func RunFromResult(ctx context.Context, c CommentService, results []*CheckResult, 32 filediffs []*diff.FileDiff, strip int, toolname string, filterMode FilterMode, failOnError bool) error { 33 return (&Reviewdog{c: c, toolname: toolname, filterMode: filterMode, failOnError: failOnError}).runFromResult(ctx, results, filediffs, strip, failOnError) 34 } 35 36 // CheckResult represents a checked result of static analysis tools. 37 // :h error-file-format 38 type CheckResult struct { 39 Path string // relative file path 40 Lnum int // line number 41 Col int // column number (1 <tab> == 1 character column) 42 Message string // error message 43 Lines []string // Original error lines (often one line) 44 } 45 46 // Parser is an interface which parses compilers, linters, or any tools 47 // results. 48 type Parser interface { 49 Parse(r io.Reader) ([]*CheckResult, error) 50 } 51 52 // Comment represents a reported result as a comment. 53 type Comment struct { 54 *CheckResult 55 Body string 56 LnumDiff int 57 ToolName string 58 } 59 60 // CommentService is an interface which posts Comment. 61 type CommentService interface { 62 Post(context.Context, *Comment) error 63 } 64 65 // BulkCommentService posts comments all at once when Flush() is called. 66 // Flush() will be called at the end of reviewdog run. 67 type BulkCommentService interface { 68 CommentService 69 Flush(context.Context) error 70 } 71 72 // DiffService is an interface which get diff. 73 type DiffService interface { 74 Diff(context.Context) ([]byte, error) 75 Strip() int 76 } 77 78 func (w *Reviewdog) runFromResult(ctx context.Context, results []*CheckResult, 79 filediffs []*diff.FileDiff, strip int, failOnError bool) error { 80 wd, err := os.Getwd() 81 if err != nil { 82 return err 83 } 84 85 checks := FilterCheck(results, filediffs, strip, wd, w.filterMode) 86 hasViolations := false 87 88 for _, check := range checks { 89 if !check.InDiff { 90 continue 91 } 92 comment := &Comment{ 93 CheckResult: check.CheckResult, 94 Body: check.Message, // TODO: format message 95 LnumDiff: check.LnumDiff, 96 ToolName: w.toolname, 97 } 98 if err := w.c.Post(ctx, comment); err != nil { 99 return err 100 } 101 hasViolations = true 102 } 103 104 if bulk, ok := w.c.(BulkCommentService); ok { 105 return bulk.Flush(ctx) 106 } 107 108 if failOnError && hasViolations { 109 return fmt.Errorf("input data has violations") 110 } 111 112 return nil 113 } 114 115 // Run runs Reviewdog application. 116 func (w *Reviewdog) Run(ctx context.Context, r io.Reader) error { 117 results, err := w.p.Parse(r) 118 if err != nil { 119 return fmt.Errorf("parse error: %v", err) 120 } 121 122 d, err := w.d.Diff(ctx) 123 if err != nil { 124 return fmt.Errorf("fail to get diff: %v", err) 125 } 126 127 filediffs, err := diff.ParseMultiFile(bytes.NewReader(d)) 128 if err != nil { 129 return fmt.Errorf("fail to parse diff: %v", err) 130 } 131 132 return w.runFromResult(ctx, results, filediffs, w.d.Strip(), w.failOnError) 133 }