Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions codeflash/languages/javascript/edit_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,38 @@ def resolve_js_test_module_path(test_module_path: str, tests_project_rootdir: Pa
return tests_project_rootdir / Path(test_module_path)


# Pattern to match import statements inside function bodies (indented).
# These are invalid JS syntax and must be converted to require() calls.
# Matches: import X from 'Y', import { X } from 'Y', import * as X from 'Y'
_INDENTED_DEFAULT_IMPORT_RE = re.compile(r"^([ \t]+)import\s+(\w+)\s+from\s+['\"]([^'\"]+)['\"];?\s*$", re.MULTILINE)
_INDENTED_NAMED_IMPORT_RE = re.compile(
r"^([ \t]+)import\s+\{([^}]+)\}\s+from\s+['\"]([^'\"]+)['\"];?\s*$", re.MULTILINE
)
_INDENTED_NAMESPACE_IMPORT_RE = re.compile(
r"^([ \t]+)import\s+\*\s+as\s+(\w+)\s+from\s+['\"]([^'\"]+)['\"];?\s*$", re.MULTILINE
)


def fix_imports_inside_blocks(source: str) -> str:
"""Convert import statements inside function bodies to require() calls.

AI-generated tests sometimes place `import X from 'Y'` inside jest.mock()
callbacks, describe() blocks, or other function bodies. This is invalid
JavaScript syntax (imports must be at the top level). This function converts
them to equivalent `const X = require('Y')` calls which ARE valid inside
function bodies.

Only converts indented imports (those starting with whitespace), preserving
top-level imports which are valid ESM syntax.
"""
# Convert: import X from 'Y' -> const X = require('Y')
source = _INDENTED_DEFAULT_IMPORT_RE.sub(r"\1const \2 = require('\3');", source)
# Convert: import { X, Y } from 'Z' -> const { X, Y } = require('Z')
source = _INDENTED_NAMED_IMPORT_RE.sub(r"\1const {\2} = require('\3');", source)
# Convert: import * as X from 'Y' -> const X = require('Y')
return _INDENTED_NAMESPACE_IMPORT_RE.sub(r"\1const \2 = require('\3');", source)


# Patterns for normalizing codeflash imports (legacy -> npm package)
# Author: Sarthak Agarwal <sarthak.saga@gmail.com>
_CODEFLASH_REQUIRE_PATTERN = re.compile(
Expand Down
9 changes: 9 additions & 0 deletions codeflash/languages/javascript/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -1777,6 +1777,7 @@ def postprocess_generated_tests(
"""Apply language-specific postprocessing to generated tests."""
from codeflash.languages.javascript.edit_tests import (
disable_ts_check,
fix_imports_inside_blocks,
inject_test_globals,
normalize_generated_tests_imports,
sanitize_mocha_imports,
Expand Down Expand Up @@ -1807,6 +1808,14 @@ def postprocess_generated_tests(
generated_tests = inject_test_globals(generated_tests, test_framework, module_system)
if self.language == Language.TYPESCRIPT:
generated_tests = disable_ts_check(generated_tests)

# Fix import statements inside function bodies (jest.mock callbacks, describe blocks, etc.)
# AI sometimes generates `import X from 'Y'` inside blocks, which is invalid JS syntax.
for test in generated_tests.generated_tests:
test.generated_original_test_source = fix_imports_inside_blocks(test.generated_original_test_source)
test.instrumented_behavior_test_source = fix_imports_inside_blocks(test.instrumented_behavior_test_source)
test.instrumented_perf_test_source = fix_imports_inside_blocks(test.instrumented_perf_test_source)

return normalize_generated_tests_imports(generated_tests)

def remove_test_functions_from_generated_tests(
Expand Down
108 changes: 108 additions & 0 deletions tests/test_languages/test_javascript_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -1723,3 +1723,111 @@ def test_language_property_is_javascript(self, js_support):

assert js_support.language == Language.JAVASCRIPT
assert str(js_support.language) == "javascript"


class TestFixImportsInsideBlocks:
"""Tests for fix_imports_inside_blocks which converts illegal import statements
inside function bodies to valid require() calls."""

def test_import_inside_jest_mock_default(self):
from codeflash.languages.javascript.edit_tests import fix_imports_inside_blocks

source = """\
jest.mock('lodash/fp', () => {
import _ from 'lodash';
return { snakeCase: _.snakeCase };
});"""
expected = """\
jest.mock('lodash/fp', () => {
const _ = require('lodash');
return { snakeCase: _.snakeCase };
});"""
assert fix_imports_inside_blocks(source) == expected

def test_import_inside_jest_mock_named(self):
from codeflash.languages.javascript.edit_tests import fix_imports_inside_blocks

source = """\
jest.mock('../types', () => {
import { getTypeValidator, yup } from 'yup';
return { getTypeValidator };
});"""
expected = """\
jest.mock('../types', () => {
const { getTypeValidator, yup } = require('yup');
return { getTypeValidator };
});"""
assert fix_imports_inside_blocks(source) == expected

def test_import_inside_describe_block(self):
from codeflash.languages.javascript.edit_tests import fix_imports_inside_blocks

source = """\
import codeflash from 'codeflash';
describe('myFunc', () => {
import { contentTypes } from '@strapi/utils';
beforeEach(() => {});
});"""
expected = """\
import codeflash from 'codeflash';
describe('myFunc', () => {
const { contentTypes } = require('@strapi/utils');
beforeEach(() => {});
});"""
assert fix_imports_inside_blocks(source) == expected

def test_namespace_import_inside_block(self):
from codeflash.languages.javascript.edit_tests import fix_imports_inside_blocks

source = """\
jest.mock('./utils', () => {
import * as utils from '../real-utils';
return utils;
});"""
expected = """\
jest.mock('./utils', () => {
const utils = require('../real-utils');
return utils;
});"""
assert fix_imports_inside_blocks(source) == expected

def test_top_level_imports_preserved(self):
from codeflash.languages.javascript.edit_tests import fix_imports_inside_blocks

source = """\
import { moveElement } from '../utils/moveElement';
import codeflash from 'codeflash';

describe('moveElement', () => {
test('basic', () => {
expect(moveElement([1,2,3], 0, 1)).toEqual([2,1,3]);
});
});"""
assert fix_imports_inside_blocks(source) == source

def test_mixed_top_level_and_indented(self):
from codeflash.languages.javascript.edit_tests import fix_imports_inside_blocks

source = """\
import { fn } from './module';

jest.mock('dep', () => {
import helper from 'helper-lib';
return { helper };
});

describe('fn', () => {
test('works', () => {});
});"""
expected = """\
import { fn } from './module';

jest.mock('dep', () => {
const helper = require('helper-lib');
return { helper };
});

describe('fn', () => {
test('works', () => {});
});"""
assert fix_imports_inside_blocks(source) == expected
Loading