From ca94013729ff85272713a17dd0b9de829b5bba9c Mon Sep 17 00:00:00 2001 From: heumsi Date: Wed, 1 Apr 2026 00:52:00 +0900 Subject: [PATCH 1/5] =?UTF-8?q?=E2=9C=A8=20feat:=20Add=20optional=20descri?= =?UTF-8?q?ption=20field=20to=20rules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an optional `description` field to the Rule dataclass that allows users to provide a human-readable explanation for each rule. When set, the description is displayed in violation output above the violation detail line. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/cookbook/attribute-matches-type.md | 3 +- docs/cookbook/bool-method-prefix.md | 3 +- docs/cookbook/constant-upper-case.md | 6 ++-- docs/cookbook/decorator-filtering.md | 6 ++-- docs/cookbook/exception-naming.md | 3 +- docs/cookbook/layer-based-rules.md | 9 ++++-- docs/cookbook/module-matches-class.md | 3 +- docs/getting-started/quick-start.md | 6 ++-- docs/guide/rules.md | 5 +++- docs/index.md | 6 ++-- python_naming_linter/checkers/__init__.py | 1 + python_naming_linter/checkers/class_.py | 1 + python_naming_linter/checkers/function.py | 1 + python_naming_linter/checkers/module.py | 3 ++ python_naming_linter/checkers/package.py | 2 ++ python_naming_linter/checkers/variable.py | 1 + python_naming_linter/config.py | 2 ++ python_naming_linter/reporter.py | 6 +++- tests/test_config.py | 20 +++++++++++++ tests/test_reporter.py | 35 +++++++++++++++++++++++ 20 files changed, 105 insertions(+), 17 deletions(-) diff --git a/docs/cookbook/attribute-matches-type.md b/docs/cookbook/attribute-matches-type.md index e588835..ebbcd43 100644 --- a/docs/cookbook/attribute-matches-type.md +++ b/docs/cookbook/attribute-matches-type.md @@ -46,7 +46,8 @@ The `{prefix}_{expected}` form is also allowed. For example, `source_object_cont ``` $ pnl check contexts/billing/domain/service.py:5 - [attribute-matches-type] repo (expected: subscription_repository) + [attribute-matches-type] + repo (expected: subscription_repository) Found 1 violation(s). ``` diff --git a/docs/cookbook/bool-method-prefix.md b/docs/cookbook/bool-method-prefix.md index db79aa2..b722ede 100644 --- a/docs/cookbook/bool-method-prefix.md +++ b/docs/cookbook/bool-method-prefix.md @@ -44,7 +44,8 @@ class SubscriptionService: ``` $ pnl check src/domain/service.py:4 - [bool-method-prefix] validate (expected prefix: is_ | has_ | should_) + [bool-method-prefix] + validate (expected prefix: is_ | has_ | should_) Found 1 violation(s). ``` diff --git a/docs/cookbook/constant-upper-case.md b/docs/cookbook/constant-upper-case.md index 5feb932..c33bd90 100644 --- a/docs/cookbook/constant-upper-case.md +++ b/docs/cookbook/constant-upper-case.md @@ -42,10 +42,12 @@ DEFAULT_TIMEOUT_SECONDS = 30 ``` $ pnl check src/config.py:3 - [constant-upper-case] max_retry_count (expected case: UPPER_CASE) + [constant-upper-case] + max_retry_count (expected case: UPPER_CASE) src/config.py:4 - [constant-upper-case] default_timeout_seconds (expected case: UPPER_CASE) + [constant-upper-case] + default_timeout_seconds (expected case: UPPER_CASE) Found 2 violation(s). ``` diff --git a/docs/cookbook/decorator-filtering.md b/docs/cookbook/decorator-filtering.md index a3f20a5..8cf82ec 100644 --- a/docs/cookbook/decorator-filtering.md +++ b/docs/cookbook/decorator-filtering.md @@ -75,10 +75,12 @@ class OrderData: ``` $ pnl check src/domain/order.py:5 - [static-factory-prefix] from_dict (expected prefix: create_ | build_) + [static-factory-prefix] + from_dict (expected prefix: create_ | build_) src/domain/order.py:9 - [dataclass-naming] OrderPayload (expected suffix: Data | Config) + [dataclass-naming] + OrderPayload (expected suffix: Data | Config) Found 2 violation(s). ``` diff --git a/docs/cookbook/exception-naming.md b/docs/cookbook/exception-naming.md index 0d4ad18..d6a1d56 100644 --- a/docs/cookbook/exception-naming.md +++ b/docs/cookbook/exception-naming.md @@ -42,7 +42,8 @@ class FilterNotFoundError(Exception): ``` $ pnl check src/domain/exceptions.py:3 - [exception-naming] FilterError (expected pattern: ^[A-Z][a-zA-Z]+(NotFound|Invalid|...)Error$) + [exception-naming] + FilterError (expected pattern: ^[A-Z][a-zA-Z]+(NotFound|Invalid|...)Error$) Found 1 violation(s). ``` diff --git a/docs/cookbook/layer-based-rules.md b/docs/cookbook/layer-based-rules.md index ccb7432..7e9842f 100644 --- a/docs/cookbook/layer-based-rules.md +++ b/docs/cookbook/layer-based-rules.md @@ -91,13 +91,16 @@ class BillingNotFoundError(Exception): ``` $ pnl check contexts/billing/domain/service.py:3 - [constant-upper-case] max_retry (expected case: UPPER_CASE) + [constant-upper-case] + max_retry (expected case: UPPER_CASE) contexts/billing/domain/service.py:6 - [bool-method-prefix] validate (expected prefix: is_ | has_ | should_) + [bool-method-prefix] + validate (expected prefix: is_ | has_ | should_) contexts/billing/domain/exceptions.py:3 - [exception-naming] BillingError (expected pattern: ^[A-Z][a-zA-Z]+(NotFound|Invalid|...)Error$) + [exception-naming] + BillingError (expected pattern: ^[A-Z][a-zA-Z]+(NotFound|Invalid|...)Error$) Found 3 violation(s). ``` diff --git a/docs/cookbook/module-matches-class.md b/docs/cookbook/module-matches-class.md index bda62b2..6f9a401 100644 --- a/docs/cookbook/module-matches-class.md +++ b/docs/cookbook/module-matches-class.md @@ -41,7 +41,8 @@ class CustomObject: ``` $ pnl check contexts/catalog/domain/custom.py:1 - [domain-module-naming] custom (expected: custom_object) + [domain-module-naming] + custom (expected: custom_object) Found 1 violation(s). ``` diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md index 8bf9501..8099711 100644 --- a/docs/getting-started/quick-start.md +++ b/docs/getting-started/quick-start.md @@ -47,10 +47,12 @@ Violations are reported with the file path, line number, rule name, and what was ``` src/domain/service.py:12 - [bool-method-prefix] validate (expected prefix: is_ | has_ | should_) + [bool-method-prefix] + validate (expected prefix: is_ | has_ | should_) src/domain/exceptions.py:8 - [exception-naming] FilterError (expected pattern: ^[A-Z][a-zA-Z]+(NotFound|Invalid|...)Error$) + [exception-naming] + FilterError (expected pattern: ^[A-Z][a-zA-Z]+(NotFound|Invalid|...)Error$) Found 2 violation(s). ``` diff --git a/docs/guide/rules.md b/docs/guide/rules.md index 029cf45..8fc4d62 100644 --- a/docs/guide/rules.md +++ b/docs/guide/rules.md @@ -4,11 +4,12 @@ Rules are the core building blocks of `pnl`. Each rule targets a specific kind o ## Structure -Every rule has three required fields and two optional ones: +Every rule has three required fields and three optional ones: ```yaml rules: - name: my-rule # Unique identifier for this rule + description: ... # (optional) Human-readable description shown in violation output type: variable # What kind of name to lint filter: { ... } # (optional) Narrow which names are checked naming: { ... } # How the name must be formed @@ -21,6 +22,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` | +| `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) | | `naming` | Yes | How the name must be formed (see [Naming Constraints](#naming-constraints) below) | @@ -916,6 +918,7 @@ apply: | Field | Required | Description | |-------|----------|-------------| | `name` | Yes | Unique identifier, referenced in `apply` and `# pnl: ignore` | +| `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 | | `naming` | Yes | How the name must be formed | diff --git a/docs/index.md b/docs/index.md index ca3fe50..7cfe625 100644 --- a/docs/index.md +++ b/docs/index.md @@ -59,10 +59,12 @@ pnl check ``` src/domain/service.py:12 - [bool-method-prefix] validate (expected prefix: is_ | has_ | should_) + [bool-method-prefix] + validate (expected prefix: is_ | has_ | should_) src/domain/exceptions.py:8 - [exception-naming] FilterError (expected pattern: ^[A-Z][a-zA-Z]+(NotFound|Invalid|...)Error$) + [exception-naming] + FilterError (expected pattern: ^[A-Z][a-zA-Z]+(NotFound|Invalid|...)Error$) Found 2 violation(s). ``` diff --git a/python_naming_linter/checkers/__init__.py b/python_naming_linter/checkers/__init__.py index 55a67f3..e6c7665 100644 --- a/python_naming_linter/checkers/__init__.py +++ b/python_naming_linter/checkers/__init__.py @@ -10,3 +10,4 @@ class Violation: lineno: int name: str message: str + rule_description: str | None = None diff --git a/python_naming_linter/checkers/class_.py b/python_naming_linter/checkers/class_.py index e533dc2..9948562 100644 --- a/python_naming_linter/checkers/class_.py +++ b/python_naming_linter/checkers/class_.py @@ -154,6 +154,7 @@ def check_class(tree: ast.Module, rule: Rule, file_path: str) -> list[Violation] lineno=node.lineno, name=class_name, message=msg, + rule_description=rule.description, ) ) diff --git a/python_naming_linter/checkers/function.py b/python_naming_linter/checkers/function.py index 11a2952..eb96e90 100644 --- a/python_naming_linter/checkers/function.py +++ b/python_naming_linter/checkers/function.py @@ -131,6 +131,7 @@ def check_function(tree: ast.Module, rule: Rule, file_path: str) -> list[Violati lineno=node.lineno, name=func_name, message=msg, + rule_description=rule.description, ) ) diff --git a/python_naming_linter/checkers/module.py b/python_naming_linter/checkers/module.py index 659b8fc..790e08e 100644 --- a/python_naming_linter/checkers/module.py +++ b/python_naming_linter/checkers/module.py @@ -49,6 +49,7 @@ def check_module(tree: ast.Module, rule: Rule, file_path: str) -> list[Violation lineno=0, name=module_name, message=msg, + rule_description=rule.description, ) ) @@ -63,6 +64,7 @@ def check_module(tree: ast.Module, rule: Rule, file_path: str) -> list[Violation lineno=0, name=module_name, message=msg, + rule_description=rule.description, ) ) @@ -86,6 +88,7 @@ def check_module(tree: ast.Module, rule: Rule, file_path: str) -> list[Violation lineno=0, name=module_name, message=msg, + rule_description=rule.description, ) ) diff --git a/python_naming_linter/checkers/package.py b/python_naming_linter/checkers/package.py index 3621ee8..b0d24d2 100644 --- a/python_naming_linter/checkers/package.py +++ b/python_naming_linter/checkers/package.py @@ -19,6 +19,7 @@ def check_package(rule: Rule, package_name: str) -> list[Violation]: lineno=0, name=package_name, message="expected: snake_case", + rule_description=rule.description, ) ) @@ -31,6 +32,7 @@ def check_package(rule: Rule, package_name: str) -> list[Violation]: lineno=0, name=package_name, message=f"expected pattern: {naming['regex']}", + rule_description=rule.description, ) ) diff --git a/python_naming_linter/checkers/variable.py b/python_naming_linter/checkers/variable.py index 4698398..3a7bd99 100644 --- a/python_naming_linter/checkers/variable.py +++ b/python_naming_linter/checkers/variable.py @@ -170,6 +170,7 @@ def check_variable(tree: ast.Module, rule: Rule, file_path: str) -> list[Violati lineno=lineno, name=var_name, message=msg, + rule_description=rule.description, ) ) return violations diff --git a/python_naming_linter/config.py b/python_naming_linter/config.py index a4f0d12..2486789 100644 --- a/python_naming_linter/config.py +++ b/python_naming_linter/config.py @@ -15,6 +15,7 @@ class Rule: type: str naming: dict filter: dict = field(default_factory=dict) + description: str | None = None @dataclass @@ -49,6 +50,7 @@ def _parse_rules(rules_data: list[dict]) -> list[Rule]: type=r["type"], naming=r.get("naming", {}), filter=r.get("filter", {}), + description=r.get("description"), ) ) return rules diff --git a/python_naming_linter/reporter.py b/python_naming_linter/reporter.py index 646ce91..159acdf 100644 --- a/python_naming_linter/reporter.py +++ b/python_naming_linter/reporter.py @@ -10,7 +10,11 @@ def format_violations(file_path: str, violations: list[Violation]) -> str: lines = [] for v in violations: lines.append(f"{file_path}:{v.lineno}") - lines.append(f" [{v.rule_name}] {v.name} ({v.message})") + if v.rule_description: + lines.append(f" [{v.rule_name}] {v.rule_description}") + else: + lines.append(f" [{v.rule_name}]") + lines.append(f" {v.name} ({v.message})") lines.append("") return "\n".join(lines) diff --git a/tests/test_config.py b/tests/test_config.py index c060690..d5faa54 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -20,6 +20,7 @@ def test_load_yaml_rule_fields(): assert rule.type == "variable" assert rule.filter == {"target": "attribute"} assert rule.naming == {"source": "type_annotation", "transform": "snake_case"} + assert rule.description is None def test_load_yaml_apply_fields(): @@ -30,6 +31,25 @@ def test_load_yaml_apply_fields(): assert apply.modules == "contexts.*.domain" +def test_load_yaml_rule_with_description(tmp_path): + config_content = """\ +rules: + - name: bool-method-prefix + description: "Bool-returning functions must use a semantic prefix" + type: function + filter: { return_type: bool } + naming: { prefix: [is_, has_, should_] } +apply: + - name: all + rules: [bool-method-prefix] + modules: "**" +""" + config_file = tmp_path / "config.yaml" + config_file.write_text(config_content) + config = load_config(config_file) + assert config.rules[0].description == "Bool-returning functions must use a semantic prefix" + + def test_load_yaml_with_include_exclude(tmp_path): config_content = """\ include: diff --git a/tests/test_reporter.py b/tests/test_reporter.py index 936ef70..be8f9e4 100644 --- a/tests/test_reporter.py +++ b/tests/test_reporter.py @@ -19,6 +19,41 @@ def test_format_single_violation(): assert "subscription_repository" in output +def test_format_violation_with_description(): + violations = [ + Violation( + rule_name="bool-method-prefix", + file_path="src/service.py", + lineno=4, + name="validate", + message="expected prefix: is_ | has_ | should_", + rule_description="Bool-returning functions must use a semantic prefix", + ) + ] + output = format_violations("src/service.py", violations) + lines = output.strip().split("\n") + assert lines[0] == "src/service.py:4" + assert lines[1] == " [bool-method-prefix] Bool-returning functions must use a semantic prefix" + assert lines[2] == " validate (expected prefix: is_ | has_ | should_)" + + +def test_format_violation_without_description(): + violations = [ + Violation( + rule_name="attr-naming", + file_path="src/models.py", + lineno=15, + name="repo", + message="expected: subscription_repository", + ) + ] + output = format_violations("src/models.py", violations) + lines = output.strip().split("\n") + assert lines[0] == "src/models.py:15" + assert lines[1] == " [attr-naming]" + assert lines[2] == " repo (expected: subscription_repository)" + + def test_format_multiple_violations(): violations = [ Violation("r1", "f.py", 1, "a", "msg1"), From be56b276e751f9d2debd8a408cf23bbf9e4976f1 Mon Sep 17 00:00:00 2001 From: heumsi Date: Wed, 1 Apr 2026 00:55:26 +0900 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=93=9D=20docs:=20Add=20description=20?= =?UTF-8?q?examples=20to=20all=20cookbook=20and=20getting-started=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/cookbook/attribute-matches-type.md | 3 ++- docs/cookbook/bool-method-prefix.md | 3 ++- docs/cookbook/constant-upper-case.md | 5 +++-- docs/cookbook/decorator-filtering.md | 6 ++++-- docs/cookbook/exception-naming.md | 3 ++- docs/cookbook/layer-based-rules.md | 11 ++++++++--- docs/cookbook/module-matches-class.md | 3 ++- docs/getting-started/quick-start.md | 8 +++++--- docs/index.md | 6 ++++-- 9 files changed, 32 insertions(+), 16 deletions(-) diff --git a/docs/cookbook/attribute-matches-type.md b/docs/cookbook/attribute-matches-type.md index ebbcd43..b899d96 100644 --- a/docs/cookbook/attribute-matches-type.md +++ b/docs/cookbook/attribute-matches-type.md @@ -9,6 +9,7 @@ When an attribute holds a repository, service, or other typed object, keeping th ```yaml rules: - name: attribute-matches-type + description: "Attribute names must match their type annotation in snake_case" type: variable filter: { target: attribute } naming: { source: type_annotation, transform: snake_case } @@ -46,7 +47,7 @@ The `{prefix}_{expected}` form is also allowed. For example, `source_object_cont ``` $ pnl check contexts/billing/domain/service.py:5 - [attribute-matches-type] + [attribute-matches-type] Attribute names must match their type annotation in snake_case repo (expected: subscription_repository) Found 1 violation(s). diff --git a/docs/cookbook/bool-method-prefix.md b/docs/cookbook/bool-method-prefix.md index b722ede..bf40b51 100644 --- a/docs/cookbook/bool-method-prefix.md +++ b/docs/cookbook/bool-method-prefix.md @@ -9,6 +9,7 @@ Functions that return `bool` are easier to read at call sites when their names r ```yaml rules: - name: bool-method-prefix + description: "Bool-returning functions must start with is_, has_, or should_" type: function filter: { return_type: bool } naming: { prefix: [is_, has_, should_] } @@ -44,7 +45,7 @@ class SubscriptionService: ``` $ pnl check src/domain/service.py:4 - [bool-method-prefix] + [bool-method-prefix] Bool-returning functions must start with is_, has_, or should_ validate (expected prefix: is_ | has_ | should_) Found 1 violation(s). diff --git a/docs/cookbook/constant-upper-case.md b/docs/cookbook/constant-upper-case.md index c33bd90..d4bad5c 100644 --- a/docs/cookbook/constant-upper-case.md +++ b/docs/cookbook/constant-upper-case.md @@ -9,6 +9,7 @@ Module-level constants are easier to distinguish from regular variables when the ```yaml rules: - name: constant-upper-case + description: "Module-level constants must use UPPER_CASE" type: variable filter: { target: constant } naming: { case: UPPER_CASE } @@ -42,11 +43,11 @@ DEFAULT_TIMEOUT_SECONDS = 30 ``` $ pnl check src/config.py:3 - [constant-upper-case] + [constant-upper-case] Module-level constants must use UPPER_CASE max_retry_count (expected case: UPPER_CASE) src/config.py:4 - [constant-upper-case] + [constant-upper-case] Module-level constants must use UPPER_CASE default_timeout_seconds (expected case: UPPER_CASE) Found 2 violation(s). diff --git a/docs/cookbook/decorator-filtering.md b/docs/cookbook/decorator-filtering.md index 8cf82ec..da4473f 100644 --- a/docs/cookbook/decorator-filtering.md +++ b/docs/cookbook/decorator-filtering.md @@ -11,6 +11,7 @@ Some naming conventions only apply to a specific kind of function or class. Deco ```yaml rules: - name: static-factory-prefix + description: "Static factory methods must start with create_ or build_" type: function filter: { decorator: staticmethod } naming: { prefix: [create_, build_] } @@ -26,6 +27,7 @@ apply: ```yaml rules: - name: dataclass-naming + description: "Dataclass names must end with Data or Config" type: class filter: { decorator: dataclass } naming: { suffix: [Data, Config] } @@ -75,11 +77,11 @@ class OrderData: ``` $ pnl check src/domain/order.py:5 - [static-factory-prefix] + [static-factory-prefix] Static factory methods must start with create_ or build_ from_dict (expected prefix: create_ | build_) src/domain/order.py:9 - [dataclass-naming] + [dataclass-naming] Dataclass names must end with Data or Config OrderPayload (expected suffix: Data | Config) Found 2 violation(s). diff --git a/docs/cookbook/exception-naming.md b/docs/cookbook/exception-naming.md index d6a1d56..9a52b3f 100644 --- a/docs/cookbook/exception-naming.md +++ b/docs/cookbook/exception-naming.md @@ -9,6 +9,7 @@ Consistent exception names make error handling code easier to scan and understan ```yaml rules: - name: exception-naming + description: "Exception classes must follow the Error pattern" type: class filter: { base_class: Exception } naming: { regex: "^[A-Z][a-zA-Z]+(NotFound|Invalid|Denied|Conflict|Failed)Error$" } @@ -42,7 +43,7 @@ class FilterNotFoundError(Exception): ``` $ pnl check src/domain/exceptions.py:3 - [exception-naming] + [exception-naming] Exception classes must follow the Error pattern FilterError (expected pattern: ^[A-Z][a-zA-Z]+(NotFound|Invalid|...)Error$) Found 1 violation(s). diff --git a/docs/cookbook/layer-based-rules.md b/docs/cookbook/layer-based-rules.md index 7e9842f..d2e8bd9 100644 --- a/docs/cookbook/layer-based-rules.md +++ b/docs/cookbook/layer-based-rules.md @@ -9,25 +9,30 @@ Real projects have distinct layers — domain, infrastructure, API — each with ```yaml rules: - name: attribute-matches-type + description: "Attribute names must match their type annotation in snake_case" type: variable filter: { target: attribute } naming: { source: type_annotation, transform: snake_case } - name: bool-method-prefix + description: "Bool-returning functions must start with is_, has_, or should_" type: function filter: { return_type: bool } naming: { prefix: [is_, has_, should_] } - name: domain-module-naming + description: "Module filename must match the primary class name in snake_case" type: module naming: { source: class_name, transform: snake_case } - name: constant-upper-case + description: "Module-level constants must use UPPER_CASE" type: variable filter: { target: constant } naming: { case: UPPER_CASE } - name: exception-naming + description: "Exception classes must follow the Error pattern" type: class filter: { base_class: Exception } naming: { regex: "^[A-Z][a-zA-Z]+(NotFound|Invalid|Denied|Conflict|Failed)Error$" } @@ -91,15 +96,15 @@ class BillingNotFoundError(Exception): ``` $ pnl check contexts/billing/domain/service.py:3 - [constant-upper-case] + [constant-upper-case] Module-level constants must use UPPER_CASE max_retry (expected case: UPPER_CASE) contexts/billing/domain/service.py:6 - [bool-method-prefix] + [bool-method-prefix] Bool-returning functions must start with is_, has_, or should_ validate (expected prefix: is_ | has_ | should_) contexts/billing/domain/exceptions.py:3 - [exception-naming] + [exception-naming] Exception classes must follow the Error pattern BillingError (expected pattern: ^[A-Z][a-zA-Z]+(NotFound|Invalid|...)Error$) Found 3 violation(s). diff --git a/docs/cookbook/module-matches-class.md b/docs/cookbook/module-matches-class.md index 6f9a401..ddc873d 100644 --- a/docs/cookbook/module-matches-class.md +++ b/docs/cookbook/module-matches-class.md @@ -9,6 +9,7 @@ When each module contains one primary class, keeping the filename in sync with t ```yaml rules: - name: domain-module-naming + description: "Module filename must match the primary class name in snake_case" type: module naming: { source: class_name, transform: snake_case } @@ -41,7 +42,7 @@ class CustomObject: ``` $ pnl check contexts/catalog/domain/custom.py:1 - [domain-module-naming] + [domain-module-naming] Module filename must match the primary class name in snake_case custom (expected: custom_object) Found 1 violation(s). diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md index 8099711..14e5203 100644 --- a/docs/getting-started/quick-start.md +++ b/docs/getting-started/quick-start.md @@ -9,11 +9,13 @@ Create `.python-naming-linter.yaml` in your project root and define your naming ```yaml rules: - name: bool-method-prefix + description: "Bool-returning functions must start with is_, has_, or should_" type: function filter: { return_type: bool } naming: { prefix: [is_, has_, should_] } - name: exception-naming + description: "Exception classes must follow the Error pattern" type: class filter: { base_class: Exception } naming: { regex: "^[A-Z][a-zA-Z]+(NotFound|Invalid|Denied|Conflict|Failed)Error$" } @@ -43,15 +45,15 @@ pnl check ## Step 3: Review the Output -Violations are reported with the file path, line number, rule name, and what was expected: +Violations are reported with the file path, line number, rule name, description, and what was expected: ``` src/domain/service.py:12 - [bool-method-prefix] + [bool-method-prefix] Bool-returning functions must start with is_, has_, or should_ validate (expected prefix: is_ | has_ | should_) src/domain/exceptions.py:8 - [exception-naming] + [exception-naming] Exception classes must follow the Error pattern FilterError (expected pattern: ^[A-Z][a-zA-Z]+(NotFound|Invalid|...)Error$) Found 2 violation(s). diff --git a/docs/index.md b/docs/index.md index 7cfe625..678a539 100644 --- a/docs/index.md +++ b/docs/index.md @@ -34,11 +34,13 @@ pip install python-naming-linter ```yaml rules: - name: bool-method-prefix + description: "Bool-returning functions must start with is_, has_, or should_" type: function filter: { return_type: bool } naming: { prefix: [is_, has_, should_] } - name: exception-naming + description: "Exception classes must follow the Error pattern" type: class filter: { base_class: Exception } naming: { regex: "^[A-Z][a-zA-Z]+(NotFound|Invalid|Denied|Conflict|Failed)Error$" } @@ -59,11 +61,11 @@ pnl check ``` src/domain/service.py:12 - [bool-method-prefix] + [bool-method-prefix] Bool-returning functions must start with is_, has_, or should_ validate (expected prefix: is_ | has_ | should_) src/domain/exceptions.py:8 - [exception-naming] + [exception-naming] Exception classes must follow the Error pattern FilterError (expected pattern: ^[A-Z][a-zA-Z]+(NotFound|Invalid|...)Error$) Found 2 violation(s). From 8d690728ecc661ae7a0ef7684cc304a3d2c28dce Mon Sep 17 00:00:00 2001 From: heumsi Date: Wed, 1 Apr 2026 01:02:06 +0900 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=93=9D=20docs:=20Remove=20unnecessary?= =?UTF-8?q?=20quotes=20from=20description=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only keep quotes where special characters (<, >) require them. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/cookbook/attribute-matches-type.md | 2 +- docs/cookbook/bool-method-prefix.md | 2 +- docs/cookbook/constant-upper-case.md | 2 +- docs/cookbook/decorator-filtering.md | 4 ++-- docs/cookbook/layer-based-rules.md | 8 ++++---- docs/cookbook/module-matches-class.md | 2 +- docs/getting-started/quick-start.md | 2 +- docs/index.md | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/cookbook/attribute-matches-type.md b/docs/cookbook/attribute-matches-type.md index b899d96..2c06070 100644 --- a/docs/cookbook/attribute-matches-type.md +++ b/docs/cookbook/attribute-matches-type.md @@ -9,7 +9,7 @@ When an attribute holds a repository, service, or other typed object, keeping th ```yaml rules: - name: attribute-matches-type - description: "Attribute names must match their type annotation in snake_case" + description: Attribute names must match their type annotation in snake_case type: variable filter: { target: attribute } naming: { source: type_annotation, transform: snake_case } diff --git a/docs/cookbook/bool-method-prefix.md b/docs/cookbook/bool-method-prefix.md index bf40b51..2507fdc 100644 --- a/docs/cookbook/bool-method-prefix.md +++ b/docs/cookbook/bool-method-prefix.md @@ -9,7 +9,7 @@ Functions that return `bool` are easier to read at call sites when their names r ```yaml rules: - name: bool-method-prefix - description: "Bool-returning functions must start with is_, has_, or should_" + description: Bool-returning functions must start with is_, has_, or should_ type: function filter: { return_type: bool } naming: { prefix: [is_, has_, should_] } diff --git a/docs/cookbook/constant-upper-case.md b/docs/cookbook/constant-upper-case.md index d4bad5c..dceb038 100644 --- a/docs/cookbook/constant-upper-case.md +++ b/docs/cookbook/constant-upper-case.md @@ -9,7 +9,7 @@ Module-level constants are easier to distinguish from regular variables when the ```yaml rules: - name: constant-upper-case - description: "Module-level constants must use UPPER_CASE" + description: Module-level constants must use UPPER_CASE type: variable filter: { target: constant } naming: { case: UPPER_CASE } diff --git a/docs/cookbook/decorator-filtering.md b/docs/cookbook/decorator-filtering.md index da4473f..02dcddd 100644 --- a/docs/cookbook/decorator-filtering.md +++ b/docs/cookbook/decorator-filtering.md @@ -11,7 +11,7 @@ Some naming conventions only apply to a specific kind of function or class. Deco ```yaml rules: - name: static-factory-prefix - description: "Static factory methods must start with create_ or build_" + description: Static factory methods must start with create_ or build_ type: function filter: { decorator: staticmethod } naming: { prefix: [create_, build_] } @@ -27,7 +27,7 @@ apply: ```yaml rules: - name: dataclass-naming - description: "Dataclass names must end with Data or Config" + description: Dataclass names must end with Data or Config type: class filter: { decorator: dataclass } naming: { suffix: [Data, Config] } diff --git a/docs/cookbook/layer-based-rules.md b/docs/cookbook/layer-based-rules.md index d2e8bd9..22137fa 100644 --- a/docs/cookbook/layer-based-rules.md +++ b/docs/cookbook/layer-based-rules.md @@ -9,24 +9,24 @@ Real projects have distinct layers — domain, infrastructure, API — each with ```yaml rules: - name: attribute-matches-type - description: "Attribute names must match their type annotation in snake_case" + description: Attribute names must match their type annotation in snake_case type: variable filter: { target: attribute } naming: { source: type_annotation, transform: snake_case } - name: bool-method-prefix - description: "Bool-returning functions must start with is_, has_, or should_" + description: Bool-returning functions must start with is_, has_, or should_ type: function filter: { return_type: bool } naming: { prefix: [is_, has_, should_] } - name: domain-module-naming - description: "Module filename must match the primary class name in snake_case" + description: Module filename must match the primary class name in snake_case type: module naming: { source: class_name, transform: snake_case } - name: constant-upper-case - description: "Module-level constants must use UPPER_CASE" + description: Module-level constants must use UPPER_CASE type: variable filter: { target: constant } naming: { case: UPPER_CASE } diff --git a/docs/cookbook/module-matches-class.md b/docs/cookbook/module-matches-class.md index ddc873d..f7c6e8c 100644 --- a/docs/cookbook/module-matches-class.md +++ b/docs/cookbook/module-matches-class.md @@ -9,7 +9,7 @@ When each module contains one primary class, keeping the filename in sync with t ```yaml rules: - name: domain-module-naming - description: "Module filename must match the primary class name in snake_case" + description: Module filename must match the primary class name in snake_case type: module naming: { source: class_name, transform: snake_case } diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md index 14e5203..2095fb2 100644 --- a/docs/getting-started/quick-start.md +++ b/docs/getting-started/quick-start.md @@ -9,7 +9,7 @@ Create `.python-naming-linter.yaml` in your project root and define your naming ```yaml rules: - name: bool-method-prefix - description: "Bool-returning functions must start with is_, has_, or should_" + description: Bool-returning functions must start with is_, has_, or should_ type: function filter: { return_type: bool } naming: { prefix: [is_, has_, should_] } diff --git a/docs/index.md b/docs/index.md index 678a539..cf00734 100644 --- a/docs/index.md +++ b/docs/index.md @@ -34,7 +34,7 @@ pip install python-naming-linter ```yaml rules: - name: bool-method-prefix - description: "Bool-returning functions must start with is_, has_, or should_" + description: Bool-returning functions must start with is_, has_, or should_ type: function filter: { return_type: bool } naming: { prefix: [is_, has_, should_] } From e4db28aa51f5b8f820e0644acd0217f28262f329 Mon Sep 17 00:00:00 2001 From: heumsi Date: Wed, 1 Apr 2026 01:03:34 +0900 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=90=9B=20fix:=20Fix=20line-too-long?= =?UTF-8?q?=20lint=20errors=20in=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/test_config.py | 4 +++- tests/test_reporter.py | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index d5faa54..9a393b7 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -47,7 +47,9 @@ def test_load_yaml_rule_with_description(tmp_path): config_file = tmp_path / "config.yaml" config_file.write_text(config_content) config = load_config(config_file) - assert config.rules[0].description == "Bool-returning functions must use a semantic prefix" + assert config.rules[0].description == ( + "Bool-returning functions must use a semantic prefix" + ) def test_load_yaml_with_include_exclude(tmp_path): diff --git a/tests/test_reporter.py b/tests/test_reporter.py index be8f9e4..375d80b 100644 --- a/tests/test_reporter.py +++ b/tests/test_reporter.py @@ -33,7 +33,11 @@ def test_format_violation_with_description(): output = format_violations("src/service.py", violations) lines = output.strip().split("\n") assert lines[0] == "src/service.py:4" - assert lines[1] == " [bool-method-prefix] Bool-returning functions must use a semantic prefix" + expected = ( + " [bool-method-prefix]" + " Bool-returning functions must use a semantic prefix" + ) + assert lines[1] == expected assert lines[2] == " validate (expected prefix: is_ | has_ | should_)" From 19578927fbb384458e1462a1fbd87004ca0dfd57 Mon Sep 17 00:00:00 2001 From: heumsi Date: Wed, 1 Apr 2026 01:05:12 +0900 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=94=A7=20chore:=20Apply=20ruff=20form?= =?UTF-8?q?at=20to=20test=5Freporter.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) --- tests/test_reporter.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_reporter.py b/tests/test_reporter.py index 375d80b..290124e 100644 --- a/tests/test_reporter.py +++ b/tests/test_reporter.py @@ -34,8 +34,7 @@ def test_format_violation_with_description(): lines = output.strip().split("\n") assert lines[0] == "src/service.py:4" expected = ( - " [bool-method-prefix]" - " Bool-returning functions must use a semantic prefix" + " [bool-method-prefix] Bool-returning functions must use a semantic prefix" ) assert lines[1] == expected assert lines[2] == " validate (expected prefix: is_ | has_ | should_)"