codeflash/tests/test_languages/test_find_references.py
Kevin Turcios eceac13fc3 Merge remote-tracking branch 'origin/main' into omni-java
# 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
2026-03-04 01:52:32 -05:00

1107 lines
39 KiB
Python

"""Comprehensive tests for JavaScript/TypeScript find references functionality.
These tests are inspired by real-world patterns found in the Appsmith codebase,
covering various import/export patterns, callback usage, memoization, and more.
Each test verifies:
1. The actual reference values (file, line, column, type, caller)
2. The formatted markdown output from _format_references_as_markdown
"""
from __future__ import annotations
from pathlib import Path
import pytest
from codeflash.discovery.functions_to_optimize import FunctionToOptimize
from codeflash.languages.base import Language, ReferenceInfo
from codeflash.languages.javascript.find_references import (
ExportedFunction,
Reference,
ReferenceFinder,
ReferenceSearchContext,
find_references,
)
from codeflash.languages.python.static_analysis.code_extractor import _format_references_as_markdown
from codeflash.models.models import FunctionParent
def make_func(name: str, file_path: Path, class_name: str | None = None) -> FunctionToOptimize:
"""Helper to create FunctionToOptimize for testing."""
parents = [FunctionParent(name=class_name, type="ClassDef")] if class_name else []
return FunctionToOptimize(function_name=name, file_path=file_path, parents=parents, language="javascript")
class TestReferenceFinder:
"""Tests for ReferenceFinder class."""
@pytest.fixture
def project_root(self, tmp_path):
"""Create a basic project structure."""
src_dir = tmp_path / "src"
src_dir.mkdir()
return tmp_path
@pytest.fixture
def finder(self, project_root):
"""Create a ReferenceFinder instance."""
return ReferenceFinder(project_root)
def test_init_default_exclude_patterns(self, project_root):
"""Test that default exclude patterns are set."""
finder = ReferenceFinder(project_root)
assert "node_modules" in finder.exclude_patterns
assert "dist" in finder.exclude_patterns
assert ".git" in finder.exclude_patterns
def test_init_custom_exclude_patterns(self, project_root):
"""Test custom exclude patterns."""
finder = ReferenceFinder(project_root, exclude_patterns=["custom_dir"])
assert "custom_dir" in finder.exclude_patterns
assert "node_modules" not in finder.exclude_patterns
def test_should_exclude_node_modules(self, finder, project_root):
"""Test that node_modules files are excluded."""
path = project_root / "node_modules" / "lodash" / "index.js"
assert finder._should_exclude(path) is True
def test_should_not_exclude_src(self, finder, project_root):
"""Test that src files are not excluded."""
path = project_root / "src" / "utils.ts"
assert finder._should_exclude(path) is False
class TestBasicNamedExports:
"""Tests for basic named export/import patterns.
Inspired by Appsmith patterns like:
import { getDynamicBindings, isDynamicValue } from "utils/DynamicBindingUtils";
"""
@pytest.fixture
def project_root(self, tmp_path):
"""Create project with named export pattern."""
src_dir = tmp_path / "src"
src_dir.mkdir()
utils_dir = src_dir / "utils"
utils_dir.mkdir()
# Source file with named export
(utils_dir / "DynamicBindingUtils.ts").write_text(
"export function getDynamicBindings(value: string): string[] {\n"
" const regex = /{{([^}]+)}}/g;\n"
" return [];\n"
"}\n"
)
# File that imports and uses the function
(src_dir / "evaluator.ts").write_text(
"import { getDynamicBindings } from './utils/DynamicBindingUtils';\n"
"\n"
"export function evaluate(expression: string) {\n"
" const bindings = getDynamicBindings(expression);\n"
" return bindings;\n"
"}\n"
)
# Another file that uses the function
(src_dir / "validator.ts").write_text(
"import { getDynamicBindings } from './utils/DynamicBindingUtils';\n"
"\n"
"export function validateBindings(input: string) {\n"
" const bindings = getDynamicBindings(input);\n"
" return bindings.length > 0;\n"
"}\n"
)
return tmp_path
def test_find_named_export_references_values(self, project_root):
"""Test finding references to a named exported function with exact values."""
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "utils" / "DynamicBindingUtils.ts"
refs = finder.find_references(make_func("getDynamicBindings", source_file))
# Sort refs by file path for consistent ordering
refs_sorted = sorted(refs, key=lambda r: (str(r.file_path), r.line))
# Should find 2 references
assert len(refs_sorted) == 2
# Check evaluator.ts reference
eval_ref = next(r for r in refs_sorted if "evaluator.ts" in str(r.file_path))
assert eval_ref.line == 4
assert eval_ref.reference_type == "call"
assert eval_ref.caller_function == "evaluate"
assert eval_ref.import_name == "getDynamicBindings"
assert "getDynamicBindings(expression)" in eval_ref.context
# Check validator.ts reference
val_ref = next(r for r in refs_sorted if "validator.ts" in str(r.file_path))
assert val_ref.line == 4
assert val_ref.reference_type == "call"
assert val_ref.caller_function == "validateBindings"
assert val_ref.import_name == "getDynamicBindings"
assert "getDynamicBindings(input)" in val_ref.context
def test_format_references_as_markdown_named_exports(self, project_root):
"""Test _format_references_as_markdown output for named exports."""
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "utils" / "DynamicBindingUtils.ts"
refs = finder.find_references(make_func("getDynamicBindings", source_file))
# Convert to ReferenceInfo and sort for consistent ordering
ref_infos = sorted(
[
ReferenceInfo(
file_path=r.file_path,
line=r.line,
column=r.column,
end_line=r.end_line,
end_column=r.end_column,
context=r.context,
reference_type=r.reference_type,
import_name=r.import_name,
caller_function=r.caller_function,
)
for r in refs
],
key=lambda r: str(r.file_path),
)
markdown = _format_references_as_markdown(ref_infos, source_file, project_root, Language.TYPESCRIPT)
expected_markdown = (
"```typescript:src/evaluator.ts\n"
"function evaluate(expression: string) {\n"
" const bindings = getDynamicBindings(expression);\n"
" return bindings;\n"
"}\n"
"```\n"
"```typescript:src/validator.ts\n"
"function validateBindings(input: string) {\n"
" const bindings = getDynamicBindings(input);\n"
" return bindings.length > 0;\n"
"}\n"
"```\n"
)
assert markdown == expected_markdown
class TestDefaultExports:
"""Tests for default export/import patterns."""
@pytest.fixture
def project_root(self, tmp_path):
"""Create project with default export pattern."""
src_dir = tmp_path / "src"
src_dir.mkdir()
# Source file with default export
(src_dir / "helper.ts").write_text(
"function processData(data: any[]) {\n"
" return data.filter(item => item.active);\n"
"}\n"
"\n"
"export default processData;\n"
)
# File that imports the default export
(src_dir / "main.ts").write_text(
"import processData from './helper';\n"
"\n"
"export function handleData(items: any[]) {\n"
" const processed = processData(items);\n"
" return processed.length;\n"
"}\n"
)
# File that imports with a different name
(src_dir / "alternative.ts").write_text(
"import myProcessor from './helper';\n"
"\n"
"export function process(items: any[]) {\n"
" return myProcessor(items);\n"
"}\n"
)
return tmp_path
def test_find_default_export_references_values(self, project_root):
"""Test finding references to a default exported function with exact values."""
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "helper.ts"
refs = finder.find_references(make_func("processData", source_file))
# Should find references in both files
ref_files = {str(ref.file_path) for ref in refs}
assert any("main.ts" in f for f in ref_files)
assert any("alternative.ts" in f for f in ref_files)
# Check main.ts reference (uses original name)
main_ref = next(r for r in refs if "main.ts" in str(r.file_path))
assert main_ref.line == 4
assert main_ref.reference_type == "call"
assert main_ref.caller_function == "handleData"
assert main_ref.import_name == "processData"
# Check alternative.ts reference (uses alias)
alt_ref = next(r for r in refs if "alternative.ts" in str(r.file_path))
assert alt_ref.line == 4
assert alt_ref.reference_type == "call"
assert alt_ref.caller_function == "process"
assert alt_ref.import_name == "myProcessor"
def test_format_references_as_markdown_default_exports(self, project_root):
"""Test markdown output for default exports with aliases."""
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "helper.ts"
refs = finder.find_references(make_func("processData", source_file))
ref_infos = sorted(
[
ReferenceInfo(
file_path=r.file_path,
line=r.line,
column=r.column,
end_line=r.end_line,
end_column=r.end_column,
context=r.context,
reference_type=r.reference_type,
import_name=r.import_name,
caller_function=r.caller_function,
)
for r in refs
],
key=lambda r: str(r.file_path),
)
markdown = _format_references_as_markdown(ref_infos, source_file, project_root, Language.TYPESCRIPT)
expected_markdown = (
"```typescript:src/alternative.ts\n"
"function process(items: any[]) {\n"
" return myProcessor(items);\n"
"}\n"
"```\n"
"```typescript:src/main.ts\n"
"function handleData(items: any[]) {\n"
" const processed = processData(items);\n"
" return processed.length;\n"
"}\n"
"```\n"
)
assert markdown == expected_markdown
class TestReExports:
"""Tests for re-export patterns."""
@pytest.fixture
def project_root(self, tmp_path):
"""Create project with re-export pattern."""
src_dir = tmp_path / "src"
src_dir.mkdir()
utils_dir = src_dir / "utils"
utils_dir.mkdir()
# Original function file
(utils_dir / "filterUtils.ts").write_text(
"export function filterBySearchTerm(items: any[], term: string) {\n"
" return items.filter(i => i.name.includes(term));\n"
"}\n"
)
# Index file that re-exports
(utils_dir / "index.ts").write_text("export { filterBySearchTerm } from './filterUtils';\n")
# Consumer that imports from index
(src_dir / "consumer.ts").write_text(
"import { filterBySearchTerm } from './utils';\n"
"\n"
"export function searchItems(items: any[], query: string) {\n"
" return filterBySearchTerm(items, query);\n"
"}\n"
)
return tmp_path
def test_find_reexport_reference_values(self, project_root):
"""Test finding re-export references with exact values."""
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "utils" / "filterUtils.ts"
refs = finder.find_references(make_func("filterBySearchTerm", source_file))
# Should find re-export in index.ts
reexport_refs = [r for r in refs if r.reference_type == "reexport"]
assert len(reexport_refs) == 1
assert "index.ts" in str(reexport_refs[0].file_path)
assert reexport_refs[0].import_name == "filterBySearchTerm"
# Should find call in consumer.ts (through re-export chain)
call_refs = [r for r in refs if r.reference_type == "call"]
assert len(call_refs) >= 1
consumer_ref = next((r for r in call_refs if "consumer.ts" in str(r.file_path)), None)
assert consumer_ref is not None
assert consumer_ref.line == 4
assert consumer_ref.caller_function == "searchItems"
def test_format_references_as_markdown_reexports(self, project_root):
"""Test markdown output for re-exports."""
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "utils" / "filterUtils.ts"
refs = finder.find_references(make_func("filterBySearchTerm", source_file))
ref_infos = sorted(
[
ReferenceInfo(
file_path=r.file_path,
line=r.line,
column=r.column,
end_line=r.end_line,
end_column=r.end_column,
context=r.context,
reference_type=r.reference_type,
import_name=r.import_name,
caller_function=r.caller_function,
)
for r in refs
],
key=lambda r: str(r.file_path),
)
markdown = _format_references_as_markdown(ref_infos, source_file, project_root, Language.TYPESCRIPT)
expected_markdown = (
"```typescript:src/consumer.ts\n"
"function searchItems(items: any[], query: string) {\n"
" return filterBySearchTerm(items, query);\n"
"}\n"
"```\n"
"```typescript:src/utils/index.ts\n"
"export { filterBySearchTerm } from './filterUtils';\n"
"```\n"
)
assert markdown == expected_markdown
class TestCallbackPatterns:
"""Tests for functions passed as callbacks."""
@pytest.fixture
def project_root(self, tmp_path):
"""Create project with callback patterns."""
src_dir = tmp_path / "src"
src_dir.mkdir()
# Helper function
(src_dir / "transforms.ts").write_text(
"export function normalizeItem(item: any) {\n return { ...item, normalized: true };\n}\n"
)
# Consumer using callbacks
(src_dir / "processor.ts").write_text(
"import { normalizeItem } from './transforms';\n"
"\n"
"export function processItems(items: any[]) {\n"
" const normalized = items.map(normalizeItem);\n"
" return normalized;\n"
"}\n"
)
return tmp_path
def test_find_callback_references_values(self, project_root):
"""Test finding functions used as callbacks with exact values."""
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "transforms.ts"
refs = finder.find_references(make_func("normalizeItem", source_file))
# Should find the callback reference
callback_refs = [r for r in refs if r.reference_type == "callback"]
assert len(callback_refs) >= 1
callback_ref = callback_refs[0]
assert "processor.ts" in str(callback_ref.file_path)
assert callback_ref.line == 4
assert callback_ref.caller_function == "processItems"
assert "items.map(normalizeItem)" in callback_ref.context
def test_format_references_as_markdown_callbacks(self, project_root):
"""Test markdown output for callback patterns."""
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "transforms.ts"
refs = finder.find_references(make_func("normalizeItem", source_file))
ref_infos = [
ReferenceInfo(
file_path=r.file_path,
line=r.line,
column=r.column,
end_line=r.end_line,
end_column=r.end_column,
context=r.context,
reference_type=r.reference_type,
import_name=r.import_name,
caller_function=r.caller_function,
)
for r in refs
]
markdown = _format_references_as_markdown(ref_infos, source_file, project_root, Language.TYPESCRIPT)
expected_markdown = (
"```typescript:src/processor.ts\n"
"function processItems(items: any[]) {\n"
" const normalized = items.map(normalizeItem);\n"
" return normalized;\n"
"}\n"
"```\n"
)
assert expected_markdown == markdown
class TestAliasImports:
"""Tests for functions imported with aliases."""
@pytest.fixture
def project_root(self, tmp_path):
"""Create project with alias import patterns."""
src_dir = tmp_path / "src"
src_dir.mkdir()
# Source file
(src_dir / "utils.ts").write_text(
"export function computeValue(input: number): number {\n return input * 2;\n}\n"
)
# File using alias
(src_dir / "consumer.ts").write_text(
"import { computeValue as calculate } from './utils';\n"
"\n"
"export function processNumber(n: number) {\n"
" const result = calculate(n);\n"
" return result + 10;\n"
"}\n"
)
return tmp_path
def test_find_aliased_import_references_values(self, project_root):
"""Test finding references when function is imported with alias."""
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "utils.ts"
refs = finder.find_references(make_func("computeValue", source_file))
# Should find the reference even though it's called as "calculate"
assert len(refs) == 1
ref = refs[0]
assert "consumer.ts" in str(ref.file_path)
assert ref.line == 4
assert ref.reference_type == "call"
assert ref.import_name == "calculate" # The aliased name
assert ref.caller_function == "processNumber"
assert "calculate(n)" in ref.context
def test_format_references_as_markdown_aliases(self, project_root):
"""Test markdown output for aliased imports."""
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "utils.ts"
refs = finder.find_references(make_func("computeValue", source_file))
ref_infos = [
ReferenceInfo(
file_path=r.file_path,
line=r.line,
column=r.column,
end_line=r.end_line,
end_column=r.end_column,
context=r.context,
reference_type=r.reference_type,
import_name=r.import_name,
caller_function=r.caller_function,
)
for r in refs
]
markdown = _format_references_as_markdown(ref_infos, source_file, project_root, Language.TYPESCRIPT)
expected_markdown = (
"```typescript:src/consumer.ts\n"
"function processNumber(n: number) {\n"
" const result = calculate(n);\n"
" return result + 10;\n"
"}\n"
"```\n"
)
assert expected_markdown == markdown
class TestNamespaceImports:
"""Tests for namespace import patterns."""
@pytest.fixture
def project_root(self, tmp_path):
"""Create project with namespace import patterns."""
src_dir = tmp_path / "src"
src_dir.mkdir()
# Source file with multiple exports
(src_dir / "mathUtils.ts").write_text(
"export function add(a: number, b: number): number {\n return a + b;\n}\n"
)
# File using namespace import
(src_dir / "calculator.ts").write_text(
"import * as MathUtils from './mathUtils';\n"
"\n"
"export function calculate(a: number, b: number) {\n"
" return MathUtils.add(a, b);\n"
"}\n"
)
return tmp_path
def test_find_namespace_import_references_values(self, project_root):
"""Test finding references via namespace imports with exact values."""
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "mathUtils.ts"
refs = finder.find_references(make_func("add", source_file))
assert len(refs) == 1
ref = refs[0]
assert "calculator.ts" in str(ref.file_path)
assert ref.line == 4
assert ref.reference_type == "call"
assert ref.import_name == "MathUtils.add"
assert ref.caller_function == "calculate"
assert "MathUtils.add(a, b)" in ref.context
def test_format_references_as_markdown_namespace(self, project_root):
"""Test markdown output for namespace imports."""
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "mathUtils.ts"
refs = finder.find_references(make_func("add", source_file))
ref_infos = [
ReferenceInfo(
file_path=r.file_path,
line=r.line,
column=r.column,
end_line=r.end_line,
end_column=r.end_column,
context=r.context,
reference_type=r.reference_type,
import_name=r.import_name,
caller_function=r.caller_function,
)
for r in refs
]
markdown = _format_references_as_markdown(ref_infos, source_file, project_root, Language.TYPESCRIPT)
expected_markdown = (
"```typescript:src/calculator.ts\n"
"function calculate(a: number, b: number) {\n"
" return MathUtils.add(a, b);\n"
"}\n"
"```\n"
)
assert expected_markdown == markdown
class TestMemoizedFunctions:
"""Tests for memoized function patterns."""
@pytest.fixture
def project_root(self, tmp_path):
"""Create project with memoized function patterns."""
src_dir = tmp_path / "src"
src_dir.mkdir()
# Source file with function to be memoized
(src_dir / "expensive.ts").write_text(
"export function computeExpensive(x: number): number {\n return x * x;\n}\n"
)
# File that memoizes the function
(src_dir / "memoized.ts").write_text(
"import memoize from 'micro-memoize';\n"
"import { computeExpensive } from './expensive';\n"
"\n"
"export const memoizedCompute = memoize(computeExpensive);\n"
"\n"
"export function process(x: number) {\n"
" return computeExpensive(x) + memoizedCompute(x);\n"
"}\n"
)
return tmp_path
def test_find_memoized_function_references_values(self, project_root):
"""Test finding references to functions passed to memoize."""
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "expensive.ts"
refs = finder.find_references(make_func("computeExpensive", source_file))
# Should find memoize call and direct call
assert len(refs) >= 2
# Check for memoized reference
memo_refs = [r for r in refs if r.reference_type == "memoized"]
assert len(memo_refs) >= 1
memo_ref = memo_refs[0]
assert "memoized.ts" in str(memo_ref.file_path)
assert "memoize(computeExpensive)" in memo_ref.context
# Check for direct call
call_refs = [r for r in refs if r.reference_type == "call"]
assert len(call_refs) >= 1
class TestSameFileReferences:
"""Tests for references within the same file."""
@pytest.fixture
def project_root(self, tmp_path):
"""Create project with same-file reference patterns."""
src_dir = tmp_path / "src"
src_dir.mkdir()
# File with internal references
(src_dir / "recursive.ts").write_text(
"export function factorial(n: number): number {\n"
" if (n <= 1) return 1;\n"
" return n * factorial(n - 1);\n"
"}\n"
)
return tmp_path
def test_find_recursive_references_values(self, project_root):
"""Test finding recursive calls within same file with exact values."""
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "recursive.ts"
refs = finder.find_references(make_func("factorial", source_file), include_definition=True)
# Should find the recursive call
call_refs = [r for r in refs if r.reference_type == "call"]
assert len(call_refs) >= 1
recursive_ref = call_refs[0]
assert recursive_ref.line == 3
assert recursive_ref.caller_function == "factorial"
assert "factorial(n - 1)" in recursive_ref.context
class TestComplexMultiFileScenarios:
"""Tests for complex multi-file scenarios inspired by Appsmith."""
@pytest.fixture
def project_root(self, tmp_path):
"""Create a complex multi-file project structure."""
src_dir = tmp_path / "src"
src_dir.mkdir()
(src_dir / "utils").mkdir()
(src_dir / "components").mkdir()
# Core utility function
(src_dir / "utils" / "widgetUtils.ts").write_text(
"export function isLargeWidget(type: string): boolean {\n return ['TABLE', 'LIST'].includes(type);\n}\n"
)
# Re-export from index
(src_dir / "utils" / "index.ts").write_text("export { isLargeWidget } from './widgetUtils';\n")
# Component using the function via re-export
(src_dir / "components" / "Widget.tsx").write_text(
"import { isLargeWidget } from '../utils';\n"
"\n"
"export function Widget({ type }: { type: string }) {\n"
" const isLarge = isLargeWidget(type);\n"
" return isLarge;\n"
"}\n"
)
return tmp_path
def test_find_all_references_across_codebase_values(self, project_root):
"""Test finding all references to isLargeWidget with exact values."""
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "utils" / "widgetUtils.ts"
refs = finder.find_references(make_func("isLargeWidget", source_file))
# Should find re-export in index.ts
reexport_refs = [r for r in refs if r.reference_type == "reexport"]
assert len(reexport_refs) == 1
assert "index.ts" in str(reexport_refs[0].file_path)
# Should find call in Widget.tsx
call_refs = [r for r in refs if r.reference_type == "call"]
assert len(call_refs) >= 1
widget_ref = next((r for r in call_refs if "Widget.tsx" in str(r.file_path)), None)
assert widget_ref is not None
assert widget_ref.line == 4
assert widget_ref.caller_function == "Widget"
def test_format_references_as_markdown_complex(self, project_root):
"""Test markdown output for complex multi-file scenario."""
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "utils" / "widgetUtils.ts"
refs = finder.find_references(make_func("isLargeWidget", source_file))
ref_infos = sorted(
[
ReferenceInfo(
file_path=r.file_path,
line=r.line,
column=r.column,
end_line=r.end_line,
end_column=r.end_column,
context=r.context,
reference_type=r.reference_type,
import_name=r.import_name,
caller_function=r.caller_function,
)
for r in refs
],
key=lambda r: str(r.file_path),
)
markdown = _format_references_as_markdown(ref_infos, source_file, project_root, Language.TYPESCRIPT)
expected_markdown = (
"```typescript:src/components/Widget.tsx\n"
"function Widget({ type }: { type: string }) {\n"
" const isLarge = isLargeWidget(type);\n"
" return isLarge;\n"
"}\n"
"```\n"
"```typescript:src/utils/index.ts\n"
"export { isLargeWidget } from './widgetUtils';\n"
"```\n"
)
assert markdown == expected_markdown
class TestEdgeCases:
"""Tests for edge cases and error handling."""
@pytest.fixture
def project_root(self, tmp_path):
"""Create project for edge case testing."""
src_dir = tmp_path / "src"
src_dir.mkdir()
return tmp_path
def test_nonexistent_file(self, project_root):
"""Test handling of nonexistent source file."""
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "nonexistent.ts"
refs = finder.find_references(make_func("someFunction", source_file))
assert refs == []
def test_non_exported_function(self, project_root):
"""Test handling of non-exported function."""
# Create a file with non-exported function
(project_root / "src" / "private.ts").write_text(
"function internalHelper() {\n"
" return 42;\n"
"}\n"
"\n"
"export function publicFunction() {\n"
" return internalHelper();\n"
"}\n"
)
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "private.ts"
refs = finder.find_references(make_func("internalHelper", source_file))
# Should only find internal reference
assert all(r.file_path == source_file for r in refs)
def test_empty_file(self, project_root):
"""Test handling of empty file."""
(project_root / "src" / "empty.ts").write_text("")
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "empty.ts"
refs = finder.find_references(make_func("anything", source_file))
assert refs == []
def test_format_references_empty_list(self, project_root):
"""Test _format_references_as_markdown with empty list."""
markdown = _format_references_as_markdown(
[], project_root / "src" / "file.ts", project_root, Language.TYPESCRIPT
)
assert markdown == ""
class TestCommonJSPatterns:
"""Tests for CommonJS require/module.exports patterns."""
@pytest.fixture
def project_root(self, tmp_path):
"""Create project with CommonJS patterns."""
src_dir = tmp_path / "src"
src_dir.mkdir()
# CommonJS module
(src_dir / "helpers.js").write_text(
"function processConfig(config) {\n"
" return { ...config, processed: true };\n"
"}\n"
"\n"
"module.exports = { processConfig };\n"
)
# Consumer using destructured require
(src_dir / "main.js").write_text(
"const { processConfig } = require('./helpers');\n"
"\n"
"function handleConfig(config) {\n"
" return processConfig(config);\n"
"}\n"
"\n"
"module.exports = handleConfig;\n"
)
return tmp_path
def test_find_commonjs_references_values(self, project_root):
"""Test finding CommonJS references with exact values."""
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "helpers.js"
refs = finder.find_references(make_func("processConfig", source_file))
assert len(refs) >= 1
main_ref = next((r for r in refs if "main.js" in str(r.file_path)), None)
assert main_ref is not None
assert main_ref.line == 4
assert main_ref.reference_type == "call"
assert main_ref.caller_function == "handleConfig"
def test_format_references_as_markdown_commonjs(self, project_root):
"""Test markdown output for CommonJS patterns."""
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "helpers.js"
refs = finder.find_references(make_func("processConfig", source_file))
ref_infos = sorted(
[
ReferenceInfo(
file_path=r.file_path,
line=r.line,
column=r.column,
end_line=r.end_line,
end_column=r.end_column,
context=r.context,
reference_type=r.reference_type,
import_name=r.import_name,
caller_function=r.caller_function,
)
for r in refs
],
key=lambda r: str(r.file_path),
)
markdown = _format_references_as_markdown(ref_infos, source_file, project_root, Language.JAVASCRIPT)
expected_markdown = (
"```javascript:src/main.js\nfunction handleConfig(config) {\n return processConfig(config);\n}\n```\n"
)
assert markdown == expected_markdown
class TestConvenienceFunction:
"""Tests for the find_references convenience function."""
@pytest.fixture
def project_root(self, tmp_path):
"""Create a simple project."""
src_dir = tmp_path / "src"
src_dir.mkdir()
(src_dir / "utils.ts").write_text("export function helper() {\n return 42;\n}\n")
(src_dir / "main.ts").write_text(
"import { helper } from './utils';\n\nexport function main() {\n return helper();\n}\n"
)
return tmp_path
def test_find_references_function_values(self, project_root):
"""Test the find_references convenience function with exact values."""
source_file = project_root / "src" / "utils.ts"
refs = find_references(make_func("helper", source_file), project_root=project_root)
assert len(refs) == 1
ref = refs[0]
assert "main.ts" in str(ref.file_path)
assert ref.line == 4
assert ref.reference_type == "call"
assert ref.caller_function == "main"
class TestReferenceDataclass:
"""Tests for Reference dataclass."""
def test_reference_creation(self, tmp_path):
"""Test creating a Reference object."""
ref = Reference(
file_path=tmp_path / "test.ts",
line=10,
column=5,
end_line=10,
end_column=15,
context="const result = myFunction();",
reference_type="call",
import_name="myFunction",
caller_function="processData",
)
assert ref.line == 10
assert ref.column == 5
assert ref.end_line == 10
assert ref.end_column == 15
assert ref.reference_type == "call"
assert ref.import_name == "myFunction"
assert ref.caller_function == "processData"
assert ref.context == "const result = myFunction();"
def test_reference_without_caller(self, tmp_path):
"""Test Reference with no caller function."""
ref = Reference(
file_path=tmp_path / "test.ts",
line=1,
column=0,
end_line=1,
end_column=10,
context="export { fn } from './module';",
reference_type="reexport",
import_name="fn",
)
assert ref.caller_function is None
class TestExportedFunctionDataclass:
"""Tests for ExportedFunction dataclass."""
def test_exported_function_named(self, tmp_path):
"""Test ExportedFunction for named export."""
exp = ExportedFunction(
function_name="myHelper", export_name="myHelper", is_default=False, file_path=tmp_path / "utils.ts"
)
assert exp.function_name == "myHelper"
assert exp.export_name == "myHelper"
assert exp.is_default is False
assert exp.file_path == tmp_path / "utils.ts"
def test_exported_function_default(self, tmp_path):
"""Test ExportedFunction for default export."""
exp = ExportedFunction(
function_name="processData", export_name="default", is_default=True, file_path=tmp_path / "processor.ts"
)
assert exp.function_name == "processData"
assert exp.is_default is True
assert exp.export_name == "default"
class TestReferenceSearchContext:
"""Tests for ReferenceSearchContext dataclass."""
def test_context_defaults(self):
"""Test default values for ReferenceSearchContext."""
ctx = ReferenceSearchContext()
assert ctx.visited_files == set()
assert ctx.max_files == 1000
def test_context_custom_max_files(self):
"""Test custom max_files value."""
ctx = ReferenceSearchContext(max_files=500)
assert ctx.max_files == 500
class TestEdgeCasesAdvanced:
"""Advanced edge case tests to catch potential failures."""
@pytest.fixture
def project_root(self, tmp_path):
"""Create project for edge case testing."""
src_dir = tmp_path / "src"
src_dir.mkdir()
return tmp_path
def test_circular_import_handling(self, project_root):
"""Test that circular imports don't cause infinite loops."""
src_dir = project_root / "src"
# Create circular import structure
(src_dir / "a.ts").write_text(
"import { funcB } from './b';\n\nexport function funcA() {\n return funcB() + 1;\n}\n"
)
(src_dir / "b.ts").write_text(
"import { funcA } from './a';\n"
"\n"
"export function funcB() {\n"
" return 42;\n"
"}\n"
"\n"
"export function callsA() {\n"
" return funcA();\n"
"}\n"
)
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "a.ts"
# Should not hang or crash
refs = finder.find_references(make_func("funcA", source_file))
# Should find reference in b.ts
b_refs = [r for r in refs if "b.ts" in str(r.file_path)]
assert len(b_refs) >= 1
assert b_refs[0].caller_function == "callsA"
def test_syntax_error_graceful_handling(self, project_root):
"""Test that syntax errors in files are handled gracefully."""
src_dir = project_root / "src"
(src_dir / "valid.ts").write_text("export function validFunction() {\n return 42;\n}\n")
# Create a file with syntax error
(src_dir / "invalid.ts").write_text(
"import { validFunction } from './valid';\n\nexport function broken( {\n return validFunction(\n}\n"
)
finder = ReferenceFinder(project_root)
source_file = project_root / "src" / "valid.ts"
# Should not crash
refs = finder.find_references(make_func("validFunction", source_file))
assert isinstance(refs, list)