Repository navigation
fix(trie-router): count every slash a pattern consumes - #5189
Merged
yusukebe merged 2 commits intoAug 5, 2026
Merged
Conversation
When a `:param{regexp}` match spans several path parts, the node is
requeued so it resumes at the part following the match. The number of
parts to skip was taken from `m[0].match(/\//)?.length`, but without the
`g` flag `String.prototype.match` returns at most one match, so the
count was 1 for any number of slashes.
A pattern spanning three or more parts therefore resumed one part too
early and matched a part it had already consumed:
- `/:dirs{.+}/file.html` did not match `/foo/bar/baz/file.html`
- `/:file{.*}/*` returned its handler twice for `/a/b/c`
Two parts happened to give the right answer, which is why the existing
`/foo/bar/file.html` case passed.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5189 +/- ##
=======================================
Coverage 79.70% 79.70%
=======================================
Files 155 155
Lines 10889 10889
Branches 2280 2280
=======================================
Hits 8679 8679
Misses 2210 2210 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Member
|
Hi @Jaybhade, Since Hono doesn't include code comments for this level of processing, I think you should just delete that part. |
Contributor
Author
|
Done in 41d9b67 — comment removed, the change is just the |
Member
|
@Jaybhade Thank you! |
BlankParticle
pushed a commit
to BlankParticle/hono
that referenced
this pull request
Sep 6, 2026
* fix(trie-router): count every slash a pattern consumes
When a `:param{regexp}` match spans several path parts, the node is
requeued so it resumes at the part following the match. The number of
parts to skip was taken from `m[0].match(/\//)?.length`, but without the
`g` flag `String.prototype.match` returns at most one match, so the
count was 1 for any number of slashes.
A pattern spanning three or more parts therefore resumed one part too
early and matched a part it had already consumed:
- `/:dirs{.+}/file.html` did not match `/foo/bar/baz/file.html`
- `/:file{.*}/*` returned its handler twice for `/a/b/c`
Two parts happened to give the right answer, which is why the existing
`/foo/bar/file.html` case passed.
* chore(trie-router): drop the explanatory comment
---------
Co-authored-by: Jayesh Bhade <[email protected]>
This was referenced Oct 6, 2026
delhombre
added a commit
to delhombre/typelapse
that referenced
this pull request
Oct 13, 2026
* fix(diff): a scene holds at most a readable number of lines One card serves every scene, so the tallest frame sizes them all: the hero's example PR (honojs/hono#5189) fitted to a 56-row frame drew its code at 9.4 px in the 1080p export. The walkthrough is now split in pages of at most 24 rows (18.8 px), a window taller than a page continues in the next scene behind a separator. Adds a read-only px measurement over the shared fit. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * fix(diff): the total budget is a page budget, hono#5189 shows node.ts MAX_WALKTHROUGH_PAGES = 8 replaces the 50-line total (the line budget is derived: pages x MAX_SCENE_LINES), the pages are cut at 8 when their scaffold pushes past it. The hero's example PR now shows all three files, no marker. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * fix(diff): the omission marker counts the files a page cut hides Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * fix(diff): a walkthrough plays at most 4 pages (19.7 s for the longest PR) Measured: the three hero examples fit 4 pages (14.2, 17.9, 14.2 s) and the 246-file comparison lands at 11 steps, 19.7 s where 8 pages ran 40 s. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * fix(diff): interleave a block's changes, caption pages by their own hunk, marker on the last page - a cut inside a rewritten block keeps each old line facing its new line - a continuation of an addition has no near-empty opening frame - a page opens on its own first hunk's caption, never the previous page's - the omission marker sits on the last page only - the px helper moves under tests/support (test-only code) Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]>
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.
When a
:param{regexp}match spans several path parts,Node#searchrequeues the child intocurNodesQueue[componentCount]so it resumes at the part after the match.componentCountcame from:Without the
gflag,String.prototype.matchreturns a single-match array, so.lengthis1for any number of slashes. A pattern spanning three or more parts resumed one part too early and matched a part it had already consumed.Two parts happen to give the right answer (one slash →
1), which is why the existing/foo/bar/file.htmlcase passes and this went unnoticed.Symptoms
Both reproduce on a plain
new Hono()—RegExpRouterreportsUnsupportedPathErrorfor these routes, soSmartRouterfalls through toTrieRouter.A route stops matching once the pattern spans 3+ parts.
/:dirs{.+}/file.htmlis the repo's ownCapture simple multiple directoriesroute:/foo/file.html/foo/bar/file.html/foo/bar/baz/file.html/a/b/c/d/file.htmlA handler is returned twice, so middleware runs twice per request:
At the raw router level:
RegExpRouterandPatternRouteralready return the correct result in every case above; this bringsTrieRouterin line.Fix
Count with a global regexp so the offset reflects every slash the pattern consumed.
Tests
common.case.test.ts— extendedCapture simple multiple directorieswith 3- and 4-directory paths. It runs for every router, so it pins the behaviour across all of them; all pass unchanged, no new skip entries needed.trie-router/node.test.ts— addedPattern spanning multiple parts, covering both the duplicate handler and the missed match at increasing depth.Without the one-line change these fail with
expected 2 to be 1andexpected +0 to be 1.I also fuzzed ~197k random route sets across all four routers looking for any handler returned twice from a single
match(): 16 distinct reproductions before, 0 after.The author should do the following, if applicable
bun run test: 146 files, 4758 passed / 35 skippedbun run format:fix && bun run lint:fixto format the code — prettier clean, 0 lint errorsNode#search; left an inline comment on the counting logic instead