Skip to content

Commit

Permalink
feat(formatter): added white space cleanup for template tags
Browse files Browse the repository at this point in the history
closes #485
  • Loading branch information
christopherpickering committed May 8, 2023
1 parent 9050d30 commit 13d0d4f
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 64 deletions.
11 changes: 6 additions & 5 deletions docs/src/docs/linter.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,13 @@ A good rule consists of

Please include a test to validate the rule.


## Custom Rules

You can add custom rules just for your project by creating a `.djlint_rules.yaml` alongside
your `pyproject.toml`. Rules can be added to this files and djLint will pick them up.

### Pattern Rules

You can add rules that fails if one of the regex pattern has a match:

```yaml
Expand All @@ -128,6 +128,7 @@ You can add rules that fails if one of the regex pattern has a match:
```
### Python module Rules
You can add rules that import and execute a custom python function:
```yaml
Expand All @@ -143,14 +144,14 @@ every checked file. It must accept the following arguments:
::: content

- `rule`: The dict that represent your rule in `.djlint_rules.yaml`. You will typically
use this variable to access the rule name and message.
use this variable to access the rule name and message.
- `config`: The DJLint configuration object.
- `html`: The full html content of the file.
- `filepath`: Path to the file that we are currently checking.
- `line_ends`: List of line `start` and `end` character position that you can use with
`djlint.lint.get_line()` to get line numbers from a character position. See the exemple.
`djlint.lint.get_line()` to get line numbers from a character position. See the exemple.
- `*args, **kwargs`: We might add other arguments in the future, so you should include
those two arguments to reduce the risk of failure on djLint upgrade.
those two arguments to reduce the risk of failure on djLint upgrade.
:::

It must return a list of dict, one for each errors, with the following keys:
Expand All @@ -159,7 +160,7 @@ It must return a list of dict, one for each errors, with the following keys:

- `code`: Code name of the rule that report the error (typically `rule['name']`)
- `line`: Line number and character number on this line, separated by a ':' as a string.
For example `"2:3"` means that the error has been found on line 2, character 3
For example `"2:3"` means that the error has been found on line 2, character 3
- `match`: The part of the content that contains the error
- `message`: The message that will be printed to signal the error (typically `rule['message']`)
:::
Expand Down
18 changes: 10 additions & 8 deletions docs/src/fr/docs/linter.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ Pour cela, créez un fichier `.djlint_rules.yaml` à côté de votre `pyproject.
Des règles peuvent être ajoutées à ce fichier et djLint les reprendra.

### Règle basé sur la recherche d'un regex

Vous pouvez ajouter une règle qui échouera si l'un des regex listés dans `patterns`
est trouvé dans le code html.

Expand All @@ -129,6 +130,7 @@ est trouvé dans le code html.
```
### Règle utilisant un module python externe
Vous pouvez ajouter une règle qui va importer et executer une fonction python
personalisée.
Expand All @@ -145,18 +147,18 @@ executé sur chacun des fichiers testés. La fonction doit accepter les argument
::: content

- `rule`: Le dictionnaire python qui représente votre règle dans `.djlint_rules.yaml`.
Utilisez cette variable pour accéder aux `name` et `message` que vous avez défini dans
le `yaml`.
Utilisez cette variable pour accéder aux `name` et `message` que vous avez défini dans
le `yaml`.
- `config`: L'objet de configuration global de DJLint.
- `html`: Le contenu html complet du fichier testé.
- `filepath`: Chemin du fichier testé.
- `line_ends`: Liste qui, pour chacune des lignes du fichier html testé, contient un
dictionnaire avec `start` et `end` qui donnent les indexes globaux dans le fichier du
début et fin de la ligne. Cette variable peut être utilisée avec `djlint.lint.get_line()`
pour récupérer le numéro de ligne à partir de l'indexe du caractère dans le fichier html.
dictionnaire avec `start` et `end` qui donnent les indexes globaux dans le fichier du
début et fin de la ligne. Cette variable peut être utilisée avec `djlint.lint.get_line()`
pour récupérer le numéro de ligne à partir de l'indexe du caractère dans le fichier html.
- `*args, **kwargs`: Il est possible que nous ajoutions d'autres arguments à l'avenir,
il est donc fortement conseillé d'ajouter ces deux arguments pour diminuer les risques
de bugs en cas de mise à jour de djLint.
il est donc fortement conseillé d'ajouter ces deux arguments pour diminuer les risques
de bugs en cas de mise à jour de djLint.
:::

La fonction doit retourner une liste de dictionnaire, un pour chacune des erreurs
Expand All @@ -166,7 +168,7 @@ trouvées. Le dictionnaire doit contenir les clées suivantes :

- `code`: Code de la règle qui rapporte l'erreur (généralement `rule['name']`)
- `line`: Numéro de ligne et numéro de caractère dans cette ligne, séparées par un `:`.
Par exemple, `"2:3"` veut dire que l'erreur a été trouvée sur la ligne 2, au caractère 3.
Par exemple, `"2:3"` veut dire que l'erreur a été trouvée sur la ligne 2, au caractère 3.
- `match`: La partie du contenu qui contient l'erreur
- `message`: Le message qui serra affiché pour signaler l'erreur (généralement `rule['message']`)
:::
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"format": "prettier --config .prettierrc \"{bin,docs}/**/*.{ts,css,less,scss,js,json,md,yaml,html}\" --write",
"postinstall": "python3 -m pip install --upgrade djlint",
"pre-commit": "lint-staged",
"commit": "git add . && pre-commit run; npm run pre-commit && cz --no-verify",
"commit": "git add . && pre-commit run; git add . && npm run pre-commit && cz --no-verify",
"test": "xo"
},
"author": {
Expand Down
84 changes: 36 additions & 48 deletions src/djlint/formatter/indent.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,42 @@

def indent_html(rawcode: str, config: Config) -> str:
"""Indent raw code."""
if config.profile not in ["handlebars", "golang"]:
# we can try to fix template tags. ignore handlebars
# this should be done before indenting to line length
# calc is preserved.

def fix_tag_spacing(html: str, match: re.Match) -> str:
if inside_ignored_block(config, html, match):
return match.group()

return f"{match.group(1)} {match.group(2)} {match.group(3)}"

"""
We should have tags like this:
{{ tag }}
{%- tag atrib -%}
"""
func = partial(fix_tag_spacing, rawcode)

rawcode = re.sub(
r"({%-?\+?)[ ]*?(\w(?:(?!%}).)*?)[ ]*?(\+?-?%})", func, rawcode
)

rawcode = re.sub(r"({{)[ ]*?(\w(?:(?!}}).)*?)[ ]*?(\+?-?}})", func, rawcode)

elif config.profile == "handlebars":

def fix_handlebars_template_tags(html: str, match: re.Match) -> str:
if inside_ignored_block(config, html, match):
return match.group()

return f"{match.group(1)} {match.group(2)}"

func = partial(fix_handlebars_template_tags, rawcode)
# handlebars templates
rawcode = re.sub(r"({{#(?:each|if).+?[^ ])(}})", func, rawcode)

rawcode_flat_list = re.split("\n", rawcode)

indent = config.indent
Expand Down Expand Up @@ -282,53 +318,6 @@ def indent_html(rawcode: str, config: Config) -> str:

beautified_code = beautified_code + tmp

# we can try to fix template tags. ignore handlebars
if config.profile not in ["handlebars", "golang"]:

def fix_non_handlebars_template_tags(
html: str, out_format: str, match: re.Match
) -> str:
if inside_ignored_block(config, html, match):
return match.group()

return out_format % (
match.group(1),
match.group(2),
match.group(3),
)

func = partial(fix_non_handlebars_template_tags, beautified_code, "%s %s%s")
beautified_code = re.sub(
r"({[{|%]\-?)(\w[^}].+?)([}|%]})", func, beautified_code
)

func = partial(fix_non_handlebars_template_tags, beautified_code, "%s%s %s")
beautified_code = re.sub(
r"({[{|%])([^}].+?[^\ \-])([}|%]})", func, beautified_code
)

func = partial(fix_non_handlebars_template_tags, beautified_code, "%s%s %s")
beautified_code = re.sub(
r"({[{|%])([^}].+?[^ -])(\-+?[}|%]})", func, beautified_code
)

elif config.profile == "handlebars":

def fix_handlebars_template_tags(
html: str, out_format: str, match: re.Match
) -> str:
if inside_ignored_block(config, html, match):
return match.group()

return out_format % (
match.group(1),
match.group(2),
)

func = partial(fix_handlebars_template_tags, beautified_code, "%s %s")
# handlebars templates
beautified_code = re.sub(r"({{#(?:each|if).+?[^ ])(}})", func, beautified_code)

# try to fix internal formatting of set tag
def format_set(config, match):
open_bracket = match.group(1)
Expand Down Expand Up @@ -372,7 +361,6 @@ def format_set(config, match):
contents = (
contents_split[0].strip() + " = " + contents_split[-1].strip()
)
pass

return f"{open_bracket} {tag} {contents} {close_braket}"

Expand Down
4 changes: 2 additions & 2 deletions tests/test_django/test_comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@
" </div>\n"
" <ul>\n"
" {% for i in items %}\n"
" <li>item {{i}}</li>\n"
" <li>item {{ i }}</li>\n"
" {% if i > 10 %}{% endif %}\n"
" <li>item {{i}}</li>\n"
" <li>item {{ i }}</li>\n"
" {% endfor %}\n"
" </ul>\n"
"</div>\n"
Expand Down
35 changes: 35 additions & 0 deletions tests/test_django/test_tag_spaces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Test django spaceless tag.
poetry run pytest tests/test_django/test_tag_spaces.py
"""
import pytest

from src.djlint.reformat import formatter
from tests.conftest import config_builder, printer

test_data = [
pytest.param(
("{% a %}\n" "{%b %}{%+c%}{%-d+%}\n" "{#a}{{g {%a%}}}{%l{{q}}+%}"),
(
"{% a %}\n"
"{% b %}{%+ c %}{%- d +%}\n"
"{#a}{{ g {% a % }}}{% l{{ q }} +%}\n"
),
({}),
id="messy stuff",
),
pytest.param(
("{% a %}\n" "{%b%}{%c%}{%-d+%}\n" "{#a}{{g {%a%}}}{%l{{q}}+%}"),
("{% a %}\n" "{%b%}{%c%}{%-d+%}\n" "{#a}{{g {%a%}}}{%l{{q}}+%}\n"),
({"profile": "handlebars"}),
id="messy stuff",
),
]


@pytest.mark.parametrize(("source", "expected", "args"), test_data)
def test_base(source, expected, args):
output = formatter(config_builder(args), source)

printer(expected, source, output)
assert expected == output

0 comments on commit 13d0d4f

Please sign in to comment.