ITADN
Joseph-Cursio/SwiftProjectLint
Joseph-Cursio/SwiftProjectLint · 文件
文件最后提交记录最后更新时间
README.md

THIS IS AN EXPERIMENT IN VIBE-CODING.

I'm letting AI do nearly all the work in generating code and tests. I have tested examining rules and simulations out with a real project only a few times. I've never looked at what the YAML part does (or does not do). I allowed different AIs to hallucinate the market potential of this experiment.

Many of these rules exist just so I can explore the effects. Some rules are bad ideas, some are poorly implemented, and some are bad ideas poorly implemented. I did not want to pollute SwiftLint with these ideas (and I don't know how to create a custom rule in SwiftLint anyway.)

Swift Project Linter

A static analysis tool for SwiftUI projects that detects architectural issues, performance problems, and code quality concerns. Parses Swift source files using SwiftSyntax AST visitors to identify anti-patterns across 201 rules in 13 categories.

The origin of this project began with a limitation of SwiftLint: it processes a single file at a time and cannot identify cross-file issues.

Motivating example: I was watching the "Quality Coding" channel on YouTube. Jon Reid had a playlist in which he was exploring TDD with SwiftUI. ALong the way he ended up with a @State variable in a window, and the same @State variable in a child window. The second definition in the child window shadows the first definition, but it is not the same variable. To use the same variable, the child window needs @Binding instead. Jon remarked that this would be a good candicate for a SwiftLint rule. But the child window can be in a different file than the parent window... See Docs/rules/related-duplicate-state-variable.md

Features

  • SwiftSyntax AST Analysis: Precise, AST-based pattern detection — no regex
  • Cross-File Analysis: Detects issues spanning multiple files (duplicate state, view hierarchies)
  • 201 Lint Rules across 13 categories
  • Three delivery targets: macOS app GUI, CLI for CI/CD, and a reusable Core library
  • YAML configuration: .swiftprojectlint.yml for per-project rule customization
  • Inline suppression: // swiftprojectlint:disable comments for per-line control
  • Type-safe rule system: RuleIdentifier enum for all rules and categories
  • Architectural layer enforcement: Declare layer boundaries in YAML; imports and type references that violate them are flagged
  • Warning-suppression detection: Flags misuse of @_disfavoredOverload, @retroactive, @preconcurrency, and @discardableResult

Targets

TargetTypeDescription
CoreLibraryThin facade that re-exports all local packages
AppmacOS AppSwiftUI interface with rule selection and results display
CLIExecutableCommand-line tool for CI/CD integration

CLI Usage

# Analyze a project (text output)
swift run CLI /path/to/project

# JSON output for CI integration
swift run CLI /path/to/project --format json

# Filter by category and severity threshold
swift run CLI /path/to/project --categories stateManagement,performance --threshold error

Rules

201 rules across 13 categories. See Docs/rules/RULES.md for the full reference.

CategoryRules
State Management13
Performance14
Animation10
Architecture32
Code Quality52
Security5
Accessibility20
Memory Management3
Networking3
UI Patterns7
Modernization25
Idempotency7
Testability10

Rules marked opt-in are disabled by default and must be explicitly listed under enabled_only in .swiftprojectlint.yml.

Architecture

Package Structure

The project uses six local Swift packages under Packages/, with Core as a thin umbrella that re-exports them all via @_exported import.

SwiftProjectLint/
├── Package.swift
├── Sources/
│   ├── Core/              # Thin umbrella re-exporting all local packages
│   ├── App/               # macOS SwiftUI app
│   └── CLI/               # Command-line tool
├── Packages/
│   ├── SwiftProjectLintModels/    # Value types (LintIssue, RuleIdentifier, etc.)
│   ├── SwiftProjectLintVisitors/  # Base visitor infrastructure
│   ├── SwiftProjectLintRegistry/  # Pattern registration and detection engine
│   ├── SwiftProjectLintConfig/    # YAML config, file discovery, suppression
│   ├── SwiftProjectLintRules/     # All rule implementations by category
│   └── SwiftProjectLintEngine/    # Analysis pipeline orchestration
├── Tests/
│   ├── CoreTests/         # Lint rule unit tests
│   ├── AppTests/          # SwiftUI view tests (ViewInspector)
│   └── CLITests/          # CLI integration tests
└── Docs/
    ├── architecture.md    # Architecture deep-dive
    ├── reference.md       # CLI and configuration reference
    ├── user-guide.md      # User guide
    ├── tutorial.md        # Getting started tutorial
    └── rules/             # Per-rule documentation (one file per rule)

Dependency Graph

SwiftProjectLintModels          (no dependencies)
        |
SwiftProjectLintVisitors        (+ SwiftSyntax)
        |
SwiftProjectLintRegistry        (+ SwiftSyntax)
    |           |
SwiftProjectLintConfig      SwiftProjectLintRules
    (+ Yams)                    (+ SwiftSyntax)
        |           |
SwiftProjectLintEngine
        |
       Core  <--  App / CLI

Analysis Pipeline

1. File Discovery       -> FileAnalysisUtils finds all .swift files
2. Pre-scans            -> Collect cross-file type metadata
3. Per-file analysis    -> Concurrent task group, one task per file:
       Parser.parse()            parse source into AST
       SourcePatternDetector     run visitors against the AST
       InlineSuppressionFilter   remove suppressed issues
4. Cross-File Analysis  -> CrossFileAnalysisEngine detects multi-file issues
5. Configuration        -> Apply severity overrides and path exclusions

Build & Test

# Build
swift build

# Run all tests
swift test

# Run a specific test suite
swift test --filter CoreTests.ArchitectureFatViewTests

# Run a specific test method
swift test --filter "CoreTests.ArchitectureFatViewTests/testFatViewDetection"

# Run with code coverage
swift test --enable-code-coverage

Configuration

Create .swiftprojectlint.yml in your project root:

# Disable specific rules
disabled_rules:
  - "Missing Documentation"
  - "TODO Comment"

# Or run only specific rules (mutually exclusive with disabled_rules)
enabled_only:
  - "Hardcoded Secret"
  - "Force Unwrap"
  - "Missing Accessibility Label"

# Exclude paths from all rules
excluded_paths:
  - "Tests/"
  - "Generated/"

# Per-rule overrides
rules:
  "Fat View":
    severity: info
  "Force Try":
    excluded_paths:
      - "LegacyViews/"

# Architectural layer boundaries
architectural_layers:
  - name: domain
    paths:
      - "Domain/"
    forbidden_imports:
      - CoreData
      - SwiftData
      - UIKit
      - SwiftUI
    forbidden_types:
      - URLSession
      - UserDefaults
      - NSManagedObject
  - name: presentation
    paths:
      - "ViewModels/"
    forbidden_imports:
      - CoreData

Inline Suppression

// swiftprojectlint:disable:next force-try
let data = try! Data(contentsOf: url)

let threshold = 42 // swiftprojectlint:disable:this magic-number

// swiftprojectlint:disable force-try force-unwrap
let a = try! loadConfig()
let b = result!
// swiftprojectlint:enable force-try force-unwrap

Severity Levels

  • Error: Critical issues (e.g., hardcoded secrets, synchronous network calls)
  • Warning: Issues that should be addressed (e.g., fat views, force unwrap, variable shadowing)
  • Info: Style suggestions and best practices

Dependencies

Documentation

License

This project is for educational and demonstration purposes. Feel free to use and modify for your own projects.