orchestrata_ultimate.refactor.codemod.analysis.dead_code

Reference › orchestrata_ultimate › refactor › codemod › analysis › dead_code

DeadCodeDetector finds modules that no entry point reaches, at module granularity only. analyze(target) returns a DeadCodeReport (see _types). _entry_roots treats as roots every module under scripts/ or tests/, every test_*.py file and the files named __main__.py, __init__.py or conftest.py, since these run or load without being imported. _unreachable_modules then follows the graph's dependencies from the roots and reports the modules left over. The report's granularity is module and its list of unreachable symbols stays empty, because the graph has no reachability query for symbols. Read-only; imported by the analysis facade.

At a glance

Source src/orchestrata_ultimate/refactor/codemod/analysis/dead_code.py
Lines of code 104
Classes 1
Functions 0
Imports (internal) orchestrata_ultimate.core.base.traces, orchestrata_ultimate.refactor.codemod._cig, orchestrata_ultimate.refactor.codemod._types, orchestrata_ultimate.refactor.codemod.analysis.protocol
Imported by orchestrata_ultimate.refactor.codemod.analysis
Tests that import it none

Class diagram

Class diagram orchestrata_ultimate.refactor.codemod.analysis.dead_code

Dependency diagram

Dependency diagram orchestrata_ultimate.refactor.codemod.analysis.dead_code

API

Classes

DeadCodeDetector

Decorators: traces(spec='110', story='US-110-006', reqs=['REQ-110-031', 'REQ-110-032']) · Line 34

Report modules unreachable from any entry root.

Method Signature Summary
__init__ __init__(cig: CodeIntelligenceGraph) -> None
analyze analyze(target: str | None = None) -> DeadCodeReport Unreachable modules, or only target when it is unreachable.
_entry_roots _entry_roots() -> frozenset[int] Modules that run or load without being imported.
_unreachable_modules _unreachable_modules(roots: frozenset[int]) -> frozenset[int] Module nodes not reachable from roots over module imports.

Source

Permalink: https://gitlab.com/MSamy/orchestrata-ultimate-architecture/-/blob/a4cbc2bdf/src/orchestrata_ultimate/refactor/codemod/analysis/dead_code.py

"""DeadCodeDetector — modules no entry point reaches (module granularity only).

GAP-1: the CIG has no reachability query and no public edge-type accessor, so
symbol-level dead code (CALLS/INHERITS/IMPORTS/OVERRIDES reachability from
entry roots) is not computable here: ``DeadCodeReport.granularity`` is
``'module'`` and ``unreachable_symbols`` is empty. Module reachability uses
the public ``CodeIntelligenceGraph.dependencies`` over Module nodes (the
IMPORTS edges between modules).

GAP-2: CALLS edges resolve to the first node inserted under a callee name, so
any CALLS-based answer would over-approximate "alive"; module reachability
does not use CALLS and is unaffected.
"""

from __future__ import annotations

from collections import deque
from pathlib import PurePosixPath
from typing import TYPE_CHECKING

from orchestrata_ultimate.core.base.traces import traces
from orchestrata_ultimate.refactor.codemod._types import DeadCodeReport
from orchestrata_ultimate.refactor.codemod.analysis.protocol import resolve_node_id

if TYPE_CHECKING:
    from orchestrata_ultimate.refactor.codemod._cig import CodeIntelligenceGraph

__all__ = ["DeadCodeDetector"]

_ENTRY_NAMES = ("__main__.py", "__init__.py", "conftest.py")


@traces(spec="110", story="US-110-006", reqs=["REQ-110-031", "REQ-110-032"])
class DeadCodeDetector:
    """Report modules unreachable from any entry root."""

    name = "dead_code"

    @traces(spec="110", story="US-110-006", reqs=["REQ-110-031", "REQ-110-032"])
    def __init__(self, cig: CodeIntelligenceGraph) -> None:
        self._cig = cig

    @traces(spec="110", story="US-110-006", reqs=["REQ-110-031", "REQ-110-032"])
    def analyze(self, target: str | None = None) -> DeadCodeReport:
        """Unreachable modules, or only ``target`` when it is unreachable.

        Raises:
            ValueError: ``target`` names no module in the graph.
        """
        roots = self._entry_roots()
        unreachable = self._unreachable_modules(roots)
        if target is not None:
            node_id = resolve_node_id(self._cig, target)
            if node_id is None:
                raise ValueError(f"module '{target}' not found in CIG")
            unreachable = unreachable & {node_id}
        return DeadCodeReport(
            roots=roots,
            unreachable_modules=frozenset(unreachable),
            unreachable_symbols=frozenset(),
            granularity="module",
        )

    @traces(spec="110", story="US-110-006", reqs=["REQ-110-031"])
    def _entry_roots(self) -> frozenset[int]:
        """Modules that run or load without being imported.

        Roots: anything under ``scripts/`` or ``tests/``, ``test_*.py``
        files, and ``__main__.py`` / ``__init__.py`` / ``conftest.py``.
        """
        roots: set[int] = set()
        for nid, node in self._cig.nodes.items():
            if node.node_type != "Module":
                continue
            pure = PurePosixPath(node.path)
            if (
                pure.name in _ENTRY_NAMES
                or pure.name.startswith("test_")
                or "tests" in pure.parts
                or "scripts" in pure.parts
            ):
                roots.add(nid)
        return frozenset(roots)

    @traces(spec="110", story="US-110-006", reqs=["REQ-110-031"])
    def _unreachable_modules(self, roots: frozenset[int]) -> frozenset[int]:
        """Module nodes not reachable from ``roots`` over module imports.

        With no roots there is nothing to reach from, so nothing is reported.
        """
        if not roots:
            return frozenset()
        nodes = self._cig.nodes
        seen: set[int] = set(roots)
        queue = deque(roots)
        while queue:
            current = queue.popleft()
            for dep in self._cig.dependencies(current):
                node = nodes.get(dep)
                if node is not None and node.node_type == "Module" and dep not in seen:
                    seen.add(dep)
                    queue.append(dep)
        modules = {nid for nid, node in nodes.items() if node.node_type == "Module"}
        return frozenset(modules - seen)