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  }