Skip to content

Fix unmatched baseline entries in time linear in the baseline size - #6977

Merged
ondrejmirtes merged 1 commit into
2.3.xfrom
linear-baseline-fixes
Nov 6, 2026
Merged

ondrejmirtes merged 1 commit into
2.3.xfrom
linear-baseline-fixes

Conversation

@ondrejmirtes

Copy link
Copy Markdown
Member

Fixing unmatched ignoreErrors entries and too high counts (added in 538b35f) took time quadratic in the number of entries. On WooCommerce's 67k-line baseline this showed up after analysis; with 20,000 unmatched entries of one path it took 252 s against 3 s on 2.3.0.

Two parts of the fixer were linear per fixed error:

  • findEntries() went through every entry with the same path for each error. The entries are now indexed by path and by the value of the first matched field (message, rawMessage, identifier) an error must match, the same matching then runs only on that bucket. This is the same idea as phpstan-fixer's change that computes the line map of an edited baseline once instead of once per fix.
  • Each NEON fix cloned the tree and printed the whole ignoreErrors list again with the format-preserving printer (Printer::diff() scanned the unchanged prefix and suffix of the list). Removing a whole entry after the first one of a block list, and lowering a count, are now edits of the tokens of the entry: the lines from the one after the previous entry through the line break after the entry, which is exactly what the printer printed. Everything else, like the first entry, list items or inline lists, still goes through the printer.

The removal edit covers only the lines of the entry, so neighbouring stale entries no longer conflict and are removed in the same round. The fix-baseline e2e round count changes for that reason; its expected files do not. The first entry still goes through the printer, its hunk overlaps the second entry's, so all entries are never removed in one round (that would leave ignoreErrors: null) - there's a test for it.

One trivial file with N unmatched entries of one path:

Entries Before Index only This PR
2,000 1.66 s 1.03 s 0.71 s
4,000 4.91 s 2.41 s 1.16 s
8,000 18.67 s 7.64 s 1.84 s
20,000 3.99 s

Running --fix to the end on a mixed baseline (generated entries with a raised count, 300 stale message and rawMessage entries, comments above and after entries, a messages list, an entry without a path), as LF and as CRLF, gives byte-identical files before and after, in 2 rounds instead of 8. The fix, fix-baseline and fix-ignored-entries e2e jobs pass locally.

Fixing unmatched ignoreErrors entries and too high counts took time quadratic
in the number of entries: 18.7 s for 8,000 unmatched entries of one path.

- The entries are indexed by path and by the value of the first matched field,
  so findEntries() no longer goes through every entry of the path for each error.
- Removing a NEON entry after the first one of a block list, and lowering a count,
  are edits of the tokens of the entry. Printing the whole ignoreErrors list again
  for each error with the format-preserving printer was linear in its length.
  The edit covers only the lines of the entry, so neighbouring entries
  are removed in the same round instead of conflicting.

8,000 unmatched entries now take 1.8 s, 20,000 take 4.0 s. The fixed config
files are the same as before.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1b1c2-4005-7120-a2fa-17285a96ea5b
@ondrejmirtes
ondrejmirtes merged commit dabe99a into 2.3.x Nov 6, 2026
1038 of 1043 checks passed
@ondrejmirtes
ondrejmirtes deleted the linear-baseline-fixes branch November 6, 2026 19:12
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.

1 participant