dmc_transform/builtin/
autolink_headings.rs1use crate::pipeline::Transformer;
2use crate::visit::{NodeAction, Visitor, walk_root};
3use dmc_diagnostic::Code;
4use dmc_diagnostic::metadata::SourceMeta;
5use dmc_parser::ast::*;
6
7#[derive(Default)]
10pub struct AutolinkHeadings {
11 pub aria_label: Option<String>,
12}
13
14impl AutolinkHeadings {
15 pub fn new() -> Self {
18 Self { aria_label: Some("Link to section".to_string()) }
19 }
20}
21
22impl Transformer for AutolinkHeadings {
23 fn name(&self) -> &str {
24 "autolink-headings"
25 }
26
27 fn transform(
28 &self,
29 doc: &mut Document,
30 _meta: &SourceMeta,
31 _diag_engine: &mut duck_diagnostic::DiagnosticEngine<Code>,
32 ) {
33 let mut v = Apply { aria_label: self.aria_label.clone() };
34 walk_root(&mut doc.children, &mut v);
35 }
36}
37
38struct Apply {
39 aria_label: Option<String>,
40}
41
42impl Visitor for Apply {
43 fn visit_node(&mut self, node: &mut Node) -> NodeAction {
44 if let Node::Heading(h) = node {
45 let slug = h.slug();
46 let span = h.span.clone();
47 let original = std::mem::take(&mut h.children);
48 let already = matches!(original.as_slice(), [Node::Link(l)] if l.href == format!("#{}", slug));
49 if already {
50 h.children = original;
51 return NodeAction::KeepSkipChildren;
52 }
53 let link =
54 Node::Link(Link { href: format!("#{}", slug), title: self.aria_label.clone(), children: original, span });
55 h.children = vec![link];
56 return NodeAction::KeepSkipChildren;
57 }
58 NodeAction::Keep
59 }
60}