From de6505215b1525b1dbb32f4354891157b46a0550 Mon Sep 17 00:00:00 2001 From: Christopher Pickering Date: Mon, 2 Aug 2021 12:31:59 -0500 Subject: [PATCH] added rule for {% url. added regex flags to rules --- docs/djlint/rules.rst | 1 + src/djlint/lint.py | 31 +++++++++++++++++++++++++++++-- src/djlint/rules.yaml | 23 +++++++++++++++++++++++ tests/test_djlint.py | 10 ++++++++++ 4 files changed, 63 insertions(+), 2 deletions(-) diff --git a/docs/djlint/rules.rst b/docs/djlint/rules.rst index 0b66478..f11fd3f 100644 --- a/docs/djlint/rules.rst +++ b/docs/djlint/rules.rst @@ -57,4 +57,5 @@ A good rule consists of - Name - Code - Codes beginning with "E" signify error, and "W" warning. - Message - Message to display when error is found. +- Flags - Regex flags. Defaults to re.DOTALL. ex: re.I|re.M - Patterns - regex expressions that will find the error. diff --git a/src/djlint/lint.py b/src/djlint/lint.py index 28cda0b..f9a7c8e 100644 --- a/src/djlint/lint.py +++ b/src/djlint/lint.py @@ -1,13 +1,37 @@ """Djlint html linter.""" -import re from pathlib import Path +import regex as re import yaml rules = yaml.load( (Path(__file__).parent / "rules.yaml").read_text(encoding="utf8"), Loader=yaml.SafeLoader, ) +flags = { + "re.A": re.A, + "re.ASCII": re.ASCII, + "re.I": re.I, + "re.IGNORECASE": re.IGNORECASE, + "re.M": re.M, + "re.MULTILINE": re.MULTILINE, + "re.S": re.S, + "re.DOTALL": re.DOTALL, + "re.X": re.X, + "re.VERBOSE": re.VERBOSE, + "re.L": re.L, + "re.LOCALE": re.LOCALE, +} + + +def build_flags(flag_list): + """Build list of regex flags.""" + split_flags = flag_list.split("|") + + combined_flags = 0 + for flag in split_flags: + combined_flags |= flags[flag.strip()] + return combined_flags def get_line(start, line_ends): @@ -35,7 +59,10 @@ def lint_file(ignore: str, this_file: Path): rule = rule["rule"] for pattern in rule["patterns"]: - for match in re.finditer(pattern, html, re.DOTALL): + + for match in re.finditer( + pattern, html, flags=build_flags(rule.get("flags", "re.DOTALL")) + ): errors[file_name].append( { "code": rule["name"], diff --git a/src/djlint/rules.yaml b/src/djlint/rules.yaml index 2de4147..63ce58e 100644 --- a/src/djlint/rules.yaml +++ b/src/djlint/rules.yaml @@ -1,6 +1,7 @@ - rule: name: E001 message: Variables should be wrapped in a single whitespace. + flags: re.DOTALL patterns: # open - '{{[^\s#]+' @@ -16,82 +17,104 @@ - rule: name: E002 message: Double quotes should be used in tags. + flags: re.DOTALL patterns: - '{%.?extends\s+?[^\"]\w+' - rule: name: W003 message: 'Endblock should have name. Ex: {% endblock body %}.' + flags: re.DOTALL patterns: - '{%\s*?endblock\s*?%}' - rule: name: W004 message: Status urls should follow {% static path/to/file %} pattern. + flags: re.DOTALL # this should be using the static path from django settings patterns: - <(?:link|img|script)\s[^\>]*?(?:href|src)=[\"\']/?static/? - rule: name: W005 message: Html tag should have lang attribute. + flags: re.DOTALL|re.I patterns: - - rule: name: W006 message: Img tag should have alt, height and width attributes. + flags: re.DOTALL|re.I patterns: - - rule: name: W007 message: should be present before the html tag. + flags: re.DOTALL|re.I patterns: - ^(?:(?!(.+\r?\n){1,}).)*<[a-zA-Z]+\d? - rule: name: W016 message: Missing title tag in html. + flags: re.DOTALL|re.I patterns: - ]*?>(?:(?!).)*</html> - rule: name: W017 message: Tag should be self closing. + flags: re.DOTALL|re.I patterns: - <(img|input|area|base|br|col|embed|hr|link|meta|param|source|track|wbr|command|keygen|menuitem|path)[^>]*?[^/]> +- rule: + name: W018 + message: Internal links should use the {% url ... %} pattern. + flags: re.DOTALL|re.I + patterns: + - <a\s+?[^>]*?\Khref=[\"|'](?!https?:)/?[^/][^/][^>]* diff --git a/tests/test_djlint.py b/tests/test_djlint.py index 12c0aff..018ab8d 100644 --- a/tests/test_djlint.py +++ b/tests/test_djlint.py @@ -200,6 +200,16 @@ def test_W017(runner, tmp_file): assert "W017 1:" in result.output +def test_W018(runner, tmp_file): + write_to_file( + tmp_file.name, + b'<a class="drop-link" href="/Collections?handler=RemoveAgreement&id=@a.Id">', + ) + result = runner.invoke(djlint, [tmp_file.name]) + assert result.exit_code == 0 + assert "W018 1:" in result.output + + def test_check(runner, tmp_file): write_to_file(tmp_file.name, b"<div></div>") result = runner.invoke(djlint, [tmp_file.name], "--check")