diff --git a/codeflash/languages/javascript/support.py b/codeflash/languages/javascript/support.py index b9037b939..4cc0d496a 100644 --- a/codeflash/languages/javascript/support.py +++ b/codeflash/languages/javascript/support.py @@ -160,9 +160,15 @@ def discover_functions( if not criteria.include_async and func.is_async: continue + # Skip nested functions (functions defined inside other functions) + # Nested functions depend on closure variables from parent scope and cannot + # be optimized in isolation without complex context extraction + if func.parent_function: + logger.debug(f"Skipping nested function: {func.name} (parent: {func.parent_function})") # noqa: G004 + continue + # Skip non-exported functions (can't be imported in tests) - # Exception: nested functions and methods are allowed if their parent is exported - if criteria.require_export and not func.is_exported and not func.parent_function: + if criteria.require_export and not func.is_exported: logger.debug(f"Skipping non-exported function: {func.name}") # noqa: G004 continue diff --git a/tests/languages/javascript/test_discover_functions.py b/tests/languages/javascript/test_discover_functions.py new file mode 100644 index 000000000..c0c5d2d37 --- /dev/null +++ b/tests/languages/javascript/test_discover_functions.py @@ -0,0 +1,117 @@ +"""Tests for JavaScript/TypeScript function discovery logic.""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from codeflash.languages.base import FunctionFilterCriteria +from codeflash.languages.javascript.support import JavaScriptSupport + + +class TestFunctionDiscovery: + """Tests for discover_functions method.""" + + @pytest.fixture + def js_support(self) -> JavaScriptSupport: + """Create a JavaScriptSupport instance.""" + return JavaScriptSupport() + + def test_discovers_top_level_function(self, js_support: JavaScriptSupport) -> None: + """Should discover top-level exported functions.""" + code = """ +export function topLevelFunc() { + return 42; +} +""" + functions = js_support.discover_functions( + code, + Path("/tmp/test.js"), + FunctionFilterCriteria(require_export=True, require_return=True), + ) + + assert len(functions) == 1 + assert functions[0].function_name == "topLevelFunc" + assert functions[0].parents == [] + + def test_skips_nested_functions_in_closures(self, js_support: JavaScriptSupport) -> None: + """Should skip nested functions that are defined inside other functions. + + Nested functions depend on closure variables from their parent scope and cannot + be optimized in isolation without extracting the entire parent context. + + Bug: Previously, nested functions were discovered and attempted to be optimized, + but the extraction logic only captured the nested function body, causing + validation errors like "Undefined variable(s): base, streamFn, record". + """ + code = """ +export function wrapStreamFn(streamFn) { + const base = { id: 1 }; + const record = (event) => { }; + + const wrapped = (model, context, options) => { + if (!model) { + return streamFn(model, context, options); + } + record({ data: base }); + return base; + }; + + return wrapped; +} +""" + functions = js_support.discover_functions( + code, + Path("/tmp/test.js"), + FunctionFilterCriteria(require_export=True, require_return=True), + ) + + # Should only discover the top-level function, not the nested ones + assert len(functions) == 1, f"Expected 1 function but found {len(functions)}: {[f.function_name for f in functions]}" + assert functions[0].function_name == "wrapStreamFn" + assert functions[0].parents == [] + + def test_discovers_class_methods(self, js_support: JavaScriptSupport) -> None: + """Should discover class methods (these are handled specially with class wrapping).""" + code = """ +export class MyClass { + myMethod() { + return 42; + } +} +""" + functions = js_support.discover_functions( + code, + Path("/tmp/test.js"), + FunctionFilterCriteria(require_export=True, require_return=True, include_methods=True), + ) + + assert len(functions) == 1 + assert functions[0].function_name == "myMethod" + assert len(functions[0].parents) == 1 + assert functions[0].parents[0].name == "MyClass" + assert functions[0].parents[0].type == "ClassDef" + + def test_skips_nested_functions_with_multiple_levels(self, js_support: JavaScriptSupport) -> None: + """Should skip deeply nested functions.""" + code = """ +export function outer() { + const middle = () => { + const inner = () => { + return 42; + }; + return inner(); + }; + return middle(); +} +""" + functions = js_support.discover_functions( + code, + Path("/tmp/test.js"), + FunctionFilterCriteria(require_export=True, require_return=True), + ) + + # Should only discover the top-level function + assert len(functions) == 1 + assert functions[0].function_name == "outer"