From 9ef5a63e102af22cbbf111ba2602bff50fe4ab14 Mon Sep 17 00:00:00 2001 From: wuyangfan <1102042793@qq.com> Date: Sun, 17 May 2026 13:27:41 +0800 Subject: [PATCH] fix(links): warn on duplicate ANCHOR start without ANCHOR_END Log a warning when a region opened with `ANCHOR: name` encounters another `ANCHOR: name` instead of `ANCHOR_END: name`, and document via test that only ANCHOR_END closes the region (duplicate starts do not). Fixes #2778 Co-authored-by: Cursor --- .../builtin_preprocessors/links/take_lines.rs | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/crates/mdbook-driver/src/builtin_preprocessors/links/take_lines.rs b/crates/mdbook-driver/src/builtin_preprocessors/links/take_lines.rs index e884a994e9..85b810a7e6 100644 --- a/crates/mdbook-driver/src/builtin_preprocessors/links/take_lines.rs +++ b/crates/mdbook-driver/src/builtin_preprocessors/links/take_lines.rs @@ -1,6 +1,7 @@ use mdbook_core::static_regex; use std::ops::Bound::{Excluded, Included, Unbounded}; use std::ops::RangeBounds; +use tracing::warn; /// Take a range of lines from a string. pub(super) fn take_lines>(s: &str, range: R) -> String { @@ -41,7 +42,15 @@ pub(super) fn take_anchored_lines(s: &str, anchor: &str) -> String { } } None => { - if !ANCHOR_START.is_match(l) { + if let Some(cap) = ANCHOR_START.captures(l) { + if &cap["anchor_name"] == anchor { + warn!( + "duplicate ANCHOR start `{anchor}` without a matching \ + `ANCHOR_END: {anchor}`; anchor regions must be closed with \ + ANCHOR_END" + ); + } + } else { retained.push(l); } } @@ -91,7 +100,15 @@ pub(super) fn take_rustdoc_include_anchored_lines(s: &str, anchor: &str) -> Stri } } None => { - if !ANCHOR_START.is_match(l) { + if let Some(cap) = ANCHOR_START.captures(l) { + if &cap["anchor_name"] == anchor { + warn!( + "duplicate ANCHOR start `{anchor}` without a matching \ + `ANCHOR_END: {anchor}`; anchor regions must be closed with \ + ANCHOR_END" + ); + } + } else { output.push_str(l); output.push('\n'); } @@ -250,4 +267,15 @@ mod tests { "# Lorem\nipsum\n# dolor\nsit\n# amet" ); } + + #[test] + fn duplicate_anchor_start_does_not_close_region() { + let closed = "ANCHOR: all\nstruct\nANCHOR_END: all\ntrailing"; + let duplicate_start = "ANCHOR: all\nstruct\nANCHOR: all\ntrailing"; + assert_eq!(take_anchored_lines(closed, "all"), "struct"); + assert_eq!( + take_anchored_lines(duplicate_start, "all"), + "struct\ntrailing" + ); + } }