Reduce lint hot-path overhead#6826
Draft
Brett-Best wants to merge 45 commits into
Draft
Conversation
Generated by 🚫 Danger |
Brett-Best
force-pushed
the
codex/lint-hot-path-performance
branch
from
July 16, 2026 20:46
4459be4 to
e7c9b0b
Compare
Contributor
Author
|
Some other benchmarks which also include the fix jpsim/SourceKitten#846 |
Comparing via debugDescription(includeTrivia:false) builds a full indented debug dump of both operand subtrees (keypath-driven child names included) for every infix operator expression. Comparing the lazy token streams short-circuits on the first mismatch and allocates nothing. ~6% of a large lint run in profiling. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The CharacterSet union was rebuilt through CoreFoundation on every checked identifier; it only changes when allowedSymbols changes. ~83% of identifier_name's cost in profiling a large repo. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
comment_spacing and period_spacing only need comment byte ranges, but obtained them via file.syntaxClassifications, which runs SwiftSyntax's general-purpose classifier over every token in the file. A direct trivia walk over the cached syntax tree yields the same ranges without classifying non-comment tokens. Both rules are migrated together since they shared the classification cache; migrating one alone would move the cost to the other. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Skip Linter.collect's concurrentPerform when no CollectingRule is enabled: it dispatched rules.count no-op iterations per file, whose existential/RuleDescription copies and task-local bookkeeping cost ~14% of a large lint run. - Memoize the minSwiftVersion support check per rule type; it compared freshly parsed version strings and copied the full RuleDescription per rule and file. - Read SWIFTLINT_DISABLE_SOURCEKIT once; ProcessInfo.environment rebridges the entire environment on every access. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The documentation promises nil when no element satisfies the predicate, but the termination check tested the midpoint index instead of the partition boundary, returning count in that case. Existing callers only survived because they immediately formed (empty) suffix slices from the result; subscripting callers crash. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
statement_position, vertical_whitespace_closing_braces, vertical_whitespace_opening_braces, and indentation_width resolve syntax kinds through a shared SwiftSyntax-derived syntax map; multiline_function_chains, multiline_parameters_brackets, and literal_expression_end_indentation are ported to SwiftSyntax visitors reproducing SourceKit's structure geometry. The shared map extends comment tokens over their terminating newline to match sourcekitd's extents, and rules build it only for files whose regexes match at all. With no enabled rule requiring SourceKit, lint runs never pay the per-file sourcekitd editorOpen round trip (~13% of CPU on a large repo profiling run). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
execution_mode/default_execution_mode existed in configuration and in isEffectivelySourceKitFree, but validate() always used the sourcekitd-backed syntax map regardless of mode — a swiftsyntax-mode configuration would even hit the "Never call this for file that sourcekitd fails" trap when SourceKit is unavailable. Dispatch on the effective mode and resolve kinds from the SwiftSyntax-derived map, built lazily only when a kind-filtered pattern matches. Also propagate defaultExecutionMode through parent/child configuration merging, which rebuilt CustomRulesConfiguration and silently dropped it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Cache validation read the full attribute dictionary (including an enumeration of all extended attributes) per file just for the modification date; ask for the single resource value instead. - Read a file's emptiness once per file instead of once per rule and file: the check acquired the contents queue lock on every call. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- mark: materialize each token's leading trivia once instead of once per piece, and gate the regex on a cheap substring pre-check. - file_length: compute everything from the root node without walking the entire tree. - trailing_whitespace: scan lines for candidates first and only pre-compute comment/string-literal line information when any exist. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
sourcekitd includes the terminating newline in line comment tokens but ends block comment tokens at the closing delimiter. Extending all comment kinds suppressed violations following block comments. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three rules walk the file for comment trivia; the result is now shared through the file cache. Byte positions are tracked incrementally during a single visitor pass: SyntaxProtocol.position and token-sequence iteration climb the tree per call, which degrades sharply on deeply nested expression trees. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
visitPost fires for every call in a chain and each computation shares its suffix with the enclosing call's, so long chains were re-walked quadratically. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every rule walks the same file's syntax tree, and concurrent walks contend on the shared syntax arena's reference counts, slowing all of them down. With at least as many files as cores, file-level parallelism already saturates the machine, so rules now run serially within each file in that case. Few-file invocations (single-file lints, editor integrations) keep concurrent rule execution. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rewriter-based corrections reconstructed the entire syntax tree and serialized it back to a string for every correctable rule and file, even when there was nothing to correct — the dominant cost of --fix runs on clean codebases. Detect violations first via validate(file:), which reports a superset of what the rewriter corrects (the rewriter additionally skips disabled regions), and only rewrite when needed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extract trailing_whitespace's candidate collection into a helper to reduce cyclomatic complexity, wrap an overlong line, and move indentation_width's helper visitors into their own file to respect the file length limit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tax map The SourceKit-free syntax map extended line comments over their terminating newline only for LF line endings; the seven rules using it now also run on platforms where checkouts commonly use CRLF. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With serial per-file rule execution, a huge file dispatched near the end of a run occupies a single core while the remaining cores sit idle. Sorting files by on-disk size before dispatch hides long single-file runtimes behind the remaining work regardless of core count. Reporting order for parallel runs is already scheduler-dependent, so this changes no observable behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The per-file artifact cache guarded every slot access with a concurrent DispatchQueue, taking a barrier sync on writes and a plain sync on reads. Each lint forces roughly a dozen slots per file across all rules, so the queue's dispatch overhead was a measurable share of a run. An NSLock with the same computed-outside-the-lock / DispatchGroup-dedup semantics is cheaper for this short-critical-section workload. Also invalidate the comment-byte-ranges slot, which was missing from invalidateAll. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The body-length rules computed the comment/whitespace-adjusted line count for every body, forcing the per-file linesWithTokens set and allocating a Set of the body's line range each time. The effective count can never exceed the physical brace-to-brace span, so bodies whose span is already within the threshold cannot violate and skip the analysis entirely. Where the count is still needed, it is now derived without materializing a Set. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gate trivia materialization on a byte-length check so empty-trivia tokens skip re-lexing, iterate trivia forward instead of copying reversed arrays, compute the token kind only when needed to detect end-of-file, and resolve line numbers through a per-line bounds cache instead of a converter lookup per token. Comment-line results are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rule ran separate tree walks for multi-line condition continuations and multiline string-literal spans; merge them into one visitor pass. Rename the helper file to match its renamed type. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SourceLocationConverter.sourceLines rebuilds the whole file as a fresh [String] on every access; the rule invoked it once per parameter of every multi-parameter declaration. Cache it once per file and compute grapheme columns only for the parameters actually compared. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
allTerms and allAllowedTerms were re-derived (set union, lowercasing, dedup, sort) on every visited token. Compute them once per file. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hoist the stringView access out of the per-comment loop and run the violation regex only on comments a cheap UTF-16 unit scan cannot rule out, matching against the file's string view in place instead of re-materializing each comment body. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hoist per-file invariants out of per-node work and gate expensive checks on cheap pre-conditions in these four rules. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Skip region partitioning and identifier-set construction when they cannot affect the result (no violations, or a single enable-everywhere region). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lint already resolves the identifier and passes it into the rule context; performLint recomputed it. Reading it goes through the rule's description witness, which copies the whole RuleDescription value, so this removes one such copy per rule and file. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rule asked for `syntaxClassifications`, which runs SwiftSyntax's general-purpose classifier over every token in the file — resolving keyword, identifier, type and literal classifications through key path comparisons — and then discarded everything that was not a doc comment. Doc comment classifications are a 1:1 mapping of the `docLineComment` and `docBlockComment` trivia piece kinds, so a single trivia walk reaches the same ranges, in the manner already used for `commentByteRanges()`. Adjacent same-kind ranges are coalesced as the classifier does, so the reported ranges are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
object_literal rendered `trimmedDescription` of the called expression for every function call in the file, which rebuilds that subtree through key path writes just to compare the result against four class names. Only `X(...)` and `X.init(...)` can match, so the identifier naming `X` now rules a call out first and the description is materialized only for the few calls that can still violate. The per-node arrays of candidate names are hoisted to static sets. `containsOnlyAllowedCharacters` built a bitmap-backed `CharacterSet` from every checked identifier to test it against the allowed set. It now scans the name's scalars, settling ASCII letters and digits inline since the allowed set always contains them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`sourceKitFreeSyntaxMap()` re-derived the whole SourceKitten token array and syntax map on every call, even though it is a pure function of the file and its bridge tokens are already cached. Several rules request it independently per file — `statement_position` alone builds it four times, plus both vertical whitespace rules and custom rules in SwiftSyntax mode — so the array map and the comment-extent adjustment ran repeatedly for the same result. Move it into the per-file artifact cache alongside the other derived artifacts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Each of the pipeline's five jobs cloned the full repository, which carries ~965 MB of history that is almost entirely historical blobs no job reads. Filtering blobs out of the clone and letting git fetch them on demand cuts a cold checkout from 104s/965 MB to 5s/15 MB; after running oss-check's merge-base, diff and worktree operations the checkout still holds 16 MB. All commits and trees are still fetched, so history-dependent steps keep working: oss-check resolves `git merge-base origin/main` and checks the result out with `git worktree add --detach`, and Danger computes the PR diff. A `--depth` shallow clone would break both, which is why the filter is used instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Brett-Best
force-pushed
the
codex/lint-hot-path-performance
branch
from
July 25, 2026 00:52
f1b5834 to
eec7ebf
Compare
`BUILDKITE_GIT_*` are protected variables: setting them in pipeline `env` is ignored and logs "Ignored BUILDKITE_GIT_CLONE_FLAGS" on every build, so the previous commit changed no behaviour and only added warning noise. Keep the finding as a comment instead, since the flag still belongs in the agent configuration, along with the measurements that justify it.
The eighteen repositories were cloned one after another, and each was checked out in full even where linting is restricted to a subtree. Brave alone took 67s to fetch 1.9 GB so that 6% of it could be linted; Swift fetched its whole tree for `stdlib`. Fetch them concurrently, since cloning is network-bound, and for the repositories restricted with `included:` fetch no file contents up front and materialize only that subtree. Tags are skipped everywhere as only the tip commit is ever checked out. Over Brave, Swift, Alamofire and Moya this goes from 79.2s and 2.2 GB to 9.3s and 182 MB, with the linted file sets unchanged: 1314 Swift files under Brave's `ios/brave-ios` and 516 under Swift's `stdlib`, as before. The sparse path is derived from the `included:` config so the two cannot drift apart and silently shrink the comparison. Clone failures now raise instead of being ignored, which sequential runs got away with but which would surface as confusing interleaved errors later on. `git -C` replaces `Dir.chdir`, which mutates the whole process's working directory and would race across the clone threads. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Collecting the rule's doc comment ranges in its own visitor added a second full walk over every file's token trivia, when the per-file cache already walks exactly that trivia for `commentByteRanges()`, which comment_spacing, period_spacing and indentation_width force on nearly every run. Collect the doc comment ranges during that existing pass and let the rule read them from the cache, so the walk happens once. Linting DuckDuckGo with all rules goes from 87.47s to 85.24s of CPU, taking the branch's total to 8.1% below its merge base, with violations unchanged. Ranges are handed back as source positions rather than byte ranges so the rule needs no SourceKitten import, and adjacent same-kind ranges are still coalesced to match the classifier these ranges used to come from. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The rule read `SourceLocationConverter.sourceLines` once per element of every dictionary literal, and that property materializes every line of the file as a fresh `[String]` on each access, making the rule quadratic in files holding large dictionary literals. This is the same defect already fixed in vertical_parameter_alignment; collection_alignment was missed. Compute the lines once per file. On realm-swift, whose test files are both very large and dictionary-heavy, the rule drops from 0.336s to 0.022s, and on DuckDuckGo from 0.561s to 0.185s. Violations are unchanged. Found by profiling realm-swift, which gained the least from this branch of any repository oss-check measures; its few-but-huge files expose costs that scale with file size rather than file count. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Only the first key and keys starting a new line are ever compared, so the grapheme column of every other key was computed and discarded. Resolve them where they are used instead, which lets single-line literals skip reading the source lines entirely. This mirrors what vertical_parameter_alignment already does, so the two rules that share this shape now share their approach. The measured gain is small on its own — caching the source lines had already removed the quadratic term, leaving collection_alignment at 0.185s on DuckDuckGo, which this takes to 0.156s, and unchanged at 0.02s on realm-swift. Sharing `validate` with the array path, which compares raw UTF-8 columns, means the column basis has to be passed in, and memoizing the first key's column across iterations turns the mapping into a loop. Violations are unchanged on both corpora. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Resolving a command's character position read `SourceLocationConverter.sourceLines`, which materializes every line of the file as a fresh `[String]`, once per command comment in the file. Compute the lines once per file instead. This is the third and last occurrence of this pattern in the codebase, after vertical_parameter_alignment and collection_alignment; a search for `sourceLines` now finds no remaining uncached reads. The gain here is smaller than in those rules because command comments are rare — the worst file across DuckDuckGo, realm-swift and SwiftLint itself holds 65 of them, and most hold none — but the quadratic term is gone either way. Violations are unchanged across all three of those repositories, including SwiftLint's own, which carries 393 command comments and therefore exercises this path hardest. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both vertical whitespace rules discard regex matches that intersect a comment or string, and reached those kinds through `sourceKitFreeSyntaxMap`, which runs SwiftSyntax's general-purpose classifier over every token in the file. That classifier resolves keyword, type and identifier classifications through key path comparisons, and these two rules discard all of it. Comments and strings are both classified context-free — comments by trivia piece kind, strings by token kind, since contextual classification only applies to identifiers — so a single token walk reaches the same ranges. Build a map holding just those tokens and hand it to the existing matching code unchanged: it only tests disjointness against `commentAndStringKinds`, so omitting the other kinds cannot change a result. Linting DuckDuckGo with each rule alone drops from 40.2s to 10.3s of CPU for the opening braces rule and 25.0s to 8.5s for the closing braces rule, against a parse-only floor of 8.4s, leaving both close to free. A full all-rules run drops 7.8%, from 87.6s to 80.7s of CPU. Violations are identical across DuckDuckGo, realm-swift and SwiftLint itself — 686,513 in total — and `--fix` output is byte-identical over the 37 files these rules correct in realm-swift. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reporting was the single-threaded tail of a run: profiling DuckDuckGo with every rule showed the last stretch on one thread inside JSONSerialization, bridging a dictionary per violation to NSDictionary and re-sorting its keys each time — `CFSortIndexes` and string comparisons for all 590,205 of them. Encode an `Encodable` violation instead, which leaves escaping to Foundation. `JSONEncoder` is given the same formatting options, including `.sortedKeys`. That option is not redundant despite the coding keys being declared in the order it sorts them into: without it `JSONEncoder` emits keys in an unrelated order, so it is what holds the report's shape. Serializing 590,205 violations takes 1765ms against 2601ms through `JSONSerialization`, taking a DuckDuckGo run with `--reporter json`, which is what oss-check uses, from 13.00s to 12.74s of wall clock. Writing the JSON by hand reached 11.79s, but owning the escaping is not worth the difference. Output stays byte for byte identical, checked against whole-file reports from the `JSONSerialization` build, and violations are unchanged across DuckDuckGo, realm-swift and SwiftLint itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rendering the report was the only part of a run that did not parallelize: profiling DuckDuckGo with every rule showed the last stretch on a single thread, and every sample in it belonged to the reporter. Serializing 590,205 violations through `JSONSerialization` bridged a dictionary per violation to `NSDictionary` and re-sorted its keys each time. `JSONEncoder` alone barely helps, because it does the same two things — boxing each value, then sorting each object's keys for `.sortedKeys`. The array is a concatenation of independent objects, so split it into a chunk per core, encode those concurrently, and join the fragments. Foundation still owns escaping and formatting. Linting DuckDuckGo with `--reporter json`, which is what oss-check runs, goes from 13.24s to 10.74s of wall clock. Writing the JSON by hand and keeping it serial reached 11.46s, so this is both faster and leaves the escaping alone. Chunking is skipped below a couple of thousand violations, where splitting costs more than it saves, and for an empty report. Output is byte for byte unchanged, checked against whole-file reports from the `JSONSerialization` build, and violations are unchanged across DuckDuckGo, realm-swift and SwiftLint itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`custom_rules` resolved syntax kinds through SourceKit unless a rule or the configuration asked otherwise, which parses every file a second time on top of the tree SwiftLint has already built. Over DuckDuckGo that was 13% of a run's CPU, and linting it with every rule drops from 83.7s to 74.8s. The rule already documented this default: its description says rules "default to SwiftSyntax mode for improved performance", and `ExecutionMode.default` is documented as "uses SwiftSyntax by default unless overridden". Only the two places applying the fallback disagreed, so this makes the behaviour match what is written. The two tests asserting otherwise are updated. Their own comments described the mode as implicit SwiftSyntax and the rule as SourceKit-free while asserting the opposite, so they are brought in line rather than kept. Violations are unchanged across DuckDuckGo, realm-swift and SwiftLint itself. Projects needing SourceKit's kinds can still ask for them with `execution_mode: sourcekit` or `default_execution_mode: sourcekit`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`URL.relativeDisplayPath` reaches `FileManager.currentDirectoryPath`, and so
a `getcwd` syscall, on every access. Reporters render a relative path per
violation — code_climate renders two, one of them inside the fingerprint —
so a run over a large code base made hundreds of thousands of them.
Add a variant taking the base directory and let each reporter resolve it
once. It stays out of `URL.cwd` itself, which cannot cache: SwiftLint
changes the process working directory when given `--working-directory`.
Linting DuckDuckGo with every rule:
code_climate 60.7s -> 42.3s
sarif 28.3s -> 19.1s
sonar_qube 25.7s -> 17.6s
Reports are byte for byte unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The set is short of a repository shaped to stress the linter rather than to be an app. Every entry is a real project of broadly the same shape — many files of a few hundred lines each — and a rule whose cost scales with file size is close to invisible across all of them. That is how a quadratic read of a file's source lines survived in collection_alignment. Brigade is built for that gap: a handful of very large catalogue files against many small ones, non-ASCII identifiers, CRLF and CR terminators, files dense in disable commands, and custom rules with kind filtering. It lints in 1.32s here against 10.51s at this branch's merge base, where the existing repositories manage about 1.5x, and adds roughly a minute to the job. Its config is kept: the custom rules and the exclusions in it are what the corpus measures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Linting a file whose nesting runs deep enough terminates SwiftLint. The crash report shows EXC_BAD_ACCESS at the stack guard region, 721 frames, with parseIfExpression -> parseCodeBlock -> parseCodeBlockItemList -> parseStatement repeating about a hundred times and `concurrentMap` at the bottom: the parse runs on a worker thread and exhausts its stack. `Parser.parse(source:)` takes no nesting bound, so swift-syntax's ceiling of 256 applies. That ceiling exists to prevent exactly this, but it is sized for a main thread's stack; on a worker the stack is gone at around a hundred levels, long before the ceiling can report the source as too deeply nested. Pass a bound that fits the stack the parser actually runs on. Only the buffer-taking entry point accepts the bound, so the source is handed over as UTF-8 rather than as a string. Violations are unchanged across DuckDuckGo, realm-swift and SwiftLint itself, none of which nest anywhere near the bound. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Summary
This PR reduces lint and correction hot-path overhead across rule execution, syntax classification, file caching, and parallel scheduling. In SwiftLint's OSS-check suite, the combined series improves end-to-end lint time by 9–35%.
Motivation
Profiling a large repository showed that a substantial portion of runtime was spent outside rule-specific analysis: SourceKit
editorOpenrequests, repeated syntax-tree walks, classification of unrelated tokens, full-tree correction rewrites on clean files, filesystem and cache synchronization, repeated source-line and term-list construction, task-local bookkeeping, violation-region partitioning, and concurrent walks contending on the same syntax arena.Most of this work is invariant for a rule type or file, can be cached, or can be avoided entirely after a cheap candidate check. This series moves invariant work out of repeated paths and makes expensive analysis conditional on a file having relevant content.
Details
Remove unnecessary SourceKit work
indentation_width,literal_expression_end_indentation,multiline_function_chains,multiline_parameters_brackets,statement_position, and the vertical-whitespace brace rules to SwiftSyntax-backed implementationsdefault_execution_modeAvoid unnecessary correction and rule work
comment_spacingregex work behind a cheap UTF-16 scan and match directly against the file's string viewcolon,comma,closure_end_indentation, andredundant_selflintinstead of copying itsRuleDescriptionagain inperformLintCache and narrow repeated analysis
NSLockwhile retaining computation deduplicationCommentLinesVisitorby avoiding empty-trivia re-lexing, reversed trivia copies, repeated token-kind checks, and per-token converter lookupsindentation_widthsyntax data in one tree walkvertical_parameter_alignmentinclusive_languageterm lists once per fileallowedSymbolsAndAlphanumerics, minimum-Swift-version support, environment state, file emptiness, and targeted file metadataImprove scheduling
Correctness fixes
RandomAccessCollection.firstIndexAssumingSorted(where:)returnnil, rather thanendIndex, when no element matchesexecution_modeand inheriteddefault_execution_modeImpact
Large multi-file lint runs should spend less CPU time in SourceKit, syntax-tree bookkeeping, filesystem access, cache synchronization, rule post-processing, and shared-arena contention. Clean
--fixruns avoid reconstructing the syntax tree for every correctable rule. Single-file and editor integrations retain concurrent rule execution. No configuration migration is required, and SourceKit execution remains available where explicitly selected or required.Validation
Brett-Best <brettbest@live.com.au>as author and committergit diff --checkreports only the two intentional Markdown hard breaks in this PR's changelog entries.