Skip to content

Commit 8cf769b

Browse files
committed
added files
0 parents  commit 8cf769b

File tree

1,515 files changed

+262036
-0
lines changed

Some content is hidden

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

1,515 files changed

+262036
-0
lines changed

app.py

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
from flask import Flask, render_template, request, jsonify, send_file
2+
import ast
3+
from radon.complexity import cc_visit
4+
from radon.metrics import h_visit
5+
import io
6+
from typing import List, Dict, Any
7+
8+
app = Flask(__name__)
9+
10+
class CodeAnalyzer:
11+
def analyze_code(self, code: str) -> Dict[str, Any]:
12+
try:
13+
tree = ast.parse(code)
14+
complexity_metrics = cc_visit(code)
15+
halstead_metrics = h_visit(code)
16+
functions = []
17+
18+
for node in ast.walk(tree):
19+
if isinstance(node, ast.FunctionDef):
20+
func_info = {
21+
'name': node.name,
22+
'args': [{'name': arg.arg, 'type': self._get_type_hint(arg)} for arg in node.args.args],
23+
'returns': self._get_return_hint(node),
24+
'complexity': next((m.complexity for m in complexity_metrics if m.name == node.name), 0),
25+
'suggestions': self._analyze_function(node),
26+
'edge_cases': self._generate_edge_cases(node)
27+
}
28+
functions.append(func_info)
29+
30+
return {
31+
'success': True,
32+
'functions': functions,
33+
'metrics': {
34+
'total_complexity': sum(m.complexity for m in complexity_metrics),
35+
'total_lines': len(code.splitlines()),
36+
'complexity_rank': 'Low' if sum(m.complexity for m in complexity_metrics) < 10 else 'Medium' if sum(m.complexity for m in complexity_metrics) < 20 else 'High'
37+
}
38+
}
39+
except Exception as e:
40+
return {'success': False, 'error': str(e)}
41+
42+
def _get_type_hint(self, arg):
43+
return arg.annotation.id if hasattr(arg, 'annotation') and hasattr(arg.annotation, 'id') else 'Any'
44+
45+
def _get_return_hint(self, node):
46+
return node.returns.id if hasattr(node, 'returns') and hasattr(node.returns, 'id') else 'Any'
47+
48+
def _analyze_function(self, node):
49+
suggestions = []
50+
51+
if not ast.get_docstring(node):
52+
suggestions.append({
53+
'type': 'documentation',
54+
'message': 'Add docstring to document function purpose and parameters'
55+
})
56+
57+
has_type_hints = all(hasattr(arg, 'annotation') for arg in node.args.args)
58+
if not has_type_hints:
59+
suggestions.append({
60+
'type': 'type_hints',
61+
'message': 'Add type hints to improve code clarity and enable static type checking'
62+
})
63+
64+
return suggestions
65+
66+
def _generate_edge_cases(self, node):
67+
test_cases = []
68+
for arg in node.args.args:
69+
type_hint = self._get_type_hint(arg)
70+
cases = self._get_type_edge_cases(type_hint)
71+
for case in cases:
72+
test_cases.append({
73+
'category': case['category'],
74+
'description': f"Test {node.name} with {arg.arg} = {case['value']}",
75+
'code': self._generate_test_code(node.name, node.args.args, arg, case)
76+
})
77+
return test_cases
78+
79+
def _get_type_edge_cases(self, type_hint):
80+
cases = []
81+
if type_hint == 'int':
82+
cases = [
83+
{'value': '0', 'category': 'boundary'},
84+
{'value': '-1', 'category': 'boundary'},
85+
{'value': 'sys.maxsize', 'category': 'edge'},
86+
{'value': '-sys.maxsize - 1', 'category': 'edge'}
87+
]
88+
elif type_hint == 'str':
89+
cases = [
90+
{'value': '""', 'category': 'boundary'},
91+
{'value': '"" * 10000', 'category': 'edge'},
92+
{'value': '"!@#$%^&*()"', 'category': 'edge'}
93+
]
94+
elif type_hint == 'list':
95+
cases = [
96+
{'value': '[]', 'category': 'boundary'},
97+
{'value': '[1] * 1000', 'category': 'edge'},
98+
{'value': '[None]', 'category': 'edge'}
99+
]
100+
return cases
101+
102+
def _generate_test_code(self, func_name, all_args, target_arg, case):
103+
args = [case['value'] if arg.arg == target_arg.arg else 'None' for arg in all_args]
104+
return f"""def test_{func_name}_{case['category']}():
105+
try:
106+
result = {func_name}({', '.join(args)})
107+
assert result is not None
108+
except Exception as e:
109+
pass # Handle expected exceptions"""
110+
111+
@app.route('/')
112+
def index():
113+
return render_template('index.html')
114+
115+
@app.route('/analyze', methods=['POST'])
116+
def analyze():
117+
code = request.json.get('code', '')
118+
if not code:
119+
return jsonify({'error': 'No code provided'}), 400
120+
121+
analyzer = CodeAnalyzer()
122+
result = analyzer.analyze_code(code)
123+
return jsonify(result)
124+
125+
@app.route('/export', methods=['POST'])
126+
def export():
127+
code = request.json.get('code', '')
128+
format_type = request.json.get('format', 'pytest')
129+
130+
analyzer = CodeAnalyzer()
131+
result = analyzer.analyze_code(code)
132+
133+
if not result['success']:
134+
return jsonify({'error': 'Analysis failed'}), 400
135+
136+
test_code = io.StringIO()
137+
if format_type == 'pytest':
138+
test_code.write('import pytest\n\n')
139+
else:
140+
test_code.write('import unittest\n\n')
141+
142+
for func in result['functions']:
143+
for case in func['edge_cases']:
144+
test_code.write(case['code'] + '\n\n')
145+
146+
return send_file(
147+
io.BytesIO(test_code.getvalue().encode()),
148+
mimetype='text/plain',
149+
as_attachment=True,
150+
download_name=f'test_cases.{format_type}.py'
151+
)
152+
153+
if __name__ == '__main__':
154+
app.run(debug=True)

requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
flask==3.0.0
2+
ast2json==0.3

static/css/style.css

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
:root {
2+
--bg-primary: #ffffff;
3+
--text-primary: #1a202c;
4+
}
5+
6+
[data-theme="dark"] {
7+
--bg-primary: #1a202c;
8+
--text-primary: #ffffff;
9+
}
10+
11+
body {
12+
background-color: var(--bg-primary);
13+
color: var(--text-primary);
14+
}
15+
16+
.CodeMirror {
17+
height: 100% !important;
18+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
19+
font-size: 14px;
20+
}
21+
22+
.test-case {
23+
position: relative;
24+
background-color: #f8f9fa;
25+
border-radius: 0.5rem;
26+
padding: 1rem;
27+
margin-bottom: 1rem;
28+
}
29+
30+
.test-case pre {
31+
background-color: #272822;
32+
color: #f8f8f2;
33+
padding: 1rem;
34+
border-radius: 0.375rem;
35+
overflow-x: auto;
36+
}
37+
38+
.copy-button {
39+
position: absolute;
40+
top: 0.5rem;
41+
right: 0.5rem;
42+
padding: 0.25rem 0.5rem;
43+
background-color: #4a5568;
44+
color: white;
45+
border-radius: 0.25rem;
46+
font-size: 0.75rem;
47+
cursor: pointer;
48+
transition: background-color 0.2s;
49+
}
50+
51+
.copy-button:hover {
52+
background-color: #2d3748;
53+
}
54+
55+
.category-filter.active {
56+
background-color: #4299e1;
57+
color: white;
58+
}
59+
60+
.collapsible {
61+
cursor: pointer;
62+
}
63+
64+
.collapsible-content {
65+
max-height: 0;
66+
overflow: hidden;
67+
transition: max-height 0.3s ease-out;
68+
}
69+
70+
.collapsible-content.open {
71+
max-height: 1000px;
72+
}

0 commit comments

Comments
 (0)