Add --summary-format=json option to XSS linter (#25851)

This will simplify updating of the linter thresholds file after XSS linter
violations are addressed.
This commit is contained in:
Tim McCormack
2020-12-11 12:34:04 +00:00
committed by GitHub
parent 79370d86cb
commit edeabc3faf
3 changed files with 77 additions and 9 deletions

View File

@@ -4,6 +4,7 @@ Tests for main.py
"""
import json
import re
from six import StringIO
from unittest import TestCase
@@ -69,6 +70,7 @@ class TestXSSLinter(TestCase):
'list_files': False,
'verbose': False,
'rule_totals': False,
'summary_format': 'eslint',
'skip_dirs': ()
},
summary_results=self.summary_results,
@@ -107,6 +109,7 @@ class TestXSSLinter(TestCase):
'list_files': False,
'verbose': True,
'rule_totals': False,
'summary_format': 'eslint',
'skip_dirs': ()
},
summary_results=self.summary_results,
@@ -139,6 +142,7 @@ class TestXSSLinter(TestCase):
'list_files': False,
'verbose': False,
'rule_totals': True,
'summary_format': 'eslint',
'skip_dirs': ()
},
summary_results=self.summary_results,
@@ -153,6 +157,36 @@ class TestXSSLinter(TestCase):
self.assertIsNotNone(re.search(r'{}:\s*{} violations'.format(self.ruleset.python_wrap_html.rule_id, 1), output))
self.assertIsNotNone(re.search(r'{} violations total'.format(5), output))
def test_lint_with_json_output(self):
"""
Tests the top-level linting with JSON summary format.
"""
_lint(
'scripts/xsslint/tests/templates',
template_linters=self.template_linters,
options={
'list_files': False,
'verbose': False,
'rule_totals': True,
'summary_format': 'json',
'skip_dirs': ()
},
summary_results=self.summary_results,
out=self.out,
)
output = self.out.getvalue()
self.assertIsNotNone(re.search(r'test\.py.*{}'.format(self.ruleset.python_wrap_html.rule_id), output))
# Find something that looks like pretty-printed JSON
json_match = re.search(r'\n\{.*\n\}', output, re.DOTALL)
self.assertIsNotNone(json_match)
data = json.loads(json_match.group())
# Check for rule counts (including zero-instance ones) and total
self.assertEqual(1, data['rules']['javascript-concat-html'])
self.assertEqual(0, data['rules']['python-concat-html'])
self.assertEqual(5, data['total'])
def test_lint_with_list_files(self):
"""
Tests the top-level linting with list files option.
@@ -164,6 +198,7 @@ class TestXSSLinter(TestCase):
'list_files': True,
'verbose': False,
'rule_totals': False,
'summary_format': 'eslint',
'skip_dirs': ()
},
summary_results=self.summary_results,

View File

@@ -151,6 +151,11 @@ def main():
'--rule-totals', dest='rule_totals', action='store_true',
help='Display the totals for each rule.'
)
parser.add_argument(
'--summary-format', dest='summary_format',
choices=['eslint', 'json'], default='eslint',
help='Choose the display format for the summary.'
)
parser.add_argument(
'--verbose', dest='verbose', action='store_true',
help='Print multiple lines where possible for additional context of violations.'
@@ -166,6 +171,7 @@ def main():
options = {
'list_files': args.list_files,
'rule_totals': args.rule_totals,
'summary_format': args.summary_format,
'verbose': args.verbose,
'skip_dirs': getattr(config, 'SKIP_DIRS', ())
}

View File

@@ -3,6 +3,7 @@ Utility classes for reporting linter results.
"""
import json
import os
import re
@@ -275,17 +276,43 @@ class SummaryResults(object):
"""
if options['list_files'] is False:
if options['rule_totals']:
max_rule_id_len = max(len(rule_id) for rule_id in self.totals_by_rule)
print("", file=out)
for rule_id in sorted(self.totals_by_rule.keys()):
padding = " " * (max_rule_id_len - len(rule_id))
print("{}: {}{} violations".format(rule_id, padding, self.totals_by_rule[rule_id]), file=out)
print("", file=out)
if options['summary_format'] == 'json':
self._print_json_format(options, out)
else:
self._print_eslint_format(options, out)
# matches output of eslint for simplicity
def _print_eslint_format(self, options, out):
"""
Implementation of print_results with eslint-style output.
"""
if options['rule_totals']:
max_rule_id_len = max(len(rule_id) for rule_id in self.totals_by_rule)
print("", file=out)
print("{} violations total".format(self.total_violations), file=out)
for rule_id in sorted(self.totals_by_rule.keys()):
padding = " " * (max_rule_id_len - len(rule_id))
print("{}: {}{} violations".format(rule_id, padding, self.totals_by_rule[rule_id]), file=out)
print("", file=out)
# matches output of eslint for simplicity
print("", file=out)
print("{} violations total".format(self.total_violations), file=out)
def _print_json_format(self, options, out):
"""
Implementation of print_results with JSON output.
"""
print("", file=out)
print("Violation counts:", file=out)
data = {'rules': self.totals_by_rule}
if options['rule_totals']:
data['total'] = self.total_violations
json.dump(data, fp=out, indent=4, sort_keys=True)
print("", file=out)
print(
"If you've fixed some XSS issues and these numbers have gone down, "
"you can use this to update scripts/xsslint_thresholds.json",
file=out
)
class FileResults(object):