mirror of https://github.com/zulip/zulip.git
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:
parent
b2ea3125b2
commit
533ff863a7
|
@ -101,7 +101,7 @@ def check_html_templates(templates: Iterable[str], all_dups: bool, fix: bool) ->
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
for fn in templates:
|
for fn in templates:
|
||||||
tokens = validate(fn)
|
tokens = validate(fn, template_format="django")
|
||||||
if not validate_indent_html(fn, tokens, fix):
|
if not validate_indent_html(fn, tokens, fix):
|
||||||
sys.exit(1)
|
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")]
|
templates = [fn for fn in templates if fn.endswith(".hbs")]
|
||||||
|
|
||||||
for fn in templates:
|
for fn in templates:
|
||||||
tokens = validate(fn)
|
tokens = validate(fn, template_format="handlebars")
|
||||||
if not validate_indent_html(fn, tokens, fix):
|
if not validate_indent_html(fn, tokens, fix):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ class Token:
|
||||||
self.parent_token: Optional[Token] = None
|
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
|
in_code_block = False
|
||||||
|
|
||||||
def advance(n: int) -> None:
|
def advance(n: int) -> None:
|
||||||
|
@ -70,13 +70,13 @@ def tokenize(text: str) -> List[Token]:
|
||||||
return looking_at("{{!")
|
return looking_at("{{!")
|
||||||
|
|
||||||
def looking_at_djangocomment() -> bool:
|
def looking_at_djangocomment() -> bool:
|
||||||
return looking_at("{#")
|
return template_format == "django" and looking_at("{#")
|
||||||
|
|
||||||
def looking_at_handlebars_partial() -> bool:
|
def looking_at_handlebars_partial() -> bool:
|
||||||
return looking_at("{{>")
|
return template_format == "handlebars" and looking_at("{{>")
|
||||||
|
|
||||||
def looking_at_handlebars_partial_block() -> bool:
|
def looking_at_handlebars_partial_block() -> bool:
|
||||||
return looking_at("{{#>")
|
return template_format == "handlebars" and looking_at("{{#>")
|
||||||
|
|
||||||
def looking_at_html_start() -> bool:
|
def looking_at_html_start() -> bool:
|
||||||
return looking_at("<") and not looking_at("</")
|
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("{{~#")
|
return looking_at("{{#") or looking_at("{{^") or looking_at("{{~#")
|
||||||
|
|
||||||
def looking_at_handlebars_else() -> bool:
|
def looking_at_handlebars_else() -> bool:
|
||||||
return looking_at("{{else")
|
return template_format == "handlebars" and looking_at("{{else")
|
||||||
|
|
||||||
def looking_at_template_var() -> bool:
|
def looking_at_template_var() -> bool:
|
||||||
return looking_at("{")
|
return looking_at("{")
|
||||||
|
|
||||||
def looking_at_handlebars_end() -> bool:
|
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:
|
def looking_at_django_start() -> bool:
|
||||||
return looking_at("{% ")
|
return template_format == "django" and looking_at("{% ")
|
||||||
|
|
||||||
def looking_at_django_else() -> bool:
|
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:
|
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:
|
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:
|
def looking_at_jinja2_start_whitespace_stripped_type2() -> bool:
|
||||||
# This function detects tag like {%- if foo -%}...{% endif %}
|
# 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:
|
def looking_at_whitespace() -> bool:
|
||||||
return looking_at("\n") or looking_at(" ")
|
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")
|
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
|
assert fn or text
|
||||||
|
|
||||||
if fn is None:
|
if fn is None:
|
||||||
|
@ -356,7 +360,7 @@ def validate(fn: Optional[str] = None, text: Optional[str] = None) -> List[Token
|
||||||
lines = text.split("\n")
|
lines = text.split("\n")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tokens = tokenize(text)
|
tokens = tokenize(text, template_format=template_format)
|
||||||
except FormattedError as e:
|
except FormattedError as e:
|
||||||
raise TemplateParserError(
|
raise TemplateParserError(
|
||||||
f"""
|
f"""
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from tools.lib.pretty_print import pretty_print_html
|
from tools.lib.pretty_print import pretty_print_html
|
||||||
from tools.lib.template_parser import validate
|
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>"
|
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)
|
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(GOOD_HTML), GOOD_HTML)
|
||||||
self.compare(pretty_print(BAD_HTML), GOOD_HTML)
|
self.compare(pretty_print(BAD_HTML), GOOD_HTML)
|
||||||
self.compare(pretty_print(BAD_HTML1), GOOD_HTML1)
|
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_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_HTML6), GOOD_HTML6)
|
||||||
self.compare(pretty_print(BAD_HTML7), GOOD_HTML7)
|
self.compare(pretty_print(BAD_HTML7, template_format="handlebars"), GOOD_HTML7)
|
||||||
self.compare(pretty_print(BAD_HTML8), GOOD_HTML8)
|
self.compare(pretty_print(BAD_HTML8, template_format="handlebars"), GOOD_HTML8)
|
||||||
self.compare(pretty_print(BAD_HTML9), GOOD_HTML9)
|
self.compare(pretty_print(BAD_HTML9, template_format="handlebars"), GOOD_HTML9)
|
||||||
self.compare(pretty_print(BAD_HTML10), GOOD_HTML10)
|
self.compare(pretty_print(BAD_HTML10, template_format="django"), GOOD_HTML10)
|
||||||
self.compare(pretty_print(BAD_HTML11), GOOD_HTML11)
|
self.compare(pretty_print(BAD_HTML11), GOOD_HTML11)
|
||||||
|
|
|
@ -20,9 +20,10 @@ class ParserTest(unittest.TestCase):
|
||||||
error: str,
|
error: str,
|
||||||
fn: Optional[str] = None,
|
fn: Optional[str] = None,
|
||||||
text: Optional[str] = None,
|
text: Optional[str] = None,
|
||||||
|
template_format: Optional[str] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
with self.assertRaisesRegex(TemplateParserError, error):
|
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:
|
def test_is_django_block_tag(self) -> None:
|
||||||
self.assertTrue(is_django_block_tag("block"))
|
self.assertTrue(is_django_block_tag("block"))
|
||||||
|
@ -47,7 +48,7 @@ class ParserTest(unittest.TestCase):
|
||||||
<p>{{stream}}</p>
|
<p>{{stream}}</p>
|
||||||
{{/with}}
|
{{/with}}
|
||||||
"""
|
"""
|
||||||
validate(text=my_html)
|
validate(text=my_html, template_format="handlebars")
|
||||||
|
|
||||||
def test_validate_handlebars_partial_block(self) -> None:
|
def test_validate_handlebars_partial_block(self) -> None:
|
||||||
my_html = """
|
my_html = """
|
||||||
|
@ -55,7 +56,7 @@ class ParserTest(unittest.TestCase):
|
||||||
<p>hello!</p>
|
<p>hello!</p>
|
||||||
{{/generic_thing}}
|
{{/generic_thing}}
|
||||||
"""
|
"""
|
||||||
validate(text=my_html)
|
validate(text=my_html, template_format="handlebars")
|
||||||
|
|
||||||
def test_validate_bad_handlebars_partial_block(self) -> None:
|
def test_validate_bad_handlebars_partial_block(self) -> None:
|
||||||
my_html = """
|
my_html = """
|
||||||
|
@ -63,7 +64,9 @@ class ParserTest(unittest.TestCase):
|
||||||
<p>hello!</p>
|
<p>hello!</p>
|
||||||
{{# generic_thing}}
|
{{# 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:
|
def test_validate_comment(self) -> None:
|
||||||
my_html = """
|
my_html = """
|
||||||
|
@ -79,7 +82,7 @@ class ParserTest(unittest.TestCase):
|
||||||
<p>bar</p>
|
<p>bar</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
validate(text=my_html)
|
validate(text=my_html, template_format="django")
|
||||||
|
|
||||||
my_html = """
|
my_html = """
|
||||||
{% block "content" %}
|
{% block "content" %}
|
||||||
|
@ -124,13 +127,18 @@ class ParserTest(unittest.TestCase):
|
||||||
'''Tag missing "}}" at line 2 col 13:"{{# foo
|
'''Tag missing "}}" at line 2 col 13:"{{# foo
|
||||||
"''',
|
"''',
|
||||||
text=my_html,
|
text=my_html,
|
||||||
|
template_format="handlebars",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_validate_incomplete_handlebars_tag_2(self) -> None:
|
def test_validate_incomplete_handlebars_tag_2(self) -> None:
|
||||||
my_html = """
|
my_html = """
|
||||||
{{# foo }
|
{{# 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:
|
def test_validate_incomplete_django_tag_1(self) -> None:
|
||||||
my_html = """
|
my_html = """
|
||||||
|
@ -140,13 +148,18 @@ class ParserTest(unittest.TestCase):
|
||||||
'''Tag missing "%}" at line 2 col 13:"{% foo
|
'''Tag missing "%}" at line 2 col 13:"{% foo
|
||||||
"''',
|
"''',
|
||||||
text=my_html,
|
text=my_html,
|
||||||
|
template_format="django",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_validate_incomplete_django_tag_2(self) -> None:
|
def test_validate_incomplete_django_tag_2(self) -> None:
|
||||||
my_html = """
|
my_html = """
|
||||||
{% foo %
|
{% 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:
|
def test_validate_incomplete_html_tag_1(self) -> None:
|
||||||
my_html = """
|
my_html = """
|
||||||
|
@ -230,7 +243,10 @@ class ParserTest(unittest.TestCase):
|
||||||
this is foo
|
this is foo
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
validate(text=my_html)
|
validate(
|
||||||
|
text=my_html,
|
||||||
|
template_format="django",
|
||||||
|
)
|
||||||
|
|
||||||
def test_validate_jinja2_whitespace_markers_2(self) -> None:
|
def test_validate_jinja2_whitespace_markers_2(self) -> None:
|
||||||
my_html = """
|
my_html = """
|
||||||
|
@ -238,7 +254,10 @@ class ParserTest(unittest.TestCase):
|
||||||
this is foo
|
this is foo
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
"""
|
"""
|
||||||
validate(text=my_html)
|
validate(
|
||||||
|
text=my_html,
|
||||||
|
template_format="django",
|
||||||
|
)
|
||||||
|
|
||||||
def test_validate_jinja2_whitespace_markers_3(self) -> None:
|
def test_validate_jinja2_whitespace_markers_3(self) -> None:
|
||||||
my_html = """
|
my_html = """
|
||||||
|
@ -246,7 +265,10 @@ class ParserTest(unittest.TestCase):
|
||||||
this is foo
|
this is foo
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
"""
|
"""
|
||||||
validate(text=my_html)
|
validate(
|
||||||
|
text=my_html,
|
||||||
|
template_format="django",
|
||||||
|
)
|
||||||
|
|
||||||
def test_validate_jinja2_whitespace_markers_4(self) -> None:
|
def test_validate_jinja2_whitespace_markers_4(self) -> None:
|
||||||
my_html = """
|
my_html = """
|
||||||
|
@ -254,7 +276,10 @@ class ParserTest(unittest.TestCase):
|
||||||
this is foo
|
this is foo
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
validate(text=my_html)
|
validate(
|
||||||
|
text=my_html,
|
||||||
|
template_format="django",
|
||||||
|
)
|
||||||
|
|
||||||
def test_validate_mismatch_jinja2_whitespace_markers_1(self) -> None:
|
def test_validate_mismatch_jinja2_whitespace_markers_1(self) -> None:
|
||||||
my_html = """
|
my_html = """
|
||||||
|
@ -262,7 +287,11 @@ class ParserTest(unittest.TestCase):
|
||||||
this is foo
|
this is foo
|
||||||
{%- if bar %}
|
{%- 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:
|
def test_validate_jinja2_whitespace_type2_markers(self) -> None:
|
||||||
my_html = """
|
my_html = """
|
||||||
|
@ -270,7 +299,10 @@ class ParserTest(unittest.TestCase):
|
||||||
this is foo
|
this is foo
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
validate(text=my_html)
|
validate(
|
||||||
|
text=my_html,
|
||||||
|
template_format="django",
|
||||||
|
)
|
||||||
|
|
||||||
def test_tokenize(self) -> None:
|
def test_tokenize(self) -> None:
|
||||||
tag = "<!DOCTYPE html>"
|
tag = "<!DOCTYPE html>"
|
||||||
|
@ -303,41 +335,41 @@ class ParserTest(unittest.TestCase):
|
||||||
self.assertEqual(token.tag, "a")
|
self.assertEqual(token.tag, "a")
|
||||||
|
|
||||||
tag = "{{#with foo}}bla"
|
tag = "{{#with foo}}bla"
|
||||||
token = tokenize(tag)[0]
|
token = tokenize(tag, template_format="handlebars")[0]
|
||||||
self.assertEqual(token.kind, "handlebars_start")
|
self.assertEqual(token.kind, "handlebars_start")
|
||||||
self.assertEqual(token.tag, "with")
|
self.assertEqual(token.tag, "with")
|
||||||
|
|
||||||
tag = "{{/with}}bla"
|
tag = "{{/with}}bla"
|
||||||
token = tokenize(tag)[0]
|
token = tokenize(tag, template_format="handlebars")[0]
|
||||||
self.assertEqual(token.kind, "handlebars_end")
|
self.assertEqual(token.kind, "handlebars_end")
|
||||||
self.assertEqual(token.tag, "with")
|
self.assertEqual(token.tag, "with")
|
||||||
|
|
||||||
tag = "{{#> compose_banner }}bla"
|
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.kind, "handlebars_partial_block")
|
||||||
self.assertEqual(token.tag, "compose_banner")
|
self.assertEqual(token.tag, "compose_banner")
|
||||||
|
|
||||||
tag = "{% if foo %}bla"
|
tag = "{% if foo %}bla"
|
||||||
token = tokenize(tag)[0]
|
token = tokenize(tag, template_format="django")[0]
|
||||||
self.assertEqual(token.kind, "django_start")
|
self.assertEqual(token.kind, "django_start")
|
||||||
self.assertEqual(token.tag, "if")
|
self.assertEqual(token.tag, "if")
|
||||||
|
|
||||||
tag = "{% endif %}bla"
|
tag = "{% endif %}bla"
|
||||||
token = tokenize(tag)[0]
|
token = tokenize(tag, template_format="django")[0]
|
||||||
self.assertEqual(token.kind, "django_end")
|
self.assertEqual(token.kind, "django_end")
|
||||||
self.assertEqual(token.tag, "if")
|
self.assertEqual(token.tag, "if")
|
||||||
|
|
||||||
tag = "{% if foo -%}bla"
|
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.kind, "jinja2_whitespace_stripped_start")
|
||||||
self.assertEqual(token.tag, "if")
|
self.assertEqual(token.tag, "if")
|
||||||
|
|
||||||
tag = "{%- endif %}bla"
|
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.kind, "jinja2_whitespace_stripped_end")
|
||||||
self.assertEqual(token.tag, "if")
|
self.assertEqual(token.tag, "if")
|
||||||
|
|
||||||
tag = "{%- if foo -%}bla"
|
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.kind, "jinja2_whitespace_stripped_type2_start")
|
||||||
self.assertEqual(token.tag, "if")
|
self.assertEqual(token.tag, "if")
|
||||||
|
|
Loading…
Reference in New Issue