github.com/alexey-mercari/reviewdog@v0.10.1-0.20200514053941-928943b10766/cmd/reviewdog/main.go (about) 1 package main 2 3 import ( 4 "crypto/tls" 5 "errors" 6 "flag" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "net/http" 11 "net/url" 12 "os" 13 "os/exec" 14 "sort" 15 "strings" 16 "text/tabwriter" 17 18 "golang.org/x/build/gerrit" 19 "golang.org/x/net/context" // "context" 20 "golang.org/x/oauth2" 21 22 "github.com/google/go-github/v31/github" 23 "github.com/mattn/go-shellwords" 24 "github.com/reviewdog/errorformat/fmts" 25 "github.com/xanzy/go-gitlab" 26 27 "github.com/reviewdog/reviewdog" 28 "github.com/reviewdog/reviewdog/cienv" 29 "github.com/reviewdog/reviewdog/commands" 30 "github.com/reviewdog/reviewdog/difffilter" 31 "github.com/reviewdog/reviewdog/project" 32 gerritservice "github.com/reviewdog/reviewdog/service/gerrit" 33 githubservice "github.com/reviewdog/reviewdog/service/github" 34 "github.com/reviewdog/reviewdog/service/github/githubutils" 35 gitlabservice "github.com/reviewdog/reviewdog/service/gitlab" 36 ) 37 38 const usageMessage = "" + 39 `Usage: reviewdog [flags] 40 reviewdog accepts any compiler or linter results from stdin and filters 41 them by diff for review. reviewdog also can posts the results as a comment to 42 GitHub if you use reviewdog in CI service.` 43 44 type option struct { 45 version bool 46 diffCmd string 47 diffStrip int 48 efms strslice 49 f string // errorformat name 50 list bool // list supported errorformat name 51 name string // tool name which is used in comment 52 ci string 53 conf string 54 runners string 55 reporter string 56 level string 57 guessPullRequest bool 58 tee bool 59 filterMode difffilter.Mode 60 failOnError bool 61 } 62 63 // flags doc 64 const ( 65 diffCmdDoc = `diff command (e.g. "git diff") for local reporter. Do not use --relative flag for git command.` 66 diffStripDoc = "strip NUM leading components from diff file names (equivalent to 'patch -p') (default is 1 for git diff)" 67 efmsDoc = `list of errorformat (https://github.com/reviewdog/errorformat)` 68 fDoc = `format name (run -list to see supported format name) for input. It's also used as tool name in review comment if -name is empty` 69 listDoc = `list supported pre-defined format names which can be used as -f arg` 70 nameDoc = `tool name in review comment. -f is used as tool name if -name is empty` 71 ciDoc = `[deprecated] reviewdog automatically get necessary data. See also -reporter for migration` 72 confDoc = `config file path` 73 runnersDoc = `comma separated runners name to run in config file. default: run all runners` 74 levelDoc = `report level currently used for github-pr-check reporter ("info","warning","error").` 75 guessPullRequestDoc = `guess Pull Request ID by branch name and commit SHA` 76 teeDoc = `enable "tee"-like mode which outputs tools's output as is while reporting results to -reporter. Useful for debugging as well.` 77 filterModeDoc = `how to filter checks results. [added, diff_context, file, nofilter]. 78 "added" (default) 79 Filter by added/modified diff lines. 80 "diff_context" 81 Filter by diff context, which can include unchanged lines. 82 i.e. changed lines +-N lines (e.g. N=3 for default git diff). 83 "file" 84 Filter by added/modified file. 85 "nofilter" 86 Do not filter any results. 87 ` 88 reporterDoc = `reporter of reviewdog results. (local, github-check, github-pr-check, github-pr-review, gitlab-mr-discussion, gitlab-mr-commit) 89 "local" (default) 90 Report results to stdout. 91 92 "github-check" 93 Report results to GitHub Check. It works both for Pull Requests and commits. 94 For Pull Request, you can see report results in GitHub PullRequest Check 95 tab and can control filtering mode by -filter-mode flag. 96 97 There are two options to use this reporter. 98 99 Option 1) Run reviewdog from GitHub Actions w/ secrets.GITHUB_TOKEN 100 Note that it reports result to GitHub Actions log console for Pull 101 Requests from fork repository due to GitHub Actions restriction. 102 https://help.github.com/en/articles/virtual-environments-for-github-actions#github_token-secret 103 104 Set REVIEWDOG_GITHUB_API_TOKEN with secrets.GITHUB_TOKEN. e.g. 105 REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} 106 107 Option 2) Install reviewdog GitHub Apps 108 1. Install reviewdog Apps. https://github.com/apps/reviewdog 109 2. Set REVIEWDOG_TOKEN or run reviewdog CLI in trusted CI providers. 110 You can get token from https://reviewdog.app/gh/<owner>/<repo-name>. 111 $ export REVIEWDOG_TOKEN="xxxxx" 112 113 Note: Token is not required if you run reviewdog in Travis CI. 114 115 "github-pr-check" 116 Same as github-check reporter but it only supports Pull Requests. 117 118 "github-pr-review" 119 Report results to GitHub review comments. 120 121 1. Set REVIEWDOG_GITHUB_API_TOKEN environment variable. 122 Go to https://github.com/settings/tokens and create new Personal access token with repo scope. 123 124 For GitHub Enterprise: 125 $ export GITHUB_API="https://example.githubenterprise.com/api/v3" 126 127 "gitlab-mr-discussion" 128 Report results to GitLab MergeRequest discussion. 129 130 1. Set REVIEWDOG_GITLAB_API_TOKEN environment variable. 131 Go to https://gitlab.com/profile/personal_access_tokens 132 133 CI_API_V4_URL (defined by Gitlab CI) as the base URL for the Gitlab API automatically. 134 Alternatively, GITLAB_API can also be defined, and it will take precedence over the former: 135 $ export GITLAB_API="https://example.gitlab.com/api/v4" 136 137 "gitlab-mr-commit" 138 Same as gitlab-mr-discussion, but report results to GitLab comments for 139 each commits in Merge Requests. 140 141 "gerrit-change-review" 142 Report results to Gerrit Change comments. 143 144 1. Set GERRIT_USERNAME and GERRIT_PASSWORD for basic authentication or 145 GIT_GITCOOKIE_PATH for git cookie based authentication. 146 2. Set GERRIT_CHANGE_ID, GERRIT_REVISION_ID GERRIT_BRANCH abd GERRIT_ADDRESS 147 148 For example: 149 $ export GERRIT_CHANGE_ID=myproject~master~I1293efab014de2 150 $ export GERRIT_REVISION_ID=ed318bf9a3c 151 $ export GERRIT_BRANCH=master 152 $ export GERRIT_ADDRESS=http://localhost:8080 153 154 For GitHub Enterprise and self hosted GitLab, set 155 REVIEWDOG_INSECURE_SKIP_VERIFY to skip verifying SSL (please use this at your own risk) 156 $ export REVIEWDOG_INSECURE_SKIP_VERIFY=true 157 158 For non-local reporters, reviewdog automatically get necessary data from 159 environment variable in CI service (GitHub Actions, Travis CI, Circle CI, drone.io, GitLab CI). 160 You can set necessary data with following environment variable manually if 161 you want (e.g. run reviewdog in Jenkins). 162 163 $ export CI_PULL_REQUEST=14 # Pull Request number (e.g. 14) 164 $ export CI_COMMIT="$(git rev-parse @)" # SHA1 for the current build 165 $ export CI_REPO_OWNER="haya14busa" # repository owner 166 $ export CI_REPO_NAME="reviewdog" # repository name 167 ` 168 failOnErrorDoc = `Returns 1 as exit code if any errors/warnings found in input` 169 ) 170 171 var opt = &option{} 172 173 func init() { 174 flag.BoolVar(&opt.version, "version", false, "print version") 175 flag.StringVar(&opt.diffCmd, "diff", "", diffCmdDoc) 176 flag.IntVar(&opt.diffStrip, "strip", 1, diffStripDoc) 177 flag.Var(&opt.efms, "efm", efmsDoc) 178 flag.StringVar(&opt.f, "f", "", fDoc) 179 flag.BoolVar(&opt.list, "list", false, listDoc) 180 flag.StringVar(&opt.name, "name", "", nameDoc) 181 flag.StringVar(&opt.ci, "ci", "", ciDoc) 182 flag.StringVar(&opt.conf, "conf", "", confDoc) 183 flag.StringVar(&opt.runners, "runners", "", runnersDoc) 184 flag.StringVar(&opt.reporter, "reporter", "local", reporterDoc) 185 flag.StringVar(&opt.level, "level", "error", levelDoc) 186 flag.BoolVar(&opt.guessPullRequest, "guess", false, guessPullRequestDoc) 187 flag.BoolVar(&opt.tee, "tee", false, teeDoc) 188 flag.Var(&opt.filterMode, "filter-mode", filterModeDoc) 189 flag.BoolVar(&opt.failOnError, "fail-on-error", false, failOnErrorDoc) 190 } 191 192 func usage() { 193 fmt.Fprintln(os.Stderr, usageMessage) 194 fmt.Fprintln(os.Stderr, "Flags:") 195 flag.PrintDefaults() 196 fmt.Fprintln(os.Stderr, "") 197 fmt.Fprintln(os.Stderr, "See https://github.com/reviewdog/reviewdog for more detail.") 198 os.Exit(2) 199 } 200 201 func main() { 202 flag.Usage = usage 203 flag.Parse() 204 if err := run(os.Stdin, os.Stdout, opt); err != nil { 205 fmt.Fprintf(os.Stderr, "reviewdog: %v\n", err) 206 os.Exit(1) 207 } 208 } 209 210 func run(r io.Reader, w io.Writer, opt *option) error { 211 ctx := context.Background() 212 213 if opt.version { 214 fmt.Fprintln(w, commands.Version) 215 return nil 216 } 217 218 if opt.list { 219 return runList(w) 220 } 221 222 // TODO(haya14busa): clean up when removing -ci flag from next release. 223 if opt.ci != "" { 224 return errors.New(`-ci flag is deprecated. 225 See -reporter flag for migration and set -reporter="github-pr-review" or -reporter="github-pr-check" or -reporter="gitlab-mr-commit"`) 226 } 227 228 if opt.tee { 229 r = io.TeeReader(r, w) 230 } 231 232 // assume it's project based run when both -efm and -f are not specified 233 isProject := len(opt.efms) == 0 && opt.f == "" 234 235 var cs reviewdog.CommentService 236 var ds reviewdog.DiffService 237 238 if isProject { 239 cs = reviewdog.NewUnifiedCommentWriter(w) 240 } else { 241 cs = reviewdog.NewRawCommentWriter(w) 242 } 243 244 switch opt.reporter { 245 default: 246 return fmt.Errorf("unknown -reporter: %s", opt.reporter) 247 case "github-check": 248 return runDoghouse(ctx, r, w, opt, isProject, false) 249 case "github-pr-check": 250 return runDoghouse(ctx, r, w, opt, isProject, true) 251 case "github-pr-review": 252 if os.Getenv("REVIEWDOG_GITHUB_API_TOKEN") == "" { 253 fmt.Fprintln(os.Stderr, "REVIEWDOG_GITHUB_API_TOKEN is not set") 254 return nil 255 } 256 gs, isPR, err := githubService(ctx, opt) 257 if err != nil { 258 return err 259 } 260 if !isPR { 261 fmt.Fprintln(os.Stderr, "reviewdog: this is not PullRequest build.") 262 return nil 263 } 264 // If it's running in GitHub Actions and it's PR from forked repository, 265 // replace comment writer to GitHubActionLogWriter to create annotations 266 // instead of review comment because if it's PR from forked repository, 267 // GitHub token doesn't have write permission due to security concern and 268 // cannot post results via Review API. 269 if cienv.IsInGitHubAction() && isPRFromForkedRepo() { 270 fmt.Fprintln(w, `reviewdog: This is Pull-Request from forked repository. 271 GitHub token doesn't have write permission of Review API, so reviewdog will 272 report results via logging command [1] and create annotations similar to 273 github-pr-check reporter as a fallback. 274 275 [1]: https://help.github.com/en/actions/automating-your-workflow-with-github-actions/development-tools-for-github-actions#logging-commands`) 276 cs = githubutils.NewGitHubActionLogWriter(opt.level) 277 } else { 278 cs = reviewdog.MultiCommentService(gs, cs) 279 } 280 ds = gs 281 case "gitlab-mr-discussion": 282 build, cli, err := gitlabBuildWithClient() 283 if err != nil { 284 return err 285 } 286 if build.PullRequest == 0 { 287 fmt.Fprintln(os.Stderr, "this is not MergeRequest build.") 288 return nil 289 } 290 291 gc, err := gitlabservice.NewGitLabMergeRequestDiscussionCommenter(cli, build.Owner, build.Repo, build.PullRequest, build.SHA) 292 if err != nil { 293 return err 294 } 295 296 cs = reviewdog.MultiCommentService(gc, cs) 297 ds, err = gitlabservice.NewGitLabMergeRequestDiff(cli, build.Owner, build.Repo, build.PullRequest, build.SHA) 298 if err != nil { 299 return err 300 } 301 case "gitlab-mr-commit": 302 build, cli, err := gitlabBuildWithClient() 303 if err != nil { 304 return err 305 } 306 if build.PullRequest == 0 { 307 fmt.Fprintln(os.Stderr, "this is not MergeRequest build.") 308 return nil 309 } 310 311 gc, err := gitlabservice.NewGitLabMergeRequestCommitCommenter(cli, build.Owner, build.Repo, build.PullRequest, build.SHA) 312 if err != nil { 313 return err 314 } 315 316 cs = reviewdog.MultiCommentService(gc, cs) 317 ds, err = gitlabservice.NewGitLabMergeRequestDiff(cli, build.Owner, build.Repo, build.PullRequest, build.SHA) 318 if err != nil { 319 return err 320 } 321 case "gerrit-change-review": 322 b, cli, err := gerritBuildWithClient() 323 if err != nil { 324 return err 325 } 326 gc, err := gerritservice.NewChangeReviewCommenter(cli, b.GerritChangeID, b.GerritRevisionID) 327 if err != nil { 328 return err 329 } 330 cs = gc 331 332 d, err := gerritservice.NewChangeDiff(cli, b.Branch, b.GerritChangeID) 333 if err != nil { 334 return err 335 } 336 ds = d 337 case "local": 338 if opt.diffCmd == "" && opt.filterMode == difffilter.ModeNoFilter { 339 ds = &reviewdog.EmptyDiff{} 340 } else { 341 d, err := diffService(opt.diffCmd, opt.diffStrip) 342 if err != nil { 343 return err 344 } 345 ds = d 346 } 347 } 348 349 if isProject { 350 conf, err := projectConfig(opt.conf) 351 if err != nil { 352 return err 353 } 354 return project.Run(ctx, conf, buildRunnersMap(opt.runners), cs, ds, opt.tee, opt.filterMode, opt.failOnError) 355 } 356 357 p, err := newParserFromOpt(opt) 358 if err != nil { 359 return err 360 } 361 362 app := reviewdog.NewReviewdog(toolName(opt), p, cs, ds, opt.filterMode, opt.failOnError) 363 return app.Run(ctx, r) 364 } 365 366 func runList(w io.Writer) error { 367 tabw := tabwriter.NewWriter(w, 0, 8, 0, '\t', 0) 368 for _, f := range sortedFmts(fmts.DefinedFmts()) { 369 fmt.Fprintf(tabw, "%s\t%s\t- %s\n", f.Name, f.Description, f.URL) 370 } 371 fmt.Fprintf(tabw, "%s\t%s\t- %s\n", "checkstyle", "checkstyle XML format", "http://checkstyle.sourceforge.net/") 372 return tabw.Flush() 373 } 374 375 type byFmtName []*fmts.Fmt 376 377 func (p byFmtName) Len() int { return len(p) } 378 func (p byFmtName) Less(i, j int) bool { return p[i].Name < p[j].Name } 379 func (p byFmtName) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 380 381 func sortedFmts(fs fmts.Fmts) []*fmts.Fmt { 382 r := make([]*fmts.Fmt, 0, len(fs)) 383 for _, f := range fs { 384 r = append(r, f) 385 } 386 sort.Sort(byFmtName(r)) 387 return r 388 } 389 390 func diffService(s string, strip int) (reviewdog.DiffService, error) { 391 cmds, err := shellwords.Parse(s) 392 if err != nil { 393 return nil, err 394 } 395 if len(cmds) < 1 { 396 return nil, errors.New("diff command is empty") 397 } 398 cmd := exec.Command(cmds[0], cmds[1:]...) 399 d := reviewdog.NewDiffCmd(cmd, strip) 400 return d, nil 401 } 402 403 func newHTTPClient() *http.Client { 404 tr := &http.Transport{ 405 Proxy: http.ProxyFromEnvironment, 406 TLSClientConfig: &tls.Config{InsecureSkipVerify: insecureSkipVerify()}, 407 } 408 return &http.Client{Transport: tr} 409 } 410 411 func insecureSkipVerify() bool { 412 return os.Getenv("REVIEWDOG_INSECURE_SKIP_VERIFY") == "true" 413 } 414 415 func githubService(ctx context.Context, opt *option) (gs *githubservice.GitHubPullRequest, isPR bool, err error) { 416 token, err := nonEmptyEnv("REVIEWDOG_GITHUB_API_TOKEN") 417 if err != nil { 418 return nil, isPR, err 419 } 420 g, isPR, err := cienv.GetBuildInfo() 421 if err != nil { 422 return nil, isPR, err 423 } 424 425 client, err := githubClient(ctx, token) 426 if err != nil { 427 return nil, isPR, err 428 } 429 430 if !isPR { 431 if !opt.guessPullRequest { 432 return nil, false, nil 433 } 434 435 if g.Branch == "" && g.SHA == "" { 436 return nil, false, nil 437 } 438 439 prID, err := getPullRequestIDByBranchOrCommit(ctx, client, g) 440 if err != nil { 441 fmt.Fprintln(os.Stderr, err) 442 return nil, false, nil 443 } 444 g.PullRequest = prID 445 } 446 447 gs, err = githubservice.NewGitHubPullRequest(client, g.Owner, g.Repo, g.PullRequest, g.SHA) 448 if err != nil { 449 return nil, false, err 450 } 451 return gs, true, nil 452 } 453 454 func getPullRequestIDByBranchOrCommit(ctx context.Context, client *github.Client, info *cienv.BuildInfo) (int, error) { 455 options := &github.SearchOptions{ 456 Sort: "updated", 457 Order: "desc", 458 } 459 460 query := []string{ 461 "type:pr", 462 "state:open", 463 fmt.Sprintf("repo:%s/%s", info.Owner, info.Repo), 464 } 465 if info.Branch != "" { 466 query = append(query, fmt.Sprintf("head:%s", info.Branch)) 467 } 468 if info.SHA != "" { 469 query = append(query, info.SHA) 470 } 471 472 preparedQuery := strings.Join(query, " ") 473 pullRequests, _, err := client.Search.Issues(ctx, preparedQuery, options) 474 if err != nil { 475 return 0, err 476 } 477 478 if *pullRequests.Total == 0 { 479 return 0, fmt.Errorf("reviewdog: PullRequest not found, query: %s", preparedQuery) 480 } 481 482 return *pullRequests.Issues[0].Number, nil 483 } 484 485 func githubClient(ctx context.Context, token string) (*github.Client, error) { 486 ctx = context.WithValue(ctx, oauth2.HTTPClient, newHTTPClient()) 487 ts := oauth2.StaticTokenSource( 488 &oauth2.Token{AccessToken: token}, 489 ) 490 tc := oauth2.NewClient(ctx, ts) 491 client := github.NewClient(tc) 492 var err error 493 client.BaseURL, err = githubBaseURL() 494 return client, err 495 } 496 497 const defaultGitHubAPI = "https://api.github.com/" 498 499 func githubBaseURL() (*url.URL, error) { 500 baseURL := os.Getenv("GITHUB_API") 501 if baseURL == "" { 502 baseURL = defaultGitHubAPI 503 } 504 u, err := url.Parse(baseURL) 505 if err != nil { 506 return nil, fmt.Errorf("GitHub base URL is invalid: %v, %v", baseURL, err) 507 } 508 return u, nil 509 } 510 511 func gitlabBuildWithClient() (*cienv.BuildInfo, *gitlab.Client, error) { 512 token, err := nonEmptyEnv("REVIEWDOG_GITLAB_API_TOKEN") 513 if err != nil { 514 return nil, nil, err 515 } 516 517 g, _, err := cienv.GetBuildInfo() 518 if err != nil { 519 return nil, nil, err 520 } 521 522 client, err := gitlabClient(token) 523 if err != nil { 524 return nil, nil, err 525 } 526 527 if g.PullRequest == 0 { 528 prNr, err := fetchMergeRequestIDFromCommit(client, g.Owner+"/"+g.Repo, g.SHA) 529 if err != nil { 530 return nil, nil, err 531 } 532 if prNr != 0 { 533 g.PullRequest = prNr 534 } 535 } 536 537 return g, client, err 538 } 539 540 func gerritBuildWithClient() (*cienv.BuildInfo, *gerrit.Client, error) { 541 buildInfo, err := cienv.GetGerritBuildInfo() 542 if err != nil { 543 return nil, nil, err 544 } 545 546 gerritAddr := os.Getenv("GERRIT_ADDRESS") 547 if gerritAddr == "" { 548 return nil, nil, errors.New("cannot get gerrit host address from environment variable. Set GERRIT_ADDRESS ?") 549 } 550 551 username := os.Getenv("GERRIT_USERNAME") 552 password := os.Getenv("GERRIT_PASSWORD") 553 if username != "" && password != "" { 554 client := gerrit.NewClient(gerritAddr, gerrit.BasicAuth(username, password)) 555 return buildInfo, client, nil 556 } 557 558 if useGitCookiePath := os.Getenv("GERRIT_GIT_COOKIE_PATH"); useGitCookiePath != "" { 559 client := gerrit.NewClient(gerritAddr, gerrit.GitCookieFileAuth(useGitCookiePath)) 560 return buildInfo, client, nil 561 } 562 563 client := gerrit.NewClient(gerritAddr, gerrit.NoAuth) 564 return buildInfo, client, nil 565 } 566 567 func fetchMergeRequestIDFromCommit(cli *gitlab.Client, projectID, sha string) (id int, err error) { 568 // https://docs.gitlab.com/ce/api/merge_requests.html#list-project-merge-requests 569 opt := &gitlab.ListProjectMergeRequestsOptions{ 570 State: gitlab.String("opened"), 571 OrderBy: gitlab.String("updated_at"), 572 } 573 mrs, _, err := cli.MergeRequests.ListProjectMergeRequests(projectID, opt) 574 if err != nil { 575 return 0, err 576 } 577 for _, mr := range mrs { 578 if mr.SHA == sha { 579 return mr.IID, nil 580 } 581 } 582 return 0, nil 583 } 584 585 func gitlabClient(token string) (*gitlab.Client, error) { 586 baseURL, err := gitlabBaseURL() 587 if err != nil { 588 return nil, err 589 } 590 client, err := gitlab.NewClient(token, gitlab.WithHTTPClient(newHTTPClient()), gitlab.WithBaseURL(baseURL.String())) 591 if err != nil { 592 return nil, err 593 } 594 return client, nil 595 } 596 597 const defaultGitLabAPI = "https://gitlab.com/api/v4" 598 599 func gitlabBaseURL() (*url.URL, error) { 600 gitlabAPI := os.Getenv("GITLAB_API") 601 gitlabV4URL := os.Getenv("CI_API_V4_URL") 602 603 var baseURL string 604 if gitlabAPI != "" { 605 baseURL = gitlabAPI 606 } else if gitlabV4URL != "" { 607 baseURL = gitlabV4URL 608 } else { 609 baseURL = defaultGitLabAPI 610 } 611 612 u, err := url.Parse(baseURL) 613 if err != nil { 614 return nil, fmt.Errorf("GitLab base URL is invalid: %v, %v", baseURL, err) 615 } 616 return u, nil 617 } 618 619 func nonEmptyEnv(env string) (string, error) { 620 v := os.Getenv(env) 621 if v == "" { 622 return "", fmt.Errorf("environment variable $%v is not set", env) 623 } 624 return v, nil 625 } 626 627 type strslice []string 628 629 func (ss *strslice) String() string { 630 return fmt.Sprintf("%v", *ss) 631 } 632 633 func (ss *strslice) Set(value string) error { 634 *ss = append(*ss, value) 635 return nil 636 } 637 638 func projectConfig(path string) (*project.Config, error) { 639 b, err := readConf(path) 640 if err != nil { 641 return nil, fmt.Errorf("fail to open config: %v", err) 642 } 643 conf, err := project.Parse(b) 644 if err != nil { 645 return nil, fmt.Errorf("config is invalid: %v", err) 646 } 647 return conf, nil 648 } 649 650 func readConf(conf string) ([]byte, error) { 651 var conffiles []string 652 if conf != "" { 653 conffiles = []string{conf} 654 } else { 655 conffiles = []string{ 656 ".reviewdog.yaml", 657 ".reviewdog.yml", 658 "reviewdog.yaml", 659 "reviewdog.yml", 660 } 661 } 662 for _, f := range conffiles { 663 bytes, err := ioutil.ReadFile(f) 664 if err == nil { 665 return bytes, nil 666 } 667 } 668 return nil, errors.New(".reviewdog.yml not found") 669 } 670 671 func newParserFromOpt(opt *option) (reviewdog.Parser, error) { 672 p, err := reviewdog.NewParser(&reviewdog.ParserOpt{FormatName: opt.f, Errorformat: opt.efms}) 673 if err != nil { 674 return nil, fmt.Errorf("fail to create parser. use either -f or -efm: %v", err) 675 } 676 return p, err 677 } 678 679 func toolName(opt *option) string { 680 name := opt.name 681 if name == "" && opt.f != "" { 682 name = opt.f 683 } 684 return name 685 } 686 687 func buildRunnersMap(runners string) map[string]bool { 688 m := make(map[string]bool) 689 for _, r := range strings.Split(runners, ",") { 690 if name := strings.TrimSpace(r); name != "" { 691 m[name] = true 692 } 693 } 694 return m 695 }