Skip to content

Commit b13c1b5

Browse files
author
LB Johnston
committed
add new rule django_block_translate_trimmed
- the rule will enforce the usage of `trimmed` when blocktranslate or blocktrans is in use
1 parent 4c36043 commit b13c1b5

File tree

9 files changed

+234
-0
lines changed

9 files changed

+234
-0
lines changed

curlylint/check.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import click
44

55
from curlylint.rules.aria_role.aria_role import aria_role
6+
from curlylint.rules.django_block_translate_trimmed.django_block_translate_trimmed import (
7+
django_block_translate_trimmed,
8+
)
69
from curlylint.rules.django_forms_rendering.django_forms_rendering import (
710
django_forms_rendering,
811
)
@@ -20,6 +23,7 @@
2023

2124
checks = {
2225
"aria_role": aria_role,
26+
"django_block_translate_trimmed": django_block_translate_trimmed,
2327
"django_forms_rendering": django_forms_rendering,
2428
"html_has_lang": html_has_lang,
2529
"image_alt": image_alt,

curlylint/rules/django_block_translate_trimmed/__init__.py

Whitespace-only changes.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from curlylint import ast
2+
from curlylint.check_node import CheckNode, build_tree
3+
from curlylint.issue import Issue
4+
5+
DJANGO_FORMS_RENDERING = "django_block_translate_trimmed"
6+
7+
RULE = {
8+
"id": "django_block_translate_trimmed",
9+
"type": "internationalisation",
10+
"docs": {
11+
"description": "Enforces the use of Django’s `trimmed` option when using `blocktranslate`/`blocktrans` so that translations do not contain leading or trailing whitespace.",
12+
"url": "https://www.curlylint.org/docs/rules/django_block_translate_trimmed",
13+
"impact": "Serious",
14+
"tags": ["cat:language"],
15+
"resources": [
16+
"[Django translations](https://docs.djangoproject.com/en/stable/topics/i18n/translation/)",
17+
],
18+
},
19+
"schema": {
20+
"$schema": "http://json-schema.org/draft/2019-09/schema#",
21+
"oneOf": [
22+
{
23+
"const": True,
24+
"title": "Template tags of blocktranslate or blocktrans must use the trimmed option",
25+
"examples": [True],
26+
}
27+
],
28+
},
29+
}
30+
31+
BLOCK_NAMES = ['translate', 'trans']
32+
33+
34+
def find_valid(node, file):
35+
36+
if isinstance(node.value, ast.JinjaElement):
37+
for part in node.value.parts:
38+
tag = part.tag
39+
content_parts = tag.content.split(' ')
40+
if tag.name == 'block' and content_parts[0] in BLOCK_NAMES:
41+
if 'trimmed' not in content_parts:
42+
return [
43+
Issue.from_node(
44+
file,
45+
node,
46+
f"`{tag}` must use the `trimmed` option",
47+
DJANGO_FORMS_RENDERING,
48+
)
49+
]
50+
51+
if not node.children:
52+
return []
53+
54+
return sum(
55+
(
56+
find_valid(child, file)
57+
for child in node.children
58+
),
59+
[],
60+
)
61+
62+
63+
def django_block_translate_trimmed(file, target):
64+
root = CheckNode(None)
65+
build_tree(root, file.tree)
66+
src = file.source.lower()
67+
68+
print('django_block_translate_trimmed', src)
69+
if "blocktrans" in src or "blocktranslate" in src:
70+
return find_valid(root, file)
71+
72+
return []
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
[
2+
{
3+
"label": "Using blocktranslate with trimmed",
4+
"template": "{% blocktranslate trimmed %} some value {% endblocktranslate %}",
5+
"example": true,
6+
"config": true,
7+
"output": []
8+
},
9+
{
10+
"label": "Using blocktranslate without trimmed",
11+
"template": "{% blocktranslate %} some value {% endblocktranslate %}",
12+
"example": true,
13+
"config": true,
14+
"output": [
15+
{
16+
"file": "test.html",
17+
"column": 1,
18+
"line": 1,
19+
"code": "django_block_translate_trimmed",
20+
"message": "`{% block translate %}` must use the `trimmed` option"
21+
}
22+
]
23+
},
24+
{
25+
"label": "Using blocktrans with trimmed",
26+
"template": "{% blocktrans trimmed %} some value {% endblocktrans %}",
27+
"example": true,
28+
"config": true,
29+
"output": []
30+
},
31+
{
32+
"label": "Using blocktrans without trimmed",
33+
"template": "{% blocktrans %} some value {% endblocktrans %}",
34+
"example": true,
35+
"config": true,
36+
"output": [
37+
{
38+
"file": "test.html",
39+
"column": 1,
40+
"line": 1,
41+
"code": "django_block_translate_trimmed",
42+
"message": "`{% block trans %}` must use the `trimmed` option"
43+
}
44+
]
45+
}
46+
]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import unittest
2+
3+
from curlylint.rules.rule_test_case import RulesTestMeta
4+
5+
from .django_block_translate_trimmed import django_block_translate_trimmed
6+
7+
8+
class TestRule(unittest.TestCase, metaclass=RulesTestMeta):
9+
fixtures = __file__.replace(".py", ".json")
10+
rule = django_block_translate_trimmed

website/build_rules.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
import toml
99

1010
from curlylint.rules.aria_role import aria_role
11+
from curlylint.rules.django_block_translate_trimmed import (
12+
django_block_translate_trimmed,
13+
)
1114
from curlylint.rules.django_forms_rendering import django_forms_rendering
1215
from curlylint.rules.html_has_lang import html_has_lang
1316
from curlylint.rules.image_alt import image_alt
@@ -18,6 +21,7 @@
1821

1922
rules = [
2023
aria_role.RULE,
24+
django_block_translate_trimmed.RULE,
2125
django_forms_rendering.RULE,
2226
html_has_lang.RULE,
2327
image_alt.RULE,

website/docs/rules/all.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import TabItem from "@theme/TabItem";
1010
import CodeSnippet from "@theme/CodeSnippet";
1111

1212
- [aria_role](aria_role): Elements with ARIA roles must use a valid, non-abstract ARIA role
13+
- [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.
1314
- [django_forms_rendering](django_forms_rendering): Disallows using Django’s convenience form rendering helpers, for which the markup isn’t screen-reader-friendly
1415
- [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.
1516
- [image_alt](image_alt): `<img>` elements must have a `alt` attribute, either with meaningful text, or an empty string for decorative images
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
---
2+
# This file is auto-generated, please do not update manually.
3+
id: django_block_translate_trimmed
4+
title: django_block_translate_trimmed
5+
custom_edit_url: https://github.com/thibaudcolas/curlylint/edit/main/curlylint/rules/django_block_translate_trimmed/django_block_translate_trimmed.py
6+
---
7+
8+
import Tabs from "@theme/Tabs";
9+
import TabItem from "@theme/TabItem";
10+
import CodeSnippet from "@theme/CodeSnippet";
11+
12+
> Enforces the use of Django’s `trimmed` option when using `blocktranslate`/`blocktrans` so that translations do not contain leading or trailing whitespace.
13+
>
14+
> User impact: **Serious**
15+
16+
This rule supports the following configuration:
17+
18+
<Tabs
19+
groupId="config-language"
20+
defaultValue="toml"
21+
values={[
22+
{ label: "TOML", value: "toml" },
23+
{ label: "Shell", value: "shell" },
24+
]}
25+
>
26+
<TabItem value="toml">
27+
<CodeSnippet
28+
snippet={`# Template tags of blocktranslate or blocktrans must use the trimmed option\ndjango_block_translate_trimmed = true`}
29+
annotations={[]}
30+
lang="toml"
31+
/>
32+
</TabItem>
33+
<TabItem value="shell">
34+
<CodeSnippet
35+
snippet={`# Template tags of blocktranslate or blocktrans must use the trimmed option\ncurlylint --rule 'django_block_translate_trimmed: true' .`}
36+
annotations={[]}
37+
lang="shell"
38+
/>
39+
</TabItem>
40+
</Tabs>
41+
42+
## Success
43+
44+
<Tabs
45+
groupId="config-language"
46+
defaultValue="toml"
47+
values={[
48+
{ label: "TOML", value: "toml" },
49+
{ label: "Shell", value: "shell" },
50+
]}
51+
>
52+
<TabItem value="toml">
53+
<CodeSnippet
54+
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 %}`}
55+
annotations={[]}
56+
lang="html"
57+
/>
58+
</TabItem>
59+
<TabItem value="shell">
60+
<CodeSnippet
61+
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 %}`}
62+
annotations={[]}
63+
lang="html"
64+
/>
65+
</TabItem>
66+
</Tabs>
67+
68+
## Fail
69+
70+
<Tabs
71+
groupId="config-language"
72+
defaultValue="toml"
73+
values={[
74+
{ label: "TOML", value: "toml" },
75+
{ label: "Shell", value: "shell" },
76+
]}
77+
>
78+
<TabItem value="toml">
79+
<CodeSnippet
80+
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\n`}
81+
annotations={[{"file": "test.html", "column": 1, "line": 3, "code": "django_block_translate_trimmed", "message": "`{% block translate %}` must use the `trimmed` option"}, {"file": "test.html", "column": 1, "line": 6, "code": "django_block_translate_trimmed", "message": "`{% block trans %}` must use the `trimmed` option"}]}
82+
lang="html"
83+
/>
84+
</TabItem>
85+
<TabItem value="shell">
86+
<CodeSnippet
87+
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\n`}
88+
annotations={[{"file": "test.html", "column": 1, "line": 3, "code": "django_block_translate_trimmed", "message": "`{% block translate %}` must use the `trimmed` option"}, {"file": "test.html", "column": 1, "line": 6, "code": "django_block_translate_trimmed", "message": "`{% block trans %}` must use the `trimmed` option"}]}
89+
lang="html"
90+
/>
91+
</TabItem>
92+
</Tabs>
93+
94+
## Resources
95+
96+
- [Django translations](https://docs.djangoproject.com/en/stable/topics/i18n/translation/)

website/rules-sidebar.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module.exports = [
22
"rules/aria_role",
3+
"rules/django_block_translate_trimmed",
34
"rules/django_forms_rendering",
45
"rules/html_has_lang",
56
"rules/image_alt",

0 commit comments

Comments
 (0)