Skip to content

⚡ various optimizations#33

Merged
techouse merged 12 commits intomainfrom
chore/optimizations
Aug 17, 2025
Merged

⚡ various optimizations#33
techouse merged 12 commits intomainfrom
chore/optimizations

Conversation

@techouse
Copy link
Owner

This pull request introduces significant improvements and bug fixes to the query string encoding and decoding logic, with a focus on robust handling of iterables, list formats, and utility methods. The changes enhance correctness, performance, and maintainability, especially in edge cases involving lists and string slicing.

Query String Decoding Improvements

  • Refactored the splitting and truncation logic for query string parameters to more accurately handle limits and overflow, ensuring correct error handling and truncation behavior.
  • Improved charset sentinel detection and key-value pair parsing by switching from elementAt to direct indexing, reducing unnecessary iterator overhead and clarifying intent. [1] [2]
  • Refined array parsing to always wrap values in a list when parseLists is enabled and a []= suffix is present, matching expected query string behavior.
  • Added a private helper method _joinIterableToCommaString to efficiently join iterable values into comma-separated strings for numeric entity interpretation.

Query String Encoding and Iterable Handling

  • Standardized the detection of comma list format using identical instead of equality, and consistently cached list forms of iterables to avoid repeated enumeration and type confusion. [1] [2] [3]
  • Refactored key generation and value access for iterables to use cached lists, ensuring correct indexing and handling of empty or single-element lists, and preventing out-of-range errors. [1] [2] [3] [4] [5]

Utility and Extension Method Fixes

  • Rewrote JS-style slice extensions for List and String to avoid reliance on clamp and min, improving negative index handling, bounds safety, and correctness for empty slices. [1] [2]
  • Removed unnecessary import of dart:math and updated documentation for clarity and style consistency.

Core Model and API Consistency

  • Cleaned up the Undefined model for API symmetry and clarified equality semantics.
  • Updated the main QS class to use a new utility method for converting iterables to index maps, and improved the handling of encoded keys and values for null safety and type correctness. [1] [2] [3]

Internal Utility Refactoring

  • Replaced inefficient conversions to maps for indexed merging with manual iteration, improving performance and correctness when merging lists and maps. [1] [2] [3] [4]
  • Optimized string escaping/unescaping routines for speed and correctness, including direct use of hex tables and early returns for strings without percent-encoding. [1] [2]

@techouse techouse self-assigned this Aug 17, 2025
@techouse techouse added the enhancement New feature or request label Aug 17, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 17, 2025

Walkthrough

Deterministic decode splitting with strict limit checks and sentinel probing; encode treats non-Map iterables via cached indexed lists and identity checks for comma-format; slice methods now clamp ranges; Utils gains segmented encoding/decoding fast-paths and createIndexMap helper.

Changes

Cohort / File(s) Summary
Decode refactor
lib/src/extensions/decode.dart
Eager split into allParts with computed takeCount and overflow detection (optional RangeError); incremental overflow checks use currentListLength + 1; index-based parts access; charset sentinel probing with early exit; numeric-entity path converts Iterables via new _joinIterableToCommaString; []= override gated by options.parseLists; adds static _joinIterableToCommaString.
Encode iterable / comma handling
lib/src/extensions/encode.dart
Replaced equality checks with identity via identical(...) for comma generator; caches non-Map Iterable as seqList_ (List) and drives length/index/value access from it; encoder selection and commaRoundTrip logic use seqList_ presence; key/value emission reads from seqList_ when present.
Slice semantics (safe JS-like slice)
lib/src/extensions/extensions.dart
Removed dart:math import; ListExtension.slice and StringExtension.slice clamp start/end to [0,length] and return empty on inverted/out-of-range ranges; behaviour non-throwing for invalid ranges.
Model minor formatting
lib/src/models/undefined.dart
Moved @OverRide to directly above props getter and changed props to return a const [] (formatting/minor immutability).
QS encode pipeline
lib/src/qs.dart
Iterable inputs normalised via Utils.createIndexMap; accumulator typed as List; precomputes gen and crt (generator and commaRoundTrip) using identical(...); when encoded is Iterable, elements added individually (nulls filtered).
Utilities and helpers
lib/src/utils.dart
Added Utils.createIndexMap(Iterable); replaced toList().asMap() + SplayTreeMap.of with explicit indexed SplayTreeMap builder; fold-based merge replaced with imperative loop; encode/unescape/escape refactors introducing segmented encoding (_writeEncodedSegment), writeCharCode usage, latin1 helpers (_decodeLatin1Percent, _hexVal), fast-paths and early exits; interpretNumericEntities fast-path added.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant QS
  participant Utils
  participant EncodeExt as _$Encode

  Client->>QS: encode(obj, options)
  alt obj is Iterable (non-Map)
    QS->>Utils: createIndexMap(iterable)
    Utils-->>QS: Map<"0","1",...>
    QS->>EncodeExt: _encode(map, generateArrayPrefix, commaRoundTrip)
  else
    QS->>EncodeExt: _encode(obj, generateArrayPrefix, commaRoundTrip)
  end
  EncodeExt-->>QS: Iterable<String> | String
  QS-->>Client: query string
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

I nibble bytes beneath the moon,
Commas hop and lists align,
Slices trimmed and fast-paths tune,
Maps from iterables — index fine.
I stitch percent-escapes in time. 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5de9644 and 645dcd1.

📒 Files selected for processing (5)
  • lib/src/extensions/decode.dart (8 hunks)
  • lib/src/extensions/encode.dart (8 hunks)
  • lib/src/models/undefined.dart (1 hunks)
  • lib/src/qs.dart (3 hunks)
  • lib/src/utils.dart (12 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • lib/src/models/undefined.dart
  • lib/src/extensions/encode.dart
  • lib/src/qs.dart
🔇 Additional comments (22)
lib/src/extensions/decode.dart (9)

52-58: Good fix for the list limit validation.

The corrected condition currentListLength + splitVal.length > options.listLimit properly checks whether adding all split values would exceed the limit, ensuring accurate validation when comma-separated values are parsed.


63-69: Correct single-element list limit check.

The updated condition currentListLength + 1 > options.listLimit properly validates whether adding a single element would exceed the limit.


98-113: Well-structured splitting logic with proper overflow handling.

The refactored approach splits once and then applies limits, which is more efficient than the previous implementation. The logic correctly handles both throwing and non-throwing scenarios for parameter limit exceeded cases.


124-133: Efficient charset sentinel detection with early exit.

Good optimisation using a local variable p and breaking immediately after finding the sentinel. The direct string comparison is cleaner than the previous approach.


139-141: Direct index access improves readability.

Using parts[i] instead of parts.elementAt(i) is more idiomatic and potentially more efficient.


172-177: Good abstraction for iterable-to-string conversion.

The use of the new _joinIterableToCommaString helper properly handles the conversion of iterables to comma-separated strings for numeric entity interpretation, with null-safe handling.


180-182: Proper gating of array override with parseLists option.

The condition now correctly ensures that the []= suffix only forces array creation when options.parseLists is true, maintaining consistency with the library's configuration.


214-228: Robust handling of nested list length calculation.

The logic properly validates the parent segment format and bounds before accessing list elements. The default to 0 for invalid cases ensures safe operation.


443-453: Clean implementation of comma-joining helper.

The _joinIterableToCommaString method efficiently builds a comma-separated string with proper null handling and minimal allocations using StringBuffer.

lib/src/utils.dart (13)

67-67: Good refactoring to use the helper method.

Using _toIndexedTreeMap improves code reuse and maintainability.


95-96: Consistent use of the indexed tree map helper.

Good consistency in using the same helper method across different code paths.


178-190: More idiomatic iteration over map entries.

The explicit for loop is clearer and potentially more efficient than the previous fold-based approach.


192-200: Efficient helper for creating indexed maps.

The _toIndexedTreeMap method efficiently converts iterables to SplayTreeMaps with integer keys, maintaining insertion order.


231-231: Minor: Character code used directly.

Using writeCharCode(c) is more efficient than writing string fragments.


236-236: Efficient hex encoding using lookup table.

Using the pre-computed hexTable for encoding is a good performance optimisation.


261-261: Good fast-path optimisation for non-percent-encoded strings.

Early return when no percent signs are present avoids unnecessary processing.


283-290: Consistent use of writeCharCode for character output.

Using writeCharCode instead of string concatenation is more efficient.

Also applies to: 304-310, 317-323


476-493: Efficient Latin-1 percent decoding with hex lookup.

The _decodeLatin1Percent method provides a fast path for Latin-1 decoding using the optimised _hexVal helper for hex digit conversion.


495-501: Clean hex digit conversion helper.

The _hexVal method efficiently converts hex characters to their numeric values with proper range checking.


514-521: Good fast-path optimisation for Latin-1 decoding.

The early return when no percent signs are present and the use of the optimised _decodeLatin1Percent helper improve performance for Latin-1 charset handling.


657-657: Efficient early exit for strings without numeric entities.

The fast-path check for '&#' avoids unnecessary processing when no numeric entities are present.


703-721: Clean utility for creating index-based maps from iterables.

The createIndexMap method efficiently converts iterables to maps with string indices, with special handling for List types to avoid unnecessary iteration.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/optimizations

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@codacy-production
Copy link

codacy-production bot commented Aug 17, 2025

Coverage summary from Codacy

See diff coverage on Codacy

Coverage variation Diff coverage
-1.68% (target: -1.00%) 90.00%
Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (1f496b8) 737 715 97.01%
Head commit (645dcd1) 815 (+78) 777 (+62) 95.34% (-1.68%)

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 (#33) 190 171 90.00%

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%

See your quality gate settings    Change summary preferences

@codecov
Copy link

codecov bot commented Aug 17, 2025

Codecov Report

❌ Patch coverage is 90.00000% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.33%. Comparing base (1f496b8) to head (645dcd1).

Files with missing lines Patch % Lines
lib/src/utils.dart 83.01% 18 Missing ⚠️
lib/src/extensions/encode.dart 94.44% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #33      +/-   ##
==========================================
- Coverage   97.01%   95.33%   -1.68%     
==========================================
  Files          14       14              
  Lines         737      815      +78     
==========================================
+ Hits          715      777      +62     
- Misses         22       38      +16     

☔ View full report in Codecov by Sentry.
📢 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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🔭 Outside diff range comments (1)
lib/src/extensions/decode.dart (1)

49-71: List limit enforcement misses cumulative growth (can exceed listLimit)

When splitting comma lists, the check only compares the new split length against listLimit, ignoring items already accumulated for the same key. Similarly, for single additions, the guard should check “current + 1 > limit”, not “>= limit”. This allows arrays to grow beyond listLimit across multiple occurrences.

Apply this diff to enforce cumulative limits:

   static dynamic _parseListValue(
     dynamic val,
     DecodeOptions options,
     int currentListLength,
   ) {
     // Fast-path: split comma-separated scalars into a list when requested.
     if (val is String && val.isNotEmpty && options.comma && val.contains(',')) {
       final List<String> splitVal = val.split(',');
-      if (options.throwOnLimitExceeded && splitVal.length > options.listLimit) {
+      if (options.throwOnLimitExceeded &&
+          currentListLength + splitVal.length > options.listLimit) {
         throw RangeError(
           'List limit exceeded. '
           'Only ${options.listLimit} element${options.listLimit == 1 ? '' : 's'} allowed in a list.',
         );
       }
       return splitVal;
     }

     // Guard incremental growth of an existing list as we parse additional items.
-    if (options.throwOnLimitExceeded &&
-        currentListLength >= options.listLimit) {
+    if (options.throwOnLimitExceeded &&
+        currentListLength + 1 > options.listLimit) {
       throw RangeError(
         'List limit exceeded. '
         'Only ${options.listLimit} element${options.listLimit == 1 ? '' : 's'} allowed in a list.',
       );
     }

     return val;
   }
🧹 Nitpick comments (8)
lib/src/models/undefined.dart (1)

20-21: Align props type with Equatable and avoid allocation

Equatable’s recommended signature is List<Object?>. Also, returning a const empty list avoids per-call allocation.

-  List<Object> get props => [];
+  List<Object?> get props => const [];
lib/src/extensions/decode.dart (2)

171-174: Numeric-entities path: helper-based join is tidy

Using a dedicated helper to join Iterables improves readability and avoids reimplementations. One tiny micro-optimisation: skip allocating tmp when val is already a String.

-        final String tmp =
-            val is Iterable ? _joinIterableToCommaString(val) : val.toString();
-        val = Utils.interpretNumericEntities(tmp);
+        if (val is Iterable) {
+          val = Utils.interpretNumericEntities(_joinIterableToCommaString(val));
+        } else {
+          val = Utils.interpretNumericEntities(val.toString());
+        }

211-219: currentListLength derivation is fragile (parsing “[idx]” from chain)

Parsing the parent index via chain.slice(0, -1).join('') won’t yield a pure integer (e.g. “[0]”), so int.tryParse fails and currentListLength becomes 0 even when a parent list exists. Prefer peeking at the immediate previous segment and extracting its bracketed integer safely.

-    if (chain.isNotEmpty && chain.last == '[]') {
-      final int? parentKey = int.tryParse(chain.slice(0, -1).join(''));
-
-      currentListLength = parentKey != null &&
-              val is List &&
-              val.firstWhereIndexedOrNull((int i, _) => i == parentKey) != null
-          ? val.elementAt(parentKey).length
-          : 0;
-    } else {
-      currentListLength = 0;
-    }
+    if (chain.length >= 2 && chain.last == '[]') {
+      final String prev = chain[chain.length - 2];
+      final bool bracketed = prev.startsWith('[') && prev.endsWith(']');
+      final int? parentIndex =
+          bracketed ? int.tryParse(prev.substring(1, prev.length - 1)) : null;
+      if (parentIndex != null &&
+          parentIndex >= 0 &&
+          val is List &&
+          parentIndex < val.length) {
+        final dynamic parent = val[parentIndex];
+        currentListLength = parent is List ? parent.length : 0;
+      } else {
+        currentListLength = 0;
+      }
+    } else {
+      currentListLength = 0;
+    }
lib/src/extensions/encode.dart (2)

190-195: Generate indices more idiomatically

Minor readability nit: List.generate is clearer than allocate-and-fill.

-        final int n = seqList_.length;
-        final List<int> idxs = List<int>.filled(n, 0, growable: false);
-        for (int i = 0; i < n; i++) {
-          idxs[i] = i;
-        }
-        keys = idxs;
+        keys = List<int>.generate(seqList_.length, (i) => i, growable: false);

264-267: Avoid relying on encodedKey when emitting comma-joined sentinel value

In the comma-join branch, objKeys contains a sentinel Map with 'value'. Building keyPrefix from key.toString() is unnecessary and brittle. Use adjustedPrefix directly when in the comma mode and processing the sentinel.

-      final String keyPrefix = seqList_ != null
-          ? generateArrayPrefix(adjustedPrefix, encodedKey)
-          : '$adjustedPrefix${allowDots ? '.$encodedKey' : '[$encodedKey]'}';
+      final bool isCommaSentinel =
+          key is Map<String, dynamic> && key.containsKey('value');
+      final String keyPrefix = (isCommaSentinel &&
+              identical(generateArrayPrefix, ListFormat.comma.generator))
+          ? adjustedPrefix
+          : (seqList_ != null
+              ? generateArrayPrefix(adjustedPrefix, encodedKey)
+              : '$adjustedPrefix${allowDots ? '.$encodedKey' : '[$encodedKey]'}');
lib/src/qs.dart (1)

150-152: Minor type check clean-up

Using “String?” in an is-check is unusual here. Plain String reads better and is sufficient since map keys are String.

-      if (key is! String? || (obj[key] == null && options.skipNulls)) {
+      if (key is! String || (obj[key] == null && options.skipNulls)) {
         continue;
       }
lib/src/utils.dart (2)

68-72: Reduce duplication: extract Iterable→indexed map helper

The two blocks that convert an Iterable into a SplayTreeMap<int, dynamic> are duplicated. Extracting a small helper improves maintainability and reduces the chance of diverging behaviour.

Apply this diff in the selected ranges:

-          final SplayTreeMap<int, dynamic> target_ =
-              SplayTreeMap<int, dynamic>();
-          int i_ = 0;
-          for (final dynamic item in target) {
-            target_[i_++] = item;
-          }
+          final SplayTreeMap<int, dynamic> target_ = _toIndexedTreeMap(target);
-              final SplayTreeMap<int, dynamic> target_ =
-                  SplayTreeMap<int, dynamic>();
-              int j_ = 0;
-              for (final dynamic item in target) {
-                target_[j_++] = item;
-              }
+              final SplayTreeMap<int, dynamic> target_ = _toIndexedTreeMap(target);

Add this helper outside the selected ranges (e.g., near the other private helpers):

static SplayTreeMap<int, dynamic> _toIndexedTreeMap(Iterable iterable) {
  final map = SplayTreeMap<int, dynamic>();
  var i = 0;
  for (final v in iterable) {
    map[i++] = v;
  }
  return map;
}

Also applies to: 100-105


398-399: Nit: comments for letter ranges are swapped

The code points are correct, but the comments should read A-Z for 0x41..0x5A and a-z for 0x61..0x7A.

Apply this diff (also included in the previous fix):

-        case int v when v >= 0x41 && v <= 0x5A: // a-z
-        case int v when v >= 0x61 && v <= 0x7A: // A-Z
+        case int v when v >= 0x41 && v <= 0x5A: // A-Z
+        case int v when v >= 0x61 && v <= 0x7A: // a-z
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1f496b8 and 5de9644.

📒 Files selected for processing (6)
  • lib/src/extensions/decode.dart (5 hunks)
  • lib/src/extensions/encode.dart (8 hunks)
  • lib/src/extensions/extensions.dart (3 hunks)
  • lib/src/models/undefined.dart (1 hunks)
  • lib/src/qs.dart (3 hunks)
  • lib/src/utils.dart (12 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (19)
lib/src/extensions/extensions.dart (2)

48-66: List.slice: safer JS-style semantics look good

Negative indices, clamping, and non-throwing behaviour are correctly implemented. Returning an empty list when e <= s matches JS slice.


85-102: String.slice: correct JS-like behaviour

Index normalisation and clamping are sound; empty result when e <= s is consistent with JS slice semantics.

lib/src/extensions/decode.dart (3)

97-112: Deterministic split + overflow handling is a solid improvement

Single split followed by truncate-or-throw is clearer and avoids repeated iteration. The throwOnLimitExceeded branch correctly throws on overflow.


123-133: Charset sentinel scan: early break is correct

Breaking after detecting the sentinel avoids needless scanning and is consistent with only a single sentinel being relevant.


177-179: Array-forcing quirk guarded by parseLists is correct

Gating the literal “[]=“ behaviour behind options.parseLists avoids surprising array construction when list parsing is disabled.

lib/src/extensions/encode.dart (3)

73-75: Identity check for ListFormat.comma is the right call

Using identical(...) avoids surprises if generators aren’t value-equal but are identity-stable.


146-155: Good: cache non-Map iterable as a list once

Avoids repeated enumeration and out-of-range lookups. This will also stabilise length checks and index access.


283-286: Encoder suppression in comma + encodeValuesOnly path is correct

Skipping encoder here prevents double-encoding after pre-encoding during join.

lib/src/qs.dart (4)

118-119: Normalising iterables via Utils.createIndexMap is a good simplification

This removes the asMap detour and centralises iterable handling in one utility.


122-122: Explicit List for fragments is a nice safety win

Clarifies intent and catches accidental non-string insertions at compile time.


154-164: Precomputing generator and comma-round-trip flag is clear and avoids recomputation

Using identical(...) for the comma format aligns with the encoder changes.


118-119: createIndexMap confirms stable String keys and preserved iteration order
The implementation uses i.toString() for all keys and builds a default Dart LinkedHashMap, which maintains insertion order for both List and general Iterable inputs. No changes required.

lib/src/utils.dart (7)

187-199: Loop-based map merge is clearer and avoids transient allocations

Replacing the fold with a straightforward for-loop improves readability and avoids building intermediate structures. Looks good.


230-236: escape(): Fast-path writes via writeCharCode/hexTable are a solid micro-optimisation

Switching to writeCharCode for safe characters and hexTable lookups for escapes reduces string churn. Good change.


260-327: unescape(): Early fast-path and error-tolerant decoding are well-implemented

  • The early return when there is no '%' avoids needless work.
  • Using writeCharCode eliminates substring concatenations.
  • Error handling gracefully treats malformed sequences as literals.

LGTM.


433-457: latin1 percent-decoder: tight, branch-light implementation

The hex nibble decoder and scan loop are efficient and easy to reason about. Nice addition.


471-478: decode(latin1): good fast-path and resilient fallback

  • Early exit when no '%' keeps the hot path cheap.
  • On errors, returning the plus-normalised string preserves original data.

LGTM.


615-615: interpretNumericEntities: early check avoids scanning when not needed

The fast-path on missing '&#' is a good low-cost guard. Approved.


660-678: createIndexMap(): useful utility; aligns with QS.encode iterable normalisation

Straightforward implementation, returns stringified indices consistently for both List and generic Iterable. Consider documenting stability guarantees (iteration order mirrors source order), but otherwise looks good.

@techouse
Copy link
Owner Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Aug 17, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

This was referenced Feb 27, 2026
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.

1 participant