diff --git a/pmd-core/pom.xml b/pmd-core/pom.xml
index 05d4226d58..694f59885f 100644
--- a/pmd-core/pom.xml
+++ b/pmd-core/pom.xml
@@ -93,6 +93,12 @@
org.ow2.asm
asm
+
+ com.google.code.gson
+ gson
+ 2.5
+
+
net.sourceforge.saxon
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CodeClimateIssue.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CodeClimateIssue.java
new file mode 100644
index 0000000000..658882c8f8
--- /dev/null
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CodeClimateIssue.java
@@ -0,0 +1,55 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.renderers;
+
+import net.sourceforge.pmd.*;
+
+/**
+ * Structure for the Code Climate Issue spec (https://github.com/codeclimate/spec/blob/master/SPEC.md#issues)
+ */
+public class CodeClimateIssue {
+ public final String type = "issue";
+ public String check_name;
+ public String description;
+ public Content content;
+ public final String[] categories = { "Style" };
+ public Location location;
+ public String severity;
+
+ /**
+ * Location structure
+ */
+ public static class Location {
+ public String path;
+ public Lines lines;
+
+ private class Lines {
+ public int begin;
+ public int end;
+ }
+
+ public Location(String path, int beginLine, int endLine) {
+ this.path = path;
+ this.lines = new Lines();
+ lines.begin = beginLine;
+ lines.end = endLine;
+ }
+ }
+
+ /**
+ * Content structure
+ */
+ public static class Content {
+ public String body;
+
+ /**
+ * Strip out all newlines from the body
+ * @param {String} body The text to compose the content from
+ */
+ public Content(String body) {
+ this.body = body.replace(PMD.EOL, " ");
+ }
+ }
+}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CodeClimateRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CodeClimateRenderer.java
new file mode 100644
index 0000000000..1687fc4b01
--- /dev/null
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CodeClimateRenderer.java
@@ -0,0 +1,72 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.renderers;
+
+import com.google.gson.Gson;
+import net.sourceforge.pmd.Rule;
+import net.sourceforge.pmd.RuleViolation;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Iterator;
+
+/**
+ * Renderer for Code Climate JSON format
+ */
+public class CodeClimateRenderer extends AbstractIncrementingRenderer {
+ public static final String NAME = "codeclimate";
+
+ protected static final String EOL = System.getProperty("line.separator", "\n");
+
+ public CodeClimateRenderer() {
+ super(NAME, "Code Climate integration.");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void renderFileViolations(Iterator violations) throws IOException {
+ Writer writer = getWriter();
+ Gson gson = new Gson();
+ while (violations.hasNext()) {
+ RuleViolation rv = violations.next();
+ writer.write(gson.toJson(makeIssue(rv)) + EOL);
+ }
+ }
+
+ /**
+ * Generate a CodeClimateIssue suitable for processing into JSON from the given RuleViolation.
+ * @param rv RuleViolation to convert.
+ * @return The generated issue.
+ */
+ private CodeClimateIssue makeIssue(RuleViolation rv) {
+ CodeClimateIssue issue = new CodeClimateIssue();
+ Rule rule = rv.getRule();
+ issue.check_name = rule.getName();
+ issue.description = rv.getDescription();
+ issue.content = new CodeClimateIssue.Content(rule.getDescription());
+ issue.location = new CodeClimateIssue.Location(rv.getFilename(), rv.getBeginLine(), rv.getEndLine());
+ switch(rule.getPriority()) {
+ case HIGH:
+ issue.severity = "critical";
+ break;
+ case MEDIUM_HIGH:
+ case MEDIUM:
+ case MEDIUM_LOW:
+ issue.severity = "normal";
+ break;
+ case LOW:
+ issue.severity = "info";
+ break;
+ }
+ return issue;
+ }
+
+ @Override
+ public String defaultFileExtension() {
+ return "json";
+ }
+}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/RendererFactory.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/RendererFactory.java
index 132c0051b9..dfc09214e2 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/RendererFactory.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/RendererFactory.java
@@ -26,6 +26,7 @@ public class RendererFactory {
public static final Map> REPORT_FORMAT_TO_RENDERER;
static {
Map> map = new TreeMap>();
+ map.put(CodeClimateRenderer.NAME, CodeClimateRenderer.class);
map.put(XMLRenderer.NAME, XMLRenderer.class);
map.put(IDEAJRenderer.NAME, IDEAJRenderer.class);
map.put(TextColorRenderer.NAME, TextColorRenderer.class);
diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/renderers/CodeClimateRendererTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/renderers/CodeClimateRendererTest.java
new file mode 100644
index 0000000000..1572f9f43d
--- /dev/null
+++ b/pmd-core/src/test/java/net/sourceforge/pmd/renderers/CodeClimateRendererTest.java
@@ -0,0 +1,31 @@
+package net.sourceforge.pmd.renderers;
+
+import net.sourceforge.pmd.*;
+
+public class CodeClimateRendererTest extends AbstractRendererTst {
+
+ @Override
+ public Renderer getRenderer() {
+ return new CodeClimateRenderer();
+ }
+
+ @Override
+ public String getExpected() {
+ return "{\"type\":\"issue\",\"check_name\":\"Foo\",\"description\":\"blah\",\"content\":{\"body\":\"desc\"},\"categories\":[\"Style\"],\"location\":{\"path\":\"n/a\",\"lines\":{\"begin\":1,\"end\":1}},\"severity\":\"info\"}" + PMD.EOL;
+ }
+
+ @Override
+ public String getExpectedEmpty() {
+ return "";
+ }
+
+ @Override
+ public String getExpectedMultiple() {
+ return "{\"type\":\"issue\",\"check_name\":\"Foo\",\"description\":\"blah\",\"content\":{\"body\":\"desc\"},\"categories\":[\"Style\"],\"location\":{\"path\":\"n/a\",\"lines\":{\"begin\":1,\"end\":1}},\"severity\":\"info\"}" + PMD.EOL +
+ "{\"type\":\"issue\",\"check_name\":\"Foo\",\"description\":\"blah\",\"content\":{\"body\":\"desc\"},\"categories\":[\"Style\"],\"location\":{\"path\":\"n/a\",\"lines\":{\"begin\":1,\"end\":1}},\"severity\":\"info\"}" + PMD.EOL;
+ }
+
+ public static junit.framework.Test suite() {
+ return new junit.framework.JUnit4TestAdapter(CodeClimateRendererTest.class);
+ }
+}