Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
os: [macos-15, ubuntu-22.04]
swift-version: ["6.1"]
swift-version: ["6.2"]
runs-on: ${{ matrix.os }}

steps:
Expand All @@ -27,7 +27,7 @@ jobs:
if: runner.os == 'macOS'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "16.4"
xcode-version: "26.1"

- name: Setup Swift
run: |
Expand Down Expand Up @@ -61,12 +61,35 @@ jobs:
restore-keys: |
${{ runner.os }}-swift-${{ matrix.swift-version }}-

- name: Build package
- name: Build package (macOS)
if: runner.os == 'macOS'
run: swift build --configuration release

- name: Run tests
- name: Build package (Linux)
if: runner.os == 'Linux'
run: |
# Swiftly installs toolchains to ~/.local/share/swiftly/toolchains/<version>/
# 'which swift' returns the wrapper, so we need to get the actual toolchain path
SWIFTLY_HOME="${HOME}/.local/share/swiftly"
TOOLCHAIN_USR=$(ls -d ${SWIFTLY_HOME}/toolchains/*/usr 2>/dev/null | head -1)
echo "Using toolchain: ${TOOLCHAIN_USR}"
swift build --configuration release \
-Xcxx -I${TOOLCHAIN_USR}/lib/swift \
-Xcxx -I${TOOLCHAIN_USR}/lib/swift/Block

- name: Run tests (macOS)
if: runner.os == 'macOS'
run: swift test

- name: Run tests (Linux)
if: runner.os == 'Linux'
run: |
SWIFTLY_HOME="${HOME}/.local/share/swiftly"
TOOLCHAIN_USR=$(ls -d ${SWIFTLY_HOME}/toolchains/*/usr 2>/dev/null | head -1)
swift test \
-Xcxx -I${TOOLCHAIN_USR}/lib/swift \
-Xcxx -I${TOOLCHAIN_USR}/lib/swift/Block

lint:
name: Code Quality
runs-on: macos-15
Expand All @@ -78,7 +101,7 @@ jobs:
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "16.4"
xcode-version: "26.1"

- name: Setup Swift
run: |
Expand Down Expand Up @@ -125,7 +148,7 @@ jobs:
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "16.4"
xcode-version: "26.1"

- name: Setup Swift
run: |
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,22 @@ jobs:
if: runner.os == 'macOS'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "16.4"
xcode-version: "26.1"

- name: Setup Swift
run: |
if [[ "$RUNNER_OS" == "Linux" ]]; then
# Install dependencies for Swiftly
sudo apt-get update && sudo apt-get install -y libcurl4-openssl-dev pkg-config python3-lldb-13

# Install Swiftly
curl -O https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz
tar zxf swiftly-$(uname -m).tar.gz
./swiftly init --verbose --assume-yes --skip-install
. ~/.local/share/swiftly/env.sh

# Install Swift
swiftly install 6.1
swiftly install 6.2
echo "$(dirname $(which swift))" >> $GITHUB_PATH
else
# macOS - use Xcode's Swift
Expand Down Expand Up @@ -112,22 +112,22 @@ jobs:
if: runner.os == 'macOS'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "16.4"
xcode-version: "26.1"

- name: Setup Swift
run: |
if [[ "$RUNNER_OS" == "Linux" ]]; then
# Install dependencies for Swiftly
sudo apt-get update && sudo apt-get install -y libcurl4-openssl-dev pkg-config python3-lldb-13

# Install Swiftly
curl -O https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz
tar zxf swiftly-$(uname -m).tar.gz
./swiftly init --verbose --assume-yes --skip-install
. ~/.local/share/swiftly/env.sh

# Install Swift
swiftly install 6.1
swiftly install 6.2
echo "$(dirname $(which swift))" >> $GITHUB_PATH
else
# macOS - use Xcode's Swift
Expand Down
10 changes: 6 additions & 4 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,19 @@
"args": [],
"cwd": "${workspaceFolder:swift-complexity}",
"name": "Debug SwiftComplexityCLI",
"program": "${workspaceFolder:swift-complexity}/.build/debug/SwiftComplexityCLI",
"preLaunchTask": "swift: Build Debug SwiftComplexityCLI"
"preLaunchTask": "swift: Build Debug SwiftComplexityCLI",
"target": "SwiftComplexityCLI",
"configuration": "debug"
},
{
"type": "swift",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:swift-complexity}",
"name": "Release SwiftComplexityCLI",
"program": "${workspaceFolder:swift-complexity}/.build/release/SwiftComplexityCLI",
"preLaunchTask": "swift: Build Release SwiftComplexityCLI"
"preLaunchTask": "swift: Build Release SwiftComplexityCLI",
"target": "SwiftComplexityCLI",
"configuration": "release"
}
]
}
22 changes: 20 additions & 2 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 6.1
// swift-tools-version: 6.2
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand All @@ -24,15 +24,19 @@ let package = Package(
),
],
dependencies: [
.package(url: "https://github.com/apple/swift-syntax.git", from: "600.0.0"),
.package(url: "https://github.com/swiftlang/swift-syntax.git", from: "600.0.0"),
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0"),
// IndexStore-DB integration (for LCOM4 semantic analysis)
.package(url: "https://github.com/swiftlang/indexstore-db", branch: "main"),
],
targets: [
.target(
name: "SwiftComplexityCore",
dependencies: [
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftParser", package: "swift-syntax"),
// IndexStore-DB integration (for LCOM4 semantic analysis)
.product(name: "IndexStoreDB", package: "indexstore-db"),
],
path: "Sources/SwiftComplexityCore",
),
Expand Down
43 changes: 37 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ A command-line tool to analyze Swift code complexity and quality metrics using s

## Features

- **Multiple Complexity Metrics**: Supports cyclomatic and cognitive complexity analysis
- **Multiple Complexity Metrics**: Supports cyclomatic complexity, cognitive complexity, and LCOM4 cohesion analysis
- **LCOM4 Class Cohesion**: High-precision (90-95%) class cohesion measurement using IndexStore-DB semantic analysis
- **Web-based Debug Interface**: Interactive browser-based complexity analyzer ([Try it online](https://swift-complexity.fummicc1.dev))
- **Xcode Integration**: Seamless integration with Xcode via Build Tool Plugin for complexity feedback during build phase
- **Xcode Diagnostics**: Display complexity warnings and errors directly in Xcode editor with accurate line numbers
Expand Down Expand Up @@ -46,6 +47,10 @@ swift run SwiftComplexityCLI Sources --format json --recursive

# Xcode diagnostics format (for IDE integration)
swift run SwiftComplexityCLI Sources --format xcode --threshold 15

# LCOM4 class cohesion analysis (requires swift build first)
swift build # Generate index
swift run SwiftComplexityCLI Sources --lcom4 --project-root .
```

## CLI Integration
Expand All @@ -65,10 +70,18 @@ swift run SwiftComplexityCLI Sources --threshold 15 --recursive

## Supported Complexity Metrics

### Function-level Metrics

- **Cyclomatic Complexity**: Measures the number of linearly independent paths through code
- **Cognitive Complexity**: Measures how difficult code is for humans to understand

*Future metrics planned: LCOM, cohesion/coupling indicators*
### Class-level Metrics

- **LCOM4 (Lack of Cohesion of Methods)**: Measures class cohesion by analyzing method-property relationships
- **Connected Components**: Counts independent groups of related methods
- **High Precision**: 90-95% accuracy using IndexStore-DB semantic analysis
- **Implicit self Detection**: Automatically detects both `self.property` and `property` accesses
- **Requirements**: Requires `swift build` to generate index data

## Documentation

Expand Down Expand Up @@ -100,11 +113,15 @@ Unified package with multiple components:
# Analyze with verbose output
swift run SwiftComplexityCLI Sources --verbose --recursive

# Exclude test files with pattern matching
# Exclude test files with pattern matching
swift run SwiftComplexityCLI Sources --recursive --exclude "*Test*.swift"

# Show only cognitive complexity above threshold
swift run SwiftComplexityCLI Sources --cognitive-only --threshold 5

# Analyze class cohesion with LCOM4
swift build # Generate index first
swift run SwiftComplexityCLI Sources --lcom4 --project-root . --format json
```

## Xcode Build Tool Plugin
Expand All @@ -114,7 +131,7 @@ Integrates with both Swift Package Manager and Xcode projects for automatic comp
### Swift Package Manager Integration

```swift
// swift-tools-version: 6.1
// swift-tools-version: 6.2
import PackageDescription

let package = Package(
Expand Down Expand Up @@ -173,6 +190,14 @@ File: Sources/ComplexityAnalyzer.swift
+------------------+----------+----------+

Total: 2 functions, Average Cyclomatic: 4.0, Average Cognitive: 4.5

Class Cohesion (LCOM4):
+------------------+----------+----------+----------+----------+
| Class/Struct | LCOM4 | Methods | Props | Level |
+------------------+----------+----------+----------+----------+
| ComplexityAnalyzer| 1 | 5 | 3 | High |
| FileProcessor | 2 | 8 | 4 | Moderate |
+------------------+----------+----------+----------+----------+
```

### Xcode Diagnostics Output
Expand All @@ -186,14 +211,20 @@ Total: 2 functions, Average Cyclomatic: 4.0, Average Cognitive: 4.5

### CLI Tool

- Swift 6.1+
- Swift 6.2+
- macOS 14+ or Linux

### Core Library

- Swift 6.1+
- Swift 6.2+
- macOS 14+, iOS 13+, or Linux

### LCOM4 Feature (Optional)

- **macOS 14+ only** (Linux not yet supported due to IndexStore-DB dependency)
- Project must be buildable with `swift build`
- Index data at `.build/index/store` (generated by build)

## License

MIT License
Loading
Loading