added option for custom rules file

This commit is contained in:
Christopher Pickering 2021-10-04 15:26:22 +02:00
parent 07a812ee1d
commit 35474de863
No known key found for this signature in database
GPG key ID: E14DB3B0A0FACF84
11 changed files with 141 additions and 14 deletions

View file

@ -4,6 +4,7 @@ Changelog
Next Release
------------
- Split ``alt`` requirement from H006 to H013
- Added optional custom rules file
0.5.1
-----

View file

@ -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

View file

@ -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"]:

View file

@ -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))) * " "

View file

@ -0,0 +1,6 @@
- rule:
name: T001
message: Find Trichotillomania
flags: re.DOTALL|re.I
patterns:
- Trichotillomania

View file

@ -0,0 +1 @@
This is trichotillomania.

View file

@ -0,0 +1 @@
[tool]

View 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

View file

@ -0,0 +1 @@
This is trichotillomania.

View file

@ -0,0 +1 @@
[tool]

View file

@ -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