diff --git a/docs/guide/rules.md b/docs/guide/rules.md index 8fc4d62..e49ab08 100644 --- a/docs/guide/rules.md +++ b/docs/guide/rules.md @@ -21,7 +21,7 @@ The `name` is used to reference the rule in `apply` blocks and in `# pnl: ignore | Field | Required | Description | |-------|----------|-------------| -| `name` | Yes | Unique identifier, referenced in `apply` and `# pnl: ignore` | +| `name` | Yes | Unique identifier, referenced in `apply` and `# pnl: ignore`. Must match `[a-zA-Z0-9_.-]+` | | `description` | No | Human-readable description shown in violation output | | `type` | Yes | What kind of name to lint (`variable`, `function`, `class`, `module`, `package`) | | `filter` | No | Narrow which names are checked (see [Filters](#filters) below) | @@ -917,7 +917,7 @@ apply: | Field | Required | Description | |-------|----------|-------------| -| `name` | Yes | Unique identifier, referenced in `apply` and `# pnl: ignore` | +| `name` | Yes | Unique identifier, referenced in `apply` and `# pnl: ignore`. Must match `[a-zA-Z0-9_.-]+` | | `description` | No | Human-readable description shown in violation output | | `type` | Yes | What kind of name to lint (`variable`, `function`, `class`, `module`, `package`) | | `filter` | No | Narrow which names are checked | diff --git a/python_naming_linter/config.py b/python_naming_linter/config.py index 2486789..3ec0655 100644 --- a/python_naming_linter/config.py +++ b/python_naming_linter/config.py @@ -6,7 +6,7 @@ import yaml -_VALID_RULE_NAME_RE = re.compile(r"^[a-zA-Z0-9_-]+$") +_VALID_RULE_NAME_RE = re.compile(r"^[a-zA-Z0-9_.\-]+$") @dataclass @@ -36,7 +36,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_.-]+" ) diff --git a/python_naming_linter/ignore.py b/python_naming_linter/ignore.py index 15f1bdd..0fa8635 100644 --- a/python_naming_linter/ignore.py +++ b/python_naming_linter/ignore.py @@ -4,7 +4,7 @@ from python_naming_linter.checkers import Violation -_IGNORE_RE = re.compile(r"#\s*pnl:\s*ignore(?:=([a-zA-Z0-9_,\s-]+))?$") +_IGNORE_RE = re.compile(r"#\s*pnl:\s*ignore(?:=([a-zA-Z0-9_.,\s-]+))?$") def parse_ignore_comments(source: str) -> dict[int, set[str] | None]: diff --git a/tests/test_config.py b/tests/test_config.py index 9a393b7..9f191d3 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -127,7 +127,14 @@ def test_find_config_skips_pyproject_without_section(tmp_path, monkeypatch): @pytest.mark.parametrize( "name", - ["attribute-matches-type", "bool_method", "rule1", "My-Rule_2"], + [ + "attribute-matches-type", + "bool_method", + "rule1", + "My-Rule_2", + "rule.name", + "shared.domain", + ], ) def test_valid_rule_names(tmp_path, name): config_content = f"""\ @@ -148,7 +155,7 @@ def test_valid_rule_names(tmp_path, name): @pytest.mark.parametrize( "name", - ["my rule", "rule!name", "rule name 123", "rule.name", "rule@name"], + ["my rule", "rule!name", "rule name 123", "rule@name"], ) def test_invalid_rule_names(tmp_path, name): config_content = f"""\ diff --git a/tests/test_ignore.py b/tests/test_ignore.py index 840454c..15756c2 100644 --- a/tests/test_ignore.py +++ b/tests/test_ignore.py @@ -44,6 +44,16 @@ def test_extra_space_after_pnl(self): source = "x: int = 1 # pnl: ignore\n" assert parse_ignore_comments(source) == {1: None} + def test_ignore_rule_with_dot(self): + source = "x: int = 1 # pnl: ignore=shared.domain\n" + assert parse_ignore_comments(source) == {1: {"shared.domain"}} + + def test_ignore_multiple_rules_with_dots(self): + source = "x: int = 1 # pnl: ignore=shared.domain,context.adapters\n" + assert parse_ignore_comments(source) == { + 1: {"shared.domain", "context.adapters"} + } + class TestFilterViolations: def _make_violation(self, rule_name: str, lineno: int) -> Violation: