|
| 1 | +## qs_dart – AI Coding Agent Guide |
| 2 | + |
| 3 | +Purpose: Provide just enough project context so an AI assistant can make correct, idiomatic edits fast. Keep responses concrete, reflect existing patterns, and avoid generic advice. |
| 4 | + |
| 5 | +### 1. Architecture & Core Concepts |
| 6 | +* Library type: Pure Dart package (query string codec) ported from Node.js `qs` with near‑parity semantics. |
| 7 | +* Public surface (`lib/qs_dart.dart`) re‑exports: |
| 8 | + - Top‑level `decode` / `encode` (wrappers around `QS.decode` / `QS.encode`). |
| 9 | + - Option models: `DecodeOptions`, `EncodeOptions`, enums (`Duplicates`, `ListFormat`, `Format`, `Sentinel`, `DecodeKind`). |
| 10 | + - `Undefined` sentinel (marks intentionally omitted values during encoding/merging). |
| 11 | + - `Uri` extensions (`src/uri.dart`) for ergonomic integration. |
| 12 | +* Implementation split: |
| 13 | + - `src/qs.dart` orchestrates encode/decode; heavy lifting delegated to private `part` files `extensions/decode.dart` & `extensions/encode.dart` and utility helpers in `utils.dart`. |
| 14 | + - `utils.dart` centralizes low‑level merging (`Utils.merge`), compaction (`Utils.compact`), scalar detection, percent encoding, HTML entity handling. |
| 15 | + - Option classes encode guard‑rails: depth, parameterLimit, listLimit, charsetSentinel, etc. |
| 16 | +* Design principle: mirror Node `qs` behavior while remaining Dart‑idiomatic (ordered `Map<String, dynamic>`, explicit options objects, strong enums instead of magic strings). |
| 17 | + |
| 18 | +### 2. Key Behavioral Nuances (Don’t Break These) |
| 19 | +* Decode limits: default `depth=5`, `parameterLimit=1000`, `listLimit=20`; exceeding may coerce indices into object keys or (with strict flags) throw. |
| 20 | +* List vs Map merging mimics Node: duplicate keys accumulate to lists unless `duplicates` option changes strategy. |
| 21 | +* `Undefined` entries are placeholders stripped by `Utils.compact` post‑decode / during encode pruning; never serialize `Undefined` itself. |
| 22 | +* Charset sentinel: when `charsetSentinel=true`, `utf8=✓` token (encoded differently per charset) overrides provided `charset` and is omitted from output. |
| 23 | +* `allowDots` & `decodeDotInKeys`: invalid combination (`decodeDotInKeys: true` with `allowDots: false`) must throw (constructor asserts). Preserve that invariant. |
| 24 | +* Negative `listLimit` disables numeric indexing; with `throwOnLimitExceeded` certain pushes must throw `RangeError` (match existing patterns in decode logic—consult decode part file before altering behavior). |
| 25 | +* Encoding pipeline can inject custom encoder/decoder hooks; preserve argument order and named params (`charset`, `format`, `kind`). |
| 26 | + |
| 27 | +### 3. Source Conventions |
| 28 | +* Public APIs: lowerCamelCase; files: snake_case; enums: PascalCase members. |
| 29 | +* Avoid printing (`avoid_print` lint); no side‑effects outside encode/decode. |
| 30 | +* Keep option constructors const & validating invariants via `assert`. |
| 31 | +* Prefer extension methods & small helpers over large monolithic functions. |
| 32 | +* Maintain iteration order: when converting iterables to maps use string indices (`Utils.createIndexMap`); merging may temporarily use `SplayTreeMap` for deterministic ordering. |
| 33 | + |
| 34 | +### 4. Testing Strategy |
| 35 | +* Unit tests in `test/unit/` mirror README examples & edge cases (list formats, depth, duplicates, charsets, null handling, custom hooks). |
| 36 | +* E2E tests in `test/e2e/` exercise higher‑level URI extensions. |
| 37 | +* Comparison tests (`test/comparison/`) ensure parity with the JS implementation via fixture JSON & a Node script; run `test/comparison/compare_outputs.sh` after semantic changes to core encode/decode logic. |
| 38 | +* When adding behavior, first add/modify a unit test replicating the JS `qs` behavior (consult upstream if uncertain), then adjust implementation. |
| 39 | + |
| 40 | +### 5. Dev Workflow / Commands |
| 41 | +* Install deps: `make install` (wraps `dart pub get`). |
| 42 | +* Quality gate before commits/PR: `make sure` (analyze + format check + tests). Always keep CI green. |
| 43 | +* Run tests: `make test` (VM platform). Add coverage: `make show_test_coverage` → opens HTML at `coverage/html/index.html`. |
| 44 | +* Formatting: `make format` (or `dart format .`); never commit unformatted code. |
| 45 | +* Dependency upgrades: `make upgrade`; check outdated: `make check_outdated`. |
| 46 | + |
| 47 | +### 6. Adding / Modifying Features (Checklist) |
| 48 | +1. Write or update a focused test under `test/unit/` (mirroring file/module naming). |
| 49 | +2. If changing cross‑language semantics, update comparison fixtures & run the comparison script. |
| 50 | +3. Implement minimal changes: prefer editing encode/decode parts or localized helpers in `utils.dart` rather than inflating public API. |
| 51 | +4. Update README only if user‑visible behavior changes (options, defaults, new enum values). Keep examples minimal and consistent with test expectations. |
| 52 | +5. Run `make sure`; fix lint warnings as errors. |
| 53 | + |
| 54 | +### 7. Common Pitfalls for Agents |
| 55 | +* DO NOT expose new symbols via `qs_dart.dart` unless intentionally part of the public contract. |
| 56 | +* Avoid broad refactors of `utils.dart`; many subtle invariants (Undefined pruning, ordering, surrogate pair handling) are covered by tests—modify surgically. |
| 57 | +* Keep percent‑encoding logic streaming & segment‑aware (`_segmentLimit=1024`) to preserve performance. |
| 58 | +* When adding an option: ensure const constructor, add to `copyWith` (if present in file—verify before editing), equality via `EquatableMixin`, and README table/example. |
| 59 | +* Ensure new enums get exported in `qs_dart.dart` only if needed by library consumers (options signatures, return types). |
| 60 | + |
| 61 | +### 8. Performance Considerations |
| 62 | +* Encoding avoids splitting surrogate pairs when chunking strings; preserve that loop structure. |
| 63 | +* Merge uses structural checks to decide list append vs map merge; naive rebuilding can degrade performance & ordering. |
| 64 | +* Avoid recursive deep traversal for compaction—current iterative approach prevents stack overflows on adversarial inputs. |
| 65 | + |
| 66 | +### 9. Error Handling Expectations |
| 67 | +* Input type validation: non‑String/non‑Map to `QS.decode` must throw `ArgumentError.value` (message currently: 'The input must be a String or a Map<String, dynamic>'). Keep wording for stable tests. |
| 68 | +* Depth/list/parameter overflows toggle between soft limiting or throwing depending on `strictDepth` / `throwOnLimitExceeded` flags; never silently ignore an explicit strict flag. |
| 69 | + |
| 70 | +### 10. Documentation & Examples |
| 71 | +* README examples double as test patterns—when you change semantics, sync both. |
| 72 | +* Keep code comments concise; prefer section banners only where logic is subtle (merging, encoding loops). |
| 73 | + |
| 74 | +### 11. Commit / PR Style |
| 75 | +* Commit subject prefix emoji (e.g., `:sparkles: Add X`, `:bug:` etc.) aligned with existing history. |
| 76 | +* PRs: link issues, state behavior changes, mention any parity deviations from Node `qs`. |
| 77 | + |
| 78 | +### 12. Safe Extension Ideas (Low Risk) |
| 79 | +* Additional list formats or duplicate strategies: introduce enum value + guarded branch + tests. |
| 80 | +* Extra utility for selective key removal during encode (implemented via `filter` lambda) — prefer documenting patterns rather than expanding API unless necessary. |
| 81 | + |
| 82 | +If anything above is unclear or you need deeper decode/encode part internals, request those specific files before editing them. |
| 83 | + |
| 84 | +--- |
| 85 | +Feedback welcome: highlight unclear invariants or missing edge cases so we can refine this guide. |
0 commit comments