Skip to content

Instantly share code, notes, and snippets.

@haya14busa
Created December 22, 2016 13:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save haya14busa/d3be48dba9d12e91c60b9a5d711da9b5 to your computer and use it in GitHub Desktop.
Save haya14busa/d3be48dba9d12e91c60b9a5d711da9b5 to your computer and use it in GitHub Desktop.
pr17.diff
diff --git a/.drone.sec b/.drone.sec
index f1a10a2..28b15e3 100644
--- a/.drone.sec
+++ b/.drone.sec
@@ -1 +1 @@
-eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.M2qFghvswadLY_OZqIAlf8RUgWQIZH5F6wL7g1DNmRDp8IzLmRltg0bbb8cDK2qJcG8STL2gi9m_SrW9bHrwR87Mg4SFYM50-RISmVh3-vTJpbKhZiGqW27xb9b0wOufJKSCWPKNtQK7mCpfO9aZMSGbMcln8aMJUyZt4povZVxRm8H1sq6KOEetg8eRYkAmbThche2KQQv3wU061dr0CaHHcdXZhrFWOuV4XlvWsB-yjpxntNksZQOCdDZ3dZHdJPdtt3AqnfkxqyGl16Q7fC1MUwDANwCvuD-omnvgGsQHybpsr2nmrG3Ce8ysMaO4Dkyv5d4M6qwHJRZfEfH1ew.W9jt_Vq4V41j1yuz.dSRr2SDGBDOMctB5RBLMhgk3w-wr9JnqCqxI02Tl_rIeXwbXsJi7Qutao1Nx8KFVB3umc7HLcizJd1KK9bwezSye2hoc3EvKBdFy4InuOKhrb7EW4bziRdE9x6e2ACAccYbZ1_Cn_-nklRP-SE0dA5C7mwzIFIbbUZlkp581nmuHmNQxzKEtmuIytm1rQnDjJD6q4aYx3gBFuA0-HPtb.43ijUiX-PTBYt9O95cuF-Q
\ No newline at end of file
+eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.GVCyMNII0AoQKyoZuVoy90AR5hhc5qKNDK8GEjaLS2xo1Kx3saLnkryg8DuOCE-Avf0O62toDYD0VIgXi6mtN5flf0JLAYdV_A6DZXfim3RVVzTBmHwVeeYwSt1K4TcOi_AT5iuXDLPj9Af87xdB7FMAvFr9hqOiCqA1_Oa2Zn-9BtzYVto_SXwrmPxM0K0pYw8M08rN1qAgM34ctV1HodoI-9Z8_Sy4HLwkCmAE42oZp93Tw3UdGtibTK1cRUg45Kw574iBUA8VzMYxwLDRJxqU3Yzat4Hbq8YL_SZJ8E0mr9rtCn3Bf3vL6NWAhH7WYH7E4NiX82_89jYEC6NcXg.IzYIlskP4oPmgS-D.65E_gjqBTQTDfNmD-mIsDSlvAspuyxcqeXj7eONaNQf0dNxXxeyPr40gX75jrSXEMySlhteJF-hO3ElwEV3vU_QXl49Yz2xr-1_1ltbOaWEjNhb3rLPNFtvsh-Ljjb9epIK2z9mWQ2m_Ss4LIKfZgJNd--tusHg0Gm3CpLui-rv8brrDKsySi1JMYP5H5hSpVJzPy3kLFXR5oW9b0Ewa.VVVMl0PAa978pZoj1iBEQA
\ No newline at end of file
diff --git a/.drone.yml b/.drone.yml
index f3c3f21..c23c562 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -1,5 +1,5 @@
# Run the below command when you edit .drone.yml
-# drone secure --repo haya14busa/watchdogs --in .drone.sec.yaml
+# drone secure --repo haya14busa/reviewdog --in .drone.sec.yaml
#
build:
test:
@@ -12,14 +12,14 @@ build:
environment:
- WATCHDOGS_GITHUB_API_TOKEN=$$WATCHDOGS_GITHUB_API_TOKEN
commands:
- - go get github.com/haya14busa/watchdogs/cmd/watchdogs
+ - go get github.com/haya14busa/reviewdog/cmd/reviewdog
- go get github.com/golang/lint/golint
- go get honnef.co/go/unused/cmd/unused
- |
- go tool vet -all -shadowstrict . | watchdogs -efm="%f:%l: %m" -ci=droneio
+ go tool vet -all -shadowstrict . | reviewdog -efm="%f:%l: %m" -ci=droneio
- |
- golint ./... | watchdogs -efm="%f:%l:%c: %m" -ci=droneio
+ golint ./... | reviewdog -efm="%f:%l:%c: %m" -ci=droneio
- |
- unused ./... | watchdogs -efm="%f:%l:%c: %m" -ci=droneio
+ unused ./... | reviewdog -efm="%f:%l:%c: %m" -ci=droneio
when:
event: pull_request
diff --git a/.travis.yml b/.travis.yml
index 60b2676..1a4175e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -27,6 +27,6 @@ script:
- goveralls -service=travis-ci
# - go vet ./... ref: circle.yml
# - unused ./... ref: .drone.yml
- - go install ./cmd/watchdogs
+ - go install ./cmd/reviewdog
- >-
- golint ./... | watchdogs -efm="%f:%l:%c: %m" -ci=travis
+ golint ./... | reviewdog -efm="%f:%l:%c: %m" -ci=travis
diff --git a/README.md b/README.md
index be339c5..0d5300e 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,3 @@
-## Watchdogs - An automated code review tool working with any lint tools
+## Reviewdog - An automated code review tool working with any lint tools
[WIP]
diff --git a/circle.yml b/circle.yml
index 0d61833..97f1403 100644
--- a/circle.yml
+++ b/circle.yml
@@ -1,7 +1,7 @@
test:
pre:
- - go build ./cmd/watchdogs
+ - go build ./cmd/reviewdog
override:
- go test -v -race ./...
- >-
- go tool vet -all -shadowstrict . | ./watchdogs -efm="%f:%l: %m" -ci="circle-ci"
+ go tool vet -all -shadowstrict . | ./reviewdog -efm="%f:%l: %m" -ci="circle-ci"
diff --git a/cmd/reviewdog/.gitignore b/cmd/reviewdog/.gitignore
new file mode 100644
index 0000000..f84e0d8
--- /dev/null
+++ b/cmd/reviewdog/.gitignore
@@ -0,0 +1 @@
+reviewdog
diff --git a/cmd/reviewdog/main.go b/cmd/reviewdog/main.go
new file mode 100644
index 0000000..f903545
--- /dev/null
+++ b/cmd/reviewdog/main.go
@@ -0,0 +1,354 @@
+package main
+
+import (
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "os"
+ "os/exec"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "golang.org/x/oauth2"
+
+ "github.com/google/go-github/github"
+ "github.com/haya14busa/errorformat"
+ "github.com/haya14busa/reviewdog"
+ "github.com/mattn/go-shellwords"
+)
+
+const usageMessage = "" +
+ `Usage: reviewdog [flags]
+ reviewdog accepts any compiler or linter results from stdin and filters
+ them by diff for review. reviewdog also can posts the results as a comment to
+ GitHub if you use reviewdog in CI service.
+`
+
+// flags
+var (
+ diffCmd string
+ diffCmdDoc = `diff command (e.g. "git diff"). diff flag is ignored if you pass "ci" flag`
+
+ diffStrip int
+ efms strslice
+
+ ci string
+ ciDoc = `CI service (supported travis, circle-ci, droneio(OSS 0.4), common)
+ If you use "ci" flag, you need to set WATCHDOGS_GITHUB_API_TOKEN environment
+ variable. Go to https://github.com/settings/tokens and create new Personal
+ access token with repo scope.
+
+ "common" requires following environment variables
+ CI_PULL_REQUEST Pull Request number (e.g. 14)
+ CI_COMMIT SHA1 for the current build
+ CI_REPO_OWNER repository owner (e.g. "haya14busa" for https://github.com/haya14busa/reviewdog)
+ CI_REPO_NAME repository name (e.g. "reviewdog" for https://github.com/haya14busa/reviewdog)
+`
+)
+
+func init() {
+ flag.StringVar(&diffCmd, "diff", "", diffCmdDoc)
+ flag.IntVar(&diffStrip, "strip", 1, "strip NUM leading components from diff file names (equivalent to `patch -p`) (default is 1 for git diff)")
+ flag.Var(&efms, "efm", "list of errorformat (https://github.com/haya14busa/errorformat)")
+ flag.StringVar(&ci, "ci", "", ciDoc)
+}
+
+func usage() {
+ fmt.Fprintln(os.Stderr, usageMessage)
+ fmt.Fprintln(os.Stderr, "Flags:")
+ flag.PrintDefaults()
+ os.Exit(2)
+}
+
+func main() {
+ flag.Usage = usage
+ flag.Parse()
+ if err := run(os.Stdin, os.Stdout, diffCmd, diffStrip, efms, ci); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+}
+
+func run(r io.Reader, w io.Writer, diffCmd string, diffStrip int, efms []string, ci string) error {
+ p, err := efmParser(efms)
+ if err != nil {
+ return err
+ }
+
+ var cs reviewdog.CommentService
+ var ds reviewdog.DiffService
+
+ if ci != "" {
+ if os.Getenv("WATCHDOGS_GITHUB_API_TOKEN") != "" {
+ gs, isPR, err := githubService(ci)
+ if err != nil {
+ return err
+ }
+ if !isPR {
+ fmt.Fprintf(os.Stderr, "this is not PullRequest build. CI: %v\n", ci)
+ return nil
+ }
+ cs = gs
+ ds = gs
+ } else {
+ fmt.Fprintf(os.Stderr, "WATCHDOGS_GITHUB_API_TOKEN is not set\n")
+ return nil
+ }
+ } else {
+ // local
+ cs = reviewdog.NewCommentWriter(w)
+ d, err := diffService(diffCmd, diffStrip)
+ if err != nil {
+ return err
+ }
+ ds = d
+ }
+
+ app := reviewdog.NewReviewdog(p, cs, ds)
+ if err := app.Run(r); err != nil {
+ return err
+ }
+ if fcs, ok := cs.(FlashCommentService); ok {
+ // Output log to writer
+ for _, c := range fcs.ListPostComments() {
+ fmt.Fprintln(w, strings.Join(c.Lines, "\n"))
+ }
+ return fcs.Flash()
+ }
+ return nil
+}
+
+// FlashCommentService is CommentService which uses Flash method to post comment.
+type FlashCommentService interface {
+ reviewdog.CommentService
+ ListPostComments() []*reviewdog.Comment
+ Flash() error
+}
+
+func efmParser(efms []string) (reviewdog.Parser, error) {
+ efm, err := errorformat.NewErrorformat(efms)
+ if err != nil {
+ return nil, err
+ }
+ return reviewdog.NewErrorformatParser(efm), nil
+}
+
+func diffService(s string, strip int) (reviewdog.DiffService, error) {
+ cmds, err := shellwords.Parse(s)
+ if err != nil {
+ return nil, err
+ }
+ if len(cmds) < 1 {
+ return nil, errors.New("diff command is empty")
+ }
+ cmd := exec.Command(cmds[0], cmds[1:]...)
+ d := reviewdog.NewDiffCmd(cmd, strip)
+ return d, nil
+}
+
+func githubService(ci string) (githubservice *reviewdog.GitHubPullRequest, isPR bool, err error) {
+ token, err := nonEmptyEnv("WATCHDOGS_GITHUB_API_TOKEN")
+ if err != nil {
+ return nil, false, err
+ }
+ var g *GitHubPR
+ switch ci {
+ case "travis":
+ g, isPR, err = travis()
+ case "circle-ci":
+ g, isPR, err = circleci()
+ case "droneio":
+ g, isPR, err = droneio()
+ case "common":
+ g, isPR, err = commonci()
+ default:
+ return nil, false, fmt.Errorf("unsupported CI: %v", ci)
+ }
+ if err != nil {
+ return nil, false, err
+ }
+ // TODO: support commit build
+ if !isPR {
+ return nil, false, nil
+ }
+ ts := oauth2.StaticTokenSource(
+ &oauth2.Token{AccessToken: token},
+ )
+ tc := oauth2.NewClient(oauth2.NoContext, ts)
+ client := github.NewClient(tc)
+ githubservice = reviewdog.NewGitHubPullReqest(client, g.owner, g.repo, g.pr, g.sha)
+ return githubservice, true, nil
+}
+
+func travis() (g *GitHubPR, isPR bool, err error) {
+ prs := os.Getenv("TRAVIS_PULL_REQUEST")
+ if prs == "false" {
+ return nil, false, nil
+ }
+ pr, err := strconv.Atoi(prs)
+ if err != nil {
+ return nil, true, fmt.Errorf("unexpected env variable. TRAVIS_PULL_REQUEST=%v", prs)
+ }
+ reposlug, err := nonEmptyEnv("TRAVIS_REPO_SLUG")
+ if err != nil {
+ return nil, true, err
+ }
+ rss := strings.SplitN(reposlug, "/", 2)
+ if len(rss) < 2 {
+ return nil, true, fmt.Errorf("unexpected env variable. TRAVIS_REPO_SLUG=%v", reposlug)
+ }
+ owner, repo := rss[0], rss[1]
+
+ sha, err := nonEmptyEnv("TRAVIS_PULL_REQUEST_SHA")
+ if err != nil {
+ return nil, true, err
+ }
+
+ g = &GitHubPR{
+ owner: owner,
+ repo: repo,
+ pr: pr,
+ sha: sha,
+ }
+ return g, true, nil
+}
+
+// https://circleci.com/docs/environment-variables/
+func circleci() (g *GitHubPR, isPR bool, err error) {
+ var prs string // pull request number in string
+ // For Pull Request from a same repository
+ // e.g. https: //github.com/haya14busa/reviewdog/pull/6
+ // it might be better to support CI_PULL_REQUESTS instead.
+ prs = os.Getenv("CI_PULL_REQUEST")
+ if prs == "" {
+ // For Pull Request by a fork repository
+ // e.g. 6
+ prs = os.Getenv("CIRCLE_PR_NUMBER")
+ }
+ if prs == "" {
+ // not a pull-request build
+ return nil, false, nil
+ }
+ // regexp.MustCompile() in func intentionally because this func is called
+ // once for one run.
+ re := regexp.MustCompile(`[1-9]\d*$`)
+ prm := re.FindString(prs)
+ pr, err := strconv.Atoi(prm)
+ if err != nil {
+ return nil, true, fmt.Errorf("unexpected env variable (CI_PULL_REQUEST or CIRCLE_PR_NUMBER): %v", prs)
+ }
+ owner, err := nonEmptyEnv("CIRCLE_PROJECT_USERNAME")
+ if err != nil {
+ return nil, true, err
+ }
+ repo, err := nonEmptyEnv("CIRCLE_PROJECT_REPONAME")
+ if err != nil {
+ return nil, true, err
+ }
+ sha, err := nonEmptyEnv("CIRCLE_SHA1")
+ if err != nil {
+ return nil, true, err
+ }
+ g = &GitHubPR{
+ owner: owner,
+ repo: repo,
+ pr: pr,
+ sha: sha,
+ }
+ return g, true, nil
+}
+
+// http://readme.drone.io/usage/variables/
+func droneio() (g *GitHubPR, isPR bool, err error) {
+ var prs string // pull request number in string
+ prs = os.Getenv("DRONE_PULL_REQUEST")
+ if prs == "" {
+ // not a pull-request build
+ return nil, false, nil
+ }
+ pr, err := strconv.Atoi(prs)
+ if err != nil {
+ return nil, true, fmt.Errorf("unexpected env variable (DRONE_PULL_REQUEST): %v", prs)
+ }
+ reposlug, err := nonEmptyEnv("DRONE_REPO") // e.g. haya14busa/reviewdog
+ if err != nil {
+ return nil, true, err
+ }
+ rss := strings.SplitN(reposlug, "/", 2)
+ if len(rss) < 2 {
+ return nil, true, fmt.Errorf("unexpected env variable. DRONE_REPO=%v", reposlug)
+ }
+ owner, repo := rss[0], rss[1]
+ sha, err := nonEmptyEnv("DRONE_COMMIT")
+ if err != nil {
+ return nil, true, err
+ }
+ g = &GitHubPR{
+ owner: owner,
+ repo: repo,
+ pr: pr,
+ sha: sha,
+ }
+ return g, true, nil
+}
+
+func commonci() (g *GitHubPR, isPR bool, err error) {
+ var prs string // pull request number in string
+ prs = os.Getenv("CI_PULL_REQUEST")
+ if prs == "" {
+ // not a pull-request build
+ return nil, false, nil
+ }
+ pr, err := strconv.Atoi(prs)
+ if err != nil {
+ return nil, true, fmt.Errorf("unexpected env variable (CI_PULL_REQUEST): %v", prs)
+ }
+ owner, err := nonEmptyEnv("CI_REPO_OWNER")
+ if err != nil {
+ return nil, true, err
+ }
+ repo, err := nonEmptyEnv("CI_REPO_NAME")
+ if err != nil {
+ return nil, true, err
+ }
+ sha, err := nonEmptyEnv("CI_COMMIT")
+ if err != nil {
+ return nil, true, err
+ }
+ g = &GitHubPR{
+ owner: owner,
+ repo: repo,
+ pr: pr,
+ sha: sha,
+ }
+ return g, true, nil
+}
+
+// GitHubPR represents required information about GitHub PullRequest.
+type GitHubPR struct {
+ owner string
+ repo string
+ pr int
+ sha string
+}
+
+func nonEmptyEnv(env string) (string, error) {
+ v := os.Getenv(env)
+ if v == "" {
+ return "", fmt.Errorf("environment variable $%v is not set", env)
+ }
+ return v, nil
+}
+
+type strslice []string
+
+func (ss *strslice) String() string {
+ return fmt.Sprintf("%v", *ss)
+}
+
+func (ss *strslice) Set(value string) error {
+ *ss = append(*ss, value)
+ return nil
+}
diff --git a/cmd/reviewdog/main_test.go b/cmd/reviewdog/main_test.go
new file mode 100644
index 0000000..0fa414b
--- /dev/null
+++ b/cmd/reviewdog/main_test.go
@@ -0,0 +1,338 @@
+package main
+
+import (
+ "bytes"
+ "os"
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func TestRun_travis(t *testing.T) {
+ envs := []string{
+ "WATCHDOGS_GITHUB_API_TOKEN",
+ "TRAVIS_PULL_REQUEST",
+ "TRAVIS_REPO_SLUG",
+ "TRAVIS_PULL_REQUEST_SHA",
+ }
+ // save and clean
+ saveEnvs := make(map[string]string)
+ for _, key := range envs {
+ saveEnvs[key] = os.Getenv(key)
+ os.Setenv(key, "")
+ }
+ // restore
+ defer func() {
+ for key, value := range saveEnvs {
+ os.Setenv(key, value)
+ }
+ }()
+
+ if err := run(nil, nil, "", 0, nil, "ciname"); err != nil {
+ t.Errorf("got an unexpected error: %v", err)
+ }
+
+ os.Setenv("WATCHDOGS_GITHUB_API_TOKEN", "<WATCHDOGS_GITHUB_API_TOKEN>")
+
+ if err := run(nil, nil, "", 0, nil, "unsupported ci"); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+
+ if err := run(nil, nil, "", 0, nil, "travis"); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+
+ os.Setenv("TRAVIS_PULL_REQUEST", "str")
+
+ if err := run(nil, nil, "", 0, nil, "travis"); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+
+ os.Setenv("TRAVIS_PULL_REQUEST", "1")
+
+ if err := run(nil, nil, "", 0, nil, "travis"); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+
+ os.Setenv("TRAVIS_REPO_SLUG", "invalid repo slug")
+
+ if err := run(nil, nil, "", 0, nil, "travis"); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+
+ os.Setenv("TRAVIS_REPO_SLUG", "haya14busa/reviewdog")
+
+ if err := run(nil, nil, "", 0, nil, "travis"); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+
+ os.Setenv("TRAVIS_PULL_REQUEST_SHA", "sha")
+
+ r := strings.NewReader("compiler result")
+
+ if err := run(r, new(bytes.Buffer), "git diff", 0, nil, "travis"); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+
+ buf := new(bytes.Buffer)
+ os.Setenv("TRAVIS_PULL_REQUEST", "false")
+ if err := run(r, buf, "", 0, nil, "travis"); err != nil {
+ t.Error(err)
+ } else {
+ t.Log(buf.String())
+ }
+
+}
+
+func TestCircleci(t *testing.T) {
+ envs := []string{
+ "CI_PULL_REQUEST",
+ "CIRCLE_PR_NUMBER",
+ "CIRCLE_PROJECT_USERNAME",
+ "CIRCLE_PROJECT_REPONAME",
+ "CIRCLE_SHA1",
+ "WATCHDOGS_GITHUB_API_TOKEN",
+ }
+ // save and clean
+ saveEnvs := make(map[string]string)
+ for _, key := range envs {
+ saveEnvs[key] = os.Getenv(key)
+ os.Setenv(key, "")
+ }
+ // restore
+ defer func() {
+ for key, value := range saveEnvs {
+ os.Setenv(key, value)
+ }
+ }()
+
+ if _, isPR, err := circleci(); isPR {
+ t.Errorf("should be non pull-request build. error: %v", err)
+ }
+
+ os.Setenv("CI_PULL_REQUEST", "invalid")
+ if _, _, err := circleci(); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+
+ os.Setenv("CI_PULL_REQUEST", "")
+ os.Setenv("CIRCLE_PR_NUMBER", "invalid")
+ if _, _, err := circleci(); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+
+ os.Setenv("CIRCLE_PR_NUMBER", "1")
+ if _, _, err := circleci(); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+
+ os.Setenv("CIRCLE_PROJECT_USERNAME", "haya14busa")
+ if _, _, err := circleci(); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+
+ os.Setenv("CIRCLE_PROJECT_REPONAME", "reviewdog")
+ if _, _, err := circleci(); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+
+ os.Setenv("CIRCLE_SHA1", "sha1")
+ g, isPR, err := circleci()
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ if !isPR {
+ t.Error("should be pull request build")
+ }
+ want := &GitHubPR{
+ owner: "haya14busa",
+ repo: "reviewdog",
+ pr: 1,
+ sha: "sha1",
+ }
+ if !reflect.DeepEqual(g, want) {
+ t.Errorf("got: %#v, want: %#v", g, want)
+ }
+
+ os.Setenv("WATCHDOGS_GITHUB_API_TOKEN", "<WATCHDOGS_GITHUB_API_TOKEN>")
+ if err := run(strings.NewReader("compiler result"), new(bytes.Buffer), "", 0, nil, "circle-ci"); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+}
+
+func TestDroneio(t *testing.T) {
+ envs := []string{
+ "DRONE_PULL_REQUEST",
+ "DRONE_REPO",
+ "DRONE_COMMIT",
+ "WATCHDOGS_GITHUB_API_TOKEN",
+ }
+ // save and clean
+ saveEnvs := make(map[string]string)
+ for _, key := range envs {
+ saveEnvs[key] = os.Getenv(key)
+ os.Setenv(key, "")
+ }
+ // restore
+ defer func() {
+ for key, value := range saveEnvs {
+ os.Setenv(key, value)
+ }
+ }()
+
+ if _, isPR, err := droneio(); isPR {
+ t.Errorf("should be non pull-request build. error: %v", err)
+ }
+
+ os.Setenv("DRONE_PULL_REQUEST", "invalid")
+ if _, _, err := droneio(); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+
+ os.Setenv("DRONE_PULL_REQUEST", "1")
+ if _, _, err := droneio(); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+
+ os.Setenv("DRONE_REPO", "invalid")
+ if _, _, err := droneio(); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+
+ os.Setenv("DRONE_REPO", "haya14busa/reviewdog")
+ if _, _, err := droneio(); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+
+ os.Setenv("DRONE_COMMIT", "sha1")
+ g, isPR, err := droneio()
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ if !isPR {
+ t.Error("should be pull request build")
+ }
+ want := &GitHubPR{
+ owner: "haya14busa",
+ repo: "reviewdog",
+ pr: 1,
+ sha: "sha1",
+ }
+ if !reflect.DeepEqual(g, want) {
+ t.Errorf("got: %#v, want: %#v", g, want)
+ }
+
+ os.Setenv("WATCHDOGS_GITHUB_API_TOKEN", "<WATCHDOGS_GITHUB_API_TOKEN>")
+ if err := run(strings.NewReader("compiler result"), new(bytes.Buffer), "", 0, nil, "droneio"); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+}
+
+func TestCommonci(t *testing.T) {
+ envs := []string{
+ "CI_PULL_REQUEST",
+ "CI_COMMIT",
+ "CI_REPO_OWNER",
+ "CI_REPO_NAME",
+ "WATCHDOGS_GITHUB_API_TOKEN",
+ }
+ // save and clean
+ saveEnvs := make(map[string]string)
+ for _, key := range envs {
+ saveEnvs[key] = os.Getenv(key)
+ os.Setenv(key, "")
+ }
+ // restore
+ defer func() {
+ for key, value := range saveEnvs {
+ os.Setenv(key, value)
+ }
+ }()
+
+ if _, isPR, err := commonci(); isPR {
+ t.Errorf("should be non pull-request build. error: %v", err)
+ }
+
+ os.Setenv("CI_PULL_REQUEST", "invalid")
+ if _, _, err := commonci(); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+
+ os.Setenv("CI_PULL_REQUEST", "1")
+ if _, _, err := commonci(); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+
+ os.Setenv("CI_REPO_OWNER", "haya14busa")
+ if _, _, err := commonci(); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+
+ os.Setenv("CI_REPO_NAME", "reviewdog")
+ if _, _, err := commonci(); err == nil {
+ t.Error("error expected but got nil")
+ } else {
+ t.Log(err)
+ }
+
+ os.Setenv("CI_COMMIT", "sha1")
+ g, isPR, err := commonci()
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ if !isPR {
+ t.Error("should be pull request build")
+ }
+ want := &GitHubPR{
+ owner: "haya14busa",
+ repo: "reviewdog",
+ pr: 1,
+ sha: "sha1",
+ }
+ if !reflect.DeepEqual(g, want) {
+ t.Errorf("got: %#v, want: %#v", g, want)
+ }
+
+}
diff --git a/cmd/watchdogs/.gitignore b/cmd/watchdogs/.gitignore
deleted file mode 100644
index 6962121..0000000
--- a/cmd/watchdogs/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-watchdogs
diff --git a/cmd/watchdogs/main.go b/cmd/watchdogs/main.go
deleted file mode 100644
index a5cb131..0000000
--- a/cmd/watchdogs/main.go
+++ /dev/null
@@ -1,354 +0,0 @@
-package main
-
-import (
- "errors"
- "flag"
- "fmt"
- "io"
- "os"
- "os/exec"
- "regexp"
- "strconv"
- "strings"
-
- "golang.org/x/oauth2"
-
- "github.com/google/go-github/github"
- "github.com/haya14busa/errorformat"
- "github.com/haya14busa/watchdogs"
- "github.com/mattn/go-shellwords"
-)
-
-const usageMessage = "" +
- `Usage: watchdogs [flags]
- watchdogs accepts any compiler or linter results from stdin and filters
- them by diff for review. watchdogs also can posts the results as a comment to
- GitHub if you use watchdogs in CI service.
-`
-
-// flags
-var (
- diffCmd string
- diffCmdDoc = `diff command (e.g. "git diff"). diff flag is ignored if you pass "ci" flag`
-
- diffStrip int
- efms strslice
-
- ci string
- ciDoc = `CI service (supported travis, circle-ci, droneio(OSS 0.4), common)
- If you use "ci" flag, you need to set WATCHDOGS_GITHUB_API_TOKEN environment
- variable. Go to https://github.com/settings/tokens and create new Personal
- access token with repo scope.
-
- "common" requires following environment variables
- CI_PULL_REQUEST Pull Request number (e.g. 14)
- CI_COMMIT SHA1 for the current build
- CI_REPO_OWNER repository owner (e.g. "haya14busa" for https://github.com/haya14busa/watchdogs)
- CI_REPO_NAME repository name (e.g. "watchdogs" for https://github.com/haya14busa/watchdogs)
-`
-)
-
-func init() {
- flag.StringVar(&diffCmd, "diff", "", diffCmdDoc)
- flag.IntVar(&diffStrip, "strip", 1, "strip NUM leading components from diff file names (equivalent to `patch -p`) (default is 1 for git diff)")
- flag.Var(&efms, "efm", "list of errorformat (https://github.com/haya14busa/errorformat)")
- flag.StringVar(&ci, "ci", "", ciDoc)
-}
-
-func usage() {
- fmt.Fprintln(os.Stderr, usageMessage)
- fmt.Fprintln(os.Stderr, "Flags:")
- flag.PrintDefaults()
- os.Exit(2)
-}
-
-func main() {
- flag.Usage = usage
- flag.Parse()
- if err := run(os.Stdin, os.Stdout, diffCmd, diffStrip, efms, ci); err != nil {
- fmt.Fprintln(os.Stderr, err)
- os.Exit(1)
- }
-}
-
-func run(r io.Reader, w io.Writer, diffCmd string, diffStrip int, efms []string, ci string) error {
- p, err := efmParser(efms)
- if err != nil {
- return err
- }
-
- var cs watchdogs.CommentService
- var ds watchdogs.DiffService
-
- if ci != "" {
- if os.Getenv("WATCHDOGS_GITHUB_API_TOKEN") != "" {
- gs, isPR, err := githubService(ci)
- if err != nil {
- return err
- }
- if !isPR {
- fmt.Fprintf(os.Stderr, "this is not PullRequest build. CI: %v\n", ci)
- return nil
- }
- cs = gs
- ds = gs
- } else {
- fmt.Fprintf(os.Stderr, "WATCHDOGS_GITHUB_API_TOKEN is not set\n")
- return nil
- }
- } else {
- // local
- cs = watchdogs.NewCommentWriter(w)
- d, err := diffService(diffCmd, diffStrip)
- if err != nil {
- return err
- }
- ds = d
- }
-
- app := watchdogs.NewWatchdogs(p, cs, ds)
- if err := app.Run(r); err != nil {
- return err
- }
- if fcs, ok := cs.(FlashCommentService); ok {
- // Output log to writer
- for _, c := range fcs.ListPostComments() {
- fmt.Fprintln(w, strings.Join(c.Lines, "\n"))
- }
- return fcs.Flash()
- }
- return nil
-}
-
-// FlashCommentService is CommentService which uses Flash method to post comment.
-type FlashCommentService interface {
- watchdogs.CommentService
- ListPostComments() []*watchdogs.Comment
- Flash() error
-}
-
-func efmParser(efms []string) (watchdogs.Parser, error) {
- efm, err := errorformat.NewErrorformat(efms)
- if err != nil {
- return nil, err
- }
- return watchdogs.NewErrorformatParser(efm), nil
-}
-
-func diffService(s string, strip int) (watchdogs.DiffService, error) {
- cmds, err := shellwords.Parse(s)
- if err != nil {
- return nil, err
- }
- if len(cmds) < 1 {
- return nil, errors.New("diff command is empty")
- }
- cmd := exec.Command(cmds[0], cmds[1:]...)
- d := watchdogs.NewDiffCmd(cmd, strip)
- return d, nil
-}
-
-func githubService(ci string) (githubservice *watchdogs.GitHubPullRequest, isPR bool, err error) {
- token, err := nonEmptyEnv("WATCHDOGS_GITHUB_API_TOKEN")
- if err != nil {
- return nil, false, err
- }
- var g *GitHubPR
- switch ci {
- case "travis":
- g, isPR, err = travis()
- case "circle-ci":
- g, isPR, err = circleci()
- case "droneio":
- g, isPR, err = droneio()
- case "common":
- g, isPR, err = commonci()
- default:
- return nil, false, fmt.Errorf("unsupported CI: %v", ci)
- }
- if err != nil {
- return nil, false, err
- }
- // TODO: support commit build
- if !isPR {
- return nil, false, nil
- }
- ts := oauth2.StaticTokenSource(
- &oauth2.Token{AccessToken: token},
- )
- tc := oauth2.NewClient(oauth2.NoContext, ts)
- client := github.NewClient(tc)
- githubservice = watchdogs.NewGitHubPullReqest(client, g.owner, g.repo, g.pr, g.sha)
- return githubservice, true, nil
-}
-
-func travis() (g *GitHubPR, isPR bool, err error) {
- prs := os.Getenv("TRAVIS_PULL_REQUEST")
- if prs == "false" {
- return nil, false, nil
- }
- pr, err := strconv.Atoi(prs)
- if err != nil {
- return nil, true, fmt.Errorf("unexpected env variable. TRAVIS_PULL_REQUEST=%v", prs)
- }
- reposlug, err := nonEmptyEnv("TRAVIS_REPO_SLUG")
- if err != nil {
- return nil, true, err
- }
- rss := strings.SplitN(reposlug, "/", 2)
- if len(rss) < 2 {
- return nil, true, fmt.Errorf("unexpected env variable. TRAVIS_REPO_SLUG=%v", reposlug)
- }
- owner, repo := rss[0], rss[1]
-
- sha, err := nonEmptyEnv("TRAVIS_PULL_REQUEST_SHA")
- if err != nil {
- return nil, true, err
- }
-
- g = &GitHubPR{
- owner: owner,
- repo: repo,
- pr: pr,
- sha: sha,
- }
- return g, true, nil
-}
-
-// https://circleci.com/docs/environment-variables/
-func circleci() (g *GitHubPR, isPR bool, err error) {
- var prs string // pull request number in string
- // For Pull Request from a same repository
- // e.g. https: //github.com/haya14busa/watchdogs/pull/6
- // it might be better to support CI_PULL_REQUESTS instead.
- prs = os.Getenv("CI_PULL_REQUEST")
- if prs == "" {
- // For Pull Request by a fork repository
- // e.g. 6
- prs = os.Getenv("CIRCLE_PR_NUMBER")
- }
- if prs == "" {
- // not a pull-request build
- return nil, false, nil
- }
- // regexp.MustCompile() in func intentionally because this func is called
- // once for one run.
- re := regexp.MustCompile(`[1-9]\d*$`)
- prm := re.FindString(prs)
- pr, err := strconv.Atoi(prm)
- if err != nil {
- return nil, true, fmt.Errorf("unexpected env variable (CI_PULL_REQUEST or CIRCLE_PR_NUMBER): %v", prs)
- }
- owner, err := nonEmptyEnv("CIRCLE_PROJECT_USERNAME")
- if err != nil {
- return nil, true, err
- }
- repo, err := nonEmptyEnv("CIRCLE_PROJECT_REPONAME")
- if err != nil {
- return nil, true, err
- }
- sha, err := nonEmptyEnv("CIRCLE_SHA1")
- if err != nil {
- return nil, true, err
- }
- g = &GitHubPR{
- owner: owner,
- repo: repo,
- pr: pr,
- sha: sha,
- }
- return g, true, nil
-}
-
-// http://readme.drone.io/usage/variables/
-func droneio() (g *GitHubPR, isPR bool, err error) {
- var prs string // pull request number in string
- prs = os.Getenv("DRONE_PULL_REQUEST")
- if prs == "" {
- // not a pull-request build
- return nil, false, nil
- }
- pr, err := strconv.Atoi(prs)
- if err != nil {
- return nil, true, fmt.Errorf("unexpected env variable (DRONE_PULL_REQUEST): %v", prs)
- }
- reposlug, err := nonEmptyEnv("DRONE_REPO") // e.g. haya14busa/watchdogs
- if err != nil {
- return nil, true, err
- }
- rss := strings.SplitN(reposlug, "/", 2)
- if len(rss) < 2 {
- return nil, true, fmt.Errorf("unexpected env variable. DRONE_REPO=%v", reposlug)
- }
- owner, repo := rss[0], rss[1]
- sha, err := nonEmptyEnv("DRONE_COMMIT")
- if err != nil {
- return nil, true, err
- }
- g = &GitHubPR{
- owner: owner,
- repo: repo,
- pr: pr,
- sha: sha,
- }
- return g, true, nil
-}
-
-func commonci() (g *GitHubPR, isPR bool, err error) {
- var prs string // pull request number in string
- prs = os.Getenv("CI_PULL_REQUEST")
- if prs == "" {
- // not a pull-request build
- return nil, false, nil
- }
- pr, err := strconv.Atoi(prs)
- if err != nil {
- return nil, true, fmt.Errorf("unexpected env variable (CI_PULL_REQUEST): %v", prs)
- }
- owner, err := nonEmptyEnv("CI_REPO_OWNER")
- if err != nil {
- return nil, true, err
- }
- repo, err := nonEmptyEnv("CI_REPO_NAME")
- if err != nil {
- return nil, true, err
- }
- sha, err := nonEmptyEnv("CI_COMMIT")
- if err != nil {
- return nil, true, err
- }
- g = &GitHubPR{
- owner: owner,
- repo: repo,
- pr: pr,
- sha: sha,
- }
- return g, true, nil
-}
-
-// GitHubPR represents required information about GitHub PullRequest.
-type GitHubPR struct {
- owner string
- repo string
- pr int
- sha string
-}
-
-func nonEmptyEnv(env string) (string, error) {
- v := os.Getenv(env)
- if v == "" {
- return "", fmt.Errorf("environment variable $%v is not set", env)
- }
- return v, nil
-}
-
-type strslice []string
-
-func (ss *strslice) String() string {
- return fmt.Sprintf("%v", *ss)
-}
-
-func (ss *strslice) Set(value string) error {
- *ss = append(*ss, value)
- return nil
-}
diff --git a/cmd/watchdogs/main_test.go b/cmd/watchdogs/main_test.go
deleted file mode 100644
index 794b91b..0000000
--- a/cmd/watchdogs/main_test.go
+++ /dev/null
@@ -1,338 +0,0 @@
-package main
-
-import (
- "bytes"
- "os"
- "reflect"
- "strings"
- "testing"
-)
-
-func TestRun_travis(t *testing.T) {
- envs := []string{
- "WATCHDOGS_GITHUB_API_TOKEN",
- "TRAVIS_PULL_REQUEST",
- "TRAVIS_REPO_SLUG",
- "TRAVIS_PULL_REQUEST_SHA",
- }
- // save and clean
- saveEnvs := make(map[string]string)
- for _, key := range envs {
- saveEnvs[key] = os.Getenv(key)
- os.Setenv(key, "")
- }
- // restore
- defer func() {
- for key, value := range saveEnvs {
- os.Setenv(key, value)
- }
- }()
-
- if err := run(nil, nil, "", 0, nil, "ciname"); err != nil {
- t.Errorf("got an unexpected error: %v", err)
- }
-
- os.Setenv("WATCHDOGS_GITHUB_API_TOKEN", "<WATCHDOGS_GITHUB_API_TOKEN>")
-
- if err := run(nil, nil, "", 0, nil, "unsupported ci"); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-
- if err := run(nil, nil, "", 0, nil, "travis"); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-
- os.Setenv("TRAVIS_PULL_REQUEST", "str")
-
- if err := run(nil, nil, "", 0, nil, "travis"); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-
- os.Setenv("TRAVIS_PULL_REQUEST", "1")
-
- if err := run(nil, nil, "", 0, nil, "travis"); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-
- os.Setenv("TRAVIS_REPO_SLUG", "invalid repo slug")
-
- if err := run(nil, nil, "", 0, nil, "travis"); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-
- os.Setenv("TRAVIS_REPO_SLUG", "haya14busa/watchdogs")
-
- if err := run(nil, nil, "", 0, nil, "travis"); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-
- os.Setenv("TRAVIS_PULL_REQUEST_SHA", "sha")
-
- r := strings.NewReader("compiler result")
-
- if err := run(r, new(bytes.Buffer), "git diff", 0, nil, "travis"); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-
- buf := new(bytes.Buffer)
- os.Setenv("TRAVIS_PULL_REQUEST", "false")
- if err := run(r, buf, "", 0, nil, "travis"); err != nil {
- t.Error(err)
- } else {
- t.Log(buf.String())
- }
-
-}
-
-func TestCircleci(t *testing.T) {
- envs := []string{
- "CI_PULL_REQUEST",
- "CIRCLE_PR_NUMBER",
- "CIRCLE_PROJECT_USERNAME",
- "CIRCLE_PROJECT_REPONAME",
- "CIRCLE_SHA1",
- "WATCHDOGS_GITHUB_API_TOKEN",
- }
- // save and clean
- saveEnvs := make(map[string]string)
- for _, key := range envs {
- saveEnvs[key] = os.Getenv(key)
- os.Setenv(key, "")
- }
- // restore
- defer func() {
- for key, value := range saveEnvs {
- os.Setenv(key, value)
- }
- }()
-
- if _, isPR, err := circleci(); isPR {
- t.Errorf("should be non pull-request build. error: %v", err)
- }
-
- os.Setenv("CI_PULL_REQUEST", "invalid")
- if _, _, err := circleci(); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-
- os.Setenv("CI_PULL_REQUEST", "")
- os.Setenv("CIRCLE_PR_NUMBER", "invalid")
- if _, _, err := circleci(); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-
- os.Setenv("CIRCLE_PR_NUMBER", "1")
- if _, _, err := circleci(); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-
- os.Setenv("CIRCLE_PROJECT_USERNAME", "haya14busa")
- if _, _, err := circleci(); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-
- os.Setenv("CIRCLE_PROJECT_REPONAME", "watchdogs")
- if _, _, err := circleci(); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-
- os.Setenv("CIRCLE_SHA1", "sha1")
- g, isPR, err := circleci()
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- }
- if !isPR {
- t.Error("should be pull request build")
- }
- want := &GitHubPR{
- owner: "haya14busa",
- repo: "watchdogs",
- pr: 1,
- sha: "sha1",
- }
- if !reflect.DeepEqual(g, want) {
- t.Errorf("got: %#v, want: %#v", g, want)
- }
-
- os.Setenv("WATCHDOGS_GITHUB_API_TOKEN", "<WATCHDOGS_GITHUB_API_TOKEN>")
- if err := run(strings.NewReader("compiler result"), new(bytes.Buffer), "", 0, nil, "circle-ci"); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-}
-
-func TestDroneio(t *testing.T) {
- envs := []string{
- "DRONE_PULL_REQUEST",
- "DRONE_REPO",
- "DRONE_COMMIT",
- "WATCHDOGS_GITHUB_API_TOKEN",
- }
- // save and clean
- saveEnvs := make(map[string]string)
- for _, key := range envs {
- saveEnvs[key] = os.Getenv(key)
- os.Setenv(key, "")
- }
- // restore
- defer func() {
- for key, value := range saveEnvs {
- os.Setenv(key, value)
- }
- }()
-
- if _, isPR, err := droneio(); isPR {
- t.Errorf("should be non pull-request build. error: %v", err)
- }
-
- os.Setenv("DRONE_PULL_REQUEST", "invalid")
- if _, _, err := droneio(); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-
- os.Setenv("DRONE_PULL_REQUEST", "1")
- if _, _, err := droneio(); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-
- os.Setenv("DRONE_REPO", "invalid")
- if _, _, err := droneio(); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-
- os.Setenv("DRONE_REPO", "haya14busa/watchdogs")
- if _, _, err := droneio(); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-
- os.Setenv("DRONE_COMMIT", "sha1")
- g, isPR, err := droneio()
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- }
- if !isPR {
- t.Error("should be pull request build")
- }
- want := &GitHubPR{
- owner: "haya14busa",
- repo: "watchdogs",
- pr: 1,
- sha: "sha1",
- }
- if !reflect.DeepEqual(g, want) {
- t.Errorf("got: %#v, want: %#v", g, want)
- }
-
- os.Setenv("WATCHDOGS_GITHUB_API_TOKEN", "<WATCHDOGS_GITHUB_API_TOKEN>")
- if err := run(strings.NewReader("compiler result"), new(bytes.Buffer), "", 0, nil, "droneio"); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-}
-
-func TestCommonci(t *testing.T) {
- envs := []string{
- "CI_PULL_REQUEST",
- "CI_COMMIT",
- "CI_REPO_OWNER",
- "CI_REPO_NAME",
- "WATCHDOGS_GITHUB_API_TOKEN",
- }
- // save and clean
- saveEnvs := make(map[string]string)
- for _, key := range envs {
- saveEnvs[key] = os.Getenv(key)
- os.Setenv(key, "")
- }
- // restore
- defer func() {
- for key, value := range saveEnvs {
- os.Setenv(key, value)
- }
- }()
-
- if _, isPR, err := commonci(); isPR {
- t.Errorf("should be non pull-request build. error: %v", err)
- }
-
- os.Setenv("CI_PULL_REQUEST", "invalid")
- if _, _, err := commonci(); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-
- os.Setenv("CI_PULL_REQUEST", "1")
- if _, _, err := commonci(); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-
- os.Setenv("CI_REPO_OWNER", "haya14busa")
- if _, _, err := commonci(); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-
- os.Setenv("CI_REPO_NAME", "watchdogs")
- if _, _, err := commonci(); err == nil {
- t.Error("error expected but got nil")
- } else {
- t.Log(err)
- }
-
- os.Setenv("CI_COMMIT", "sha1")
- g, isPR, err := commonci()
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- }
- if !isPR {
- t.Error("should be pull request build")
- }
- want := &GitHubPR{
- owner: "haya14busa",
- repo: "watchdogs",
- pr: 1,
- sha: "sha1",
- }
- if !reflect.DeepEqual(g, want) {
- t.Errorf("got: %#v, want: %#v", g, want)
- }
-
-}
diff --git a/comment_iowriter.go b/comment_iowriter.go
index 18bf7db..b18d17c 100644
--- a/comment_iowriter.go
+++ b/comment_iowriter.go
@@ -1,4 +1,4 @@
-package watchdogs
+package reviewdog
import (
"fmt"
diff --git a/diff.go b/diff.go
index b380b67..9b714ba 100644
--- a/diff.go
+++ b/diff.go
@@ -1,4 +1,4 @@
-package watchdogs
+package reviewdog
import (
"os/exec"
diff --git a/diff/parse.go b/diff/parse.go
index 6d08b49..5a453a9 100644
--- a/diff/parse.go
+++ b/diff/parse.go
@@ -178,10 +178,7 @@ func (p *hunkParser) Parse() (*Hunk, error) {
lold := hr.lold
lnew := hr.lnew
endhunk:
- for {
- if b, err := p.r.Peek(3); err != nil || string(b) == tokenOldFile {
- break
- }
+ for !p.done(lold, lnew, hr) {
b, err := p.r.Peek(1)
if err != nil {
break
@@ -223,6 +220,14 @@ endhunk:
return hunk, nil
}
+func (p *hunkParser) done(lold, lnew int, hr *hunkrange) bool {
+ end := (lold >= hr.lold+hr.sold && lnew >= hr.lnew+hr.snew)
+ if b, err := p.r.Peek(1); err != nil || (string(b) != tokenNoNewlineAtEOF && end) {
+ return true
+ }
+ return false
+}
+
// @@ -l,s +l,s @@ optional section heading
type hunkrange struct {
lold, sold, lnew, snew int
diff --git a/diff_test.go b/diff_test.go
index 4f1f5f8..146994c 100644
--- a/diff_test.go
+++ b/diff_test.go
@@ -1,4 +1,4 @@
-package watchdogs
+package reviewdog
import "io/ioutil"
diff --git a/github.go b/github.go
index e9ffee0..360f36b 100644
--- a/github.go
+++ b/github.go
@@ -1,7 +1,7 @@
-package watchdogs
+package reviewdog
import (
- "bytes"
+ "os/exec"
"github.com/google/go-github/github"
"golang.org/x/sync/errgroup"
@@ -74,7 +74,7 @@ func (g *GitHubPullRequest) ListPostComments() []*Comment {
return g.postComments
}
-const bodyPrefix = `:dog: [[watchdogs](https://github.com/haya14busa/watchdogs)] :dog:`
+const bodyPrefix = `:dog: [[reviewdog](https://github.com/haya14busa/reviewdog)] :dog:`
func commentBody(c *Comment) string {
return bodyPrefix + "\n" + c.Body
@@ -132,21 +132,17 @@ func (g *GitHubPullRequest) setPostedComment() error {
return nil
}
-// Diff returns a diff of PullRequest.
+// Diff returns a diff of PullRequest. It runs `git diff` locally instead of
+// diff_url of GitHub Pull Request because diff of diff_url is not suited for
+// comment API in a sense that diff of diff_url is equivalent to
+// `git diff --no-renames`, we want diff which is equivalent to
+// `git diff --find-renames`.
func (g *GitHubPullRequest) Diff() ([]byte, error) {
pr, _, err := g.cli.PullRequests.Get(g.owner, g.repo, g.pr)
if err != nil {
return nil, err
}
- req, err := g.cli.NewRequest("GET", *pr.DiffURL, nil)
- if err != nil {
- return nil, err
- }
- buf := new(bytes.Buffer)
- if _, err := g.cli.Do(req, buf); err != nil {
- return nil, err
- }
- return buf.Bytes(), nil
+ return exec.Command("git", "diff", "--find-renames", *pr.Base.SHA, g.sha).Output()
}
// Strip returns 1 as a strip of git diff.
diff --git a/github_test.go b/github_test.go
index 1bbe232..e100637 100644
--- a/github_test.go
+++ b/github_test.go
@@ -1,4 +1,4 @@
-package watchdogs
+package reviewdog
import (
"os"
@@ -29,21 +29,21 @@ func TestGitHubPullRequest_Post(t *testing.T) {
t.Skip(notokenSkipTestMes)
}
- // https://github.com/haya14busa/watchdogs/pull/2
+ // https://github.com/haya14busa/reviewdog/pull/2
owner := "haya14busa"
- repo := "watchdogs"
+ repo := "reviewdog"
pr := 2
sha := "cce89afa9ac5519a7f5b1734db2e3aa776b138a7"
g := NewGitHubPullReqest(client, owner, repo, pr, sha)
comment := &Comment{
CheckResult: &CheckResult{
- Path: "watchdogs.go",
+ Path: "reviewdog.go",
},
LnumDiff: 17,
- Body: "[watchdogs] test",
+ Body: "[reviewdog] test",
}
- // https://github.com/haya14busa/watchdogs/pull/2/files#diff-ed1d019a10f54464cfaeaf6a736b7d27L20
+ // https://github.com/haya14busa/reviewdog/pull/2/files#diff-ed1d019a10f54464cfaeaf6a736b7d27L20
if err := g.Post(comment); err != nil {
t.Error(err)
}
@@ -75,28 +75,28 @@ index b380b67..6abc0f1 100644
var _ DiffService = &DiffString{}
type DiffString struct {
-diff --git a/watchdogs.go b/watchdogs.go
+diff --git a/reviewdog.go b/reviewdog.go
index 61450f3..f63f149 100644
---- a/watchdogs.go
-+++ b/watchdogs.go
+--- a/reviewdog.go
++++ b/reviewdog.go
@@ -10,18 +10,18 @@ import (
- "github.com/haya14busa/watchdogs/diff"
+ "github.com/haya14busa/reviewdog/diff"
)
+var TestExportedVarWithoutComment = 1
+
-+func NewWatchdogs(p Parser, c CommentService, d DiffService) *Watchdogs {
-+ return &Watchdogs{p: p, c: c, d: d}
++func NewReviewdog(p Parser, c CommentService, d DiffService) *Reviewdog {
++ return &Reviewdog{p: p, c: c, d: d}
+}
+
- type Watchdogs struct {
+ type Reviewdog struct {
p Parser
c CommentService
d DiffService
}
--func NewWatchdogs(p Parser, c CommentService, d DiffService) *Watchdogs {
-- return &Watchdogs{p: p, c: c, d: d}
+-func NewReviewdog(p Parser, c CommentService, d DiffService) *Reviewdog {
+- return &Reviewdog{p: p, c: c, d: d}
-}
-
-// CheckResult represents a checked result of static analysis tools.
@@ -106,9 +106,9 @@ index 61450f3..f63f149 100644
Lnum int // line number
`
- // https://github.com/haya14busa/watchdogs/pull/2
+ // https://github.com/haya14busa/reviewdog/pull/2
owner := "haya14busa"
- repo := "watchdogs"
+ repo := "reviewdog"
pr := 2
g := NewGitHubPullReqest(client, owner, repo, pr, "")
b, err := g.Diff()
@@ -128,9 +128,9 @@ func TestGitHubPullRequest_comment(t *testing.T) {
if client == nil {
t.Skip(notokenSkipTestMes)
}
- // https://github.com/haya14busa/watchdogs/pull/2
+ // https://github.com/haya14busa/reviewdog/pull/2
owner := "haya14busa"
- repo := "watchdogs"
+ repo := "reviewdog"
pr := 2
g := NewGitHubPullReqest(client, owner, repo, pr, "")
comments, err := g.comment()
diff --git a/parser.go b/parser.go
index 13382a2..d409adf 100644
--- a/parser.go
+++ b/parser.go
@@ -1,4 +1,4 @@
-package watchdogs
+package reviewdog
import (
"io"
diff --git a/reviewdog.go b/reviewdog.go
new file mode 100644
index 0000000..ae92e5e
--- /dev/null
+++ b/reviewdog.go
@@ -0,0 +1,177 @@
+package reviewdog
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/haya14busa/reviewdog/diff"
+)
+
+// Reviewdog represents review dog application which parses result of compiler
+// or linter, get diff and filter the results by diff, and report filterd
+// results.
+type Reviewdog struct {
+ p Parser
+ c CommentService
+ d DiffService
+}
+
+// NewReviewdog returns a new Reviewdog.
+func NewReviewdog(p Parser, c CommentService, d DiffService) *Reviewdog {
+ return &Reviewdog{p: p, c: c, d: d}
+}
+
+// CheckResult represents a checked result of static analysis tools.
+// :h error-file-format
+type CheckResult struct {
+ Path string // relative file path
+ Lnum int // line number
+ Col int // column number (1 <tab> == 1 character column)
+ Message string // error message
+ Lines []string // Original error lines (often one line)
+}
+
+// Parser is an interface which parses compilers, linters, or any tools
+// results.
+type Parser interface {
+ Parse(r io.Reader) ([]*CheckResult, error)
+}
+
+// Comment represents a reported result as a comment.
+type Comment struct {
+ *CheckResult
+ Body string
+ LnumDiff int
+}
+
+// CommentService is an interface which posts Comment.
+type CommentService interface {
+ Post(*Comment) error
+}
+
+// DiffService is an interface which get diff.
+type DiffService interface {
+ Diff() ([]byte, error)
+ Strip() int
+}
+
+// Run runs Reviewdog application.
+func (w *Reviewdog) Run(r io.Reader) error {
+ results, err := w.p.Parse(r)
+ if err != nil {
+ return fmt.Errorf("parse error: %v", err)
+ }
+
+ d, err := w.d.Diff()
+ if err != nil {
+ return fmt.Errorf("fail to get diff: %v", err)
+ }
+
+ filediffs, err := diff.ParseMultiFile(bytes.NewReader(d))
+ if err != nil {
+ return fmt.Errorf("fail to parse diff: %v", err)
+ }
+ addedlines := addedDiffLines(filediffs, w.d.Strip())
+
+ wd, err := os.Getwd()
+ if err != nil {
+ return err
+ }
+
+ for _, result := range results {
+ addedline := addedlines.Get(result.Path, result.Lnum)
+ if filepath.IsAbs(result.Path) {
+ relpath, err := filepath.Rel(wd, result.Path)
+ if err != nil {
+ return err
+ }
+ result.Path = relpath
+ }
+ if addedline != nil {
+ comment := &Comment{
+ CheckResult: result,
+ Body: result.Message, // TODO: format message
+ LnumDiff: addedline.LnumDiff,
+ }
+ if err := w.c.Post(comment); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
+
+// AddedLine represents added line in diff.
+type AddedLine struct {
+ Path string // path to new file
+ Lnum int // the line number in the new file
+ LnumDiff int // the line number of the diff (Same as Lnumdiff of diff.Line)
+ Content string // line content
+}
+
+// posToAddedLine is a hash table of normalized path to line number to AddedLine.
+type posToAddedLine map[string]map[int]*AddedLine
+
+func (p posToAddedLine) Get(path string, lnum int) *AddedLine {
+ npath, err := normalizePath(path)
+ if err != nil {
+ return nil
+ }
+ ltodiff, ok := p[npath]
+ if !ok {
+ return nil
+ }
+ diffline, ok := ltodiff[lnum]
+ if !ok {
+ return nil
+ }
+ return diffline
+}
+
+// addedDiffLines traverse []*diff.FileDiff and returns posToAddedLine.
+func addedDiffLines(filediffs []*diff.FileDiff, strip int) posToAddedLine {
+ r := make(posToAddedLine)
+ for _, filediff := range filediffs {
+ path := filediff.PathNew
+ ltodiff := make(map[int]*AddedLine)
+ ps := strings.Split(filepath.ToSlash(filediff.PathNew), "/")
+
+ if len(ps) > strip {
+ path = filepath.Join(ps[strip:]...)
+ }
+ np, err := normalizePath(path)
+ if err != nil {
+ // FIXME(haya14busa): log or return error?
+ continue
+ }
+ path = np
+
+ for _, hunk := range filediff.Hunks {
+ for _, line := range hunk.Lines {
+ if line.Type == diff.LineAdded {
+ ltodiff[line.LnumNew] = &AddedLine{
+ Path: path,
+ Lnum: line.LnumNew,
+ LnumDiff: line.LnumDiff,
+ Content: line.Content,
+ }
+ }
+ }
+ }
+ r[path] = ltodiff
+ }
+ return r
+}
+
+func normalizePath(p string) (string, error) {
+ path, err := filepath.Abs(p)
+ if err != nil {
+ return "", err
+ }
+ return filepath.ToSlash(path), nil
+}
diff --git a/reviewdog_test.go b/reviewdog_test.go
new file mode 100644
index 0000000..d457573
--- /dev/null
+++ b/reviewdog_test.go
@@ -0,0 +1,92 @@
+package reviewdog
+
+import (
+ "fmt"
+ "os"
+ "reflect"
+ "sort"
+ "strings"
+ "testing"
+
+ "github.com/haya14busa/errorformat"
+ "github.com/haya14busa/reviewdog/diff"
+)
+
+func ExampleReviewdog() {
+ difftext := `diff --git a/golint.old.go b/golint.new.go
+index 34cacb9..a727dd3 100644
+--- a/golint.old.go
++++ b/golint.new.go
+@@ -2,6 +2,12 @@ package test
+
+ var V int
+
++var NewError1 int
++
+ // invalid func comment
+ func F() {
+ }
++
++// invalid func comment2
++func F2() {
++}
+`
+ lintresult := `golint.new.go:3:5: exported var V should have comment or be unexported
+golint.new.go:5:5: exported var NewError1 should have comment or be unexported
+golint.new.go:7:1: comment on exported function F should be of the form "F ..."
+golint.new.go:11:1: comment on exported function F2 should be of the form "F2 ..."
+`
+ efm, _ := errorformat.NewErrorformat([]string{`%f:%l:%c: %m`})
+ p := NewErrorformatParser(efm)
+ c := NewCommentWriter(os.Stdout)
+ d := NewDiffString(difftext, 1)
+ app := NewReviewdog(p, c, d)
+ app.Run(strings.NewReader(lintresult))
+ // Unordered output:
+ // golint.new.go:5:5: exported var NewError1 should have comment or be unexported
+ // golint.new.go:11:1: comment on exported function F2 should be of the form "F2 ..."
+}
+
+func TestAddedDiffLines(t *testing.T) {
+ content := `--- sample.old.txt 2016-10-13 05:09:35.820791185 +0900
++++ sample.new.txt 2016-10-13 05:15:26.839245048 +0900
+@@ -1,3 +1,4 @@
+ unchanged, contextual line
+-deleted line
++added line
++added line
+ unchanged, contextual line
+--- nonewline.old.txt 2016-10-13 15:34:14.931778318 +0900
++++ nonewline.new.txt 2016-10-13 15:34:14.868444672 +0900
+@@ -1,4 +1,4 @@
+ " vim: nofixeol noendofline
+ No newline at end of both the old and new file
+-a
+-a
+\ No newline at end of file
++b
++b
+\ No newline at end of file
+`
+
+ filediffs, _ := diff.ParseMultiFile(strings.NewReader(content))
+ wd, _ := os.Getwd()
+ wantlines := []string{
+ "sample.new.txt:2:(difflnum:3) added line",
+ "sample.new.txt:3:(difflnum:4) added line",
+ "nonewline.new.txt:3:(difflnum:5) b",
+ "nonewline.new.txt:4:(difflnum:6) b",
+ }
+ var gotlines []string
+ for path, ltol := range addedDiffLines(filediffs, 0) {
+ for lnum, addedline := range ltol {
+ l := fmt.Sprintf("%v:%v:(difflnum:%v) %v", path[len(wd)+1:], lnum, addedline.LnumDiff, addedline.Content)
+ gotlines = append(gotlines, l)
+ }
+ }
+ sort.Strings(gotlines)
+ sort.Strings(wantlines)
+ if !reflect.DeepEqual(gotlines, wantlines) {
+ t.Errorf("got:\n%v\nwant:\n%v", gotlines, wantlines)
+ }
+}
diff --git a/watchdogs.go b/watchdogs.go
deleted file mode 100644
index a1dd247..0000000
--- a/watchdogs.go
+++ /dev/null
@@ -1,167 +0,0 @@
-package watchdogs
-
-import (
- "bytes"
- "fmt"
- "io"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/haya14busa/watchdogs/diff"
-)
-
-type Watchdogs struct {
- p Parser
- c CommentService
- d DiffService
-}
-
-func NewWatchdogs(p Parser, c CommentService, d DiffService) *Watchdogs {
- return &Watchdogs{p: p, c: c, d: d}
-}
-
-// CheckResult represents a checked result of static analysis tools.
-// :h error-file-format
-type CheckResult struct {
- Path string // relative file path
- Lnum int // line number
- Col int // column number (1 <tab> == 1 character column)
- Message string // error message
- Lines []string // Original error lines (often one line)
-}
-
-type Parser interface {
- Parse(r io.Reader) ([]*CheckResult, error)
-}
-
-type Comment struct {
- *CheckResult
- Body string
- LnumDiff int
-}
-
-type CommentService interface {
- Post(*Comment) error
-}
-
-type DiffService interface {
- Diff() ([]byte, error)
- Strip() int
-}
-
-func (w *Watchdogs) Run(r io.Reader) error {
- results, err := w.p.Parse(r)
- if err != nil {
- return fmt.Errorf("parse error: %v", err)
- }
-
- d, err := w.d.Diff()
- if err != nil {
- return fmt.Errorf("fail to get diff: %v", err)
- }
-
- filediffs, err := diff.ParseMultiFile(bytes.NewReader(d))
- if err != nil {
- return fmt.Errorf("fail to parse diff: %v", err)
- }
- addedlines := AddedLines(filediffs, w.d.Strip())
-
- wd, err := os.Getwd()
- if err != nil {
- return err
- }
-
- for _, result := range results {
- addedline := addedlines.Get(result.Path, result.Lnum)
- if filepath.IsAbs(result.Path) {
- relpath, err := filepath.Rel(wd, result.Path)
- if err != nil {
- return err
- }
- result.Path = relpath
- }
- if addedline != nil {
- comment := &Comment{
- CheckResult: result,
- Body: result.Message, // TODO: format message
- LnumDiff: addedline.LnumDiff,
- }
- if err := w.c.Post(comment); err != nil {
- return err
- }
- }
- }
-
- return nil
-}
-
-// AddedLine represents added line in diff.
-type AddedLine struct {
- Path string // path to new file
- Lnum int // the line number in the new file
- LnumDiff int // the line number of the diff (Same as Lnumdiff of diff.Line)
- Content string // line content
-}
-
-// PosToAddedLine is a hash table of normalized path to line number to AddedLine.
-type PosToAddedLine map[string]map[int]*AddedLine
-
-func (p PosToAddedLine) Get(path string, lnum int) *AddedLine {
- npath, err := normalizePath(path)
- if err != nil {
- return nil
- }
- ltodiff, ok := p[npath]
- if !ok {
- return nil
- }
- diffline, ok := ltodiff[lnum]
- if !ok {
- return nil
- }
- return diffline
-}
-
-// AddedLines traverse []*diff.FileDiff and returns PosToAddedLine.
-func AddedLines(filediffs []*diff.FileDiff, strip int) PosToAddedLine {
- r := make(PosToAddedLine)
- for _, filediff := range filediffs {
- path := filediff.PathNew
- ltodiff := make(map[int]*AddedLine)
- ps := strings.Split(filepath.ToSlash(filediff.PathNew), "/")
-
- if len(ps) > strip {
- path = filepath.Join(ps[strip:]...)
- }
- np, err := normalizePath(path)
- if err != nil {
- // FIXME(haya14busa): log or return error?
- continue
- }
- path = np
-
- for _, hunk := range filediff.Hunks {
- for _, line := range hunk.Lines {
- if line.Type == diff.LineAdded {
- ltodiff[line.LnumNew] = &AddedLine{
- Path: path,
- Lnum: line.LnumNew,
- LnumDiff: line.LnumDiff,
- Content: line.Content,
- }
- }
- }
- }
- r[path] = ltodiff
- }
- return r
-}
-
-func normalizePath(p string) (string, error) {
- path, err := filepath.Abs(p)
- if err != nil {
- return "", err
- }
- return filepath.ToSlash(path), nil
-}
diff --git a/watchdogs_test.go b/watchdogs_test.go
deleted file mode 100644
index 08a5ef0..0000000
--- a/watchdogs_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package watchdogs
-
-import (
- "fmt"
- "os"
- "strings"
-
- "github.com/haya14busa/errorformat"
- "github.com/haya14busa/watchdogs/diff"
-)
-
-func ExampleWatchdogs() {
- difftext := `diff --git a/golint.old.go b/golint.new.go
-index 34cacb9..a727dd3 100644
---- a/golint.old.go
-+++ b/golint.new.go
-@@ -2,6 +2,12 @@ package test
-
- var V int
-
-+var NewError1 int
-+
- // invalid func comment
- func F() {
- }
-+
-+// invalid func comment2
-+func F2() {
-+}
-`
- lintresult := `golint.new.go:3:5: exported var V should have comment or be unexported
-golint.new.go:5:5: exported var NewError1 should have comment or be unexported
-golint.new.go:7:1: comment on exported function F should be of the form "F ..."
-golint.new.go:11:1: comment on exported function F2 should be of the form "F2 ..."
-`
- efm, _ := errorformat.NewErrorformat([]string{`%f:%l:%c: %m`})
- p := NewErrorformatParser(efm)
- c := NewCommentWriter(os.Stdout)
- d := NewDiffString(difftext, 1)
- app := NewWatchdogs(p, c, d)
- app.Run(strings.NewReader(lintresult))
- // Unordered output:
- // golint.new.go:5:5: exported var NewError1 should have comment or be unexported
- // golint.new.go:11:1: comment on exported function F2 should be of the form "F2 ..."
-}
-
-func ExampleAddedLines() {
- content := `--- sample.old.txt 2016-10-13 05:09:35.820791185 +0900
-+++ sample.new.txt 2016-10-13 05:15:26.839245048 +0900
-@@ -1,3 +1,4 @@
- unchanged, contextual line
--deleted line
-+added line
-+added line
- unchanged, contextual line
---- nonewline.old.txt 2016-10-13 15:34:14.931778318 +0900
-+++ nonewline.new.txt 2016-10-13 15:34:14.868444672 +0900
-@@ -1,4 +1,4 @@
- " vim: nofixeol noendofline
- No newline at end of both the old and new file
--a
--a
-\ No newline at end of file
-+b
-+b
-\ No newline at end of file
-`
-
- filediffs, _ := diff.ParseMultiFile(strings.NewReader(content))
- wd, _ := os.Getwd()
- for path, ltol := range AddedLines(filediffs, 0) {
- for lnum, addedline := range ltol {
- fmt.Printf("%v:%v:(difflnum:%v) %v\n", path[len(wd)+1:], lnum, addedline.LnumDiff, addedline.Content)
- }
- }
- // Unordered output:
- // sample.new.txt:2:(difflnum:3) added line
- // sample.new.txt:3:(difflnum:4) added line
- // nonewline.new.txt:3:(difflnum:5) b
- // nonewline.new.txt:4:(difflnum:6) b
-}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment