orchestrata_ultimate.verification.review.checks_quality

Reference › orchestrata_ultimate › verification › review › checks_quality

The quality checks of the deterministic review, called by the review engine and by the refactor codemod's review checks. check_type_hints (check 17) requires annotations on public functions, with helpers for __init__ parameters, return annotations and parameters (skipping self and cls). check_mutable_defaults (check 18, DEFAULT_VALUES) flags mutable default arguments. check_docstrings (check 19) requires docstrings on public classes and functions, and check_line_length (check 21) flags lines longer than 88 characters by default. Each returns a list of issue tuples and only reads the parsed tree and source lines. Public-name tests come from helpers. The commit-time lint step is described on the ruff gate page.

At a glance

Source src/orchestrata_ultimate/verification/review/checks_quality.py
Lines of code 207
Classes 0
Functions 7
Imports (internal) orchestrata_ultimate.verification.review.helpers, orchestrata_ultimate.verification.review.types
Imported by orchestrata_ultimate.refactor.codemod.review_checks, orchestrata_ultimate.verification.review.engine
Tests that import it tests/verification/review/test_checks_quality.py, tests/verification/test_checks_quality.py, tests/verification/test_review_checks.py, tests/verification/test_review_checks_expanded.py

Class diagram

No classes; see Functions.

Dependency diagram

Dependency diagram orchestrata_ultimate.verification.review.checks_quality

API

Functions

Function Signature Summary
check_type_hints check_type_hints(filepath: str, tree: ast.Module) -> list[Issue] Check 17: TYPE_HINTS -- public functions have annotations.
_check_init_params _check_init_params(node: ast.FunctionDef | ast.AsyncFunctionDef, rel_name: str, issues: list[Issue]) -> None Check init parameter annotations (skip self).
_check_return_annotation _check_return_annotation(node: ast.FunctionDef | ast.AsyncFunctionDef, rel_name: str, issues: list[Issue]) -> None Check that a function has a return type annotation.
_check_param_annotations _check_param_annotations(node: ast.FunctionDef | ast.AsyncFunctionDef, rel_name: str, issues: list[Issue]) -> None Check parameter type annotations (skip self/cls).
check_mutable_defaults check_mutable_defaults(filepath: str, tree: ast.Module) -> list[Issue] Check 18: DEFAULT_VALUES -- no mutable defaults.
check_docstrings check_docstrings(filepath: str, tree: ast.Module) -> list[Issue] Check 19: DOCSTRINGS -- public classes and functions.
check_line_length check_line_length(filepath: str, lines: list[str], max_len: int = 88) -> list[Issue] Check 21: LINE_LENGTH -- max characters per line.

Source

Permalink: https://gitlab.com/MSamy/orchestrata-ultimate-architecture/-/blob/a4cbc2bdf/src/orchestrata_ultimate/verification/review/checks_quality.py

"""Quality checks: type hints, mutable defaults, docstrings, line length.

Covers checklist items 17, 18, 19, 21 from the CODE_REVIEW_PROMPT.
"""

import ast
import os

from orchestrata_ultimate.verification.review.helpers import _is_public
from orchestrata_ultimate.verification.review.types import Issue


def check_type_hints(
    filepath: str,
    tree: ast.Module,
) -> list[Issue]:
    """Check 17: TYPE_HINTS -- public functions have annotations.

    Args:
        filepath: Absolute path to the file.
        tree: Parsed AST module.

    Returns:
        List of missing type annotation issues.
    """
    issues: list[Issue] = []
    rel_name = os.path.basename(filepath)
    for node in ast.walk(tree):
        if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
            continue
        if not _is_public(node.name):
            continue
        if node.name == "__init__":
            _check_init_params(node, rel_name, issues)
            continue
        _check_return_annotation(node, rel_name, issues)
        _check_param_annotations(node, rel_name, issues)
    return issues


def _check_init_params(
    node: ast.FunctionDef | ast.AsyncFunctionDef,
    rel_name: str,
    issues: list[Issue],
) -> None:
    """Check __init__ parameter annotations (skip self)."""
    for arg in node.args.args[1:]:
        if arg.annotation is None:
            issues.append(
                (
                    "TYPE_HINTS",
                    rel_name,
                    f"L{node.lineno}: `{node.name}()` "
                    f"param '{arg.arg}' has no type annotation.",
                )
            )


def _check_return_annotation(
    node: ast.FunctionDef | ast.AsyncFunctionDef,
    rel_name: str,
    issues: list[Issue],
) -> None:
    """Check that a function has a return type annotation."""
    if node.returns is None:
        issues.append(
            (
                "TYPE_HINTS",
                rel_name,
                f"L{node.lineno}: `{node.name}()` missing return type annotation.",
            )
        )


def _check_param_annotations(
    node: ast.FunctionDef | ast.AsyncFunctionDef,
    rel_name: str,
    issues: list[Issue],
) -> None:
    """Check parameter type annotations (skip self/cls)."""
    params = node.args.args
    start = 0
    if params and params[0].arg in ("self", "cls"):
        start = 1
    for arg in params[start:]:
        if arg.annotation is None:
            issues.append(
                (
                    "TYPE_HINTS",
                    rel_name,
                    f"L{node.lineno}: `{node.name}()` "
                    f"param '{arg.arg}' has no type annotation.",
                )
            )


def check_mutable_defaults(
    filepath: str,
    tree: ast.Module,
) -> list[Issue]:
    """Check 18: DEFAULT_VALUES -- no mutable defaults.

    Args:
        filepath: Absolute path to the file.
        tree: Parsed AST module.

    Returns:
        List of mutable default argument issues.
    """
    issues: list[Issue] = []
    rel_name = os.path.basename(filepath)
    mutable_types = (ast.List, ast.Dict, ast.Set)
    for node in ast.walk(tree):
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
            all_defaults = node.args.defaults + node.args.kw_defaults
            for default in all_defaults:
                if default is not None and isinstance(default, mutable_types):
                    issues.append(
                        (
                            "MUTABLE_DEFAULT",
                            rel_name,
                            f"L{node.lineno}: `{node.name}()` has "
                            f"mutable default argument. "
                            f"Use None instead.",
                        )
                    )
    return issues


def check_docstrings(
    filepath: str,
    tree: ast.Module,
) -> list[Issue]:
    """Check 19: DOCSTRINGS -- public classes and functions.

    Args:
        filepath: Absolute path to the file.
        tree: Parsed AST module.

    Returns:
        List of missing docstring issues.
    """
    issues: list[Issue] = []
    rel_name = os.path.basename(filepath)
    for node in ast.walk(tree):
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
            if _is_public(node.name) and not ast.get_docstring(node):
                issues.append(
                    (
                        "DOCSTRING",
                        rel_name,
                        f"L{node.lineno}: `{node.name}()` missing docstring.",
                    )
                )
        elif isinstance(node, ast.ClassDef):
            if _is_public(node.name) and not ast.get_docstring(node):
                issues.append(
                    (
                        "DOCSTRING",
                        rel_name,
                        f"L{node.lineno}: `{node.name}` class missing docstring.",
                    )
                )
    return issues


def check_line_length(
    filepath: str,
    lines: list[str],
    max_len: int = 88,
) -> list[Issue]:
    """Check 21: LINE_LENGTH -- max characters per line.

    Args:
        filepath: Absolute path to the file.
        lines: File lines.
        max_len: Maximum allowed line length.

    Returns:
        List of line length violation issues.
    """
    issues: list[Issue] = []
    rel_name = os.path.basename(filepath)
    violations = 0
    for i, line in enumerate(lines, 1):
        stripped = line.rstrip("\n\r")
        if len(stripped) > max_len:
            if "http://" in stripped or "https://" in stripped:
                continue
            violations += 1
            if violations <= 3:
                issues.append(
                    (
                        "LINE_LENGTH",
                        rel_name,
                        f"L{i}: {len(stripped)} chars (max {max_len}).",
                    )
                )
    if violations > 3:
        issues.append(
            (
                "LINE_LENGTH",
                rel_name,
                f"...and {violations - 3} more lines over {max_len} chars.",
            )
        )
    return issues