Skip to content

Pattern Heuristics & Fixes (alnoms.patterns & alnoms.fixes)

The internal diagnostic rule engine that identifies computational bottlenecks and their corresponding deterministic cures.

🔍 Detectors

Orchestrates AST analysis using the registered pattern detectors.

The engine loads all OSS detectors by default and conditionally extends the registry with PRO and ENTERPRISE detectors based on environment feature flags. Each detector is responsible for identifying a specific algorithmic anti‑pattern.

This class exposes a single static method, analyze_code, which parses the file into an AST and dispatches it to all registered detectors.

Source code in src/alnoms/patterns/heuristics.py
class HeuristicsEngine:
    """Orchestrates AST analysis using the registered pattern detectors.

    The engine loads all OSS detectors by default and conditionally extends
    the registry with PRO and ENTERPRISE detectors based on environment
    feature flags. Each detector is responsible for identifying a specific
    algorithmic anti‑pattern.

    This class exposes a single static method, `analyze_code`, which parses
    the file into an AST and dispatches it to all registered detectors.
    """

    @staticmethod
    def analyze_code(path: str) -> List[Dict[str, Any]]:
        """Analyze a Python file using all registered pattern detectors.

        Args:
            path (str): Path to the Python source file.

        Returns:
            List[Dict[str, Any]]:
                A flat list of findings from all detectors. Each finding is a
                dictionary containing detector‑specific metadata such as:
                    - "function": Function where the issue occurs
                    - "pattern_id": Identifier of the detector
                    - "issue": Description of the anti‑pattern
                    - "line": Line number of the issue
                    - Additional detector‑specific fields

        Notes:
            - Empty files return an empty list.
            - Syntax errors or unexpected exceptions are captured and returned
              as a single file‑level finding rather than raising an exception.
        """
        try:
            with open(path, "r", encoding="utf-8", errors="ignore") as f:
                source = f.read()
                if not source.strip():
                    return []
                tree = ast.parse(source)

            all_findings = []
            # Dispatch AST to all registered detectors
            for detector in REGISTRY:
                findings = detector.detect(tree)
                all_findings.extend(findings)

            return all_findings

        except Exception as e:
            return [
                {
                    "function": "file_level",
                    "issue": f"Static Analysis Error: {str(e)}",
                    "complexity": "Unknown",
                    "suggestion": "Check file syntax. Empirical tests will proceed.",
                    "line": 0,
                }
            ]

analyze_code(path) staticmethod

Analyze a Python file using all registered pattern detectors.

Parameters:

Name Type Description Default
path str

Path to the Python source file.

required

Returns:

Type Description
List[Dict[str, Any]]

List[Dict[str, Any]]: A flat list of findings from all detectors. Each finding is a dictionary containing detector‑specific metadata such as: - "function": Function where the issue occurs - "pattern_id": Identifier of the detector - "issue": Description of the anti‑pattern - "line": Line number of the issue - Additional detector‑specific fields

Notes
  • Empty files return an empty list.
  • Syntax errors or unexpected exceptions are captured and returned as a single file‑level finding rather than raising an exception.
Source code in src/alnoms/patterns/heuristics.py
@staticmethod
def analyze_code(path: str) -> List[Dict[str, Any]]:
    """Analyze a Python file using all registered pattern detectors.

    Args:
        path (str): Path to the Python source file.

    Returns:
        List[Dict[str, Any]]:
            A flat list of findings from all detectors. Each finding is a
            dictionary containing detector‑specific metadata such as:
                - "function": Function where the issue occurs
                - "pattern_id": Identifier of the detector
                - "issue": Description of the anti‑pattern
                - "line": Line number of the issue
                - Additional detector‑specific fields

    Notes:
        - Empty files return an empty list.
        - Syntax errors or unexpected exceptions are captured and returned
          as a single file‑level finding rather than raising an exception.
    """
    try:
        with open(path, "r", encoding="utf-8", errors="ignore") as f:
            source = f.read()
            if not source.strip():
                return []
            tree = ast.parse(source)

        all_findings = []
        # Dispatch AST to all registered detectors
        for detector in REGISTRY:
            findings = detector.detect(tree)
            all_findings.extend(findings)

        return all_findings

    except Exception as e:
        return [
            {
                "function": "file_level",
                "issue": f"Static Analysis Error: {str(e)}",
                "complexity": "Unknown",
                "suggestion": "Check file syntax. Empirical tests will proceed.",
                "line": 0,
            }
        ]

Bases: PatternDetector

Detects nested loops and classifies their algorithmic intent.

This detector identifies functions containing nested for or while loops and applies lightweight heuristics to infer the likely purpose of the nested structure. The intent classification enables downstream fixers to provide context‑aware remediation strategies.

Supported intent categories

• membership — equality checks, membership tests, pairwise scans • sorting — range(len(...)) patterns resembling selection/bubble sort • dfs — nested iteration over adjacency lists or graph structures • generic — fallback for unclassified quadratic scans

Source code in src/alnoms/patterns/nested_loops.py
class NestedLoopDetector(PatternDetector):
    """Detects nested loops and classifies their algorithmic intent.

    This detector identifies functions containing nested `for` or `while`
    loops and applies lightweight heuristics to infer the likely purpose
    of the nested structure. The intent classification enables downstream
    fixers to provide context‑aware remediation strategies.

    Supported intent categories:
        • **membership** — equality checks, membership tests, pairwise scans
        • **sorting** — range(len(...)) patterns resembling selection/bubble sort
        • **dfs** — nested iteration over adjacency lists or graph structures
        • **generic** — fallback for unclassified quadratic scans
    """

    id = "nested_loops"
    name = "Nested Loop Detection"
    description = "Detects loops nested inside other loops."

    def _classify_intent(self, loop: ast.AST) -> str:
        """Infers the likely algorithmic intent of a nested loop.

        This heuristic inspects the body of a loop to detect common
        patterns associated with membership checks, sorting‑like scans,
        or DFS‑style neighbor traversal. If no recognizable pattern is
        found, the loop is classified as ``generic``.

        Args:
            loop (ast.AST): The AST node representing the outer loop.

        Returns:
            str: One of ``"membership"``, ``"sorting"``, ``"dfs"``, or
            ``"generic"``.
        """
        for child in ast.walk(loop):
            # Membership / equality checks
            if isinstance(child, ast.Compare):
                for op in child.ops:
                    if isinstance(op, (ast.Eq, ast.In, ast.NotIn)):
                        return "membership"

            # Sorting-like: range(len(...))
            if isinstance(child, ast.Call) and isinstance(child.func, ast.Name):
                if child.func.id == "range":
                    for arg in child.args:
                        if isinstance(arg, ast.Call) and isinstance(arg.func, ast.Name):
                            if arg.func.id == "len":
                                return "sorting"

            # DFS-like: adjacency traversal
            if isinstance(child, ast.Name) and child.id.lower() in {
                "neighbors",
                "adj",
                "graph",
            }:
                return "dfs"

        return "generic"

    def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
        """Detects nested loops within function bodies.

        Traverses the AST to locate functions containing nested ``for`` or
        ``while`` loops. When a nested loop is found, the detector assigns
        an intent classification and records a single finding per function
        to avoid noise.

        Args:
            tree (ast.AST): The parsed AST of the module being analyzed.

        Returns:
            List[Dict[str, Any]]: A list of findings, where each finding
            includes:
                - function (str): Name of the function containing the loop.
                - pattern_id (str): Identifier for this detector.
                - issue (str): Human-readable description of the problem.
                - complexity (str): Complexity classification (always O(N^2)).
                - intent (str): Classified nested-loop intent.
                - suggestion (str): Guidance for remediation.
                - line (int): Line number of the outer loop.
        """
        findings = []

        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                loops = [
                    c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
                ]

                for loop in loops:
                    sub_loops = [
                        c
                        for c in ast.walk(loop)
                        if isinstance(c, (ast.For, ast.While)) and c is not loop
                    ]

                    if sub_loops:
                        intent = self._classify_intent(loop)

                        findings.append(
                            {
                                "function": node.name,
                                "pattern_id": self.id,
                                "issue": "Nested loop detected",
                                "complexity": "O(N^2)",
                                "intent": intent,
                                "suggestion": (
                                    "Nested loop detected; see fixer for pattern-specific remediation."
                                ),
                                "line": loop.lineno,
                            }
                        )
                        break  # one finding per function

        return findings

detect(tree)

Detects nested loops within function bodies.

Traverses the AST to locate functions containing nested for or while loops. When a nested loop is found, the detector assigns an intent classification and records a single finding per function to avoid noise.

Parameters:

Name Type Description Default
tree AST

The parsed AST of the module being analyzed.

required

Returns:

Name Type Description
List[Dict[str, Any]]

List[Dict[str, Any]]: A list of findings, where each finding

includes List[Dict[str, Any]]
  • function (str): Name of the function containing the loop.
  • pattern_id (str): Identifier for this detector.
  • issue (str): Human-readable description of the problem.
  • complexity (str): Complexity classification (always O(N^2)).
  • intent (str): Classified nested-loop intent.
  • suggestion (str): Guidance for remediation.
  • line (int): Line number of the outer loop.
Source code in src/alnoms/patterns/nested_loops.py
def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
    """Detects nested loops within function bodies.

    Traverses the AST to locate functions containing nested ``for`` or
    ``while`` loops. When a nested loop is found, the detector assigns
    an intent classification and records a single finding per function
    to avoid noise.

    Args:
        tree (ast.AST): The parsed AST of the module being analyzed.

    Returns:
        List[Dict[str, Any]]: A list of findings, where each finding
        includes:
            - function (str): Name of the function containing the loop.
            - pattern_id (str): Identifier for this detector.
            - issue (str): Human-readable description of the problem.
            - complexity (str): Complexity classification (always O(N^2)).
            - intent (str): Classified nested-loop intent.
            - suggestion (str): Guidance for remediation.
            - line (int): Line number of the outer loop.
    """
    findings = []

    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            loops = [
                c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
            ]

            for loop in loops:
                sub_loops = [
                    c
                    for c in ast.walk(loop)
                    if isinstance(c, (ast.For, ast.While)) and c is not loop
                ]

                if sub_loops:
                    intent = self._classify_intent(loop)

                    findings.append(
                        {
                            "function": node.name,
                            "pattern_id": self.id,
                            "issue": "Nested loop detected",
                            "complexity": "O(N^2)",
                            "intent": intent,
                            "suggestion": (
                                "Nested loop detected; see fixer for pattern-specific remediation."
                            ),
                            "line": loop.lineno,
                        }
                    )
                    break  # one finding per function

    return findings

Bases: PatternDetector

Detect inefficient membership tests inside loops.

This detector flags occurrences of:

- `x in some_list`
- `x not in some_list`
- membership checks on literal lists/tuples
- membership checks on variables that are likely lists

When these appear inside loops, they create a hidden O(N²) pattern due to repeated linear scans. The detector uses heuristics to avoid false positives on safe containers such as sets, dicts, ranges, and variables whose names imply O(1) lookup semantics (e.g., visited, cache, lookup).

Attributes:

Name Type Description
SAFE_CONTAINER_HINTS set[str]

Variable‑name substrings that imply O(1) membership semantics.

Source code in src/alnoms/patterns/inefficient_membership.py
class MembershipDetector(PatternDetector):
    """Detect inefficient membership tests inside loops.

    This detector flags occurrences of:

        - `x in some_list`
        - `x not in some_list`
        - membership checks on literal lists/tuples
        - membership checks on variables that are likely lists

    When these appear inside loops, they create a hidden O(N²) pattern due to
    repeated linear scans. The detector uses heuristics to avoid false positives
    on safe containers such as sets, dicts, ranges, and variables whose names
    imply O(1) lookup semantics (e.g., `visited`, `cache`, `lookup`).

    Attributes:
        SAFE_CONTAINER_HINTS (set[str]):
            Variable‑name substrings that imply O(1) membership semantics.
    """

    id = "inefficient_membership"
    name = "Inefficient Membership Detection"
    description = "Detects 'in' operator usage on list-like containers inside loops."

    SAFE_CONTAINER_HINTS = {
        "set",
        "dict",
        "map",
        "cache",
        "visited",
        "seen",
        "lookup",
        "index",
    }

    def _is_safe_container_name(self, name: str) -> bool:
        """Return True if the variable name implies O(1) membership.

        Args:
            name (str): Variable name extracted from the AST.

        Returns:
            bool: True if the name suggests a set/dict‑like container.
        """
        name = name.lower()
        return any(hint in name for hint in self.SAFE_CONTAINER_HINTS)

    def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
        """Analyze the AST and flag inefficient membership tests inside loops.

        Args:
            tree (ast.AST): Parsed AST of the target Python file.

        Returns:
            List[Dict[str, Any]]:
                A list of findings. Each finding includes:
                    - "function": Function where the issue occurs
                    - "pattern_id": Identifier for this detector
                    - "issue": Description of the membership test
                    - "complexity": Estimated complexity impact
                    - "suggestion": Recommended remediation
                    - "line": Line number of the issue

        Notes:
            - Literal lists/tuples of size ≤ 3 are ignored as they are cheap.
            - Safe containers (set/dict/range) are skipped.
            - Variable names are heuristically classified for safety.
        """
        findings = []

        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                loops = [
                    c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
                ]

                for loop in loops:
                    for child in ast.walk(loop):
                        # Look for "x in Y" or "x not in Y"
                        if isinstance(child, ast.Compare):
                            for op in child.ops:
                                if isinstance(op, (ast.In, ast.NotIn)):
                                    container = child.comparators[0]

                                    # --- CASE 1: Literal list/tuple ---
                                    if isinstance(container, (ast.List, ast.Tuple)):
                                        # Ignore tiny constant lists (<= 3)
                                        if len(container.elts) <= 3:
                                            continue

                                        findings.append(
                                            {
                                                "function": node.name,
                                                "pattern_id": self.id,
                                                "issue": (
                                                    "Membership test on list literal "
                                                    "inside loop"
                                                ),
                                                "complexity": "Potential O(N^2)",
                                                "suggestion": (
                                                    "Convert the list literal to a set "
                                                    "for O(1) lookups."
                                                ),
                                                "line": child.lineno,
                                            }
                                        )
                                        continue

                                    # --- CASE 2: Variable name ---
                                    if isinstance(container, ast.Name):
                                        var_name = container.id

                                        # Skip safe containers by name
                                        if self._is_safe_container_name(var_name):
                                            continue

                                        findings.append(
                                            {
                                                "function": node.name,
                                                "pattern_id": self.id,
                                                "issue": (
                                                    f"Membership test ('in {var_name}') "
                                                    "inside loop"
                                                ),
                                                "complexity": "Potential O(N^2)",
                                                "suggestion": (
                                                    f"Ensure '{var_name}' is a Set or Dict "
                                                    "for O(1) lookups, not a List."
                                                ),
                                                "line": child.lineno,
                                            }
                                        )
                                        continue

                                    # --- CASE 3: Calls like set(...), dict(...), range(...) ---
                                    if isinstance(container, ast.Call):
                                        if isinstance(container.func, ast.Name):
                                            if container.func.id in {
                                                "set",
                                                "dict",
                                                "range",
                                            }:
                                                continue  # safe

                                    # --- DEFAULT: Unknown container type ---
                                    findings.append(
                                        {
                                            "function": node.name,
                                            "pattern_id": self.id,
                                            "issue": "Membership test inside loop",
                                            "complexity": "Potential O(N^2)",
                                            "suggestion": (
                                                "Ensure the target collection is a Set or Dict."
                                            ),
                                            "line": child.lineno,
                                        }
                                    )

        return findings

detect(tree)

Analyze the AST and flag inefficient membership tests inside loops.

Parameters:

Name Type Description Default
tree AST

Parsed AST of the target Python file.

required

Returns:

Type Description
List[Dict[str, Any]]

List[Dict[str, Any]]: A list of findings. Each finding includes: - "function": Function where the issue occurs - "pattern_id": Identifier for this detector - "issue": Description of the membership test - "complexity": Estimated complexity impact - "suggestion": Recommended remediation - "line": Line number of the issue

Notes
  • Literal lists/tuples of size ≤ 3 are ignored as they are cheap.
  • Safe containers (set/dict/range) are skipped.
  • Variable names are heuristically classified for safety.
Source code in src/alnoms/patterns/inefficient_membership.py
def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
    """Analyze the AST and flag inefficient membership tests inside loops.

    Args:
        tree (ast.AST): Parsed AST of the target Python file.

    Returns:
        List[Dict[str, Any]]:
            A list of findings. Each finding includes:
                - "function": Function where the issue occurs
                - "pattern_id": Identifier for this detector
                - "issue": Description of the membership test
                - "complexity": Estimated complexity impact
                - "suggestion": Recommended remediation
                - "line": Line number of the issue

    Notes:
        - Literal lists/tuples of size ≤ 3 are ignored as they are cheap.
        - Safe containers (set/dict/range) are skipped.
        - Variable names are heuristically classified for safety.
    """
    findings = []

    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            loops = [
                c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
            ]

            for loop in loops:
                for child in ast.walk(loop):
                    # Look for "x in Y" or "x not in Y"
                    if isinstance(child, ast.Compare):
                        for op in child.ops:
                            if isinstance(op, (ast.In, ast.NotIn)):
                                container = child.comparators[0]

                                # --- CASE 1: Literal list/tuple ---
                                if isinstance(container, (ast.List, ast.Tuple)):
                                    # Ignore tiny constant lists (<= 3)
                                    if len(container.elts) <= 3:
                                        continue

                                    findings.append(
                                        {
                                            "function": node.name,
                                            "pattern_id": self.id,
                                            "issue": (
                                                "Membership test on list literal "
                                                "inside loop"
                                            ),
                                            "complexity": "Potential O(N^2)",
                                            "suggestion": (
                                                "Convert the list literal to a set "
                                                "for O(1) lookups."
                                            ),
                                            "line": child.lineno,
                                        }
                                    )
                                    continue

                                # --- CASE 2: Variable name ---
                                if isinstance(container, ast.Name):
                                    var_name = container.id

                                    # Skip safe containers by name
                                    if self._is_safe_container_name(var_name):
                                        continue

                                    findings.append(
                                        {
                                            "function": node.name,
                                            "pattern_id": self.id,
                                            "issue": (
                                                f"Membership test ('in {var_name}') "
                                                "inside loop"
                                            ),
                                            "complexity": "Potential O(N^2)",
                                            "suggestion": (
                                                f"Ensure '{var_name}' is a Set or Dict "
                                                "for O(1) lookups, not a List."
                                            ),
                                            "line": child.lineno,
                                        }
                                    )
                                    continue

                                # --- CASE 3: Calls like set(...), dict(...), range(...) ---
                                if isinstance(container, ast.Call):
                                    if isinstance(container.func, ast.Name):
                                        if container.func.id in {
                                            "set",
                                            "dict",
                                            "range",
                                        }:
                                            continue  # safe

                                # --- DEFAULT: Unknown container type ---
                                findings.append(
                                    {
                                        "function": node.name,
                                        "pattern_id": self.id,
                                        "issue": "Membership test inside loop",
                                        "complexity": "Potential O(N^2)",
                                        "suggestion": (
                                            "Ensure the target collection is a Set or Dict."
                                        ),
                                        "line": child.lineno,
                                    }
                                )

    return findings

Bases: PatternDetector

Detect potentially expensive function calls inside loops.

This detector walks the AST of each function and inspects all for and while loops. Any function call inside a loop that is not part of the SAFE_CALLS whitelist is flagged as a potential performance risk.

The whitelist includes:

  • Common O(1) built‑ins (len, range, int, etc.)
  • Common O(1) container methods (append, pop, get, etc.)
  • Sorting and I/O calls, which are handled by dedicated detectors

Attributes:

Name Type Description
SAFE_CALLS set[str]

Set of known O(1) or trivial operations that should not be flagged.

Source code in src/alnoms/patterns/expensive_calls.py
class ExpensiveCallDetector(PatternDetector):
    """Detect potentially expensive function calls inside loops.

    This detector walks the AST of each function and inspects all `for` and
    `while` loops. Any function call inside a loop that is *not* part of the
    `SAFE_CALLS` whitelist is flagged as a potential performance risk.

    The whitelist includes:

    - Common O(1) built‑ins (`len`, `range`, `int`, etc.)
    - Common O(1) container methods (`append`, `pop`, `get`, etc.)
    - Sorting and I/O calls, which are handled by dedicated detectors

    Attributes:
        SAFE_CALLS (set[str]): Set of known O(1) or trivial operations that
            should not be flagged.
    """

    id = "expensive_calls"
    name = "Expensive Call Detection"
    description = "Detects potentially expensive function calls inside loops."

    # --- SAFE CALLS (O(1) or trivial) ---
    SAFE_CALLS = {
        # Builtins
        "range",
        "len",
        "print",
        "enumerate",
        "int",
        "str",
        "float",
        "list",
        "dict",
        "set",
        "tuple",
        # Common O(1) methods
        "append",
        "pop",
        "add",
        "get",
        "update",
        "remove",
        "clear",
        # Sorting is handled by a separate detector
        "sort",
        "sorted",
        # IO is handled separately
        "open",
        "read",
        "write",
    }

    def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
        """Analyze the AST and flag non‑whitelisted calls inside loops.

        Args:
            tree (ast.AST): Parsed AST of the target Python file.

        Returns:
            List[Dict[str, Any]]:
                A list of findings. Each finding includes:
                    - "function": Name of the function containing the loop.
                    - "pattern_id": Identifier for this detector.
                    - "issue": Description of the detected call.
                    - "complexity": Estimated complexity impact.
                    - "suggestion": Recommended remediation.
                    - "line": Line number of the call.

        Notes:
            - Only explicit `ast.Call` nodes inside `for`/`while` loops are inspected.
            - Calls to safe O(1) operations are ignored.
            - Sorting and I/O calls are delegated to other detectors.
        """
        findings = []

        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                loops = [
                    c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
                ]

                for loop in loops:
                    for child in ast.walk(loop):
                        if isinstance(child, ast.Call):
                            # Extract function name
                            func_name = ""
                            if isinstance(child.func, ast.Name):
                                func_name = child.func.id
                            elif isinstance(child.func, ast.Attribute):
                                func_name = child.func.attr

                            # Skip safe calls
                            if func_name in self.SAFE_CALLS:
                                continue

                            # Skip empty or unknown names
                            if not func_name:
                                continue

                            # Flag everything else as potentially expensive
                            findings.append(
                                {
                                    "function": node.name,
                                    "pattern_id": self.id,
                                    "issue": (
                                        f"Potentially expensive call '{func_name}()' "
                                        "inside loop"
                                    ),
                                    "complexity": "O(N * K)",
                                    "suggestion": (
                                        "Consider hoisting or caching this call outside "
                                        "the loop to avoid repeated execution."
                                    ),
                                    "line": child.lineno,
                                }
                            )

        return findings

detect(tree)

Analyze the AST and flag non‑whitelisted calls inside loops.

Parameters:

Name Type Description Default
tree AST

Parsed AST of the target Python file.

required

Returns:

Type Description
List[Dict[str, Any]]

List[Dict[str, Any]]: A list of findings. Each finding includes: - "function": Name of the function containing the loop. - "pattern_id": Identifier for this detector. - "issue": Description of the detected call. - "complexity": Estimated complexity impact. - "suggestion": Recommended remediation. - "line": Line number of the call.

Notes
  • Only explicit ast.Call nodes inside for/while loops are inspected.
  • Calls to safe O(1) operations are ignored.
  • Sorting and I/O calls are delegated to other detectors.
Source code in src/alnoms/patterns/expensive_calls.py
def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
    """Analyze the AST and flag non‑whitelisted calls inside loops.

    Args:
        tree (ast.AST): Parsed AST of the target Python file.

    Returns:
        List[Dict[str, Any]]:
            A list of findings. Each finding includes:
                - "function": Name of the function containing the loop.
                - "pattern_id": Identifier for this detector.
                - "issue": Description of the detected call.
                - "complexity": Estimated complexity impact.
                - "suggestion": Recommended remediation.
                - "line": Line number of the call.

    Notes:
        - Only explicit `ast.Call` nodes inside `for`/`while` loops are inspected.
        - Calls to safe O(1) operations are ignored.
        - Sorting and I/O calls are delegated to other detectors.
    """
    findings = []

    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            loops = [
                c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
            ]

            for loop in loops:
                for child in ast.walk(loop):
                    if isinstance(child, ast.Call):
                        # Extract function name
                        func_name = ""
                        if isinstance(child.func, ast.Name):
                            func_name = child.func.id
                        elif isinstance(child.func, ast.Attribute):
                            func_name = child.func.attr

                        # Skip safe calls
                        if func_name in self.SAFE_CALLS:
                            continue

                        # Skip empty or unknown names
                        if not func_name:
                            continue

                        # Flag everything else as potentially expensive
                        findings.append(
                            {
                                "function": node.name,
                                "pattern_id": self.id,
                                "issue": (
                                    f"Potentially expensive call '{func_name}()' "
                                    "inside loop"
                                ),
                                "complexity": "O(N * K)",
                                "suggestion": (
                                    "Consider hoisting or caching this call outside "
                                    "the loop to avoid repeated execution."
                                ),
                                "line": child.lineno,
                            }
                        )

    return findings

Bases: PatternDetector

Detect high‑frequency I/O operations inside loops.

This detector walks the AST of each function and inspects all for and while loops. Any call to open, read, or write inside a loop is flagged as a performance risk due to:

  • Excessive system calls
  • Kernel context switching
  • Disk or network latency
  • Reduced throughput compared to buffered operations

Attributes:

Name Type Description
id str

Unique identifier for this detector.

name str

Human‑readable name for reporting.

description str

Short description of the detector's purpose.

Source code in src/alnoms/patterns/high_freq_io.py
class HighFrequencyIODetector(PatternDetector):
    """Detect high‑frequency I/O operations inside loops.

    This detector walks the AST of each function and inspects all `for` and
    `while` loops. Any call to `open`, `read`, or `write` inside a loop is
    flagged as a performance risk due to:

    - Excessive system calls
    - Kernel context switching
    - Disk or network latency
    - Reduced throughput compared to buffered operations

    Attributes:
        id (str): Unique identifier for this detector.
        name (str): Human‑readable name for reporting.
        description (str): Short description of the detector's purpose.
    """

    id = "high_freq_io"
    name = "High-Frequency I/O Detection"
    description = "Detects file or network I/O operations inside loops."

    def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
        """Analyze the AST and flag I/O operations inside loops.

        Args:
            tree (ast.AST): Parsed AST of the target Python file.

        Returns:
            List[Dict[str, Any]]:
                A list of findings. Each finding includes:
                    - "function": Name of the function containing the loop.
                    - "pattern_id": Identifier for this detector.
                    - "issue": Description of the detected I/O call.
                    - "complexity": Qualitative complexity impact.
                    - "suggestion": Recommended remediation.
                    - "line": Line number of the call.

        Notes:
            - Only explicit `ast.Call` nodes inside `for`/`while` loops are inspected.
            - This detector focuses on file and network I/O primitives.
            - Buffered or batched I/O is recommended for performance.
        """
        findings = []
        io_funcs = {"open", "read", "write"}

        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                loops = [
                    c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
                ]

                for loop in loops:
                    for child in ast.walk(loop):
                        if isinstance(child, ast.Call):
                            func_name = ""
                            if isinstance(child.func, ast.Name):
                                func_name = child.func.id
                            elif isinstance(child.func, ast.Attribute):
                                func_name = child.func.attr

                            if func_name in io_funcs:
                                findings.append(
                                    {
                                        "function": node.name,
                                        "pattern_id": self.id,
                                        "issue": (
                                            f"High-frequency I/O ('{func_name}') inside loop"
                                        ),
                                        "complexity": "High I/O Wait",
                                        "suggestion": (
                                            "Buffer data in memory and perform a single "
                                            "batch I/O operation outside the loop."
                                        ),
                                        "line": child.lineno,
                                    }
                                )

        return findings

detect(tree)

Analyze the AST and flag I/O operations inside loops.

Parameters:

Name Type Description Default
tree AST

Parsed AST of the target Python file.

required

Returns:

Type Description
List[Dict[str, Any]]

List[Dict[str, Any]]: A list of findings. Each finding includes: - "function": Name of the function containing the loop. - "pattern_id": Identifier for this detector. - "issue": Description of the detected I/O call. - "complexity": Qualitative complexity impact. - "suggestion": Recommended remediation. - "line": Line number of the call.

Notes
  • Only explicit ast.Call nodes inside for/while loops are inspected.
  • This detector focuses on file and network I/O primitives.
  • Buffered or batched I/O is recommended for performance.
Source code in src/alnoms/patterns/high_freq_io.py
def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
    """Analyze the AST and flag I/O operations inside loops.

    Args:
        tree (ast.AST): Parsed AST of the target Python file.

    Returns:
        List[Dict[str, Any]]:
            A list of findings. Each finding includes:
                - "function": Name of the function containing the loop.
                - "pattern_id": Identifier for this detector.
                - "issue": Description of the detected I/O call.
                - "complexity": Qualitative complexity impact.
                - "suggestion": Recommended remediation.
                - "line": Line number of the call.

    Notes:
        - Only explicit `ast.Call` nodes inside `for`/`while` loops are inspected.
        - This detector focuses on file and network I/O primitives.
        - Buffered or batched I/O is recommended for performance.
    """
    findings = []
    io_funcs = {"open", "read", "write"}

    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            loops = [
                c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
            ]

            for loop in loops:
                for child in ast.walk(loop):
                    if isinstance(child, ast.Call):
                        func_name = ""
                        if isinstance(child.func, ast.Name):
                            func_name = child.func.id
                        elif isinstance(child.func, ast.Attribute):
                            func_name = child.func.attr

                        if func_name in io_funcs:
                            findings.append(
                                {
                                    "function": node.name,
                                    "pattern_id": self.id,
                                    "issue": (
                                        f"High-frequency I/O ('{func_name}') inside loop"
                                    ),
                                    "complexity": "High I/O Wait",
                                    "suggestion": (
                                        "Buffer data in memory and perform a single "
                                        "batch I/O operation outside the loop."
                                    ),
                                    "line": child.lineno,
                                }
                            )

    return findings

Bases: PatternDetector

Detects in-place string/list concatenation inside loops.

This detector identifies patterns where += or *= are used on potentially non-numeric variables inside for or while loops. Such operations can trigger repeated memory reallocations, resulting in O(N^2) scaling.

The detector includes heuristics to avoid false positives on numeric accumulation patterns (e.g., counters, totals, index updates).

Source code in src/alnoms/patterns/inplace_concat.py
class InplaceConcatDetector(PatternDetector):
    """Detects in-place string/list concatenation inside loops.

    This detector identifies patterns where `+=` or `*=` are used on
    potentially non-numeric variables inside `for` or `while` loops.
    Such operations can trigger repeated memory reallocations, resulting
    in O(N^2) scaling.

    The detector includes heuristics to avoid false positives on numeric
    accumulation patterns (e.g., counters, totals, index updates).
    """

    id = "inplace_concat"
    name = "In-Place Concatenation Detection"
    description = "Detects memory-heavy string or list concatenation inside loops."

    NUMERIC_HINTS = {
        "i",
        "j",
        "k",
        "n",
        "m",
        "idx",
        "count",
        "total",
        "sum",
        "acc",
        "value",
    }

    def _looks_numeric(self, node: ast.AST) -> bool:
        """Determines whether an AST node represents numeric-like intent.

        This heuristic helps avoid false positives by identifying RHS
        expressions that are likely numeric accumulations rather than
        string/list concatenations.

        Args:
            node (ast.AST): The AST node representing the right-hand side
                of an augmented assignment.

        Returns:
            bool: True if the node appears numeric (literal number,
            multiplication, or numeric-like variable name). False otherwise.
        """
        # Literal number
        if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
            return True

        # RHS contains multiplication → numeric accumulation
        if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Mult):
            return True

        # RHS is a variable with numeric-like name
        if isinstance(node, ast.Name) and node.id.lower() in self.NUMERIC_HINTS:
            return True

        return False

    def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
        """Detects in-place concatenation patterns inside loops.

        Traverses the AST to find augmented assignments (`+=`, `*=`) that
        occur within `for` or `while` loops. The detector filters out
        numeric accumulation patterns and flags only those operations that
        are likely to cause O(N^2) memory behavior due to repeated
        reallocation of strings or lists.

        Args:
            tree (ast.AST): The parsed AST of the target Python module or
                function.

        Returns:
            List[Dict[str, Any]]: A list of findings, where each finding
            includes:
                - function (str): Name of the function containing the issue.
                - pattern_id (str): Identifier for this detector.
                - issue (str): Human-readable description of the problem.
                - complexity (str): Complexity classification.
                - suggestion (str): Recommended remediation strategy.
                - line (int): Line number where the issue occurs.
        """
        findings = []

        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                loops = [
                    c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
                ]

                for loop in loops:
                    for child in ast.walk(loop):
                        # Detect += or *=
                        if isinstance(child, ast.AugAssign) and isinstance(
                            child.op, (ast.Add, ast.Mult)
                        ):
                            target = child.target
                            value = child.value

                            # -----------------------------------------------------
                            # 1. Skip numeric accumulation (LHS or RHS numeric)
                            # -----------------------------------------------------
                            # Case: C[i][j] += ...
                            if isinstance(target, ast.Subscript):
                                continue

                            # Case: RHS looks numeric
                            if self._looks_numeric(value):
                                continue

                            # Case: LHS variable name suggests numeric intent
                            if (
                                isinstance(target, ast.Name)
                                and target.id.lower() in self.NUMERIC_HINTS
                            ):
                                continue

                            # -----------------------------------------------------
                            # 2. Detect REAL concatenation
                            # -----------------------------------------------------
                            # Case: literal string/list on RHS
                            if isinstance(
                                value,
                                (ast.List, ast.Tuple, ast.Constant, ast.JoinedStr),
                            ):
                                pass  # real concatenation

                            # Case: variable on LHS that is not numeric-like
                            elif (
                                isinstance(target, ast.Name)
                                and target.id.lower() not in self.NUMERIC_HINTS
                            ):
                                pass

                            else:
                                # Not enough evidence → skip
                                continue

                            # -----------------------------------------------------
                            # 3. Record finding
                            # -----------------------------------------------------
                            findings.append(
                                {
                                    "function": node.name,
                                    "pattern_id": self.id,
                                    "issue": "In-place concatenation inside loop",
                                    "complexity": "O(N^2) Memory Risk",
                                    "suggestion": (
                                        "Collect items in a list and use ''.join() or list.extend() "
                                        "for linear building."
                                    ),
                                    "line": child.lineno,
                                }
                            )

        return findings

detect(tree)

Detects in-place concatenation patterns inside loops.

Traverses the AST to find augmented assignments (+=, *=) that occur within for or while loops. The detector filters out numeric accumulation patterns and flags only those operations that are likely to cause O(N^2) memory behavior due to repeated reallocation of strings or lists.

Parameters:

Name Type Description Default
tree AST

The parsed AST of the target Python module or function.

required

Returns:

Name Type Description
List[Dict[str, Any]]

List[Dict[str, Any]]: A list of findings, where each finding

includes List[Dict[str, Any]]
  • function (str): Name of the function containing the issue.
  • pattern_id (str): Identifier for this detector.
  • issue (str): Human-readable description of the problem.
  • complexity (str): Complexity classification.
  • suggestion (str): Recommended remediation strategy.
  • line (int): Line number where the issue occurs.
Source code in src/alnoms/patterns/inplace_concat.py
def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
    """Detects in-place concatenation patterns inside loops.

    Traverses the AST to find augmented assignments (`+=`, `*=`) that
    occur within `for` or `while` loops. The detector filters out
    numeric accumulation patterns and flags only those operations that
    are likely to cause O(N^2) memory behavior due to repeated
    reallocation of strings or lists.

    Args:
        tree (ast.AST): The parsed AST of the target Python module or
            function.

    Returns:
        List[Dict[str, Any]]: A list of findings, where each finding
        includes:
            - function (str): Name of the function containing the issue.
            - pattern_id (str): Identifier for this detector.
            - issue (str): Human-readable description of the problem.
            - complexity (str): Complexity classification.
            - suggestion (str): Recommended remediation strategy.
            - line (int): Line number where the issue occurs.
    """
    findings = []

    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            loops = [
                c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
            ]

            for loop in loops:
                for child in ast.walk(loop):
                    # Detect += or *=
                    if isinstance(child, ast.AugAssign) and isinstance(
                        child.op, (ast.Add, ast.Mult)
                    ):
                        target = child.target
                        value = child.value

                        # -----------------------------------------------------
                        # 1. Skip numeric accumulation (LHS or RHS numeric)
                        # -----------------------------------------------------
                        # Case: C[i][j] += ...
                        if isinstance(target, ast.Subscript):
                            continue

                        # Case: RHS looks numeric
                        if self._looks_numeric(value):
                            continue

                        # Case: LHS variable name suggests numeric intent
                        if (
                            isinstance(target, ast.Name)
                            and target.id.lower() in self.NUMERIC_HINTS
                        ):
                            continue

                        # -----------------------------------------------------
                        # 2. Detect REAL concatenation
                        # -----------------------------------------------------
                        # Case: literal string/list on RHS
                        if isinstance(
                            value,
                            (ast.List, ast.Tuple, ast.Constant, ast.JoinedStr),
                        ):
                            pass  # real concatenation

                        # Case: variable on LHS that is not numeric-like
                        elif (
                            isinstance(target, ast.Name)
                            and target.id.lower() not in self.NUMERIC_HINTS
                        ):
                            pass

                        else:
                            # Not enough evidence → skip
                            continue

                        # -----------------------------------------------------
                        # 3. Record finding
                        # -----------------------------------------------------
                        findings.append(
                            {
                                "function": node.name,
                                "pattern_id": self.id,
                                "issue": "In-place concatenation inside loop",
                                "complexity": "O(N^2) Memory Risk",
                                "suggestion": (
                                    "Collect items in a list and use ''.join() or list.extend() "
                                    "for linear building."
                                ),
                                "line": child.lineno,
                            }
                        )

    return findings

Bases: PatternDetector

Detects repeated sorting operations executed inside loops.

This detector inspects loop bodies for calls to Python's built‑in sorting mechanisms (sorted() or list .sort()). When these calls appear inside for or while loops, they can introduce O(N^2 log N) behavior due to repeated sorting of the same or similar data structures.

Sorting is typically intended to be performed once before iteration, or once after data collection, rather than on every loop iteration.

Source code in src/alnoms/patterns/redundant_sort.py
class RedundantSortDetector(PatternDetector):
    """Detects repeated sorting operations executed inside loops.

    This detector inspects loop bodies for calls to Python's built‑in
    sorting mechanisms (`sorted()` or list `.sort()`). When these calls
    appear inside `for` or `while` loops, they can introduce
    O(N^2 log N) behavior due to repeated sorting of the same or similar
    data structures.

    Sorting is typically intended to be performed once before iteration,
    or once after data collection, rather than on every loop iteration.
    """

    id = "redundant_sort"
    name = "Redundant Sort Detection"
    description = "Detects sorting operations executed inside loops."

    def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
        """Detects sorting calls (`sort`, `sorted`) inside loop bodies.

        Traverses the AST to locate function definitions and inspects all
        nested loops for calls to Python's standard sorting functions.
        When found, the detector records a finding indicating that sorting
        should be moved outside the loop to avoid repeated O(N log N)
        operations.

        Args:
            tree (ast.AST): The parsed AST of the module being analyzed.

        Returns:
            List[Dict[str, Any]]: A list of findings, where each finding
            includes:
                - function (str): Name of the function containing the issue.
                - pattern_id (str): Identifier for this detector.
                - issue (str): Description of the redundant sorting pattern.
                - complexity (str): Complexity classification (O(N^2 log N)).
                - suggestion (str): Recommended remediation strategy.
                - line (int): Line number where the sorting call occurs.
        """
        findings = []
        sorting_funcs = {"sort", "sorted"}

        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                loops = [
                    c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
                ]

                for loop in loops:
                    for child in ast.walk(loop):
                        if isinstance(child, ast.Call):
                            func_name = ""

                            # Case: sorted(...)
                            if isinstance(child.func, ast.Name):
                                func_name = child.func.id

                            # Case: list.sort(...)
                            elif isinstance(child.func, ast.Attribute):
                                func_name = child.func.attr

                            if func_name in sorting_funcs:
                                findings.append(
                                    {
                                        "function": node.name,
                                        "pattern_id": self.id,
                                        "issue": "Redundant sorting inside loop",
                                        "complexity": "O(N^2 log N)",
                                        "suggestion": (
                                            "Move sorting logic outside the loop to sort data only once."
                                        ),
                                        "line": child.lineno,
                                    }
                                )

        return findings

detect(tree)

Detects sorting calls (sort, sorted) inside loop bodies.

Traverses the AST to locate function definitions and inspects all nested loops for calls to Python's standard sorting functions. When found, the detector records a finding indicating that sorting should be moved outside the loop to avoid repeated O(N log N) operations.

Parameters:

Name Type Description Default
tree AST

The parsed AST of the module being analyzed.

required

Returns:

Name Type Description
List[Dict[str, Any]]

List[Dict[str, Any]]: A list of findings, where each finding

includes List[Dict[str, Any]]
  • function (str): Name of the function containing the issue.
  • pattern_id (str): Identifier for this detector.
  • issue (str): Description of the redundant sorting pattern.
  • complexity (str): Complexity classification (O(N^2 log N)).
  • suggestion (str): Recommended remediation strategy.
  • line (int): Line number where the sorting call occurs.
Source code in src/alnoms/patterns/redundant_sort.py
def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
    """Detects sorting calls (`sort`, `sorted`) inside loop bodies.

    Traverses the AST to locate function definitions and inspects all
    nested loops for calls to Python's standard sorting functions.
    When found, the detector records a finding indicating that sorting
    should be moved outside the loop to avoid repeated O(N log N)
    operations.

    Args:
        tree (ast.AST): The parsed AST of the module being analyzed.

    Returns:
        List[Dict[str, Any]]: A list of findings, where each finding
        includes:
            - function (str): Name of the function containing the issue.
            - pattern_id (str): Identifier for this detector.
            - issue (str): Description of the redundant sorting pattern.
            - complexity (str): Complexity classification (O(N^2 log N)).
            - suggestion (str): Recommended remediation strategy.
            - line (int): Line number where the sorting call occurs.
    """
    findings = []
    sorting_funcs = {"sort", "sorted"}

    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            loops = [
                c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
            ]

            for loop in loops:
                for child in ast.walk(loop):
                    if isinstance(child, ast.Call):
                        func_name = ""

                        # Case: sorted(...)
                        if isinstance(child.func, ast.Name):
                            func_name = child.func.id

                        # Case: list.sort(...)
                        elif isinstance(child.func, ast.Attribute):
                            func_name = child.func.attr

                        if func_name in sorting_funcs:
                            findings.append(
                                {
                                    "function": node.name,
                                    "pattern_id": self.id,
                                    "issue": "Redundant sorting inside loop",
                                    "complexity": "O(N^2 log N)",
                                    "suggestion": (
                                        "Move sorting logic outside the loop to sort data only once."
                                    ),
                                    "line": child.lineno,
                                }
                            )

    return findings

đź’Š Fixers

Bases: Fixer

Remediation strategy for nested loop patterns.

This fixer provides intent‑aware remediation for nested loops, distinguishing between cubic patterns (e.g., matrix multiplication) and quadratic patterns such as membership scans, manual sorting, and DFS‑like adjacency traversal. Recommendations include algorithmic redesign, vectorization, hashing, and use of efficient data structures.

Attributes:

Name Type Description
pattern_id str

Identifier for the associated detector pattern.

Source code in src/alnoms/fixes/nested_loops_fixer.py
class NestedLoopFixer(Fixer):
    """Remediation strategy for nested loop patterns.

    This fixer provides intent‑aware remediation for nested loops,
    distinguishing between cubic patterns (e.g., matrix multiplication)
    and quadratic patterns such as membership scans, manual sorting, and
    DFS‑like adjacency traversal. Recommendations include algorithmic
    redesign, vectorization, hashing, and use of efficient data
    structures.

    Attributes:
        pattern_id (str): Identifier for the associated detector pattern.
    """

    pattern_id = "nested_loops"

    # -----------------------------
    # Heuristics
    # -----------------------------

    def _looks_like_matrix_multiply(self, finding):
        """Detects matrix‑multiplication‑like triple‑nested loops.

        This heuristic checks for patterns resembling:

            A[i][k] * B[k][j]

        It is intentionally conservative to avoid false positives.

        Args:
            finding (Dict): The detector finding containing source context.

        Returns:
            bool: True if the code resembles matrix multiplication.
        """
        code = finding.get("source", "") or ""
        code_lower = code.lower()

        return (
            "*" in code
            and "[" in code
            and "]" in code
            and ("a[" in code_lower or "matrix" in code_lower)
            and ("b[" in code_lower or "matrix" in code_lower)
        )

    # -----------------------------
    # Explanation
    # -----------------------------

    def explain(self, finding, detected_complexity="Unknown"):
        """Provides a human‑readable explanation of the nested loop issue.

        This method generates intent‑aware explanations for nested loops,
        distinguishing between cubic and quadratic patterns. It also
        provides domain‑specific guidance for matrix multiplication,
        membership scans, sorting‑like routines, and DFS‑style traversal.

        Args:
            finding (Dict): The detector finding describing the nested loop.
            detected_complexity (str): Static or empirical complexity.

        Returns:
            str: A narrative explanation describing the issue and the
            recommended remediation strategy.
        """
        depth = finding.get("loop_depth", 1)
        intent = finding.get("intent", "generic")

        # --- Cubic Cases ---
        if depth >= 3:
            if self._looks_like_matrix_multiply(finding):
                return (
                    "Cubic complexity detected (Matrix Multiplication Pattern). "
                    "This is a classic O(N^3) algorithm. High risk for production. "
                    "Use vectorized linear algebra (NumPy / BLAS) for 10x–100x speedups."
                )
            return (
                "Cubic complexity detected. This triple-nested loop is a high-risk "
                "algorithmic bottleneck. Consider pruning, memoization, or reducing "
                "the search space."
            )

        # --- Quadratic Cases (Intent-Aware) ---
        if intent == "membership":
            return (
                "This nested loop appears to perform membership or pairwise comparison. "
                "Use a hash-based index (set or dict) to reduce O(N^2) lookups to O(N)."
            )

        if intent == "sorting":
            return (
                "This nested loop resembles a manual sorting routine (e.g., bubble sort). "
                "Replace with an O(N log N) sorting algorithm such as merge sort."
            )

        if intent == "dfs":
            return (
                "This nested loop resembles a graph traversal pattern. "
                "Use a visited set and adjacency list to avoid redundant scanning."
            )

        return (
            "This nested loop likely causes O(N^2) behavior. "
            "Consider algorithmic redesign, pruning, or using more efficient data structures."
        )

    # -----------------------------
    # Snippets
    # -----------------------------

    def snippet_before_after(self, finding, detected_complexity="Unknown"):
        """Returns before/after code snippets illustrating the fix.

        Snippets are intent‑aware and tailored to the specific nested loop
        pattern detected. Cubic patterns receive vectorization or pruning
        guidance, while quadratic patterns receive hashing, sorting, or
        DFS‑related improvements.

        Args:
            finding (Dict): The detector finding describing the nested loop.
            detected_complexity (str): Complexity classification.

        Returns:
            Dict[str, str]: A dictionary containing:
                - ``before``: Example of the inefficient nested loop.
                - ``after``: Example of the recommended remediation.
        """
        depth = finding.get("loop_depth", 1)
        intent = finding.get("intent", "generic")

        # --- Cubic + Matrix Multiplication ---
        if depth >= 3 and self._looks_like_matrix_multiply(finding):
            before = (
                "# Triple-nested loop matrix multiplication\n"
                "for i in range(N):\n"
                "    for j in range(N):\n"
                "        for k in range(N):\n"
                "            C[i][j] += A[i][k] * B[k][j]"
            )
            after = (
                "# Use Vectorized Library (NumPy) or BLAS\n"
                "import numpy as np\n"
                "C = np.matmul(A, B)"
            )
            return {"before": before, "after": after}

        # --- Generic Cubic Fallback ---
        if depth >= 3:
            before = (
                "# Triple-nested loop\n"
                "for i in range(N):\n"
                "    for j in range(N):\n"
                "        for k in range(N):\n"
                "            work(i, j, k)"
            )
            after = (
                "# Consider pruning, memoization, or reducing search space\n"
                "optimized = optimized_algorithm(data)"
            )
            return {"before": before, "after": after}

        # --- Quadratic Membership Fix ---
        if intent == "membership":
            before = (
                "for a in A:\n"
                "    for b in B:\n"
                "        if a == b:\n"
                "            process(a)"
            )
            after = (
                "index = set(B)\nfor a in A:\n    if a in index:\n        process(a)"
            )
            return {"before": before, "after": after}

        # --- Sorting-Like Fix ---
        if intent == "sorting":
            before = (
                "# Manual quadratic sorting (e.g., bubble sort)\n"
                "for i in range(len(arr)):\n"
                "    for j in range(len(arr) - 1):\n"
                "        if arr[j] > arr[j + 1]:\n"
                "            arr[j], arr[j + 1] = arr[j + 1], arr[j]"
            )
            after = "# Replace with O(N log N) sort\narr = sorted(arr)"
            return {"before": before, "after": after}

        # --- DFS-Like Fix ---
        if intent == "dfs":
            before = (
                "# DFS-like nested loop\n"
                "for node in graph:\n"
                "    for neighbor in graph[node]:\n"
                "        process(node, neighbor)"
            )
            after = (
                "# Use visited set to avoid redundant scanning\n"
                "visited = set()\n"
                "def dfs(node):\n"
                "    if node in visited:\n"
                "        return\n"
                "    visited.add(node)\n"
                "    for neighbor in graph[node]:\n"
                "        dfs(neighbor)"
            )
            return {"before": before, "after": after}

        # --- Generic Quadratic Fallback ---
        before = (
            "# Quadratic nested loop\n"
            "for i in range(N):\n"
            "    for j in range(N):\n"
            "        work(i, j)"
        )
        after = (
            "# Consider algorithmic redesign or pruning\n"
            "optimized = optimized_algorithm(data)"
        )
        return {"before": before, "after": after}

    # -----------------------------
    # Cost Estimate
    # -----------------------------

    def cost_estimate(self, finding, detected_complexity="Unknown"):
        """Provides a qualitative estimate of the complexity improvement.

        Args:
            finding (Dict): The detector finding associated with the issue.
            detected_complexity (str): The complexity classification.

        Returns:
            Dict[str, str]: A dictionary describing expected improvements
            in time and memory complexity for cubic and quadratic cases.
        """
        depth = finding.get("loop_depth", 1)

        if depth >= 3:
            return {
                "time": "O(N^3) → O(N^2.8) or Hardware Vectorized",
                "memory": "Implementation Dependent",
            }

        return {
            "time": "O(N^2) → O(N)",
            "memory": "O(N) extra for set (if membership pattern)",
        }

cost_estimate(finding, detected_complexity='Unknown')

Provides a qualitative estimate of the complexity improvement.

Parameters:

Name Type Description Default
finding Dict

The detector finding associated with the issue.

required
detected_complexity str

The complexity classification.

'Unknown'

Returns:

Type Description

Dict[str, str]: A dictionary describing expected improvements

in time and memory complexity for cubic and quadratic cases.

Source code in src/alnoms/fixes/nested_loops_fixer.py
def cost_estimate(self, finding, detected_complexity="Unknown"):
    """Provides a qualitative estimate of the complexity improvement.

    Args:
        finding (Dict): The detector finding associated with the issue.
        detected_complexity (str): The complexity classification.

    Returns:
        Dict[str, str]: A dictionary describing expected improvements
        in time and memory complexity for cubic and quadratic cases.
    """
    depth = finding.get("loop_depth", 1)

    if depth >= 3:
        return {
            "time": "O(N^3) → O(N^2.8) or Hardware Vectorized",
            "memory": "Implementation Dependent",
        }

    return {
        "time": "O(N^2) → O(N)",
        "memory": "O(N) extra for set (if membership pattern)",
    }

explain(finding, detected_complexity='Unknown')

Provides a human‑readable explanation of the nested loop issue.

This method generates intent‑aware explanations for nested loops, distinguishing between cubic and quadratic patterns. It also provides domain‑specific guidance for matrix multiplication, membership scans, sorting‑like routines, and DFS‑style traversal.

Parameters:

Name Type Description Default
finding Dict

The detector finding describing the nested loop.

required
detected_complexity str

Static or empirical complexity.

'Unknown'

Returns:

Name Type Description
str

A narrative explanation describing the issue and the

recommended remediation strategy.

Source code in src/alnoms/fixes/nested_loops_fixer.py
def explain(self, finding, detected_complexity="Unknown"):
    """Provides a human‑readable explanation of the nested loop issue.

    This method generates intent‑aware explanations for nested loops,
    distinguishing between cubic and quadratic patterns. It also
    provides domain‑specific guidance for matrix multiplication,
    membership scans, sorting‑like routines, and DFS‑style traversal.

    Args:
        finding (Dict): The detector finding describing the nested loop.
        detected_complexity (str): Static or empirical complexity.

    Returns:
        str: A narrative explanation describing the issue and the
        recommended remediation strategy.
    """
    depth = finding.get("loop_depth", 1)
    intent = finding.get("intent", "generic")

    # --- Cubic Cases ---
    if depth >= 3:
        if self._looks_like_matrix_multiply(finding):
            return (
                "Cubic complexity detected (Matrix Multiplication Pattern). "
                "This is a classic O(N^3) algorithm. High risk for production. "
                "Use vectorized linear algebra (NumPy / BLAS) for 10x–100x speedups."
            )
        return (
            "Cubic complexity detected. This triple-nested loop is a high-risk "
            "algorithmic bottleneck. Consider pruning, memoization, or reducing "
            "the search space."
        )

    # --- Quadratic Cases (Intent-Aware) ---
    if intent == "membership":
        return (
            "This nested loop appears to perform membership or pairwise comparison. "
            "Use a hash-based index (set or dict) to reduce O(N^2) lookups to O(N)."
        )

    if intent == "sorting":
        return (
            "This nested loop resembles a manual sorting routine (e.g., bubble sort). "
            "Replace with an O(N log N) sorting algorithm such as merge sort."
        )

    if intent == "dfs":
        return (
            "This nested loop resembles a graph traversal pattern. "
            "Use a visited set and adjacency list to avoid redundant scanning."
        )

    return (
        "This nested loop likely causes O(N^2) behavior. "
        "Consider algorithmic redesign, pruning, or using more efficient data structures."
    )

snippet_before_after(finding, detected_complexity='Unknown')

Returns before/after code snippets illustrating the fix.

Snippets are intent‑aware and tailored to the specific nested loop pattern detected. Cubic patterns receive vectorization or pruning guidance, while quadratic patterns receive hashing, sorting, or DFS‑related improvements.

Parameters:

Name Type Description Default
finding Dict

The detector finding describing the nested loop.

required
detected_complexity str

Complexity classification.

'Unknown'

Returns:

Type Description

Dict[str, str]: A dictionary containing: - before: Example of the inefficient nested loop. - after: Example of the recommended remediation.

Source code in src/alnoms/fixes/nested_loops_fixer.py
def snippet_before_after(self, finding, detected_complexity="Unknown"):
    """Returns before/after code snippets illustrating the fix.

    Snippets are intent‑aware and tailored to the specific nested loop
    pattern detected. Cubic patterns receive vectorization or pruning
    guidance, while quadratic patterns receive hashing, sorting, or
    DFS‑related improvements.

    Args:
        finding (Dict): The detector finding describing the nested loop.
        detected_complexity (str): Complexity classification.

    Returns:
        Dict[str, str]: A dictionary containing:
            - ``before``: Example of the inefficient nested loop.
            - ``after``: Example of the recommended remediation.
    """
    depth = finding.get("loop_depth", 1)
    intent = finding.get("intent", "generic")

    # --- Cubic + Matrix Multiplication ---
    if depth >= 3 and self._looks_like_matrix_multiply(finding):
        before = (
            "# Triple-nested loop matrix multiplication\n"
            "for i in range(N):\n"
            "    for j in range(N):\n"
            "        for k in range(N):\n"
            "            C[i][j] += A[i][k] * B[k][j]"
        )
        after = (
            "# Use Vectorized Library (NumPy) or BLAS\n"
            "import numpy as np\n"
            "C = np.matmul(A, B)"
        )
        return {"before": before, "after": after}

    # --- Generic Cubic Fallback ---
    if depth >= 3:
        before = (
            "# Triple-nested loop\n"
            "for i in range(N):\n"
            "    for j in range(N):\n"
            "        for k in range(N):\n"
            "            work(i, j, k)"
        )
        after = (
            "# Consider pruning, memoization, or reducing search space\n"
            "optimized = optimized_algorithm(data)"
        )
        return {"before": before, "after": after}

    # --- Quadratic Membership Fix ---
    if intent == "membership":
        before = (
            "for a in A:\n"
            "    for b in B:\n"
            "        if a == b:\n"
            "            process(a)"
        )
        after = (
            "index = set(B)\nfor a in A:\n    if a in index:\n        process(a)"
        )
        return {"before": before, "after": after}

    # --- Sorting-Like Fix ---
    if intent == "sorting":
        before = (
            "# Manual quadratic sorting (e.g., bubble sort)\n"
            "for i in range(len(arr)):\n"
            "    for j in range(len(arr) - 1):\n"
            "        if arr[j] > arr[j + 1]:\n"
            "            arr[j], arr[j + 1] = arr[j + 1], arr[j]"
        )
        after = "# Replace with O(N log N) sort\narr = sorted(arr)"
        return {"before": before, "after": after}

    # --- DFS-Like Fix ---
    if intent == "dfs":
        before = (
            "# DFS-like nested loop\n"
            "for node in graph:\n"
            "    for neighbor in graph[node]:\n"
            "        process(node, neighbor)"
        )
        after = (
            "# Use visited set to avoid redundant scanning\n"
            "visited = set()\n"
            "def dfs(node):\n"
            "    if node in visited:\n"
            "        return\n"
            "    visited.add(node)\n"
            "    for neighbor in graph[node]:\n"
            "        dfs(neighbor)"
        )
        return {"before": before, "after": after}

    # --- Generic Quadratic Fallback ---
    before = (
        "# Quadratic nested loop\n"
        "for i in range(N):\n"
        "    for j in range(N):\n"
        "        work(i, j)"
    )
    after = (
        "# Consider algorithmic redesign or pruning\n"
        "optimized = optimized_algorithm(data)"
    )
    return {"before": before, "after": after}

Bases: Fixer

Remediation strategy for inefficient membership tests inside loops.

This fixer addresses patterns where membership checks such as x in list or x in tuple occur inside a loop. These operations are O(N) per lookup, leading to accidental O(N*M) or O(N²) behavior. The recommended remediation is to convert the container to a set to achieve O(1) average‑case membership checks.

Attributes:

Name Type Description
pattern_id str

Identifier for the associated detector pattern.

Source code in src/alnoms/fixes/inefficient_membership_fixer.py
class InefficientMembershipFixer(Fixer):
    """Remediation strategy for inefficient membership tests inside loops.

    This fixer addresses patterns where membership checks such as
    ``x in list`` or ``x in tuple`` occur inside a loop. These operations
    are O(N) per lookup, leading to accidental O(N*M) or O(N²) behavior.
    The recommended remediation is to convert the container to a ``set``
    to achieve O(1) average‑case membership checks.

    Attributes:
        pattern_id (str): Identifier for the associated detector pattern.
    """

    pattern_id = "inefficient_membership"

    def explain(self, finding, detected_complexity="Unknown"):
        """Provides a human‑readable explanation of the optimization.

        Args:
            finding (Dict): The detector finding describing the membership pattern.
            detected_complexity (str): The static or empirical complexity
                associated with the anti‑pattern.

        Returns:
            str: A narrative explanation describing why list‑based membership
            checks inside loops are slow and how converting to a set improves
            performance.
        """
        return (
            "Membership checks on lists inside loops are O(N). "
            "Convert to a set for O(1) lookups."
        )

    def snippet_before_after(self, finding, detected_complexity="Unknown"):
        """Returns before/after code snippets illustrating the fix.

        Args:
            finding (Dict): The detector output for the inefficient membership test.
            detected_complexity (str): Complexity classification used to
                contextualize the snippet.

        Returns:
            Dict[str, str]: A dictionary containing:
                - ``before``: Example of repeated list membership checks.
                - ``after``: Example using a set for constant‑time lookups.
        """
        before = "for k in keys:\n    if k in items:\n        process(k)"
        after = (
            "items_set = set(items)\n"
            "for k in keys:\n"
            "    if k in items_set:\n"
            "        process(k)"
        )
        return {"before": before, "after": after}

    def cost_estimate(self, finding, detected_complexity="Unknown"):
        """Provides a qualitative estimate of the complexity improvement.

        Args:
            finding (Dict): The detector finding associated with the issue.
            detected_complexity (str): The complexity classification.

        Returns:
            Dict[str, str]: A dictionary describing expected improvements
            in time and memory complexity. Converting to a set reduces
            repeated O(N) scans to O(1) lookups.
        """
        return {"time": "O(N*M) → O(N + M)", "memory": "O(M)"}

cost_estimate(finding, detected_complexity='Unknown')

Provides a qualitative estimate of the complexity improvement.

Parameters:

Name Type Description Default
finding Dict

The detector finding associated with the issue.

required
detected_complexity str

The complexity classification.

'Unknown'

Returns:

Type Description

Dict[str, str]: A dictionary describing expected improvements

in time and memory complexity. Converting to a set reduces

repeated O(N) scans to O(1) lookups.

Source code in src/alnoms/fixes/inefficient_membership_fixer.py
def cost_estimate(self, finding, detected_complexity="Unknown"):
    """Provides a qualitative estimate of the complexity improvement.

    Args:
        finding (Dict): The detector finding associated with the issue.
        detected_complexity (str): The complexity classification.

    Returns:
        Dict[str, str]: A dictionary describing expected improvements
        in time and memory complexity. Converting to a set reduces
        repeated O(N) scans to O(1) lookups.
    """
    return {"time": "O(N*M) → O(N + M)", "memory": "O(M)"}

explain(finding, detected_complexity='Unknown')

Provides a human‑readable explanation of the optimization.

Parameters:

Name Type Description Default
finding Dict

The detector finding describing the membership pattern.

required
detected_complexity str

The static or empirical complexity associated with the anti‑pattern.

'Unknown'

Returns:

Name Type Description
str

A narrative explanation describing why list‑based membership

checks inside loops are slow and how converting to a set improves

performance.

Source code in src/alnoms/fixes/inefficient_membership_fixer.py
def explain(self, finding, detected_complexity="Unknown"):
    """Provides a human‑readable explanation of the optimization.

    Args:
        finding (Dict): The detector finding describing the membership pattern.
        detected_complexity (str): The static or empirical complexity
            associated with the anti‑pattern.

    Returns:
        str: A narrative explanation describing why list‑based membership
        checks inside loops are slow and how converting to a set improves
        performance.
    """
    return (
        "Membership checks on lists inside loops are O(N). "
        "Convert to a set for O(1) lookups."
    )

snippet_before_after(finding, detected_complexity='Unknown')

Returns before/after code snippets illustrating the fix.

Parameters:

Name Type Description Default
finding Dict

The detector output for the inefficient membership test.

required
detected_complexity str

Complexity classification used to contextualize the snippet.

'Unknown'

Returns:

Type Description

Dict[str, str]: A dictionary containing: - before: Example of repeated list membership checks. - after: Example using a set for constant‑time lookups.

Source code in src/alnoms/fixes/inefficient_membership_fixer.py
def snippet_before_after(self, finding, detected_complexity="Unknown"):
    """Returns before/after code snippets illustrating the fix.

    Args:
        finding (Dict): The detector output for the inefficient membership test.
        detected_complexity (str): Complexity classification used to
            contextualize the snippet.

    Returns:
        Dict[str, str]: A dictionary containing:
            - ``before``: Example of repeated list membership checks.
            - ``after``: Example using a set for constant‑time lookups.
    """
    before = "for k in keys:\n    if k in items:\n        process(k)"
    after = (
        "items_set = set(items)\n"
        "for k in keys:\n"
        "    if k in items_set:\n"
        "        process(k)"
    )
    return {"before": before, "after": after}