rumdl_lib/utils/
skip_context.rs1use crate::config::MarkdownFlavor;
7use crate::lint_context::LintContext;
8use crate::utils::kramdown_utils::is_math_block_delimiter;
9use crate::utils::mkdocs_admonitions;
10use crate::utils::mkdocs_critic;
11use crate::utils::mkdocs_footnotes;
12use crate::utils::mkdocs_snippets;
13use crate::utils::mkdocs_tabs;
14use crate::utils::mkdocstrings_refs;
15use crate::utils::regex_cache::HTML_COMMENT_PATTERN;
16use lazy_static::lazy_static;
17use regex::Regex;
18
19lazy_static! {
20 static ref INLINE_MATH_REGEX: Regex = Regex::new(r"\$(?:\$)?[^$]+\$(?:\$)?").unwrap();
22}
23
24pub fn is_in_front_matter(content: &str, line_num: usize) -> bool {
26 let lines: Vec<&str> = content.lines().collect();
27
28 if !lines.is_empty() && lines[0] == "---" {
30 for (i, line) in lines.iter().enumerate().skip(1) {
31 if *line == "---" {
32 return line_num <= i;
33 }
34 }
35 }
36
37 if !lines.is_empty() && lines[0] == "+++" {
39 for (i, line) in lines.iter().enumerate().skip(1) {
40 if *line == "+++" {
41 return line_num <= i;
42 }
43 }
44 }
45
46 false
47}
48
49pub fn is_in_skip_context(ctx: &LintContext, byte_pos: usize) -> bool {
51 if ctx.is_in_code_block_or_span(byte_pos) {
53 return true;
54 }
55
56 if is_in_html_comment(ctx.content, byte_pos) {
58 return true;
59 }
60
61 if is_in_math_context(ctx, byte_pos) {
63 return true;
64 }
65
66 if is_in_html_tag(ctx, byte_pos) {
68 return true;
69 }
70
71 if ctx.flavor == MarkdownFlavor::MkDocs && mkdocs_snippets::is_within_snippet_section(ctx.content, byte_pos) {
73 return true;
74 }
75
76 if ctx.flavor == MarkdownFlavor::MkDocs && mkdocs_admonitions::is_within_admonition(ctx.content, byte_pos) {
78 return true;
79 }
80
81 if ctx.flavor == MarkdownFlavor::MkDocs && mkdocs_footnotes::is_within_footnote_definition(ctx.content, byte_pos) {
83 return true;
84 }
85
86 if ctx.flavor == MarkdownFlavor::MkDocs && mkdocs_tabs::is_within_tab_content(ctx.content, byte_pos) {
88 return true;
89 }
90
91 if ctx.flavor == MarkdownFlavor::MkDocs && mkdocstrings_refs::is_within_autodoc_block(ctx.content, byte_pos) {
93 return true;
94 }
95
96 if ctx.flavor == MarkdownFlavor::MkDocs && mkdocs_critic::is_within_critic_markup(ctx.content, byte_pos) {
98 return true;
99 }
100
101 false
102}
103
104pub fn is_mkdocs_snippet_line(line: &str, flavor: MarkdownFlavor) -> bool {
106 flavor == MarkdownFlavor::MkDocs && mkdocs_snippets::is_snippet_marker(line)
107}
108
109pub fn is_mkdocs_admonition_line(line: &str, flavor: MarkdownFlavor) -> bool {
111 flavor == MarkdownFlavor::MkDocs && mkdocs_admonitions::is_admonition_marker(line)
112}
113
114pub fn is_mkdocs_footnote_line(line: &str, flavor: MarkdownFlavor) -> bool {
116 flavor == MarkdownFlavor::MkDocs && mkdocs_footnotes::is_footnote_definition(line)
117}
118
119pub fn is_mkdocs_tab_line(line: &str, flavor: MarkdownFlavor) -> bool {
121 flavor == MarkdownFlavor::MkDocs && mkdocs_tabs::is_tab_marker(line)
122}
123
124pub fn is_mkdocstrings_autodoc_line(line: &str, flavor: MarkdownFlavor) -> bool {
126 flavor == MarkdownFlavor::MkDocs && mkdocstrings_refs::is_autodoc_marker(line)
127}
128
129pub fn is_mkdocs_critic_line(line: &str, flavor: MarkdownFlavor) -> bool {
131 flavor == MarkdownFlavor::MkDocs && mkdocs_critic::contains_critic_markup(line)
132}
133
134pub fn is_in_html_comment(content: &str, byte_pos: usize) -> bool {
136 for m in HTML_COMMENT_PATTERN.find_iter(content) {
137 if m.start() <= byte_pos && byte_pos < m.end() {
138 return true;
139 }
140 }
141 false
142}
143
144pub fn is_in_html_tag(ctx: &LintContext, byte_pos: usize) -> bool {
146 for html_tag in ctx.html_tags().iter() {
147 if html_tag.byte_offset <= byte_pos && byte_pos < html_tag.byte_end {
148 return true;
149 }
150 }
151 false
152}
153
154pub fn is_in_math_context(ctx: &LintContext, byte_pos: usize) -> bool {
156 let content = ctx.content;
157
158 if is_in_math_block(content, byte_pos) {
160 return true;
161 }
162
163 if is_in_inline_math(content, byte_pos) {
165 return true;
166 }
167
168 false
169}
170
171pub fn is_in_math_block(content: &str, byte_pos: usize) -> bool {
173 let mut in_math_block = false;
174 let mut current_pos = 0;
175
176 for line in content.lines() {
177 let line_start = current_pos;
178 let line_end = current_pos + line.len();
179
180 if is_math_block_delimiter(line) {
182 if byte_pos >= line_start && byte_pos <= line_end {
183 return true;
185 }
186 in_math_block = !in_math_block;
187 } else if in_math_block && byte_pos >= line_start && byte_pos <= line_end {
188 return true;
190 }
191
192 current_pos = line_end + 1; }
194
195 false
196}
197
198pub fn is_in_inline_math(content: &str, byte_pos: usize) -> bool {
200 for m in INLINE_MATH_REGEX.find_iter(content) {
202 if m.start() <= byte_pos && byte_pos < m.end() {
203 return true;
204 }
205 }
206 false
207}
208
209pub fn is_in_table_cell(ctx: &LintContext, line_num: usize, _col: usize) -> bool {
211 for table_row in ctx.table_rows().iter() {
213 if table_row.line == line_num {
214 return true;
218 }
219 }
220 false
221}
222
223pub fn is_table_line(line: &str) -> bool {
225 let trimmed = line.trim();
226
227 if trimmed
229 .chars()
230 .all(|c| c == '|' || c == '-' || c == ':' || c.is_whitespace())
231 && trimmed.contains('|')
232 && trimmed.contains('-')
233 {
234 return true;
235 }
236
237 if (trimmed.starts_with('|') || trimmed.ends_with('|')) && trimmed.matches('|').count() >= 2 {
239 return true;
240 }
241
242 false
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 #[test]
250 fn test_html_comment_detection() {
251 let content = "Text <!-- comment --> more text";
252 assert!(is_in_html_comment(content, 10)); assert!(!is_in_html_comment(content, 0)); assert!(!is_in_html_comment(content, 25)); }
256
257 #[test]
258 fn test_math_block_detection() {
259 let content = "Text\n$$\nmath content\n$$\nmore text";
260 assert!(is_in_math_block(content, 8)); assert!(is_in_math_block(content, 15)); assert!(!is_in_math_block(content, 0)); assert!(!is_in_math_block(content, 30)); }
265
266 #[test]
267 fn test_inline_math_detection() {
268 let content = "Text $x + y$ and $$a^2 + b^2$$ here";
269 assert!(is_in_inline_math(content, 7)); assert!(is_in_inline_math(content, 20)); assert!(!is_in_inline_math(content, 0)); assert!(!is_in_inline_math(content, 35)); }
274
275 #[test]
276 fn test_table_line_detection() {
277 assert!(is_table_line("| Header | Column |"));
278 assert!(is_table_line("|--------|--------|"));
279 assert!(is_table_line("| Cell 1 | Cell 2 |"));
280 assert!(!is_table_line("Regular text"));
281 assert!(!is_table_line("Just a pipe | here"));
282 }
283
284 #[test]
285 fn test_is_in_front_matter() {
286 let yaml_content = r#"---
288title: "My Post"
289tags: ["test", "example"]
290---
291
292# Content"#;
293
294 assert!(
295 is_in_front_matter(yaml_content, 0),
296 "Line 1 should be in YAML front matter"
297 );
298 assert!(
299 is_in_front_matter(yaml_content, 2),
300 "Line 3 should be in YAML front matter"
301 );
302 assert!(
303 is_in_front_matter(yaml_content, 3),
304 "Line 4 should be in YAML front matter"
305 );
306 assert!(
307 !is_in_front_matter(yaml_content, 4),
308 "Line 5 should NOT be in front matter"
309 );
310
311 let toml_content = r#"+++
313title = "My Post"
314tags = ["test", "example"]
315+++
316
317# Content"#;
318
319 assert!(
320 is_in_front_matter(toml_content, 0),
321 "Line 1 should be in TOML front matter"
322 );
323 assert!(
324 is_in_front_matter(toml_content, 2),
325 "Line 3 should be in TOML front matter"
326 );
327 assert!(
328 is_in_front_matter(toml_content, 3),
329 "Line 4 should be in TOML front matter"
330 );
331 assert!(
332 !is_in_front_matter(toml_content, 4),
333 "Line 5 should NOT be in front matter"
334 );
335
336 let mixed_content = r#"# Content
338
339+++
340title = "Not frontmatter"
341+++
342
343More content"#;
344
345 assert!(
346 !is_in_front_matter(mixed_content, 2),
347 "TOML block not at beginning should NOT be front matter"
348 );
349 assert!(
350 !is_in_front_matter(mixed_content, 3),
351 "TOML block not at beginning should NOT be front matter"
352 );
353 assert!(
354 !is_in_front_matter(mixed_content, 4),
355 "TOML block not at beginning should NOT be front matter"
356 );
357 }
358}