Skip to content

✨ qs.js 6.15.3 parity#61

Merged
techouse merged 8 commits into
mainfrom
chore/qs-5.13.2
Jul 2, 2026
Merged

✨ qs.js 6.15.3 parity#61
techouse merged 8 commits into
mainfrom
chore/qs-5.13.2

Conversation

@techouse

@techouse techouse commented Jul 2, 2026

Copy link
Copy Markdown
Owner

This pull request focuses on improving how list limits and error handling are enforced during query string (qs) decoding, ensuring stricter compliance with Node qs 6.15.3 behavior. It also adds regression test coverage for edge cases and unbalanced bracket keys. The main changes include stricter enforcement of list limits (with exceptions thrown when exceeded in strict mode), unification of error handling logic, and expanded test cases to match Node qs behavior.

List limit enforcement and error handling:

  • Introduced Utils.throwListLimitExceeded() to standardize throwing a RangeError when the list limit is exceeded, and replaced all previous ad hoc error messages and throws with this utility. [1] [2] [3] [4]
  • Updated Utils.combine() and all internal merging logic to throw immediately (when throwOnLimitExceeded is enabled) instead of returning an overflow map, for both flat comma lists and incremental list growth. [1] [2] [3] [4] [5]
  • Added pre-allocation checks for flat comma-separated values to reject oversized values before splitting and allocating memory, matching Node qs cumulative enforcement. [1] [2]

Behavioral and compatibility improvements:

  • Ensured that duplicate key combinations and mixed list merges enforce the cumulative list limit across all code paths, matching Node qs 6.15.3. [1] [2] [3] [4] [5] [6] [7] [8]
  • Updated error handling in tests to expect exceptions (not overflow maps) when strict list limits are exceeded.

Regression test coverage:

  • Added comprehensive tests for unbalanced bracket keys, chunk-boundary surrogate pairs, and cyclic compaction to match Node qs 6.15.3 regression coverage.

Documentation and housekeeping:

  • Updated documentation and comments to clarify the semantics of flat vs. nested list values, and documented new error handling logic. [1] [2]
  • Updated CHANGELOG.md to reflect the bug fixes, new strict error handling, and new regression test coverage.

@techouse techouse self-assigned this Jul 2, 2026
@techouse techouse added the enhancement New feature or request label Jul 2, 2026
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@techouse, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 38 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9190c1a6-46d1-4476-aff2-7c3536ab8e27

📥 Commits

Reviewing files that changed from the base of the PR and between 8835a06 and 5793a48.

📒 Files selected for processing (2)
  • lib/src/extensions/decode.dart
  • lib/src/utils.dart

Walkthrough

This PR tightens listLimit/throwOnLimitExceeded enforcement in query-string decoding. It distinguishes flat vs. nested comma lists, adds throwing helpers in Utils, propagates the flag through combine/merge and decode call sites, and updates/adds corresponding tests and a changelog entry.

Changes

List limit enforcement

Layer / File(s) Summary
Utils.combine/merge limit enforcement core
lib/src/utils.dart
Adds _throwListLimitExceeded and _enforceListLimit helpers; applies enforcement across merge's list-building paths and extends combine with a throwOnLimitExceeded parameter that throws instead of building overflow maps.
Decode.dart flat/nested comma list handling
lib/src/extensions/decode.dart
Introduces isFlatListValue distinction, counts commas and throws when limits are exceeded in _parseListValue, and forwards throwOnLimitExceeded through Utils.combine calls in promotion, duplicate-key, and bracket-segment handling.
Test coverage and changelog
test/unit/decode_test.dart, test/unit/utils_test.dart, CHANGELOG.md
Adds unbalanced-bracket-key and qs 6.15.3 parity test groups, updates overflow expectations, extends Utils.merge/combine tests for limits, revises cycle and surrogate-boundary tests, and adds a changelog entry.

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

Sequence Diagram(s)

sequenceDiagram
  participant Decoder as _parseQueryStringValues
  participant ListParser as _parseListValue
  participant Promoter as _promoteCommaListIfNeeded
  participant Utils as Utils.combine/merge

  Decoder->>ListParser: parse value with isFlatListValue flag
  ListParser->>ListParser: count commas, check listLimit
  alt limit exceeded & throwOnLimitExceeded
    ListParser-->>Decoder: throw RangeError
  else within limit
    ListParser-->>Decoder: parsed list value
  end
  Decoder->>Promoter: promote comma list if needed
  Promoter->>Utils: combine(listLimit, throwOnLimitExceeded)
  Decoder->>Utils: combine duplicate keys (throwOnLimitExceeded)
  alt overflow & throwOnLimitExceeded
    Utils-->>Decoder: throw RangeError
  else overflow, no throw
    Utils-->>Decoder: overflow map
  end
Loading

Possibly related PRs

  • techouse/qs#26: Introduces the throwOnLimitExceeded option and list-limit RangeError behaviour that this PR builds upon.
  • techouse/qs#46: Also modifies Utils.combine/Utils.merge and decode.dart to propagate listLimit and handle overflow.
  • techouse/qs#55: Modifies the same comma/list promotion paths in decode.dart and Utils.combine list-limit handling.

Suggested labels: bug, test

Suggested reviewers: Copilot

Poem

A comma too far, a list stretched thin,
Now RangeError shouts before the overflow begins.
Flat or nested, the limit holds true,
Surrogate pairs stitched, cycles seen through.
This bunny hops on, tests all in a row —
🐇 thump, thump, safe limits below!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the change summary, but it misses required template sections such as Fixes #(issue), testing, type of change, and checklist items. Reformat the PR body to match the template, add the issue number, testing steps, type of change, and complete the checklist.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title clearly refers to the PR's main change: parity with Node qs 6.15.3.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/qs-5.13.2

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.

@codacy-production

codacy-production Bot commented Jul 2, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 duplication

Metric Results
Duplication 0

View in Codacy

🟢 Coverage 96.88% diff coverage · +0.04% coverage variation

Metric Results
Coverage variation +0.04% coverage variation (-1.00%)
Diff coverage 96.88% diff coverage

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (4ee0278) 1423 1391 97.75%
Head commit (5793a48) 1452 (+29) 1420 (+29) 97.80% (+0.04%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#61) 64 62 96.88%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.87500% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.79%. Comparing base (4ee0278) to head (5793a48).

Files with missing lines Patch % Lines
lib/src/utils.dart 95.23% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #61      +/-   ##
==========================================
+ Coverage   97.75%   97.79%   +0.04%     
==========================================
  Files          20       20              
  Lines        1423     1452      +29     
==========================================
+ Hits         1391     1420      +29     
  Misses         32       32              

☔ 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

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
lib/src/extensions/decode.dart (1)

352-368: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate Utils.combine call bodies for the two switch cases.

The (true, Duplicates.combine) and (true, _) when bracketSuffix cases are byte-for-byte identical. Dart's switch-statement fallthrough (stacking case labels with no code between them) lets you share the body without changing the guard semantics:

♻️ Proposed fix using switch fallthrough
       switch ((existing, options.duplicates)) {
         case (true, Duplicates.combine):
-          // Existing key + `combine` policy: merge old/new values.
-          obj[key] = Utils.combine(
-            obj[key],
-            val,
-            listLimit: options.listLimit,
-            throwOnLimitExceeded: options.throwOnLimitExceeded,
-          );
-          break;
-        case (true, _) when bracketSuffix:
+        case (true, _) when bracketSuffix:
           // `qs` always combines duplicate bracket-notation keys, even when
-          // the duplicates option is `first` or `last`.
+          // the duplicates option is `first` or `last` (and the `combine`
+          // policy always merges regardless of notation).
           obj[key] = Utils.combine(
             obj[key],
             val,
             listLimit: options.listLimit,
             throwOnLimitExceeded: options.throwOnLimitExceeded,
           );
           break;

This PR itself needed to update both blocks identically, which is a good sign this duplication should be collapsed to prevent future divergence.

🤖 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 `@lib/src/extensions/decode.dart` around lines 352 - 368, The `(true,
Duplicates.combine)` and `(true, _) when bracketSuffix` branches in the switch
both perform the same Utils.combine update on obj[key], so collapse them into a
single shared body using switch fallthrough while preserving the bracketSuffix
guard behavior. Keep the logic in decode.dart centered on the same unique
symbols (Utils.combine, bracketSuffix, and the switch on duplicates) so future
changes only need to be made once.
lib/src/utils.dart (1)

68-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Unify the list-limit error text
lib/src/utils.dart and lib/src/extensions/decode.dart both build the same List parsing is disabled / List limit exceeded message. Move that into one internal helper so wording and pluralisation stay in sync.

🤖 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 `@lib/src/utils.dart` around lines 68 - 75, The list-limit error message is
duplicated between _throwListLimitExceeded in utils.dart and the matching logic
in decode.dart, so centralize the wording in one internal helper and have both
call it. Update _throwListLimitExceeded to own the full message construction,
then refactor the decode-side list parsing path to reuse that helper so the
disabled/limit-exceeded text and pluralisation stay identical.
🤖 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.

Nitpick comments:
In `@lib/src/extensions/decode.dart`:
- Around line 352-368: The `(true, Duplicates.combine)` and `(true, _) when
bracketSuffix` branches in the switch both perform the same Utils.combine update
on obj[key], so collapse them into a single shared body using switch fallthrough
while preserving the bracketSuffix guard behavior. Keep the logic in decode.dart
centered on the same unique symbols (Utils.combine, bracketSuffix, and the
switch on duplicates) so future changes only need to be made once.

In `@lib/src/utils.dart`:
- Around line 68-75: The list-limit error message is duplicated between
_throwListLimitExceeded in utils.dart and the matching logic in decode.dart, so
centralize the wording in one internal helper and have both call it. Update
_throwListLimitExceeded to own the full message construction, then refactor the
decode-side list parsing path to reuse that helper so the
disabled/limit-exceeded text and pluralisation stay identical.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e3ec32d5-999d-4301-a056-31f61dd0bd96

📥 Commits

Reviewing files that changed from the base of the PR and between 4ee0278 and 8835a06.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • lib/src/extensions/decode.dart
  • lib/src/utils.dart
  • test/unit/decode_test.dart
  • test/unit/utils_test.dart

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR tightens query-string decoding behavior to better match Node qs 6.15.3, focusing on stricter list-limit enforcement (including strict-mode exceptions) and expanding regression coverage for tricky parsing and encoding edge cases.

Changes:

  • Centralizes strict list-limit failures via Utils.throwListLimitExceeded() and threads strict enforcement through list merging/combining paths.
  • Adjusts decode list-growth and comma-split behaviors to enforce cumulative list limits more consistently across duplicate keys and mixed notation.
  • Adds regression tests for unbalanced bracket keys, list-limit edge cases, cyclic compaction, and surrogate-pair chunk-boundary encoding.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/unit/utils_test.dart Adds coverage for Utils.merge/Utils.combine list-limit overflow vs strict-throw behavior, plus cyclic compaction and surrogate-boundary encoding tests.
test/unit/decode_test.dart Adds unbalanced-bracket regression cases and qs 6.15.3 list-limit parity tests (strict throws vs overflow objects).
lib/src/utils.dart Introduces throwListLimitExceeded and enforces listLimit during merge/combine flows (including strict-mode throwing).
lib/src/extensions/decode.dart Unifies strict list-limit error behavior and adds a pre-check for oversized flat comma-separated values.
CHANGELOG.md Documents the parity fixes and newly added regression coverage under 1.8.1-dev.

Comment thread lib/src/extensions/decode.dart
@techouse techouse merged commit aaf5783 into main Jul 2, 2026
17 of 18 checks passed
@techouse techouse deleted the chore/qs-5.13.2 branch July 2, 2026 22:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants