mirror of
https://github.com/Hopiu/djLint.git
synced 2026-05-27 05:43:59 +00:00
added option for custom rules file
This commit is contained in:
parent
07a812ee1d
commit
35474de863
11 changed files with 141 additions and 14 deletions
|
|
@ -4,6 +4,7 @@ Changelog
|
|||
Next Release
|
||||
------------
|
||||
- Split ``alt`` requirement from H006 to H013
|
||||
- Added optional custom rules file
|
||||
|
||||
0.5.1
|
||||
-----
|
||||
|
|
|
|||
|
|
@ -73,3 +73,19 @@ The first letter of a code follows the pattern:
|
|||
- J: applies specifically to Jinja
|
||||
- N: applies specifically to Nunjucks
|
||||
- M: applies specifically to Handlebars
|
||||
|
||||
Custom Rules
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Create a file ``.djlint_rules.yaml`` alongside your ``pyproject.toml``. Rules can be added to this files and djLint will pick them up.
|
||||
|
||||
A good rule follows this pattern:
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
- rule:
|
||||
name: T001
|
||||
message: Find Trichotillomania
|
||||
flags: re.DOTALL|re.I
|
||||
patterns:
|
||||
- Trichotillomania
|
||||
|
|
|
|||
|
|
@ -3,14 +3,9 @@ from pathlib import Path
|
|||
from typing import Dict, List
|
||||
|
||||
import regex as re
|
||||
import yaml
|
||||
|
||||
from .settings import Config
|
||||
|
||||
rules = yaml.load(
|
||||
(Path(__file__).parent / "rules.yaml").read_text(encoding="utf8"),
|
||||
Loader=yaml.SafeLoader,
|
||||
)
|
||||
flags = {
|
||||
"re.A": re.A,
|
||||
"re.ASCII": re.ASCII,
|
||||
|
|
@ -69,14 +64,7 @@ def lint_file(config: Config, this_file: Path) -> Dict:
|
|||
for m in re.finditer(r"(?:.*\n)|(?:[^\n]+$)", html)
|
||||
]
|
||||
|
||||
for rule in list(
|
||||
filter(
|
||||
lambda x: x["rule"]["name"] not in config.ignore.split(",")
|
||||
and x["rule"]["name"][0] not in config.profile_code
|
||||
and config.profile not in x["rule"].get("exclude", []),
|
||||
rules,
|
||||
)
|
||||
):
|
||||
for rule in config.linter_rules:
|
||||
rule = rule["rule"]
|
||||
|
||||
for pattern in rule["patterns"]:
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ from pathlib import Path
|
|||
from typing import Dict, List, Optional, Union
|
||||
|
||||
import tomlkit
|
||||
import yaml
|
||||
from click import echo
|
||||
from colorama import Fore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -28,6 +31,19 @@ def find_pyproject(src: Path) -> Optional[Path]:
|
|||
return None
|
||||
|
||||
|
||||
def find_djlint_rules(src: Path) -> Optional[Path]:
|
||||
"""Search upstream for a pyprojec.toml file."""
|
||||
|
||||
for directory in [src, *src.resolve().parents]:
|
||||
|
||||
candidate = directory / ".djlint_rules.yaml"
|
||||
|
||||
if candidate.is_file():
|
||||
return candidate
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def load_pyproject_settings(src: Path) -> Dict:
|
||||
"""Load djlint config from pyproject.toml."""
|
||||
|
||||
|
|
@ -44,6 +60,44 @@ def load_pyproject_settings(src: Path) -> Dict:
|
|||
return djlint_content
|
||||
|
||||
|
||||
def validate_rules(rules: List) -> List:
|
||||
clean_rules = []
|
||||
|
||||
for rule in rules:
|
||||
# check for name
|
||||
warning = 0
|
||||
name = rule["rule"].get("name", "undefined")
|
||||
if "name" not in rule["rule"]:
|
||||
warning += 1
|
||||
echo(Fore.RED + f"Warning: A rule is missing a name! 😢")
|
||||
if "patterns" not in rule["rule"]:
|
||||
warning += 1
|
||||
echo(Fore.RED + f"Warning: Rule {name} is missing a pattern! 😢")
|
||||
if "message" not in rule["rule"]:
|
||||
warning += 1
|
||||
echo(Fore.RED + f"Warning: Rule {name} is missing a message! 😢")
|
||||
|
||||
if warning == 0:
|
||||
clean_rules.append(rule)
|
||||
|
||||
return clean_rules
|
||||
|
||||
|
||||
def load_custom_rules(src: Path) -> List:
|
||||
"""Load djlint config from pyproject.toml."""
|
||||
|
||||
djlint_content: List = []
|
||||
djlint_rules_file = find_djlint_rules(src)
|
||||
|
||||
if djlint_rules_file:
|
||||
djlint_content = yaml.load(
|
||||
Path(djlint_rules_file).read_text(encoding="utf8"),
|
||||
Loader=yaml.SafeLoader,
|
||||
)
|
||||
|
||||
return djlint_content
|
||||
|
||||
|
||||
def build_custom_blocks(custom_blocks: Union[str, None]) -> Optional[str]:
|
||||
"""Build regex string for custom template blocks."""
|
||||
if custom_blocks:
|
||||
|
|
@ -91,6 +145,24 @@ class Config:
|
|||
profile or djlint_settings.get("profile", "all")
|
||||
).lower()
|
||||
|
||||
# load linter rules
|
||||
rule_set = validate_rules(
|
||||
yaml.load(
|
||||
(Path(__file__).parent / "rules.yaml").read_text(encoding="utf8"),
|
||||
Loader=yaml.SafeLoader,
|
||||
)
|
||||
+ load_custom_rules(Path(src))
|
||||
)
|
||||
|
||||
self.linter_rules = list(
|
||||
filter(
|
||||
lambda x: x["rule"]["name"] not in self.ignore.split(",")
|
||||
and x["rule"]["name"][0] not in self.profile_code
|
||||
and self.profile not in x["rule"].get("exclude", []),
|
||||
rule_set,
|
||||
)
|
||||
)
|
||||
|
||||
# base options
|
||||
self.indent: str = (indent or int(djlint_settings.get("indent", 4))) * " "
|
||||
|
||||
|
|
|
|||
6
tests/custom_rules/.djlint_rules.yaml
Normal file
6
tests/custom_rules/.djlint_rules.yaml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
- rule:
|
||||
name: T001
|
||||
message: Find Trichotillomania
|
||||
flags: re.DOTALL|re.I
|
||||
patterns:
|
||||
- Trichotillomania
|
||||
1
tests/custom_rules/html.html
Normal file
1
tests/custom_rules/html.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
This is trichotillomania.
|
||||
1
tests/custom_rules/pyproject.toml
Normal file
1
tests/custom_rules/pyproject.toml
Normal file
|
|
@ -0,0 +1 @@
|
|||
[tool]
|
||||
24
tests/custom_rules_bad/.djlint_rules.yaml
Normal file
24
tests/custom_rules_bad/.djlint_rules.yaml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
- rule:
|
||||
name: T001
|
||||
message: Find Trichotillomania
|
||||
flags: re.DOTALL|re.I
|
||||
patterns:
|
||||
- Trichotillomania
|
||||
|
||||
- rule:
|
||||
name: T002
|
||||
flags: re.DOTALL|re.I
|
||||
patterns:
|
||||
- Trichotillomania
|
||||
|
||||
- rule:
|
||||
name: T003
|
||||
patterns:
|
||||
- Trichotillomania
|
||||
|
||||
- rule:
|
||||
name: T004
|
||||
|
||||
- rule:
|
||||
patterns:
|
||||
- Trichotillomania
|
||||
1
tests/custom_rules_bad/html.html
Normal file
1
tests/custom_rules_bad/html.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
This is trichotillomania.
|
||||
1
tests/custom_rules_bad/pyproject.toml
Normal file
1
tests/custom_rules_bad/pyproject.toml
Normal file
|
|
@ -0,0 +1 @@
|
|||
[tool]
|
||||
|
|
@ -7,7 +7,7 @@ run::
|
|||
|
||||
# for a single test
|
||||
|
||||
pytest tests/test_linter.py::test_DJ018 --cov=src/djlint --cov-branch \
|
||||
pytest tests/test_linter.py::test_custom_rules_bad_config --cov=src/djlint --cov-branch \
|
||||
--cov-report xml:coverage.xml --cov-report term-missing
|
||||
|
||||
"""
|
||||
|
|
@ -254,3 +254,19 @@ def test_rules_not_matched_in_ignored_block(
|
|||
print(result.output)
|
||||
assert result.exit_code == 0
|
||||
assert "H011 1:" not in result.output
|
||||
|
||||
|
||||
def test_custom_rules(runner: CliRunner, tmp_file: TextIO) -> None:
|
||||
result = runner.invoke(djlint, ["tests/custom_rules"])
|
||||
assert """Linting""" in result.output
|
||||
assert """1/1""" in result.output
|
||||
assert """T001 1:""" in result.output
|
||||
assert result.exit_code == 1
|
||||
|
||||
|
||||
def test_custom_rules_bad_config(runner: CliRunner, tmp_file: TextIO) -> None:
|
||||
result = runner.invoke(djlint, ["tests/custom_rules_bad"])
|
||||
assert """Linting""" in result.output
|
||||
assert """1/1""" in result.output
|
||||
assert """T001 1:""" in result.output
|
||||
assert result.exit_code == 1
|
||||
|
|
|
|||
Loading…
Reference in a new issue