Enhance Mako linting
- Lint JavaScript context for JavaScript violations - Lint for Mako specific JavaScript rules - Skip commented lines - Change unicode to decode.utf8 - Count lint violations
This commit is contained in:
@@ -10,10 +10,50 @@ import textwrap
|
||||
from unittest import TestCase
|
||||
|
||||
from ..safe_template_linter import (
|
||||
_process_os_walk, FileResults, JavaScriptLinter, MakoTemplateLinter, ParseString, UnderscoreTemplateLinter, Rules
|
||||
_process_os_walk, FileResults, JavaScriptLinter, MakoTemplateLinter, ParseString, StringLines,
|
||||
UnderscoreTemplateLinter, Rules
|
||||
)
|
||||
|
||||
|
||||
@ddt
|
||||
class TestStringLines(TestCase):
|
||||
"""
|
||||
Test StringLines class.
|
||||
"""
|
||||
@data(
|
||||
{'string': 'test', 'index': 0, 'line_start_index': 0, 'line_end_index': 4},
|
||||
{'string': 'test', 'index': 2, 'line_start_index': 0, 'line_end_index': 4},
|
||||
{'string': 'test', 'index': 3, 'line_start_index': 0, 'line_end_index': 4},
|
||||
{'string': '\ntest', 'index': 0, 'line_start_index': 0, 'line_end_index': 1},
|
||||
{'string': '\ntest', 'index': 2, 'line_start_index': 1, 'line_end_index': 5},
|
||||
{'string': '\ntest\n', 'index': 0, 'line_start_index': 0, 'line_end_index': 1},
|
||||
{'string': '\ntest\n', 'index': 2, 'line_start_index': 1, 'line_end_index': 6},
|
||||
{'string': '\ntest\n', 'index': 6, 'line_start_index': 6, 'line_end_index': 6},
|
||||
)
|
||||
def test_string_lines_start_end_index(self, data):
|
||||
"""
|
||||
Test StringLines index_to_line_start_index and index_to_line_end_index.
|
||||
"""
|
||||
lines = StringLines(data['string'])
|
||||
self.assertEqual(lines.index_to_line_start_index(data['index']), data['line_start_index'])
|
||||
self.assertEqual(lines.index_to_line_end_index(data['index']), data['line_end_index'])
|
||||
|
||||
@data(
|
||||
{'string': 'test', 'line_number': 1, 'line': 'test'},
|
||||
{'string': '\ntest', 'line_number': 1, 'line': ''},
|
||||
{'string': '\ntest', 'line_number': 2, 'line': 'test'},
|
||||
{'string': '\ntest\n', 'line_number': 1, 'line': ''},
|
||||
{'string': '\ntest\n', 'line_number': 2, 'line': 'test'},
|
||||
{'string': '\ntest\n', 'line_number': 3, 'line': ''},
|
||||
)
|
||||
def test_string_lines_start_end_index(self, data):
|
||||
"""
|
||||
Test line_number_to_line.
|
||||
"""
|
||||
lines = StringLines(data['string'])
|
||||
self.assertEqual(lines.line_number_to_line(data['line_number']), data['line'])
|
||||
|
||||
|
||||
class TestLinter(TestCase):
|
||||
"""
|
||||
Test Linter base class
|
||||
@@ -31,7 +71,7 @@ class TestSafeTemplateLinter(TestCase):
|
||||
Test some top-level linter functions
|
||||
"""
|
||||
|
||||
def test_process_os_walk_with_includes(self):
|
||||
def test_process_os_walk(self):
|
||||
"""
|
||||
Tests the top-level processing of template files, including Mako
|
||||
includes.
|
||||
@@ -47,9 +87,10 @@ class TestSafeTemplateLinter(TestCase):
|
||||
with mock.patch.object(MakoTemplateLinter, '_is_valid_directory', return_value=True):
|
||||
with mock.patch.object(JavaScriptLinter, '_is_valid_directory', return_value=True):
|
||||
with mock.patch.object(UnderscoreTemplateLinter, '_is_valid_directory', return_value=True):
|
||||
_process_os_walk('scripts/tests/templates', template_linters, options, out)
|
||||
num_violations = _process_os_walk('scripts/tests/templates', template_linters, options, out)
|
||||
|
||||
output = out.getvalue()
|
||||
self.assertEqual(num_violations, 6)
|
||||
self.assertIsNotNone(re.search('test\.html.*mako-missing-default', output))
|
||||
self.assertIsNotNone(re.search('test\.coffee.*javascript-concat-html', output))
|
||||
self.assertIsNotNone(re.search('test\.coffee.*underscore-not-escaped', output))
|
||||
@@ -83,36 +124,40 @@ class TestMakoTemplateLinter(TestLinter):
|
||||
@data(
|
||||
{
|
||||
'template': '\n <%page expression_filter="h"/>',
|
||||
'violations': 0,
|
||||
'rule': None
|
||||
},
|
||||
{
|
||||
'template':
|
||||
'\n <%page args="section_data" expression_filter="h" /> ',
|
||||
'violations': 0,
|
||||
'rule': None
|
||||
},
|
||||
{
|
||||
'template': '\n ## <%page expression_filter="h"/>',
|
||||
'rule': Rules.mako_missing_default
|
||||
},
|
||||
{
|
||||
'template':
|
||||
'\n <%page expression_filter="h" /> '
|
||||
'\n <%page args="section_data"/>',
|
||||
'violations': 1,
|
||||
'rule': Rules.mako_multiple_page_tags
|
||||
},
|
||||
{
|
||||
'template':
|
||||
'\n <%page expression_filter="h" /> '
|
||||
'\n ## <%page args="section_data"/>',
|
||||
'rule': None
|
||||
},
|
||||
{
|
||||
'template': '\n <%page args="section_data" /> ',
|
||||
'violations': 1,
|
||||
'rule': Rules.mako_missing_default
|
||||
},
|
||||
{
|
||||
'template':
|
||||
'\n <%page args="section_data"/> <some-other-tag expression_filter="h" /> ',
|
||||
'violations': 1,
|
||||
'rule': Rules.mako_missing_default
|
||||
},
|
||||
{
|
||||
'template': '\n',
|
||||
'violations': 1,
|
||||
'rule': Rules.mako_missing_default
|
||||
},
|
||||
)
|
||||
@@ -125,16 +170,18 @@ class TestMakoTemplateLinter(TestLinter):
|
||||
|
||||
linter._check_mako_file_is_safe(data['template'], results)
|
||||
|
||||
self.assertEqual(len(results.violations), data['violations'])
|
||||
if data['violations'] > 0:
|
||||
num_violations = 0 if data['rule'] is None else 1
|
||||
self.assertEqual(len(results.violations), num_violations)
|
||||
if num_violations > 0:
|
||||
self.assertEqual(results.violations[0].rule, data['rule'])
|
||||
|
||||
@data(
|
||||
{'expression': '${x}', 'rule': None},
|
||||
{'expression': '${{unbalanced}', 'rule': Rules.mako_unparseable_expression},
|
||||
{'expression': '${x | n}', 'rule': Rules.mako_invalid_html_filter},
|
||||
{'expression': '${x | n, unicode}', 'rule': None},
|
||||
{'expression': '${x | n, decode.utf8}', 'rule': None},
|
||||
{'expression': '${x | h}', 'rule': Rules.mako_unwanted_html_filter},
|
||||
{'expression': ' ## ${commented_out | h}', 'rule': None},
|
||||
{'expression': '${x | n, dump_js_escaped_json}', 'rule': Rules.mako_invalid_html_filter},
|
||||
)
|
||||
def test_check_mako_expressions_in_html(self, data):
|
||||
@@ -378,7 +425,7 @@ class TestMakoTemplateLinter(TestLinter):
|
||||
{'expression': '${x | n}', 'rule': Rules.mako_invalid_js_filter},
|
||||
{'expression': '${x | h}', 'rule': Rules.mako_invalid_js_filter},
|
||||
{'expression': '${x | n, dump_js_escaped_json}', 'rule': None},
|
||||
{'expression': '${x | n, unicode}', 'rule': None},
|
||||
{'expression': '${x | n, decode.utf8}', 'rule': None},
|
||||
)
|
||||
def test_check_mako_expressions_in_javascript(self, data):
|
||||
"""
|
||||
@@ -401,7 +448,7 @@ class TestMakoTemplateLinter(TestLinter):
|
||||
|
||||
@data(
|
||||
{'expression': '${x}', 'rule': Rules.mako_invalid_js_filter},
|
||||
{'expression': '${x | n, js_escaped_string}', 'rule': None},
|
||||
{'expression': '"${x | n, js_escaped_string}"', 'rule': None},
|
||||
)
|
||||
def test_check_mako_expressions_in_require_js(self, data):
|
||||
"""
|
||||
@@ -478,6 +525,63 @@ class TestMakoTemplateLinter(TestLinter):
|
||||
self.assertEqual(results.violations[3].rule, Rules.mako_invalid_js_filter)
|
||||
self.assertEqual(results.violations[4].rule, Rules.mako_unwanted_html_filter)
|
||||
|
||||
def test_check_mako_expressions_javascript_strings(self):
|
||||
"""
|
||||
Test _check_mako_file_is_safe javascript string specific rules.
|
||||
- mako_js_missing_quotes
|
||||
- mako_js_html_string
|
||||
"""
|
||||
linter = MakoTemplateLinter()
|
||||
results = FileResults('')
|
||||
|
||||
mako_template = textwrap.dedent("""
|
||||
<%page expression_filter="h"/>
|
||||
<script type="text/javascript">
|
||||
var valid1 = '${x | n, js_escaped_string} ${y | n, js_escaped_string}'
|
||||
var valid2 = '${x | n, js_escaped_string} ${y | n, js_escaped_string}'
|
||||
var valid3 = 'string' + ' ${x | n, js_escaped_string} '
|
||||
var valid4 = "${Text(_('Some mixed text{begin_span}with html{end_span}')).format(
|
||||
begin_span=HTML('<span>'),
|
||||
end_span=HTML('</span>'),
|
||||
) | n, js_escaped_string}"
|
||||
var valid5 = " " + "${Text(_('Please {link_start}send us e-mail{link_end}.')).format(
|
||||
link_start=HTML('<a href="#" id="feedback_email">'),
|
||||
link_end=HTML('</a>'),
|
||||
) | n, js_escaped_string}";
|
||||
var invalid1 = ${x | n, js_escaped_string};
|
||||
var invalid2 = '<strong>${x | n, js_escaped_string}</strong>'
|
||||
var invalid3 = '<strong>${x | n, dump_js_escaped_json}</strong>'
|
||||
</script>
|
||||
""")
|
||||
|
||||
linter._check_mako_file_is_safe(mako_template, results)
|
||||
|
||||
self.assertEqual(len(results.violations), 3)
|
||||
self.assertEqual(results.violations[0].rule, Rules.mako_js_missing_quotes)
|
||||
self.assertEqual(results.violations[1].rule, Rules.mako_js_html_string)
|
||||
self.assertEqual(results.violations[2].rule, Rules.mako_js_html_string)
|
||||
|
||||
def test_check_javascript_in_mako_javascript_context(self):
|
||||
"""
|
||||
Test _check_mako_file_is_safe with JavaScript error in JavaScript
|
||||
context.
|
||||
"""
|
||||
linter = MakoTemplateLinter()
|
||||
results = FileResults('')
|
||||
|
||||
mako_template = textwrap.dedent("""
|
||||
<%page expression_filter="h"/>
|
||||
<script type="text/javascript">
|
||||
var message = '<p>' + msg + '</p>';
|
||||
</script>
|
||||
""")
|
||||
|
||||
linter._check_mako_file_is_safe(mako_template, results)
|
||||
|
||||
self.assertEqual(len(results.violations), 1)
|
||||
self.assertEqual(results.violations[0].rule, Rules.javascript_concat_html)
|
||||
self.assertEqual(results.violations[0].start_line, 4)
|
||||
|
||||
@data(
|
||||
{'template': "\n${x | n}", 'parseable': True},
|
||||
{
|
||||
@@ -536,10 +640,10 @@ class TestMakoTemplateLinter(TestLinter):
|
||||
expressions = linter._find_mako_expressions(data['template'])
|
||||
|
||||
self.assertEqual(len(expressions), 1)
|
||||
start_index = expressions[0]['start_index']
|
||||
end_index = expressions[0]['end_index']
|
||||
start_index = expressions[0].start_index
|
||||
end_index = expressions[0].end_index
|
||||
self.assertEqual(data['template'][start_index:end_index], data['template'].strip())
|
||||
self.assertEqual(expressions[0]['expression'], data['template'].strip())
|
||||
self.assertEqual(expressions[0].expression, data['template'].strip())
|
||||
|
||||
@data(
|
||||
{'template': " ${{unparseable} ${}", 'start_index': 1},
|
||||
@@ -553,8 +657,8 @@ class TestMakoTemplateLinter(TestLinter):
|
||||
|
||||
expressions = linter._find_mako_expressions(data['template'])
|
||||
self.assertTrue(2 <= len(expressions))
|
||||
self.assertEqual(expressions[0]['start_index'], data['start_index'])
|
||||
self.assertIsNone(expressions[0]['expression'])
|
||||
self.assertEqual(expressions[0].start_index, data['start_index'])
|
||||
self.assertIsNone(expressions[0].expression)
|
||||
|
||||
@data(
|
||||
{
|
||||
@@ -577,6 +681,10 @@ class TestMakoTemplateLinter(TestLinter):
|
||||
'template': r""" ${" \" \\"} """,
|
||||
'result': {'start_index': 3, 'end_index': 11, 'quote_length': 1}
|
||||
},
|
||||
{
|
||||
'template': "${'broken string}",
|
||||
'result': {'start_index': 2, 'end_index': None, 'quote_length': None}
|
||||
},
|
||||
)
|
||||
def test_parse_string(self, data):
|
||||
"""
|
||||
@@ -592,10 +700,11 @@ class TestMakoTemplateLinter(TestLinter):
|
||||
}
|
||||
|
||||
self.assertDictEqual(string_dict, data['result'])
|
||||
self.assertEqual(data['template'][parse_string.start_index:parse_string.end_index], parse_string.string)
|
||||
start_index = parse_string.start_index + parse_string.quote_length
|
||||
end_index = parse_string.end_index - parse_string.quote_length
|
||||
self.assertEqual(data['template'][start_index:end_index], parse_string.string_inner)
|
||||
if parse_string.end_index is not None:
|
||||
self.assertEqual(data['template'][parse_string.start_index:parse_string.end_index], parse_string.string)
|
||||
start_inner_index = parse_string.start_index + parse_string.quote_length
|
||||
end_inner_index = parse_string.end_index - parse_string.quote_length
|
||||
self.assertEqual(data['template'][start_inner_index:end_inner_index], parse_string.string_inner)
|
||||
|
||||
|
||||
@ddt
|
||||
@@ -750,21 +859,22 @@ class TestJavaScriptLinter(TestLinter):
|
||||
"""
|
||||
Test JavaScriptLinter
|
||||
"""
|
||||
|
||||
@data(
|
||||
{'template': 'var m = "Plain text " + message + "plain text"', 'rule': None},
|
||||
{'template': 'var m = "檌檒濦 " + message + "plain text"', 'rule': None},
|
||||
{'template': 'var m = "<p>" + message + "</p>"', 'rule': Rules.javascript_concat_html},
|
||||
{'template': ' // var m = "<p>" + commentedOutMessage + "</p>"', 'rule': None},
|
||||
{'template': 'var m = " <p> " + message + " </p> "', 'rule': Rules.javascript_concat_html},
|
||||
{'template': 'var m = " <p> " + message + " broken string', 'rule': Rules.javascript_concat_html},
|
||||
)
|
||||
def test_concat_with_html(self, data):
|
||||
"""
|
||||
Test _check_javascript_file_is_safe with concatenating strings and HTML
|
||||
Test check_javascript_file_is_safe with concatenating strings and HTML
|
||||
"""
|
||||
linter = JavaScriptLinter()
|
||||
results = FileResults('')
|
||||
|
||||
linter._check_javascript_file_is_safe(data['template'], results)
|
||||
linter.check_javascript_file_is_safe(data['template'], results)
|
||||
self._validate_data_rule(data, results)
|
||||
|
||||
@data(
|
||||
@@ -789,12 +899,12 @@ class TestJavaScriptLinter(TestLinter):
|
||||
)
|
||||
def test_jquery_append(self, data):
|
||||
"""
|
||||
Test _check_javascript_file_is_safe with JQuery append()
|
||||
Test check_javascript_file_is_safe with JQuery append()
|
||||
"""
|
||||
linter = JavaScriptLinter()
|
||||
results = FileResults('')
|
||||
|
||||
linter._check_javascript_file_is_safe(data['template'], results)
|
||||
linter.check_javascript_file_is_safe(data['template'], results)
|
||||
|
||||
self._validate_data_rule(data, results)
|
||||
|
||||
@@ -810,18 +920,19 @@ class TestJavaScriptLinter(TestLinter):
|
||||
{'template': 'test.prepend($("<div/>"))', 'rule': None},
|
||||
{'template': 'test.prepend(HtmlUtils.ensureHtml(htmlSnippet).toString())', 'rule': None},
|
||||
{'template': 'HtmlUtils.prepend($el, someHtml)', 'rule': None},
|
||||
{'template': 'test.prepend("broken string)', 'rule': Rules.javascript_jquery_prepend},
|
||||
{'template': 'test.prepend("fail on concat" + test.render().el)', 'rule': Rules.javascript_jquery_prepend},
|
||||
{'template': 'test.prepend("fail on concat" + testEl)', 'rule': Rules.javascript_jquery_prepend},
|
||||
{'template': 'test.prepend(message)', 'rule': Rules.javascript_jquery_prepend},
|
||||
)
|
||||
def test_jquery_prepend(self, data):
|
||||
"""
|
||||
Test _check_javascript_file_is_safe with JQuery prepend()
|
||||
Test check_javascript_file_is_safe with JQuery prepend()
|
||||
"""
|
||||
linter = JavaScriptLinter()
|
||||
results = FileResults('')
|
||||
|
||||
linter._check_javascript_file_is_safe(data['template'], results)
|
||||
linter.check_javascript_file_is_safe(data['template'], results)
|
||||
|
||||
self._validate_data_rule(data, results)
|
||||
|
||||
@@ -846,14 +957,14 @@ class TestJavaScriptLinter(TestLinter):
|
||||
)
|
||||
def test_jquery_insertion(self, data):
|
||||
"""
|
||||
Test _check_javascript_file_is_safe with JQuery insertion functions
|
||||
Test check_javascript_file_is_safe with JQuery insertion functions
|
||||
other than append(), prepend() and html() that take content as an
|
||||
argument (e.g. before(), after()).
|
||||
"""
|
||||
linter = JavaScriptLinter()
|
||||
results = FileResults('')
|
||||
|
||||
linter._check_javascript_file_is_safe(data['template'], results)
|
||||
linter.check_javascript_file_is_safe(data['template'], results)
|
||||
|
||||
self._validate_data_rule(data, results)
|
||||
|
||||
@@ -877,14 +988,14 @@ class TestJavaScriptLinter(TestLinter):
|
||||
)
|
||||
def test_jquery_insert_to_target(self, data):
|
||||
"""
|
||||
Test _check_javascript_file_is_safe with JQuery insert to target
|
||||
Test check_javascript_file_is_safe with JQuery insert to target
|
||||
functions that take a target as an argument, like appendTo() and
|
||||
prependTo().
|
||||
"""
|
||||
linter = JavaScriptLinter()
|
||||
results = FileResults('')
|
||||
|
||||
linter._check_javascript_file_is_safe(data['template'], results)
|
||||
linter.check_javascript_file_is_safe(data['template'], results)
|
||||
|
||||
self._validate_data_rule(data, results)
|
||||
|
||||
@@ -897,18 +1008,18 @@ class TestJavaScriptLinter(TestLinter):
|
||||
{'template': 'test.html(HtmlUtils.ensureHtml(htmlSnippet).toString())', 'rule': None},
|
||||
{'template': 'HtmlUtils.setHtml($el, someHtml)', 'rule': None},
|
||||
{'template': 'test.html("any string")', 'rule': Rules.javascript_jquery_html},
|
||||
{'template': 'test.html("broken string)', 'rule': Rules.javascript_jquery_html},
|
||||
{'template': 'test.html("檌檒濦")', 'rule': Rules.javascript_jquery_html},
|
||||
{'template': 'test.html(anything)', 'rule': Rules.javascript_jquery_html},
|
||||
)
|
||||
def test_jquery_html(self, data):
|
||||
"""
|
||||
Test _check_javascript_file_is_safe with JQuery html()
|
||||
Test check_javascript_file_is_safe with JQuery html()
|
||||
"""
|
||||
linter = JavaScriptLinter()
|
||||
results = FileResults('')
|
||||
|
||||
linter._check_javascript_file_is_safe(data['template'], results)
|
||||
|
||||
linter.check_javascript_file_is_safe(data['template'], results)
|
||||
self._validate_data_rule(data, results)
|
||||
|
||||
@data(
|
||||
@@ -918,26 +1029,26 @@ class TestJavaScriptLinter(TestLinter):
|
||||
)
|
||||
def test_javascript_interpolate(self, data):
|
||||
"""
|
||||
Test _check_javascript_file_is_safe with interpolate()
|
||||
Test check_javascript_file_is_safe with interpolate()
|
||||
"""
|
||||
linter = JavaScriptLinter()
|
||||
results = FileResults('')
|
||||
|
||||
linter._check_javascript_file_is_safe(data['template'], results)
|
||||
linter.check_javascript_file_is_safe(data['template'], results)
|
||||
|
||||
self._validate_data_rule(data, results)
|
||||
|
||||
@data(
|
||||
{'template': '_.escape()', 'rule': None},
|
||||
{'template': 'anything.escape()', 'rule': Rules.javascript_escape},
|
||||
{'template': '_.escape(message)', 'rule': None},
|
||||
{'template': 'anything.escape(message)', 'rule': Rules.javascript_escape},
|
||||
)
|
||||
def test_javascript_interpolate(self, data):
|
||||
"""
|
||||
Test _check_javascript_file_is_safe with interpolate()
|
||||
Test check_javascript_file_is_safe with interpolate()
|
||||
"""
|
||||
linter = JavaScriptLinter()
|
||||
results = FileResults('')
|
||||
|
||||
linter._check_javascript_file_is_safe(data['template'], results)
|
||||
linter.check_javascript_file_is_safe(data['template'], results)
|
||||
|
||||
self._validate_data_rule(data, results)
|
||||
|
||||
Reference in New Issue
Block a user