Skip to content

feat(experimental): never decode the hash, protect-only encoding - #2760

Draft
posva wants to merge 1 commit into
mainfrom
feat/experimental-hash-encoding
Draft

feat(experimental): never decode the hash, protect-only encoding#2760
posva wants to merge 1 commit into
mainfrom
feat/experimental-hash-encoding

Conversation

@posva

@posva posva commented Jul 20, 2026

Copy link
Copy Markdown
Member

The experimental router no longer decodes the hash. route.hash is exactly what appears in the URL, like location.hash.

encodeHash only escapes unsafe characters (space, ", <, >, backtick, non-ASCII). Existing percent sequences and lone % are kept, so it never double-encodes and resolving a resolved location changes nothing.

This keeps %26 and & distinct and avoids the bug behind nuxt/nuxt#32774 / #2756 (experimental only, v4 unchanged).

  • New src/experimental/encoding.ts: defines encodeHash, re-exports the rest from the legacy module to avoid bundle duplication.
  • Query and params are unchanged: they still decode.
  • To read a decoded hash, use decodeURIComponent(route.hash).
  • Removes the unused NEW_stringifyURL from src/location.ts.

Size: webRouter_experimental.js min 16.43kb -> 16.52kb.

The hash is now kept as it appears in the URL, like location.hash.
encodeHash only protects unsafe characters and keeps existing percent
sequences and lone % as they are, making it idempotent and resolve
round-trips lossless.

Also removes the now-unused NEW_stringifyURL from src/location.ts.
@netlify

netlify Bot commented Jul 20, 2026

Copy link
Copy Markdown

Deploy Preview for vue-router canceled.

Name Link
🔨 Latest commit 0f89589
🔍 Latest deploy log https://app.netlify.com/projects/vue-router/deploys/6a5e404e1dce3f000849586e

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The experimental router now preserves raw hash encoding during parsing, adds an idempotent encodeHash, and uses it when constructing resolved URLs. URL stringification moved to an experimental helper, with tests covering encoded hashes, navigation, and re-resolution.

Changes

Hash encoding and routing

Layer / File(s) Summary
Hash encoding contract
packages/router/src/experimental/encoding.ts, packages/router/src/experimental/encoding.spec.ts
Adds encodeHash, re-exports encoding helpers, preserves existing percent-encoded sequences, and tests encoding and idempotency behavior.
Raw hash parsing and URL stringification
packages/router/src/experimental/location.ts, packages/router/src/experimental/location.spec.ts, packages/router/src/location.ts
Preserves raw hashes in experimental_parseURL, adds experimental_stringifyURL, and removes NEW_stringifyURL.
Resolver and navigation integration
packages/router/src/experimental/route-resolver/*, packages/router/src/experimental/query.ts, packages/router/src/experimental/router.spec.ts
Routes encoding imports through the experimental module, encodes hashes during resolution, and verifies navigation and re-resolution stability.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Router
  participant Resolver
  participant encodeHash
  participant stringifyURL
  Router->>Resolver: resolve location
  Resolver->>encodeHash: encode hash
  encodeHash-->>Resolver: stable encoded hash
  Resolver->>stringifyURL: build fullPath
  stringifyURL-->>Router: resolved URL and hash
Loading

Possibly related PRs

  • vuejs/router#2661: Updates related experimental_parseURL hash handling in the same location module.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main experimental hash-handling change and mentions both never decoding and protect-only encoding.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/experimental-hash-encoding

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Jul 20, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/vue-router@2760

commit: 0f89589

@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.59459% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.01%. Comparing base (94dca11) to head (0f89589).
⚠️ Report is 28 commits behind head on main.

Files with missing lines Patch % Lines
.../src/experimental/route-resolver/resolver-fixed.ts 80.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2760      +/-   ##
==========================================
+ Coverage   86.99%   87.01%   +0.01%     
==========================================
  Files          93       94       +1     
  Lines       10722    10738      +16     
  Branches     2478     2479       +1     
==========================================
+ Hits         9328     9344      +16     
  Misses       1388     1388              
  Partials        6        6              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/router/src/experimental/route-resolver/resolver-fixed.ts (1)

305-305: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use nullish coalescing for consistency.

For consistency with the identical assignment on line 221 (to.hash ?? ''), consider using the nullish coalescing operator ?? here instead of the logical OR ||.

♻️ Proposed refactor
-        const hash = encodeHash(to.hash || '')
+        const hash = encodeHash(to.hash ?? '')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/router/src/experimental/route-resolver/resolver-fixed.ts` at line
305, Update the hash assignment in the resolver flow to use nullish coalescing
(`??`) instead of logical OR (`||`), matching the existing `to.hash ?? ''`
assignment and preserving non-nullish hash values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/router/src/experimental/encoding.ts`:
- Around line 32-36: Reorder the replacement chain in the encoding logic so
ENC_CURLY_OPEN_RE, ENC_CURLY_CLOSE_RE, and ENC_CARET_RE are restored before
ENC_PERCENT_RE. Keep percent restoration last, ensuring values such as %257B
become %7B without being decoded into literal characters.

---

Nitpick comments:
In `@packages/router/src/experimental/route-resolver/resolver-fixed.ts`:
- Line 305: Update the hash assignment in the resolver flow to use nullish
coalescing (`??`) instead of logical OR (`||`), matching the existing `to.hash
?? ''` assignment and preserving non-nullish hash values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 29d91d0e-6975-44c9-ada1-8d7aec9c74e2

📥 Commits

Reviewing files that changed from the base of the PR and between 94dca11 and 0f89589.

📒 Files selected for processing (11)
  • packages/router/src/experimental/encoding.spec.ts
  • packages/router/src/experimental/encoding.ts
  • packages/router/src/experimental/location.spec.ts
  • packages/router/src/experimental/location.ts
  • packages/router/src/experimental/query.ts
  • packages/router/src/experimental/route-resolver/matchers/matcher-pattern.ts
  • packages/router/src/experimental/route-resolver/resolver-abstract.ts
  • packages/router/src/experimental/route-resolver/resolver-fixed.spec.ts
  • packages/router/src/experimental/route-resolver/resolver-fixed.ts
  • packages/router/src/experimental/router.spec.ts
  • packages/router/src/location.ts

Comment on lines +32 to +36
// restore every original %, so pre-encoded sequences pass through
.replace(ENC_PERCENT_RE, '%')
.replace(ENC_CURLY_OPEN_RE, '{')
.replace(ENC_CURLY_CLOSE_RE, '}')
.replace(ENC_CARET_RE, '^')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reorder regex replacements to prevent accidental decoding.

By restoring %25 to % before restoring {, }, and ^, explicitly encoded characters like %7B will be unintentionally decoded into {, violating the "never decoding" contract.

For example:

  1. Input: %7B
  2. commonEncode encodes it to %257B.
  3. replace(ENC_PERCENT_RE, '%') restores it to %7B.
  4. replace(ENC_CURLY_OPEN_RE, '{') matches the restored %7B and replaces it with {.

By moving the % restoration to the end of the chain, commonEncode's %257B will safely bypass the curly-brace replace (since it doesn't match %7B) and then correctly restore to %7B.

🐛 Proposed fix
     commonEncode(text)
-      // restore every original %, so pre-encoded sequences pass through
-      .replace(ENC_PERCENT_RE, '%')
       .replace(ENC_CURLY_OPEN_RE, '{')
       .replace(ENC_CURLY_CLOSE_RE, '}')
       .replace(ENC_CARET_RE, '^')
+      // restore every original %, so pre-encoded sequences pass through.
+      // done last so explicitly encoded chars like %7B bypass the replaces above
+      .replace(ENC_PERCENT_RE, '%')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// restore every original %, so pre-encoded sequences pass through
.replace(ENC_PERCENT_RE, '%')
.replace(ENC_CURLY_OPEN_RE, '{')
.replace(ENC_CURLY_CLOSE_RE, '}')
.replace(ENC_CARET_RE, '^')
// restore every original %, so pre-encoded sequences pass through
.replace(ENC_CURLY_OPEN_RE, '{')
.replace(ENC_CURLY_CLOSE_RE, '}')
.replace(ENC_CARET_RE, '^')
// restore every original %, so pre-encoded sequences pass through.
// done last so explicitly encoded chars like %7B bypass the replaces above
.replace(ENC_PERCENT_RE, '%')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/router/src/experimental/encoding.ts` around lines 32 - 36, Reorder
the replacement chain in the encoding logic so ENC_CURLY_OPEN_RE,
ENC_CURLY_CLOSE_RE, and ENC_CARET_RE are restored before ENC_PERCENT_RE. Keep
percent restoration last, ensuring values such as %257B become %7B without being
decoded into literal characters.

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