Skip to content

Commit 6b77be5

Browse files
ignore calls inside string litrals for instrumentation and fix e2e test
1 parent e07fd1d commit 6b77be5

4 files changed

Lines changed: 174 additions & 5 deletions

File tree

code_to_optimize/js/code_to_optimize_js/fibonacci.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* @param {number} n - The index of the Fibonacci number to calculate
99
* @returns {number} - The nth Fibonacci number
1010
*/
11-
export function fibonacci(n) {
11+
function fibonacci(n) {
1212
if (n <= 1) {
1313
return n;
1414
}
@@ -20,7 +20,7 @@ export function fibonacci(n) {
2020
* @param {number} num - The number to check
2121
* @returns {boolean} - True if num is a Fibonacci number
2222
*/
23-
export function isFibonacci(num) {
23+
function isFibonacci(num) {
2424
// A number is Fibonacci if one of (5*n*n + 4) or (5*n*n - 4) is a perfect square
2525
const check1 = 5 * num * num + 4;
2626
const check2 = 5 * num * num - 4;
@@ -33,7 +33,7 @@ export function isFibonacci(num) {
3333
* @param {number} n - The number to check
3434
* @returns {boolean} - True if n is a perfect square
3535
*/
36-
export function isPerfectSquare(n) {
36+
function isPerfectSquare(n) {
3737
const sqrt = Math.sqrt(n);
3838
return sqrt === Math.floor(sqrt);
3939
}
@@ -43,7 +43,7 @@ export function isPerfectSquare(n) {
4343
* @param {number} n - The number of Fibonacci numbers to generate
4444
* @returns {number[]} - Array of Fibonacci numbers
4545
*/
46-
export function fibonacciSequence(n) {
46+
function fibonacciSequence(n) {
4747
const result = [];
4848
for (let i = 0; i < n; i++) {
4949
result.push(fibonacci(i));

codeflash/languages/javascript/instrument.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,46 @@ class StandaloneCallMatch:
5656
)
5757

5858

59+
def is_inside_string(code: str, pos: int) -> bool:
60+
"""Check if a position in code is inside a string literal.
61+
62+
Handles single quotes, double quotes, and template literals (backticks).
63+
Properly handles escaped quotes.
64+
65+
Args:
66+
code: The source code.
67+
pos: The position to check.
68+
69+
Returns:
70+
True if the position is inside a string literal.
71+
72+
"""
73+
in_string = False
74+
string_char = None
75+
i = 0
76+
77+
while i < pos:
78+
char = code[i]
79+
80+
if in_string:
81+
# Check for escape sequence
82+
if char == "\\" and i + 1 < len(code):
83+
i += 2 # Skip escaped character
84+
continue
85+
# Check for end of string
86+
if char == string_char:
87+
in_string = False
88+
string_char = None
89+
# Check for start of string
90+
elif char in "\"'`":
91+
in_string = True
92+
string_char = char
93+
94+
i += 1
95+
96+
return in_string
97+
98+
5999
class StandaloneCallTransformer:
60100
"""Transforms standalone func(...) calls in JavaScript test code.
61101
@@ -150,6 +190,10 @@ def transform(self, code: str) -> str:
150190

151191
def _should_skip_match(self, code: str, start: int, match: re.Match) -> bool:
152192
"""Check if the match should be skipped (inside expect, already transformed, etc.)."""
193+
# Skip if inside a string literal (e.g., test description)
194+
if is_inside_string(code, start):
195+
return True
196+
153197
# Look backwards to check context
154198
lookback_start = max(0, start - 200)
155199
lookback = code[lookback_start:start]
@@ -439,6 +483,12 @@ def transform(self, code: str) -> str:
439483
result.append(code[pos:])
440484
break
441485

486+
# Skip if inside a string literal (e.g., test description)
487+
if is_inside_string(code, match.start()):
488+
result.append(code[pos : match.end()])
489+
pos = match.end()
490+
continue
491+
442492
# Add everything before the match
443493
result.append(code[pos : match.start()])
444494

codeflash/languages/javascript/test_runner.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,8 @@ def run_jest_behavioral_tests(
803803
wall_clock_ns = time.perf_counter_ns() - start_time_ns
804804
logger.debug(f"Jest behavioral tests completed in {wall_clock_ns / 1e9:.2f}s")
805805

806+
print(result.stdout)
807+
806808
return result_file_path, result, coverage_json_path, None
807809

808810

tests/test_languages/test_javascript_instrumentation.py

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -856,4 +856,121 @@ def test_empty_code(self):
856856
test_file = tests_dir / "test.ts"
857857

858858
assert fix_jest_mock_paths("", test_file, source_file, tests_dir) == ""
859-
assert fix_jest_mock_paths(" ", test_file, source_file, tests_dir) == " "
859+
assert fix_jest_mock_paths(" ", test_file, source_file, tests_dir) == " "
860+
861+
862+
class TestFunctionCallsInStrings:
863+
"""Tests for skipping function calls inside string literals."""
864+
865+
def test_skip_function_in_test_description_single_quotes(self):
866+
"""Test that function calls in single-quoted test descriptions are not transformed."""
867+
from codeflash.languages.javascript.instrument import transform_standalone_calls
868+
869+
func = make_func("fibonacci")
870+
code = """
871+
test('should compute fibonacci(20) and fibonacci(30) to known values', () => {
872+
const result = fibonacci(10);
873+
expect(result).toBe(55);
874+
});
875+
"""
876+
transformed, _counter = transform_standalone_calls(code, func, "capture")
877+
878+
# The function call in the test description should NOT be transformed
879+
assert "fibonacci(20)" in transformed
880+
assert "fibonacci(30)" in transformed
881+
# The actual call should be transformed
882+
assert "codeflash.capture('fibonacci'" in transformed
883+
884+
def test_skip_function_in_test_description_double_quotes(self):
885+
"""Test that function calls in double-quoted test descriptions are not transformed."""
886+
from codeflash.languages.javascript.instrument import transform_standalone_calls
887+
888+
func = make_func("fibonacci")
889+
code = '''
890+
test("should compute fibonacci(20) correctly", () => {
891+
const result = fibonacci(10);
892+
});
893+
'''
894+
transformed, _counter = transform_standalone_calls(code, func, "capture")
895+
896+
# The function call in the test description should NOT be transformed
897+
assert 'fibonacci(20)' in transformed
898+
# The actual call should be transformed
899+
assert "codeflash.capture('fibonacci'" in transformed
900+
901+
def test_skip_function_in_template_literal(self):
902+
"""Test that function calls in template literals are not transformed."""
903+
from codeflash.languages.javascript.instrument import transform_standalone_calls
904+
905+
func = make_func("fibonacci")
906+
code = """
907+
test(`should compute fibonacci(20) correctly`, () => {
908+
const result = fibonacci(10);
909+
});
910+
"""
911+
transformed, _counter = transform_standalone_calls(code, func, "capture")
912+
913+
# The function call in the template literal should NOT be transformed
914+
assert "fibonacci(20)" in transformed
915+
# The actual call should be transformed
916+
assert "codeflash.capture('fibonacci'" in transformed
917+
918+
def test_skip_expect_in_string_literal(self):
919+
"""Test that expect(func()) in string literals is not transformed."""
920+
from codeflash.languages.javascript.instrument import transform_expect_calls
921+
922+
func = make_func("fibonacci")
923+
code = """
924+
describe('testing expect(fibonacci(n)) patterns', () => {
925+
test('works', () => {
926+
expect(fibonacci(10)).toBe(55);
927+
});
928+
});
929+
"""
930+
transformed, _counter = transform_expect_calls(code, func, "capture")
931+
932+
# The expect in the describe string should NOT be transformed
933+
assert "expect(fibonacci(n))" in transformed
934+
# The actual expect call should be transformed
935+
assert "codeflash.capture('fibonacci'" in transformed
936+
937+
def test_handle_escaped_quotes_in_string(self):
938+
"""Test that escaped quotes in strings are handled correctly."""
939+
from codeflash.languages.javascript.instrument import transform_standalone_calls
940+
941+
func = make_func("fibonacci")
942+
code = """
943+
test('test \\'fibonacci(5)\\' escaping', () => {
944+
const result = fibonacci(10);
945+
});
946+
"""
947+
transformed, _counter = transform_standalone_calls(code, func, "capture")
948+
949+
# The function call in the escaped string should NOT be transformed
950+
assert "fibonacci(5)" in transformed
951+
# The actual call should be transformed
952+
assert "codeflash.capture('fibonacci'" in transformed
953+
954+
def test_is_inside_string_helper(self):
955+
"""Test the is_inside_string helper function directly."""
956+
from codeflash.languages.javascript.instrument import is_inside_string
957+
958+
# Position inside single-quoted string
959+
code1 = "test('fibonacci(5)', () => {})"
960+
assert is_inside_string(code1, 10) is True # Inside the string
961+
962+
# Position outside string
963+
assert is_inside_string(code1, 0) is False # Before string
964+
assert is_inside_string(code1, 25) is False # After string
965+
966+
# Double quotes
967+
code2 = 'test("fibonacci(5)", () => {})'
968+
assert is_inside_string(code2, 10) is True
969+
970+
# Template literal
971+
code3 = "test(`fibonacci(5)`, () => {})"
972+
assert is_inside_string(code3, 10) is True
973+
974+
# Escaped quote doesn't end string
975+
code4 = "test('fib\\'s result', () => {})"
976+
assert is_inside_string(code4, 15) is True # Still inside after escaped quote

0 commit comments

Comments
 (0)