Skip to content

Commit 6cc200f

Browse files
Optimize _node_contains_jsx
The optimized code achieves a **684% speedup** (from 1.03ms to 131μs) by replacing recursive calls with an iterative stack-based approach and using a frozenset for type checking. ## Key Optimizations **1. Eliminated Recursive Overhead** The original code uses recursion with multiple call sites, creating significant overhead from: - Function call stack frames for each node traversal - Generator expression overhead in `any(_node_contains_jsx(child) for child in node.children)` - Repeated function entry/exit costs The optimized version uses an explicit stack with a while loop, eliminating all recursion overhead. This is particularly impactful for tree traversal where every node would trigger a new function call in the original version. **2. Frozenset Lookup for Type Checking** Moving JSX type checks into a `frozenset` (`_JSX_TYPES`) provides O(1) average-case lookup instead of tuple membership testing, which is O(n) linear scan. While the performance difference is small for 5 types, frozensets are optimized for membership testing and signal intent clearly. **3. Simplified Control Flow** The original code had special-case handling for `return_statement` nodes before the generic child traversal. The optimized version treats all nodes uniformly—checking the type first, then processing children—reducing branching and making the code path more predictable. ## Performance Impact by Test Case The optimization excels on **breadth-heavy trees**: - `test_large_breadth_many_children_with_one_jsx_at_end`: **7541% faster** (324μs → 4.25μs) - `test_large_breadth_many_children_no_jsx`: **292% faster** (338μs → 86.5μs) For these cases, the original recursive approach with generator expressions incurs massive overhead when traversing 1000 siblings, while the iterative approach efficiently processes them with minimal per-node cost. For simple cases with few nodes, the speedup is more modest (47-99% faster) due to lower recursion depth, though there is a slight regression (~1-25% slower) for the absolute simplest cases with a single node, likely due to the stack initialization overhead. This is an acceptable trade-off given the dramatic improvements on realistic tree structures. The optimization preserves all behavior including the `TypeError` when `children` is `None`, ensuring backward compatibility.
1 parent 5c3a8dc commit 6cc200f

1 file changed

Lines changed: 15 additions & 16 deletions

File tree

codeflash/languages/javascript/frameworks/react/discovery.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@
1919

2020
from codeflash.languages.javascript.treesitter import FunctionNode, TreeSitterAnalyzer
2121

22+
_JSX_TYPES = frozenset((
23+
"jsx_element",
24+
"jsx_self_closing_element",
25+
"jsx_fragment",
26+
"jsx_expression",
27+
"jsx_opening_element",
28+
))
29+
2230
logger = logging.getLogger(__name__)
2331

2432
PASCAL_CASE_RE = re.compile(r"^[A-Z][a-zA-Z0-9]*$")
@@ -177,22 +185,13 @@ def _function_returns_jsx(func: FunctionNode, source: str, analyzer: TreeSitterA
177185

178186
def _node_contains_jsx(node: Node) -> bool:
179187
"""Recursively check if a tree-sitter node contains JSX."""
180-
if node.type in (
181-
"jsx_element",
182-
"jsx_self_closing_element",
183-
"jsx_fragment",
184-
"jsx_expression",
185-
"jsx_opening_element",
186-
):
187-
return True
188-
189-
# Check return statements
190-
if node.type == "return_statement":
191-
for child in node.children:
192-
if _node_contains_jsx(child):
193-
return True
194-
195-
return any(_node_contains_jsx(child) for child in node.children)
188+
stack = [node]
189+
while stack:
190+
current = stack.pop()
191+
if current.type in _JSX_TYPES:
192+
return True
193+
stack.extend(current.children)
194+
return False
196195

197196

198197
HOOK_EXTRACT_RE = re.compile(r"\b(use[A-Z]\w*)\s*(?:<[^>]*>)?\s*\(")

0 commit comments

Comments
 (0)