use std::sync::OnceLock;
use regex::Regex;
use crate::diagnostic::{Diagnostic, Fix};
use crate::regex_util::compile_static;
use crate::rule::LintRule;
use mdwright_document::Document;
pub struct EscapedEmphasis;
fn pattern() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| compile_static(r"\\[_*`]"))
}
impl LintRule for EscapedEmphasis {
fn name(&self) -> &str {
"escaped-emphasis"
}
fn description(&self) -> &str {
"Literal `\\_`, `\\*`, or `` \\` `` escape in prose (mdformat damage)."
}
fn explain(&self) -> &str {
include_str!("explain/escaped_emphasis.md")
}
fn produces_fix(&self) -> bool {
true
}
fn is_default(&self) -> bool {
false
}
fn check(&self, doc: &Document, out: &mut Vec<Diagnostic>) {
for chunk in doc.prose_chunks() {
for m in pattern().find_iter(&chunk.text) {
let target = m.as_str().as_bytes().get(1).copied();
let (fix_char, label) = match target {
Some(b'_') => ("*", r"\_"),
Some(b'*') => ("*", r"\*"),
Some(b'`') => ("`", r"\`"),
_ => continue,
};
let message = format!(
"`{label}` escape — likely italic-delimiter damage from a previous \
formatter pass; prefer `*…*` for italics, never `_…_`"
);
if let Some(d) = Diagnostic::at(
doc,
chunk.byte_offset,
m.range(),
message,
Some(Fix {
replacement: fix_char.to_owned(),
safe: true,
}),
) {
out.push(d);
}
}
}
}
}