Conversation
`getPartialMatcher` decides whether a directory is worth crawling. It walked the pattern and the input in lockstep, so `**` could only ever stand for one or more segments: once its matcher rejected an input part, the whole pattern was abandoned. That is wrong whenever the segment after `**` is the one that matches. With `dot: false` picomatch's `**` does not match `.a`, so `**/.a/*.txt` never descended into `.a` and the glob returned nothing, while fast-glob returns the file. Track the pattern and input positions separately so a `**` that does not consume the current segment falls through to the next pattern part instead of ending the walk. Fixes SuperchupuDev#187
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.
Fixes #187.
The bug
getPartialMatcheris the predicate that decides whether a directory is worth descending into. It walked the pattern parts and the input parts in lockstep with a single index, which meant**could only ever stand for one or more segments — as soon as the**matcher rejected the current input part, the whole pattern was abandoned.**also matches zero segments, and that case matters as soon as the segment right after the**is the one that matches. Withdot: false, picomatch's**does not match.foo, so:The crawler never entered
.foo, so the correct full matcher downstream never got the chance to run. Withdot: truethe**matcher accepts.foo, which is why the issue only reproduces on the default.The same shape breaks any pattern where a
**precedes a segment the**itself won't match —**/.deep/a/**/*.txtreturned[]too.The fix
Track the pattern position (
j) and the input position (k) separately. When a**doesn't consume the current input segment, advance only the pattern and retry the next pattern part against the same segment, instead of ending the walk. When it does consume it, the existing earlyreturn trueis unchanged.The two indices only diverge in that one case, so the common path costs the same.
noglobstarstill treats**as a literal part.Validation
pnpm test→ 135 pass, 0 fail. Reverting onlysrc/utils.tsfails all 5 new assertions, so they pin the fix rather than restate current behavior.pnpm typecheck,pnpm check(biome),pnpm buildall clean.pnpm benchon thetypescript-eslintfixture — no regression, tinyglobby still fastest in both:packages/*/tsconfig.json**/*Tests added: three unit tests on
getPartialMatcher(leading**, mid-pattern**, and a negative case that the looser walk doesn't turn into a false positive) plus two integration tests against the existing.a/.deepfixtures.I did not touch the
dotsemantics themselves — this is purely the traversal predicate being stricter than the matcher it guards. The other half of #187 (whetherdot: falseshould skip dot directories at all, as opposed to dot files) is a deliberate behavior choice and is left alone.🤖 Written with assistance from Claude Code.