Repository: sscpac/statick (language: python)

Issue:
Create json reporting plugin
The [shellcheck](https://github.com/koalaman/shellcheck/wiki/Integration#json-output) utility has an option to provide its output in json format. That made it very simple to parse the output in Statick. We should add a Statick reporting plugin that does the same thing. This would make it easier to use Statick as part of a pipeline of other tools.


Discussion / hints:

Fix patch:
diff --git a/statick_tool/plugins/reporting/print_json_reporting_plugin.py b/statick_tool/plugins/reporting/print_json_reporting_plugin.py
new file mode 100644
index 00000000..803c845b
--- /dev/null
+++ b/statick_tool/plugins/reporting/print_json_reporting_plugin.py
@@ -0,0 +1,48 @@
+"""Prints the Statick reports out to the terminal in JSON format."""
+import json
+from collections import OrderedDict
+from typing import Dict, List, Optional, Tuple
+
+from statick_tool.issue import Issue
+from statick_tool.package import Package
+from statick_tool.reporting_plugin import ReportingPlugin
+
+
+class PrintJsonReportingPlugin(ReportingPlugin):
+    """Prints the Statick reports out to the terminal in JSON format."""
+
+    def get_name(self) -> str:
+        """Return the plugin name."""
+        return "print_json"
+
+    def report(
+        self, package: Package, issues: Dict[str, List[Issue]], level: str
+    ) -> Tuple[Optional[None], bool]:
+        """Go through the issues list and print them to the console in JSON format.
+
+        Args:
+            package (:obj:`Package`): The Package object that was analyzed.
+            issues (:obj:`dict` of :obj:`str` to :obj:`Issue`): The issues
+                found by the Statick analysis, keyed by the tool that found
+                them.
+            level: (:obj:`str`): Name of the level used in the scan.
+        """
+        all_issues = []
+        for _, value in issues.items():
+            for issue in value:
+                issue_dict = OrderedDict()
+                issue_dict["fileName"] = issue.filename
+                issue_dict["lineNumber"] = issue.line_number
+                issue_dict["tool"] = issue.tool
+                issue_dict["type"] = issue.issue_type
+                issue_dict["severity"] = issue.severity
+                issue_dict["message"] = issue.message
+                issue_dict["certReference"] = ""
+                if issue.cert_reference:
+                    issue_dict["certReference"] = issue.cert_reference
+                all_issues.append(issue_dict)
+        report_json = {"issues": all_issues}
+        line = json.dumps(report_json)
+        print(line)
+
+        return None, True
diff --git a/statick_tool/plugins/reporting/print_json_reporting_plugin.yapsy-plugin b/statick_tool/plugins/reporting/print_json_reporting_plugin.yapsy-plugin
new file mode 100644
index 00000000..771bdcad
--- /dev/null
+++ b/statick_tool/plugins/reporting/print_json_reporting_plugin.yapsy-plugin
@@ -0,0 +1,3 @@
+[Core]
+Name = Print JSON Reporting Plugin
+Module = print_json_reporting_plugin


Test patch:
diff --git a/tests/plugins/reporting/print_json_reporting_plugin/test_print_json_reporting_plugin.py b/tests/plugins/reporting/print_json_reporting_plugin/test_print_json_reporting_plugin.py
new file mode 100644
index 00000000..83d680b7
--- /dev/null
+++ b/tests/plugins/reporting/print_json_reporting_plugin/test_print_json_reporting_plugin.py
@@ -0,0 +1,82 @@
+"""Unit tests for the print JSON reporting plugin."""
+import json
+import os
+
+from yapsy.PluginManager import PluginManager
+
+import statick_tool
+from statick_tool.issue import Issue
+from statick_tool.package import Package
+from statick_tool.plugins.reporting.print_json_reporting_plugin import (
+    PrintJsonReportingPlugin,
+)
+from statick_tool.reporting_plugin import ReportingPlugin
+
+
+def test_print_json_reporting_plugin_found():
+    """Test that the plugin manager finds the print json reporting plugin."""
+    manager = PluginManager()
+    # Get the path to statick_tool/__init__.py, get the directory part, and
+    # add 'plugins' to that to get the standard plugins dir
+    manager.setPluginPlaces(
+        [os.path.join(os.path.dirname(statick_tool.__file__), "plugins")]
+    )
+    manager.setCategoriesFilter(
+        {
+            "Reporting": ReportingPlugin,
+        }
+    )
+    manager.collectPlugins()
+    assert any(
+        plugin_info.plugin_object.get_name() == "print_json"
+        for plugin_info in manager.getPluginsOfCategory("Reporting")
+    )
+    assert any(
+        plugin_info.name == "Print JSON Reporting Plugin"
+        for plugin_info in manager.getPluginsOfCategory("Reporting")
+    )
+
+
+def test_print_json_reporting_plugin_report_cert(capsys):
+    """Test the output of the reporting plugin."""
+    pjrp = PrintJsonReportingPlugin()
+    package = Package(
+        "valid_package", os.path.join(os.path.dirname(__file__), "valid_package")
+    )
+    issues = {
+        "tool_a": [
+            Issue("test.txt", 1, "tool_a", "type", 1, "This is a test", "MEM50-CPP")
+        ]
+    }
+
+    pjrp.report(package, issues, "level")
+    captured = capsys.readouterr()
+    output = captured.out.splitlines()[0]
+    assert (
+        output
+        == '{"issues": [{"fileName": "test.txt", "lineNumber": 1, "tool": "tool_a", "type": "type", "severity": 1, "message": "This is a test", "certReference": "MEM50-CPP"}]}'
+    )
+    assert json.loads(output)
+
+
+def test_print_json_reporting_plugin_report_nocert(capsys):
+    """Test the output of the reporting plugin."""
+    pjrp = PrintJsonReportingPlugin()
+    package = Package(
+        "valid_package", os.path.join(os.path.dirname(__file__), "valid_package")
+    )
+    issues = {
+        "tool_a": [
+            Issue("test.txt", 1, "tool_a", "type", 1, "This is a test", None),
+            Issue("test2.txt", 2, "tool_b", "type", 2, "This is a second test", None),
+        ]
+    }
+
+    pjrp.report(package, issues, "level")
+    captured = capsys.readouterr()
+    output = captured.out.splitlines()[0]
+    assert (
+        output
+        == '{"issues": [{"fileName": "test.txt", "lineNumber": 1, "tool": "tool_a", "type": "type", "severity": 1, "message": "This is a test", "certReference": ""}, {"fileName": "test2.txt", "lineNumber": 2, "tool": "tool_b", "type": "type", "severity": 2, "message": "This is a second test", "certReference": ""}]}'
+    )
+    assert json.loads(output)