Skip to content

Optimize SwiftLint: switch to only_rules for 16x speedup #47

@kj6dev

Description

@kj6dev

Summary

SwiftLint is running 88 "invisible" default-ON rules not explicitly mentioned in our config. Switching from disabled_rules to only_rules yields a 16x performance improvement (1.27s → 0.08s).

Benchmarks

Config Time Rules
Current (disabled_rules) 1.27s 107 rules
only_rules (no SourceKit) 0.08s 20 rules
swiftlintcustom-smart 0.07s 14 rules

Root Cause

SwiftLint loads ALL ~200 rules, then applies disabled_rules filter. With only_rules, it only loads what you specify.

Additionally, many enabled rules use SourceKit (compiler queries), which are slow:

  • file_length
  • statement_position
  • trailing_whitespace
  • vertical_whitespace
  • unused_import (analyzer)

Invisible Default Rules (88 total)

These rules are running but not mentioned in our config:

Formatting/Style (SwiftFormat handles these)

  • closing_brace, colon, comma, leading_whitespace
  • operator_whitespace, return_arrow_whitespace
  • statement_position, trailing_newline, trailing_semicolon
  • trailing_whitespace, vertical_whitespace, vertical_parameter_alignment

Redundancy Detection (useful, keep)

  • redundant_discardable_let, redundant_objc_attribute
  • redundant_optional_initialization, redundant_set_access_control
  • redundant_string_enum_value, redundant_void_return, redundant_sendable

Legacy Code Detection (useful, keep)

  • legacy_cggeometry_functions, legacy_constant, legacy_constructor
  • legacy_hashing, legacy_random, legacy_nsgeometry_functions

Safety/Bugs (useful, keep)

  • force_cast, force_try, duplicate_enum_cases, duplicate_imports
  • unused_closure_parameter, unused_optional_binding, unused_enumerated
  • duplicate_conditions, duplicated_key_in_dictionary_literal

Low Value / Noise (consider dropping)

  • block_based_kvo, class_delegate_protocol, xctfail_message
  • valid_ibinspectable, private_unit_test, nsobject_prefer_isequal
  • inclusive_language, orphaned_doc_comment

Metrics (configured but using SourceKit)

  • cyclomatic_complexity, file_length, function_parameter_count
  • large_tuple, nesting, type_body_length, type_name

Recommendations

1. Create only_rules config

Replace disabled_rules approach with explicit allowlist:

only_rules:
  # Safety
  - force_cast
  - force_try
  - duplicate_imports
  - duplicate_enum_cases
  - unused_closure_parameter
  
  # Redundancy
  - redundant_discardable_let
  - redundant_optional_initialization
  - redundant_void_return
  
  # Legacy
  - legacy_constructor
  - legacy_random
  
  # Metrics (non-SourceKit)
  - cyclomatic_complexity
  - nesting
  
  # Opt-ins we already use
  - empty_count
  - empty_string
  - first_where
  - last_where
  - toggle_bool
  - implicit_return

2. Remove rules SwiftFormat handles

These are redundant with SwiftFormat:

  • All spacing rules (colon, comma, operator_whitespace, etc.)
  • trailing_whitespace, trailing_newline
  • vertical_whitespace

3. Consider two configs

Configs/
├── shared-swiftlint.yml         # Full config for CI
└── shared-swiftlint-fast.yml    # Fast config for local dev

Add --fast flag to swiftlint-smart for quick iterations.

4. Move SourceKit rules to CI-only

These are valuable but slow:

  • unused_import (analyzer rule)
  • file_length

Run them in CI, not on every save.

Impact

  • 16x faster local linting
  • Explicit control over what runs
  • No duplicate checks with SwiftFormat
  • Same coverage for meaningful rules

Related

  • Custom rules already achieve 0.07s by being focused and avoiding SourceKit
  • SwiftFormat handles most style/formatting rules better

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions