Skip to content

Commit 465316a

Browse files
authored
Merge pull request #1546 from codeflash-ai/follow-up-reference-graph
refactor: move Python static analysis modules to languages/python/static_analysis/
2 parents 3dabd44 + 48c30f5 commit 465316a

41 files changed

Lines changed: 875 additions & 556 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ Discovery → Ranking → Context Extraction → Test Gen + Optimization → Bas
2828
- **Tracer**: Profiling system that records function call trees and timings (`tracing/`, `tracer.py`)
2929
- **Worktree mode**: Git worktree-based parallel optimization (`--worktree` flag)
3030

31+
## PR Reviews
32+
33+
- GitHub PR comments and review feedback can be stale — they may reference issues already fixed by a later commit. Before acting on review feedback, verify it still applies to the current code. If the issue no longer exists, resolve the conversation in the GitHub UI.
34+
3135
<!-- Section below is auto-generated by `tessl install` - do not edit manually -->
3236

3337
# Agent Rules <!-- tessl-managed -->

codeflash/languages/base.py

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from pathlib import Path
1616

1717
from codeflash.discovery.functions_to_optimize import FunctionToOptimize
18-
from codeflash.models.models import FunctionSource
18+
from codeflash.models.models import FunctionSource, GeneratedTestsList, InvocationId
1919

2020
from codeflash.languages.language_enum import Language
2121
from codeflash.models.function_types import FunctionParent
@@ -538,6 +538,87 @@ def remove_test_functions(self, test_source: str, functions_to_remove: list[str]
538538
"""
539539
...
540540

541+
def postprocess_generated_tests(
542+
self, generated_tests: GeneratedTestsList, test_framework: str, project_root: Path, source_file_path: Path
543+
) -> GeneratedTestsList:
544+
"""Apply language-specific postprocessing to generated tests.
545+
546+
Args:
547+
generated_tests: Generated tests to update.
548+
test_framework: Test framework used for the project.
549+
project_root: Project root directory.
550+
source_file_path: Path to the source file under optimization.
551+
552+
Returns:
553+
Updated generated tests.
554+
555+
"""
556+
...
557+
558+
def remove_test_functions_from_generated_tests(
559+
self, generated_tests: GeneratedTestsList, functions_to_remove: list[str]
560+
) -> GeneratedTestsList:
561+
"""Remove specific test functions from generated tests.
562+
563+
Args:
564+
generated_tests: Generated tests to update.
565+
functions_to_remove: List of function names to remove.
566+
567+
Returns:
568+
Updated generated tests.
569+
570+
"""
571+
...
572+
573+
def add_runtime_comments_to_generated_tests(
574+
self,
575+
generated_tests: GeneratedTestsList,
576+
original_runtimes: dict[InvocationId, list[int]],
577+
optimized_runtimes: dict[InvocationId, list[int]],
578+
tests_project_rootdir: Path | None = None,
579+
) -> GeneratedTestsList:
580+
"""Add runtime comments to generated tests.
581+
582+
Args:
583+
generated_tests: Generated tests to update.
584+
original_runtimes: Mapping of invocation IDs to original runtimes.
585+
optimized_runtimes: Mapping of invocation IDs to optimized runtimes.
586+
tests_project_rootdir: Root directory for tests (if applicable).
587+
588+
Returns:
589+
Updated generated tests.
590+
591+
"""
592+
...
593+
594+
def add_global_declarations(self, optimized_code: str, original_source: str, module_abspath: Path) -> str:
595+
"""Add new global declarations from optimized code to original source.
596+
597+
Args:
598+
optimized_code: The optimized code that may contain new declarations.
599+
original_source: The original source code.
600+
module_abspath: Path to the module file (for parser selection).
601+
602+
Returns:
603+
Original source with new declarations added.
604+
605+
"""
606+
...
607+
608+
def extract_calling_function_source(self, source_code: str, function_name: str, ref_line: int) -> str | None:
609+
"""Extract the source code of a calling function.
610+
611+
Args:
612+
source_code: Full source code of the file.
613+
function_name: Name of the function to extract.
614+
ref_line: Line number where the reference is.
615+
616+
Returns:
617+
Source code of the function, or None if not found.
618+
619+
"""
620+
...
621+
541622
# === Test Result Comparison ===
542623

543624
def compare_test_results(
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
"""JavaScript/TypeScript code replacement helpers."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING
6+
7+
from codeflash.cli_cmds.console import logger
8+
9+
if TYPE_CHECKING:
10+
from pathlib import Path
11+
12+
from codeflash.languages.base import Language
13+
from codeflash.languages.javascript.treesitter import TreeSitterAnalyzer
14+
15+
16+
# Author: ali <mohammed18200118@gmail.com>
17+
def _add_global_declarations_for_language(
18+
optimized_code: str, original_source: str, module_abspath: Path, language: Language
19+
) -> str:
20+
"""Add new global declarations from optimized code to original source.
21+
22+
Finds module-level declarations (const, let, var, class, type, interface, enum)
23+
in the optimized code that don't exist in the original source and adds them.
24+
25+
New declarations are inserted after any existing declarations they depend on.
26+
For example, if optimized code has `const _has = FOO.bar.bind(FOO)`, and `FOO`
27+
is already declared in the original source, `_has` will be inserted after `FOO`.
28+
29+
Args:
30+
optimized_code: The optimized code that may contain new declarations.
31+
original_source: The original source code.
32+
module_abspath: Path to the module file (for parser selection).
33+
language: The language of the code.
34+
35+
Returns:
36+
Original source with new declarations added in dependency order.
37+
38+
"""
39+
from codeflash.languages.base import Language
40+
41+
if language not in (Language.JAVASCRIPT, Language.TYPESCRIPT):
42+
return original_source
43+
44+
try:
45+
from codeflash.languages.javascript.treesitter import get_analyzer_for_file
46+
47+
analyzer = get_analyzer_for_file(module_abspath)
48+
49+
original_declarations = analyzer.find_module_level_declarations(original_source)
50+
optimized_declarations = analyzer.find_module_level_declarations(optimized_code)
51+
52+
if not optimized_declarations:
53+
return original_source
54+
55+
existing_names = _get_existing_names(original_declarations, analyzer, original_source)
56+
new_declarations = _filter_new_declarations(optimized_declarations, existing_names)
57+
58+
if not new_declarations:
59+
return original_source
60+
61+
# Build a map of existing declaration names to their end lines (1-indexed)
62+
existing_decl_end_lines = {decl.name: decl.end_line for decl in original_declarations}
63+
64+
# Insert each new declaration after its dependencies
65+
result = original_source
66+
for decl in new_declarations:
67+
result = _insert_declaration_after_dependencies(
68+
result, decl, existing_decl_end_lines, analyzer, module_abspath
69+
)
70+
# Update the map with the newly inserted declaration for subsequent insertions
71+
# Re-parse to get accurate line numbers after insertion
72+
updated_declarations = analyzer.find_module_level_declarations(result)
73+
existing_decl_end_lines = {d.name: d.end_line for d in updated_declarations}
74+
75+
return result
76+
77+
except Exception as e:
78+
logger.debug(f"Error adding global declarations: {e}")
79+
return original_source
80+
81+
82+
# Author: ali <mohammed18200118@gmail.com>
83+
def _get_existing_names(original_declarations: list, analyzer: TreeSitterAnalyzer, original_source: str) -> set[str]:
84+
"""Get all names that already exist in the original source (declarations + imports)."""
85+
existing_names = {decl.name for decl in original_declarations}
86+
87+
original_imports = analyzer.find_imports(original_source)
88+
for imp in original_imports:
89+
if imp.default_import:
90+
existing_names.add(imp.default_import)
91+
for name, alias in imp.named_imports:
92+
existing_names.add(alias if alias else name)
93+
if imp.namespace_import:
94+
existing_names.add(imp.namespace_import)
95+
96+
return existing_names
97+
98+
99+
# Author: ali <mohammed18200118@gmail.com>
100+
def _filter_new_declarations(optimized_declarations: list, existing_names: set[str]) -> list:
101+
"""Filter declarations to only those that don't exist in the original source."""
102+
new_declarations = []
103+
seen_sources: set[str] = set()
104+
105+
# Sort by line number to maintain order from optimized code
106+
sorted_declarations = sorted(optimized_declarations, key=lambda d: d.start_line)
107+
108+
for decl in sorted_declarations:
109+
if decl.name not in existing_names and decl.source_code not in seen_sources:
110+
new_declarations.append(decl)
111+
seen_sources.add(decl.source_code)
112+
113+
return new_declarations
114+
115+
116+
# Author: ali <mohammed18200118@gmail.com>
117+
def _insert_declaration_after_dependencies(
118+
source: str,
119+
declaration,
120+
existing_decl_end_lines: dict[str, int],
121+
analyzer: TreeSitterAnalyzer,
122+
module_abspath: Path,
123+
) -> str:
124+
"""Insert a declaration after the last existing declaration it depends on.
125+
126+
Args:
127+
source: Current source code.
128+
declaration: The declaration to insert.
129+
existing_decl_end_lines: Map of existing declaration names to their end lines.
130+
analyzer: TreeSitter analyzer.
131+
module_abspath: Path to the module file.
132+
133+
Returns:
134+
Source code with the declaration inserted at the correct position.
135+
136+
"""
137+
# Find identifiers referenced in this declaration
138+
referenced_names = analyzer.find_referenced_identifiers(declaration.source_code)
139+
140+
# Find the latest end line among all referenced declarations
141+
insertion_line = _find_insertion_line_for_declaration(source, referenced_names, existing_decl_end_lines, analyzer)
142+
143+
lines = source.splitlines(keepends=True)
144+
145+
# Ensure proper spacing
146+
decl_code = declaration.source_code
147+
if not decl_code.endswith("\n"):
148+
decl_code += "\n"
149+
150+
# Add blank line before if inserting after content
151+
if insertion_line > 0 and lines[insertion_line - 1].strip():
152+
decl_code = "\n" + decl_code
153+
154+
before = lines[:insertion_line]
155+
after = lines[insertion_line:]
156+
157+
return "".join([*before, decl_code, *after])
158+
159+
160+
# Author: ali <mohammed18200118@gmail.com>
161+
def _find_insertion_line_for_declaration(
162+
source: str, referenced_names: set[str], existing_decl_end_lines: dict[str, int], analyzer: TreeSitterAnalyzer
163+
) -> int:
164+
"""Find the line where a declaration should be inserted based on its dependencies.
165+
166+
Args:
167+
source: Source code.
168+
referenced_names: Names referenced by the declaration.
169+
existing_decl_end_lines: Map of declaration names to their end lines (1-indexed).
170+
analyzer: TreeSitter analyzer.
171+
172+
Returns:
173+
Line index (0-based) where the declaration should be inserted.
174+
175+
"""
176+
# Find the maximum end line among referenced declarations
177+
max_dependency_line = 0
178+
for name in referenced_names:
179+
if name in existing_decl_end_lines:
180+
max_dependency_line = max(max_dependency_line, existing_decl_end_lines[name])
181+
182+
if max_dependency_line > 0:
183+
# Insert after the last dependency (end_line is 1-indexed, we need 0-indexed)
184+
return max_dependency_line
185+
186+
# No dependencies found - insert after imports
187+
lines = source.splitlines(keepends=True)
188+
return _find_line_after_imports(lines, analyzer, source)
189+
190+
191+
# Author: ali <mohammed18200118@gmail.com>
192+
def _find_line_after_imports(lines: list[str], analyzer: TreeSitterAnalyzer, source: str) -> int:
193+
"""Find the line index after all imports.
194+
195+
Args:
196+
lines: Source lines.
197+
analyzer: TreeSitter analyzer.
198+
source: Full source code.
199+
200+
Returns:
201+
Line index (0-based) for insertion after imports.
202+
203+
"""
204+
try:
205+
imports = analyzer.find_imports(source)
206+
if imports:
207+
return max(imp.end_line for imp in imports)
208+
except Exception as exc:
209+
logger.debug(f"Exception in _find_line_after_imports: {exc}")
210+
211+
# Default: insert at beginning (after shebang/directive comments)
212+
for i, line in enumerate(lines):
213+
stripped = line.strip()
214+
if stripped and not stripped.startswith("//") and not stripped.startswith("#!"):
215+
return i
216+
217+
return 0

0 commit comments

Comments
 (0)