Skip to content

Commit 2136866

Browse files
authored
New tool that converts SARIF file to txt suitable for Emacs (#16)
Introduce new 'emacs' sub-command (cloned html command) that converts SARIF to a text file. When opened in emacs, this file will bring a compilation mode, allowing easy navigation among code scan issues.
1 parent 4ac74bb commit 2136866

File tree

3 files changed

+150
-4
lines changed

3 files changed

+150
-4
lines changed

sarif/cmdline/main.py

+18-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
csv_op,
1616
diff_op,
1717
html_op,
18+
emacs_op,
1819
info_op,
1920
ls_op,
2021
summary_op,
@@ -81,7 +82,7 @@ def _create_arg_parser():
8182
+ "(or for diff, an increase in issues at that level).",
8283
)
8384

84-
for cmd in ["blame", "csv", "html", "summary", "word"]:
85+
for cmd in ["blame", "csv", "html", "emacs", "summary", "word"]:
8586
subparser[cmd].add_argument(
8687
"--output", "-o", type=str, metavar="PATH", help="Output file or directory"
8788
)
@@ -90,7 +91,7 @@ def _create_arg_parser():
9091
"--output", "-o", type=str, metavar="FILE", help="Output file"
9192
)
9293

93-
for cmd in ["copy", "csv", "diff", "summary", "html", "trend", "word"]:
94+
for cmd in ["copy", "csv", "diff", "summary", "html", "emacs", "trend", "word"]:
9495
subparser[cmd].add_argument(
9596
"--blame-filter",
9697
"-b",
@@ -122,7 +123,7 @@ def _create_arg_parser():
122123
help="Strip off the common prefix of paths in the CSV output",
123124
)
124125
# word and html default to trimming
125-
for cmd in ["html", "word"]:
126+
for cmd in ["html", "emacs", "word"]:
126127
subparser[cmd].add_argument(
127128
"--no-autotrim",
128129
"-n",
@@ -135,7 +136,7 @@ def _create_arg_parser():
135136
help="Image to include at top of file - SARIF logo by default",
136137
)
137138
# csv, html and word allow trimmable paths to be specified
138-
for cmd in ["csv", "word", "html"]:
139+
for cmd in ["csv", "word", "html", "emacs"]:
139140
subparser[cmd].add_argument(
140141
"--trim",
141142
metavar="PREFIX",
@@ -371,6 +372,15 @@ def _html(args):
371372
html_op.generate_html(input_files, args.image, output, multiple_file_output)
372373
return _check(input_files, args.check)
373374

375+
def _emacs(args):
376+
input_files = loader.load_sarif_files(*args.files_or_dirs)
377+
input_files.init_default_line_number_1()
378+
_init_path_prefix_stripping(input_files, args, strip_by_default=True)
379+
_init_blame_filtering(input_files, args)
380+
(output, multiple_file_output) = _prepare_output(input_files, args.output, ".txt")
381+
emacs_op.generate_compile(input_files, output)
382+
return _check(input_files, args.check)
383+
374384

375385
def _info(args):
376386
input_files = loader.load_sarif_files(*args.files_or_dirs)
@@ -459,6 +469,10 @@ def _word(args):
459469
"fn": _html,
460470
"desc": "Write an HTML representation of SARIF file(s) for viewing in a web browser",
461471
},
472+
"emacs": {
473+
"fn": _emacs,
474+
"desc": "Write a representation of SARIF file(s) for viewing in emacs",
475+
},
462476
"info": {"fn": _info, "desc": "Print information about SARIF file(s) structure"},
463477
"ls": {"fn": _ls, "desc": "List all SARIF files in the directories specified"},
464478
"summary": {

sarif/operations/emacs_op.py

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
"""
2+
Code for `sarif emacs` command.
3+
"""
4+
5+
import base64
6+
from datetime import datetime
7+
import os
8+
9+
from jinja2 import Environment, FileSystemLoader, select_autoescape
10+
11+
from sarif import charts
12+
from sarif.sarif_file import SarifFileSet
13+
14+
_THIS_MODULE_PATH = os.path.dirname(__file__)
15+
16+
_TEMPLATES_PATH = os.path.join(_THIS_MODULE_PATH, "templates")
17+
18+
_ENV = Environment(
19+
loader=FileSystemLoader(searchpath=_TEMPLATES_PATH),
20+
autoescape=select_autoescape(),
21+
)
22+
23+
24+
def generate_compile(
25+
input_files: SarifFileSet, output: str, output_multiple_files: bool
26+
):
27+
"""
28+
Generate txt file from the input files.
29+
"""
30+
date_val = datetime.now()
31+
32+
output_file = output
33+
if output_multiple_files:
34+
for input_file in input_files:
35+
output_file_name = input_file.get_file_name_without_extension() + ".txt"
36+
print(
37+
"Writing results for",
38+
input_file.get_file_name(),
39+
"to",
40+
output_file_name,
41+
)
42+
_generate_single_txt(
43+
input_file,
44+
os.path.join(output, output_file_name),
45+
date_val
46+
)
47+
output_file = os.path.join(output, ".compile.txt")
48+
source_description = input_files.get_description()
49+
print(
50+
"Writing results for",
51+
source_description,
52+
"to",
53+
os.path.basename(output_file),
54+
)
55+
_generate_single_txt(
56+
input_files, output_file, date_val
57+
)
58+
59+
60+
def _generate_single_txt(
61+
input_file, output_file, date_val
62+
):
63+
64+
all_tools = input_file.get_distinct_tool_names()
65+
66+
total_distinct_issue_codes = 0
67+
problems = []
68+
69+
issues_by_severity = input_file.get_records_grouped_by_severity()
70+
for (severity, issues_of_severity) in issues_by_severity.items():
71+
issue_code_histogram = input_file.get_issue_code_histogram(severity)
72+
73+
distinct_issue_codes = len(issue_code_histogram)
74+
total_distinct_issue_codes += distinct_issue_codes
75+
76+
severity_details = _enrich_details(issue_code_histogram, issues_of_severity)
77+
78+
severity_section = {
79+
"type": severity,
80+
"count": distinct_issue_codes,
81+
"details": severity_details,
82+
}
83+
84+
problems.append(severity_section)
85+
86+
filtered = None
87+
filter_stats = input_file.get_filter_stats()
88+
if filter_stats:
89+
filtered = f"Results were filtered by {filter_stats}."
90+
91+
template = _ENV.get_template("sarif_emacs.txt")
92+
txt_content = template.render(
93+
report_type=", ".join(all_tools),
94+
report_date=date_val,
95+
severities=", ".join(issues_by_severity.keys()),
96+
total=total_distinct_issue_codes,
97+
problems=problems,
98+
filtered=filtered,
99+
)
100+
101+
with open(output_file, "wt", encoding="utf-8") as file_out:
102+
file_out.write(txt_content)
103+
104+
105+
def _enrich_details(histogram, records_of_severity):
106+
enriched_details = []
107+
108+
for (error_code, count) in histogram:
109+
error_lines = [e for e in records_of_severity if e["Code"] == error_code]
110+
lines = sorted(
111+
error_lines, key=lambda x: x["Location"] + str(x["Line"]).zfill(6)
112+
)
113+
enriched_details.append({"code": error_code, "count": count, "details": lines})
114+
return enriched_details
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
-*- compilation -*-
2+
3+
Sarif Summary: {{ report_type }}
4+
Document generated on: {{ report_date }}
5+
Total number of distinct issues of all severities ({{ severities }}): {{ total }}
6+
{% if filtered -%}
7+
<p>{{ filtered }}</p>
8+
{%- endif %}
9+
10+
{% for problem in problems %}
11+
Severity : {{ problem.type }} [{{ problem.count }}]
12+
{% for error in problem.details -%}
13+
{% for line in error.details -%}
14+
{{ line.Location }}:{{ line.Line }}: {{ error.code }}
15+
{% endfor %}
16+
{% endfor %}
17+
{% endfor -%}
18+

0 commit comments

Comments
 (0)