const MARKER: &str = "~keep";
const MARKER_PUNCTUATION: [char; 3] = [':', ',', ';'];
const MARKER_DELIMITERS: [(char, char); 3] = [('(', ')'), ('[', ']'), ('`', '`')];
pub(crate) fn strip_keep_markers(rendered: &str) -> String {
if !rendered.contains(MARKER) {
return rendered.to_string();
}
rendered.split('\n').map(strip_line).collect::<Vec<_>>().join("\n")
}
fn strip_line(line: &str) -> String {
let mut output = line.to_string();
let mut search_from = 0;
while let Some(found) = output[search_from..].find(MARKER) {
let mut start = search_from + found;
let mut end = start + MARKER.len();
if let Some(punctuation) = output[end..].chars().next()
&& MARKER_PUNCTUATION.contains(&punctuation)
{
end += punctuation.len_utf8();
}
for (open, close) in MARKER_DELIMITERS {
if output[..start].ends_with(open) && output[end..].starts_with(close) {
start -= open.len_utf8();
end += close.len_utf8();
break;
}
}
let space_before = start == 0 || output[..start].ends_with(char::is_whitespace);
let space_after = end == output.len() || output[end..].starts_with(char::is_whitespace);
if space_before && space_after {
if end == output.len() {
start -= trailing_whitespace_len(&output[..start]);
} else {
end += leading_whitespace_len(&output[end..]);
}
} else if space_before {
start -= trailing_whitespace_len(&output[..start]);
}
output.replace_range(start..end, "");
search_from = start;
}
output
}
fn trailing_whitespace_len(text: &str) -> usize {
text.len() - text.trim_end().len()
}
fn leading_whitespace_len(text: &str) -> usize {
text.len() - text.trim_start().len()
}
#[cfg(test)]
mod tests {
use super::strip_keep_markers;
#[test]
fn strip_keep_markers_leaves_text_without_a_marker_byte_identical() {
let source = "// A comment with no marker.\nfn main() {}\n";
assert_eq!(strip_keep_markers(source), source);
}
#[test]
fn strip_keep_markers_removes_a_trailing_marker_and_keeps_the_whole_comment() {
let rendered = "/// Backed by the FFI's `AlefHandle` (`u64`), not a pointer. ~keep\npub const A: u8 = 0;\n";
let stripped = strip_keep_markers(rendered);
assert_eq!(
stripped,
"/// Backed by the FFI's `AlefHandle` (`u64`), not a pointer.\npub const A: u8 = 0;\n"
);
}
#[test]
fn strip_keep_markers_removes_a_mid_comment_marker_and_keeps_the_prose_on_both_sides() {
let rendered = "// SAFETY: ~keep The Swift API requires conformers to synchronize state.\n";
assert_eq!(
strip_keep_markers(rendered),
"// SAFETY: The Swift API requires conformers to synchronize state.\n"
);
}
#[test]
fn strip_keep_markers_removes_a_parenthesized_marker_without_leaving_empty_parentheses() {
let rendered = "/// never smuggled into human-facing text. (~keep)\n";
assert_eq!(
strip_keep_markers(rendered),
"/// never smuggled into human-facing text.\n"
);
}
#[test]
fn strip_keep_markers_strips_every_marker_variant_leaving_readable_prose() {
let cases: &[(&str, &str)] = &[
("Recovered from a parse error. ~keep", "Recovered from a parse error."),
(
"~keep Explains why the fallback is safe.",
"Explains why the fallback is safe.",
),
(
"The pool ~keep is rebuilt per request.",
"The pool is rebuilt per request.",
),
(
"Drained after every page. ~keep: that drain observes one thread.",
"Drained after every page. that drain observes one thread.",
),
("See the note below. ~keep:", "See the note below."),
("~keep: mirrors the bench tolerance.", "mirrors the bench tolerance."),
(
"Mirrors `image.rs`. ~keep, same rationale as above.",
"Mirrors `image.rs`. same rationale as above.",
),
(
"Guarded by a flag. ~keep; the flag is compile-time.",
"Guarded by a flag. the flag is compile-time.",
),
(
"Never smuggled into human-facing text. (~keep)",
"Never smuggled into human-facing text.",
),
(
"Applies to both paths (see the note ~keep).",
"Applies to both paths (see the note).",
),
(
"Tracked separately [~keep] for the async path.",
"Tracked separately for the async path.",
),
(
"See the `~keep` note in `prepare_session`.",
"See the note in `prepare_session`.",
),
("Two markers ~keep on one line. ~keep", "Two markers on one line."),
("~keep", ""),
("~keep:", ""),
("(~keep)", ""),
];
for (raw, expected) in cases {
let stripped = strip_keep_markers(raw);
assert_eq!(&stripped, expected, "stripping {raw:?}");
assert!(!stripped.contains("~keep"), "marker survived in {stripped:?}");
assert!(!stripped.contains(".:"), "dangling punctuation in {stripped:?}");
}
}
#[test]
fn strip_keep_markers_does_not_consume_the_newline_or_the_next_line_indentation() {
let rendered = " // context survives. ~keep\n let value = 1;\n";
let stripped = strip_keep_markers(rendered);
assert_eq!(stripped, " // context survives.\n let value = 1;\n");
assert_eq!(stripped.lines().count(), 2, "line count changed: {stripped:?}");
}
#[test]
fn strip_keep_markers_handles_a_marker_on_every_line_of_a_block() {
let rendered = "// first line. ~keep\n// second line. ~keep\n// third line. ~keep\n";
assert_eq!(
strip_keep_markers(rendered),
"// first line.\n// second line.\n// third line.\n"
);
}
#[test]
fn strip_keep_markers_keeps_the_comment_introducer_when_the_marker_is_the_whole_comment() {
assert_eq!(strip_keep_markers(" // ~keep\n"), " //\n");
}
}
#[cfg(test)]
mod rendered_template_tests {
#[test]
fn zig_trait_bridge_alias_renders_its_rationale_without_the_keep_marker() {
let rendered = crate::backends::zig::template_env::render(
"trait_bridge_alias.jinja",
minijinja::context! { alias => "VisitorHandle" },
);
assert!(
rendered.contains("/// Backed by the FFI's `AlefHandle` (`u64`), not a pointer."),
"the rationale comment must survive the strip verbatim, got:\n{rendered}"
);
assert!(
rendered.contains("pub const VisitorHandle = u64;"),
"the aliased declaration must still render, got:\n{rendered}"
);
assert!(
!rendered.contains("~keep"),
"marker leaked into zig output:\n{rendered}"
);
}
#[test]
fn napi_error_converter_renders_its_rationale_without_the_parenthesized_keep_marker() {
let rendered = crate::codegen::template_env::render(
"error_gen/napi_error_converter.jinja",
minijinja::context! { rust_path => "sample_crate::SampleError", fn_name => "sample_error_to_napi" },
);
assert!(
rendered.contains("never smuggled into human-facing text."),
"the rationale comment must survive the strip verbatim, got:\n{rendered}"
);
assert!(
!rendered.contains("text. ()") && !rendered.contains("text. )"),
"the marker's own parentheses must go with it, got:\n{rendered}"
);
assert!(
rendered.contains("fn sample_error_to_napi(e: sample_crate::SampleError) -> napi::Error {"),
"the converter signature must still render, got:\n{rendered}"
);
assert!(
!rendered.contains("~keep"),
"marker leaked into napi output:\n{rendered}"
);
}
#[test]
fn csharp_opaque_handle_header_renders_its_rationale_without_the_keep_marker() {
let rendered = crate::backends::csharp::template_env::render(
"opaque_handle_header.jinja",
minijinja::context! {
namespace => "Sample.Bindings",
class_name => "Document",
free_method => "sample_document_free",
},
);
assert!(
rendered
.contains("billion concurrently live handles of this type, and 32-bit .NET targets are legacy-only."),
"the rationale comment must survive the strip verbatim, got:\n{rendered}"
);
assert!(
rendered.contains("internal sealed class DocumentSafeHandle : SafeHandle"),
"the class declaration on the line after the comment must still render, got:\n{rendered}"
);
assert!(!rendered.contains("~keep"), "marker leaked into C# output:\n{rendered}");
}
}