mirror of
https://github.com/codeflash-ai/codeflash.git
synced 2026-05-04 18:25:17 +00:00
# Conflicts: # .claude/rules/architecture.md # .claude/rules/code-style.md # .github/workflows/claude.yml # .github/workflows/duplicate-code-detector.yml # codeflash/api/aiservice.py # codeflash/cli_cmds/console.py # codeflash/cli_cmds/logging_config.py # codeflash/code_utils/deduplicate_code.py # codeflash/discovery/discover_unit_tests.py # codeflash/languages/base.py # codeflash/languages/code_replacer.py # codeflash/languages/javascript/mocha_runner.py # codeflash/languages/javascript/support.py # codeflash/languages/python/support.py # codeflash/optimization/function_optimizer.py # codeflash/verification/parse_test_output.py # codeflash/verification/verification_utils.py # codeflash/verification/verifier.py # packages/codeflash/package-lock.json # packages/codeflash/package.json # tests/languages/javascript/test_support_dispatch.py # tests/test_codeflash_capture.py # tests/test_languages/test_javascript_test_runner.py # tests/test_multi_file_code_replacement.py
718 lines
25 KiB
Python
718 lines
25 KiB
Python
"""Tests for JavaScript/TypeScript import resolver.
|
|
|
|
These tests verify that the ImportResolver correctly resolves import paths
|
|
to actual file paths, enabling multi-file context extraction.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from codeflash.languages.javascript.import_resolver import HelperSearchContext, ImportResolver, MultiFileHelperFinder
|
|
from codeflash.languages.javascript.treesitter import ImportInfo
|
|
|
|
|
|
class TestImportResolver:
|
|
"""Tests for ImportResolver class."""
|
|
|
|
@pytest.fixture
|
|
def project_root(self, tmp_path):
|
|
"""Create a temporary project structure."""
|
|
# Create directories
|
|
src_dir = tmp_path / "src"
|
|
src_dir.mkdir()
|
|
lib_dir = src_dir / "lib"
|
|
lib_dir.mkdir()
|
|
utils_dir = src_dir / "utils"
|
|
utils_dir.mkdir()
|
|
|
|
# Create some test files
|
|
(src_dir / "main.ts").write_text("export function main() {}")
|
|
(src_dir / "helper.ts").write_text("export function helper() {}")
|
|
(lib_dir / "math.ts").write_text("export function add() {}")
|
|
(utils_dir / "index.ts").write_text("export function util() {}")
|
|
|
|
return tmp_path
|
|
|
|
@pytest.fixture
|
|
def resolver(self, project_root):
|
|
"""Create an ImportResolver for the project."""
|
|
return ImportResolver(project_root)
|
|
|
|
def test_is_external_package_lodash(self, resolver):
|
|
"""Test that bare imports are detected as external."""
|
|
assert resolver._is_external_package("lodash") is True
|
|
|
|
def test_is_external_package_scoped(self, resolver):
|
|
"""Test that scoped packages are detected as external."""
|
|
assert resolver._is_external_package("@company/utils") is True
|
|
|
|
def test_is_external_package_react(self, resolver):
|
|
"""Test that react is detected as external."""
|
|
assert resolver._is_external_package("react") is True
|
|
|
|
def test_is_not_external_package_relative(self, resolver):
|
|
"""Test that relative imports are not external."""
|
|
assert resolver._is_external_package("./utils") is False
|
|
|
|
def test_is_not_external_package_parent_relative(self, resolver):
|
|
"""Test that parent relative imports are not external."""
|
|
assert resolver._is_external_package("../lib/math") is False
|
|
|
|
def test_resolve_relative_import_same_dir(self, resolver, project_root):
|
|
"""Test resolving ./helper from same directory."""
|
|
source_file = project_root / "src" / "main.ts"
|
|
import_info = ImportInfo(
|
|
module_path="./helper",
|
|
default_import=None,
|
|
named_imports=[("helper", None)],
|
|
namespace_import=None,
|
|
is_type_only=False,
|
|
start_line=1,
|
|
end_line=1,
|
|
)
|
|
|
|
result = resolver.resolve_import(import_info, source_file)
|
|
|
|
assert result is not None
|
|
assert result.file_path == project_root / "src" / "helper.ts"
|
|
assert result.module_path == "./helper"
|
|
|
|
def test_resolve_relative_import_subdirectory(self, resolver, project_root):
|
|
"""Test resolving ./lib/math from parent directory."""
|
|
source_file = project_root / "src" / "main.ts"
|
|
import_info = ImportInfo(
|
|
module_path="./lib/math",
|
|
default_import=None,
|
|
named_imports=[("add", None)],
|
|
namespace_import=None,
|
|
is_type_only=False,
|
|
start_line=1,
|
|
end_line=1,
|
|
)
|
|
|
|
result = resolver.resolve_import(import_info, source_file)
|
|
|
|
assert result is not None
|
|
assert result.file_path == project_root / "src" / "lib" / "math.ts"
|
|
|
|
def test_resolve_index_file(self, resolver, project_root):
|
|
"""Test resolving ./utils to ./utils/index.ts."""
|
|
source_file = project_root / "src" / "main.ts"
|
|
import_info = ImportInfo(
|
|
module_path="./utils",
|
|
default_import=None,
|
|
named_imports=[("util", None)],
|
|
namespace_import=None,
|
|
is_type_only=False,
|
|
start_line=1,
|
|
end_line=1,
|
|
)
|
|
|
|
result = resolver.resolve_import(import_info, source_file)
|
|
|
|
assert result is not None
|
|
assert result.file_path == project_root / "src" / "utils" / "index.ts"
|
|
|
|
def test_resolve_external_package_returns_none(self, resolver, project_root):
|
|
"""Test that external package imports return None."""
|
|
source_file = project_root / "src" / "main.ts"
|
|
import_info = ImportInfo(
|
|
module_path="lodash",
|
|
default_import="_",
|
|
named_imports=[],
|
|
namespace_import=None,
|
|
is_type_only=False,
|
|
start_line=1,
|
|
end_line=1,
|
|
)
|
|
|
|
result = resolver.resolve_import(import_info, source_file)
|
|
|
|
assert result is None
|
|
|
|
def test_resolve_nonexistent_file_returns_none(self, resolver, project_root):
|
|
"""Test that nonexistent file imports return None."""
|
|
source_file = project_root / "src" / "main.ts"
|
|
import_info = ImportInfo(
|
|
module_path="./nonexistent",
|
|
default_import=None,
|
|
named_imports=[("foo", None)],
|
|
namespace_import=None,
|
|
is_type_only=False,
|
|
start_line=1,
|
|
end_line=1,
|
|
)
|
|
|
|
result = resolver.resolve_import(import_info, source_file)
|
|
|
|
assert result is None
|
|
|
|
def test_resolve_with_explicit_extension(self, resolver, project_root):
|
|
"""Test resolving import with explicit .ts extension."""
|
|
source_file = project_root / "src" / "main.ts"
|
|
import_info = ImportInfo(
|
|
module_path="./helper.ts",
|
|
default_import=None,
|
|
named_imports=[("helper", None)],
|
|
namespace_import=None,
|
|
is_type_only=False,
|
|
start_line=1,
|
|
end_line=1,
|
|
)
|
|
|
|
result = resolver.resolve_import(import_info, source_file)
|
|
|
|
assert result is not None
|
|
assert result.file_path == project_root / "src" / "helper.ts"
|
|
|
|
def test_resolved_import_contains_imported_names(self, resolver, project_root):
|
|
"""Test that ResolvedImport contains correct imported names."""
|
|
source_file = project_root / "src" / "main.ts"
|
|
import_info = ImportInfo(
|
|
module_path="./helper",
|
|
default_import="Helper",
|
|
named_imports=[("foo", None), ("bar", "baz")],
|
|
namespace_import=None,
|
|
is_type_only=False,
|
|
start_line=1,
|
|
end_line=1,
|
|
)
|
|
|
|
result = resolver.resolve_import(import_info, source_file)
|
|
|
|
assert result is not None
|
|
assert "Helper" in result.imported_names
|
|
assert "foo" in result.imported_names
|
|
assert "baz" in result.imported_names # alias is used
|
|
assert result.is_default_import is True
|
|
|
|
def test_namespace_import_detection(self, resolver, project_root):
|
|
"""Test that namespace imports are correctly detected."""
|
|
source_file = project_root / "src" / "main.ts"
|
|
import_info = ImportInfo(
|
|
module_path="./helper",
|
|
default_import=None,
|
|
named_imports=[],
|
|
namespace_import="utils",
|
|
is_type_only=False,
|
|
start_line=1,
|
|
end_line=1,
|
|
)
|
|
|
|
result = resolver.resolve_import(import_info, source_file)
|
|
|
|
assert result is not None
|
|
assert result.is_namespace_import is True
|
|
assert result.namespace_name == "utils"
|
|
|
|
def test_caching_works(self, resolver, project_root):
|
|
"""Test that resolution results are cached."""
|
|
source_file = project_root / "src" / "main.ts"
|
|
import_info = ImportInfo(
|
|
module_path="./helper",
|
|
default_import=None,
|
|
named_imports=[("helper", None)],
|
|
namespace_import=None,
|
|
is_type_only=False,
|
|
start_line=1,
|
|
end_line=1,
|
|
)
|
|
|
|
# First resolution
|
|
result1 = resolver.resolve_import(import_info, source_file)
|
|
# Second resolution should use cache
|
|
result2 = resolver.resolve_import(import_info, source_file)
|
|
|
|
assert result1 is not None
|
|
assert result2 is not None
|
|
assert result1.file_path == result2.file_path
|
|
# Check cache was populated
|
|
assert (source_file, "./helper") in resolver._resolution_cache
|
|
|
|
|
|
class TestMultiFileHelperFinder:
|
|
"""Tests for MultiFileHelperFinder class."""
|
|
|
|
@pytest.fixture
|
|
def project_root(self, tmp_path):
|
|
"""Create a temporary project with multi-file structure."""
|
|
src_dir = tmp_path / "src"
|
|
src_dir.mkdir()
|
|
|
|
# Main file that imports helper
|
|
(src_dir / "main.ts").write_text("""
|
|
import { helperFunc } from './helper';
|
|
|
|
export function mainFunc() {
|
|
return helperFunc() + 1;
|
|
}
|
|
""")
|
|
|
|
# Helper file
|
|
(src_dir / "helper.ts").write_text("""
|
|
export function helperFunc() {
|
|
return 42;
|
|
}
|
|
|
|
export function unusedHelper() {
|
|
return 0;
|
|
}
|
|
""")
|
|
|
|
return tmp_path
|
|
|
|
@pytest.fixture
|
|
def resolver(self, project_root):
|
|
"""Create an ImportResolver."""
|
|
return ImportResolver(project_root)
|
|
|
|
@pytest.fixture
|
|
def finder(self, project_root, resolver):
|
|
"""Create a MultiFileHelperFinder."""
|
|
return MultiFileHelperFinder(project_root, resolver)
|
|
|
|
def test_helper_search_context_defaults(self):
|
|
"""Test HelperSearchContext default values."""
|
|
context = HelperSearchContext()
|
|
assert context.visited_files == set()
|
|
assert context.visited_functions == set()
|
|
assert context.current_depth == 0
|
|
assert context.max_depth == 2
|
|
|
|
|
|
class TestExportInfo:
|
|
"""Tests for ExportInfo parsing in TreeSitterAnalyzer."""
|
|
|
|
@pytest.fixture
|
|
def js_analyzer(self):
|
|
"""Create a JavaScript analyzer."""
|
|
from codeflash.languages.javascript.treesitter import TreeSitterAnalyzer, TreeSitterLanguage
|
|
|
|
return TreeSitterAnalyzer(TreeSitterLanguage.JAVASCRIPT)
|
|
|
|
def test_find_named_export_function(self, js_analyzer):
|
|
"""Test finding export function declaration."""
|
|
code = "export function helper() { return 1; }"
|
|
exports = js_analyzer.find_exports(code)
|
|
|
|
assert len(exports) == 1
|
|
assert ("helper", None) in exports[0].exported_names
|
|
assert exports[0].is_reexport is False
|
|
|
|
def test_find_default_export_function(self, js_analyzer):
|
|
"""Test finding export default function."""
|
|
code = "export default function myFunc() { return 1; }"
|
|
exports = js_analyzer.find_exports(code)
|
|
|
|
assert len(exports) == 1
|
|
assert exports[0].default_export == "myFunc"
|
|
|
|
def test_find_export_declaration(self, js_analyzer):
|
|
"""Test finding export { name }."""
|
|
code = """
|
|
function helper() { return 1; }
|
|
export { helper };
|
|
"""
|
|
exports = js_analyzer.find_exports(code)
|
|
|
|
assert len(exports) == 1
|
|
assert ("helper", None) in exports[0].exported_names
|
|
|
|
def test_find_export_with_alias(self, js_analyzer):
|
|
"""Test finding export { name as alias }."""
|
|
code = """
|
|
function helper() { return 1; }
|
|
export { helper as myHelper };
|
|
"""
|
|
exports = js_analyzer.find_exports(code)
|
|
|
|
assert len(exports) == 1
|
|
assert ("helper", "myHelper") in exports[0].exported_names
|
|
|
|
def test_find_reexport(self, js_analyzer):
|
|
"""Test finding re-export from another module."""
|
|
code = "export { helper } from './other';"
|
|
exports = js_analyzer.find_exports(code)
|
|
|
|
assert len(exports) == 1
|
|
assert exports[0].is_reexport is True
|
|
assert exports[0].reexport_source == "./other"
|
|
|
|
def test_find_export_const(self, js_analyzer):
|
|
"""Test finding export const declaration."""
|
|
code = "export const myVar = 42;"
|
|
exports = js_analyzer.find_exports(code)
|
|
|
|
assert len(exports) == 1
|
|
assert ("myVar", None) in exports[0].exported_names
|
|
|
|
def test_is_function_exported_true(self, js_analyzer):
|
|
"""Test is_function_exported returns True for exported function."""
|
|
code = "export function helper() { return 1; }"
|
|
is_exported, export_name = js_analyzer.is_function_exported(code, "helper")
|
|
|
|
assert is_exported is True
|
|
assert export_name == "helper"
|
|
|
|
def test_is_function_exported_false(self, js_analyzer):
|
|
"""Test is_function_exported returns False for non-exported function."""
|
|
code = "function helper() { return 1; }"
|
|
is_exported, export_name = js_analyzer.is_function_exported(code, "helper")
|
|
|
|
assert is_exported is False
|
|
assert export_name is None
|
|
|
|
def test_is_function_exported_with_alias(self, js_analyzer):
|
|
"""Test is_function_exported returns alias name."""
|
|
code = """
|
|
function helper() { return 1; }
|
|
export { helper as myHelper };
|
|
"""
|
|
is_exported, export_name = js_analyzer.is_function_exported(code, "helper")
|
|
|
|
assert is_exported is True
|
|
assert export_name == "myHelper"
|
|
|
|
def test_is_function_exported_default(self, js_analyzer):
|
|
"""Test is_function_exported returns 'default' for default export."""
|
|
code = "export default function helper() { return 1; }"
|
|
is_exported, export_name = js_analyzer.is_function_exported(code, "helper")
|
|
|
|
assert is_exported is True
|
|
assert export_name == "default"
|
|
|
|
|
|
class TestCommonJSRequire:
|
|
"""Tests for CommonJS require() import parsing."""
|
|
|
|
@pytest.fixture
|
|
def js_analyzer(self):
|
|
"""Create a JavaScript analyzer."""
|
|
from codeflash.languages.javascript.treesitter import TreeSitterAnalyzer, TreeSitterLanguage
|
|
|
|
return TreeSitterAnalyzer(TreeSitterLanguage.JAVASCRIPT)
|
|
|
|
def test_require_default_import(self, js_analyzer):
|
|
"""Test const foo = require('./module')."""
|
|
code = "const helper = require('./helper');"
|
|
imports = js_analyzer.find_imports(code)
|
|
|
|
assert len(imports) == 1
|
|
assert imports[0].module_path == "./helper"
|
|
assert imports[0].default_import == "helper"
|
|
assert imports[0].named_imports == []
|
|
|
|
def test_require_destructured_import(self, js_analyzer):
|
|
"""Test const { a, b } = require('./module')."""
|
|
code = "const { foo, bar } = require('./helper');"
|
|
imports = js_analyzer.find_imports(code)
|
|
|
|
assert len(imports) == 1
|
|
assert imports[0].module_path == "./helper"
|
|
assert imports[0].default_import is None
|
|
assert ("foo", None) in imports[0].named_imports
|
|
assert ("bar", None) in imports[0].named_imports
|
|
|
|
def test_require_destructured_with_alias(self, js_analyzer):
|
|
"""Test const { a: aliasA } = require('./module')."""
|
|
code = "const { foo: myFoo, bar } = require('./helper');"
|
|
imports = js_analyzer.find_imports(code)
|
|
|
|
assert len(imports) == 1
|
|
assert imports[0].module_path == "./helper"
|
|
assert ("foo", "myFoo") in imports[0].named_imports
|
|
assert ("bar", None) in imports[0].named_imports
|
|
|
|
def test_require_property_access(self, js_analyzer):
|
|
"""Test const foo = require('./module').bar."""
|
|
code = "const myFunc = require('./helper').helperFunc;"
|
|
imports = js_analyzer.find_imports(code)
|
|
|
|
assert len(imports) == 1
|
|
assert imports[0].module_path == "./helper"
|
|
assert imports[0].default_import is None
|
|
# helperFunc is imported and assigned to myFunc
|
|
assert ("helperFunc", "myFunc") in imports[0].named_imports
|
|
|
|
def test_require_property_access_same_name(self, js_analyzer):
|
|
"""Test const foo = require('./module').foo."""
|
|
code = "const helperFunc = require('./helper').helperFunc;"
|
|
imports = js_analyzer.find_imports(code)
|
|
|
|
assert len(imports) == 1
|
|
assert imports[0].module_path == "./helper"
|
|
# When var name equals property, no alias needed
|
|
assert ("helperFunc", None) in imports[0].named_imports
|
|
|
|
def test_require_external_package(self, js_analyzer):
|
|
"""Test require for external packages."""
|
|
code = "const lodash = require('lodash');"
|
|
imports = js_analyzer.find_imports(code)
|
|
|
|
assert len(imports) == 1
|
|
assert imports[0].module_path == "lodash"
|
|
assert imports[0].default_import == "lodash"
|
|
|
|
def test_require_side_effect_import(self, js_analyzer):
|
|
"""Test require('./module') without assignment."""
|
|
code = "require('./side-effects');"
|
|
imports = js_analyzer.find_imports(code)
|
|
|
|
assert len(imports) == 1
|
|
assert imports[0].module_path == "./side-effects"
|
|
assert imports[0].default_import is None
|
|
assert imports[0].named_imports == []
|
|
|
|
|
|
class TestCommonJSExports:
|
|
"""Tests for CommonJS module.exports parsing."""
|
|
|
|
@pytest.fixture
|
|
def js_analyzer(self):
|
|
"""Create a JavaScript analyzer."""
|
|
from codeflash.languages.javascript.treesitter import TreeSitterAnalyzer, TreeSitterLanguage
|
|
|
|
return TreeSitterAnalyzer(TreeSitterLanguage.JAVASCRIPT)
|
|
|
|
@pytest.fixture
|
|
def ts_analyzer(self):
|
|
"""Create a TypeScript analyzer."""
|
|
from codeflash.languages.javascript.treesitter import TreeSitterAnalyzer, TreeSitterLanguage
|
|
|
|
return TreeSitterAnalyzer(TreeSitterLanguage.TYPESCRIPT)
|
|
|
|
def test_module_exports_function(self, js_analyzer):
|
|
"""Test module.exports = function() {}."""
|
|
code = "module.exports = function helper() { return 1; };"
|
|
exports = js_analyzer.find_exports(code)
|
|
|
|
assert len(exports) == 1
|
|
assert exports[0].default_export == "helper"
|
|
|
|
def test_module_exports_anonymous_function(self, js_analyzer):
|
|
"""Test module.exports = function() {} (anonymous)."""
|
|
code = "module.exports = function() { return 1; };"
|
|
exports = js_analyzer.find_exports(code)
|
|
|
|
assert len(exports) == 1
|
|
assert exports[0].default_export == "default"
|
|
|
|
def test_module_exports_arrow_function(self, js_analyzer):
|
|
"""Test module.exports = () => {}."""
|
|
code = "module.exports = () => { return 1; };"
|
|
exports = js_analyzer.find_exports(code)
|
|
|
|
assert len(exports) == 1
|
|
assert exports[0].default_export == "default"
|
|
|
|
def test_module_exports_identifier(self, js_analyzer):
|
|
"""Test module.exports = existingFunction."""
|
|
code = """
|
|
function helper() { return 1; }
|
|
module.exports = helper;
|
|
"""
|
|
exports = js_analyzer.find_exports(code)
|
|
|
|
assert len(exports) == 1
|
|
assert exports[0].default_export == "helper"
|
|
|
|
def test_module_exports_object(self, js_analyzer):
|
|
"""Test module.exports = { foo, bar }."""
|
|
code = """
|
|
function foo() {}
|
|
function bar() {}
|
|
module.exports = { foo, bar };
|
|
"""
|
|
exports = js_analyzer.find_exports(code)
|
|
|
|
# Should find the module.exports object
|
|
module_export = [e for e in exports if e.exported_names]
|
|
assert len(module_export) == 1
|
|
assert ("foo", None) in module_export[0].exported_names
|
|
assert ("bar", None) in module_export[0].exported_names
|
|
|
|
def test_module_exports_object_with_rename(self, js_analyzer):
|
|
"""Test module.exports = { publicName: localFunc }."""
|
|
code = """
|
|
function helper() {}
|
|
module.exports = { publicHelper: helper };
|
|
"""
|
|
exports = js_analyzer.find_exports(code)
|
|
|
|
module_export = [e for e in exports if e.exported_names]
|
|
assert len(module_export) == 1
|
|
# helper is exported as publicHelper
|
|
assert ("helper", "publicHelper") in module_export[0].exported_names
|
|
|
|
def test_module_exports_property(self, js_analyzer):
|
|
"""Test module.exports.foo = function() {}."""
|
|
code = "module.exports.helper = function() { return 1; };"
|
|
exports = js_analyzer.find_exports(code)
|
|
|
|
assert len(exports) == 1
|
|
assert ("helper", None) in exports[0].exported_names
|
|
|
|
def test_exports_property(self, js_analyzer):
|
|
"""Test exports.foo = function() {}."""
|
|
code = "exports.helper = function() { return 1; };"
|
|
exports = js_analyzer.find_exports(code)
|
|
|
|
assert len(exports) == 1
|
|
assert ("helper", None) in exports[0].exported_names
|
|
|
|
def test_module_exports_require_reexport(self, js_analyzer):
|
|
"""Test module.exports = require('./other')."""
|
|
code = "module.exports = require('./other');"
|
|
exports = js_analyzer.find_exports(code)
|
|
|
|
assert len(exports) == 1
|
|
assert exports[0].is_reexport is True
|
|
assert exports[0].reexport_source == "./other"
|
|
|
|
def test_is_function_exported_commonjs(self, js_analyzer):
|
|
"""Test is_function_exported works with CommonJS exports."""
|
|
code = """
|
|
function helper() { return 1; }
|
|
module.exports = { helper };
|
|
"""
|
|
is_exported, export_name = js_analyzer.is_function_exported(code, "helper")
|
|
|
|
assert is_exported is True
|
|
assert export_name == "helper"
|
|
|
|
def test_is_function_exported_commonjs_property(self, js_analyzer):
|
|
"""Test is_function_exported with exports.foo pattern."""
|
|
code = """
|
|
function helper() { return 1; }
|
|
exports.helper = helper;
|
|
"""
|
|
is_exported, export_name = js_analyzer.is_function_exported(code, "helper")
|
|
|
|
assert is_exported is True
|
|
assert export_name == "helper"
|
|
|
|
def test_is_class_method_exported_via_class(self, ts_analyzer):
|
|
"""Test is_function_exported returns True for method of exported class."""
|
|
code = """
|
|
export class BloomFilter {
|
|
getHashValues(key: string): number[] {
|
|
return [1, 2, 3];
|
|
}
|
|
}
|
|
"""
|
|
# Method itself is not directly exported
|
|
is_exported, export_name = ts_analyzer.is_function_exported(code, "getHashValues")
|
|
assert is_exported is False
|
|
assert export_name is None
|
|
|
|
# But when we pass the class name, it should find the class export
|
|
is_exported, export_name = ts_analyzer.is_function_exported(code, "getHashValues", "BloomFilter")
|
|
assert is_exported is True
|
|
assert export_name == "BloomFilter"
|
|
|
|
def test_is_class_method_exported_default_class(self, ts_analyzer):
|
|
"""Test is_function_exported returns True for method of default exported class."""
|
|
code = """
|
|
export default class Calculator {
|
|
add(a: number, b: number): number {
|
|
return a + b;
|
|
}
|
|
}
|
|
"""
|
|
# When we pass the class name, it should find the default export
|
|
is_exported, export_name = ts_analyzer.is_function_exported(code, "add", "Calculator")
|
|
assert is_exported is True
|
|
assert export_name == "Calculator"
|
|
|
|
def test_is_class_method_not_exported_non_exported_class(self, ts_analyzer):
|
|
"""Test is_function_exported returns False for method of non-exported class."""
|
|
code = """
|
|
class InternalClass {
|
|
helper(): void {}
|
|
}
|
|
"""
|
|
# Even with class name, non-exported class method should not be exported
|
|
is_exported, export_name = ts_analyzer.is_function_exported(code, "helper", "InternalClass")
|
|
assert is_exported is False
|
|
assert export_name is None
|
|
|
|
|
|
class TestCommonJSImportResolver:
|
|
"""Tests for ImportResolver with CommonJS require() imports."""
|
|
|
|
@pytest.fixture
|
|
def project_root(self, tmp_path):
|
|
"""Create a temporary project structure with CommonJS files."""
|
|
src_dir = tmp_path / "src"
|
|
src_dir.mkdir()
|
|
|
|
# Create CommonJS module files
|
|
(src_dir / "main.js").write_text("""
|
|
const helper = require('./helper');
|
|
const { add, subtract } = require('./math');
|
|
|
|
function main() {
|
|
return helper.process() + add(1, 2);
|
|
}
|
|
|
|
module.exports = main;
|
|
""")
|
|
|
|
(src_dir / "helper.js").write_text("""
|
|
function process() {
|
|
return 42;
|
|
}
|
|
|
|
module.exports = { process };
|
|
""")
|
|
|
|
(src_dir / "math.js").write_text("""
|
|
function add(a, b) { return a + b; }
|
|
function subtract(a, b) { return a - b; }
|
|
|
|
module.exports = { add, subtract };
|
|
""")
|
|
|
|
return tmp_path
|
|
|
|
@pytest.fixture
|
|
def resolver(self, project_root):
|
|
"""Create an ImportResolver for the project."""
|
|
return ImportResolver(project_root)
|
|
|
|
def test_resolve_commonjs_default_require(self, resolver, project_root):
|
|
"""Test resolving const foo = require('./module')."""
|
|
source_file = project_root / "src" / "main.js"
|
|
import_info = ImportInfo(
|
|
module_path="./helper",
|
|
default_import="helper",
|
|
named_imports=[],
|
|
namespace_import=None,
|
|
is_type_only=False,
|
|
start_line=1,
|
|
end_line=1,
|
|
)
|
|
|
|
result = resolver.resolve_import(import_info, source_file)
|
|
|
|
assert result is not None
|
|
assert result.file_path == project_root / "src" / "helper.js"
|
|
|
|
def test_resolve_commonjs_destructured_require(self, resolver, project_root):
|
|
"""Test resolving const { a, b } = require('./module')."""
|
|
source_file = project_root / "src" / "main.js"
|
|
import_info = ImportInfo(
|
|
module_path="./math",
|
|
default_import=None,
|
|
named_imports=[("add", None), ("subtract", None)],
|
|
namespace_import=None,
|
|
is_type_only=False,
|
|
start_line=1,
|
|
end_line=1,
|
|
)
|
|
|
|
result = resolver.resolve_import(import_info, source_file)
|
|
|
|
assert result is not None
|
|
assert result.file_path == project_root / "src" / "math.js"
|
|
assert "add" in result.imported_names
|
|
assert "subtract" in result.imported_names
|