Conversation
oida (github.com/kolonialno/oida-swift) is our own linter: fifteen rules decide the shape of every argument list, and the ones this repository opts into are in .oida.yml. bin/lint fetches the pinned release, checksums it and runs it, then hands the files to swift-format, which owns every indent. There is no baseline. A standing violation would be silent under one, and this repository adopted oida to find what it says rather than to record that it once said something — so the tree goes to zero and stays there. This commit is the mechanical half: what --fix produces, 438 violations down to 278. The suite is 180/180 green against go-httpbin, so the reformat changed shape and nothing else. The remainder is hand work, and the gate goes on once it is at zero.
bin/lint already hands the corrected files to swift-format, so the standalone swift-format CI job and the swift-format pre-commit hook were running it a second time. The hook now calls bin/lint --staged and CI calls bin/lint --check. .swift-format stays: swift-format still owns every indent, it is just reached through oida now, which is what keeps the tree and Xcode's Format File agreeing.
XCTFail() with no message tells whoever reads the failure nothing the test name
did not already say, so all seventeen now name the expectation: "a GET to a path
that does not exist must fail", "the image should come back from the file cache".
The rest is mechanical. Trailing commas dropped from collection literals. Two
force-unwrapped `"...".data(using: .utf8)!` become `Data("...".utf8)`, which
cannot fail and so needs no unwrap. `arc4random()` becomes
`UInt32.random(in: .min ... .max)`. Two test classes lose the underscore their
filename put in their name.
438 violations down to 238, suite 180/180 green.
Eleven `// MARK: -` banners named what the declarations under them already named, so they are gone. A doc comment that sat above two ordinary comments moves down onto the declaration it documents, where it is read as one. The rest is spacing: a stored property now stands off the function under it. 238 violations down to 220, suite 180/180 green.
…ndling
Seventeen force unwraps and three force casts, each taken on its own terms.
Handled, because the value can genuinely be absent: a macOS image with no TIFF
representation now returns nil, which the signature already allowed; a folder
name that will not parse as a URL throws or returns nil rather than trapping;
five `"...".data(using: .utf8)!` become `Data("...".utf8)`, which cannot fail.
Stated, because the absence would be our fault rather than a caller's: a bundled
image that is missing is a packaging fault, so Image.find says so and traps with
the name it looked for. Two justified with a scoped disable: the URL reaching the
HTTPURLResponse convenience initializer has already been parsed by composedURL,
and the three generic casts are each guarded by the type test above them.
In tests a nil is a failed test, so those take XCTUnwrap.
220 violations down to 206, suite 180/180 green.
… three steps FakeRequest.find carried an exact lookup and the templated-path walk in one body, at complexity 14. It is now three: find tries the exact path then hands over, templated walks the candidates, and captures answers what one placeholder path took from a lookup — or nil where the two describe different paths. The body substitution both halves needed is String.replacing(_:). A `for` whose body was one `if` takes a `where` clause. no_single_use_void_functions is left out of the rule set, with the reason in .oida.yml: nine of the ten it finds here are a library's own seams and one is documented public API, whose callers are in other people's projects (kolonialno/oida-swift#21). Inlining the tenth does not even compile — it is the hop that makes an actor-isolated write legal from a nonisolated closure. 206 violations down to 195, suite 180/180 green.
0.15.0 answers all three issues this adoption raised. The 149 violations that had no fix in 0.14.0 are gone: multiline_call_arguments and multiline_conditions correct now that width is the formatter's alone, and opening_brace takes ignore_multiline_function_signatures, which is the shape a wrapped signature could never satisfy before (#19, #20). no_single_use_void_functions no longer flags public clearCache, whose callers are in other people's projects (#21), so the rule comes back into the set. The configuration loses what 0.15.0 deleted: requires_single_line, the eight count rules, nesting and cyclomatic_complexity. Naming a rule that no longer exists is naming nothing. Linting needs no Xcode, so its job moves to ubuntu-latest in swift:6.3.2-noble, the shape tienda-ios already runs. The toolchain image is what makes Linux work at all: oida's SourceKit rules and its swift-format handoff both resolve through it. The macOS runner stays for build, test and the warnings gate. 195 violations down to 48, suite 180/180 green.
Thirty-nine places where the prose said what something was rather than what it is. A few read better for it: "If your file is not located in the main bundle you have to specify using the bundle parameters" is now "A file outside the main bundle takes the bundle parameter"; "makes no assumption about the error body's shape" is "treats the error body as opaque"; the `.ephemeral` paragraph, carried in from Apple's own documentation, says what it keeps in RAM instead of what it declines to write. Two uses of "simply" go, which was the whole of document_avoids_retired_words. Every relative link still resolves.
swift-format adds a trailing comma to a multi-line collection literal by default and oida's trailing_comma forbids one, so bin/lint --fix wrote fifteen commas that bin/lint --check then rejected — stably, since a second --fix changed nothing. tienda-ios has always set this key, which is why it reports none. multiElementCollectionTrailingCommas: false is the half that makes both directions agree; mandatory_comma: true alone leaves seven, because the formatter adds the comma in most multi-line literals rather than all of them. Filed as kolonialno/oida-swift#34. --fix is idempotent now.
0.16.0 takes 209 rules to 89, and 25 of the names this configuration carried are among them — the SwiftLint opt-in menu, the whitespace rules swift-format already reports, and the preferences that pick one of two correct spellings. trailing_comma goes with them, which closes #34: layout is the formatter's, so multiElementCollectionTrailingCommas decides alone and oida holds no opinion to disagree with. file_header and force_unwrapping left too. The scoped disable on the HTTPURLResponse initializer goes with force_unwrapping, and its comment stays: why that unwrap is safe is worth reading whether or not a rule asks. --fix is idempotent, suite 180/180 green, and the tree is at 9 — no_single_use_void_functions, all of it.
appendToLogFile had one caller, in the same file, as its last statement — the jump bought the reader nothing and its guards read the same inside record. The other two same-file cases stay. removeContinuation is an actor-isolation hop whose caller is a nonisolated termination handler, and inlining it fails to compile. logCompletion is fifty-five lines with three early exits, called from a function that carries on afterwards; folding it turns those exits into nested conditionals and makes complete do three jobs. Suite 180/180 green.
Each carries a scoped disable and the reason above it. Six are seams: the cancel path reached from the verbs, and the cache store's set, write, clear and sweep, each reached from Networking and three of them driven directly by tests. Folding any of them pushes a store's internals into its consumer. Two are same-file and still stay. logCompletion is fifty lines of logging behind a name, with three early exits the caller must never inherit — returning from complete would skip emit and the result. removeContinuation is the hop that makes an actor-isolated write legal from a nonisolated termination handler, and inlining it fails to compile. clear() takes a block disable rather than :next, so its doc comment stays on the declaration it documents. bin/lint --check is clean, --fix is idempotent, suite 180/180 green. The gate is hard from here: any new violation fails.
3lvis
marked this pull request as ready for review
September 16, 2026 19:55
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adopts oida as this repository's linter, with no baseline: the tree is kept at zero and any violation fails.
bin/lintfetches the pinned release, checksums it against.oida-checksum-<platform>, asserts the version after unzipping, and runs it..oida.ymlcarries the rules. The commit hook callsbin/lint --staged, and CI callsbin/lint --checkonubuntu-latestinswift:6.3.2-noble— linting needs no Xcode, and that is the shapetienda-iosalready runs. The macOS runner stays for build, test and the warnings gate.One tool formats now. oida hands the corrected files to swift-format, so the standalone
swift-format checkjob and the swift-format pre-commit hook were running it a second time; both are gone..swift-formatstays — swift-format still owns every indent, reached through oida, which is what keeps the tree and ⇧⌃I agreeing.Where it stands
438 violations at the start, 9 now, suite 180/180 green at every commit and
bin/lint --fixidempotent.The nine are all
no_single_use_void_functions, and they want a decision rather than an edit:CacheStore.storeData,CacheStore.sweepExpired,Networking+Private.cancelRequest. 0.15.0's note says a call from another file is an interface across a layer that the rule leaves alone; the shipped rule flags them, and its message says only "called from exactly one place in the app".Networking.removeContinuationcannot be inlined at all. Its one caller is anAsyncStreamtermination handler, and putting the statement where it runs fails to compile:actor-isolated property 'streamContinuations' can not be mutated from a nonisolated context. The function is the isolation hop.What the adoption changed
--fixreformat, taken as one commitData(_.utf8), type namesFakeRequestmatching split into three stepsFour oida issues, which is the point of adopting it
All four are closed, and 0.15.0 and 0.16.0 are what closed them.
multiline_call_arguments:--fixcleared 158 of 283 and settled. One of its three reasons was corrected zero times out of 37.multiline_parametersandopening_braceadmitted no legal shape for a two-parameter signature longer thanlineLength. 18 sites. 0.16.0'signore_multiline_function_signaturesis the answer.no_single_use_void_functionsflaggedpublic func clearCache(), which the README documents as the consumer API.--fixwrote 15 trailing commas that--checkthen rejected, stably. swift-format adds them by default and oida'strailing_commaforbade them. 0.16.0 deletes the rule: layout is the formatter's, somultiElementCollectionTrailingCommasdecides alone.Worth a look in review
Image.findtraps with the name it looked for rather than force-unwrapping, on the grounds that a bundled asset is a packaging fault and never a caller's. Two generic-erasure casts keep a scopedoida:disablewith the guard written next to them. And the prose pass rewrote 39 lines across README, AGENTS and LEARNINGS so they say what is — "A file outside the main bundle takes the bundle parameter" rather than "If your file is not located in the main bundle you have to specify using the bundle parameters".