Skip to content

perf(lockfile): byte-cursor subset parser for pnpm-lock.yaml - #912

Merged
jdx merged 14 commits into
aubepkg:mainfrom
jdalton:perf/pnpm-lock-subset-parser
Jun 24, 2026
Merged

jdx merged 14 commits into
aubepkg:mainfrom
jdalton:perf/pnpm-lock-subset-parser

Conversation

@jdalton

@jdalton jdalton commented Jun 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds an additive, byte-cursor subset parser for pnpm-lock.yaml as a fast path in front of the existing yaml_serde parser.

pnpm emits a tightly constrained dialect of YAML: 2-space block indentation, no anchors/aliases, no multi-line scalars, and flow style only for small inline maps (resolution: {...}, engines: {...}) and seqs (os: [linux, darwin]). That regularity lets a purpose-built parser walk the bytes directly — using memchr for line boundaries and the structural : separator — instead of running a general YAML event stream through serde. The bulk of the work in a large lockfile is thousands of trivial key: value snapshot/dependency lines, which is exactly what the byte cursor handles cheaply.

Behavior preservation

The new parser is a strict subset parser. It produces the same RawPnpmLockfile the serde path produces, and the instant it meets anything outside the recognized shape — a multi-document stream (pnpm v11 bootstrap + project doc), an unmodeled top-level key, an unexpected indent, a quoting style it can't normalize — it returns None and parse_raw_lockfile transparently falls back to the existing yaml_serde parser.

Fiddly inline flow values (resolution / variants / string_or_seq) and full package bodies are handed to yaml_serde for a small reconstructed fragment, so those shapes stay in lockstep with serde rather than being re-implemented. Net result: the fast path only ever fires when it can produce a byte-identical result; everything else degrades to the original parser.

Benchmark

Added crates/aube-lockfile/benches/pnpm_lock_parse.rs (criterion) comparing both paths. Set AUBE_BENCH_LOCKFILE=/path/to/pnpm-lock.yaml to point it at a real lockfile.

On a generated 1.1 MB / 4000-package lockfile (200 deps + 200 devDeps in the importer, 4000 packages: entries, 4000 snapshots:):

path time
yaml_serde (existing) ~45.3 ms
subset parser (new) ~28.0 ms

The bench asserts both paths agree on the parsed structure before measuring.

Tests

Existing aube-lockfile suite stays green: 345 unit tests + 5 proptests, including the pnpm roundtrip proptest (which now also exercises the subset parser on randomly generated lockfiles). Added four targeted unit tests for the subset module (fast-path fires on the native fixture; declines multi-doc streams; declines unknown top-level keys; structural-colon splitting respects quoting).

Notes

  • memchr and rustc-hash added as deps of aube-lockfile; criterion added as a dev-dep with the [[bench]] entry.

  • Draft: opening for design feedback on whether the subset-parser-with-fallback approach is a direction you'd want upstream, and on the bench numbers above. Happy to adjust the subset coverage (e.g. hand-parse package bodies too) if the tradeoff is interesting.

  • The byte-cursor + decline-on-doubt shape (a raw &[u8] cursor that scans for structural bytes and declines to a full parser the moment input leaves the recognized subset) was inspired by jzon-rs: https://github.com/Rajaniraiyn/jzon-rs. Its specific SWAR/SIMD structural-scan kernels are not used here — this is a plain byte-cursor line scanner that finds line boundaries with memchr and matches the single structural : with a short scalar pass; the constrained pnpm dialect doesn't justify vectorized kernels.

Summary by CodeRabbit

  • Performance
    • Significantly improved pnpm-lock.yaml parsing with a new fast-path for common pnpm/YAML patterns and automatic fallback to the existing parser when unsupported.
    • Added consistency/count checks between the fast-path and the full parser.
  • Tests
    • Expanded fixture coverage (including additional pnpm versions, workspaces, catalogs, git resolution, and kitchen-sink scenarios) plus targeted adversarial edge cases.
  • Chores
    • Added feature-gated Criterion benchmarks to compare parsing approaches, with input sourced from an environment variable (with a bundled fallback).

@coderabbitai

coderabbitai Bot commented Jun 18, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

A new byte-cursor "subset" YAML parser (subset.rs) is added for pnpm-lock.yaml, returning None for unsupported constructs to trigger serde fallback. parse_raw_lockfile in raw.rs is updated to attempt this fast path first. Criterion benchmarks are added comparing both parsing paths, along with comprehensive fixtures and differential testing utilities.

Changes

pnpm Lockfile Subset Parser and Benchmarks

Layer / File(s) Summary
Benchmark dependencies and Cargo configuration
crates/aube-lockfile/Cargo.toml
Adds memchr, rustc-hash runtime dependencies and criterion dev-dependency with default-features = false; declares pnpm_lock_parse bench target with harness = false.
Subset parser entry point and line-cursor infrastructure
crates/aube-lockfile/src/pnpm/subset.rs
Module documentation, try_parse entry point with multi-document rejection, Parser struct, and logical line-iteration helpers (peek_line, consume_line, indentation counting) that skip blanks and full-line comments while handling CRLF line endings.
Main parsing loop and top-level key dispatch
crates/aube-lockfile/src/pnpm/subset.rs
Core Parser::parse control flow enforcing indent-0 top-level structure, routing all recognized top-level keys (lockfileVersion, settings, overrides, catalogs, patchedDependencies, importers, packages, snapshots, time) to subtree parsers, and returning None on unknown keys or unexpected indentation.
Subtree parsing utilities and helper functions
crates/aube-lockfile/src/pnpm/subset.rs
Utility functions for safe fallback: parse_via_serde and take_block capture raw nested YAML blocks, dedent them, and deserialize via yaml_serde; parse_string_map enforces block-only parsing; parse_seq_value handles inline and block sequences; parse_catalogs delegates to serde.
Importers, packages, and snapshots section parsers
crates/aube-lockfile/src/pnpm/subset.rs
parse_importers/parse_importer_body/parse_dep_specs handle inline empty {} with serde-equivalent defaults; parse_packages/parse_package_body enforce indentation and empty-block shapes; parse_snapshots/parse_snapshot_body validate fields including typed-boolean optional with rejection of quoted booleans.
Scalar decoding, key splitting, and structural helpers
crates/aube-lockfile/src/pnpm/subset.rs
Default constructors for empty representations, split_key with quoted-region skipping and whitespace trimming, scalar decoding with quote/escape rules and rejection of unsupported constructs, value parsing (parse_scalar_value, parse_bool, serde_value_from_fragment), and dedent validation.
Fast-path integration into raw.rs and pnpm/mod.rs
crates/aube-lockfile/src/pnpm/mod.rs, crates/aube-lockfile/src/pnpm/raw.rs
Declares subset module; wires subset::try_parse as early-return fast path in parse_raw_lockfile with serde fallback; splits off parse_raw_lockfile_serde as explicit fallback; adds __bench_counts, SubsetDiff enum, and diff_subset_vs_serde for differential testing; exposes __bench_parse_subset and __bench_parse_serde benchmark entry points; adds Default derive to RawPackageInfo.
Unit tests: differential, regression, and integration
crates/aube-lockfile/src/pnpm/subset.rs
Fixture differential tests, regression and adversarial tests validating fast-path boundaries (empty {} equivalence, inline-flow rejection, quoted/structural edge cases, CRLF/comment/blank skipping), proptest-based fuzz test generating pnpm maps, and optional real-corpus integration test with divergence detection.
Criterion benchmark implementation and test fixtures
crates/aube-lockfile/benches/pnpm_lock_parse.rs, crates/aube-lockfile/tests/fixtures/pnpm-*
Implements environment-driven fixture loading, logs input size, asserts cross-parser structural equality, and benchmarks both parsers in a Criterion group; includes nine comprehensive YAML fixtures covering pnpm v5.3/v5.4/v6 single/workspace layouts, v9 catalog-backed and exotic resolutions, plus kitchen-sink coverage of settings, overrides, patches, and peer dependencies.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A new fast path through YAML land,
I skip the colons, key by hand.
When quoting trips me, serde takes the leap —
my None means "fall back, don't lose sleep."
With Criterion timing every byte,
this rabbit parses pnpm right! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately summarizes the main change: adding a byte-cursor subset parser for pnpm-lock.yaml files as a performance optimization.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

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


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.

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/aube-lockfile/src/pnpm/subset.rs (1)

686-699: ⚡ Quick win

Add regression tests for overrides/time inline-map decline behavior.

Given the fallback contract, it’s worth adding explicit tests that try_parse returns None for overrides: {k: v} and time: {k: v} so this doesn’t regress silently.

🤖 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 `@crates/aube-lockfile/src/pnpm/subset.rs` around lines 686 - 699, Add two new
regression test functions following the pattern of the existing
declines_unknown_top_level_key test. Create a test that verifies try_parse
returns None when given a lockfile string containing overrides with an
inline-map format (e.g., "overrides: {k: v}"), and another test that verifies
try_parse returns None for time with an inline-map format (e.g., "time: {k:
v}"). These tests ensure the fallback contract is maintained and prevents silent
regressions where these inline-map formats could be incorrectly accepted or
silently dropped.
🤖 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 `@crates/aube-lockfile/src/pnpm/subset.rs`:
- Around line 155-157: The parse_string_map method is being called
unconditionally for both the overrides field (around line 155) and the time
field (around line 179) without checking whether the input is actually in
string-map form or in inline/null form. This causes inline maps like `overrides:
{foo: bar}` to be silently misparsed as empty maps. You need to add a check
before calling parse_string_map to detect if the value is in inline or null
form, and if so, skip the custom parsing to allow serde to handle it with its
default fallback behavior instead of forcing the parse_string_map call.

---

Nitpick comments:
In `@crates/aube-lockfile/src/pnpm/subset.rs`:
- Around line 686-699: Add two new regression test functions following the
pattern of the existing declines_unknown_top_level_key test. Create a test that
verifies try_parse returns None when given a lockfile string containing
overrides with an inline-map format (e.g., "overrides: {k: v}"), and another
test that verifies try_parse returns None for time with an inline-map format
(e.g., "time: {k: v}"). These tests ensure the fallback contract is maintained
and prevents silent regressions where these inline-map formats could be
incorrectly accepted or silently dropped.
🪄 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: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: ee3f9645-bbf7-4fe0-98a8-66d777898f65

📥 Commits

Reviewing files that changed from the base of the PR and between 0ca333b and 21958dc.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • crates/aube-lockfile/Cargo.toml
  • crates/aube-lockfile/benches/pnpm_lock_parse.rs
  • crates/aube-lockfile/src/pnpm/mod.rs
  • crates/aube-lockfile/src/pnpm/raw.rs
  • crates/aube-lockfile/src/pnpm/subset.rs

Comment thread crates/aube-lockfile/src/pnpm/subset.rs Resolved
@greptile-apps

greptile-apps Bot commented Jun 18, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds a byte-cursor subset parser for pnpm-lock.yaml as a fast path in front of the existing yaml_serde parser, achieving roughly a 38% parse-time reduction on large lockfiles by walking structural bytes directly with memchr instead of running a full YAML event stream through serde. The parser is strictly additive — it returns None for any input outside its recognized subset and the call-site transparently falls back to yaml_serde, so behavior is preserved by construction.

  • subset.rs implements the full line-cursor parser with split_key, scalar_string (including inline-comment stripping), parse_dep_specs, parse_importer_body, parse_snapshot_body, and delegation back to yaml_serde for fiddly sub-trees (resolution, catalogs, patchedDependencies).
  • All previously-flagged issues (empty-map guard in parse_dep_specs, RawPackageInfo::default() replacing a repeated serde call, inline-comment stripping, the unused header param, and the bench-feature gate for __bench_parse_*) are addressed in this revision.
  • Test coverage is extensive: cross-version fixtures, differential {:#?} equality against serde, proptest over the inline-vs-block boundary, and adversarial edge cases for quoting, CRLF, tabs, and block scalars.

Confidence Score: 5/5

Safe to merge. The fast path is purely additive — any input it doesn't recognize falls through to the unchanged serde path — and the differential {:#?} test harness, proptest, and cross-version fixture corpus collectively make a silent wrong-parse very hard to land without a test failure.

All previously-raised concerns have been resolved in this iteration: the empty-map guard in parse_dep_specs, inline-comment stripping in scalar_string, RawPackageInfo::default() replacing a repeated serde call, the unused header param, and the bench-feature gate for the __bench_parse_* shims. The fallback-on-doubt design means any missed edge case degrades gracefully to the existing parser rather than producing a wrong result silently. No new correctness issues were found.

No files require special attention.

Important Files Changed

Filename Overview
crates/aube-lockfile/src/pnpm/subset.rs New 1487-line byte-cursor subset parser for pnpm-lock.yaml; previously-flagged issues (inline-comment stripping, empty-map guard, default_package_info using Default, header param, bench-feature gate) are all addressed in this revision.
crates/aube-lockfile/src/pnpm/raw.rs Adds the fast-path hook in parse_raw_lockfile, bench helpers behind the bench feature gate, and the diff_subset_vs_serde differential harness used by tests; RawPackageInfo gains #[derive(Default)].
crates/aube-lockfile/src/pnpm/mod.rs Wires in the subset module and exposes __bench_parse_subset/__bench_parse_serde only under #[cfg(feature = "bench")], addressing the semver-surface concern from the previous review.
crates/aube-lockfile/benches/pnpm_lock_parse.rs Criterion benchmark comparing subset vs serde paths; correctness assertion still only checks count triples rather than full structural equality (previously flagged P2).
crates/aube-lockfile/Cargo.toml Adds memchr = "2" as a runtime dep, criterion as a dev-dep, and the bench feature flag with the [[bench]] entry gated on it.

Reviews (13): Last reviewed commit: "fix(lockfile): strip YAML inline comment..." | Re-trigger Greptile

Comment thread crates/aube-lockfile/src/pnpm/subset.rs Resolved
Comment thread crates/aube-lockfile/src/pnpm/subset.rs Resolved
Comment thread crates/aube-lockfile/src/pnpm/mod.rs Resolved
Comment thread crates/aube-lockfile/src/pnpm/subset.rs Resolved

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

Actionable comments posted: 2

🤖 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 `@crates/aube-lockfile/tests/fixtures/pnpm-kitchen-sink.yaml`:
- Around line 102-107: The bundled-pkg@1.0.0 package definition (lines 102-107)
is not referenced anywhere in the importers section or snapshot dependency
trees, making it an orphaned package entry. Either add a dependency reference to
bundled-pkg in one of the importer sections to make it part of the actual
dependency graph, or remove the entire bundled-pkg@1.0.0 package definition and
its snapshot entry if it is not needed for testing parser functionality. Ensure
that if this package is intentionally included for comprehensive parser testing,
add a comment explaining this in the fixture file.
- Around line 104-106: The bundledDependencies list in the package definition
and the snapshot section of the pnpm-kitchen-sink.yaml fixture file are
inconsistent. In the package definition around lines 104-106, the
bundledDependencies includes both '`@napi-rs/wasm-runtime`' and '`@emnapi/core`',
but the snapshot section around lines 136-137 only includes
'`@napi-rs/wasm-runtime`'. Update one of these lists to match the other so that
the bundledDependencies declaration is consistent between the package metadata
and its resolved snapshot state. Choose whichever list correctly represents the
intended fixture state and update the mismatched section accordingly.
🪄 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: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 612dd5ef-aae6-4c9e-a516-497bc41385e9

📥 Commits

Reviewing files that changed from the base of the PR and between 21958dc and 328e2b3.

📒 Files selected for processing (5)
  • crates/aube-lockfile/src/pnpm/raw.rs
  • crates/aube-lockfile/src/pnpm/subset.rs
  • crates/aube-lockfile/tests/fixtures/pnpm-git-resolution.yaml
  • crates/aube-lockfile/tests/fixtures/pnpm-kitchen-sink.yaml
  • crates/aube-lockfile/tests/fixtures/pnpm-v6.yaml
✅ Files skipped from review due to trivial changes (1)
  • crates/aube-lockfile/tests/fixtures/pnpm-v6.yaml

@jdalton

jdalton commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review feedback. Summary:

Finding 1 (CodeRabbit, silent wrong-parse) — fixed. Confirmed real via a differential test: overrides: {foo: bar} inline produced Some({}) while serde produced {foo: bar}, and overrides: (null body) produced Some({}) where serde reads None. Auditing all parse_string_map / serde-delegate call sites showed the same class affected time, settings, catalogs, patchedDependencies, importers, packages, snapshots (each ignored an inline {...} value), plus empty snapshot dependencies:/optionalDependencies: blocks. Fix: parse_string_map now takes the inline value and declines on an inline map or an empty result; the block-bodied top-level arms decline on any inline value. Added inline_flow_map_fields_decline_not_misparse and empty_block_map_declines_to_distinguish_null_from_empty covering every touched field, plus block_string_map_still_fires to keep the normal path firing.

Finding 2 (Greptile) — fixed. Removed the unused header: &Line parameter from parse_seq_value and updated all call sites.

Finding 3 (Greptile) — fixed. Broadened has_document_separator to also catch ---\r, ---\t, and ---#, and documented that it is a fast-path hint — the real guarantee is the structural-colon decline (split_key returns None → serde fallback).

Finding 4 (Greptile) — fixed. __bench_parse_subset / __bench_parse_serde (and the __bench_counts helper) are now behind a bench cargo feature, off by default, so they leave the crate's default public surface. The [[bench]] target sets required-features = ["bench"]; cargo bench -p aube-lockfile builds, the normal build no longer exposes them.

Finding 5 (Greptile) — fixed. default_package_info() no longer calls from_str("{}").expect(...) per call; RawPackageInfo now derives Default and the helper returns RawPackageInfo::default(). A test pins it byte-identical to serde's {} parse. The sibling default_importer/default_snapshot helpers already built their structs by hand — no panic path there.

Finding 6 (CodeRabbit) — fixed. Added a comment to the orphaned bundled-pkg@1.0.0 kitchen-sink entry noting it exists intentionally to cover the bundledDependencies: block seq.

Attribution. Credited jzon-rs (Maciej Hirsz's json-rust/jzon) for the byte-cursor design in the module header and in the PR description.

Validation: cargo test -p aube-lockfile green (375 unit + proptests), clippy clean (default and --features bench), bench builds. Kept as a draft.

Comment thread crates/aube-lockfile/src/pnpm/subset.rs Resolved
@github-actions

Copy link
Copy Markdown

This PR currently has failing checks. If this continues for 7 days, it will be closed automatically.

This is warning day 1 of 7.

Please update the PR when you have a chance. Feel free to reopen or create a new PR if it is closed and you'd like to continue working on it.

This comment was generated by an automated workflow.

@greptile-apps

greptile-apps Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Want your agent to iterate on Greptile's feedback? Try greploops.

@github-actions

Copy link
Copy Markdown

This PR currently has failing checks. If this continues for 7 days, it will be closed automatically.

This is warning day 1 of 7.

Please update the PR when you have a chance. Feel free to reopen or create a new PR if it is closed and you'd like to continue working on it.

This comment was generated by an automated workflow.

@github-actions

Copy link
Copy Markdown

This PR currently has failing checks. If this continues for 7 days, it will be closed automatically.

This is warning day 2 of 7.

Please update the PR when you have a chance. Feel free to reopen or create a new PR if it is closed and you'd like to continue working on it.

This comment was generated by an automated workflow.

@github-actions

Copy link
Copy Markdown

This PR currently has failing checks. If this continues for 7 days, it will be closed automatically.

This is warning day 3 of 7.

Please update the PR when you have a chance. Feel free to reopen or create a new PR if it is closed and you'd like to continue working on it.

This comment was generated by an automated workflow.

jdalton and others added 11 commits June 23, 2026 13:13
Add an additive fast-path parser for the constrained YAML dialect pnpm
emits (2-space block indent, no anchors/aliases/multiline scalars, flow
style only for small inline maps/seqs). It walks the bytes directly with
memchr for line boundaries and the structural ':' separator, producing
the same RawPnpmLockfile the serde/yaml_serde path produces.

The parser is a strict subset parser: on any construct outside the
recognized shape (multi-document streams, unmodeled top-level keys,
unexpected indent, quoting it cannot normalize) it returns None and
parse_raw_lockfile transparently falls back to the existing yaml_serde
parser. Fiddly inline flow values (resolution/variants/string_or_seq)
and package bodies are handed to yaml_serde so those shapes stay in
lockstep with serde. Standalone behavior is preserved by construction.

Adds a criterion bench (benches/pnpm_lock_parse.rs) comparing both paths;
set AUBE_BENCH_LOCKFILE to point at a real lockfile. On a generated
1.1MB / 4000-package lockfile the subset path parses in ~28ms vs ~45ms
for the serde path. Existing aube-lockfile tests (345 unit + 5 proptest,
including the pnpm roundtrip proptest) stay green.
…npm subset parser

Add a differential test harness for the pnpm-lock.yaml subset parser:
parse a fixture both ways (subset fast path and the yaml_serde path)
and assert the resulting RawPnpmLockfile is structurally identical, or
that the subset path declined (an acceptable fallback). Equality is
compared via the {:#?} rendering since the raw types are
Deserialize-only. Covers committed fixtures and an opt-in sweep over an
external corpus directory (AUBE_PNPM_CORPUS), which fails on any
divergence.

Add fixtures exercising catalog:/catalogs:, overrides,
patchedDependencies, npm-alias deps, scoped packages,
peerDependencies(+Meta), os/cpu/libc, deprecated, bundledDependencies,
remote-tarball resolutions, empty importer {}, optional snapshots,
transitivePeerDependencies, the v6 layout, and git/ssh resolutions with
peer-paren and subpath keys. Add byte-scanner edge-case unit tests:
CRLF, tab indentation (declines), comments/blank lines, colon-bearing
values, quote styles and escapes, trailing whitespace.

Fix: the subset parser declined any importer written as an inline `{}`
(a workspace package with no dependencies), falling the whole lockfile
back to serde. Accept `{}` as an empty importer body, mirroring the
existing package/snapshot handling, so the fast path fires on
multi-package workspaces. Verified default-preserving against a 38-file
real-world corpus (pnpm lockfileVersion 9): 36 match, 2 decline
(multi-document streams), 0 divergences; the fix raised matches from 13
to 36 with no silent wrong parse.
Add real pnpm-lock.yaml fixtures harvested from pinned pnpm releases
(pnpm 6 -> lv5.3, 7 -> lv5.4, 8 -> lv6.0, 9/10 -> lv9.0), covering
plain, workspace, patched/overrides, and git/tarball/npm-alias deps.

Differential coverage added:
- cross-version match-or-decline check over the committed fixtures
- pre-v9 top-level-dependency formats decline rather than misparse
- v9 git/tarball/alias package keys (embedded #, @https://) fire and
  match serde
- adversarial unit tests: snapshot dep values with peer parens, scoped
  peer keys, numeric/bool-looking string values, quoted bool for
  optional (declines), multiline block scalars (decline)

Corpus sweep (differential_real_corpus) print per-file outcome. No
divergences found across any pnpm/lockfile version.
…pnpm subset parser

The subset parser called the block-map/serde-delegate helpers
unconditionally for map-typed fields, ignoring an inline `{...}` value on
the header line. For `overrides`/`time`/`settings`/`catalogs`/
`patchedDependencies`/`importers`/`packages`/`snapshots` written in inline
flow form, it scanned an empty block and produced `Some({})` while serde
parsed the real entries — a silent divergence. An empty block map (`key:`
with a null body, or an empty snapshot `dependencies:`) also produced
`Some({})` where serde reads `None`.

parse_string_map now takes the inline value and declines on an inline map
or an empty result; the block-bodied top-level arms decline on any inline
value. Adds differential tests covering the inline-flow and empty-block
forms for every field touched.

Also:
- remove the unused `header` parameter from parse_seq_value
- broaden has_document_separator to catch `---\r`/`---\t`/`---#`; note it
  is a fast-path hint with the structural-colon decline as the guarantee
- gate the `__bench_parse_*`/`__bench_counts` shims behind a `bench`
  feature so they leave the crate's default public surface; the
  pnpm_lock_parse bench requires it
- replace default_package_info's per-call `from_str("{}").expect(...)`
  with `RawPackageInfo::default()` (derive Default), pinned equal to
  serde's `{}` parse by a test
- comment the intentionally-orphaned bundled-pkg kitchen-sink fixture
  entry
- credit jzon-rs for the byte-cursor design in the module header
…it-rate guards for pnpm subset parser

Hardens the c766fc4 decline fix against two regressions:

- inline_vs_block_never_diverges: a proptest (256 cases, deterministic
  ChaCha seed) generates a logical lockfile over the map-typed fields
  (overrides/time) as Null/Empty/Populated, renders it both block and
  inline-flow style, and asserts diff_subset_vs_serde returns Match or
  Declined for BOTH renderings. Fuzzes the inline-vs-block boundary so a
  re-introduced silent inline misparse is caught.

- populated_block_forms_still_fire_per_field: pins the legitimate
  populated BLOCK form of overrides/time/settings/patchedDependencies/
  catalogs/importers/snapshot.optionalDependencies to the fast path
  (fires + matches), evidencing the inline-decline fix did not regress
  the common-case hit rate.

- map_field_null_empty_populated_track_serde: asserts the
  null/empty/populated decision matches serde's None/empty/populated
  reading (populated block fires; null body and inline {} decline; no
  divergence either way).
…ique reuse

The pnpm subset parser is a plain byte-cursor line scanner: it finds
line boundaries with memchr and matches the single structural colon with
a short scalar pass. It does not use jzon-rs's distinctive SWAR/SIMD
structural-scan kernels. Reword the module doc to credit jzon-rs for the
byte-cursor + decline-on-doubt shape that inspired the design, rather
than claiming its techniques were adopted.
A bare `dependencies:` (no children) under an importer caused
`parse_dep_specs` to build an empty map and store `Some({})`, while the
serde path reads the `Option<BTreeMap<…>>` field as `None`. That is a
silent subset-vs-serde divergence, which the subset parser's contract
forbids (it must match serde or decline to the fallback, never produce a
different result).

Add an empty-map guard before the final `Some(map)` so the empty case
declines and serde decides, mirroring the existing guard in the sibling
`parse_string_map`. Adds a regression test with an importer carrying an
empty `dependencies:` plus a populated `devDependencies:`.
@jdalton
jdalton force-pushed the perf/pnpm-lock-subset-parser branch from 4929114 to 851988a Compare June 23, 2026 17:25
Comment thread crates/aube-lockfile/src/pnpm/subset.rs Resolved
@jdalton
jdalton marked this pull request as ready for review June 24, 2026 21:47
@jdx
jdx enabled auto-merge (squash) June 24, 2026 21:53
@jdx
jdx merged commit a646ac9 into aubepkg:main Jun 24, 2026
17 checks passed
@cursor cursor Bot mentioned this pull request Jun 24, 2026
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.

2 participants