Repository: reviewdog/reviewdog
Pull request #703 (2020-07-26 16:16:42)

parser: support rdjson format (JSON of DiagnosticResult)
- [x] Updated Unreleased section in [CHANGELOG](https://github.com/reviewdog/reviewdog/blob/master/CHANGELOG.md) or it's not notable changes.


Patch:
diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml
index 2adbf4ec..6d27436e 100644
--- a/.github/workflows/reviewdog.yml
+++ b/.github/workflows/reviewdog.yml
@@ -37,12 +37,12 @@ jobs:
         run: |
           cd ./_testdata/ && golint ./... | reviewdog -f=golint -name=golint-check-subdir -reporter=github-check -level=info -filter-mode=nofilter
 
-      - name: Custom rdjsonl test (github-check)
+      - name: Custom rdjson test (github-check)
         env:
           REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         run: |
-          cat ./_testdata/custom_rdjsonl.json | \
-            reviewdog -name="custom-rdjsonl" -f=rdjsonl -reporter=github-check -level=info
+          cat ./_testdata/custom_rdjson.json | \
+            reviewdog -name="custom-rdjson" -f=rdjson -reporter=github-check -level=info
 
   reviewdog-pr:
     if: github.event_name == 'pull_request'
@@ -147,7 +147,7 @@ jobs:
           cd ./_testdata/ && reviewdog -conf=reviewdog_error.yml \
             -reporter=local -diff='git diff master' || EXIT_CODE=$?
           test "${EXIT_CODE}" = 1
-      - name: Suggestion
+      - name: Suggestion (rdjsonl)
         env:
           REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         run: |
@@ -158,8 +158,8 @@ jobs:
         env:
           REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         run: |
-          cat ./_testdata/custom_rdjsonl.json | \
-            reviewdog -name="custom-rdjsonl" -f=rdjsonl -reporter=github-pr-review
+          cat ./_testdata/custom_rdjson.json | \
+            reviewdog -name="custom-rdjson" -f=rdjson -reporter=github-pr-review
 
       - name: gofmt -s with reviewdog
         env:
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3164b185..2f8c5521 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -43,4 +43,4 @@ Reviewdog:
     - golint ./... | reviewdog -f=golint -name=golint-discussion -reporter=gitlab-mr-discussion -filter-mode=nofilter
     - golint ./... | reviewdog -f=golint -name=golint-commit -reporter=gitlab-mr-commit
     - cd ./_testdata/ && golint ./... | reviewdog -f=golint -name=golint-discussion-subdir -reporter=gitlab-mr-discussion; cd -
-    - cat ./_testdata/custom_rdjsonl.json | reviewdog -name="custom-rdjsonl" -f=rdjsonl -reporter=gitlab-mr-discussion
+    - cat ./_testdata/custom_rdjson.json | reviewdog -name="custom-rdjson" -f=rdjson -reporter=gitlab-mr-discussion
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0e3282e0..1a16bed4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### :rocket: Enhancements
 - [#629](https://github.com/reviewdog/reviewdog/pull/629) Introduced Reviewdog Diagnostic Format. ([@haya14busa])
- - [#674](https://github.com/reviewdog/reviewdog/pull/674) Support rdjsonl as input format
+ - [#674](https://github.com/reviewdog/reviewdog/pull/674) [#703](https://github.com/reviewdog/reviewdog/pull/703) Support rdjsonl/rdjson as input format
  - [#680](https://github.com/reviewdog/reviewdog/pull/680) github-pr-review: Support multiline comments
  - [#675](https://github.com/reviewdog/reviewdog/pull/675) [#698](https://github.com/reviewdog/reviewdog/pull/698) github-pr-review: Support suggested changes
  - [#699](https://github.com/reviewdog/reviewdog/pull/699) Support diff input format (`-f=diff`). Useful for suggested changes.
diff --git a/cmd/reviewdog/main.go b/cmd/reviewdog/main.go
index 42723fdb..3c5ecaf1 100644
--- a/cmd/reviewdog/main.go
+++ b/cmd/reviewdog/main.go
@@ -364,7 +364,8 @@ func runList(w io.Writer) error {
 		fmt.Fprintf(tabw, "%s\t%s\t- %s\n", f.Name, f.Description, f.URL)
 	}
 	fmt.Fprintf(tabw, "%s\t%s\t- %s\n", "checkstyle", "checkstyle XML format", "http://checkstyle.sourceforge.net/")
-	fmt.Fprintf(tabw, "%s\t%s\t- %s\n", "rdjsonl", "Reviewdog Diagnostic JSONL Format", "https://github.com/reviewdog/reviewdog")
+	fmt.Fprintf(tabw, "%s\t%s\t- %s\n", "rdjsonl", "Reviewdog Diagnostic JSONL Format (JSONL of Diagnostic message)", "https://github.com/reviewdog/reviewdog")
+	fmt.Fprintf(tabw, "%s\t%s\t- %s\n", "rdjson", "Reviewdog Diagnostic JSON Format (JSON of DiagnosticResult message)", "https://github.com/reviewdog/reviewdog")
 	return tabw.Flush()
 }
 
diff --git a/parser/parser.go b/parser/parser.go
index a3c05c76..4f2a4cc7 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -36,6 +36,8 @@ func New(opt *Option) (Parser, error) {
 		return NewCheckStyleParser(), nil
 	case "rdjsonl":
 		return NewRDJSONLParser(), nil
+	case "rdjson":
+		return NewRDJSONParser(), nil
 	case "diff":
 		return NewDiffParser(opt.DiffStrip), nil
 	}
diff --git a/parser/rdjson.go b/parser/rdjson.go
new file mode 100644
index 00000000..4327ecc6
--- /dev/null
+++ b/parser/rdjson.go
@@ -0,0 +1,46 @@
+package parser
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+
+	"github.com/reviewdog/reviewdog/proto/rdf"
+	"google.golang.org/protobuf/encoding/protojson"
+)
+
+var _ Parser = &RDJSONParser{}
+
+// RDJSONParser is parser for rdjsonl format.
+type RDJSONParser struct{}
+
+// NewRDJSONParser returns a new RDJSONParser.
+func NewRDJSONParser() *RDJSONParser {
+	return &RDJSONParser{}
+}
+
+// Parse parses rdjson (JSON of DiagnosticResult).
+func (p *RDJSONParser) Parse(r io.Reader) ([]*rdf.Diagnostic, error) {
+	b, err := ioutil.ReadAll(r)
+	if err != nil {
+		return nil, err
+	}
+	var dr rdf.DiagnosticResult
+	if err := protojson.Unmarshal(b, &dr); err != nil {
+		return nil, fmt.Errorf("failed to unmarshal rdjson (DiagnosticResult): %w", err)
+	}
+	for _, d := range dr.Diagnostics {
+		// Fill in default severity and source for each diagnostic.
+		if d.Severity == rdf.Severity_UNKNOWN_SEVERITY {
+			d.Severity = dr.GetSeverity()
+		}
+		if d.Source == nil {
+			d.Source = dr.Source
+		}
+		if d.GetOriginalOutput() == "" {
+			// TODO(haya14busa): Refactor not to fill in original output.
+			d.OriginalOutput = d.String()
+		}
+	}
+	return dr.Diagnostics, nil
+}
diff --git a/parser/rdjsonl.go b/parser/rdjsonl.go
index d4065f8f..73962c8a 100644
--- a/parser/rdjsonl.go
+++ b/parser/rdjsonl.go
@@ -2,6 +2,7 @@ package parser
 
 import (
 	"bufio"
+	"fmt"
 	"io"
 
 	"github.com/reviewdog/reviewdog/proto/rdf"
@@ -13,17 +14,19 @@ var _ Parser = &RDJSONLParser{}
 // RDJSONLParser is parser for rdjsonl format.
 type RDJSONLParser struct{}
 
+// NewRDJSONLParser returns a new RDJSONParser.
 func NewRDJSONLParser() *RDJSONLParser {
 	return &RDJSONLParser{}
 }
 
+// Parse parses rdjson (JSONL of Diagnostic).
 func (p *RDJSONLParser) Parse(r io.Reader) ([]*rdf.Diagnostic, error) {
 	var results []*rdf.Diagnostic
 	s := bufio.NewScanner(r)
 	for s.Scan() {
 		d := new(rdf.Diagnostic)
 		if err := protojson.Unmarshal(s.Bytes(), d); err != nil {
-			return nil, err
+			return nil, fmt.Errorf("failed to unmarshal rdjsonl (Diagnostic): %w", err)
 		}
 		if d.GetOriginalOutput() == "" {
 			// TODO(haya14busa): Refactor not to fill in original output.


Test patch:
diff --git a/_testdata/custom_rdjson.json b/_testdata/custom_rdjson.json
new file mode 100644
index 00000000..a757257d
--- /dev/null
+++ b/_testdata/custom_rdjson.json
@@ -0,0 +1,179 @@
+{
+  "diagnostics": [
+    {
+      "message": "range test 1 (line only)",
+      "location": {
+        "path": "_testdata/custom.txt",
+        "range": {
+          "start": {
+            "line": 5
+          },
+          "end": {
+            "line": 9
+          }
+        }
+      }
+    },
+    {
+      "message": "range test 2 (same line)",
+      "location": {
+        "path": "_testdata/custom.txt",
+        "range": {
+          "start": {
+            "line": 5
+          },
+          "end": {
+            "line": 5
+          }
+        }
+      }
+    },
+    {
+      "message": "range test 3 (with line-break)",
+      "location": {
+        "path": "_testdata/custom.txt",
+        "range": {
+          "start": {
+            "line": 6,
+            "column": 2
+          },
+          "end": {
+            "line": 7,
+            "column": 1
+          }
+        }
+      }
+    },
+    {
+      "message": "range test 4 (with line-break)",
+      "location": {
+        "path": "_testdata/custom.txt",
+        "range": {
+          "start": {
+            "line": 6,
+            "column": 2
+          },
+          "end": {
+            "line": 9,
+            "column": 1
+          }
+        }
+      }
+    },
+    {
+      "message": "range test 5 (within line)",
+      "location": {
+        "path": "_testdata/custom.txt",
+        "range": {
+          "start": {
+            "line": 12,
+            "column": 5
+          },
+          "end": {
+            "line": 12,
+            "column": 7
+          }
+        }
+      }
+    },
+    {
+      "message": "severity test 1 (INFO)",
+      "location": {
+        "path": "_testdata/custom.txt",
+        "range": {
+          "start": {
+            "line": 3,
+            "column": 5
+          }
+        }
+      },
+      "severity": "INFO"
+    },
+    {
+      "message": "severity test 2 (INFO)",
+      "location": {
+        "path": "_testdata/custom.txt",
+        "range": {
+          "start": {
+            "line": 3,
+            "column": 4
+          }
+        }
+      },
+      "severity": 2
+    },
+    {
+      "message": "severity test 3 (WARNING)",
+      "location": {
+        "path": "_testdata/custom.txt",
+        "range": {
+          "start": {
+            "line": 3,
+            "column": 5
+          }
+        }
+      },
+      "severity": "WARNING"
+    },
+    {
+      "message": "severity test 4 (ERROR)",
+      "location": {
+        "path": "_testdata/custom.txt",
+        "range": {
+          "start": {
+            "line": 3,
+            "column": 5
+          }
+        }
+      },
+      "severity": "ERROR"
+    },
+    {
+      "message": "code test 1 (rule only)",
+      "code": {
+        "value": "E14"
+      },
+      "location": {
+        "path": "_testdata/custom.txt",
+        "range": {
+          "start": {
+            "line": 3,
+            "column": 5
+          }
+        }
+      },
+      "severity": "ERROR"
+    },
+    {
+      "message": "code test 2 (rule w/ URL)",
+      "code": {
+        "value": "E14",
+        "url": "https://github.com/reviewdog/reviewdog#E14"
+      },
+      "location": {
+        "path": "_testdata/custom.txt",
+        "range": {
+          "start": {
+            "line": 3,
+            "column": 5
+          }
+        }
+      },
+      "severity": "ERROR"
+    },
+    {
+      "source": {
+        "name": "rdjsonl-source"
+      },
+      "message": "source test",
+      "location": {
+        "path": "_testdata/custom.txt",
+        "range": {
+          "start": {
+            "line": 3
+          }
+        }
+      }
+    }
+  ]
+}
diff --git a/_testdata/custom_rdjsonl.json b/_testdata/custom_rdjsonl.json
deleted file mode 100644
index d1833997..00000000
--- a/_testdata/custom_rdjsonl.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{"message":"range test 1 (line only)","location":{"path":"_testdata/custom.txt","range":{"start":{"line":5}, "end":{"line":9}}}}
-{"message":"range test 2 (same line)","location":{"path":"_testdata/custom.txt","range":{"start":{"line":5}, "end":{"line":5}}}}
-{"message":"range test 3 (with line-break)","location":{"path":"_testdata/custom.txt","range":{"start":{"line":6,"column":2}, "end":{"line":7,"column":1}}}}
-{"message":"range test 4 (with line-break)","location":{"path":"_testdata/custom.txt","range":{"start":{"line":6,"column":2}, "end":{"line":9,"column":1}}}}
-{"message":"range test 5 (within line)","location":{"path":"_testdata/custom.txt","range":{"start":{"line":12, "column": 5},"end":{"line":12, "column": 7}}}}
-{"message":"severity test 1 (INFO)","location":{"path":"_testdata/custom.txt","range":{"start":{"line":3, "column": 5}}}, "severity": "INFO"}
-{"message":"severity test 2 (INFO)","location":{"path":"_testdata/custom.txt","range":{"start":{"line":3, "column": 4}}}, "severity": 2}
-{"message":"severity test 3 (WARNING)","location":{"path":"_testdata/custom.txt","range":{"start":{"line":3, "column": 5}}}, "severity": "WARNING"}
-{"message":"severity test 4 (ERROR)","location":{"path":"_testdata/custom.txt","range":{"start":{"line":3, "column": 5}}}, "severity": "ERROR"}
-{"message":"code test 1 (rule only)","code":{"value":"E14"},"location":{"path":"_testdata/custom.txt","range":{"start":{"line":3, "column": 5}}}, "severity": "ERROR"}
-{"message":"code test 2 (rule w/ URL)","code":{"value":"E14","url":"https://github.com/reviewdog/reviewdog#E14"},"location":{"path":"_testdata/custom.txt","range":{"start":{"line":3, "column": 5}}}, "severity": "ERROR"}
-{"source":{"name": "rdjsonl-source"}, "message":"source test","location":{"path":"_testdata/custom.txt","range":{"start":{"line":3}}}}
diff --git a/parser/rdjson_test.go b/parser/rdjson_test.go
new file mode 100644
index 00000000..b8e49272
--- /dev/null
+++ b/parser/rdjson_test.go
@@ -0,0 +1,160 @@
+package parser
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"strings"
+
+	"google.golang.org/protobuf/encoding/protojson"
+)
+
+func ExampleRDJSONParser() {
+	const sample = `
+{
+  "source": {
+    "name": "linter-name",
+    "url": "https://github.com/reviewdog#linter-name"
+  },
+  "severity": "INFO",
+  "diagnostics": [
+    {
+      "source": {
+        "name": "deadcode"
+      },
+      "message": "'unused' is unused",
+      "location": {
+        "path": "testdata/main.go",
+        "range": {
+          "start": {
+            "line": 18,
+            "column": 6
+          }
+        }
+      }
+    },
+    {
+      "message": "printf: Sprintf format %d reads arg #1, but call has 0 args",
+      "location": {
+        "path": "testdata/main.go",
+        "range": {
+          "start": {
+            "line": 13,
+            "column": 2
+          }
+        }
+      }
+    },
+    {
+      "source": {
+        "name": "severity-test"
+      },
+      "message": "severity test (string)",
+      "location": {
+        "path": "testdata/main.go",
+        "range": {
+          "start": {
+            "line": 24,
+            "column": 6
+          }
+        }
+      },
+      "severity": "WARNING"
+    },
+    {
+      "source": {
+        "name": "severity-test"
+      },
+      "message": "severity test (number)",
+      "location": {
+        "path": "testdata/main.go",
+        "range": {
+          "start": {
+            "line": 24,
+            "column": 6
+          }
+        }
+      },
+      "severity": 1
+    }
+  ]
+}`
+	p := NewRDJSONParser()
+	diagnostics, err := p.Parse(strings.NewReader(sample))
+	if err != nil {
+		panic(err)
+	}
+	for _, d := range diagnostics {
+		d.OriginalOutput = "" // Skip for testing as it's not deterministic.
+		rdjson, _ := protojson.MarshalOptions{Indent: "  "}.Marshal(d)
+		var out bytes.Buffer
+		json.Indent(&out, rdjson, "", "  ")
+		fmt.Println(out.String())
+	}
+	// Output:
+	// {
+	//   "message": "'unused' is unused",
+	//   "location": {
+	//     "path": "testdata/main.go",
+	//     "range": {
+	//       "start": {
+	//         "line": 18,
+	//         "column": 6
+	//       }
+	//     }
+	//   },
+	//   "severity": "INFO",
+	//   "source": {
+	//     "name": "deadcode"
+	//   }
+	// }
+	// {
+	//   "message": "printf: Sprintf format %d reads arg #1, but call has 0 args",
+	//   "location": {
+	//     "path": "testdata/main.go",
+	//     "range": {
+	//       "start": {
+	//         "line": 13,
+	//         "column": 2
+	//       }
+	//     }
+	//   },
+	//   "severity": "INFO",
+	//   "source": {
+	//     "name": "linter-name",
+	//     "url": "https://github.com/reviewdog#linter-name"
+	//   }
+	// }
+	// {
+	//   "message": "severity test (string)",
+	//   "location": {
+	//     "path": "testdata/main.go",
+	//     "range": {
+	//       "start": {
+	//         "line": 24,
+	//         "column": 6
+	//       }
+	//     }
+	//   },
+	//   "severity": "WARNING",
+	//   "source": {
+	//     "name": "severity-test"
+	//   }
+	// }
+	// {
+	//   "message": "severity test (number)",
+	//   "location": {
+	//     "path": "testdata/main.go",
+	//     "range": {
+	//       "start": {
+	//         "line": 24,
+	//         "column": 6
+	//       }
+	//     }
+	//   },
+	//   "severity": "ERROR",
+	//   "source": {
+	//     "name": "severity-test"
+	//   }
+	// }
+}