Skip to content

Commit 590ff14

Browse files
committed
feat: make it usable
1 parent 00336b7 commit 590ff14

File tree

13 files changed

+1207
-201
lines changed

13 files changed

+1207
-201
lines changed

.github/workflows/deploy.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
jobs:
14+
build:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Setup Go
20+
uses: actions/setup-go@v4
21+
with:
22+
go-version: '1.21'
23+
24+
- name: Build WASM
25+
run: |
26+
chmod +x scripts/build_wasm.sh
27+
./scripts/build_wasm.sh
28+
29+
- name: Build static files
30+
run: |
31+
mkdir -p dist/static/samples
32+
cp -r static/* dist/static/
33+
cp templates/index.html dist/index.html
34+
cp static/samples/*.go dist/static/samples/
35+
36+
- name: Setup Pages
37+
uses: actions/configure-pages@v4
38+
39+
- name: Upload artifact
40+
uses: actions/upload-pages-artifact@v3
41+
with:
42+
path: './dist'
43+
44+
deploy:
45+
needs: build
46+
runs-on: ubuntu-latest
47+
environment:
48+
name: github-pages
49+
url: ${{ steps.deployment.outputs.page_url }}
50+
steps:
51+
- name: Deploy to GitHub Pages
52+
id: deployment
53+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Logs
2+
logs/
3+
*.log
4+
5+
# Build artifacts
6+
dist/
7+
code-complexity-viz
8+
9+
# WASM files
10+
static/analyzer.wasm
11+
static/wasm_exec.js
12+
13+
.idea/

README.md

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,68 @@
11
# Code Complexity Visualizer
22

3-
A tool to analyze and visualize code complexity metrics for Go programs.
3+
A web-based tool for analyzing and visualizing various code complexity metrics in Go source files.
4+
5+
🔗 [Try the Live Demo](https://amanv8060.github.io/code-complexity-viz)
6+
7+
## Usage Modes
8+
9+
### Browser Analysis (WASM)
10+
- Runs entirely in your browser
11+
- No server required
12+
- Perfect for quick analysis
13+
- Available in the live demo
14+
15+
### Server Analysis
16+
- Full feature set
17+
- Handles larger files
18+
- Better performance
19+
- Requires local setup
20+
21+
## Features
22+
23+
### Complexity Metrics
24+
- **Cyclomatic Complexity (McCabe)**: Measures the number of linearly independent paths through code
25+
- **Cognitive Complexity**: Measures how difficult it is to understand the code's control flow
26+
- **Halstead Metrics**:
27+
- Volume: Measures the size of the implementation
28+
- Difficulty: Indicates how hard the code is to understand
29+
- Effort: Estimates the effort required to maintain the code
30+
- **Maintainability Index**: A composite metric indicating overall maintainability (0-100 scale)
31+
- **Lines of Code**: Physical lines of code per function
32+
33+
## Installation
34+
35+
1. Clone the repository:
36+
```bash
37+
git clone https://github.com/aman/code-complexity-viz
38+
cd code-complexity-viz
39+
```
40+
41+
2. Install dependencies:
42+
```bash
43+
go mod tidy
44+
```
45+
46+
3. Run the server:
47+
```bash
48+
go run main.go
49+
```
50+
51+
4. Open `http://localhost:8080` in your browser
52+
53+
## Usage
54+
55+
1. Upload a Go source file using the web interface
56+
2. Click "Analyze" to process the file
57+
3. View the visualization of complexity metrics
58+
4. Use the dropdown to switch between different metrics
59+
5. Hover over bars to see detailed metrics for each function
60+
61+
### Limitations
62+
- Maximum file size: 5MB
63+
- Only analyzes `.go` files
64+
- Functions must be syntactically valid Go code
65+
66+
## License
67+
68+
MIT License - See LICENSE file for details

analyzer/complexity.go

Lines changed: 76 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,19 @@ type Operand struct {
2222

2323
// MetricsResult stores the complexity metrics for a single file or function
2424
type MetricsResult struct {
25-
Name string `json:"name"`
26-
CyclomaticComplexity int `json:"cyclomaticComplexity"`
27-
CognitiveComplexity int `json:"cognitiveComplexity"`
28-
LinesOfCode int `json:"linesOfCode"`
29-
HalsteadVolume float64 `json:"halsteadVolume"`
30-
HalsteadDifficulty float64 `json:"halsteadDifficulty"`
31-
HalsteadEffort float64 `json:"halsteadEffort"`
32-
MaintainabilityIndex float64 `json:"maintainabilityIndex"`
33-
NestingLevel int `json:"nestingLevel"`
25+
Name string `json:"name"`
26+
CyclomaticComplexity int `json:"cyclomaticComplexity"`
27+
CognitiveComplexity int `json:"cognitiveComplexity"`
28+
LinesOfCode int `json:"linesOfCode"`
29+
HalsteadVolume float64 `json:"halsteadVolume"`
30+
HalsteadDifficulty float64 `json:"halsteadDifficulty"`
31+
HalsteadEffort float64 `json:"halsteadEffort"`
32+
MaintainabilityIndex float64 `json:"maintainabilityIndex"`
33+
NestingLevel int `json:"nestingLevel"`
34+
NestedDepth int `json:"nestedDepth"`
35+
CommentDensity float64 `json:"commentDensity"`
36+
FunctionParameters int `json:"functionParameters"`
37+
ReturnStatements int `json:"returnStatements"`
3438
}
3539

3640
// FileAnalyzer handles the analysis of a single file
@@ -132,8 +136,8 @@ func (fa *FileAnalyzer) calculateHalsteadMetrics(node ast.Node) (volume, difficu
132136

133137
n1 := float64(len(operators)) // unique operators
134138
n2 := float64(len(operands)) // unique operands
135-
N1 := float64(0) // total operators
136-
N2 := float64(0) // total operands
139+
N1 := float64(0) // total operators
140+
N2 := float64(0) // total operands
137141

138142
for _, count := range operators {
139143
N1 += float64(count)
@@ -177,19 +181,31 @@ func (fa *FileAnalyzer) AnalyzeFunction(funcDecl *ast.FuncDecl) *MetricsResult {
177181
cyclomaticComplexity := fa.CalculateCyclomaticComplexity(funcDecl)
178182
cognitiveComplexity := fa.CalculateCognitiveComplexity(funcDecl)
179183
linesOfCode := fa.CountLinesOfCode(funcDecl)
180-
184+
181185
volume, difficulty, effort := fa.calculateHalsteadMetrics(funcDecl)
182-
186+
183187
// Add validation for edge cases
184188
if volume <= 0 {
185189
volume = 1 // Avoid log(0) in maintainability index calculation
186190
}
187191
if linesOfCode <= 0 {
188192
linesOfCode = 1
189193
}
190-
194+
191195
maintainabilityIndex := fa.CalculateMaintainabilityIndex(cyclomaticComplexity, volume, linesOfCode)
192196

197+
// Calculate nested depth
198+
nestedDepth := fa.calculateNestedDepth(funcDecl)
199+
200+
// Calculate comment density
201+
commentDensity := fa.calculateCommentDensity(funcDecl)
202+
203+
// Count parameters
204+
paramCount := len(funcDecl.Type.Params.List)
205+
206+
// Count return statements
207+
returnCount := fa.countReturnStatements(funcDecl)
208+
193209
// Ensure all metrics are valid
194210
if math.IsNaN(volume) || math.IsInf(volume, 0) {
195211
volume = 0
@@ -205,14 +221,18 @@ func (fa *FileAnalyzer) AnalyzeFunction(funcDecl *ast.FuncDecl) *MetricsResult {
205221
}
206222

207223
return &MetricsResult{
208-
Name: funcDecl.Name.Name,
224+
Name: funcDecl.Name.Name,
209225
CyclomaticComplexity: cyclomaticComplexity,
210226
CognitiveComplexity: cognitiveComplexity,
211227
LinesOfCode: linesOfCode,
212228
HalsteadVolume: math.Round(volume*100) / 100,
213229
HalsteadDifficulty: math.Round(difficulty*100) / 100,
214230
HalsteadEffort: math.Round(effort*100) / 100,
215231
MaintainabilityIndex: math.Round(maintainabilityIndex*100) / 100,
232+
NestedDepth: nestedDepth,
233+
CommentDensity: commentDensity,
234+
FunctionParameters: paramCount,
235+
ReturnStatements: returnCount,
216236
}
217237
}
218238

@@ -229,3 +249,44 @@ func (fa *FileAnalyzer) AnalyzeFile() []*MetricsResult {
229249

230250
return results
231251
}
252+
253+
// Add new analysis methods
254+
func (fa *FileAnalyzer) calculateNestedDepth(node ast.Node) int {
255+
maxDepth := 0
256+
currentDepth := 0
257+
258+
ast.Inspect(node, func(n ast.Node) bool {
259+
switch n.(type) {
260+
case *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.SelectStmt:
261+
currentDepth++
262+
if currentDepth > maxDepth {
263+
maxDepth = currentDepth
264+
}
265+
return true
266+
}
267+
currentDepth = 0
268+
return true
269+
})
270+
271+
return maxDepth
272+
}
273+
274+
func (fa *FileAnalyzer) calculateCommentDensity(node ast.Node) float64 {
275+
comments := len(fa.ast.Comments)
276+
lines := fa.CountLinesOfCode(node)
277+
if lines == 0 {
278+
return 0
279+
}
280+
return float64(comments) / float64(lines)
281+
}
282+
283+
func (fa *FileAnalyzer) countReturnStatements(node ast.Node) int {
284+
count := 0
285+
ast.Inspect(node, func(n ast.Node) bool {
286+
if _, ok := n.(*ast.ReturnStmt); ok {
287+
count++
288+
}
289+
return true
290+
})
291+
return count
292+
}

go.mod

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,38 @@
11
module github.com/aman/code-complexity-viz
22

3-
go 1.22.11
3+
go 1.21
44

55
require (
6-
github.com/bytedance/sonic v1.11.6 // indirect
7-
github.com/bytedance/sonic/loader v0.1.1 // indirect
8-
github.com/cloudwego/base64x v0.1.4 // indirect
9-
github.com/cloudwego/iasm v0.2.0 // indirect
6+
github.com/gin-contrib/cors v1.5.0
7+
github.com/gin-contrib/gzip v0.0.6
8+
github.com/gin-contrib/secure v0.0.1
9+
github.com/gin-gonic/gin v1.9.1
10+
)
11+
12+
require (
13+
github.com/bytedance/sonic v1.11.2 // indirect
14+
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
15+
github.com/chenzhuoyu/iasm v0.9.1 // indirect
1016
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
1117
github.com/gin-contrib/sse v0.1.0 // indirect
12-
github.com/gin-gonic/gin v1.10.0 // indirect
1318
github.com/go-playground/locales v0.14.1 // indirect
1419
github.com/go-playground/universal-translator v0.18.1 // indirect
15-
github.com/go-playground/validator/v10 v10.20.0 // indirect
20+
github.com/go-playground/validator/v10 v10.18.0 // indirect
1621
github.com/goccy/go-json v0.10.2 // indirect
1722
github.com/json-iterator/go v1.1.12 // indirect
1823
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
1924
github.com/leodido/go-urn v1.4.0 // indirect
2025
github.com/mattn/go-isatty v0.0.20 // indirect
2126
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
2227
github.com/modern-go/reflect2 v1.0.2 // indirect
23-
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
28+
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
2429
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
2530
github.com/ugorji/go/codec v1.2.12 // indirect
26-
golang.org/x/arch v0.8.0 // indirect
27-
golang.org/x/crypto v0.23.0 // indirect
28-
golang.org/x/net v0.25.0 // indirect
29-
golang.org/x/sys v0.20.0 // indirect
30-
golang.org/x/text v0.15.0 // indirect
31-
google.golang.org/protobuf v1.34.1 // indirect
31+
golang.org/x/arch v0.7.0 // indirect
32+
golang.org/x/crypto v0.19.0 // indirect
33+
golang.org/x/net v0.21.0 // indirect
34+
golang.org/x/sys v0.17.0 // indirect
35+
golang.org/x/text v0.14.0 // indirect
36+
google.golang.org/protobuf v1.32.0 // indirect
3237
gopkg.in/yaml.v3 v3.0.1 // indirect
3338
)

0 commit comments

Comments
 (0)