Skip to content

⚡ more performance optimizations#50

Merged
techouse merged 4 commits intomainfrom
chore/optimizations-2
Feb 27, 2026
Merged

⚡ more performance optimizations#50
techouse merged 4 commits intomainfrom
chore/optimizations-2

Conversation

@techouse
Copy link
Owner

@techouse techouse commented Feb 27, 2026

This pull request introduces significant performance optimizations and a major refactor of the iterative encoder traversal logic in the query string serialization library. The changes include a new phase-driven stack frame model that eliminates per-child callback allocations, a guarded fast path for deep single-key map chains, improved key-path materialization, and lower-allocation fragment assembly. Additionally, new internal types and utility classes are introduced to support these improvements, and test/performance infrastructure is enhanced.

Performance optimizations and encoder traversal refactor:

  • Refactored the core encoder to use a phase-driven stack frame model (EncodePhase), removing per-child callback allocations and preserving existing cycle/null/comma semantics. This significantly reduces allocation overhead and improves traversal efficiency. [1] [2] [3] [4]
  • Added a guarded fast path (_tryEncodeLinearChain) for deep single-key map chains under strict option constraints, enabling linear-time encoding and reducing allocations for these workloads. [1] [2]
  • Optimized key-path materialization in KeyPathNode.materialize() to be linear-time by building the string once at the end instead of per-step.
  • Introduced single-entry MRU caches for bracket and dot key segments to minimize repeated string allocations during key-path construction.
  • Lowered allocation pressure in top-level fragment assembly and improved handling of comma-list and empty-list cases. [1] [2] [3]

Internal types and utility classes:

  • Introduced the _ValueSentinel class to distinguish synthetic "value" entries (e.g., comma-list joins) from normal keys during traversal. [1] [2]

Test and performance infrastructure:

  • Added tool/perf_snapshot.dart for repeatable deep encode performance snapshots and expanded encode/key-path regression coverage for deep chains and fast-path fallback scenarios.

Changelog:

  • Updated CHANGELOG.md to document all performance improvements, refactors, and new test infrastructure.

Dart: main vs PR head

Variant 2000 5000 12000
Dart main (9385664) 1.213 7.388 38.704
Dart PR head (97ea7ca) 0.204 0.507 1.410
Speedup (main / PR) 5.95x 14.57x 27.45x

PR head vs other ports

Port 2000 5000 12000
Dart 0.204 0.507 1.410
QsSwift (Swift) 0.411 1.002 2.397
Kotlin 0.559 0.820 1.938
.NET/C# 0.754 2.032 4.314
Python 6.694 17.269 40.802

@techouse techouse self-assigned this Feb 27, 2026
@techouse techouse added the enhancement New feature or request label Feb 27, 2026
@techouse techouse requested a review from Copilot February 27, 2026 07:05
@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

Walkthrough

Refactors the QS encoder into a frame-and-phase-driven DFS, introduces EncodePhase, rewrites encode traversal and key-materialisation, consolidates payload assembly with a StringBuffer, adds performance tooling, and expands unit tests for deep chains and encoding edge-cases.

Changes

Cohort / File(s) Summary
Encode Phase & Frame
lib/src/enums/encode_phase.dart, lib/src/models/encode_frame.dart
Adds EncodePhase enum and converts EncodeFrame to phase-based traversal (start/iterate/awaitChild); removes callback lifecycle and adds iteration state, seq caching, adjustedPath and accumulated child values.
Encoding Engine
lib/src/extensions/encode.dart
Replaces recursive encoder with explicit DFS stack and per-frame phases; adds linear-chain fast-path, MRU segment caches, sequence sentinel, refined null/primitive handling, child-awaiting/result propagation and broadened _encode return semantics.
Path Materialisation
lib/src/models/key_path_node.dart
Reworks materialise to assemble full path once and pre-cache suffix partials up to a limit; adds per-node prefixLength to avoid O(depth^2) rebuilds.
Payload Assembly
lib/src/qs.dart
Switches from collecting fragments in a list to using a single StringBuffer with addFragment helper; consolidates emission logic and removes intermediate join.
Tests & Tools
test/unit/..., tool/perf_snapshot.dart
Adds many unit tests covering deep single-key chains, ByteBuffer/charset cases, sorting, cycle detection, allowDots/comma modes and unstable charset validation; adds perf benchmark tool for deep payloads.
Changelog
CHANGELOG.md
Prepends 1.7.2-wip section documenting performance and encode-path chores.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant EncoderStack as "EncodeFrame Stack"
    participant PhaseMachine as "Phase Machine"
    participant PathBuilder as "Path Builder"

    Client->>EncoderStack: push(root value, phase: start)
    loop process frames
        EncoderStack->>PhaseMachine: inspect current phase
        alt start
            PhaseMachine->>PathBuilder: prepare objKeys / seqList
            PathBuilder-->>EncoderStack: objKeys / seqList
            PhaseMachine-->>EncoderStack: transition -> iterate
        else iterate
            PhaseMachine->>EncoderStack: if has next child
            EncoderStack->>EncoderStack: push(child frame, phase: start)
            EncoderStack-->>EncoderStack: child returns fragment(s)
            EncoderStack->>EncoderStack: collect child fragments into values[]
            PhaseMachine-->>EncoderStack: index++
        else exhausted
            PhaseMachine->>PathBuilder: materialise path & assemble fragment
            PathBuilder-->>EncoderStack: fragment appended to payload
            EncoderStack-->>Client: propagate fragment or final payload
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

I hop through frames with tiny paws,
Phases ticking like careful clocks,
Keys and paths I build and store,
Deep chains, benchmarks — I adore,
Encoding carrots in blossom-box 🐇✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The PR description is comprehensive and well-structured, covering performance optimizations, refactoring details, internal types, test infrastructure, and benchmarks, but does not follow the template structure. Restructure the description to match the template: include explicit 'Fixes #(issue)', select Type of change with checkboxes, detail testing methodology under 'How Has This Been Tested?', and complete the Checklist section.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title ':zap: more performance optimizations' accurately describes the core objective of this PR, which focuses on performance improvements through refactoring and optimization.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/optimizations-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 and usage tips.

@codacy-production
Copy link

codacy-production bot commented Feb 27, 2026

Coverage summary from Codacy

See diff coverage on Codacy

Coverage variation Diff coverage
+0.48% (target: -1.00%) 98.11%
Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (9385664) 1248 1217 97.52%
Head commit (f7a0c48) 1300 (+52) 1274 (+57) 98.00% (+0.48%)

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 (#50) 212 208 98.11%

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 Feb 27, 2026

Codecov Report

❌ Patch coverage is 98.25328% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.03%. Comparing base (9385664) to head (97ea7ca).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
lib/src/extensions/encode.dart 98.00% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #50      +/-   ##
==========================================
+ Coverage   97.51%   98.03%   +0.51%     
==========================================
  Files          18       18              
  Lines        1247     1322      +75     
==========================================
+ Hits         1216     1296      +80     
+ Misses         31       26       -5     

☔ 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.

🧹 Nitpick comments (6)
CHANGELOG.md (1)

3-7: Consider adding issue/PR links per bullet for traceability.

The entries are clear, but linking each bullet to its issue/PR would make regression tracking easier across releases.

Based on learnings: PRs should link issues, state behavior changes, and mention any parity deviations from Node qs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` around lines 3 - 7, Update the CHANGELOG.md bullets to include
explicit issue or PR links and brief traceability notes: for each bullet (e.g.,
the encode fast-path/guarded single-key linear map, the encoder traversal
refactor, KeyPathNode.materialize(), segment-string reuse/top-level fragment
assembly, and tool/perf_snapshot.dart) append the corresponding issue/PR link
and, where relevant, a one-line note about any behavior changes or parity
deviations from Node qs; ensure each entry cites the PR/issue number (or “N/A”
if none) and a short parenthetical like “(PR `#1234`)” or “(issue `#567`)” so future
readers can trace regressions.
lib/src/enums/encode_phase.dart (1)

4-7: Rename enum members to PascalCase to match repository rules.

start, iterate, and awaitChild should follow the project’s enum-member naming convention.

As per coding guidelines: Enum members should use PascalCase naming convention.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/src/enums/encode_phase.dart` around lines 4 - 7, Rename the enum members
in EncodePhase from start, iterate, awaitChild to PascalCase (Start, Iterate,
AwaitChild) to comply with the project convention; update all usages of
EncodePhase.start, EncodePhase.iterate, and EncodePhase.awaitChild throughout
the codebase to the new names (EncodePhase.Start, EncodePhase.Iterate,
EncodePhase.AwaitChild) to avoid compilation errors.
tool/perf_snapshot.dart (1)

28-30: Avoid direct console output in non encode/decode paths.

This benchmarking tool currently performs direct print side-effects; please align it with the project rule (for example, return structured results from pure helpers and keep I/O handling isolated/explicit).

As per coding guidelines: Avoid printing and side-effects outside encode/decode operations.

Also applies to: 55-59

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tool/perf_snapshot.dart` around lines 28 - 30, The two direct print calls in
perf_snapshot.dart (the 'qs.dart perf snapshot...' and 'Encode (encode=false,
deep nesting):' lines) introduce IO side-effects; refactor by extracting the
benchmark logic into a pure helper (e.g., collectPerfSnapshot or
generatePerfMetrics) that returns a structured result object or Map with fields
for title, test name, and median timings, and move all console output to the
top-level runner (e.g., main) which reads that result and prints. Also remove or
refactor any other prints at lines 55-59 to follow the same pattern so all
printing is isolated to the explicit IO path.
lib/src/extensions/encode.dart (3)

458-463: Exception safety: tracked objects may leak on unexpected errors.

If an exception other than the intentional RangeError (e.g., from config.formatter or config.serializeDate) is thrown within the loop, cleanupTracked() is not invoked. While this likely has minimal impact since sideChannel is scoped per-encode call, consider wrapping the loop in a try-finally for robustness.

🛡️ Proposed try-finally wrapper
     final List<Object> tracked = [];

-    void cleanupTracked() {
-      for (int i = tracked.length - 1; i >= 0; i--) {
-        sideChannel.remove(tracked[i]);
-      }
-      tracked.clear();
-    }
-
-    while (true) {
+    try {
+      while (true) {
         // ... existing loop body ...
+      }
+    } finally {
+      for (int i = tracked.length - 1; i >= 0; i--) {
+        sideChannel.remove(tracked[i]);
+      }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/src/extensions/encode.dart` around lines 458 - 463, The removal loop that
iterates tracked and calls sideChannel.remove may throw from user formatters, so
wrap the loop in a try { ... } finally { tracked.clear(); } (or call the
existing cleanupTracked() in the finally) to guarantee tracked is cleared and
sideChannel entries are removed on any exception; update the cleanup area around
the tracked/sideChannel removal in encode.dart (referencing cleanupTracked(),
tracked, and sideChannel) so removal and tracked.clear() always run in finally.

346-354: Consider narrowing the catch clause or adding a debug assertion.

The catch-all catch (_) silently swallows all exceptions when accessing a dynamic indexer, which could mask unexpected errors (e.g., OutOfMemoryError, StackOverflowError). While the intent to handle missing indexers gracefully is clear, consider catching only NoSuchMethodError or logging in debug mode to aid troubleshooting.

🔧 Proposed narrower catch
               } else {
                 // Best-effort dynamic indexer for user-defined classes that expose `operator []`.
                 // If it throws (no indexer / wrong type), we fall through to the catch and mark undefined.
                 value = (frame.object as dynamic)[key];
                 valueUndefined = false;
               }
-            } catch (_) {
+            } on NoSuchMethodError catch (_) {
               value = null;
               valueUndefined = true;
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/src/extensions/encode.dart` around lines 346 - 354, The current catch-all
in the dynamic indexer block (around the use of (frame.object as dynamic)[key])
swallows all exceptions; change it to only catch expected errors (e.g.,
NoSuchMethodError and TypeError) and treat others as rethrows, and add a
debug-only assertion or logging to surface unexpected exceptions during
development; update the try/catch around the operator [] access (the block that
sets value and valueUndefined) to catch specific exceptions and rethrow or
assert on anything else so OutOfMemoryError/StackOverflowError aren’t silently
ignored.

409-419: Minor optimisation opportunity for list concatenation.

When encoded is a List<String>, using addAll with a filtered view would be slightly more efficient than iterating element-by-element. However, this is a micro-optimisation and the current code is clear.

♻️ Optional: use addAll for list results
         case EncodePhase.awaitChild:
           final dynamic encoded = lastResult;
-          if (encoded is Iterable) {
-            for (final dynamic e in encoded) {
-              if (e != null) {
-                frame.values.add(e as String);
-              }
-            }
+          if (encoded is List<String>) {
+            frame.values.addAll(encoded.where((e) => e != null));
+          } else if (encoded is Iterable) {
+            frame.values.addAll(encoded.whereType<String>());
           } else if (encoded != null) {
             frame.values.add(encoded as String);
           }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/src/extensions/encode.dart` around lines 409 - 419, The loop in the
EncodePhase.awaitChild branch can be replaced with a single addAll call to avoid
element-by-element iteration: when lastResult (local variable encoded) is an
Iterable, cast it to Iterable and call frame.values.addAll((encoded as
Iterable).whereType<String>()) so only non-null Strings are added; keep the
existing else branch for a single non-null encoded value. Reference symbols:
EncodePhase.awaitChild, lastResult/encoded, and frame.values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@CHANGELOG.md`:
- Around line 3-7: Update the CHANGELOG.md bullets to include explicit issue or
PR links and brief traceability notes: for each bullet (e.g., the encode
fast-path/guarded single-key linear map, the encoder traversal refactor,
KeyPathNode.materialize(), segment-string reuse/top-level fragment assembly, and
tool/perf_snapshot.dart) append the corresponding issue/PR link and, where
relevant, a one-line note about any behavior changes or parity deviations from
Node qs; ensure each entry cites the PR/issue number (or “N/A” if none) and a
short parenthetical like “(PR `#1234`)” or “(issue `#567`)” so future readers can
trace regressions.

In `@lib/src/enums/encode_phase.dart`:
- Around line 4-7: Rename the enum members in EncodePhase from start, iterate,
awaitChild to PascalCase (Start, Iterate, AwaitChild) to comply with the project
convention; update all usages of EncodePhase.start, EncodePhase.iterate, and
EncodePhase.awaitChild throughout the codebase to the new names
(EncodePhase.Start, EncodePhase.Iterate, EncodePhase.AwaitChild) to avoid
compilation errors.

In `@lib/src/extensions/encode.dart`:
- Around line 458-463: The removal loop that iterates tracked and calls
sideChannel.remove may throw from user formatters, so wrap the loop in a try {
... } finally { tracked.clear(); } (or call the existing cleanupTracked() in the
finally) to guarantee tracked is cleared and sideChannel entries are removed on
any exception; update the cleanup area around the tracked/sideChannel removal in
encode.dart (referencing cleanupTracked(), tracked, and sideChannel) so removal
and tracked.clear() always run in finally.
- Around line 346-354: The current catch-all in the dynamic indexer block
(around the use of (frame.object as dynamic)[key]) swallows all exceptions;
change it to only catch expected errors (e.g., NoSuchMethodError and TypeError)
and treat others as rethrows, and add a debug-only assertion or logging to
surface unexpected exceptions during development; update the try/catch around
the operator [] access (the block that sets value and valueUndefined) to catch
specific exceptions and rethrow or assert on anything else so
OutOfMemoryError/StackOverflowError aren’t silently ignored.
- Around line 409-419: The loop in the EncodePhase.awaitChild branch can be
replaced with a single addAll call to avoid element-by-element iteration: when
lastResult (local variable encoded) is an Iterable, cast it to Iterable and call
frame.values.addAll((encoded as Iterable).whereType<String>()) so only non-null
Strings are added; keep the existing else branch for a single non-null encoded
value. Reference symbols: EncodePhase.awaitChild, lastResult/encoded, and
frame.values.

In `@tool/perf_snapshot.dart`:
- Around line 28-30: The two direct print calls in perf_snapshot.dart (the
'qs.dart perf snapshot...' and 'Encode (encode=false, deep nesting):' lines)
introduce IO side-effects; refactor by extracting the benchmark logic into a
pure helper (e.g., collectPerfSnapshot or generatePerfMetrics) that returns a
structured result object or Map with fields for title, test name, and median
timings, and move all console output to the top-level runner (e.g., main) which
reads that result and prints. Also remove or refactor any other prints at lines
55-59 to follow the same pattern so all printing is isolated to the explicit IO
path.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9385664 and 3b01ee4.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • lib/src/enums/encode_phase.dart
  • lib/src/extensions/encode.dart
  • lib/src/models/encode_frame.dart
  • lib/src/models/key_path_node.dart
  • lib/src/qs.dart
  • test/unit/encode_test.dart
  • test/unit/models/key_path_node_test.dart
  • tool/perf_snapshot.dart

Copy link

Copilot AI left a comment

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 refactors the core query-string encoder traversal to reduce allocations and improve deep-structure encode performance, while adding targeted regression tests and a repeatable perf snapshot tool.

Changes:

  • Reworked iterative encoder traversal to a phase-driven EncodeFrame/EncodePhase model and introduced a guarded deep single-key map-chain fast path.
  • Optimized top-level fragment assembly in QS.encode to reduce intermediate list allocations.
  • Improved KeyPathNode.materialize() deep-chain behavior and added deep-chain regression/perf infrastructure (unit tests + perf snapshot script).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
lib/src/extensions/encode.dart Major traversal refactor (phase-driven stack), MRU segment reuse, and _tryEncodeLinearChain fast path.
lib/src/models/encode_frame.dart Updates frame state to support phase-driven traversal (removes callback, adds phase + iteration state).
lib/src/enums/encode_phase.dart Adds internal phase enum used by the new traversal model.
lib/src/qs.dart Switches fragment assembly to StringBuffer with incremental delimiter writes.
lib/src/models/key_path_node.dart Reworks materialize() to build the path string in one pass for deep chains.
test/unit/encode_test.dart Adds deep-chain and fast-path fallback/regression tests for encode=false and option interactions.
test/unit/models/key_path_node_test.dart Adds very-deep materialize() regression coverage.
tool/perf_snapshot.dart Adds a local script for repeatable deep-encode performance snapshots.
CHANGELOG.md Documents the performance refactor and new tooling/tests for the next version.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

@techouse
Copy link
Owner Author

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f7a0c48fcd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@techouse
Copy link
Owner Author

@codex review

@techouse techouse requested a review from Copilot February 27, 2026 09:00
@techouse
Copy link
Owner Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

✅ 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.

@codacy-production
Copy link

codacy-production bot commented Feb 27, 2026

Coverage summary from Codacy

See diff coverage on Codacy

Coverage variation Diff coverage
+0.52% (target: -1.00%) 98.25%
Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (9385664) 1248 1217 97.52%
Head commit (97ea7ca) 1322 (+74) 1296 (+79) 98.03% (+0.52%)

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 (#50) 229 225 98.25%

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

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. Another round soon, please!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

@techouse techouse requested a review from Copilot February 27, 2026 18:26
@techouse
Copy link
Owner Author

@coderabbitai resume

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/src/extensions/encode.dart`:
- Around line 118-120: The EncodePhase enum members are using lowerCamelCase;
rename the enum members to PascalCase (e.g., start -> Start, iterate -> Iterate,
awaitChild -> AwaitChild) in the enum declaration and update every usage: change
references like EncodePhase.start, EncodePhase.iterate, EncodePhase.awaitChild,
and switch arms in functions/methods that pattern-match on EncodePhase (such as
the switch handling frame.phase and any other comparisons or assignments found
later in this file) to their PascalCase equivalents to satisfy the repository
naming rules.
- Around line 157-163: The code currently emits raw materialized keys (via
materializedPath()) in the strict-null/empty-list branches which bypasses the
normal key encoding/formatting pipeline; update the branches that call
finishFrame(frame, keyOnly) so they first run the key through the same
encoder/formatting logic used elsewhere (respecting config.encoder and
config.encodeValuesOnly) instead of using materializedPath()
directly—specifically adjust the logic around materializedPath(), the
construction of keyOnly, and the calls to finishFrame() so the produced key is
encoder!(...) or formatted exactly like other paths; apply the same change to
the analogous empty-list branch that currently emits materializedPath() at the
other location.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f7a0c48 and 733646a.

📒 Files selected for processing (2)
  • lib/src/extensions/encode.dart
  • lib/src/models/key_path_node.dart

@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

✅ Actions performed

Reviews resumed.

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.

♻️ Duplicate comments (2)
lib/src/extensions/encode.dart (2)

118-120: ⚠️ Potential issue | 🟡 Minor

Enum case names should follow PascalCase.

EncodePhase.start, EncodePhase.iterate, and EncodePhase.awaitChild violate the repo enum naming rule and should be updated consistently with the enum declaration and all references.

#!/bin/bash
# Verify enum declaration and all lowerCamelCase member usages.
rg -nP --type=dart 'enum\s+EncodePhase\b' -C6
rg -nP --type=dart '\bEncodePhase\.(start|iterate|awaitChild)\b' -C2

As per coding guidelines: Enum members should use PascalCase naming convention.

Also applies to: 310-312, 412-425

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/src/extensions/encode.dart` around lines 118 - 120, The enum member
usages EncodePhase.start, EncodePhase.iterate, and EncodePhase.awaitChild (and
their other occurrences around lines noted) violate the PascalCase rule; update
the enum declaration to use PascalCase names (e.g., Start, Iterate, AwaitChild)
and then rename all references to those members (places like the switch on
frame.phase and any equality checks or case labels) to the new PascalCase
identifiers so the declaration and all usages remain consistent (search for
EncodePhase.start/iterate/awaitChild and replace with
EncodePhase.Start/Iterate/AwaitChild).

157-163: ⚠️ Potential issue | 🟠 Major

Key-only emissions still bypass the standard key formatting pipeline.

At Line 159 and Line 297, emitted key-only fragments skip config.formatter(...), which can produce inconsistent output versus normal key handling (for example formatter-driven space handling).

Suggested fix
@@
-          String? pathText;
-          String materializedPath() => pathText ??= frame.path.materialize();
+          String? pathText;
+          String materializedPath() => pathText ??= frame.path.materialize();
+          String formatKeyOnly(String rawPath) {
+            final String key = config.encoder != null && !config.encodeValuesOnly
+                ? config.encoder!(rawPath)
+                : rawPath;
+            return config.formatter(key);
+          }
@@
-              final String keyOnly =
-                  config.encoder != null && !config.encodeValuesOnly
-                      ? config.encoder!(materializedPath())
-                      : materializedPath();
-              finishFrame(frame, keyOnly);
+              finishFrame(frame, formatKeyOnly(materializedPath()));
               continue;
@@
-          if (config.allowEmptyLists && seqList != null && seqList.isEmpty) {
-            finishFrame(frame, adjustedPath.append('[]').materialize());
+          if (config.allowEmptyLists && seqList != null && seqList.isEmpty) {
+            finishFrame(
+              frame,
+              formatKeyOnly(adjustedPath.append('[]').materialize()),
+            );
             continue;
           }

Also applies to: 296-297

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/src/extensions/encode.dart` around lines 157 - 163, The key-only emission
bypasses the standard formatter: when building the key-only string (using
materializedPath() and optional config.encoder) before calling finishFrame (in
the strictNullHandling branch and the analogous branch later), pass the computed
key through config.formatter(...) (respecting encodeValuesOnly and encoder
logic) so the same formatting pipeline is applied; update the key-only
construction used in finishFrame calls to use config.formatter(keyOnly) when
config.formatter is set.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@lib/src/extensions/encode.dart`:
- Around line 118-120: The enum member usages EncodePhase.start,
EncodePhase.iterate, and EncodePhase.awaitChild (and their other occurrences
around lines noted) violate the PascalCase rule; update the enum declaration to
use PascalCase names (e.g., Start, Iterate, AwaitChild) and then rename all
references to those members (places like the switch on frame.phase and any
equality checks or case labels) to the new PascalCase identifiers so the
declaration and all usages remain consistent (search for
EncodePhase.start/iterate/awaitChild and replace with
EncodePhase.Start/Iterate/AwaitChild).
- Around line 157-163: The key-only emission bypasses the standard formatter:
when building the key-only string (using materializedPath() and optional
config.encoder) before calling finishFrame (in the strictNullHandling branch and
the analogous branch later), pass the computed key through config.formatter(...)
(respecting encodeValuesOnly and encoder logic) so the same formatting pipeline
is applied; update the key-only construction used in finishFrame calls to use
config.formatter(keyOnly) when config.formatter is set.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f7a0c48 and 733646a.

📒 Files selected for processing (2)
  • lib/src/extensions/encode.dart
  • lib/src/models/key_path_node.dart

@techouse techouse force-pushed the chore/optimizations-2 branch from b371481 to 97ea7ca Compare February 27, 2026 18:48
@techouse techouse merged commit f028e1c into main Feb 27, 2026
14 checks passed
@techouse techouse deleted the chore/optimizations-2 branch February 27, 2026 19:01
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