Skip to content
Merged
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
24 changes: 20 additions & 4 deletions .github/workflows/lint-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,31 @@ concurrency:

jobs:
swiftlint:
name: SwiftLint (warning-only)
# No longer "warning-only". With a config in place the error-severity rules
# (force_try, force_cast, implicitly_unwrapped_optional) now BLOCK, because
# a red check that blocks nothing is what let 3,104 violations accumulate.
# The remaining ~33 are warning-severity and do not fail the job; zeroing
# them and adding --strict is the follow-up.
name: SwiftLint
runs-on: macos-14
timeout-minutes: 10
continue-on-error: true
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4

- name: Install SwiftLint
run: brew install swiftlint
# PINNED, for the same reason ruff is pinned in helper-ci.yml: an
# unpinned linter turns PRs red for code they never touched the moment
# upstream enables a rule. `brew install swiftlint` was unpinned here
# while ruff carried a paragraph explaining why that is unacceptable.
# Raising this is a deliberate task — bump it, run the lint, fix or
# allowlist what the new version finds, in a PR that does only that.
- name: Install SwiftLint (pinned)
run: |
set -euo pipefail
curl -fsSL -o /tmp/swiftlint.zip \
https://github.com/realm/SwiftLint/releases/download/0.63.2/portable_swiftlint.zip
unzip -q -o /tmp/swiftlint.zip -d /tmp/swiftlint
sudo install -m 0755 /tmp/swiftlint/swiftlint /usr/local/bin/swiftlint
swiftlint version

- name: Run SwiftLint on CLI Pulse Bar targets
working-directory: CLI Pulse Bar
Expand Down
66 changes: 66 additions & 0 deletions CLI Pulse Bar/.swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# SwiftLint configuration — opt-in, not opt-out.
#
# WHY THIS FILE EXISTS
# --------------------
# There was no config at all, so CI ran SwiftLint's DEFAULT rule set and
# reported 3,104 violations across 248 files, every run, for months. A check
# that is always red carries exactly as much information as one that is always
# green: none. It cost real time too — v1.49's PR had to verify by hand that
# its new files were clean, because the check itself could not say.
#
# The defaults were not wrong so much as not this project's. 1,734 of the
# 3,104 — 56% — were `identifier_name` objecting to `s`, `v`, `c`, `k`, `d`
# in closures and math. That is a house-style opinion this codebase has
# declined 1,734 times. Another ~700 were size and line-length rules, which
# measure "this file is big", something everyone already knows.
#
# So this is `only_rules`, an ALLOWLIST, rather than a long `disabled_rules`
# list. Two reasons:
# * it states what we believe instead of what we tolerate, and
# * a SwiftLint upgrade cannot silently add a new rule and turn every PR
# red for code nobody touched. That failure has a precedent here: see the
# pinned-ruff comment in helper-ci.yml.
#
# Adding a rule is deliberate: add it, fix the fallout in a PR that does only
# that, and it becomes load-bearing from then on.
only_rules:
# Crashes waiting to happen.
- force_try
- force_cast
- implicitly_unwrapped_optional

# Code that says something other than what it does.
- unused_optional_binding
- unused_closure_parameter
- unused_control_flow_label
- redundant_discardable_let
- redundant_string_enum_value
- redundant_void_return
- unneeded_synthesized_initializer
- unneeded_break_in_switch

# Readability rules with no plausible false positive.
- control_statement
- for_where
- syntactic_sugar
- empty_count
- empty_string
- is_disjoint
- toggle_bool
- last_where
- first_where
- contains_over_filter_count
- contains_over_first_not_nil

# Non-negotiable regardless of style.
- inclusive_language

# force_try defaults to ERROR severity, which is why it — and only it — made
# this job exit 2. Keeping it as an error is correct: an unjustified `try!` in
# this codebase should stop a merge. The justified ones carry a reasoned
# `swiftlint:disable:next` at the site, which is better documentation than
# silence and better safety than a blanket downgrade.

excluded:
- CLIPulseCore/.build
- build
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,10 @@ public enum ClaudeConversationPreviewFormatter {
/// Only triggers when the punctuation is immediately followed by
/// an uppercase ASCII letter (heuristic for sentence boundary).
private static let sentencePunctPattern: NSRegularExpression = {
// The pattern is a compile-time string literal, so this cannot fail at
// runtime. Making the property optional would push a nil check onto every
// call site for no safety gain.
// swiftlint:disable:next force_try
try! NSRegularExpression(pattern: "([\\.!?])([A-Z])", options: [])
}()

Expand Down Expand Up @@ -399,6 +403,10 @@ public enum ClaudeConversationPreviewFormatter {
/// consume `[1a` and leave a dangling `]`.
private static let orphanCsiPattern: NSRegularExpression = {
let pattern = "\\[[0-9;:?<>=]+[ -/]*[a-zA-Z](?!\\])"
// The pattern is a compile-time string literal, so this cannot fail at
// runtime. Making the property optional would push a nil check onto every
// call site for no safety gain.
// swiftlint:disable:next force_try
return try! NSRegularExpression(pattern: pattern, options: [])
}()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ public enum CodexConversationPreviewFormatter {
/// emits them.
private static let numberedMenuPattern: NSRegularExpression = {
let pattern = "^›?\\s*(?:\\(\\d+\\)|\\[\\d+\\]|\\d+\\.)\\s+\\S"
// The pattern is a compile-time string literal, so this cannot fail at
// runtime. Making the property optional would push a nil check onto every
// call site for no safety gain.
// swiftlint:disable:next force_try
return try! NSRegularExpression(pattern: pattern, options: [])
}()

Expand Down Expand Up @@ -278,6 +282,10 @@ public enum CodexConversationPreviewFormatter {
// covers every real-world CSI final byte, and the lookahead
// catches the `[1a]`-style footnote false-positive class.
let pattern = "\\[[0-9;:?<>=]+[ -/]*[a-zA-Z](?!\\])"
// The pattern is a compile-time string literal, so this cannot fail at
// runtime. Making the property optional would push a nil check onto every
// call site for no safety gain.
// swiftlint:disable:next force_try
return try! NSRegularExpression(pattern: pattern, options: [])
}()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@ public enum GeminiConversationPreviewFormatter {
/// optional leading whitespace.
private static let numberedMenuPattern: NSRegularExpression = {
let pattern = "^\\s*(?:\\(\\d+\\)|\\[\\d+\\]|\\d+\\.)\\s+\\S"
// The pattern is a compile-time string literal, so this cannot fail at
// runtime. Making the property optional would push a nil check onto every
// call site for no safety gain.
// swiftlint:disable:next force_try
return try! NSRegularExpression(pattern: pattern, options: [])
}()

Expand Down Expand Up @@ -309,6 +313,10 @@ public enum GeminiConversationPreviewFormatter {
// covers every real-world CSI final byte, and the lookahead
// catches the `[1a]`-style footnote false-positive class.
let pattern = "\\[[0-9;:?<>=]+[ -/]*[a-zA-Z](?!\\])"
// The pattern is a compile-time string literal, so this cannot fail at
// runtime. Making the property optional would push a nil check onto every
// call site for no safety gain.
// swiftlint:disable:next force_try
return try! NSRegularExpression(pattern: pattern, options: [])
}()

Expand Down
Loading