Skip to content

Feature: undocumented-init-params enrichment rule #328

@Alberto-Codes

Description

@Alberto-Codes

Problem Statement

docvet defaults to ignore_init = True, which is correct — most style guides recommend documenting constructor parameters in the class docstring, not in __init__. But this creates a gap: if __init__ is ignored for presence, and the class docstring has no Args: section, then constructor parameters go entirely undocumented.

Current Behavior

A class can have an __init__ with multiple parameters and no documentation of those parameters anywhere — no finding produced.

class Connection:
    """A database connection.

    Attributes:
        host: The database hostname.
        port: The connection port.
    """

    def __init__(self, host: str, port: int, timeout: float = 30.0):
        self.host = host
        self.port = port
        self._timeout = timeout

This class documents host and port as attributes but never documents timeout as a constructor parameter. A user reading the docs doesn't know timeout exists or what it defaults to.

Proposed Solution

Check: undocumented-init-params

Either:

  1. The class docstring has an Args:/Parameters: section, OR
  2. __init__ itself has a docstring with an Args: section

At least one must exist when __init__ takes parameters beyond self.

What it doesn't check

This rule checks that constructor parameters are documented somewhere. It's the constructor-specific companion to the presence check.

Example

class Worker:
    """A background worker."""

    def __init__(self, name: str, max_retries: int = 3):
        self.name = name
        self._max_retries = max_retries

Finding: undocumented-init-params: Class 'Worker' __init__ takes parameters (name, max_retries) but neither the class docstring nor __init__ has an Args section

Fixed:

class Worker:
    """A background worker.

    Args:
        name: The worker's identifier.
        max_retries: Maximum retry attempts before giving up.
    """

    def __init__(self, name: str, max_retries: int = 3):
        self.name = name
        self._max_retries = max_retries

Implementation sketch

def _check_undocumented_init_params(symbol, sections, node_index, config, file_path) -> Finding | None:
    if symbol.kind != "class":
        return None
    node = node_index.get(symbol.line)
    if not isinstance(node, ast.ClassDef):
        return None
    init_node = _find_init(node)
    if init_node is None:
        return None
    # Check if __init__ has non-self parameters
    args = init_node.args
    all_params = args.posonlyargs + args.args + args.kwonlyargs
    non_self = [a for a in all_params if a.arg not in ("self", "cls")]
    if not non_self:
        return None
    # Check class docstring for Args/Parameters section
    if "Args" in sections or "Parameters" in sections:
        return None
    # Check __init__ docstring for Args section
    init_docstring = ast.get_docstring(init_node, clean=False)
    if init_docstring:
        init_sections = _parse_sections(init_docstring)
        if "Args" in init_sections or "Parameters" in init_sections:
            return None
    param_names = ", ".join(a.arg for a in non_self)
    return Finding(
        file=file_path, line=symbol.line, symbol=symbol.name,
        rule="undocumented-init-params",
        message=f"Class '{symbol.name}' __init__ takes parameters ({param_names}) "
                f"but neither the class docstring nor __init__ has an Args section",
        category="recommended",
    )

Configuration

[tool.docvet.enrichment]
require-init-param-docs = true  # default

Acceptance Criteria

  • Classes with parameterized __init__ but no Args: section anywhere produce a finding
  • Args: in class docstring satisfies the check
  • Args: in __init__ docstring satisfies the check
  • Classes with only self in __init__ produce no finding
  • Classes with no __init__ produce no finding
  • Config key require-init-param-docs added

Technical Notes

Files changed: enrichment.py (~40 lines), config.py (~3 lines), tests (~50 lines)

Category: recommended — some classes intentionally have minimal public surface and document via type annotations only.

Sphinx/RST interaction: Check for :param name: / :type name: fields in either docstring.

Interaction with presence.ignore_init: Specifically designed to complement ignore_init = True. When ignore_init = False, the user already requires __init__ docstrings and param agreement would cover the content.


BMAD Workflow

When ready to implement:

  • /bmad-bmm-quick-spec -> /bmad-bmm-quick-dev

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions