Skip to content

Blink: let rules skip files without their trigger - #288

Merged
TomasVotruba merged 2 commits into
ecsphp:mainfrom
gnutix:perf/rule-candidates
Nov 4, 2026
Merged

TomasVotruba merged 2 commits into
ecsphp:mainfrom
gnutix:perf/rule-candidates

Conversation

@gnutix

@gnutix gnutix commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #279 (it uses Stream.Memo): please merge that one first. Until then this diff also shows #279's commit.

Problem: many rules look for a word (sprintf, switch, null…) or a docblock that most files don't have, but still walk every token of every file to find out. PHP-CS-Fixer avoids this with isCandidate().

Change:

  • Stream.Index(): built once per file, cached with Stream.Memo. It holds the token kinds present, and the lowercased words in a Bloom filter. It can say "present" for an absent word, never the reverse. A non-ASCII word makes every word "present", because case folding maps some non-ASCII letters to ASCII ones.
  • fixer.Candidate: an optional IsCandidate(*Stream) bool. The runner skips Fix and Errors when it's false. Fixers without it, including blink/extend ones, run as before.
  • 39 rules implement it, each with a trigger taken from its own Fix:
    • one or more function names: NoUselessSprintf, ImplodeCall, NoAliasFunctions, EregToPreg, FunctionToConstant, ModernizeTypesCasting, CombineNestedDirname, DirConstant, SetTypeToCast, ArrayPush, PowToExponentiation, IsNull, GetClassToClassKeyword, FopenFlagOrder, StringLengthToEmpty, ModernizeStrpos, ModernSerializationMethods
    • keywords: MagicConstantCasing, MagicMethodCasing, NoAliasLanguageConstructCall, SwitchCaseSemicolonToColon, SwitchCaseSpace, NoBreakComment, SwitchContinueToBreak, ListSyntax, NoUnsetCast, NoMixedEchoPrint, LogicalOperators, LowercaseStaticReference, NoNullPropertyInitialization, NullableTypeDeclaration, NullableTypeDeclarationForDefaultNullValue
    • a non-ASCII byte: NonPrintableCharacter
    • a docblock or comment: PhpdocOrder, PhpdocTypesOrder, PhpdocLineSpan, NoSuperfluousPhpdocTags, TypeToVarTag, RemoveUselessDefaultComment
  • Shared helpers (applyToDocblocks, streamHas*, phpUnitTestClasses) check the index first, which helps about 30 more rules.
  • Not gated: triggers in nearly every file (declare, use, class…), punctuation, text inside docblocks, whitespace rules.

Result: on a private 8.8k-file codebase, the 72 rules this touches: 5.91 s → 4.38 s of CPU. FunctionToConstant 140 → 5 ms, EregToPreg 139 → 6 ms. Building the index costs about 1%.

Tests:

  • New tests for the index, the runner skip, and each gated rule with its trigger in odd case (SprintF, __Line__…).
  • TestMain checks that no rule rules out a source its own tests fix.
  • Existing tests pass, and the private codebase still has 0 diffs. On 28k files with 18.5k diffs, Blink: add a stream version, to share work between rules #279 and this branch give the same output.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Y1vvcRhpVMjWLC4U5j78Xp

gnutix and others added 2 commits November 4, 2026 18:36
Every write to the stream now changes its version. Three rules share one
symbol scan while the file is unchanged, and eight rules skip a second
render when nothing changed. The output is the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A per-file index records the token kinds and words a file holds. A rule
can implement IsCandidate, like PHP-CS-Fixer's isCandidate, and the runner
skips it on files that cannot concern it. The output is the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gnutix
gnutix force-pushed the perf/rule-candidates branch from e477802 to f6b3363 Compare November 4, 2026 17:38
@gnutix gnutix changed the title Blink: let rules skip files that cannot concern them (isCandidate) Blink: let rules skip files without their trigger Nov 4, 2026
@TomasVotruba
TomasVotruba merged commit 4bb7124 into ecsphp:main Nov 4, 2026
20 of 21 checks passed
@TomasVotruba

Copy link
Copy Markdown
Member

Nice speed improvement!

I think we should be able to get under 2 secs on such a project

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.

2 participants