From 73966d1a05956735f93f493f39d575e64f646c5c Mon Sep 17 00:00:00 2001
From: Robert Raposa
Date: Fri, 6 May 2016 13:23:11 -0400
Subject: [PATCH 1/3] Add output of violation counts by rule
---
scripts/safe-commit-linter.sh | 8 +-
scripts/safe_template_linter.py | 295 ++++++++++-----------
scripts/tests/templates/test.underscore | 4 +-
scripts/tests/test_safe_template_linter.py | 149 +++++++++--
4 files changed, 275 insertions(+), 181 deletions(-)
diff --git a/scripts/safe-commit-linter.sh b/scripts/safe-commit-linter.sh
index dcccd43306..2d57a777aa 100755
--- a/scripts/safe-commit-linter.sh
+++ b/scripts/safe-commit-linter.sh
@@ -15,11 +15,17 @@ show_help() {
echo "Runs the Safe Template Linter against all files in a git commit."
echo ""
echo "Mandatory arguments to long options are mandatory for short options too."
+ echo " -h, --help Output this help."
echo " -m, --main-branch=COMMIT Run against files changed between the"
echo " current branch and this commit."
echo " Defaults to origin/master."
echo ""
- echo "For additional help:"
+ echo "This scripts does not give a grand total. Be sure to check for"
+ echo "0 violations on each file."
+ echo ""
+ echo "For more help using the safe template linter, including details on how"
+ echo "to understand and fix any violations, read the docs here:"
+ echo ""
echo " http://edx.readthedocs.org/projects/edx-developer-guide/en/latest/conventions/safe_templates.html#safe-template-linter"
}
diff --git a/scripts/safe_template_linter.py b/scripts/safe_template_linter.py
index 64ca9a7748..2865276b6f 100755
--- a/scripts/safe_template_linter.py
+++ b/scripts/safe_template_linter.py
@@ -189,118 +189,38 @@ class Rules(Enum):
"""
An Enum of each rule which the linter will check.
"""
- mako_missing_default = (
- 'mako-missing-default',
- 'Missing default <%page expression_filter="h"/>.'
- )
- mako_multiple_page_tags = (
- 'mako-multiple-page-tags',
- 'A Mako template can only have one <%page> tag.'
- )
- mako_unparseable_expression = (
- 'mako-unparseable-expression',
- 'The expression could not be properly parsed.'
- )
- mako_unwanted_html_filter = (
- 'mako-unwanted-html-filter',
- 'Remove explicit h filters when it is provided by the page directive.'
- )
- mako_invalid_html_filter = (
- 'mako-invalid-html-filter',
- 'The expression is using an invalid filter in an HTML context.'
- )
- mako_invalid_js_filter = (
- 'mako-invalid-js-filter',
- 'The expression is using an invalid filter in a JavaScript context.'
- )
- mako_js_missing_quotes = (
- 'mako-js-missing-quotes',
- 'An expression using js_escaped_string must be wrapped in quotes.'
- )
- mako_js_html_string = (
- 'mako-js-html-string',
- 'A JavaScript string containing HTML should not have an embedded Mako expression.'
- )
- mako_html_entities = (
- 'mako-html-entities',
- "HTML entities should be plain text or wrapped with HTML()."
- )
- mako_unknown_context = (
- 'mako-unknown-context',
- "The context could not be determined."
- )
- underscore_not_escaped = (
- 'underscore-not-escaped',
- 'Expressions should be escaped using <%- expression %>.'
- )
- javascript_jquery_append = (
- 'javascript-jquery-append',
- 'Use HtmlUtils.append() or .append(HtmlUtils.xxx().toString()).'
- )
- javascript_jquery_prepend = (
- 'javascript-jquery-prepend',
- 'Use HtmlUtils.prepend() or .prepend(HtmlUtils.xxx().toString()).'
- )
- javascript_jquery_insertion = (
- 'javascript-jquery-insertion',
- 'JQuery DOM insertion calls that take content must use HtmlUtils (e.g. $el.after(HtmlUtils.xxx().toString()).'
- )
- javascript_jquery_insert_into_target = (
- 'javascript-jquery-insert-into-target',
- 'JQuery DOM insertion calls that take a target can only be called from elements (e.g. .$el.appendTo()).'
- )
- javascript_jquery_html = (
- 'javascript-jquery-html',
- "Use HtmlUtils.setHtml(), .html(HtmlUtils.xxx().toString()), or JQuery's text() function."
- )
- javascript_concat_html = (
- 'javascript-concat-html',
- 'Use HtmlUtils functions rather than concatenating strings with HTML.'
- )
- javascript_escape = (
- 'javascript-escape',
- "Avoid calls to escape(), especially in Backbone. Use templates, HtmlUtils, or JQuery's text() function."
- )
- javascript_interpolate = (
- 'javascript-interpolate',
- 'Use StringUtils.interpolate() or HtmlUtils.interpolateHtml() as appropriate.'
- )
- python_concat_html = (
- 'python-concat-html',
- 'Use HTML() and Text() functions rather than concatenating strings with HTML.'
- )
- python_custom_escape = (
- 'python-custom-escape',
- "Use markupsafe.escape() rather than custom escaping for '<'."
- )
- python_deprecated_display_name = (
- 'python-deprecated-display-name',
- 'Replace deprecated display_name_with_default_escaped with display_name_with_default.'
- )
- python_requires_html_or_text = (
- 'python-requires-html-or-text',
- 'You must start with Text() or HTML() if you use HTML() or Text() during interpolation.'
- )
- python_close_before_format = (
- 'python-close-before-format',
- 'You must close any call to Text() or HTML() before calling format().'
- )
- python_wrap_html = (
- 'python-wrap-html',
- "String containing HTML should be wrapped with call to HTML()."
- )
- python_interpolate_html = (
- 'python-interpolate-html',
- "Use HTML(), Text(), and format() functions for interpolating strings with HTML."
- )
- python_parse_error = (
- 'python-parse-error',
- 'Error parsing Python function or string.'
- )
+ # IMPORTANT: Do not edit without also updating the docs:
+ # - http://edx.readthedocs.io/projects/edx-developer-guide/en/latest/conventions/safe_templates.html#safe-template-linter
+ mako_missing_default = 'mako-missing-default'
+ mako_multiple_page_tags = 'mako-multiple-page-tags'
+ mako_unparseable_expression = 'mako-unparseable-expression'
+ mako_unwanted_html_filter = 'mako-unwanted-html-filter'
+ mako_invalid_html_filter = 'mako-invalid-html-filter'
+ mako_invalid_js_filter = 'mako-invalid-js-filter'
+ mako_js_missing_quotes = 'mako-js-missing-quotes'
+ mako_js_html_string = 'mako-js-html-string'
+ mako_html_entities = 'mako-html-entities'
+ mako_unknown_context = 'mako-unknown-context'
+ underscore_not_escaped = 'underscore-not-escaped'
+ javascript_jquery_append = 'javascript-jquery-append'
+ javascript_jquery_prepend = 'javascript-jquery-prepend'
+ javascript_jquery_insertion = 'javascript-jquery-insertion'
+ javascript_jquery_insert_into_target = 'javascript-jquery-insert-into-target'
+ javascript_jquery_html = 'javascript-jquery-html'
+ javascript_concat_html = 'javascript-concat-html'
+ javascript_escape = 'javascript-escape'
+ javascript_interpolate = 'javascript-interpolate'
+ python_concat_html = 'python-concat-html'
+ python_custom_escape = 'python-custom-escape'
+ python_deprecated_display_name = 'python-deprecated-display-name'
+ python_requires_html_or_text = 'python-requires-html-or-text'
+ python_close_before_format = 'python-close-before-format'
+ python_wrap_html = 'python-wrap-html'
+ python_interpolate_html = 'python-interpolate-html'
+ python_parse_error = 'python-parse-error'
- def __init__(self, rule_id, rule_summary):
+ def __init__(self, rule_id):
self.rule_id = rule_id
- self.rule_summary = rule_summary
class Expression(object):
@@ -577,6 +497,57 @@ class ExpressionRuleViolation(RuleViolation):
), file=out)
+class SummaryResults(object):
+ """
+ Contains the summary results for all violations.
+ """
+
+ def __init__(self):
+ """
+ Init method.
+ """
+ self.total_violations = 0
+ self.totals_by_rule = dict.fromkeys(
+ [rule.rule_id for rule in Rules.__members__.values()], 0
+ )
+
+ def add_violation(self, violation):
+ """
+ Adds a violation to the summary details.
+
+ Arguments:
+ violation: The violation to add to the summary.
+
+ """
+ self.total_violations += 1
+ self.totals_by_rule[violation.rule.rule_id] += 1
+
+ def print_results(self, options, out):
+ """
+ Prints the results (i.e. violations) in this file.
+
+ Arguments:
+ options: A list of the following options:
+ list_files: True to print only file names, and False to print
+ all violations.
+ rule_totals: If True include totals by rule.
+ out: output file
+
+ """
+ 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)
+
+ # matches output of jshint for simplicity
+ print("", file=out)
+ print("{} violations total".format(self.total_violations), file=out)
+
+
class FileResults(object):
"""
Contains the results, or violations, for a file.
@@ -611,7 +582,7 @@ class FileResults(object):
if line_comment_delim is not None:
self._filter_commented_code(line_comment_delim)
- def print_results(self, options, out):
+ def print_results(self, options, summary_results, out):
"""
Prints the results (i.e. violations) in this file.
@@ -619,26 +590,23 @@ class FileResults(object):
options: A list of the following options:
list_files: True to print only file names, and False to print
all violations.
+ summary_results: A SummaryResults with a summary of the violations.
verbose: True for multiple lines of context, False single line.
out: output file
- Returns:
- The number of violations. When using --quiet, returns number of
- files with violations.
+ Side effect:
+ Updates the passed SummaryResults.
"""
- num_violations = 0
if options['list_files']:
if self.violations is not None and 0 < len(self.violations):
- num_violations += 1
print(self.full_path, file=out)
else:
self.violations.sort(key=lambda violation: violation.sort_key())
for violation in self.violations:
if not violation.is_disabled:
- num_violations += 1
violation.print_results(options, out)
- return num_violations
+ summary_results.add_violation(violation)
def _filter_commented_code(self, line_comment_delim):
"""
@@ -789,15 +757,16 @@ class BaseLinter(object):
Init method.
"""
self._skip_dirs = (
+ '.git',
'.pycharm_helpers',
'common/static/xmodule/modules',
+ 'perf_tests',
'node_modules',
'reports/diff_quality',
- 'spec',
'scripts/tests/templates',
+ 'spec',
'test_root',
'vendor',
- 'perf_tests'
)
def _is_skip_dir(self, skip_dirs, directory):
@@ -1554,7 +1523,7 @@ class HtmlStringVisitor(BaseVisitor):
node: An AST node.
"""
# Skips '<' (and '>') in regex named groups. For example, "(?P)".
- if re.search('[(][?]P<', node.s) is None and re.search('[<>]', node.s) is not None:
+ if re.search('[(][?]P<', node.s) is None and re.search('<', node.s) is not None:
self.unsafe_html_string_nodes.append(node)
if re.search(r"&[#]?[a-zA-Z0-9]+;", node.s):
self.over_escaped_entity_string_nodes.append(node)
@@ -2505,7 +2474,7 @@ class MakoTemplateLinter(BaseLinter):
return expressions
-def _process_file(full_path, template_linters, options, out):
+def _process_file(full_path, template_linters, options, summary_results, out):
"""
For each linter, lints the provided file. This means finding and printing
violations.
@@ -2514,22 +2483,19 @@ def _process_file(full_path, template_linters, options, out):
full_path: The full path of the file to lint.
template_linters: A list of linting objects.
options: A list of the options.
+ summary_results: A SummaryResults with a summary of the violations.
out: output file
- Returns:
- The number of violations.
-
"""
num_violations = 0
directory = os.path.dirname(full_path)
file_name = os.path.basename(full_path)
for template_linter in template_linters:
results = template_linter.process_file(directory, file_name)
- num_violations += results.print_results(options, out)
- return num_violations
+ results.print_results(options, summary_results, out)
-def _process_current_walk(current_walk, template_linters, options, out):
+def _process_current_walk(current_walk, template_linters, options, summary_results, out):
"""
For each linter, lints all the files in the current os walk. This means
finding and printing violations.
@@ -2538,22 +2504,19 @@ def _process_current_walk(current_walk, template_linters, options, out):
current_walk: A walk returned by os.walk().
template_linters: A list of linting objects.
options: A list of the options.
+ summary_results: A SummaryResults with a summary of the violations.
out: output file
- Returns:
- The number of violations.
-
"""
num_violations = 0
walk_directory = os.path.normpath(current_walk[0])
walk_files = current_walk[2]
for walk_file in walk_files:
full_path = os.path.join(walk_directory, walk_file)
- num_violations += _process_file(full_path, template_linters, options, out)
- return num_violations
+ _process_file(full_path, template_linters, options, summary_results, out)
-def _process_os_walk(starting_dir, template_linters, options, out):
+def _process_os_walk(starting_dir, template_linters, options, summary_results, out):
"""
For each linter, lints all the directories in the starting directory.
@@ -2561,16 +2524,40 @@ def _process_os_walk(starting_dir, template_linters, options, out):
starting_dir: The initial directory to begin the walk.
template_linters: A list of linting objects.
options: A list of the options.
+ summary_results: A SummaryResults with a summary of the violations.
out: output file
- Returns:
- The number of violations.
-
"""
num_violations = 0
for current_walk in os.walk(starting_dir):
- num_violations += _process_current_walk(current_walk, template_linters, options, out)
- return num_violations
+ _process_current_walk(current_walk, template_linters, options, summary_results, out)
+
+
+def _lint(file_or_dir, template_linters, options, summary_results, out):
+ """
+ For each linter, lints the provided file or directory.
+
+ Arguments:
+ file_or_dir: The file or initial directory to lint.
+ template_linters: A list of linting objects.
+ options: A list of the options.
+ summary_results: A SummaryResults with a summary of the violations.
+ out: output file
+
+ """
+
+ if file_or_dir is not None and os.path.isfile(file_or_dir):
+ _process_file(file_or_dir, template_linters, options, summary_results, out)
+ else:
+ directory = "."
+ if file_or_dir is not None:
+ if os.path.exists(file_or_dir):
+ directory = file_or_dir
+ else:
+ raise ValueError("Path [{}] is not a valid file or directory.".format(file_or_dir))
+ _process_os_walk(directory, template_linters, options, summary_results, out)
+
+ summary_results.print_results(options, out)
def main():
@@ -2579,11 +2566,9 @@ def main():
Prints all violations.
"""
- epilog = 'rules:\n'
- for rule in Rules.__members__.values():
- epilog += " {0[0]}: {0[1]}\n".format(rule.value)
+ epilog = "For more help using the safe template linter, including details on how\n"
+ epilog += "to understand and fix any violations, read the docs here:\n"
epilog += "\n"
- epilog += "additional help:\n"
# pylint: disable=line-too-long
epilog += " http://edx.readthedocs.org/projects/edx-developer-guide/en/latest/conventions/safe_templates.html#safe-template-linter\n"
@@ -2596,6 +2581,10 @@ def main():
'--list-files', dest='list_files', action='store_true',
help='Only display the filenames that contain violations.'
)
+ parser.add_argument(
+ '--rule-totals', dest='rule_totals', action='store_true',
+ help='Display the totals for each rule.'
+ )
parser.add_argument(
'--verbose', dest='verbose', action='store_true',
help='Print multiple lines where possible for additional context of violations.'
@@ -2606,25 +2595,13 @@ def main():
options = {
'list_files': args.list_files,
- 'verbose': args.verbose
+ 'rule_totals': args.rule_totals,
+ 'verbose': args.verbose,
}
template_linters = [MakoTemplateLinter(), UnderscoreTemplateLinter(), JavaScriptLinter(), PythonLinter()]
+ summary_results = SummaryResults()
- if args.path is not None and os.path.isfile(args.path):
- num_violations = _process_file(args.path, template_linters, options, out=sys.stdout)
- else:
- directory = "."
- if args.path is not None:
- if os.path.exists(args.path):
- directory = args.path
- else:
- raise ValueError("Path [{}] is not a valid file or directory.".format(args.path))
- num_violations = _process_os_walk(directory, template_linters, options, out=sys.stdout)
-
- if options['list_files'] is False:
- # matches output of jshint for simplicity
- print("")
- print("{} violations found".format(num_violations))
+ _lint(args.path, template_linters, options, summary_results, out=sys.stdout)
if __name__ == "__main__":
diff --git a/scripts/tests/templates/test.underscore b/scripts/tests/templates/test.underscore
index 57fedb860a..9f634102d8 100644
--- a/scripts/tests/templates/test.underscore
+++ b/scripts/tests/templates/test.underscore
@@ -1 +1,3 @@
-<%= invalid %>
+<%=
+ 'multi-line invalid'
+%>
diff --git a/scripts/tests/test_safe_template_linter.py b/scripts/tests/test_safe_template_linter.py
index 15ac87b13e..1b788a890d 100644
--- a/scripts/tests/test_safe_template_linter.py
+++ b/scripts/tests/test_safe_template_linter.py
@@ -10,8 +10,8 @@ import textwrap
from unittest import TestCase
from ..safe_template_linter import (
- _process_os_walk, FileResults, JavaScriptLinter, MakoTemplateLinter, ParseString,
- StringLines, PythonLinter, UnderscoreTemplateLinter, Rules
+ _lint, FileResults, JavaScriptLinter, MakoTemplateLinter, ParseString,
+ StringLines, PythonLinter, SummaryResults, UnderscoreTemplateLinter, Rules
)
@@ -83,6 +83,15 @@ class TestSafeTemplateLinter(TestCase):
Test some top-level linter functions
"""
+ def setUp(self):
+ """
+ Setup patches on linters for testing.
+ """
+ self.patch_is_valid_directory(MakoTemplateLinter)
+ self.patch_is_valid_directory(JavaScriptLinter)
+ self.patch_is_valid_directory(UnderscoreTemplateLinter)
+ self.patch_is_valid_directory(PythonLinter)
+
def patch_is_valid_directory(self, linter_class):
"""
Creates a mock patch for _is_valid_directory on a Linter to always
@@ -96,37 +105,137 @@ class TestSafeTemplateLinter(TestCase):
self.addCleanup(patcher.stop)
return patch_start
- def test_process_os_walk(self):
+ def test_lint_defaults(self):
"""
- Tests the top-level processing of template files, including Mako
- includes.
+ Tests the top-level linting with default options.
"""
out = StringIO()
+ summary_results = SummaryResults()
- options = {
- 'list_files': False,
- 'verbose': False,
- }
-
- template_linters = [MakoTemplateLinter(), JavaScriptLinter(), UnderscoreTemplateLinter(), PythonLinter()]
-
- self.patch_is_valid_directory(MakoTemplateLinter)
- self.patch_is_valid_directory(JavaScriptLinter)
- self.patch_is_valid_directory(UnderscoreTemplateLinter)
- self.patch_is_valid_directory(PythonLinter)
-
- num_violations = _process_os_walk('scripts/tests/templates', template_linters, options, out)
+ _lint(
+ 'scripts/tests/templates',
+ template_linters=[MakoTemplateLinter(), UnderscoreTemplateLinter(), JavaScriptLinter(), PythonLinter()],
+ options={
+ 'list_files': False,
+ 'verbose': False,
+ 'rule_totals': False,
+ },
+ summary_results=summary_results,
+ out=out,
+ )
output = out.getvalue()
- self.assertEqual(num_violations, 7)
+ # Assert violation details are displayed.
self.assertIsNotNone(re.search('test\.html.*{}'.format(Rules.mako_missing_default.rule_id), output))
self.assertIsNotNone(re.search('test\.coffee.*{}'.format(Rules.javascript_concat_html.rule_id), output))
self.assertIsNotNone(re.search('test\.coffee.*{}'.format(Rules.underscore_not_escaped.rule_id), output))
self.assertIsNotNone(re.search('test\.js.*{}'.format(Rules.javascript_concat_html.rule_id), output))
self.assertIsNotNone(re.search('test\.js.*{}'.format(Rules.underscore_not_escaped.rule_id), output))
- self.assertIsNotNone(re.search('test\.underscore.*{}'.format(Rules.underscore_not_escaped.rule_id), output))
+ lines_with_rule = 0
+ lines_without_rule = 0 # Output with verbose setting only.
+ for underscore_match in re.finditer('test\.underscore:.*\n', output):
+ if re.search(Rules.underscore_not_escaped.rule_id, underscore_match.group()) is not None:
+ lines_with_rule += 1
+ else:
+ lines_without_rule += 1
+ self.assertGreaterEqual(lines_with_rule, 1)
+ self.assertEquals(lines_without_rule, 0)
self.assertIsNone(re.search('test\.py.*{}'.format(Rules.python_parse_error.rule_id), output))
self.assertIsNotNone(re.search('test\.py.*{}'.format(Rules.python_wrap_html.rule_id), output))
+ # Assert no rule totals.
+ self.assertIsNone(re.search('{}:\s*{} violations'.format(Rules.python_parse_error.rule_id, 0), output))
+ # Assert final total
+ self.assertIsNotNone(re.search('{} violations total'.format(7), output))
+
+ def test_lint_with_verbose(self):
+ """
+ Tests the top-level linting with verbose option.
+ """
+ out = StringIO()
+ summary_results = SummaryResults()
+
+ _lint(
+ 'scripts/tests/templates',
+ template_linters=[MakoTemplateLinter(), UnderscoreTemplateLinter(), JavaScriptLinter(), PythonLinter()],
+ options={
+ 'list_files': False,
+ 'verbose': True,
+ 'rule_totals': False,
+ },
+ summary_results=summary_results,
+ out=out,
+ )
+
+ output = out.getvalue()
+ lines_with_rule = 0
+ lines_without_rule = 0 # Output with verbose setting only.
+ for underscore_match in re.finditer('test\.underscore:.*\n', output):
+ if re.search(Rules.underscore_not_escaped.rule_id, underscore_match.group()) is not None:
+ lines_with_rule += 1
+ else:
+ lines_without_rule += 1
+ self.assertGreaterEqual(lines_with_rule, 1)
+ self.assertGreaterEqual(lines_without_rule, 1)
+ # Assert no rule totals.
+ self.assertIsNone(re.search('{}:\s*{} violations'.format(Rules.python_parse_error.rule_id, 0), output))
+ # Assert final total
+ self.assertIsNotNone(re.search('{} violations total'.format(7), output))
+
+ def test_lint_with_rule_totals(self):
+ """
+ Tests the top-level linting with rule totals option.
+ """
+ out = StringIO()
+ summary_results = SummaryResults()
+
+ _lint(
+ 'scripts/tests/templates',
+ template_linters=[MakoTemplateLinter(), UnderscoreTemplateLinter(), JavaScriptLinter(), PythonLinter()],
+ options={
+ 'list_files': False,
+ 'verbose': False,
+ 'rule_totals': True,
+ },
+ summary_results=summary_results,
+ out=out,
+ )
+
+ output = out.getvalue()
+ self.assertIsNotNone(re.search('test\.py.*{}'.format(Rules.python_wrap_html.rule_id), output))
+
+ # Assert totals output.
+ self.assertIsNotNone(re.search('{}:\s*{} violations'.format(Rules.python_parse_error.rule_id, 0), output))
+ self.assertIsNotNone(re.search('{}:\s*{} violations'.format(Rules.python_wrap_html.rule_id, 1), output))
+ self.assertIsNotNone(re.search('{} violations total'.format(7), output))
+
+ def test_lint_with_list_files(self):
+ """
+ Tests the top-level linting with list files option.
+ """
+ out = StringIO()
+ summary_results = SummaryResults()
+
+ _lint(
+ 'scripts/tests/templates',
+ template_linters=[MakoTemplateLinter(), UnderscoreTemplateLinter(), JavaScriptLinter(), PythonLinter()],
+ options={
+ 'list_files': True,
+ 'verbose': False,
+ 'rule_totals': False,
+ },
+ summary_results=summary_results,
+ out=out,
+ )
+
+ output = out.getvalue()
+ # Assert file with rule is not output.
+ self.assertIsNone(re.search('test\.py.*{}'.format(Rules.python_wrap_html.rule_id), output))
+ # Assert file is output.
+ self.assertIsNotNone(re.search('test\.py', output))
+
+ # Assert no totals.
+ self.assertIsNone(re.search('{}:\s*{} violations'.format(Rules.python_parse_error.rule_id, 0), output))
+ self.assertIsNone(re.search('{} violations total'.format(7), output))
@ddt
From 80c3f842628ab9f3420e3646ad003ec33ead5a93 Mon Sep 17 00:00:00 2001
From: Robert Raposa
Date: Fri, 6 May 2016 14:39:56 -0400
Subject: [PATCH 2/3] Fix safe template issues for 0 violations for some rules
---
cms/templates/404.html | 4 ++--
cms/templates/500.html | 6 +++---
cms/templates/course_outline.html | 2 +-
cms/templates/group_configurations.html | 2 +-
cms/templates/widgets/header.html | 7 +++----
.../instructor_analytics.html | 2 +-
lms/templates/navigation.html | 9 +++------
pavelib/quality.py | 6 ++++--
themes/edx.org/lms/templates/header.html | 14 ++++++++++----
9 files changed, 28 insertions(+), 24 deletions(-)
diff --git a/cms/templates/404.html b/cms/templates/404.html
index 8753d39ee1..0c24c8dae8 100644
--- a/cms/templates/404.html
+++ b/cms/templates/404.html
@@ -19,9 +19,9 @@ from openedx.core.djangolib.markup import Text, HTML
${_('The page that you were looking for was not found.')}
${Text(_('Go back to the {homepage} or let us know about any pages that may have been moved at {email}.')).format(
homepage=HTML('homepage'),
- email=HTML('{address}'.format(
+ email=HTML('{address}').format(
address=Text(settings.TECH_SUPPORT_EMAIL)
- ))
+ )
)}
diff --git a/cms/templates/500.html b/cms/templates/500.html
index 6b77cda024..ffd3ed0e1a 100644
--- a/cms/templates/500.html
+++ b/cms/templates/500.html
@@ -30,9 +30,9 @@ ${Text(_("{studio_name} Server Error")).format(
)}
${_("We've logged the error and our staff is currently working to resolve this error as soon as possible.")}
${Text(_(u'If the problem persists, please email us at {email_link}.')).format(
- email_link=HTML(u'{email_address}'.format(
- email_address=Text(settings.TECH_SUPPORT_EMAIL),
- ))
+ email_link=HTML(u'{email_address}').format(
+ email_address=Text(settings.TECH_SUPPORT_EMAIL),
+ )
)}
diff --git a/cms/templates/course_outline.html b/cms/templates/course_outline.html
index 16b34e31fe..faf07b2b48 100644
--- a/cms/templates/course_outline.html
+++ b/cms/templates/course_outline.html
@@ -121,7 +121,7 @@ from openedx.core.djangolib.markup import Text, HTML