Purpose

When an attribute holds a repository, service, or other typed object, keeping the attribute name in sync with the type annotation removes ambiguity and makes dependency injection transparent at a glance. This rule requires each class attribute name to be the snake_case form of its type annotation.

Configuration

rules:
  - name: attribute-matches-type
    type: variable
    filter: { target: attribute }
    naming: { source: type_annotation, transform: snake_case }

apply:
  - name: domain-layer
    rules: [attribute-matches-type]
    modules: contexts.*.domain

Violation Example

# contexts/billing/domain/service.py

class BillingService:
    def __init__(self, repo: SubscriptionRepository) -> None:
        self.repo = repo   # should be subscription_repository

Passing Example

# contexts/billing/domain/service.py

class BillingService:
    def __init__(self, repo: SubscriptionRepository) -> None:
        self.subscription_repository = repo

The {prefix}_{expected} form is also allowed. For example, source_object_context: ObjectContext passes because the name ends with _object_context.

Output

$ pnl check
contexts/billing/domain/service.py:5
    [attribute-matches-type] repo (expected: subscription_repository)

Found 1 violation(s).