diff --git a/codeflash/languages/javascript/frameworks/detector.py b/codeflash/languages/javascript/frameworks/detector.py index 013de47f5..f4905c593 100644 --- a/codeflash/languages/javascript/frameworks/detector.py +++ b/codeflash/languages/javascript/frameworks/detector.py @@ -10,7 +10,10 @@ import logging from dataclasses import dataclass, field from functools import lru_cache -from pathlib import Path +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from pathlib import Path logger = logging.getLogger(__name__) diff --git a/codeflash/languages/javascript/frameworks/react/analyzer.py b/codeflash/languages/javascript/frameworks/react/analyzer.py index 2de39c802..4d3452984 100644 --- a/codeflash/languages/javascript/frameworks/react/analyzer.py +++ b/codeflash/languages/javascript/frameworks/react/analyzer.py @@ -64,7 +64,10 @@ def detect_optimization_opportunities(source: str, component_info: ReactComponen start = component_info.start_line - 1 end = min(component_info.end_line, len(lines)) component_lines = lines[start:end] - component_source = "\n".join(component_lines) + # Avoid building a large joined string; detectors will inspect lines directly. + component_source = "" # kept for signature compatibility with helpers + + # Check for inline objects in JSX props # Check for inline objects in JSX props _detect_inline_props(component_lines, start, opportunities) @@ -94,6 +97,9 @@ def _detect_inline_props(lines: list[str], offset: int, opportunities: list[Opti """Detect inline object/array literals in JSX prop positions.""" for i, line in enumerate(lines): line_num = offset + i + 1 + # Quick check to avoid running regexes when no JSX prop assignment is present + if "={" not in line: + continue if INLINE_OBJECT_IN_JSX_RE.search(line): opportunities.append( OptimizationOpportunity( @@ -120,12 +126,23 @@ def _detect_missing_usecallback( component_source: str, lines: list[str], offset: int, opportunities: list[OptimizationOpportunity] ) -> None: """Detect arrow functions or function expressions that could use useCallback.""" - has_usecallback = bool(USECALLBACK_RE.search(component_source)) + # Determine whether the component uses useCallback anywhere by scanning lines (avoid joining) + has_usecallback = False + for line in lines: + if "useCallback" in line: + # cheap substring check before regex to avoid unnecessary work + if USECALLBACK_RE.search(line): + has_usecallback = True + break for i, line in enumerate(lines): line_num = offset + i + 1 stripped = line.strip() # Look for arrow function or function expression definitions inside the component + # Quick substring check: FUNCTION_DEF_RE targets lines with var/const/let/function + if "const" not in stripped and "let" not in stripped and "var" not in stripped and "function" not in stripped: + continue + # Look for arrow function or function expression definitions inside the component if FUNCTION_DEF_RE.search(stripped) and "useCallback" not in stripped and "useMemo" not in stripped: # Skip if the component already uses useCallback extensively if not has_usecallback: @@ -147,6 +164,10 @@ def _detect_missing_usememo( for i, line in enumerate(lines): line_num = offset + i + 1 stripped = line.strip() + # Quick exclusions to avoid running the expensive regex when impossible to match + # expensive ops are accessed via a dot call like arr.map( + if "." not in stripped: + continue if EXPENSIVE_OPS_RE.search(stripped) and "useMemo" not in stripped: opportunities.append( OptimizationOpportunity( diff --git a/codeflash/languages/javascript/frameworks/react/context.py b/codeflash/languages/javascript/frameworks/react/context.py index 0d53e5c8b..d451fdb28 100644 --- a/codeflash/languages/javascript/frameworks/react/context.py +++ b/codeflash/languages/javascript/frameworks/react/context.py @@ -8,10 +8,11 @@ import logging from dataclasses import dataclass, field -from pathlib import Path from typing import TYPE_CHECKING if TYPE_CHECKING: + from pathlib import Path + from tree_sitter import Node from codeflash.languages.javascript.frameworks.react.analyzer import OptimizationOpportunity diff --git a/codeflash/languages/javascript/frameworks/react/discovery.py b/codeflash/languages/javascript/frameworks/react/discovery.py index 9e39de817..d4f30a9e6 100644 --- a/codeflash/languages/javascript/frameworks/react/discovery.py +++ b/codeflash/languages/javascript/frameworks/react/discovery.py @@ -10,10 +10,11 @@ import re from dataclasses import dataclass from enum import Enum -from pathlib import Path from typing import TYPE_CHECKING if TYPE_CHECKING: + from pathlib import Path + from tree_sitter import Node from codeflash.languages.javascript.treesitter import FunctionNode, TreeSitterAnalyzer @@ -191,11 +192,7 @@ def _node_contains_jsx(node: Node) -> bool: if _node_contains_jsx(child): return True - for child in node.children: - if _node_contains_jsx(child): - return True - - return False + return any(_node_contains_jsx(child) for child in node.children) def _extract_hooks_used(function_source: str) -> list[str]: diff --git a/codeflash/languages/javascript/frameworks/react/profiler.py b/codeflash/languages/javascript/frameworks/react/profiler.py index 880793c11..1321d0af5 100644 --- a/codeflash/languages/javascript/frameworks/react/profiler.py +++ b/codeflash/languages/javascript/frameworks/react/profiler.py @@ -12,10 +12,11 @@ import logging import re -from pathlib import Path from typing import TYPE_CHECKING if TYPE_CHECKING: + from pathlib import Path + from tree_sitter import Node from codeflash.languages.javascript.treesitter import TreeSitterAnalyzer @@ -76,9 +77,7 @@ def instrument_component_with_profiler(source: str, component_name: str, analyze result = _insert_after_imports(result, counter_code, analyzer) # Ensure React is imported - result = _ensure_react_import(result) - - return result + return _ensure_react_import(result) def instrument_all_components_for_tracing(source: str, file_path: Path, analyzer: TreeSitterAnalyzer) -> str: @@ -161,11 +160,12 @@ def walk(node: Node) -> None: def _contains_jsx(node: Node) -> bool: """Check if a tree-sitter node contains JSX elements.""" - if node.type in ("jsx_element", "jsx_self_closing_element", "jsx_fragment"): - return True - for child in node.children: - if _contains_jsx(child): + stack = [node] + while stack: + current = stack.pop() + if current.type in ("jsx_element", "jsx_self_closing_element", "jsx_fragment"): return True + stack.extend(current.children) return False diff --git a/codeflash/languages/javascript/treesitter_utils.py b/codeflash/languages/javascript/treesitter_utils.py index b6126ec9a..75792be6f 100644 --- a/codeflash/languages/javascript/treesitter_utils.py +++ b/codeflash/languages/javascript/treesitter_utils.py @@ -1580,9 +1580,9 @@ def get_analyzer_for_file(file_path: Path) -> TreeSitterAnalyzer: """ suffix = file_path.suffix.lower() - if suffix in (".ts",): + if suffix == ".ts": return TreeSitterAnalyzer(TreeSitterLanguage.TYPESCRIPT) - if suffix in (".tsx",): + if suffix == ".tsx": return TreeSitterAnalyzer(TreeSitterLanguage.TSX) # Default to JavaScript for .js, .jsx, .mjs, .cjs return TreeSitterAnalyzer(TreeSitterLanguage.JAVASCRIPT)