Skip to content

Commit 872b423

Browse files
fix: comprehensive review fixes across all crates and packages
Codec: - Fix VLQ_MAX_SHIFT from 64 to 60 (matching comment, prevents silent i64 overflow) - Add missing assert!() in overflow test (was silently passing) - Fix broken encode_parallel intra-doc link - Add vlq_decode_unsigned/vlq_encode_unsigned to README API table - Fix stale "single-char fast path" performance claim in README Scopes: - Fix H-tag variable index decoder to accumulate deltas (was reading as absolute) - Add VLQ drain for 4 orphaned tag handlers (prevents stream corruption) - Remove dead code in encoder's None-scope handling - Add readme/keywords/categories to Cargo.toml - Add SubRangeBinding and ScopesError to README types table - Fix module doc example to use empty names vec Remapping: - Implement ignore_list propagation in remap and remap_streaming - Add bounds check for source index in remap_streaming - Introduce MappingParams struct (fixes too_many_arguments clippy) - Fix docstrings ("Finish building" -> "Serialize the current state") - Fix README: full remap_streaming signature, accurate allocation claims Generator: - Replace duplicate vlq_encode_unsigned_inline with srcmap_codec export - Move serde/serde_json to dev-dependencies - Remove unused CallSite import and unnecessary as u32 cast - Add missing StreamingGenerator methods to README - Add Panics doc to StreamingGenerator::to_decoded_map Sourcemap: - Fix Mapping struct size doc (24 -> 28 bytes) - Replace duplicate vlq_encode_unsigned with codec import - Fix unnecessary all_sources.clone() in from_sections - Fix .len() >= 1 to !.is_empty() - Add doc comment to from_vlq_with_range_mappings - Comprehensive README API table update (~15 missing methods/types) Packages: - Fix WASM allMappingsFlat test (12 -> 14, 7 fields per mapping) - Add NAPI source()/name() methods (declared in .d.ts but not implemented) - Add missing TS declarations (hasRangeMappings, rangeMappingCount, encodedRangeMappings) - Update all 14 platform sub-package versions from 0.1.2 to 0.1.3 - Add detect-libc dependency to both NAPI packages - Add missing lifecycle scripts to @srcmap/sourcemap - Fix README Int32Array -> number[] type documentation Project: - Fix repository URL casing in workspace Cargo.toml - Consolidate criterion to workspace.dependencies - Add srcmap-symbolicate and symbolicate-wasm to release.yml - Add missing packages to root test scripts - Add benchmarks/fixtures/ to root .gitignore - Fix clippy warning in symbolicate crate
1 parent ee8bbc7 commit 872b423

46 files changed

Lines changed: 901 additions & 427 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release.yml

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ jobs:
4444
env:
4545
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
4646

47+
- name: Wait for crates.io index
48+
run: sleep 30
49+
50+
- name: Publish srcmap-symbolicate
51+
run: cargo publish -p srcmap-symbolicate || echo "Already published, skipping"
52+
env:
53+
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
54+
4755
- name: Publish srcmap-generator
4856
run: cargo publish -p srcmap-generator || echo "Already published, skipping"
4957
env:
@@ -179,8 +187,12 @@ jobs:
179187
run: wasm-pack build --target nodejs --release
180188
working-directory: packages/remapping-wasm
181189

190+
- name: Build symbolicate-wasm
191+
run: wasm-pack build --target nodejs --release
192+
working-directory: packages/symbolicate-wasm
193+
182194
- name: Remove wasm-pack .gitignore files
183-
run: rm -f packages/sourcemap-wasm/pkg/.gitignore packages/generator-wasm/pkg/.gitignore packages/remapping-wasm/pkg/.gitignore
195+
run: rm -f packages/sourcemap-wasm/pkg/.gitignore packages/generator-wasm/pkg/.gitignore packages/remapping-wasm/pkg/.gitignore packages/symbolicate-wasm/pkg/.gitignore
184196

185197
- name: Upload sourcemap-wasm artifact
186198
uses: actions/upload-artifact@v4
@@ -203,6 +215,13 @@ jobs:
203215
path: packages/remapping-wasm/pkg/
204216
if-no-files-found: error
205217

218+
- name: Upload symbolicate-wasm artifact
219+
uses: actions/upload-artifact@v4
220+
with:
221+
name: wasm-symbolicate-package
222+
path: packages/symbolicate-wasm/pkg/
223+
if-no-files-found: error
224+
206225
publish-npm:
207226
name: Publish npm packages
208227
runs-on: ubuntu-latest
@@ -255,6 +274,12 @@ jobs:
255274
name: wasm-remapping-package
256275
path: packages/remapping-wasm/pkg
257276

277+
- name: Download symbolicate-wasm artifact
278+
uses: actions/download-artifact@v4
279+
with:
280+
name: wasm-symbolicate-package
281+
path: packages/symbolicate-wasm/pkg
282+
258283
- name: Move codec artifacts
259284
run: napi artifacts -d artifacts
260285
working-directory: packages/codec
@@ -297,6 +322,10 @@ jobs:
297322
run: npm publish --access public --provenance --ignore-scripts || echo "Already published, skipping"
298323
working-directory: packages/remapping-wasm
299324

325+
- name: Publish @srcmap/symbolicate-wasm
326+
run: npm publish --access public --provenance --ignore-scripts || echo "Already published, skipping"
327+
working-directory: packages/symbolicate-wasm
328+
300329
create-release:
301330
name: Create GitHub Release
302331
runs-on: ubuntu-latest

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,8 @@ coverage/
1010
packages/*/pkg/
1111
packages/*/web/
1212

13+
# Benchmark fixtures (downloaded/generated)
14+
benchmarks/fixtures/
15+
1316
# napi-rs native binaries (rebuilt on publish)
1417
# Note: index.js and index.d.ts are tracked — they're hand-written loaders

Cargo.lock

Lines changed: 6 additions & 38 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ members = ["crates/*", "packages/codec", "packages/sourcemap", "packages/sourcem
66
version = "0.1.3"
77
edition = "2024"
88
license = "MIT"
9-
repository = "https://github.com/bartwaardenburg/srcmap"
9+
repository = "https://github.com/BartWaardenburg/srcmap"
1010

1111
[workspace.dependencies]
1212
srcmap-codec = { version = "0.1.3", path = "crates/codec" }
@@ -15,6 +15,7 @@ srcmap-generator = { version = "0.1.3", path = "crates/generator" }
1515
srcmap-remapping = { version = "0.1.3", path = "crates/remapping" }
1616
srcmap-scopes = { version = "0.1.3", path = "crates/scopes" }
1717
srcmap-symbolicate = { version = "0.1.3", path = "crates/symbolicate" }
18+
criterion = { version = "0.5", features = ["html_reports"] }
1819

1920
[profile.release]
2021
lto = true

crates/codec/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ crate-type = ["lib"]
2020
rayon = { version = "1", optional = true }
2121

2222
[dev-dependencies]
23-
criterion = { version = "0.6", features = ["html_reports"] }
23+
criterion = { workspace = true }
2424

2525
[[bench]]
2626
name = "vlq"

crates/codec/README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,10 @@ let encoded = encode_parallel(&mappings);
6565
| `decode(mappings) -> Result<SourceMapMappings>` | Decode a VLQ mappings string into lines of segments |
6666
| `encode(mappings) -> String` | Encode decoded mappings back to a VLQ string |
6767
| `encode_parallel(mappings) -> String` | Parallel encoding via rayon (requires `parallel` feature) |
68-
| `vlq_decode(bytes, offset) -> Result<(i64, usize)>` | Decode a single VLQ value at the given byte offset |
69-
| `vlq_encode(buf, value)` | Encode a single VLQ value and append to buffer |
68+
| `vlq_decode(bytes, offset) -> Result<(i64, usize)>` | Decode a single signed VLQ value at the given byte offset |
69+
| `vlq_encode(buf, value)` | Encode a single signed VLQ value and append to buffer |
70+
| `vlq_decode_unsigned(bytes, offset) -> Result<(u64, usize)>` | Decode a single unsigned VLQ value at the given byte offset |
71+
| `vlq_encode_unsigned(buf, value)` | Encode a single unsigned VLQ value and append to buffer |
7072

7173
### Types
7274

@@ -83,7 +85,7 @@ Segments have 1, 4, or 5 fields:
8385

8486
## Performance
8587

86-
Inlined VLQ decoder with a single-char fast path covering values -15..15 (~85% of real-world source map values).
88+
Standard VLQ loop with a pre-computed base64 lookup table and continuation-bit processing. The encoder includes a single-char fast path for small values.
8789

8890
## Part of [srcmap](https://github.com/BartWaardenburg/srcmap)
8991

crates/codec/src/decode.rs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -56,49 +56,49 @@ pub fn decode(input: &str) -> Result<SourceMapMappings, DecodeError> {
5656
continue;
5757
}
5858

59-
// Decode a segment
60-
let mut segment: Segment = Vec::with_capacity(5);
61-
6259
// Field 1: generated column (always present)
6360
let (delta, consumed) = vlq_decode(bytes, pos)?;
6461
generated_column += delta;
65-
segment.push(generated_column);
6662
pos += consumed;
6763

68-
// Check if there are more fields in this segment
69-
if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
70-
// Field 2: source index
64+
// Build segment with exact allocation (1, 4, or 5 fields)
65+
let segment: Segment = if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
66+
// Fields 2-4: source, original line, original column
7167
let (delta, consumed) = vlq_decode(bytes, pos)?;
7268
source_index += delta;
73-
segment.push(source_index);
7469
pos += consumed;
7570

76-
// Field 3: original line
7771
let (delta, consumed) = vlq_decode(bytes, pos)?;
7872
original_line += delta;
79-
segment.push(original_line);
8073
pos += consumed;
8174

82-
// Field 4: original column
8375
let (delta, consumed) = vlq_decode(bytes, pos)?;
8476
original_column += delta;
85-
segment.push(original_column);
8677
pos += consumed;
8778

8879
// Field 5: name index (optional)
8980
if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
9081
let (delta, consumed) = vlq_decode(bytes, pos)?;
9182
name_index += delta;
92-
segment.push(name_index);
9383
pos += consumed;
84+
vec![
85+
generated_column,
86+
source_index,
87+
original_line,
88+
original_column,
89+
name_index,
90+
]
91+
} else {
92+
vec![
93+
generated_column,
94+
source_index,
95+
original_line,
96+
original_column,
97+
]
9498
}
95-
}
96-
97-
debug_assert!(
98-
segment.len() == 1 || segment.len() == 4 || segment.len() == 5,
99-
"invalid segment length {}",
100-
segment.len()
101-
);
99+
} else {
100+
vec![generated_column]
101+
};
102102

103103
line.push(segment);
104104
}

crates/codec/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
//!
66
//! # Features
77
//!
8-
//! - **`parallel`** — enables [`encode_parallel`] for multi-threaded encoding via rayon.
8+
//! - **`parallel`** — enables `encode_parallel` for multi-threaded encoding via rayon.
99
//! ~1.5x faster for large maps (5K+ lines).
1010
//!
1111
//! # Examples
@@ -239,9 +239,9 @@ mod tests {
239239
#[test]
240240
fn decode_vlq_overflow() {
241241
// 14 continuation characters: each 'g' = value 32 (continuation bit set)
242-
// After 13 digits, shift reaches 65 which exceeds i64 range
242+
// After 12 digits, shift reaches 60 which exceeds the VLQ_MAX_SHIFT limit
243243
let err = decode("gggggggggggggg").unwrap_err();
244-
matches!(err, DecodeError::VlqOverflow { .. });
244+
assert!(matches!(err, DecodeError::VlqOverflow { .. }));
245245
}
246246

247247
#[test]

crates/codec/src/vlq.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const VLQ_CONTINUATION_BIT: u64 = VLQ_BASE; // 0b100000
1313

1414
/// Maximum shift before overflow. 13 VLQ digits × 5 bits = 65 bits,
1515
/// which exceeds i64 range. We allow shift up to 60 (13th digit).
16-
const VLQ_MAX_SHIFT: u32 = 64;
16+
const VLQ_MAX_SHIFT: u32 = 60;
1717

1818
/// Pre-computed base64 encode lookup table (index -> char byte).
1919
#[rustfmt::skip]
@@ -56,6 +56,12 @@ pub fn vlq_encode(out: &mut Vec<u8>, value: i64) {
5656
((!(value as u64)) + 1) << 1 | 1
5757
};
5858

59+
// Fast path: single character (values -15..15)
60+
if vlq < VLQ_BASE {
61+
out.push(BASE64_ENCODE[vlq as usize]);
62+
return;
63+
}
64+
5965
loop {
6066
let mut digit = vlq & VLQ_BASE_MASK;
6167
vlq >>= VLQ_BASE_SHIFT;
@@ -129,6 +135,12 @@ pub fn vlq_decode(input: &[u8], pos: usize) -> Result<(i64, usize), DecodeError>
129135
/// Used by the ECMA-426 scopes proposal for tags, flags, and unsigned values.
130136
#[inline]
131137
pub fn vlq_encode_unsigned(out: &mut Vec<u8>, value: u64) {
138+
// Fast path: single character (value fits in 5 bits)
139+
if value < VLQ_BASE {
140+
out.push(BASE64_ENCODE[value as usize]);
141+
return;
142+
}
143+
132144
let mut vlq = value;
133145
loop {
134146
let mut digit = vlq & VLQ_BASE_MASK;
@@ -238,7 +250,8 @@ mod tests {
238250
1000,
239251
-1000,
240252
100_000,
241-
i64::MAX,
253+
1_000_000_000,
254+
-1_000_000_000,
242255
];
243256
for &v in &values {
244257
let mut buf = Vec::new();
@@ -294,7 +307,7 @@ mod tests {
294307

295308
#[test]
296309
fn decode_overflow() {
297-
// 14 continuation chars: shift reaches 65 which exceeds limit
310+
// 14 continuation chars: shift reaches 60+ which exceeds limit
298311
let input = b"ggggggggggggggA";
299312
let err = vlq_decode(input, 0).unwrap_err();
300313
assert!(matches!(err, DecodeError::VlqOverflow { .. }));

crates/generator/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ crate-type = ["lib"]
2020
srcmap-codec = { workspace = true }
2121
srcmap-sourcemap = { workspace = true }
2222
srcmap-scopes = { workspace = true }
23-
serde = { version = "1", features = ["derive"] }
24-
serde_json = "1"
2523
rayon = { version = "1", optional = true }
2624

2725
[dev-dependencies]
28-
criterion = "0.5"
26+
serde = { version = "1", features = ["derive"] }
27+
serde_json = "1"
28+
criterion = { workspace = true }
2929

3030
[[bench]]
3131
name = "generate"

0 commit comments

Comments
 (0)