Skip to content

Cache source lines in vertical_parameter_alignment - #6865

Open
Brett-Best wants to merge 1 commit into
realm:mainfrom
Brett-Best:vertical-param-source-lines
Open

Cache source lines in vertical_parameter_alignment#6865
Brett-Best wants to merge 1 commit into
realm:mainfrom
Brett-Best:vertical-param-source-lines

Conversation

@Brett-Best

Copy link
Copy Markdown
Contributor

vertical_parameter_alignment converts a parameter's UTF-8 column into a grapheme-cluster column, so that declarations preceded by multi-byte characters are compared by visible alignment. It reads the file's source lines to do that:

private func graphemeColumn(line: Int, column: Int) -> Int {
    guard let graphemeClusters = String(locationConverter.sourceLines[line - 1].utf8.prefix(column - 1)) else {

sourceLines is a computed property in swift-syntax, and it rebuilds the entire file on each access:

public var sourceLines: [String] {
  var result: [String] = []
  self.forEachSourceLine { line in
    result.append(String(syntaxText: line))
  }
  return result
}

graphemeColumn is called once per parameter, for every declaration that has more than one parameter:

let paramLocations = params.compactMap { param -> (position: AbsolutePosition, line: Int, column: Int)? in
    let position = param.positionAfterSkippingLeadingTrivia
    let location = locationConverter.location(for: position)
    return (position, location.line, graphemeColumn(line: location.line, column: location.column))
}

So a file of m lines whose multi-parameter declarations hold p parameters in total allocates m × p strings in order to look at p lines. Hoisting the property into a lazy var on the visitor computes it once per file.

collection_alignment already carries this same hoist, for the same reason.

Scaling

One file of n two-parameter functions, --only-rule vertical_parameter_alignment, best of three:

functions before after
500 0.14s 0.06s
1,000 0.33s 0.07s
2,000 1.17s 0.08s
4,000 4.47s 0.10s

Before, the time roughly quadruples each time the count doubles; after, it is flat. The declarations are correctly aligned, so both builds report no violations on that file — all 4.47 seconds go to finding nothing.

python3 -c "
for i in range(4000):
    print('func compute%d(first: Int, second: Int) -> Int {' % i)
    print('    first + second')
    print('}')" > Decls.swift
swiftlint lint --no-cache --quiet --only-rule vertical_parameter_alignment Decls.swift

Real projects

Unlike a defect that only shows up in unusually large files, this one is visible on ordinary code, because the rule touches the source lines for every parameter of every multi-parameter declaration. Both binaries run interleaved, five runs each, on an idle machine:

corpus before (median / best) after (median / best)
DuckDuckGo, 6,747 files 1.93s / 1.82s 1.52s / 1.40s
realm-swift, 152 files 0.31s / 0.30s 0.19s / 0.18s

No behaviour change

With --enable-all-rules, the violations are identical before and after — 590,289 on DuckDuckGo and 64,834 on realm-swift, 655,123 in total, matching once sorted. (The raw JSON order is not stable between runs of the same binary, since linting is parallel.) The test suite passes.

🤖 Generated with Claude Code

`SourceLocationConverter.sourceLines` rebuilds every line of the file as a new
`[String]` on each access, and the rule read it once per parameter. Hoisting it
into a `lazy var` computes it once per file.
Copilot AI lite review requested due to automatic review settings August 8, 2026 11:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the performance of the built-in vertical_parameter_alignment rule by avoiding repeated reconstruction of SourceLocationConverter.sourceLines (a computed property that materializes all file lines) during per-parameter alignment checks.

Changes:

  • Cache locationConverter.sourceLines in the rule visitor via a private lazy var sourceLines.
  • Update graphemeColumn(line:column:) to read from the cached sourceLines instead of recomputing per access.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@Brett-Best Brett-Best changed the title Cache source lines in vertical_parameter_alignment Cache source lines in vertical_parameter_alignment Aug 8, 2026
@SwiftLintBot

SwiftLintBot commented Aug 8, 2026

Copy link
Copy Markdown
1 Warning
⚠️ If this is a user-facing change, please include a CHANGELOG entry to credit yourself!
You can find it at CHANGELOG.md.
19 Messages
📖 Building this branch resulted in a binary size of 28461.02 KiB vs 28460.45 KiB when built on main (0% larger).
📖 Linting Aerial with this PR took 0.14 s vs 0.11 s on main (27% slower).
📖 Linting Alamofire with this PR took 0.1 s vs 0.15 s on main (33% faster).
📖 Linting Brave with this PR took 0.42 s vs 0.45 s on main (6% faster).
📖 Linting DuckDuckGo with this PR took 1.91 s vs 2.17 s on main (11% faster).
📖 Linting Firefox with this PR took 0.79 s vs 0.95 s on main (16% faster).
📖 Linting Kickstarter with this PR took 0.47 s vs 0.79 s on main (40% faster).
📖 Linting Moya with this PR took 0.11 s vs 0.1 s on main (9% slower).
📖 Linting NetNewsWire with this PR took 0.22 s vs 0.2 s on main (9% slower).
📖 Linting Nimble with this PR took 0.13 s vs 0.15 s on main (13% faster).
📖 Linting PocketCasts with this PR took 0.53 s vs 0.84 s on main (36% faster).
📖 Linting Quick with this PR took 0.09 s vs 0.1 s on main (10% faster).
📖 Linting Realm with this PR took 0.21 s vs 0.33 s on main (36% faster).
📖 Linting Sourcery with this PR took 0.2 s vs 0.15 s on main (33% slower).
📖 Linting Swift with this PR took 0.24 s vs 0.48 s on main (50% faster).
📖 Linting SwiftLintPerformanceTests with this PR took 3.77 s vs 3.73 s on main (1% slower).
📖 Linting VLC with this PR took 0.18 s vs 0.2 s on main (10% faster).
📖 Linting Wire with this PR took 0.82 s vs 0.99 s on main (17% faster).
📖 Linting WordPress with this PR took 0.58 s vs 0.66 s on main (12% faster).

Here's an example of your CHANGELOG entry:

* Cache source lines in `vertical_parameter_alignment`.  
  [Brett-Best](https://github.com/Brett-Best)
  [#issue_number](https://github.com/realm/SwiftLint/issues/issue_number)

note: There are two invisible spaces after the entry's text.

Generated by 🚫 Danger

// Computed once per file: `SourceLocationConverter.sourceLines` materializes every line of the
// file as a new `[String]` on each access, and `graphemeColumn(line:column:)` below reads it
// once per parameter, which is quadratic in the size of the file.
private lazy var sourceLines = locationConverter.sourceLines

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also have this cached as part of SwiftLintFile+Cache to make it available to all rules.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants