Skip to content

Commit 11e6952

Browse files
committed
fix data collection code
1 parent 462a7a7 commit 11e6952

3 files changed

Lines changed: 974 additions & 143 deletions

File tree

testexplora/build_benchmark/build_dependency_graph.py

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import networkx as nx
1010
from matplotlib.lines import Line2D
1111
import networkx as nx
12+
import logging
1213

1314
VERSION = 'v2.3'
1415
NODE_TYPE_DIRECTORY = 'directory'
@@ -33,14 +34,18 @@
3334
EDGE_TYPE_IMPORTS,
3435
]
3536

36-
SKIP_DIRS = ['.github', '.git']
37+
SKIP_DIRS = [
38+
'.git', '.github', '__pycache__', '.mypy_cache', '.pytest_cache', '.tox',
39+
'build', 'dist', '.eggs', 'node_modules', '.venv', 'venv', 'env',
40+
'.idea', '.vscode', '.direnv', '.pytest', '.coverage', 'docs'
41+
]
42+
3743

3844

3945
def is_skip_dir(dirname):
40-
for skip_dir in SKIP_DIRS:
41-
if skip_dir in dirname:
42-
return True
43-
return False
46+
parts = set(dirname.split(os.sep))
47+
return any(skip in parts for skip in SKIP_DIRS)
48+
4449

4550

4651
def handle_edge_cases(code):
@@ -124,8 +129,9 @@ def find_imports(filepath, repo_path, tree=None):
124129

125130

126131
class CodeAnalyzer(ast.NodeVisitor):
127-
def __init__(self, filename):
132+
def __init__(self, filename, code):
128133
self.filename = filename
134+
self.code = code
129135
self.nodes = []
130136
self.node_name_stack = []
131137
self.node_type_stack = []
@@ -184,12 +190,12 @@ def _visit_func(self, node):
184190
self.node_type_stack.pop()
185191

186192
def _get_source_segment(self, node):
187-
with open(self.filename, 'r') as file:
188-
source_code = file.read()
189-
return ast.get_source_segment(source_code, node)
193+
# with open(self.filename, 'r') as file:
194+
# source_code = file.read()
195+
return ast.get_source_segment(self.code, node)
190196

191197

192-
# Parse the specified file, using CodeAnalyzer to analyze classes and top-level functions
198+
# 解析指定文件,使用CodeAnalyzer分析文件中的类和顶级函数
193199
def analyze_file(filepath):
194200
with open(filepath, 'r') as file:
195201
code = file.read()
@@ -198,7 +204,7 @@ def analyze_file(filepath):
198204
tree = ast.parse(code, filename=filepath)
199205
except:
200206
raise SyntaxError
201-
analyzer = CodeAnalyzer(filepath)
207+
analyzer = CodeAnalyzer(filepath, code)
202208
try:
203209
analyzer.visit(tree)
204210
except RecursionError:
@@ -331,7 +337,7 @@ def resolve_symlink(file_path):
331337
# return file_content
332338

333339

334-
# Traverse all Python files under repo_path, building a dependency graph of files, classes, and functions
340+
# 遍历repo_path下的所有Python文件,构建文件、类和函数的依赖关系图
335341
def build_graph(repo_path, fuzzy_search=True, global_import=False):
336342
graph = nx.MultiDiGraph()
337343
file_nodes = {}
@@ -386,7 +392,7 @@ def build_graph(repo_path, fuzzy_search=True, global_import=False):
386392
else:
387393
with open(file_path, 'r') as f:
388394
file_content = f.read()
389-
395+
logging.debug(f'Analyzing file: {file_path}')
390396
graph.add_node(filename, type=NODE_TYPE_FILE, code=file_content)
391397
file_nodes[filename] = file_path
392398

@@ -516,10 +522,9 @@ def build_graph(repo_path, fuzzy_search=True, global_import=False):
516522

517523
def build_json_graph(repo_path, fuzzy_search=True, global_import=False):
518524
graph = build_graph(repo_path, fuzzy_search, global_import)
519-
temp_graph_dict = nx.node_link_data(graph, edges="links")
525+
temp_graph_dict = nx.node_link_data(graph)
520526
codes = {}
521527
folders = {}
522-
523528
nodes = temp_graph_dict['nodes']
524529
edges = temp_graph_dict['links']
525530
edge_dic = {}
@@ -718,9 +723,9 @@ def process_inheritance_node(_inheritance_node):
718723

719724
for sub_node in ast.walk(body_item):
720725
if isinstance(sub_node, ast.Call):
721-
if isinstance(sub_node.func, ast.Name): # plain function or class
726+
if isinstance(sub_node.func, ast.Name): # 普通函数或类
722727
add_invoke(sub_node.func.id)
723-
if isinstance(sub_node.func, ast.Attribute): # member function
728+
if isinstance(sub_node.func, ast.Attribute): # 成员函数
724729
add_invoke(sub_node.func.attr)
725730
break
726731
break
@@ -732,7 +737,7 @@ def analyze_invokes(node, code_tree, graph, repo_path):
732737
caller_name = node.split(':')[-1].split('.')[-1]
733738
file_path = os.path.join(repo_path, node.split(':')[0])
734739

735-
# Store found call relationships
740+
# 存储找到的调用关系
736741
invocations = []
737742

738743
def add_invoke(func_name):
@@ -764,7 +769,7 @@ def traverse_call(_node):
764769
# Recursively traverse child nodes
765770
traverse_call(child)
766771

767-
# Traverse AST nodes to find call relationships
772+
# 遍历 AST 节点以找到调用关系
768773
for ast_node in ast.walk(code_tree):
769774
if (
770775
isinstance(ast_node, (ast.FunctionDef, ast.AsyncFunctionDef))
@@ -774,11 +779,11 @@ def traverse_call(_node):
774779
imports = find_imports(file_path, repo_path, tree=ast_node)
775780
add_imports(node, imports, graph, repo_path)
776781

777-
# Traverse function decorators
782+
# 遍历函数装饰器
778783
for decorator_node in ast_node.decorator_list:
779784
process_decorator_node(decorator_node)
780785

781-
# Traverse all invoke child nodes in the function body (excluding inner functions and classes)
786+
# 遍历函数体内的所有invoke子节点 (不包括内部函数、类)
782787
traverse_call(ast_node)
783788
break
784789

@@ -949,12 +954,10 @@ def main():
949954

950955

951956
if __name__ == '__main__':
952-
parser = argparse.ArgumentParser()
953-
parser.add_argument(
954-
'--repo_path', type=str, default='DATA/repo/pallets__flask-5063'
957+
logging.basicConfig(level=logging.DEBUG)
958+
a = build_json_graph(
959+
'/home/superbench/jiaxiang/Dev-TestCaseGen/build_benchmark/temp_repos/pyramid',
960+
fuzzy_search=True
955961
)
956-
parser.add_argument('--visualize', action='store_true')
957-
parser.add_argument('--global_import', action='store_true')
958-
args = parser.parse_args()
959962

960-
main()
963+
b = 1

testexplora/build_benchmark/parse_repo.py

Lines changed: 97 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import ast
22
import asttokens
33
import os
4+
import json
5+
import re
46

57
def extract_classes_and_functions(filepath, repo_saved_path):
68
file_text = ""
@@ -41,6 +43,8 @@ class ClassVisitor(ast.NodeVisitor):
4143
def visit_ClassDef(self, node):
4244
try:
4345
class_docstring = ast.get_docstring(node)
46+
if class_docstring is None:
47+
class_docstring = ""
4448
class_docstring_end = get_docstring_end_line(node)
4549
clazz = {
4650
"name": node.name,
@@ -51,9 +55,12 @@ def visit_ClassDef(self, node):
5155
"docstring": class_docstring if class_docstring is not None else "",
5256
"methods": []
5357
}
54-
all_lines[prefix + ":" + node.name] = {"line": (clazz["start_line"], clazz["end_line"]), "docstring": class_docstring, "docstring_end_line": clazz["docstring_end_line"], "file": prefix}
58+
all_lines[prefix + ":" + node.name] = {"line": (clazz["start_line"], clazz["end_line"]), "docstring": class_docstring, "docstring_end_line": clazz["docstring_end_line"], "file": prefix, "class": "", "type": "class", "methods": [], "parameters": {}, "returns": {}}
59+
all_methods = []
60+
all_parameters = {}
61+
all_returns = {}
5562
for item in node.body:
56-
if isinstance(item, ast.FunctionDef):
63+
if isinstance(item, ast.FunctionDef) or isinstance(item, ast.AsyncFunctionDef):
5764
returns = [
5865
ast.unparse(stmt.value) if stmt.value else None
5966
for stmt in item.body if isinstance(stmt, ast.Return)
@@ -62,19 +69,29 @@ def visit_ClassDef(self, node):
6269
docstring_end_line = get_docstring_end_line(item)
6370
decorators = [ast.unparse(d) for d in item.decorator_list]
6471
body_text = atok.get_text(item)[item.body[0].col_offset:] if item.body else ""
72+
parmeters = {}
73+
for arg in item.args.args:
74+
annotation = ast.unparse(arg.annotation) if arg.annotation else None
75+
parmeters[arg.arg] = annotation
6576

6677
method = {
6778
"name": item.name,
6879
"start_line": item.decorator_list[0].lineno if item.decorator_list else item.lineno,
6980
"end_line": item.end_lineno,
7081
"docstring_end_line": docstring_end_line,
71-
"parameters": [arg.arg for arg in item.args.args],
82+
"parameters": parmeters,
7283
"returns": returns if returns else None,
7384
"docstring": docstring if docstring is not None else "",
7485
"decorators": decorators if decorators else None,
7586
"body": body_text.strip()
7687
}
77-
all_lines[prefix + ":" + node.name + "." + item.name] = {"line": (method["start_line"], method["end_line"]), "docstring": method["docstring"], "docstring_end_line": method["docstring_end_line"], "file": prefix}
88+
all_lines[prefix + ":" + node.name + "." + item.name] = {"line": (method["start_line"], method["end_line"]), "docstring": method["docstring"], "docstring_end_line": method["docstring_end_line"], "file": prefix, "class": prefix + ":" + node.name, "type": "method", "methods": [], "parameters": method["parameters"], "returns": method["returns"]}
89+
all_methods.append(prefix + ":" + node.name + "." + item.name)
90+
all_parameters[prefix + ":" + node.name + "." + item.name] = method["parameters"]
91+
all_returns[prefix + ":" + node.name + "." + item.name] = method["returns"]
92+
all_lines[prefix + ":" + node.name]["methods"] = all_methods
93+
all_lines[prefix + ":" + node.name]["parameters"] = all_parameters
94+
all_lines[prefix + ":" + node.name]["returns"] = all_returns
7895

7996
# classes.append(clazz)
8097
except Exception as e:
@@ -104,19 +121,22 @@ def visit_FunctionDef(self, node):
104121
docstring = ast.get_docstring(node)
105122
docstring_end_line = get_docstring_end_line(node)
106123
body_text = atok.get_text(node)[node.body[0].col_offset:] if node.body else ""
107-
124+
parmeters = {}
125+
for arg in node.args.args:
126+
annotation = ast.unparse(arg.annotation) if arg.annotation else None
127+
parmeters[arg.arg] = annotation
108128
function = {
109129
"name": node.name,
110130
"start_line": node.decorator_list[0].lineno if node.decorator_list else node.lineno,
111131
"end_line": node.end_lineno,
112132
"docstring_end_line": docstring_end_line,
113-
"parameters": [arg.arg for arg in node.args.args],
133+
"parameters": parmeters,
114134
"returns": returns if returns else None,
115135
"docstring": docstring if docstring is not None else "",
116136
"decorators": decorators if decorators else None,
117137
"body": body_text.strip()
118138
}
119-
all_lines[prefix + ":" + node.name] = {"line": (function["start_line"], function["end_line"]), "docstring": function["docstring"], "docstring_end_line": function["docstring_end_line"], "file": prefix}
139+
all_lines[prefix + ":" + node.name] = {"line": (function["start_line"], function["end_line"]), "docstring": function["docstring"], "docstring_end_line": function["docstring_end_line"], "file": prefix, "class": "", "type": "function", "methods": [], "parameters": {prefix + ":" + node.name: function["parameters"]}, "returns": {prefix + ":" + node.name: function["returns"]}}
120140
except Exception as e:
121141
print(f"Error while processing function {node.name}: {e}")
122142
finally:
@@ -140,4 +160,73 @@ def extract_classes_and_functions_from_repo(repo_saved_path):
140160
all_code_lines.update(file_lines)
141161
except Exception as e:
142162
print(f"Error processing file {filepath}: {e}")
143-
return all_code_lines
163+
return all_code_lines
164+
165+
def extract_import_lines(file_content: str):
166+
"""
167+
Extracts import lines from the given file content: import, import as, from, and from ... import.
168+
Use ast to parse the file content and extract import statements.
169+
"""
170+
imports = []
171+
try:
172+
tree = ast.parse(file_content)
173+
except SyntaxError as e:
174+
import_pattern = re.compile(r'^\s*(import|from)\s+.*$', re.MULTILINE)
175+
lines = file_content.split("\n")
176+
for line in lines:
177+
if import_pattern.match(line):
178+
imports.append(line.strip())
179+
return "\n".join(imports)
180+
181+
# 遍历 AST 的顶层节点
182+
for node in ast.walk(tree):
183+
if isinstance(node, ast.Import):
184+
for alias in node.names:
185+
if alias.asname:
186+
imports.append(f"import {alias.name} as {alias.asname}")
187+
else:
188+
imports.append(f"import {alias.name}")
189+
elif isinstance(node, ast.ImportFrom):
190+
module = node.module if node.module else "" # 'from . import ...' 的情况
191+
names = []
192+
for alias in node.names:
193+
if alias.asname:
194+
names.append(f"{alias.name} as {alias.asname}")
195+
else:
196+
names.append(alias.name)
197+
if node.level > 0: # 处理相对导入,例如 from .module import name
198+
module_prefix = "." * node.level
199+
module = module_prefix + module
200+
imports.append(f"from {module} import {', '.join(names)}")
201+
return "\n".join(imports)
202+
203+
class Repo_file_container:
204+
def __init__(self, repo_saved_path: str):
205+
self.repo_saved_path = repo_saved_path
206+
self.file_dic = {}
207+
self.get_all_files()
208+
209+
def get_all_files(self):
210+
"""
211+
Returns a list of all Python files in the dataset directory.
212+
"""
213+
for root, _, files in os.walk(self.repo_saved_path):
214+
for file in files:
215+
if not file.endswith(".py"):
216+
continue
217+
file_path = os.path.join(root, file)
218+
try:
219+
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
220+
content = f.read()
221+
f.close()
222+
except Exception as e:
223+
logging.info(f"Error reading {file_path}: {e}")
224+
continue
225+
import_lines = extract_import_lines(content)
226+
prefix = file_path.replace(self.repo_saved_path, "").lstrip("/")
227+
self.file_dic[prefix] = {"lines": content.split("\n"), "imports": import_lines}
228+
def get_file_content(self, file_key: str):
229+
"""
230+
Returns the content of a file given its key.
231+
"""
232+
return self.file_dic[file_key]

0 commit comments

Comments
 (0)