diff --git a/python_naming_linter/config.py b/python_naming_linter/config.py index cd9dff9..a4f0d12 100644 --- a/python_naming_linter/config.py +++ b/python_naming_linter/config.py @@ -1,10 +1,13 @@ from __future__ import annotations +import re from dataclasses import dataclass, field from pathlib import Path import yaml +_VALID_RULE_NAME_RE = re.compile(r"^[a-zA-Z0-9_-]+$") + @dataclass class Rule: @@ -29,9 +32,17 @@ class Config: exclude: list[str] | None = None +def _validate_rule_name(name: str) -> None: + if not _VALID_RULE_NAME_RE.match(name): + raise ValueError( + f"Invalid rule name '{name}'. Rule names must match [a-zA-Z0-9_-]+" + ) + + def _parse_rules(rules_data: list[dict]) -> list[Rule]: rules = [] for r in rules_data: + _validate_rule_name(r["name"]) rules.append( Rule( name=r["name"], diff --git a/tests/test_config.py b/tests/test_config.py index 1221b65..c060690 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,5 +1,7 @@ from pathlib import Path +import pytest + from python_naming_linter.config import find_config, load_config FIXTURES = Path(__file__).parent / "fixtures" @@ -57,8 +59,6 @@ def test_load_yaml_without_include_exclude(): def test_load_config_file_not_found(): - import pytest - with pytest.raises(FileNotFoundError): load_config(Path("nonexistent.yaml")) @@ -101,3 +101,45 @@ def test_find_config_skips_pyproject_without_section(tmp_path, monkeypatch): (tmp_path / "pyproject.toml").write_text("[tool.other]\nfoo = 1\n") monkeypatch.chdir(tmp_path) assert find_config() is None + + +@pytest.mark.parametrize( + "name", + ["attribute-matches-type", "bool_method", "rule1", "My-Rule_2"], +) +def test_valid_rule_names(tmp_path, name): + config_content = f"""\ +rules: + - name: {name} + type: variable + naming: {{case: snake_case}} +apply: + - name: all + rules: [{name}] + modules: "**" +""" + config_file = tmp_path / "config.yaml" + config_file.write_text(config_content) + config = load_config(config_file) + assert config.rules[0].name == name + + +@pytest.mark.parametrize( + "name", + ["my rule", "rule!name", "rule name 123", "rule.name", "rule@name"], +) +def test_invalid_rule_names(tmp_path, name): + config_content = f"""\ +rules: + - name: "{name}" + type: variable + naming: {{case: snake_case}} +apply: + - name: all + rules: ["{name}"] + modules: "**" +""" + config_file = tmp_path / "config.yaml" + config_file.write_text(config_content) + with pytest.raises(ValueError, match="Invalid rule name"): + load_config(config_file)