Skip to content

Commit b8c31b3

Browse files
authored
Merge pull request #1968 from codeflash-ai/fix/skip-nested-functions-js
Fix: Skip nested functions in JavaScript/TypeScript discovery
2 parents fdf74e2 + dc13d3c commit b8c31b3

2 files changed

Lines changed: 125 additions & 2 deletions

File tree

codeflash/languages/javascript/support.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,15 @@ def discover_functions(
160160
if not criteria.include_async and func.is_async:
161161
continue
162162

163+
# Skip nested functions (functions defined inside other functions)
164+
# Nested functions depend on closure variables from parent scope and cannot
165+
# be optimized in isolation without complex context extraction
166+
if func.parent_function:
167+
logger.debug(f"Skipping nested function: {func.name} (parent: {func.parent_function})") # noqa: G004
168+
continue
169+
163170
# Skip non-exported functions (can't be imported in tests)
164-
# Exception: nested functions and methods are allowed if their parent is exported
165-
if criteria.require_export and not func.is_exported and not func.parent_function:
171+
if criteria.require_export and not func.is_exported:
166172
logger.debug(f"Skipping non-exported function: {func.name}") # noqa: G004
167173
continue
168174

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
"""Tests for JavaScript/TypeScript function discovery logic."""
2+
3+
from __future__ import annotations
4+
5+
from pathlib import Path
6+
7+
import pytest
8+
9+
from codeflash.languages.base import FunctionFilterCriteria
10+
from codeflash.languages.javascript.support import JavaScriptSupport
11+
12+
13+
class TestFunctionDiscovery:
14+
"""Tests for discover_functions method."""
15+
16+
@pytest.fixture
17+
def js_support(self) -> JavaScriptSupport:
18+
"""Create a JavaScriptSupport instance."""
19+
return JavaScriptSupport()
20+
21+
def test_discovers_top_level_function(self, js_support: JavaScriptSupport) -> None:
22+
"""Should discover top-level exported functions."""
23+
code = """
24+
export function topLevelFunc() {
25+
return 42;
26+
}
27+
"""
28+
functions = js_support.discover_functions(
29+
code,
30+
Path("/tmp/test.js"),
31+
FunctionFilterCriteria(require_export=True, require_return=True),
32+
)
33+
34+
assert len(functions) == 1
35+
assert functions[0].function_name == "topLevelFunc"
36+
assert functions[0].parents == []
37+
38+
def test_skips_nested_functions_in_closures(self, js_support: JavaScriptSupport) -> None:
39+
"""Should skip nested functions that are defined inside other functions.
40+
41+
Nested functions depend on closure variables from their parent scope and cannot
42+
be optimized in isolation without extracting the entire parent context.
43+
44+
Bug: Previously, nested functions were discovered and attempted to be optimized,
45+
but the extraction logic only captured the nested function body, causing
46+
validation errors like "Undefined variable(s): base, streamFn, record".
47+
"""
48+
code = """
49+
export function wrapStreamFn(streamFn) {
50+
const base = { id: 1 };
51+
const record = (event) => { };
52+
53+
const wrapped = (model, context, options) => {
54+
if (!model) {
55+
return streamFn(model, context, options);
56+
}
57+
record({ data: base });
58+
return base;
59+
};
60+
61+
return wrapped;
62+
}
63+
"""
64+
functions = js_support.discover_functions(
65+
code,
66+
Path("/tmp/test.js"),
67+
FunctionFilterCriteria(require_export=True, require_return=True),
68+
)
69+
70+
# Should only discover the top-level function, not the nested ones
71+
assert len(functions) == 1, f"Expected 1 function but found {len(functions)}: {[f.function_name for f in functions]}"
72+
assert functions[0].function_name == "wrapStreamFn"
73+
assert functions[0].parents == []
74+
75+
def test_discovers_class_methods(self, js_support: JavaScriptSupport) -> None:
76+
"""Should discover class methods (these are handled specially with class wrapping)."""
77+
code = """
78+
export class MyClass {
79+
myMethod() {
80+
return 42;
81+
}
82+
}
83+
"""
84+
functions = js_support.discover_functions(
85+
code,
86+
Path("/tmp/test.js"),
87+
FunctionFilterCriteria(require_export=True, require_return=True, include_methods=True),
88+
)
89+
90+
assert len(functions) == 1
91+
assert functions[0].function_name == "myMethod"
92+
assert len(functions[0].parents) == 1
93+
assert functions[0].parents[0].name == "MyClass"
94+
assert functions[0].parents[0].type == "ClassDef"
95+
96+
def test_skips_nested_functions_with_multiple_levels(self, js_support: JavaScriptSupport) -> None:
97+
"""Should skip deeply nested functions."""
98+
code = """
99+
export function outer() {
100+
const middle = () => {
101+
const inner = () => {
102+
return 42;
103+
};
104+
return inner();
105+
};
106+
return middle();
107+
}
108+
"""
109+
functions = js_support.discover_functions(
110+
code,
111+
Path("/tmp/test.js"),
112+
FunctionFilterCriteria(require_export=True, require_return=True),
113+
)
114+
115+
# Should only discover the top-level function
116+
assert len(functions) == 1
117+
assert functions[0].function_name == "outer"

0 commit comments

Comments
 (0)