github.com/alexey-mercari/reviewdog@v0.10.1-0.20200514053941-928943b10766/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 "github.com/reviewdog/reviewdog/difffilter" 12 ) 13 14 // Reviewdog represents review dog application which parses result of compiler 15 // or linter, get diff and filter the results by diff, and report filtered 16 // results. 17 type Reviewdog struct { 18 toolname string 19 p Parser 20 c CommentService 21 d DiffService 22 filterMode difffilter.Mode 23 failOnError bool 24 } 25 26 // NewReviewdog returns a new Reviewdog. 27 func NewReviewdog(toolname string, p Parser, c CommentService, d DiffService, filterMode difffilter.Mode, failOnError bool) *Reviewdog { 28 return &Reviewdog{p: p, c: c, d: d, toolname: toolname, filterMode: filterMode, failOnError: failOnError} 29 } 30 31 // RunFromResult creates a new Reviewdog and runs it with check results. 32 func RunFromResult(ctx context.Context, c CommentService, results []*CheckResult, 33 filediffs []*diff.FileDiff, strip int, toolname string, filterMode difffilter.Mode, failOnError bool) error { 34 return (&Reviewdog{c: c, toolname: toolname, filterMode: filterMode, failOnError: failOnError}).runFromResult(ctx, results, filediffs, strip, failOnError) 35 } 36 37 // CheckResult represents a checked result of static analysis tools. 38 // :h error-file-format 39 type CheckResult struct { 40 Path string // relative file path 41 Lnum int // line number 42 Col int // column number (1 <tab> == 1 character column) 43 Message string // error message 44 Lines []string // Original error lines (often one line) 45 } 46 47 // Parser is an interface which parses compilers, linters, or any tools 48 // results. 49 type Parser interface { 50 Parse(r io.Reader) ([]*CheckResult, error) 51 } 52 53 // Comment represents a reported result as a comment. 54 type Comment struct { 55 Result *FilteredCheck 56 ToolName string 57 Body 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.ShouldReport { 90 continue 91 } 92 comment := &Comment{ 93 Result: check, 94 Body: check.Message, 95 ToolName: w.toolname, 96 } 97 if err := w.c.Post(ctx, comment); err != nil { 98 return err 99 } 100 hasViolations = true 101 } 102 103 if bulk, ok := w.c.(BulkCommentService); ok { 104 if err := bulk.Flush(ctx); err != nil { 105 return err 106 } 107 } 108 109 if failOnError && hasViolations { 110 return fmt.Errorf("input data has violations") 111 } 112 113 return nil 114 } 115 116 // Run runs Reviewdog application. 117 func (w *Reviewdog) Run(ctx context.Context, r io.Reader) error { 118 results, err := w.p.Parse(r) 119 if err != nil { 120 return fmt.Errorf("parse error: %v", err) 121 } 122 123 d, err := w.d.Diff(ctx) 124 if err != nil { 125 return fmt.Errorf("fail to get diff: %v", err) 126 } 127 128 filediffs, err := diff.ParseMultiFile(bytes.NewReader(d)) 129 if err != nil { 130 return fmt.Errorf("fail to parse diff: %v", err) 131 } 132 133 return w.runFromResult(ctx, results, filediffs, w.d.Strip(), w.failOnError) 134 }