diff --git a/codeflash/languages/javascript/edit_tests.py b/codeflash/languages/javascript/edit_tests.py index 7f5281c02..07339a0b2 100644 --- a/codeflash/languages/javascript/edit_tests.py +++ b/codeflash/languages/javascript/edit_tests.py @@ -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 _CODEFLASH_REQUIRE_PATTERN = re.compile( diff --git a/codeflash/languages/javascript/support.py b/codeflash/languages/javascript/support.py index 039d1ce98..577712e39 100644 --- a/codeflash/languages/javascript/support.py +++ b/codeflash/languages/javascript/support.py @@ -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, @@ -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( diff --git a/tests/test_languages/test_javascript_support.py b/tests/test_languages/test_javascript_support.py index 5d5943151..534b447b7 100644 --- a/tests/test_languages/test_javascript_support.py +++ b/tests/test_languages/test_javascript_support.py @@ -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