From 7fc82058287550f892999210e8a24d152afc838a Mon Sep 17 00:00:00 2001 From: Owais Jamil Date: Thu, 13 Nov 2025 14:46:01 -0600 Subject: [PATCH] docs: mdbook & example/sample files --- docs/src/SUMMARY.md | 19 +- docs/src/cli-reference.md | 239 +++++++++++++++++++++++++ docs/src/clone-detection.md | 276 +++++++++++++++++++++++++++++ docs/src/configuration.md | 230 ++++++++++++++++++++++++ docs/src/cyclomatic-complexity.md | 197 +++++++++++++++++++++ docs/src/examples.md | 284 ++++++++++++++++++++++++++++++ docs/src/installation.md | 56 ++++++ docs/src/introduction.md | 29 +++ docs/src/lines-of-code.md | 193 ++++++++++++++++++++ docs/src/quickstart.md | 83 +++++++++ examples/complex.js | 48 +++++ examples/long.py | 97 ++++++++++ examples/not_dry.go | 85 +++++++++ 13 files changed, 1835 insertions(+), 1 deletion(-) create mode 100644 docs/src/cli-reference.md create mode 100644 docs/src/clone-detection.md create mode 100644 docs/src/configuration.md create mode 100644 docs/src/cyclomatic-complexity.md create mode 100644 docs/src/examples.md create mode 100644 docs/src/installation.md create mode 100644 docs/src/introduction.md create mode 100644 docs/src/lines-of-code.md create mode 100644 docs/src/quickstart.md create mode 100644 examples/complex.js create mode 100644 examples/long.py create mode 100644 examples/not_dry.go diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 7390c82..1baa111 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -1,3 +1,20 @@ # Summary -- [Chapter 1](./chapter_1.md) +[Introduction](./introduction.md) + +# User Guide + +- [Installation](./installation.md) +- [Quick Start](./quickstart.md) +- [CLI Reference](./cli-reference.md) +- [Configuration](./configuration.md) + +# Algorithms + +- [Cyclomatic Complexity](./cyclomatic-complexity.md) +- [Lines of Code (LOC)](./lines-of-code.md) +- [Clone Detection](./clone-detection.md) + +# Examples + +- [Usage](./examples.md) diff --git a/docs/src/cli-reference.md b/docs/src/cli-reference.md new file mode 100644 index 0000000..0088007 --- /dev/null +++ b/docs/src/cli-reference.md @@ -0,0 +1,239 @@ +# CLI Reference + +## Commands + +### `analyze` + +Run full analysis (complexity + clones + LOC). + +```bash +mccabre analyze [OPTIONS] [PATH] +``` + +**Arguments:** + +- `[PATH]` - Path to file or directory (default: `.`) + +**Options:** + +- `-j, --json` - Output in JSON format +- `--threshold ` - Complexity warning threshold +- `--min-tokens ` - Minimum tokens for clone detection (default: 30) +- `-c, --config ` - Path to config file +- `--no-gitignore` - Disable gitignore awareness + +**Examples:** + +```bash +# Analyze current directory +mccabre analyze + +# Analyze specific path with JSON output +mccabre analyze ./src --json + +# Custom thresholds +mccabre analyze ./src --threshold 15 --min-tokens 25 +``` + +### `complexity` + +Analyze cyclomatic complexity and LOC only. + +```bash +mccabre complexity [OPTIONS] [PATH] +``` + +**Arguments:** + +- `[PATH]` - Path to file or directory (default: `.`) + +**Options:** + +- `-j, --json` - Output in JSON format +- `--threshold ` - Complexity warning threshold +- `-c, --config ` - Path to config file +- `--no-gitignore` - Disable gitignore awareness + +**Examples:** + +```bash +# Check complexity of src/ +mccabre complexity src/ + +# Fail if any file exceeds complexity of 20 +mccabre complexity src/ --threshold 20 +``` + +### `clones` + +Detect code clones only. + +```bash +mccabre clones [OPTIONS] [PATH] +``` + +**Arguments:** + +- `[PATH]` - Path to file or directory (default: `.`) + +**Options:** + +- `-j, --json` - Output in JSON format +- `--min-tokens ` - Minimum tokens for detection (default: 30) +- `-c, --config ` - Path to config file +- `--no-gitignore` - Disable gitignore awareness + +**Examples:** + +```bash +# Find clones in current directory +mccabre clones . + +# Find only large clones (50+ tokens) +mccabre clones . --min-tokens 50 + +# JSON output for processing +mccabre clones src/ --json | jq '.clones | length' +``` + +### `dump-config` + +Display current configuration. + +```bash +mccabre dump-config [OPTIONS] +``` + +**Options:** + +- `-c, --config ` - Path to config file (shows default if not specified) + +**Examples:** + +```bash +# Show default configuration +mccabre dump-config + +# Show loaded configuration +mccabre dump-config --config mccabre.toml +``` + +## Global Options + +### `-h, --help` + +Show help information. + +```bash +mccabre --help +mccabre analyze --help +``` + +### `-V, --version` + +Show version information. + +```bash +mccabre --version +``` + +## Output Formats + +### Terminal (Default) + +Colored, human-readable output: + +```text +FILE: src/main.rs + Cyclomatic Complexity: 15 (warning) + Physical LOC: 120 + Logical LOC: 85 +``` + +Colors: + +- **Green**: Low/good +- **Yellow**: Moderate/warning +- **Red**: High/error + +### JSON + +Machine-readable output for CI/CD: + +```bash +mccabre analyze src/ --json +``` + +```json +{ + "files": [ + { + "path": "src/main.rs", + "loc": { + "physical": 120, + "logical": 85, + "comments": 25, + "blank": 10 + }, + "cyclomatic": { + "file_complexity": 15, + "functions": [] + } + } + ], + "clones": [], + "summary": { + "total_files": 1, + "total_physical_loc": 120, + "total_logical_loc": 85, + "avg_complexity": 15.0, + "max_complexity": 15, + "high_complexity_files": 1, + "total_clones": 0 + } +} +``` + +## File Selection + +### Supported Languages + +- **Rust**: `.rs` +- **JavaScript**: `.js`, `.jsx`, `.mjs`, `.cjs` +- **TypeScript**: `.ts`, `.tsx` +- **Go**: `.go` +- **Java**: `.java` +- **C++**: `.cpp`, `.cc`, `.cxx`, `.h`, `.hpp`, `.hxx` + +### Gitignore Support + +Mccabre respects `.gitignore` files by default: + +```bash +# Respects .gitignore (default) +mccabre analyze . + +# Ignores .gitignore +mccabre analyze . --no-gitignore +``` + +Automatically skips: + +- Files/directories in .gitignore +- `.git/` directory +- Binary files (by extension) + +## Environment Variables + +Currently none. Configuration via: + +1. CLI flags (highest priority) +2. Config file +3. Defaults + +## See Also + +- [Configuration](./configuration.md) +- [Cyclomatic Complexity](./cyclomatic-complexity.md) +- [Clone Detection](./clone-detection.md) +- [Examples](./examples.md) diff --git a/docs/src/clone-detection.md b/docs/src/clone-detection.md new file mode 100644 index 0000000..d152fc3 --- /dev/null +++ b/docs/src/clone-detection.md @@ -0,0 +1,276 @@ +# Clone Detection + +## What is Code Cloning? + +Code clones are similar or identical code fragments that appear in multiple places. They indicate duplication and potential refactoring opportunities. + +## How Mccabre Detects Clones + +Mccabre uses **Rabin-Karp rolling hash**, a fast string matching algorithm adapted for token sequences. + +### Algorithm Overview + +1. **Tokenization**: Convert source code to tokens +2. **Windowing**: Slide a window of N tokens across the sequence +3. **Hashing**: Compute a rolling hash for each window +4. **Matching**: Identify windows with identical hashes +5. **Reporting**: Group matches into clone groups + +### Why This Approach? + +**Advantages:** + +- **Fast**: O(n) time complexity +- **Language-agnostic**: Works on tokens, not syntax trees +- **Tunable**: Adjust window size to find smaller or larger clones + +**Trade-offs:** + +- Finds exact token matches only +- Doesn't detect semantic equivalence +- May miss clones with renamed variables + +## Using Clone Detection + +### Basic Usage + +```bash +mccabre clones src/ +``` + +### Adjust Sensitivity + +The `--min-tokens` flag controls the minimum clone size: + +```bash +# Find larger clones (more strict) +mccabre clones src/ --min-tokens 50 + +# Find smaller clones (more sensitive) +mccabre clones src/ --min-tokens 15 +``` + +### Sample Output + +```text +DETECTED CLONES +-------------------------------------------------------------------------------- +Clone Group #1 (length: 32 tokens, 3 occurrences) + - src/user.go:15-28 + - src/product.go:42-55 + - src/order.go:88-101 + +Clone Group #2 (length: 45 tokens, 2 occurrences) + - src/validators.rs:120-145 + - src/sanitizers.rs:67-92 +``` + +## Interpreting Results + +### Clone Group Fields + +- **ID**: Unique identifier for the clone group +- **Length**: Number of tokens in the duplicated sequence +- **Occurrences**: How many times this clone appears +- **Locations**: File paths and line ranges + +### Significance + +| Tokens | Significance | Action | +|--------|-------------|--------| +| 15-25 | Minor duplication | Consider refactoring if repeated 3+ times | +| 26-50 | Moderate duplication | Should refactor | +| 50+ | Major duplication | Urgent refactoring needed | + +## Refactoring Clones + +### Example: Extract Function + +**Before:** + +```go +// In file1.go +func processUser(input string) string { + trimmed := strings.TrimSpace(input) + if len(trimmed) == 0 { + return "" + } + lower := strings.ToLower(trimmed) + return lower +} + +// In file2.go +func processProduct(name string) string { + trimmed := strings.TrimSpace(name) + if len(trimmed) == 0 { + return "" + } + lower := strings.ToLower(trimmed) + return lower +} +``` + +**After:** + +```go +// In utils.go +func sanitizeString(input string) string { + trimmed := strings.TrimSpace(input) + if len(trimmed) == 0 { + return "" + } + return strings.ToLower(trimmed) +} + +// In file1.go +func processUser(input string) string { + return sanitizeString(input) +} + +// In file2.go +func processProduct(name string) string { + return sanitizeString(name) +} +``` + +### Example: Extract Class/Module + +**Before:** Multiple files with similar validation logic + +**After:** Single `validation` module imported by all files + +## Types of Clones + +### Type 1: Exact Clones + +Identical code except for whitespace and comments. + +```javascript +// Clone 1 +function calc(a, b) { + return a + b; +} + +// Clone 2 +function calc(a, b) { + return a + b; +} +``` + +✅ **Mccabre detects these** + +### Type 2: Renamed Clones + +Identical except for variable/function names. + +```javascript +// Clone 1 +function add(x, y) { + return x + y; +} + +// Clone 2 +function sum(a, b) { + return a + b; +} +``` + +❌ **Mccabre does NOT detect these** (yet) + +### Type 3: Near-Miss Clones + +Similar structure with minor modifications. + +```javascript +// Clone 1 +function validate(user) { + if (!user.email) return false; + if (!user.name) return false; + return true; +} + +// Clone 2 +function validate(product) { + if (!product.id) return false; + if (!product.price) return false; + if (!product.name) return false; + return true; +} +``` + +❌ **Mccabre does NOT detect these** + +### Type 4: Semantic Clones + +Different syntax, same behavior. + +```javascript +// Clone 1 +const sum = arr.reduce((a, b) => a + b, 0); + +// Clone 2 +let sum = 0; +for (let num of arr) { + sum += num; +} +``` + +❌ **Mccabre does NOT detect these** + +## Configuration + +### Via Command Line + +```bash +mccabre clones . --min-tokens 30 +``` + +### Via Config File + +Create `mccabre.toml`: + +```toml +[clones] +enabled = true +min_tokens = 30 +``` + +## JSON Output + +```bash +mccabre clones src/ --json +``` + +```json +{ + "clones": [ + { + "id": 1, + "length": 32, + "locations": [ + { + "file": "src/user.go", + "start_line": 15, + "end_line": 28 + }, + { + "file": "src/product.go", + "start_line": 42, + "end_line": 55 + } + ] + } + ] +} +``` + +## References + +- [Rabin-Karp Algorithm](https://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_algorithm) +- [Code Clone Research](https://www.sei.cmu.edu/library/code-similarity-detection-using-syntax-agnostic-locality-sensitive-hashing/) + +## See Also + +- [Cyclomatic Complexity](./cyclomatic-complexity.md) +- [CLI Reference](./cli-reference.md) +- [Examples](./examples.md) diff --git a/docs/src/configuration.md b/docs/src/configuration.md new file mode 100644 index 0000000..cdb8cee --- /dev/null +++ b/docs/src/configuration.md @@ -0,0 +1,230 @@ +# Configuration + +Mccabre can be configured via config files and CLI flags. + +## Configuration Priority + +1. **CLI flags** (highest priority) +2. **Config file** (`mccabre.toml`) +3. **Defaults** (lowest priority) + +CLI flags override config file settings. + +## Config File + +Create a `mccabre.toml` file in your project root: + +```toml +[complexity] +warning_threshold = 10 +error_threshold = 20 + +[clones] +enabled = true +min_tokens = 30 + +[files] +respect_gitignore = true +``` + +## Configuration Options + +### Complexity Settings + +```toml +[complexity] +warning_threshold = 10 # Yellow warning at this level +error_threshold = 20 # Red error at this level +``` + +**Defaults:** + +- `warning_threshold`: 10 +- `error_threshold`: 20 + +**CLI Override:** + +```bash +mccabre analyze --threshold 15 +``` + +### Clone Detection Settings + +```toml +[clones] +enabled = true # Enable/disable clone detection +min_tokens = 30 # Minimum token sequence length +``` + +**Defaults:** + +- `enabled`: true +- `min_tokens`: 30 + +**CLI Override:** + +```bash +mccabre analyze --min-tokens 25 +``` + +### File Settings + +```toml +[files] +respect_gitignore = true # Honor .gitignore files +``` + +**Defaults:** + +- `respect_gitignore`: true + +**CLI Override:** + +```bash +mccabre analyze --no-gitignore +``` + +## Loading Configuration + +### Automatic Discovery + +Mccabre searches for config files in this order: + +1. `mccabre.toml` +2. `.mccabre.toml` +3. `.mccabre/config.toml` + +The first file found is used. + +### Explicit Path + +Specify a config file: + +```bash +mccabre analyze --config /path/to/config.toml +``` + +### No Config File + +If no config file exists, defaults are used. + +## Example Configurations + +### Strict Mode + +For critical codebases: + +```toml +[complexity] +warning_threshold = 5 +error_threshold = 10 + +[clones] +enabled = true +min_tokens = 20 + +[files] +respect_gitignore = true +``` + +### Lenient Mode + +For legacy codebases: + +```toml +[complexity] +warning_threshold = 20 +error_threshold = 40 + +[clones] +enabled = true +min_tokens = 50 + +[files] +respect_gitignore = true +``` + +### Clone-Focused + +Focus on duplication: + +```toml +[complexity] +warning_threshold = 100 # Effectively disable +error_threshold = 200 + +[clones] +enabled = true +min_tokens = 15 # Very sensitive + +[files] +respect_gitignore = true +``` + +## Per-Project Settings + +Different projects can have different configs: + +```bash +project-a/ + ├── mccabre.toml # Strict settings + └── src/ + +project-b/ + ├── mccabre.toml # Lenient settings + └── src/ +``` + +Each project's config is automatically loaded when analyzing that directory. + +## Viewing Current Configuration + +Check what settings are active: + +```bash +mccabre dump-config +``` + +Output: + +```text +CONFIGURATION +================================================================================ + +Complexity Settings: + Warning threshold: 10 + Error threshold: 20 + +Clone Detection Settings: + Enabled: true + Minimum tokens: 30 + +File Settings: + Respect .gitignore: true +``` + +## Ignoring Files + +Use `.gitignore` to exclude files/directories: + +```text +# .gitignore +target/ +node_modules/ +build/ +*.generated.rs +``` + +Mccabre automatically respects these exclusions. + +To analyze everything (ignore gitignore): + +```bash +mccabre analyze . --no-gitignore +``` + +## See Also + +- [CLI Reference](./cli-reference.md) +- [Cyclomatic Complexity](./cyclomatic-complexity.md) +- [Clone Detection](./clone-detection.md) diff --git a/docs/src/cyclomatic-complexity.md b/docs/src/cyclomatic-complexity.md new file mode 100644 index 0000000..258d1c9 --- /dev/null +++ b/docs/src/cyclomatic-complexity.md @@ -0,0 +1,197 @@ +# Cyclomatic Complexity + +## What is Cyclomatic Complexity? + +Cyclomatic Complexity (CC), introduced by Thomas McCabe in 1976, measures the number of independent paths through a program's source code. It provides a quantitative measure of code complexity. + +## How It Works + +Mccabre uses a simplified formula: + +`CC = (number of decision points) + 1` + +### Decision Points + +A decision point is any control flow statement that creates a branch: + +- `if`, `else if` +- `while`, `for`, `loop` +- `switch`, `match`, `case` +- `catch` +- Logical operators: `&&`, `||` +- Ternary operator: `?` + +### Example + +```javascript +function checkUser(user) { // CC starts at 1 + if (!user) { // +1 = 2 + return false; + } + + if (user.age > 18 && user.verified) { // +1 (if) +1 (&&) = 4 + return true; + } + + return false; +} +// Total CC = 4 +``` + +## Interpretation + +| CC Range | Risk Level | Recommendation | +|----------|-----------|----------------| +| 1-10 | Low | Simple, easy to test | +| 11-20 | Moderate | Consider refactoring | +| 21-50 | High | Should refactor | +| 50+ | Very High | Urgent refactoring needed | + +## Why It Matters + +### Testing Complexity + +Higher CC means: + +- More test cases needed for full coverage +- Higher chance of bugs +- Harder to understand and maintain + +A function with CC=10 requires at least 10 test cases to cover all paths. + +### Maintenance Burden + +Complex functions are: + +- Harder to modify without introducing bugs +- More difficult for new developers to understand +- More prone to subtle edge cases + +## Reducing Complexity + +### Extract Methods + +**Before (CC=8):** + +```javascript +function processOrder(order) { + if (!order.id) throw new Error("No ID"); + if (order.status === "cancelled") return null; + if (order.items.length === 0) throw new Error("No items"); + + let total = 0; + for (let item of order.items) { + if (item.price && item.quantity) { + total += item.price * item.quantity; + } + } + + return total; +} +``` + +**After (CC=3 + 3 = 6 total):** + +```javascript +function processOrder(order) { // CC=3 + validateOrder(order); + return calculateTotal(order); +} + +function validateOrder(order) { // CC=3 + if (!order.id) throw new Error("No ID"); + if (order.status === "cancelled") return null; + if (order.items.length === 0) throw new Error("No items"); +} + +function calculateTotal(order) { // CC=2 + let total = 0; + for (let item of order.items) { + if (item.price && item.quantity) { + total += item.price * item.quantity; + } + } + return total; +} +``` + +### Use Early Returns + +**Before:** + +```rust +fn check(x: i32) -> bool { // CC=3 + let mut result = false; + if x > 0 { + if x < 100 { + result = true; + } + } + result +} +``` + +**After:** + +```rust +fn check(x: i32) -> bool { // CC=3 (same but cleaner) + if x <= 0 { return false; } + if x >= 100 { return false; } + true +} +``` + +### Replace Complex Conditions + +**Before:** + +```javascript +if ((user.role === "admin" || user.role === "moderator") && + user.active && !user.suspended) { // CC contribution: 4 + // ... +} +``` + +**After:** + +```javascript +function canModerate(user) { // CC=3 + const isModerator = user.role === "admin" || user.role === "moderator"; + return isModerator && user.active && !user.suspended; +} + +if (canModerate(user)) { // CC=1 + // ... +} +``` + +## Using with Mccabre + +### Check Specific Files + +```bash +mccabre complexity src/complex.js +``` + +### Set Custom Threshold + +```bash +mccabre complexity . --threshold 15 +``` + +### JSON Output for CI + +```bash +mccabre complexity . --json | jq '.files[] | select(.cyclomatic.file_complexity > 20)' +``` + +## References + +- [McCabe (1976): "A Complexity Measure"](https://www.literateprogramming.com/mccabe.pdf) +- [NIST Special Publication 500-235](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication500-235.pdf) + +## See Also + +- [Lines of Code](./lines-of-code.md) +- [Clone Detection](./clone-detection.md) +- [CLI Reference](./cli-reference.md) diff --git a/docs/src/examples.md b/docs/src/examples.md new file mode 100644 index 0000000..08ba43d --- /dev/null +++ b/docs/src/examples.md @@ -0,0 +1,284 @@ +# Example Usage + +This page demonstrates common Mccabre workflows with real examples. + +## Example Files + +The `examples/` directory contains sample files demonstrating different issues: + +- **complex.js** - High cyclomatic complexity +- **long.py** - Many lines of code with comments +- **not_dry.go** - Duplicated code (clones) + +## Analyzing the Examples + +### Full Analysis + +```bash +mccabre analyze examples/ +``` + +**Output:** + +```text +================================================================================ +MCCABRE CODE ANALYSIS REPORT +================================================================================ + +SUMMARY +-------------------------------------------------------------------------------- +Total files analyzed: 3 +Total physical LOC: 215 +Total logical LOC: 165 +Average complexity: 10.33 +Maximum complexity: 18 +High complexity files: 1 +Clone groups detected: 2 + +FILE METRICS +-------------------------------------------------------------------------------- +FILE: examples/complex.js + Cyclomatic Complexity: 18 (moderate) + Physical LOC: 49 + Logical LOC: 42 + Comment lines: 2 + Blank lines: 5 + +FILE: examples/long.py + Cyclomatic Complexity: 8 (low) + Physical LOC: 95 + Logical LOC: 62 + Comment lines: 18 + Blank lines: 15 + +FILE: examples/not_dry.go + Cyclomatic Complexity: 6 (low) + Physical LOC: 71 + Logical LOC: 61 + Comment lines: 5 + Blank lines: 5 + +DETECTED CLONES +-------------------------------------------------------------------------------- +Clone Group #1 (length: 30 tokens, 3 occurrences) + - examples/not_dry.go:12-26 + - examples/not_dry.go:30-44 + - examples/not_dry.go:48-62 +``` + +### Complexity Only + +```bash +mccabre complexity examples/complex.js +``` + +Shows that `complex.js` has high cyclomatic complexity due to many conditional branches. + +### Clone Detection Only + +```bash +mccabre clones examples/not_dry.go +``` + +Identifies the three nearly-identical functions in `not_dry.go`. + +## Real-World Scenarios + +### Scenario 1: Pre-Commit Check + +Ensure code quality before committing: + +```bash +#!/bin/sh +# .git/hooks/pre-commit + +echo "Checking code complexity..." + +# Get list of staged Rust files +FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.rs$') + +if [ -n "$FILES" ]; then + mccabre complexity $FILES --threshold 15 + if [ $? -ne 0 ]; then + echo "❌ Complexity check failed!" + exit 1 + fi +fi + +echo "✅ Complexity check passed!" +``` + +### Scenario 2: Finding Refactoring Targets + +Combine complexity and clone detection: + +```bash +# Find high-complexity files +echo "=== High Complexity Files ===" +mccabre complexity src/ --json | \ + jq -r '.files[] | select(.cyclomatic.file_complexity > 15) | .path' + +# Find duplicated code +echo "\n=== Code Clones ===" +mccabre clones src/ --min-tokens 25 +``` + +Then refactor the flagged files. + +### Scenario 3: Tracking Technical Debt + +Weekly complexity tracking: + +```bash +#!/bin/bash +# weekly-report.sh + +DATE=$(date +%Y-%m-%d) +REPORT_DIR="reports" + +mkdir -p "$REPORT_DIR" + +# Generate report +mccabre analyze src/ --json > "$REPORT_DIR/report-$DATE.json" + +# Extract key metrics +echo "Complexity Report - $DATE" +jq '.summary' "$REPORT_DIR/report-$DATE.json" + +# Compare with last week +LAST_WEEK=$(ls -t $REPORT_DIR/report-*.json | sed -n 2p) +if [ -n "$LAST_WEEK" ]; then + echo "\nChange from last week:" + jq -s '.[1].summary.avg_complexity - .[0].summary.avg_complexity' \ + "$LAST_WEEK" "$REPORT_DIR/report-$DATE.json" +fi +``` + +### Scenario 5: Code Review Automation + +Automatically comment on PRs with complexity issues: + +```bash +#!/bin/bash +# pr-comment.sh + +# Run analysis +mccabre analyze src/ --json > complexity.json + +# Extract high complexity files +HIGH_COMPLEXITY=$(jq -r '.files[] | select(.cyclomatic.file_complexity > 15) | + "- `\(.path)`: Complexity \(.cyclomatic.file_complexity)"' complexity.json) + +if [ -n "$HIGH_COMPLEXITY" ]; then + # Post comment to GitHub PR (requires gh CLI) + gh pr comment --body "## ⚠️ Complexity Warning + +Files with high complexity: +$HIGH_COMPLEXITY + +Consider refactoring before merging." +fi +``` + +### Scenario 5: Compare Branches + +Compare complexity between branches: + +```bash +#!/bin/bash +# compare-branches.sh + +MAIN_BRANCH="main" +FEATURE_BRANCH=$(git branch --show-current) + +# Analyze main +git checkout "$MAIN_BRANCH" +mccabre analyze src/ --json > /tmp/main-complexity.json + +# Analyze feature +git checkout "$FEATURE_BRANCH" +mccabre analyze src/ --json > /tmp/feature-complexity.json + +# Compare +echo "=== Complexity Comparison ===" +echo "Main branch avg: $(jq '.summary.avg_complexity' /tmp/main-complexity.json)" +echo "Feature branch avg: $(jq '.summary.avg_complexity' /tmp/feature-complexity.json)" + +# Check if complexity increased +MAIN_AVG=$(jq '.summary.avg_complexity' /tmp/main-complexity.json) +FEATURE_AVG=$(jq '.summary.avg_complexity' /tmp/feature-complexity.json) + +if (( $(echo "$FEATURE_AVG > $MAIN_AVG * 1.1" | bc -l) )); then + echo "❌ Complexity increased by more than 10%!" + exit 1 +else + echo "✅ Complexity is acceptable" +fi +``` + +## Filtering and Processing + +### Find Files Above Threshold + +```bash +# JSON output piped to jq +mccabre complexity src/ --json | \ + jq '.files[] | select(.cyclomatic.file_complexity > 10) | .path' +``` + +### Count Clone Groups + +```bash +mccabre clones src/ --json | jq '.clones | length' +``` + +### Generate HTML + +```bash +# Create HTML from JSON +mccabre analyze src/ --json | \ + jq -r '.files[] | "\(.path)\(.cyclomatic.file_complexity)"' | \ + (echo "" && cat && echo "
") > report.html +``` + +### Summary Statistics + +```bash +# Extract just the summary +mccabre analyze src/ --json | jq '.summary' +``` + +## Tips and Tricks + +### Incremental Analysis + +Analyze only changed files: + +```bash +# Files changed in last commit +git diff --name-only HEAD~1 | grep '\.rs$' | xargs mccabre complexity +``` + +### Watch Mode + +Continuously monitor (requires `watch` or `entr`): + +```bash +# Using entr +ls src/**/*.rs | entr mccabre complexity src/ +``` + +### Focus on New Code + +Analyze only files in your feature branch: + +```bash +git diff --name-only main...HEAD | grep '\.rs$' | xargs mccabre analyze +``` + +## Next Steps + +- Read about [Cyclomatic Complexity](./cyclomatic-complexity.md) +- Learn about [Clone Detection](./clone-detection.md) +- Configure [thresholds](./configuration.md) +- Check the [CLI Reference](./cli-reference.md) diff --git a/docs/src/installation.md b/docs/src/installation.md new file mode 100644 index 0000000..ef5e69d --- /dev/null +++ b/docs/src/installation.md @@ -0,0 +1,56 @@ +# Installation + +## From Source + +### Prerequisites + +- Rust 1.70 or later +- Cargo (comes with Rust) + +### Build and Install + +```bash +# Clone the repository +git clone https://github.com/yourusername/mccabre.git +cd mccabre + +# Build and install +cargo install --path crates/cli +``` + +### Development Build + +```bash +# Build in debug mode +cargo build + +# Run directly +cargo run --bin mccabre -- analyze examples/ + +# Run tests +cargo test --quiet +``` + +## Verifying Installation + +After building, verify your installation: + +```bash +mccabre --version + +mccabre --help + +mccabre analyze examples/ +``` + +You should see colored output with complexity metrics and detected clones. + +## Uninstall + +```bash +cargo uninstall mccabre +``` + +## Configuration + +See [Configuration](./configuration.md) for customizing thresholds and behavior. diff --git a/docs/src/introduction.md b/docs/src/introduction.md new file mode 100644 index 0000000..020d57f --- /dev/null +++ b/docs/src/introduction.md @@ -0,0 +1,29 @@ +# Introduction + +**Mccabre** language-agnostic code complexity and clone detection tool designed to help developers identify problematic & repeated code patterns. + +## Features + +- **Cyclomatic Complexity Analysis**: Measure control-flow complexity using McCabe's algorithm +- **Lines of Code Metrics**: Count physical, logical, comment, and blank lines +- **Clone Detection**: Find duplicated code using Rabin-Karp rolling hash +- **Multi-Language Support**: Rust, JavaScript/TypeScript, Go, Java, and C++ +- **Gitignore Aware**: Automatically respects .gitignore files +- **Multiple Output Formats**: Beautiful terminal output or JSON + +## Design Philosophy + +Mccabre prioritizes: + +1. **Speed**: Linear or near-linear performance through tokenization instead of full parsing +2. **Simplicity**: Easy to use with sensible defaults +3. **Actionability**: Clear, color-coded output highlighting issues +4. **Extensibility**: Modular design allowing future enhancements + +## Limitations + +- **Token-based**: Function detection is heuristic-based and may miss some functions +- **Language Support**: Currently supports C-style languages; Python coming soon +- **Clone Detection**: Finds exact token matches, not semantic equivalence (yet) + +See the [Quick Start](./quickstart.md) guide to begin using Mccabre. diff --git a/docs/src/lines-of-code.md b/docs/src/lines-of-code.md new file mode 100644 index 0000000..fa8cf22 --- /dev/null +++ b/docs/src/lines-of-code.md @@ -0,0 +1,193 @@ +# Lines of Code (LOC) + +## Overview + +Lines of Code (LOC) is a fundamental software metric that measures the size of a codebase. Mccabre provides several LOC variants to give you a complete picture. + +## Metrics Provided + +### Physical LOC + +Total number of lines in the file, including everything. + +```rust +fn hello() { // Line 1 + + + println!("Hi"); // Line 4 +} // Line 5 +// Physical LOC = 5 +``` + +### Logical LOC + +Non-blank, non-comment lines that contain actual code. + +```rust +fn hello() { // Logical + // This is a comment (not counted) + + println!("Hi"); // Logical +} // Logical +// Logical LOC = 3 +``` + +### Comment Lines + +Lines that contain comments (single-line or multi-line). + +```rust +// This is counted // Comment line +/* This too */ // Comment line +let x = 5; // inline // Code line (code takes precedence) +``` + +### Blank Lines + +Lines that contain only whitespace. + +## Why LOC Matters + +### Productivity Tracking + +- Monitor codebase growth over time +- Estimate project size +- Compare different implementations + +### Maintenance Effort + +Larger codebases generally require: + +- More time to understand +- More effort to maintain +- More potential for bugs + +### Code Density + +Compare logical vs physical LOC: + +- High ratio (logical/physical): Dense code, few comments +- Low ratio: Well-commented, more likely to be readable code + +## Using LOC with Mccabre + +### Basic Usage + +```bash +# Analyze LOC for a directory +mccabre analyze src/ + +# Complexity command also includes LOC +mccabre complexity src/ +``` + +### Sample Output + +```text +FILE: src/main.rs + Cyclomatic Complexity: 5 + Physical LOC: 120 + Logical LOC: 85 + Comment lines: 25 + Blank lines: 10 +``` + +### JSON + +```bash +mccabre analyze src/ --json +``` + +```json +{ + "files": [ + { + "path": "src/main.rs", + "loc": { + "physical": 120, + "logical": 85, + "comments": 25, + "blank": 10 + } + } + ] +} +``` + +## Interpreting Results + +### Healthy Ratios + +**Comment Ratio**: `comments / logical` + +- 0.1-0.3 (10-30%): Generally good +- <0.05: Likely under-commented +- >0.5: Possibly over-commented or tutorial code + +**Code Density**: `logical / physical` + +- 0.6-0.8: Good balance +- <0.5: Many blank lines/comments (verbose) +- >0.9: Very dense (potentially hard to read) + +## LOC Limitations + +### Not a Quality Metric + +More LOC doesn't mean: + +- ✗ Better code +- ✗ More features +- ✗ More value + +### Context Matters + +Compare LOC only within similar contexts: + +- Same language +- Same problem domain +- Same team/style + +### Generated Code + +LOC counts everything, including: + +- Auto-generated code +- Vendored dependencies +- Build artifacts + +Use `.gitignore` to exclude these (Mccabre respects gitignore). + +## Tracking LOC Over Time + +### Baseline + +```bash +# Create baseline +mccabre analyze src/ --json > baseline.json +``` + +### Compare + +```bash +# Later... +mccabre analyze src/ --json > current.json + +# Compare (using jq) +jq '.summary.total_logical_loc' baseline.json +jq '.summary.total_logical_loc' current.json +``` + +### Visualize Growth + +Integrate with your CI to track: + +- LOC growth per sprint +- LOC per feature +- Comment ratio trends + +## See Also + +- [Cyclomatic Complexity](./cyclomatic-complexity.md) +- [Clone Detection](./clone-detection.md) +- [Configuration](./configuration.md) diff --git a/docs/src/quickstart.md b/docs/src/quickstart.md new file mode 100644 index 0000000..0e84b73 --- /dev/null +++ b/docs/src/quickstart.md @@ -0,0 +1,83 @@ +# Quick Start + +This guide will get you analyzing code in minutes. + +## Basic Usage + +### Analyze Everything + +Run a full analysis on a directory: + +```bash +mccabre analyze ./src +``` + +This will show: + +- Cyclomatic complexity per file +- Lines of code metrics +- Detected code clones + +### Complexity Only + +To check only complexity metrics: + +```bash +mccabre complexity ./src +``` + +### Clone Detection Only + +To find duplicated code: + +```bash +mccabre clones ./src +``` + +## Understanding the Output + +### Terminal Output + +Mccabre uses colors to highlight issues: + +- **Green**: Low complexity (1-10) +- **Yellow**: Moderate complexity (11-20) +- **Red**: High complexity (21+) + +Example output: + +```text +================================================================================ +MCCABRE CODE ANALYSIS REPORT +================================================================================ + +SUMMARY +-------------------------------------------------------------------------------- +Total files analyzed: 5 +Total physical LOC: 450 +Total logical LOC: 320 +Average complexity: 8.50 +Maximum complexity: 18 +High complexity files: 2 +Clone groups detected: 3 + +FILE METRICS +-------------------------------------------------------------------------------- +FILE: src/utils.rs + Cyclomatic Complexity: 5 (low) + Physical LOC: 45 + Logical LOC: 32 +``` + +### JSON + +```bash +mccabre analyze ./src --json > report.json +``` + +## Next Steps + +- Learn about [Cyclomatic Complexity](./cyclomatic-complexity.md) +- Understand [Clone Detection](./clone-detection.md) +- Configure [thresholds](./configuration.md) +- See more [examples](./examples.md) diff --git a/examples/complex.js b/examples/complex.js new file mode 100644 index 0000000..e52aeae --- /dev/null +++ b/examples/complex.js @@ -0,0 +1,48 @@ +// Example of high cyclomatic complexity +function processUserData(user, options) { + if (!user) { + throw new Error("User required"); + } + + if (user.age < 18 && !options.allowMinors) { + return { error: "User too young" }; + } + + if (user.country === "US" || user.country === "CA") { + if (user.state && user.state.length === 2) { + console.log("North American user"); + } + } else if (user.country === "UK" || user.country === "IE") { + console.log("European user"); + } + + const result = {}; + + if (options.includeEmail && user.email) { + result.email = user.email.toLowerCase(); + } + + if (options.includePhone && user.phone) { + result.phone = user.phone.replace(/\D/g, ''); + } + + if (user.premium || (user.credits && user.credits > 100)) { + result.tier = "premium"; + } else if (user.credits && user.credits > 10) { + result.tier = "standard"; + } else { + result.tier = "basic"; + } + + for (let i = 0; i < user.preferences.length; i++) { + const pref = user.preferences[i]; + if (pref.enabled && pref.value !== null) { + result.preferences = result.preferences || []; + result.preferences.push(pref); + } + } + + return result; +} + +// Cyclomatic complexity: ~15-20 diff --git a/examples/long.py b/examples/long.py new file mode 100644 index 0000000..6e6a45f --- /dev/null +++ b/examples/long.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +""" +Example Python file with many lines of code +Demonstrates LOC counting with comments and blank lines +""" + +import sys +import os +from typing import List, Dict, Optional + + +class DataProcessor: + """Process data with various transformations""" + + def __init__(self, config: Dict): + self.config = config + self.data = [] + self.results = {} + + def load_data(self, filepath: str) -> bool: + """Load data from a file""" + try: + with open(filepath, 'r') as f: + self.data = [line.strip() for line in f] + return True + except FileNotFoundError: + print(f"File not found: {filepath}") + return False + + def process(self) -> List[str]: + """Process the loaded data""" + results = [] + + for item in self.data: + # Skip empty lines + if not item: + continue + + # Transform the item + transformed = self._transform(item) + + # Validate + if self._validate(transformed): + results.append(transformed) + + return results + + def _transform(self, item: str) -> str: + """Transform a single item""" + # Convert to lowercase + item = item.lower() + + # Remove special characters + item = ''.join(c for c in item if c.isalnum() or c.isspace()) + + # Trim whitespace + item = item.strip() + + return item + + def _validate(self, item: str) -> bool: + """Validate an item""" + if len(item) < 3: + return False + + if not any(c.isalpha() for c in item): + return False + + return True + + def save_results(self, output_path: str) -> None: + """Save processed results""" + with open(output_path, 'w') as f: + for item in self.results: + f.write(f"{item}\n") + + +def main(): + """Main entry point""" + if len(sys.argv) < 2: + print("Usage: python long.py ") + sys.exit(1) + + processor = DataProcessor({"strict": True}) + processor.load_data(sys.argv[1]) + results = processor.process() + + print(f"Processed {len(results)} items") + + +if __name__ == "__main__": + main() + +# Physical LOC: ~95 +# Logical LOC: ~60 +# Comment lines: ~15 +# Blank lines: ~20 diff --git a/examples/not_dry.go b/examples/not_dry.go new file mode 100644 index 0000000..17d15e7 --- /dev/null +++ b/examples/not_dry.go @@ -0,0 +1,85 @@ +package main + +import ( + "fmt" + "strings" +) + +// Example demonstrating code duplication (clones) +// Multiple functions have similar logic + +func processUserInput(input string) string { + // Trim and validate input + trimmed := strings.TrimSpace(input) + if len(trimmed) == 0 { + return "" + } + + // Convert to lowercase + lower := strings.ToLower(trimmed) + + // Remove special characters + cleaned := "" + for _, char := range lower { + if (char >= 'a' && char <= 'z') || (char >= '0' && char <= '9') || char == ' ' { + cleaned += string(char) + } + } + + return cleaned +} + +func processProductName(name string) string { + // Trim and validate input + trimmed := strings.TrimSpace(name) + if len(trimmed) == 0 { + return "" + } + + // Convert to lowercase + lower := strings.ToLower(trimmed) + + // Remove special characters + cleaned := "" + for _, char := range lower { + if (char >= 'a' && char <= 'z') || (char >= '0' && char <= '9') || char == ' ' { + cleaned += string(char) + } + } + + return cleaned +} + +func sanitizeFileName(filename string) string { + // Trim and validate input + trimmed := strings.TrimSpace(filename) + if len(trimmed) == 0 { + return "" + } + + // Convert to lowercase + lower := strings.ToLower(trimmed) + + // Remove special characters + cleaned := "" + for _, char := range lower { + if (char >= 'a' && char <= 'z') || (char >= '0' && char <= '9') || char == ' ' { + cleaned += string(char) + } + } + + return cleaned +} + +func main() { + user := processUserInput(" Hello World! ") + product := processProductName(" Test Product 123 ") + file := sanitizeFileName(" my_file.txt ") + + fmt.Println(user) + fmt.Println(product) + fmt.Println(file) +} + +// This file has obvious code duplication across the three functions +// Clone detection should identify the repeated token sequences -- 2.51.2