rumdl_lib/utils/
skip_context.rs1use crate::lint_context::LintContext;
7use crate::utils::kramdown_utils::is_math_block_delimiter;
8use crate::utils::regex_cache::HTML_COMMENT_PATTERN;
9use lazy_static::lazy_static;
10use regex::Regex;
11
12lazy_static! {
13 static ref INLINE_MATH_REGEX: Regex = Regex::new(r"\$(?:\$)?[^$]+\$(?:\$)?").unwrap();
15}
16
17pub fn is_in_front_matter(content: &str, line_num: usize) -> bool {
19 let lines: Vec<&str> = content.lines().collect();
20
21 if !lines.is_empty() && lines[0] == "---" {
23 for (i, line) in lines.iter().enumerate().skip(1) {
24 if *line == "---" {
25 return line_num <= i;
26 }
27 }
28 }
29
30 if !lines.is_empty() && lines[0] == "+++" {
32 for (i, line) in lines.iter().enumerate().skip(1) {
33 if *line == "+++" {
34 return line_num <= i;
35 }
36 }
37 }
38
39 false
40}
41
42pub fn is_in_skip_context(ctx: &LintContext, byte_pos: usize) -> bool {
44 if ctx.is_in_code_block_or_span(byte_pos) {
46 return true;
47 }
48
49 if is_in_html_comment(ctx.content, byte_pos) {
51 return true;
52 }
53
54 if is_in_math_context(ctx, byte_pos) {
56 return true;
57 }
58
59 if is_in_html_tag(ctx, byte_pos) {
61 return true;
62 }
63
64 false
65}
66
67pub fn is_in_html_comment(content: &str, byte_pos: usize) -> bool {
69 for m in HTML_COMMENT_PATTERN.find_iter(content) {
70 if m.start() <= byte_pos && byte_pos < m.end() {
71 return true;
72 }
73 }
74 false
75}
76
77pub fn is_in_html_tag(ctx: &LintContext, byte_pos: usize) -> bool {
79 for html_tag in ctx.html_tags().iter() {
80 if html_tag.byte_offset <= byte_pos && byte_pos < html_tag.byte_end {
81 return true;
82 }
83 }
84 false
85}
86
87pub fn is_in_math_context(ctx: &LintContext, byte_pos: usize) -> bool {
89 let content = ctx.content;
90
91 if is_in_math_block(content, byte_pos) {
93 return true;
94 }
95
96 if is_in_inline_math(content, byte_pos) {
98 return true;
99 }
100
101 false
102}
103
104pub fn is_in_math_block(content: &str, byte_pos: usize) -> bool {
106 let mut in_math_block = false;
107 let mut current_pos = 0;
108
109 for line in content.lines() {
110 let line_start = current_pos;
111 let line_end = current_pos + line.len();
112
113 if is_math_block_delimiter(line) {
115 if byte_pos >= line_start && byte_pos <= line_end {
116 return true;
118 }
119 in_math_block = !in_math_block;
120 } else if in_math_block && byte_pos >= line_start && byte_pos <= line_end {
121 return true;
123 }
124
125 current_pos = line_end + 1; }
127
128 false
129}
130
131pub fn is_in_inline_math(content: &str, byte_pos: usize) -> bool {
133 for m in INLINE_MATH_REGEX.find_iter(content) {
135 if m.start() <= byte_pos && byte_pos < m.end() {
136 return true;
137 }
138 }
139 false
140}
141
142pub fn is_in_table_cell(ctx: &LintContext, line_num: usize, _col: usize) -> bool {
144 for table_row in ctx.table_rows().iter() {
146 if table_row.line == line_num {
147 return true;
151 }
152 }
153 false
154}
155
156pub fn is_table_line(line: &str) -> bool {
158 let trimmed = line.trim();
159
160 if trimmed
162 .chars()
163 .all(|c| c == '|' || c == '-' || c == ':' || c.is_whitespace())
164 && trimmed.contains('|')
165 && trimmed.contains('-')
166 {
167 return true;
168 }
169
170 if (trimmed.starts_with('|') || trimmed.ends_with('|')) && trimmed.matches('|').count() >= 2 {
172 return true;
173 }
174
175 false
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn test_html_comment_detection() {
184 let content = "Text <!-- comment --> more text";
185 assert!(is_in_html_comment(content, 10)); assert!(!is_in_html_comment(content, 0)); assert!(!is_in_html_comment(content, 25)); }
189
190 #[test]
191 fn test_math_block_detection() {
192 let content = "Text\n$$\nmath content\n$$\nmore text";
193 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)); }
198
199 #[test]
200 fn test_inline_math_detection() {
201 let content = "Text $x + y$ and $$a^2 + b^2$$ here";
202 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)); }
207
208 #[test]
209 fn test_table_line_detection() {
210 assert!(is_table_line("| Header | Column |"));
211 assert!(is_table_line("|--------|--------|"));
212 assert!(is_table_line("| Cell 1 | Cell 2 |"));
213 assert!(!is_table_line("Regular text"));
214 assert!(!is_table_line("Just a pipe | here"));
215 }
216
217 #[test]
218 fn test_is_in_front_matter() {
219 let yaml_content = r#"---
221title: "My Post"
222tags: ["test", "example"]
223---
224
225# Content"#;
226
227 assert!(
228 is_in_front_matter(yaml_content, 0),
229 "Line 1 should be in YAML front matter"
230 );
231 assert!(
232 is_in_front_matter(yaml_content, 2),
233 "Line 3 should be in YAML front matter"
234 );
235 assert!(
236 is_in_front_matter(yaml_content, 3),
237 "Line 4 should be in YAML front matter"
238 );
239 assert!(
240 !is_in_front_matter(yaml_content, 4),
241 "Line 5 should NOT be in front matter"
242 );
243
244 let toml_content = r#"+++
246title = "My Post"
247tags = ["test", "example"]
248+++
249
250# Content"#;
251
252 assert!(
253 is_in_front_matter(toml_content, 0),
254 "Line 1 should be in TOML front matter"
255 );
256 assert!(
257 is_in_front_matter(toml_content, 2),
258 "Line 3 should be in TOML front matter"
259 );
260 assert!(
261 is_in_front_matter(toml_content, 3),
262 "Line 4 should be in TOML front matter"
263 );
264 assert!(
265 !is_in_front_matter(toml_content, 4),
266 "Line 5 should NOT be in front matter"
267 );
268
269 let mixed_content = r#"# Content
271
272+++
273title = "Not frontmatter"
274+++
275
276More content"#;
277
278 assert!(
279 !is_in_front_matter(mixed_content, 2),
280 "TOML block not at beginning should NOT be front matter"
281 );
282 assert!(
283 !is_in_front_matter(mixed_content, 3),
284 "TOML block not at beginning should NOT be front matter"
285 );
286 assert!(
287 !is_in_front_matter(mixed_content, 4),
288 "TOML block not at beginning should NOT be front matter"
289 );
290 }
291}