Skip to content

Commit a7289f1

Browse files
authored
Cyclomatic Complexity Example (#47)
* computes cyclomatic complexity * . * . * Automated pre-commit update * Remove unnecessary files from codegen * Automated pre-commit update * . --------- Co-authored-by: jayhack <2548876+jayhack@users.noreply.github.com>
1 parent 04ce83b commit a7289f1

File tree

4 files changed

+243
-0
lines changed

4 files changed

+243
-0
lines changed

β€Ž.codegen/.gitignoreβ€Ž

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Codegen
2+
docs/
3+
examples/
4+
prompts/
5+
jupyter/
6+
.venv/
7+
codegen-system-prompt.txt
8+
9+
# Python cache files
10+
__pycache__/
11+
*.py[cod]
12+
*$py.class
13+
14+
# Keep config.toml and codemods
15+
!config.toml
16+
!codemods/
17+
!codemods/**

β€Ž.codegen/config.tomlβ€Ž

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
organization_name = "codegen-sh"
2+
repo_name = "codegen-examples"
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# Cyclomatic Complexity Analyzer
2+
3+
This example demonstrates how to analyze the cyclomatic complexity of Python codebases using Codegen. The script provides detailed insights into code complexity by analyzing control flow structures and providing a comprehensive report.
4+
5+
> [!NOTE]
6+
> The cyclomatic complexity metric helps identify complex code that might need refactoring. A higher score indicates more complex code with multiple decision points.
7+
8+
## How the Analysis Script Works
9+
10+
The script (`run.py`) performs the complexity analysis in several key steps:
11+
12+
1. **Codebase Loading**
13+
```python
14+
codebase = Codebase.from_repo("fastapi/fastapi")
15+
```
16+
- Loads any Python codebase into Codegen's analysis engine
17+
- Works with local or remote Git repositories
18+
- Supports analyzing specific commits
19+
20+
2. **Complexity Calculation**
21+
```python
22+
def calculate_cyclomatic_complexity(code_block):
23+
complexity = 1 # Base complexity
24+
for statement in code_block.statements:
25+
if isinstance(statement, IfBlockStatement):
26+
complexity += 1 + len(statement.elif_statements)
27+
```
28+
- Analyzes control flow structures (if/elif/else, loops, try/except)
29+
- Calculates complexity based on decision points
30+
- Handles nested structures appropriately
31+
32+
3. **Function Analysis**
33+
```python
34+
callables = codebase.functions + [m for c in codebase.classes for m in c.methods]
35+
for function in callables:
36+
complexity = calculate_cyclomatic_complexity(function.code_block)
37+
```
38+
- Processes both standalone functions and class methods
39+
- Calculates complexity for each callable
40+
- Tracks file locations and function names
41+
42+
4. **Report Generation**
43+
```python
44+
print("\nπŸ“Š Cyclomatic Complexity Analysis")
45+
print(f" β€’ Total Functions: {total_functions}")
46+
print(f" β€’ Average Complexity: {average:.2f}")
47+
```
48+
- Provides comprehensive complexity statistics
49+
- Shows distribution of complexity across functions
50+
- Identifies the most complex functions
51+
52+
## Output
53+
```
54+
πŸ“Š Cyclomatic Complexity Analysis
55+
============================================================
56+
57+
πŸ“ˆ Overall Stats:
58+
β€’ Total Functions: 3538
59+
β€’ Average Complexity: 1.27
60+
β€’ Total Complexity: 4478
61+
62+
πŸ” Top 10 Most Complex Functions:
63+
------------------------------------------------------------
64+
β€’ jsonable_encoder 16 | fastapi/encoders.py
65+
β€’ get_openapi 13 | fastapi/openapi/utils.py
66+
β€’ __init__ 12 | fastapi/routing.py
67+
β€’ solve_dependencies 10 | fastapi/dependencies/utils.py
68+
β€’ main 9 | scripts/notify_translations.py
69+
β€’ analyze_param 9 | fastapi/dependencies/utils.py
70+
β€’ __init__ 8 | fastapi/params.py
71+
β€’ __init__ 8 | fastapi/params.py
72+
β€’ main 7 | scripts/deploy_docs_status.py
73+
β€’ create_model_field 7 | fastapi/utils.py
74+
75+
πŸ“‰ Complexity Distribution:
76+
β€’ Low (1-5): 3514 functions (99.3%)
77+
β€’ Medium (6-10): 21 functions (0.6%)
78+
β€’ High (>10): 3 functions (0.1%)
79+
```
80+
81+
## Complexity Metrics
82+
83+
The analyzer tracks several key metrics:
84+
85+
### Complexity Sources
86+
- If statements (+1)
87+
- Elif statements (+1 each)
88+
- Else statements (+1)
89+
- Loops (while/for) (+1)
90+
- Try-except blocks (+1 per except)
91+
92+
### Complexity Categories
93+
- Low (1-5): Generally clean and maintainable code
94+
- Medium (6-10): Moderate complexity, may need attention
95+
- High (>10): Complex code that should be reviewed
96+
97+
## Running the Analysis
98+
99+
```bash
100+
# Install Codegen
101+
pip install codegen
102+
103+
# Run the analysis
104+
python run.py
105+
```
106+
107+
## Example Output
108+
109+
```
110+
πŸ“Š Cyclomatic Complexity Analysis
111+
============================================================
112+
113+
πŸ“ˆ Overall Stats:
114+
β€’ Total Functions: 150
115+
β€’ Average Complexity: 3.45
116+
β€’ Total Complexity: 518
117+
118+
πŸ” Top 10 Most Complex Functions:
119+
------------------------------------------------------------
120+
β€’ validate_response 12 | ...api/endpoints/auth.py
121+
β€’ process_request 10 | ...core/middleware.py
122+
β€’ handle_exception 9 | ...utils/error_handlers.py
123+
124+
πŸ“‰ Complexity Distribution:
125+
β€’ Low (1-5): 105 functions (70.0%)
126+
β€’ Medium (6-10): 35 functions (23.3%)
127+
β€’ High (>10): 10 functions (6.7%)
128+
```
129+
130+
## Learn More
131+
132+
- [About Cyclomatic Complexity](https://en.wikipedia.org/wiki/Cyclomatic_complexity)
133+
- [Codegen Documentation](https://docs.codegen.com)
134+
135+
## Contributing
136+
137+
Feel free to submit issues and enhancement requests!
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import codegen
2+
from codegen import Codebase
3+
from codegen.sdk.core.statements.for_loop_statement import ForLoopStatement
4+
from codegen.sdk.core.statements.if_block_statement import IfBlockStatement
5+
from codegen.sdk.core.statements.try_catch_statement import TryCatchStatement
6+
from codegen.sdk.core.statements.while_statement import WhileStatement
7+
8+
9+
@codegen.function("cyclomatic-complexity")
10+
def run(codebase: Codebase):
11+
def calculate_cyclomatic_complexity(code_block):
12+
# Initialize cyclomatic complexity count
13+
complexity = 1 # Start with one for the default path
14+
15+
# Count decision points
16+
for statement in code_block.statements:
17+
if isinstance(statement, IfBlockStatement):
18+
complexity += 1 + len(statement.elif_statements) # +1 for if, each elif adds another path
19+
if statement.else_statement:
20+
complexity += 1
21+
elif isinstance(statement, WhileStatement) or isinstance(statement, ForLoopStatement):
22+
complexity += 1 # Loops introduce a new path
23+
elif isinstance(statement, TryCatchStatement):
24+
complexity += 1 # try-catch introduces a new path
25+
# Count except blocks by counting nested code blocks after the first one (try block)
26+
complexity += len(statement.nested_code_blocks) - 1 # -1 to exclude the try block itself
27+
28+
return complexity
29+
30+
# Initialize total complexity
31+
total_complexity = 0
32+
# Count total functions
33+
total_functions = 0
34+
# Store results for sorting
35+
results = []
36+
37+
# Get all functions or methods
38+
callables = codebase.functions + [m for c in codebase.classes for m in c.methods]
39+
40+
# Analyze each function
41+
for function in callables:
42+
complexity = calculate_cyclomatic_complexity(function.code_block)
43+
results.append((function.name, complexity, function.filepath))
44+
total_complexity += complexity
45+
total_functions += 1
46+
47+
# Sort by complexity (highest first)
48+
results.sort(key=lambda x: x[1], reverse=True)
49+
50+
# Print summary
51+
print("\nπŸ“Š Cyclomatic Complexity Analysis")
52+
print("=" * 60)
53+
54+
if total_functions > 0:
55+
average = total_complexity / total_functions
56+
print("\nπŸ“ˆ Overall Stats:")
57+
print(f" β€’ Total Functions: {total_functions}")
58+
print(f" β€’ Average Complexity: {average:.2f}")
59+
print(f" β€’ Total Complexity: {total_complexity}")
60+
61+
print("\nπŸ” Top 10 Most Complex Functions:")
62+
print("-" * 60)
63+
for name, complexity, filepath in results[:10]:
64+
# Truncate filepath if too long
65+
if len(filepath) > 40:
66+
filepath = "..." + filepath[-37:]
67+
print(f" β€’ {name:<30} {complexity:>3} | {filepath}")
68+
69+
# Complexity distribution
70+
low = sum(1 for _, c, _ in results if c <= 5)
71+
medium = sum(1 for _, c, _ in results if 5 < c <= 10)
72+
high = sum(1 for _, c, _ in results if c > 10)
73+
74+
print("\nπŸ“‰ Complexity Distribution:")
75+
print(f" β€’ Low (1-5): {low} functions ({low / total_functions * 100:.1f}%)")
76+
print(f" β€’ Medium (6-10): {medium} functions ({medium / total_functions * 100:.1f}%)")
77+
print(f" β€’ High (>10): {high} functions ({high / total_functions * 100:.1f}%)")
78+
else:
79+
print("❌ No functions found in the codebase to analyze.")
80+
81+
82+
if __name__ == "__main__":
83+
print("πŸ” Analyzing codebase...")
84+
codebase = Codebase.from_repo("fastapi/fastapi", commit="887270ff8a54bb58c406b0651678a27589793d2f")
85+
86+
print("Running analysis...")
87+
run(codebase)

0 commit comments

Comments
Β (0)