macos: lint Swift files using SwiftLint

SwiftLint <https://realm.github.io/SwiftLint/> is both a linter and
formatting. It's a popular way to spot issues and enforce a consistent
style.

Our SwiftLint configuration lives in macos/.swiftlint.yml, where is is
automatically discovered. It's very configurable, and I made an initial
pass as some basic, weakly-opinionated rules. The "TODO" section lists
rules that currently have violations but can be easily (auto)fixed in
follow-up commits.

Our integration is CLI-based. Similar to our other support tools, we
expect developers to install `swiftlint` via nix or e.g. Homebrew.
This is documented in HACKING.md.

We also have an optional Xcode integration, for in-editor feedback. When
`swiftlint` is available, it's run as a script-based Build Phase.

SwiftLint supports an auto-fix mode (--fix). Agents are aware of this
via AGENTS.md.

The rules are enforced using a (nix-based) CI job.
This commit is contained in:
Jon Parise
2026-02-19 10:55:43 -05:00
parent c11db662e6
commit 21ea94610a
6 changed files with 140 additions and 0 deletions

View File

@@ -36,6 +36,7 @@ jobs:
- test-macos
- pinact
- prettier
- swiftlint
- alejandra
- typos
- shellcheck
@@ -927,6 +928,27 @@ jobs:
- name: prettier check
run: nix develop -c prettier --check .
swiftlint:
if: github.repository == 'ghostty-org/ghostty'
runs-on: namespace-profile-ghostty-macos-tahoe
timeout-minutes: 60
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# TODO(tahoe): https://github.com/NixOS/nix/issues/13342
- uses: DeterminateSystems/nix-installer-action@main
with:
determinate: true
- uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16
with:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
skipPush: true
useDaemon: false # sometimes fails on short jobs
- name: swiftlint check
run: nix develop -c swiftlint lint --strict macos
alejandra:
if: github.repository == 'ghostty-org/ghostty'
runs-on: namespace-profile-ghostty-xsm

View File

@@ -8,6 +8,7 @@ A file for [guiding coding agents](https://agents.md/).
- **Test (Zig):** `zig build test`
- **Test filter (Zig)**: `zig build test -Dtest-filter=<test name>`
- **Formatting (Zig)**: `zig fmt .`
- **Formatting (Swift)**: `swiftlint lint --fix macos`
- **Formatting (other)**: `prettier -w .`
## Directory Structure

View File

@@ -186,6 +186,31 @@ shellcheck \
$(find . \( -name "*.sh" -o -name "*.bash" \) -type f ! -path "./zig-out/*" ! -path "./macos/build/*" ! -path "./.git/*" | sort)
```
### SwiftLint
Swift code is linted using [SwiftLint](https://github.com/realm/SwiftLint). A
SwiftLint CI check will fail builds with improper formatting. Therefore, if you
are modifying Swift code, you may want to install it locally and run this from
the repo root before you commit:
```
swiftlint lint --fix macos
```
Make sure your SwiftLint version matches the version in [devShell.nix](https://github.com/ghostty-org/ghostty/blob/main/nix/devShell.nix).
Nix users can use the following command to format with SwiftLint:
```
nix develop -c swiftlint lint --fix macos
```
To check for violations without auto-fixing:
```
nix develop -c swiftlint lint --strict macos
```
### Updating the Zig Cache Fixed-Output Derivation Hash
The Nix package depends on a [fixed-output

64
macos/.swiftlint.yml Normal file
View File

@@ -0,0 +1,64 @@
# SwiftLint <https://realm.github.io/SwiftLint/>
#
check_for_updates: false
disabled_rules:
- cyclomatic_complexity
- file_length
- function_body_length
- nesting
- todo
- trailing_comma
- trailing_newline
- type_body_length
# TODO
- colon
- comma
- comment_spacing
- control_statement
- deployment_target
- empty_enum_arguments
- empty_parentheses_with_trailing_closure
- for_where
- force_cast
- implicit_getter
- implicit_optional_initialization
- legacy_constant
- legacy_constructor
- line_length
- mark
- multiple_closures_with_trailing_closure
- no_fallthrough_only
- opening_brace
- orphaned_doc_comment
- private_over_fileprivate
- shorthand_operator
- switch_case_alignment
- syntactic_sugar
- trailing_semicolon
- trailing_whitespace
- unneeded_synthesized_initializer
- unused_closure_parameter
- unused_enumerated
- unused_optional_binding
- vertical_parameter_alignment
- vertical_whitespace
identifier_name:
min_length: 1
allowed_symbols: ["_"]
excluded:
- Core.*
type_name:
min_length: 2
allowed_symbols: ["_"]
excluded:
- iOS_.*
function_parameter_count:
warning: 6
large_tuple:
warning: 3

View File

@@ -355,6 +355,7 @@
isa = PBXNativeTarget;
buildConfigurationList = A5B30540299BEAAB0047F10C /* Build configuration list for PBXNativeTarget "Ghostty" */;
buildPhases = (
FC501E0B2F46B410007AE49D /* Run SwiftLint */,
A5B3052D299BEAAA0047F10C /* Sources */,
A5B3052E299BEAAA0047F10C /* Frameworks */,
A5B3052F299BEAAA0047F10C /* Resources */,
@@ -490,6 +491,29 @@
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
FC501E0B2F46B410007AE49D /* Run SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Run SwiftLint";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "[[ -z \"$GITHUB_ACTIONS\" ]] || exit 0;\n\nSWIFTLINT=\"\"\nif command -v swiftlint >/dev/null 2>&1; then\n SWIFTLINT=\"$(command -v swiftlint)\"\nelif [[ -f \"/opt/homebrew/bin/swiftlint\" ]]; then\n SWIFTLINT=\"/opt/homebrew/bin/swiftlint\"\nfi\n\nif [[ -n \"$SWIFTLINT\" ]]; then\n \"$SWIFTLINT\" lint --quiet \"$SRCROOT\"\nfi\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
810ACC9B2E9D3301004F8F92 /* Sources */ = {
isa = PBXSourcesBuildPhase;

View File

@@ -66,6 +66,7 @@
poop,
typos,
shellcheck,
swiftlint,
uv,
wayland,
wayland-scanner,
@@ -198,6 +199,9 @@ in
# for benchmarking
poop
]
++ lib.optionals stdenv.hostPlatform.isDarwin [
swiftlint
];
# This should be set onto the rpath of the ghostty binary if you want