Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions codewiki/src/be/dependency_analyzer/leaf_selection.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Dict, List, Set
import re

from codewiki.src.be.dependency_analyzer.models.core import Node

Expand All @@ -16,6 +17,11 @@

OOP_TYPES = {"class", "interface", "struct"}

# Error strings that occasionally reach leaf-node selection instead of an
# identifier. Matched on word boundaries and only for entries that are not
# known components, so that names like `handleInvalidInput` survive.
ERROR_MESSAGE_RE = re.compile(r"\b(error|exception|failed|invalid)\b", re.IGNORECASE)


def compute_valid_leaf_types(components: Dict[str, Node]) -> Set[str]:
"""
Expand Down Expand Up @@ -60,8 +66,13 @@ def filter_leaf_nodes(
"""Keep leaf nodes that are known components of a valid type."""
keep_leaf_nodes = []
for leaf_node in leaf_nodes:
# Skip any leaf nodes that are clearly error strings or invalid identifiers
if not isinstance(leaf_node, str) or leaf_node.strip() == "" or any(err_keyword in leaf_node.lower() for err_keyword in ['error', 'exception', 'failed', 'invalid']):
if not isinstance(leaf_node, str) or leaf_node.strip() == "":
logger.debug(f"Skipping invalid leaf node identifier: '{leaf_node}'")
continue

# Only reject strings that look like error messages, not identifiers
# that merely contain such a word (handleInvalidInput, ErrorLog, ...).
if leaf_node not in components and ERROR_MESSAGE_RE.search(leaf_node):
logger.debug(f"Skipping invalid leaf node identifier: '{leaf_node}'")
continue

Expand Down
79 changes: 79 additions & 0 deletions tests/test_leaf_selection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""Tests for leaf-node identifier filtering.

Covers filter_leaf_nodes: identifiers that merely contain a word like
"invalid" must survive, while strings that are actually error messages
reaching leaf-node selection instead of an identifier are dropped.
"""

from __future__ import annotations

from codewiki.src.be.dependency_analyzer.leaf_selection import filter_leaf_nodes
from codewiki.src.be.dependency_analyzer.models.core import Node


def _component(component_id: str, component_type: str = "function") -> Node:
file_path = component_id.split("::")[0]
return Node(
id=component_id,
name=component_id.split("::")[-1],
component_type=component_type,
file_path=file_path,
relative_path=file_path,
)


def _components(*component_ids: str) -> dict[str, Node]:
return {cid: _component(cid) for cid in component_ids}


def test_identifiers_containing_error_words_are_kept() -> None:
components = _components(
"src/input.cpp::handleInvalidInput",
"src/game.cpp::gameFailedCheck",
"src/log.cpp::ErrorLog",
"src/parser.cpp::parseExceptionTable",
)

kept = filter_leaf_nodes(list(components), components, {"function"})

assert set(kept) == set(components)


def test_error_messages_are_dropped() -> None:
components = _components("src/player.cpp::playerMove")
candidates = [
"src/player.cpp::playerMove",
"Error: could not parse file",
"invalid syntax at line 3",
"Analysis failed for this component",
]

kept = filter_leaf_nodes(candidates, components, {"function"})

assert kept == ["src/player.cpp::playerMove"]


def test_unknown_and_malformed_entries_are_dropped() -> None:
components = _components("src/player.cpp::playerMove")
candidates = [
"src/player.cpp::playerMove",
"src/player.cpp::doesNotExist",
"",
" ",
None,
]

kept = filter_leaf_nodes(candidates, components, {"function"})

assert kept == ["src/player.cpp::playerMove"]


def test_components_of_other_types_are_dropped() -> None:
components = {
"src/game.cpp::runGame": _component("src/game.cpp::runGame", "function"),
"src/game.h::GameState": _component("src/game.h::GameState", "struct"),
}

kept = filter_leaf_nodes(list(components), components, {"function"})

assert kept == ["src/game.cpp::runGame"]