check-templates: Check Django/Handlebars templates differently.

This is a bit hacky, but avoids incorrectly checking Handlebars
templates for Django style templates, which in particular interacts
poorly with ICU Message plural syntax.
This commit is contained in:
Tim Abbott 2023-10-11 16:56:46 -07:00
parent b2ea3125b2
commit 533ff863a7
4 changed files with 81 additions and 44 deletions

View File

@ -101,7 +101,7 @@ def check_html_templates(templates: Iterable[str], all_dups: bool, fix: bool) ->
sys.exit(1)
for fn in templates:
tokens = validate(fn)
tokens = validate(fn, template_format="django")
if not validate_indent_html(fn, tokens, fix):
sys.exit(1)
@ -111,7 +111,7 @@ def check_handlebars_templates(templates: Iterable[str], fix: bool) -> None:
templates = [fn for fn in templates if fn.endswith(".hbs")]
for fn in templates:
tokens = validate(fn)
tokens = validate(fn, template_format="handlebars")
if not validate_indent_html(fn, tokens, fix):
sys.exit(1)

View File

@ -48,7 +48,7 @@ class Token:
self.parent_token: Optional[Token] = None
def tokenize(text: str) -> List[Token]:
def tokenize(text: str, template_format: Optional[str] = None) -> List[Token]:
in_code_block = False
def advance(n: int) -> None:
@ -70,13 +70,13 @@ def tokenize(text: str) -> List[Token]:
return looking_at("{{!")
def looking_at_djangocomment() -> bool:
return looking_at("{#")
return template_format == "django" and looking_at("{#")
def looking_at_handlebars_partial() -> bool:
return looking_at("{{>")
return template_format == "handlebars" and looking_at("{{>")
def looking_at_handlebars_partial_block() -> bool:
return looking_at("{{#>")
return template_format == "handlebars" and looking_at("{{#>")
def looking_at_html_start() -> bool:
return looking_at("<") and not looking_at("</")
@ -88,29 +88,29 @@ def tokenize(text: str) -> List[Token]:
return looking_at("{{#") or looking_at("{{^") or looking_at("{{~#")
def looking_at_handlebars_else() -> bool:
return looking_at("{{else")
return template_format == "handlebars" and looking_at("{{else")
def looking_at_template_var() -> bool:
return looking_at("{")
def looking_at_handlebars_end() -> bool:
return looking_at("{{/") or looking_at("{{~/")
return template_format == "handlebars" and (looking_at("{{/") or looking_at("{{~/"))
def looking_at_django_start() -> bool:
return looking_at("{% ")
return template_format == "django" and looking_at("{% ")
def looking_at_django_else() -> bool:
return looking_at("{% else") or looking_at("{% elif")
return template_format == "django" and (looking_at("{% else") or looking_at("{% elif"))
def looking_at_django_end() -> bool:
return looking_at("{% end")
return template_format == "django" and looking_at("{% end")
def looking_at_jinja2_end_whitespace_stripped() -> bool:
return looking_at("{%- end")
return template_format == "django" and looking_at("{%- end")
def looking_at_jinja2_start_whitespace_stripped_type2() -> bool:
# This function detects tag like {%- if foo -%}...{% endif %}
return looking_at("{%-") and not looking_at("{%- end")
return template_format == "django" and looking_at("{%-") and not looking_at("{%- end")
def looking_at_whitespace() -> bool:
return looking_at("\n") or looking_at(" ")
@ -343,7 +343,11 @@ def tag_flavor(token: Token) -> Optional[str]:
raise AssertionError(f"tools programmer neglected to handle {kind} tokens")
def validate(fn: Optional[str] = None, text: Optional[str] = None) -> List[Token]:
def validate(
fn: Optional[str] = None,
text: Optional[str] = None,
template_format: Optional[str] = None,
) -> List[Token]:
assert fn or text
if fn is None:
@ -356,7 +360,7 @@ def validate(fn: Optional[str] = None, text: Optional[str] = None) -> List[Token
lines = text.split("\n")
try:
tokens = tokenize(text)
tokens = tokenize(text, template_format=template_format)
except FormattedError as e:
raise TemplateParserError(
f"""

View File

@ -1,4 +1,5 @@
import unittest
from typing import Optional
from tools.lib.pretty_print import pretty_print_html
from tools.lib.template_parser import validate
@ -280,9 +281,9 @@ GOOD_HTML11 = """
"""
def pretty_print(html: str) -> str:
def pretty_print(html: str, template_format: Optional[str] = None) -> str:
fn = "<test str>"
tokens = validate(fn=fn, text=html)
tokens = validate(fn=fn, text=html, template_format=template_format)
return pretty_print_html(tokens, fn=fn)
@ -294,12 +295,12 @@ class TestPrettyPrinter(unittest.TestCase):
self.compare(pretty_print(GOOD_HTML), GOOD_HTML)
self.compare(pretty_print(BAD_HTML), GOOD_HTML)
self.compare(pretty_print(BAD_HTML1), GOOD_HTML1)
self.compare(pretty_print(BAD_HTML2), GOOD_HTML2)
self.compare(pretty_print(BAD_HTML2, template_format="handlebars"), GOOD_HTML2)
self.compare(pretty_print(BAD_HTML4), GOOD_HTML4)
self.compare(pretty_print(BAD_HTML5), GOOD_HTML5)
self.compare(pretty_print(BAD_HTML5, template_format="handlebars"), GOOD_HTML5)
self.compare(pretty_print(BAD_HTML6), GOOD_HTML6)
self.compare(pretty_print(BAD_HTML7), GOOD_HTML7)
self.compare(pretty_print(BAD_HTML8), GOOD_HTML8)
self.compare(pretty_print(BAD_HTML9), GOOD_HTML9)
self.compare(pretty_print(BAD_HTML10), GOOD_HTML10)
self.compare(pretty_print(BAD_HTML7, template_format="handlebars"), GOOD_HTML7)
self.compare(pretty_print(BAD_HTML8, template_format="handlebars"), GOOD_HTML8)
self.compare(pretty_print(BAD_HTML9, template_format="handlebars"), GOOD_HTML9)
self.compare(pretty_print(BAD_HTML10, template_format="django"), GOOD_HTML10)
self.compare(pretty_print(BAD_HTML11), GOOD_HTML11)

View File

@ -20,9 +20,10 @@ class ParserTest(unittest.TestCase):
error: str,
fn: Optional[str] = None,
text: Optional[str] = None,
template_format: Optional[str] = None,
) -> None:
with self.assertRaisesRegex(TemplateParserError, error):
validate(fn=fn, text=text)
validate(fn=fn, text=text, template_format=template_format)
def test_is_django_block_tag(self) -> None:
self.assertTrue(is_django_block_tag("block"))
@ -47,7 +48,7 @@ class ParserTest(unittest.TestCase):
<p>{{stream}}</p>
{{/with}}
"""
validate(text=my_html)
validate(text=my_html, template_format="handlebars")
def test_validate_handlebars_partial_block(self) -> None:
my_html = """
@ -55,7 +56,7 @@ class ParserTest(unittest.TestCase):
<p>hello!</p>
{{/generic_thing}}
"""
validate(text=my_html)
validate(text=my_html, template_format="handlebars")
def test_validate_bad_handlebars_partial_block(self) -> None:
my_html = """
@ -63,7 +64,9 @@ class ParserTest(unittest.TestCase):
<p>hello!</p>
{{# generic_thing}}
"""
self._assert_validate_error("Missing end tag for the token at row 4 13!", text=my_html)
self._assert_validate_error(
"Missing end tag for the token at row 4 13!", text=my_html, template_format="handlebars"
)
def test_validate_comment(self) -> None:
my_html = """
@ -79,7 +82,7 @@ class ParserTest(unittest.TestCase):
<p>bar</p>
{% endif %}
"""
validate(text=my_html)
validate(text=my_html, template_format="django")
my_html = """
{% block "content" %}
@ -124,13 +127,18 @@ class ParserTest(unittest.TestCase):
'''Tag missing "}}" at line 2 col 13:"{{# foo
"''',
text=my_html,
template_format="handlebars",
)
def test_validate_incomplete_handlebars_tag_2(self) -> None:
my_html = """
{{# foo }
"""
self._assert_validate_error('Tag missing "}}" at line 2 col 13:"{{# foo }\n"', text=my_html)
self._assert_validate_error(
'Tag missing "}}" at line 2 col 13:"{{# foo }\n"',
text=my_html,
template_format="handlebars",
)
def test_validate_incomplete_django_tag_1(self) -> None:
my_html = """
@ -140,13 +148,18 @@ class ParserTest(unittest.TestCase):
'''Tag missing "%}" at line 2 col 13:"{% foo
"''',
text=my_html,
template_format="django",
)
def test_validate_incomplete_django_tag_2(self) -> None:
my_html = """
{% foo %
"""
self._assert_validate_error('Tag missing "%}" at line 2 col 13:"{% foo %\n"', text=my_html)
self._assert_validate_error(
'Tag missing "%}" at line 2 col 13:"{% foo %\n"',
text=my_html,
template_format="django",
)
def test_validate_incomplete_html_tag_1(self) -> None:
my_html = """
@ -230,7 +243,10 @@ class ParserTest(unittest.TestCase):
this is foo
{% endif %}
"""
validate(text=my_html)
validate(
text=my_html,
template_format="django",
)
def test_validate_jinja2_whitespace_markers_2(self) -> None:
my_html = """
@ -238,7 +254,10 @@ class ParserTest(unittest.TestCase):
this is foo
{%- endif %}
"""
validate(text=my_html)
validate(
text=my_html,
template_format="django",
)
def test_validate_jinja2_whitespace_markers_3(self) -> None:
my_html = """
@ -246,7 +265,10 @@ class ParserTest(unittest.TestCase):
this is foo
{% endif -%}
"""
validate(text=my_html)
validate(
text=my_html,
template_format="django",
)
def test_validate_jinja2_whitespace_markers_4(self) -> None:
my_html = """
@ -254,7 +276,10 @@ class ParserTest(unittest.TestCase):
this is foo
{% endif %}
"""
validate(text=my_html)
validate(
text=my_html,
template_format="django",
)
def test_validate_mismatch_jinja2_whitespace_markers_1(self) -> None:
my_html = """
@ -262,7 +287,11 @@ class ParserTest(unittest.TestCase):
this is foo
{%- if bar %}
"""
self._assert_validate_error("Missing end tag", text=my_html)
self._assert_validate_error(
"Missing end tag",
text=my_html,
template_format="django",
)
def test_validate_jinja2_whitespace_type2_markers(self) -> None:
my_html = """
@ -270,7 +299,10 @@ class ParserTest(unittest.TestCase):
this is foo
{% endif %}
"""
validate(text=my_html)
validate(
text=my_html,
template_format="django",
)
def test_tokenize(self) -> None:
tag = "<!DOCTYPE html>"
@ -303,41 +335,41 @@ class ParserTest(unittest.TestCase):
self.assertEqual(token.tag, "a")
tag = "{{#with foo}}bla"
token = tokenize(tag)[0]
token = tokenize(tag, template_format="handlebars")[0]
self.assertEqual(token.kind, "handlebars_start")
self.assertEqual(token.tag, "with")
tag = "{{/with}}bla"
token = tokenize(tag)[0]
token = tokenize(tag, template_format="handlebars")[0]
self.assertEqual(token.kind, "handlebars_end")
self.assertEqual(token.tag, "with")
tag = "{{#> compose_banner }}bla"
token = tokenize(tag)[0]
token = tokenize(tag, template_format="handlebars")[0]
self.assertEqual(token.kind, "handlebars_partial_block")
self.assertEqual(token.tag, "compose_banner")
tag = "{% if foo %}bla"
token = tokenize(tag)[0]
token = tokenize(tag, template_format="django")[0]
self.assertEqual(token.kind, "django_start")
self.assertEqual(token.tag, "if")
tag = "{% endif %}bla"
token = tokenize(tag)[0]
token = tokenize(tag, template_format="django")[0]
self.assertEqual(token.kind, "django_end")
self.assertEqual(token.tag, "if")
tag = "{% if foo -%}bla"
token = tokenize(tag)[0]
token = tokenize(tag, template_format="django")[0]
self.assertEqual(token.kind, "jinja2_whitespace_stripped_start")
self.assertEqual(token.tag, "if")
tag = "{%- endif %}bla"
token = tokenize(tag)[0]
token = tokenize(tag, template_format="django")[0]
self.assertEqual(token.kind, "jinja2_whitespace_stripped_end")
self.assertEqual(token.tag, "if")
tag = "{%- if foo -%}bla"
token = tokenize(tag)[0]
token = tokenize(tag, template_format="django")[0]
self.assertEqual(token.kind, "jinja2_whitespace_stripped_type2_start")
self.assertEqual(token.tag, "if")