Created
December 22, 2016 13:40
-
-
Save haya14busa/d3be48dba9d12e91c60b9a5d711da9b5 to your computer and use it in GitHub Desktop.
pr17.diff
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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