From 998060e2df61912ec130b5cc096a07ec83ef2c49 Mon Sep 17 00:00:00 2001 From: heumsi Date: Tue, 31 Mar 2026 11:49:18 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20feat:=20Validate=20rule=20names?= =?UTF-8?q?=20to=20allow=20only=20alphanumeric,=20hyphen,=20and=20undersco?= =?UTF-8?q?re=20characters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rule names must now match [a-zA-Z0-9_-]+. This restriction ensures consistent naming and enables future inline ignore comments (# pnl: ignore=rule-name) to be parsed unambiguously. Co-Authored-By: Claude Opus 4.6 (1M context) --- python_naming_linter/config.py | 12 +++++++++ tests/test_config.py | 46 ++++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/python_naming_linter/config.py b/python_naming_linter/config.py index cd9dff9..7ee494e 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,18 @@ 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) From 4da0c7afe7d53eecc7a17ce5478463645ce8f0c5 Mon Sep 17 00:00:00 2001 From: heumsi Date: Tue, 31 Mar 2026 12:00:58 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=94=A7=20chore:=20Fix=20ruff=20format?= =?UTF-8?q?=20issue=20in=20config.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- python_naming_linter/config.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python_naming_linter/config.py b/python_naming_linter/config.py index 7ee494e..a4f0d12 100644 --- a/python_naming_linter/config.py +++ b/python_naming_linter/config.py @@ -35,8 +35,7 @@ class Config: 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_-]+" + f"Invalid rule name '{name}'. Rule names must match [a-zA-Z0-9_-]+" )