diff --git a/README.md b/README.md index 25c536c..8b66d93 100644 --- a/README.md +++ b/README.md @@ -410,9 +410,9 @@ typed on `ConvertError` takes both. A call reports the first refusal in document order and stops, so a document with several surfaces them one per call. Every refusal is deterministic — there is no I/O anywhere — so a retry returns -the identical error: fix the input, or set the document aside. On JavaScriptCore (Bun, Safari), -retry once any call that returns `unsupported-node-shape` at the empty path: that engine reports a -heap running short the same way as a string too long to build. +the identical error: fix the input, or set the document aside. The exception is memory running +short, which an engine may report as `unsupported-node-shape` at the empty path, and JavaScriptCore +(Bun, Safari) reports the same way as a string too long to build: retry such a call once. ### Parsing diff --git a/src/markdown/emit/line-escaping.ts b/src/markdown/emit/line-escaping.ts index 9052be7..4252c54 100644 --- a/src/markdown/emit/line-escaping.ts +++ b/src/markdown/emit/line-escaping.ts @@ -95,8 +95,7 @@ function escape(segments: readonly InlineSegment[], container: LineContainer, cl const code = codes[index] return code === undefined ? undefined : escapingCodes[code] } - const escaped = escapeClaims(scan, escapings, container, claims) - escapeClosedRuns(scan, escapings, escaped) + const escaped = escapeClosedRuns(scan, escapings, escapeClaims(scan, escapings, container, claims)) const { output, placements } = backslashed(segments, scan, escaped) if (container === 'paragraph' && opensLinkDefinition(output)) { if (segments[0]?.nodes !== undefined) return { line: output, openingLinkAsDirective: true, unspellableRuns: [] } @@ -227,7 +226,7 @@ function claimsCharacter({ container, escapings, headingCloser, linkClose, scan // CommonMark reads no escape inside a code span, so a backtick string left beside an escape still closes a span that an earlier bare run of its length opens. // The walk runs backwards and escapes only the run it just read, so every index it reads still holds the claims alone. -function escapeClosedRuns(scan: string, escapings: Escapings, escaped: Uint8Array): void { +function escapeClosedRuns(scan: string, escapings: Escapings, escaped: Uint8Array): Uint8Array { // The lengths of the backtick strings escapes leave: a bare run of one of these lengths would open a span one of them closes, so it is escaped too. const formed = new Set() let end = scan.length - 1 @@ -252,6 +251,7 @@ function escapeClosedRuns(scan: string, escapings: Escapings, escaped: Uint8Arra } end = start - 1 } + return escaped } // One emphasis run, the innermost, or every highlight run the line cannot spell. diff --git a/src/markdown/length-bound.test.ts b/src/markdown/length-bound.test.ts index cd8b501..2ce911f 100644 --- a/src/markdown/length-bound.test.ts +++ b/src/markdown/length-bound.test.ts @@ -31,6 +31,7 @@ test('refuses a string past the bound as too long on every engine, and names any ok: false, }) assert.equal(withinEngineLimits(thrown('RangeError', 'Invalid array length')).ok, false) + assert.equal(withinEngineLimits(thrown('RangeError', 'Array buffer allocation failed')).ok, false) assert.deepEqual( withinEngineLimits(() => success('x')), { ok: true, value: 'x' }, diff --git a/src/markdown/length-bound.ts b/src/markdown/length-bound.ts index 62cc2f5..5d49ecd 100644 --- a/src/markdown/length-bound.ts +++ b/src/markdown/length-bound.ts @@ -3,9 +3,9 @@ import { failure, success, type Result } from '../result.ts' // V8's longest string (docs/decisions.md §Markdown past V8's longest string is refused on every engine). export const longestMarkdown = 2 ** 29 - 24 -// Measured on Firefox 155, Node 24 and Bun 1.4. SpiderMonkey's overflow and V8's string overflow both mean a string past longestMarkdown; the other two name an engine's own limit, and JavaScriptCore's also a heap running short. +// Measured on Firefox 155, Node 24 and Bun 1.4. SpiderMonkey's overflow and V8's string overflow both mean a string past longestMarkdown; the others name an engine's own limit or its memory running short. const pastLongestString = ['InternalError: allocation size overflow', 'RangeError: Invalid string length'] -const pastEngineLimit = ['RangeError: Invalid array length', 'RangeError: Out of memory'] +const pastEngineLimit = ['RangeError: Array buffer allocation failed', 'RangeError: Invalid array length', 'RangeError: Out of memory'] export function tooLong(): Result { return failure('unsupported-node-shape', `the markdown for this document, or for a directive's plain CommonMark, would be longer than ${longestMarkdown} characters, the longest this library writes`, [])