Skip to content

Commit c4eb2bc

Browse files
authored
perf(parser): skip the pre-pass line scan when the source has no "]:" (#517)
A reference definition's label must be closed by ] immediately followed by : ([label]:), and a footnote opener requires the same ([^label]:). A ] and : split across a line break never parses as a definition (the joined chunk keeps the newline between them), so the absence of a contiguous "]:" proves the whole line scan would collect nothing. Gate build_prepass on one memmem sweep for that needle; typical link-only documents skip the scan entirely. 1 MB JS-harness sample (no "]:"): pipeline p50 14.92 -> 14.40 ms (-3.5%), build_prepass self 0.45 -> 0.03 ms/iter. Documents containing "]:" (e.g. types.md) pay one extra memmem sweep and are unchanged. Three new edge tests pin the necessary-condition reasoning (split "]\n:" is not a definition, escaped brackets keep the real "]:" visible to the gate, link-only documents still parse). Differential fuzz vs the pre-fusion baseline: 232 cases, zero mismatches.
1 parent 3961189 commit c4eb2bc

2 files changed

Lines changed: 36 additions & 3 deletions

File tree

crates/ox_content_parser/src/parser/prepass.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,16 @@ impl<'a> Parser<'a> {
3131
/// with sub-parsers.
3232
pub(super) fn build_prepass(&self) -> (Rc<ReferenceMap<'a>>, Rc<FootnoteLabels>) {
3333
profile_span!("parser::build_prepass");
34-
// Cheap bail: a definition and a footnote label both start with
35-
// `[`, so a source without one has neither.
36-
if !self.source.contains('[') {
34+
// Cheap bails. Both collectors need a `[` somewhere, and both a
35+
// literal `]:`: a reference definition's label must be closed by
36+
// `]` immediately followed by `:` (`[label]:`), and a footnote
37+
// opener requires the same (`[^label]:`). A `]` and `:` split
38+
// across a line break never parses as a definition, so absence of
39+
// the contiguous needle proves the scan would collect nothing.
40+
// Typical link-only documents skip the whole line scan here.
41+
if !self.source.contains('[')
42+
|| memchr::memmem::find(self.source.as_bytes(), b"]:").is_none()
43+
{
3744
return (Rc::new(ReferenceMap::default()), Rc::new(FootnoteLabels::default()));
3845
}
3946
let collect_footnotes = self.options.footnotes && self.source.contains("[^");

crates/ox_content_parser/tests/edge_cases/prepass.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,3 +205,29 @@ fn footnote_definition_directly_after_multiline_definition_resolves() {
205205
fn multiline_definition_with_title_resolves() {
206206
assert!(resolves_reference("[a]: /url\n\"title\"\n\n[a]"));
207207
}
208+
209+
#[test]
210+
fn bracket_and_colon_split_across_lines_is_not_a_definition() {
211+
// `]` at end of one line and `:` starting the next never form a
212+
// definition (the joined chunk carries the newline between them), so
213+
// the pre-pass may skip sources without a contiguous "]:".
214+
assert!(!resolves_reference("[a]\n: /url\n\n[a]"));
215+
}
216+
217+
#[test]
218+
fn escaped_bracket_does_not_end_a_label() {
219+
// `\]` stays inside the label; the definition's real close is the
220+
// unescaped `]:` later, which the needle gate must also see.
221+
assert!(resolves_reference("[a\\]b]: /url\n\n[a\\]b]"));
222+
}
223+
224+
#[test]
225+
fn link_only_document_resolves_nothing_and_parses_fine() {
226+
let allocator = Allocator::new();
227+
let doc = parse_with_options(
228+
&allocator,
229+
"Here's a [link](https://example.com) and [another](/x).",
230+
ParserOptions::default(),
231+
);
232+
assert!(contains_link(&doc.children));
233+
}

0 commit comments

Comments
 (0)