66import pytest
77from codeflash .context .unused_definition_remover import detect_unused_helper_functions
88from codeflash .discovery .functions_to_optimize import FunctionToOptimize
9- from codeflash .models .models import CodeStringsMarkdown
9+ from codeflash .models .models import CodeStringsMarkdown , FunctionParent
1010from codeflash .optimization .function_optimizer import FunctionOptimizer
1111from codeflash .verification .verification_utils import TestConfig
1212
@@ -1460,3 +1460,152 @@ def calculate_class(cls, n):
14601460 import shutil
14611461
14621462 shutil .rmtree (temp_dir , ignore_errors = True )
1463+
1464+
1465+ def test_unused_helper_detection_with_duplicated_function_name_in_different_classes ():
1466+ """Test detection when helpers are called via module.function style."""
1467+ temp_dir = Path (tempfile .mkdtemp ())
1468+
1469+ try :
1470+ # Main file
1471+ main_file = temp_dir / "main.py"
1472+ main_file .write_text ("""from __future__ import annotations
1473+ import json
1474+ from helpers import replace_quotes_with_backticks, simplify_worktree_paths
1475+ from dataclasses import asdict, dataclass
1476+
1477+ @dataclass
1478+ class LspMessage:
1479+
1480+ def serialize(self) -> str:
1481+ data = self._loop_through(asdict(self))
1482+ # Important: keep type as the first key, for making it easy and fast for the client to know if this is a lsp message before parsing it
1483+ ordered = {"type": self.type(), **data}
1484+ return (
1485+ message_delimiter
1486+ + json.dumps(ordered)
1487+ + message_delimiter
1488+ )
1489+
1490+
1491+ @dataclass
1492+ class LspMarkdownMessage(LspMessage):
1493+
1494+ def serialize(self) -> str:
1495+ self.markdown = simplify_worktree_paths(self.markdown)
1496+ self.markdown = replace_quotes_with_backticks(self.markdown)
1497+ return super().serialize()
1498+ """ )
1499+
1500+ # Helpers file
1501+ helpers_file = temp_dir / "helpers.py"
1502+ helpers_file .write_text ("""def simplify_worktree_paths(msg: str, highlight: bool = True) -> str: # noqa: FBT001, FBT002
1503+ path_in_msg = worktree_path_regex.search(msg)
1504+ if path_in_msg:
1505+ last_part_of_path = path_in_msg.group(0).split("/")[-1]
1506+ if highlight:
1507+ last_part_of_path = f"`{last_part_of_path}`"
1508+ return msg.replace(path_in_msg.group(0), last_part_of_path)
1509+ return msg
1510+
1511+
1512+ def replace_quotes_with_backticks(text: str) -> str:
1513+ # double-quoted strings
1514+ text = _double_quote_pat.sub(r"`\1 `", text)
1515+ # single-quoted strings
1516+ return _single_quote_pat.sub(r"`\1 `", text)
1517+ """ )
1518+
1519+ # Optimized version that only uses add_numbers
1520+ optimized_code = """
1521+ ```python:main.py
1522+ from __future__ import annotations
1523+
1524+ import json
1525+ from dataclasses import asdict, dataclass
1526+
1527+ from codeflash.lsp.helpers import (replace_quotes_with_backticks,
1528+ simplify_worktree_paths)
1529+
1530+
1531+ @dataclass
1532+ class LspMessage:
1533+
1534+ def serialize(self) -> str:
1535+ # Use local variable to minimize lookup costs and avoid unnecessary dictionary unpacking
1536+ data = self._loop_through(asdict(self))
1537+ msg_type = self.type()
1538+ ordered = {'type': msg_type}
1539+ ordered.update(data)
1540+ return (
1541+ message_delimiter
1542+ + json.dumps(ordered)
1543+ + message_delimiter # \u241F is the message delimiter becuase it can be more than one message sent over the same message, so we need something to separate each message
1544+ )
1545+
1546+ @dataclass
1547+ class LspMarkdownMessage(LspMessage):
1548+
1549+ def serialize(self) -> str:
1550+ # Side effect required, must preserve for behavioral correctness
1551+ self.markdown = simplify_worktree_paths(self.markdown)
1552+ self.markdown = replace_quotes_with_backticks(self.markdown)
1553+ return super().serialize()
1554+ ```
1555+ ```python:helpers.py
1556+ def simplify_worktree_paths(msg: str, highlight: bool = True) -> str: # noqa: FBT001, FBT002
1557+ m = worktree_path_regex.search(msg)
1558+ if m:
1559+ # More efficient way to get last path part
1560+ last_part_of_path = m.group(0).rpartition('/')[-1]
1561+ if highlight:
1562+ last_part_of_path = f"`{last_part_of_path}`"
1563+ return msg.replace(m.group(0), last_part_of_path)
1564+ return msg
1565+
1566+ def replace_quotes_with_backticks(text: str) -> str:
1567+ # Efficient string substitution, reduces intermediate string allocations
1568+ return _single_quote_pat.sub(
1569+ r"`\1 `",
1570+ _double_quote_pat.sub(r"`\1 `", text),
1571+ )
1572+ ```
1573+ """
1574+
1575+ # Create test config
1576+ test_cfg = TestConfig (
1577+ tests_root = temp_dir / "tests" ,
1578+ tests_project_rootdir = temp_dir ,
1579+ project_root_path = temp_dir ,
1580+ test_framework = "pytest" ,
1581+ pytest_cmd = "pytest" ,
1582+ )
1583+
1584+ # Create FunctionToOptimize instance
1585+ function_to_optimize = FunctionToOptimize (
1586+ file_path = main_file , function_name = "serialize" , qualified_name = "serialize" , parents = [
1587+ FunctionParent (name = "LspMarkdownMessage" , type = "ClassDef" ),
1588+ ]
1589+ )
1590+
1591+ optimizer = FunctionOptimizer (
1592+ function_to_optimize = function_to_optimize ,
1593+ test_cfg = test_cfg ,
1594+ function_to_optimize_source_code = main_file .read_text (),
1595+ )
1596+
1597+ ctx_result = optimizer .get_code_optimization_context ()
1598+ assert ctx_result .is_successful (), f"Failed to get context: { ctx_result .failure ()} "
1599+
1600+ code_context = ctx_result .unwrap ()
1601+
1602+ unused_helpers = detect_unused_helper_functions (optimizer .function_to_optimize , code_context , CodeStringsMarkdown .parse_markdown_code (optimized_code ))
1603+
1604+ unused_names = {uh .qualified_name for uh in unused_helpers }
1605+ assert len (unused_names ) == 0 # no unused helpers
1606+
1607+ finally :
1608+ # Cleanup
1609+ import shutil
1610+
1611+ shutil .rmtree (temp_dir , ignore_errors = True )
0 commit comments