djLint/src/djlint/lint.py

143 lines
4.9 KiB
Python
Raw Normal View History

2021-07-23 20:58:24 +00:00
"""Djlint html linter."""
2021-10-06 13:00:08 +00:00
import copy
2021-07-23 20:58:24 +00:00
from pathlib import Path
from typing import Dict, List
2021-07-23 20:58:24 +00:00
import regex as re
2021-07-23 20:58:24 +00:00
from .helpers import inside_ignored_block, inside_ignored_rule
from .settings import Config
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: str) -> int:
"""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
2021-07-23 20:58:24 +00:00
def get_line(start: int, line_ends: List) -> str:
2021-07-23 20:58:24 +00:00
"""Get the line number and index of match."""
line = list(filter(lambda pair: pair["end"] > start, line_ends))[0]
2021-09-17 08:25:01 +00:00
# pylint: disable=C0209
2021-07-23 20:58:24 +00:00
return "%d:%d" % (line_ends.index(line) + 1, start - line["start"])
def lint_file(config: Config, this_file: Path) -> Dict:
2021-07-23 20:58:24 +00:00
"""Check file for formatting errors."""
filename = str(this_file)
errors: dict = {filename: []}
2021-07-23 20:58:24 +00:00
html = this_file.read_text(encoding="utf8")
# build list of line ends for file
line_ends = [
{"start": m.start(), "end": m.end()}
for m in re.finditer(r"(?:.*\n)|(?:[^\n]+$)", html)
]
ignored_rules: List[str] = []
# remove ignored rules for file
for pattern, rules in config.per_file_ignores.items():
if re.search(pattern, this_file.as_posix(), re.VERBOSE):
ignored_rules += [x.strip() for x in rules.split(",")]
2021-10-04 13:26:22 +00:00
for rule in config.linter_rules:
2021-07-23 20:58:24 +00:00
rule = rule["rule"]
for pattern in rule["patterns"]:
# skip ignored rules
if rule["name"] in ignored_rules:
continue
2021-11-23 09:16:40 +00:00
# rule H025 is a special case where the output must be an even number.
2021-10-06 13:00:08 +00:00
if rule["name"] == "H025":
2021-10-11 08:59:19 +00:00
open_tags: List[re.Match] = []
2021-10-06 13:00:08 +00:00
for match in re.finditer(
re.compile(
pattern, flags=build_flags(rule.get("flags", "re.DOTALL"))
),
html,
):
2021-11-22 14:21:18 +00:00
if match.group(2) and not re.search(
re.compile(
2022-01-30 01:05:11 +00:00
rf"^/?{config.always_self_closing_html_tags}\b", re.I | re.X
),
2021-11-22 14:21:18 +00:00
match.group(2),
):
# close tags should equal open tags
2021-11-22 14:21:18 +00:00
if match.group(2)[0] != "/":
2021-10-11 07:59:10 +00:00
open_tags.insert(0, match)
else:
for i, tag in enumerate(copy.deepcopy(open_tags)):
2022-01-14 15:07:57 +00:00
if tag.group(3) == match.group(2)[1:]:
open_tags.pop(i)
break
else:
# there was no open tag matching the close tag
2021-10-11 07:59:10 +00:00
open_tags.insert(0, match)
2021-10-06 13:00:08 +00:00
for match in open_tags:
if (
inside_ignored_block(config, html, match) is False
and inside_ignored_rule(config, html, match, rule["name"])
is False
):
errors[filename].append(
2021-10-06 13:00:08 +00:00
{
"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 (
inside_ignored_block(config, html, match) is False
and inside_ignored_rule(config, html, match, rule["name"])
is False
):
errors[filename].append(
2021-10-06 13:00:08 +00:00
{
"code": rule["name"],
"line": get_line(match.start(), line_ends),
"match": match.group().strip()[:20],
"message": rule["message"],
}
)
2021-07-23 20:58:24 +00:00
# remove duplicate matches
for filename, error_dict in errors.items():
unique_errors = []
for dict_ in error_dict:
if dict_ not in unique_errors:
unique_errors.append(dict_)
errors[filename] = unique_errors
2021-07-23 20:58:24 +00:00
return errors