Skip to content

Commit

Permalink
add new rule django_block_translate_trimmed
Browse files Browse the repository at this point in the history
- the rule will enforce the usage of `trimmed` when blocktranslate or blocktrans is in use
  • Loading branch information
LB Johnston committed Apr 13, 2022
1 parent 4c36043 commit d8be7a2
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 3 deletions.
4 changes: 4 additions & 0 deletions curlylint/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import click

from curlylint.rules.aria_role.aria_role import aria_role
from curlylint.rules.django_block_translate_trimmed.django_block_translate_trimmed import (
django_block_translate_trimmed,
)
from curlylint.rules.django_forms_rendering.django_forms_rendering import (
django_forms_rendering,
)
Expand All @@ -20,6 +23,7 @@

checks = {
"aria_role": aria_role,
"django_block_translate_trimmed": django_block_translate_trimmed,
"django_forms_rendering": django_forms_rendering,
"html_has_lang": html_has_lang,
"image_alt": image_alt,
Expand Down
3 changes: 2 additions & 1 deletion curlylint/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,9 @@

DEFAULT_JINJA_STRUCTURED_ELEMENTS_NAMES = [
("autoescape", "endautoescape"),
("block", "endblock"),
("blocktranslate", "plural", "endblocktranslate"),
("blocktrans", "plural", "endblocktrans"),
("block", "endblock"),
("comment", "endcomment"),
("filter", "endfilter"),
("for", "else", "empty", "endfor"),
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from curlylint import ast
from curlylint.check_node import CheckNode, build_tree
from curlylint.issue import Issue

DJANGO_FORMS_RENDERING = "django_block_translate_trimmed"

RULE = {
"id": "django_block_translate_trimmed",
"type": "internationalisation",
"docs": {
"description": "Enforces the use of Django’s `trimmed` option when using `blocktranslate`/`blocktrans` so that translations do not contain leading or trailing whitespace.",
"url": "https://www.curlylint.org/docs/rules/django_block_translate_trimmed",
"impact": "Serious",
"tags": ["cat:language"],
"resources": [
"[Django translations](https://docs.djangoproject.com/en/stable/topics/i18n/translation/)",
],
},
"schema": {
"$schema": "http://json-schema.org/draft/2019-09/schema#",
"oneOf": [
{
"const": True,
"title": "Template tags of blocktranslate or blocktrans must use the trimmed option",
"examples": [True],
}
],
},
}

BLOCK_NAMES = ["blocktranslate", "blocktrans"]


def find_valid(node, file):

if isinstance(node.value, ast.JinjaElement):
for part in node.value.parts:

tag = part.tag

if tag.name in BLOCK_NAMES:
if "trimmed" not in tag.content.split(" "):
return [
Issue.from_node(
file,
node,
f"`{tag}` must use the `trimmed` option",
DJANGO_FORMS_RENDERING,
)
]

if not node.children:
return []

return sum(
(find_valid(child, file) for child in node.children),
[],
)


def django_block_translate_trimmed(file, target):
root = CheckNode(None)
build_tree(root, file.tree)
src = file.source.lower()

print("django_block_translate_trimmed", src)
if "blocktrans" in src or "blocktranslate" in src:
return find_valid(root, file)

return []
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
[
{
"label": "Using blocktranslate with trimmed",
"template": "{% blocktranslate trimmed %} some value {% endblocktranslate %}",
"example": true,
"config": true,
"output": []
},
{
"label": "Using blocktranslate without trimmed",
"template": "{% blocktranslate %} some value {% endblocktranslate %}",
"example": true,
"config": true,
"output": [
{
"file": "test.html",
"column": 1,
"line": 1,
"code": "django_block_translate_trimmed",
"message": "`{% blocktranslate %}` must use the `trimmed` option"
}
]
},
{
"label": "Using blocktrans with trimmed",
"template": "{% blocktrans trimmed %} some value {% endblocktrans %}",
"example": true,
"config": true,
"output": []
},
{
"label": "Using blocktrans without trimmed",
"template": "{% blocktrans %} some value {% endblocktrans %}",
"example": true,
"config": true,
"output": [
{
"file": "test.html",
"column": 1,
"line": 1,
"code": "django_block_translate_trimmed",
"message": "`{% blocktrans %}` must use the `trimmed` option"
}
]
},
{
"label": "Using blocktranslate with trimmed and other options",
"template": "{% blocktranslate trimmed with time_period=revision.created_at|timesince_simple %} some value {% endblocktranslate %}",
"example": true,
"config": true,
"output": []
},
{
"label": "Using blocktranslate without trimmed but with other options",
"template": "{% blocktranslate count counter=list|length %} some value {% endblocktranslate %}",
"example": true,
"config": true,
"output": [
{
"file": "test.html",
"column": 1,
"line": 1,
"code": "django_block_translate_trimmed",
"message": "`{% blocktranslate count counter=list|length %}` must use the `trimmed` option"
}
]
},
{
"label": "Using blocktrans with other options",
"template": "{% blocktrans trimmed with book_t=book|title %} some value {% endblocktrans %}",
"example": true,
"config": true,
"output": []
},
{
"label": "Using blocktrans without trimmed but with other options",
"template": "{% blocktrans with book_t=book|title %} some value {% endblocktrans %}",
"example": true,
"config": true,
"output": [
{
"file": "test.html",
"column": 1,
"line": 1,
"code": "django_block_translate_trimmed",
"message": "`{% blocktrans with book_t=book|title %}` must use the `trimmed` option"
}
]
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import unittest

from curlylint.rules.rule_test_case import RulesTestMeta

from .django_block_translate_trimmed import django_block_translate_trimmed


class TestRule(unittest.TestCase, metaclass=RulesTestMeta):
fixtures = __file__.replace(".py", ".json")
rule = django_block_translate_trimmed
4 changes: 4 additions & 0 deletions website/build_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import toml

from curlylint.rules.aria_role import aria_role
from curlylint.rules.django_block_translate_trimmed import (
django_block_translate_trimmed,
)
from curlylint.rules.django_forms_rendering import django_forms_rendering
from curlylint.rules.html_has_lang import html_has_lang
from curlylint.rules.image_alt import image_alt
Expand All @@ -18,6 +21,7 @@

rules = [
aria_role.RULE,
django_block_translate_trimmed.RULE,
django_forms_rendering.RULE,
html_has_lang.RULE,
image_alt.RULE,
Expand Down
5 changes: 3 additions & 2 deletions website/docs/rules/all.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import TabItem from "@theme/TabItem";
import CodeSnippet from "@theme/CodeSnippet";

- [aria_role](aria_role): Elements with ARIA roles must use a valid, non-abstract ARIA role
- [django_block_translate_trimmed](django_block_translate_trimmed): Enforces the use of Django’s `trimmed` option when using `blocktranslate`/`blocktrans` so that translations do not contain leading or trailing whitespace.
- [django_forms_rendering](django_forms_rendering): Disallows using Django’s convenience form rendering helpers, for which the markup isn’t screen-reader-friendly
- [html_has_lang](html_has_lang): `<html>` elements must have a `lang` attribute, using a [BCP 47](https://www.ietf.org/rfc/bcp/bcp47.txt) language tag.
- [image_alt](image_alt): `<img>` elements must have a `alt` attribute, either with meaningful text, or an empty string for decorative images
Expand All @@ -32,14 +33,14 @@ Here is a sample configuration with all of Curlylint’s rules enabled. Note **t
>
<TabItem value="toml">
<CodeSnippet
snippet={`[tool.curlylint.rules]\n# All role attributes must be valid.\n# See https://www.curlylint.org/docs/rules/aria_role.\naria_role = true\n# Forms cannot be rendered with as_table, as_ul, or as_p\n# See https://www.curlylint.org/docs/rules/django_forms_rendering.\ndjango_forms_rendering = true\n# The \`lang\` attribute must be present.\n# See https://www.curlylint.org/docs/rules/html_has_lang.\nhtml_has_lang = true\n# The \`alt\` attribute must be present.\n# See https://www.curlylint.org/docs/rules/image_alt.\nimage_alt = true\n# Use tabs.\n# See https://www.curlylint.org/docs/rules/indent.\nindent = "tab"\n# \`user-scalable=no\` must not be used, and \`maximum-scale\` should be 2 or above.\n# See https://www.curlylint.org/docs/rules/meta_viewport.\nmeta_viewport = true\n# The \`autofocus\` attribute must not be used.\n# See https://www.curlylint.org/docs/rules/no_autofocus.\nno_autofocus = true\n# Avoid positive \`tabindex\` values, change the order of elements on the page instead.\n# See https://www.curlylint.org/docs/rules/tabindex_no_positive.\ntabindex_no_positive = true`}
snippet={`[tool.curlylint.rules]\n# All role attributes must be valid.\n# See https://www.curlylint.org/docs/rules/aria_role.\naria_role = true\n# Template tags of blocktranslate or blocktrans must use the trimmed option\n# See https://www.curlylint.org/docs/rules/django_block_translate_trimmed.\ndjango_block_translate_trimmed = true\n# Forms cannot be rendered with as_table, as_ul, or as_p\n# See https://www.curlylint.org/docs/rules/django_forms_rendering.\ndjango_forms_rendering = true\n# The \`lang\` attribute must be present.\n# See https://www.curlylint.org/docs/rules/html_has_lang.\nhtml_has_lang = true\n# The \`alt\` attribute must be present.\n# See https://www.curlylint.org/docs/rules/image_alt.\nimage_alt = true\n# Use tabs.\n# See https://www.curlylint.org/docs/rules/indent.\nindent = "tab"\n# \`user-scalable=no\` must not be used, and \`maximum-scale\` should be 2 or above.\n# See https://www.curlylint.org/docs/rules/meta_viewport.\nmeta_viewport = true\n# The \`autofocus\` attribute must not be used.\n# See https://www.curlylint.org/docs/rules/no_autofocus.\nno_autofocus = true\n# Avoid positive \`tabindex\` values, change the order of elements on the page instead.\n# See https://www.curlylint.org/docs/rules/tabindex_no_positive.\ntabindex_no_positive = true`}
annotations={[]}
lang="toml"
/>
</TabItem>
<TabItem value="shell">
<CodeSnippet
snippet={`curlylint --rule 'aria_role: true' --rule 'django_forms_rendering: true' --rule 'html_has_lang: true' --rule 'image_alt: true' --rule 'indent: "tab"' --rule 'meta_viewport: true' --rule 'no_autofocus: true' --rule 'tabindex_no_positive: true' .`}
snippet={`curlylint --rule 'aria_role: true' --rule 'django_block_translate_trimmed: true' --rule 'django_forms_rendering: true' --rule 'html_has_lang: true' --rule 'image_alt: true' --rule 'indent: "tab"' --rule 'meta_viewport: true' --rule 'no_autofocus: true' --rule 'tabindex_no_positive: true' .`}
annotations={[]}
lang="shell"
/>
Expand Down
96 changes: 96 additions & 0 deletions website/docs/rules/django_block_translate_trimmed.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
---
# This file is auto-generated, please do not update manually.
id: django_block_translate_trimmed
title: django_block_translate_trimmed
custom_edit_url: https://github.com/thibaudcolas/curlylint/edit/main/curlylint/rules/django_block_translate_trimmed/django_block_translate_trimmed.py
---

import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import CodeSnippet from "@theme/CodeSnippet";

> Enforces the use of Django’s `trimmed` option when using `blocktranslate`/`blocktrans` so that translations do not contain leading or trailing whitespace.
>
> User impact: **Serious**
This rule supports the following configuration:

<Tabs
groupId="config-language"
defaultValue="toml"
values={[
{ label: "TOML", value: "toml" },
{ label: "Shell", value: "shell" },
]}
>
<TabItem value="toml">
<CodeSnippet
snippet={`# Template tags of blocktranslate or blocktrans must use the trimmed option\ndjango_block_translate_trimmed = true`}
annotations={[]}
lang="toml"
/>
</TabItem>
<TabItem value="shell">
<CodeSnippet
snippet={`# Template tags of blocktranslate or blocktrans must use the trimmed option\ncurlylint --rule 'django_block_translate_trimmed: true' .`}
annotations={[]}
lang="shell"
/>
</TabItem>
</Tabs>

## Success

<Tabs
groupId="config-language"
defaultValue="toml"
values={[
{ label: "TOML", value: "toml" },
{ label: "Shell", value: "shell" },
]}
>
<TabItem value="toml">
<CodeSnippet
snippet={`<!-- Good: Using blocktranslate with trimmed -->\n<!-- django_block_translate_trimmed = true -->\n{% blocktranslate trimmed %} some value {% endblocktranslate %}\n<!-- Good: Using blocktrans with trimmed -->\n<!-- django_block_translate_trimmed = true -->\n{% blocktrans trimmed %} some value {% endblocktrans %}\n<!-- Good: Using blocktranslate with trimmed and other options -->\n<!-- django_block_translate_trimmed = true -->\n{% blocktranslate trimmed with time_period=revision.created_at|timesince_simple %} some value {% endblocktranslate %}\n<!-- Good: Using blocktrans with other options -->\n<!-- django_block_translate_trimmed = true -->\n{% blocktrans trimmed with book_t=book|title %} some value {% endblocktrans %}`}
annotations={[]}
lang="html"
/>
</TabItem>
<TabItem value="shell">
<CodeSnippet
snippet={`<!-- Good: Using blocktranslate with trimmed -->\n<!-- curlylint --rule 'django_block_translate_trimmed: true' . -->\n{% blocktranslate trimmed %} some value {% endblocktranslate %}\n<!-- Good: Using blocktrans with trimmed -->\n<!-- curlylint --rule 'django_block_translate_trimmed: true' . -->\n{% blocktrans trimmed %} some value {% endblocktrans %}\n<!-- Good: Using blocktranslate with trimmed and other options -->\n<!-- curlylint --rule 'django_block_translate_trimmed: true' . -->\n{% blocktranslate trimmed with time_period=revision.created_at|timesince_simple %} some value {% endblocktranslate %}\n<!-- Good: Using blocktrans with other options -->\n<!-- curlylint --rule 'django_block_translate_trimmed: true' . -->\n{% blocktrans trimmed with book_t=book|title %} some value {% endblocktrans %}`}
annotations={[]}
lang="html"
/>
</TabItem>
</Tabs>

## Fail

<Tabs
groupId="config-language"
defaultValue="toml"
values={[
{ label: "TOML", value: "toml" },
{ label: "Shell", value: "shell" },
]}
>
<TabItem value="toml">
<CodeSnippet
snippet={`<!-- Bad: Using blocktranslate without trimmed -->\n<!-- django_block_translate_trimmed = true -->\n{% blocktranslate %} some value {% endblocktranslate %}\n<!-- Bad: Using blocktrans without trimmed -->\n<!-- django_block_translate_trimmed = true -->\n{% blocktrans %} some value {% endblocktrans %}\n<!-- Bad: Using blocktranslate without trimmed but with other options -->\n<!-- django_block_translate_trimmed = true -->\n{% blocktranslate count counter=list|length %} some value {% endblocktranslate %}\n<!-- Bad: Using blocktrans without trimmed but with other options -->\n<!-- django_block_translate_trimmed = true -->\n{% blocktrans with book_t=book|title %} some value {% endblocktrans %}\n\n`}
annotations={[{"file": "test.html", "column": 1, "line": 3, "code": "django_block_translate_trimmed", "message": "`{% blocktranslate %}` must use the `trimmed` option"}, {"file": "test.html", "column": 1, "line": 6, "code": "django_block_translate_trimmed", "message": "`{% blocktrans %}` must use the `trimmed` option"}, {"file": "test.html", "column": 1, "line": 9, "code": "django_block_translate_trimmed", "message": "`{% blocktranslate count counter=list|length %}` must use the `trimmed` option"}, {"file": "test.html", "column": 1, "line": 12, "code": "django_block_translate_trimmed", "message": "`{% blocktrans with book_t=book|title %}` must use the `trimmed` option"}]}
lang="html"
/>
</TabItem>
<TabItem value="shell">
<CodeSnippet
snippet={`<!-- Bad: Using blocktranslate without trimmed -->\n<!-- curlylint --rule 'django_block_translate_trimmed: true' . -->\n{% blocktranslate %} some value {% endblocktranslate %}\n<!-- Bad: Using blocktrans without trimmed -->\n<!-- curlylint --rule 'django_block_translate_trimmed: true' . -->\n{% blocktrans %} some value {% endblocktrans %}\n<!-- Bad: Using blocktranslate without trimmed but with other options -->\n<!-- curlylint --rule 'django_block_translate_trimmed: true' . -->\n{% blocktranslate count counter=list|length %} some value {% endblocktranslate %}\n<!-- Bad: Using blocktrans without trimmed but with other options -->\n<!-- curlylint --rule 'django_block_translate_trimmed: true' . -->\n{% blocktrans with book_t=book|title %} some value {% endblocktrans %}\n\n`}
annotations={[{"file": "test.html", "column": 1, "line": 3, "code": "django_block_translate_trimmed", "message": "`{% blocktranslate %}` must use the `trimmed` option"}, {"file": "test.html", "column": 1, "line": 6, "code": "django_block_translate_trimmed", "message": "`{% blocktrans %}` must use the `trimmed` option"}, {"file": "test.html", "column": 1, "line": 9, "code": "django_block_translate_trimmed", "message": "`{% blocktranslate count counter=list|length %}` must use the `trimmed` option"}, {"file": "test.html", "column": 1, "line": 12, "code": "django_block_translate_trimmed", "message": "`{% blocktrans with book_t=book|title %}` must use the `trimmed` option"}]}
lang="html"
/>
</TabItem>
</Tabs>

## Resources

- [Django translations](https://docs.djangoproject.com/en/stable/topics/i18n/translation/)
1 change: 1 addition & 0 deletions website/rules-sidebar.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = [
"rules/aria_role",
"rules/django_block_translate_trimmed",
"rules/django_forms_rendering",
"rules/html_has_lang",
"rules/image_alt",
Expand Down

0 comments on commit d8be7a2

Please sign in to comment.