From b8562e463af7e4b7fd2b23a097a578e073930781 Mon Sep 17 00:00:00 2001 From: Christopher Pickering Date: Wed, 6 Oct 2021 15:00:08 +0200 Subject: [PATCH] added rule H025, closes #58 --- docs/djlint/changelog.rst | 3 +- src/djlint/lint.py | 66 +++++++++++++++++++++++++++++++-------- src/djlint/rules.yaml | 5 +++ tests/test_linter.py | 14 +++++++-- 4 files changed, 71 insertions(+), 17 deletions(-) diff --git a/docs/djlint/changelog.rst b/docs/djlint/changelog.rst index cf5abd4..7f7b189 100644 --- a/docs/djlint/changelog.rst +++ b/docs/djlint/changelog.rst @@ -8,13 +8,14 @@ Next Release - Added rule H022 to find http links - Added rule H023 to find entity references - Added rule H024 to find type on scripts and styles +- Added rule H025 to check for orphan tags. Thanks to https://stackoverflow.com/a/1736801/10265880 - Improved attribute formatting - Updated ``blank_line_after_tag`` option to add newline regardless of location - Fixed django ``trans`` tag formatting - Added formatting for inline styles - Added formatting for template conditions inside attributes - Added srcset as possible url location in linter rules - +- Special thanks to `jayvdb `_ 0.5.3 ----- - Change stdout for ``--reformat/check`` options to only print new html when using stdin as the input diff --git a/src/djlint/lint.py b/src/djlint/lint.py index 6c17fd3..32f6001 100644 --- a/src/djlint/lint.py +++ b/src/djlint/lint.py @@ -1,4 +1,5 @@ """Djlint html linter.""" +import copy from pathlib import Path from typing import Dict, List @@ -69,20 +70,59 @@ def lint_file(config: Config, this_file: Path) -> Dict: for pattern in rule["patterns"]: - for match in re.finditer( - re.compile(pattern, flags=build_flags(rule.get("flags", "re.DOTALL"))), - html, - ): + # rule H025 is a special case where the output must be an even num. + if rule["name"] == "H025": + open_tags = [] - if _should_ignore(config, html, match) is False: - errors[file_name].append( - { - "code": rule["name"], - "line": get_line(match.start(), line_ends), - "match": match.group().strip()[:20], - "message": rule["message"], - } - ) + for match in re.finditer( + re.compile( + pattern, flags=build_flags(rule.get("flags", "re.DOTALL")) + ), + html, + ): + # close tags should equal open tags + if match.group(1).split(" ")[0][0] != "/": + open_tags.append(match) + else: + for i, tag in enumerate(copy.deepcopy(open_tags)): + if ( + tag.group(1).split(" ")[0] + == match.group(1).split(" ")[0][1:] + ): + open_tags.pop(i) + break + else: + # there was no open tag matching the close tag + open_tags.append(match) + + for match in open_tags: + if _should_ignore(config, html, match) is False: + errors[file_name].append( + { + "code": rule["name"], + "line": get_line(match.start(), line_ends), + "match": match.group().strip()[:20], + "message": rule["message"], + } + ) + else: + + for match in re.finditer( + re.compile( + pattern, flags=build_flags(rule.get("flags", "re.DOTALL")) + ), + html, + ): + + if _should_ignore(config, html, match) is False: + errors[file_name].append( + { + "code": rule["name"], + "line": get_line(match.start(), line_ends), + "match": match.group().strip()[:20], + "message": rule["message"], + } + ) # remove duplicate matches for file_name, error_dict in errors.items(): diff --git a/src/djlint/rules.yaml b/src/djlint/rules.yaml index c331e60..e8764ef 100644 --- a/src/djlint/rules.yaml +++ b/src/djlint/rules.yaml @@ -299,3 +299,8 @@ flags: re.I patterns: - <(?:script|style)[^>]*?type=["|'][^>]*?> +- rule: + name: H025 + message: Tag seems to be an orphan. + patterns: + - <((?:\"[^\"<>]*\"['\"]*|'[^'<>]*'['\"]*|[^'\"<>])+)(? diff --git a/tests/test_linter.py b/tests/test_linter.py index a29119c..25c2966 100644 --- a/tests/test_linter.py +++ b/tests/test_linter.py @@ -7,7 +7,7 @@ run:: # for a single test - pytest tests/test_linter.py::test_H021 --cov=src/djlint --cov-branch \ + pytest tests/test_linter.py::test_H012 --cov=src/djlint --cov-branch \ --cov-report xml:coverage.xml --cov-report term-missing """ @@ -152,6 +152,7 @@ def test_H012(runner: CliRunner, tmp_file: TextIO) -> None: b"

{% if activity.reporting_groups|length <= 0 %}

{% trans 'General' %}

{% endif %}

", ) result = runner.invoke(djlint, [tmp_file.name]) + print(result.output) assert result.exit_code == 0 assert "H012 1:" not in result.output @@ -203,7 +204,7 @@ def test_H017(runner: CliRunner, tmp_file: TextIO) -> None: assert "H017 1:" in result.output # test colgroup tag - write_to_file(tmp_file.name, b"") + write_to_file(tmp_file.name, b"") result = runner.invoke(djlint, [tmp_file.name]) assert result.exit_code == 0 assert "H017 1:" not in result.output @@ -212,7 +213,7 @@ def test_H017(runner: CliRunner, tmp_file: TextIO) -> None: def test_DJ018(runner: CliRunner, tmp_file: TextIO) -> None: write_to_file( tmp_file.name, - b'\n
', + b'\n
', ) result = runner.invoke(djlint, [tmp_file.name]) assert result.exit_code == 1 @@ -306,6 +307,13 @@ def test_H024(runner: CliRunner, tmp_file: TextIO) -> None: assert "H024 1:" in result.output +def test_H025(runner: CliRunner, tmp_file: TextIO) -> None: + write_to_file(tmp_file.name, b"
") + result = runner.invoke(djlint, [tmp_file.name]) + assert result.exit_code == 1 + assert "H025 1:" in result.output + + def test_rules_not_matched_in_ignored_block( runner: CliRunner, tmp_file: TextIO ) -> None: