1use crate::rule::{Fix, LintError, LintResult, LintWarning, Rule, RuleCategory, Severity};
2use crate::utils::range_utils::calculate_line_range;
3use crate::utils::table_utils::TableUtils;
4
5#[derive(Debug, Clone)]
10pub struct MD056TableColumnCount;
11
12impl Default for MD056TableColumnCount {
13 fn default() -> Self {
14 MD056TableColumnCount
15 }
16}
17
18impl MD056TableColumnCount {
19 fn fix_table_row_content(
21 &self,
22 row_content: &str,
23 expected_count: usize,
24 flavor: crate::config::MarkdownFlavor,
25 table_block: &crate::utils::table_utils::TableBlock,
26 line_index: usize,
27 original_line: &str,
28 ) -> Option<String> {
29 let current_count = TableUtils::count_cells_with_flavor(row_content, flavor);
30
31 if current_count == expected_count || current_count == 0 {
32 return None;
33 }
34
35 let fixed = self.fix_row_by_truncation(row_content, expected_count, flavor)?;
36 Some(self.restore_prefixes(&fixed, table_block, line_index, original_line))
37 }
38
39 fn restore_prefixes(
41 &self,
42 fixed_content: &str,
43 table_block: &crate::utils::table_utils::TableBlock,
44 line_index: usize,
45 original_line: &str,
46 ) -> String {
47 let (blockquote_prefix, _) = TableUtils::extract_blockquote_prefix(original_line);
49
50 if let Some(ref list_ctx) = table_block.list_context {
52 if line_index == 0 {
53 format!("{blockquote_prefix}{}{fixed_content}", list_ctx.list_prefix)
55 } else {
56 let indent = " ".repeat(list_ctx.content_indent);
58 format!("{blockquote_prefix}{indent}{fixed_content}")
59 }
60 } else {
61 if blockquote_prefix.is_empty() {
63 fixed_content.to_string()
64 } else {
65 format!("{blockquote_prefix}{fixed_content}")
66 }
67 }
68 }
69
70 fn fix_row_by_truncation(
72 &self,
73 row: &str,
74 expected_count: usize,
75 flavor: crate::config::MarkdownFlavor,
76 ) -> Option<String> {
77 let current_count = TableUtils::count_cells_with_flavor(row, flavor);
78
79 if current_count == expected_count || current_count == 0 {
80 return None;
81 }
82
83 let trimmed = row.trim();
84 let has_leading_pipe = trimmed.starts_with('|');
85 let has_trailing_pipe = trimmed.ends_with('|');
86
87 let cells = TableUtils::split_table_row_with_flavor(trimmed, flavor);
89 let mut cell_contents: Vec<&str> = cells.iter().map(|c| c.trim()).collect();
90
91 match current_count.cmp(&expected_count) {
93 std::cmp::Ordering::Greater => {
94 cell_contents.truncate(expected_count);
96 }
97 std::cmp::Ordering::Less => {
98 while cell_contents.len() < expected_count {
100 cell_contents.push("");
101 }
102 }
103 std::cmp::Ordering::Equal => {
104 }
106 }
107
108 let close_row = has_trailing_pipe || cell_contents.last().is_some_and(|cell| cell.is_empty());
111
112 let mut result = String::new();
114 if has_leading_pipe {
115 result.push('|');
116 }
117
118 for (i, cell) in cell_contents.iter().enumerate() {
119 result.push_str(&format!(" {cell} "));
120 if i < cell_contents.len() - 1 || close_row {
121 result.push('|');
122 }
123 }
124
125 Some(result)
126 }
127}
128
129impl Rule for MD056TableColumnCount {
130 fn name(&self) -> &'static str {
131 "MD056"
132 }
133
134 fn description(&self) -> &'static str {
135 "Table column count should be consistent"
136 }
137
138 fn category(&self) -> RuleCategory {
139 RuleCategory::Table
140 }
141
142 fn should_skip(&self, ctx: &crate::lint_context::LintContext) -> bool {
143 !ctx.likely_has_tables()
145 }
146
147 fn check(&self, ctx: &crate::lint_context::LintContext) -> LintResult {
148 let content = ctx.content;
149 let flavor = ctx.flavor;
150 let mut warnings = Vec::new();
151
152 if content.is_empty() || !content.contains('|') {
154 return Ok(Vec::new());
155 }
156
157 let lines = ctx.raw_lines();
158
159 let table_blocks = &ctx.table_blocks;
161
162 for table_block in table_blocks {
163 let all_line_indices: Vec<usize> = std::iter::once(table_block.header_line)
165 .chain(std::iter::once(table_block.delimiter_line))
166 .chain(table_block.content_lines.iter().copied())
167 .collect();
168
169 let header_content = TableUtils::extract_table_row_content(lines[table_block.header_line], table_block, 0);
171 let expected_count = TableUtils::count_cells_with_flavor(header_content, flavor);
172
173 if expected_count == 0 {
174 continue; }
176
177 for (i, &line_idx) in all_line_indices.iter().enumerate() {
181 let line = lines[line_idx];
182 let row_content = TableUtils::extract_table_row_content(line, table_block, i);
183 let count = TableUtils::count_cells_with_flavor(row_content, flavor);
184
185 if count > 0 && count != expected_count {
186 let (start_line, start_col, end_line, end_col) = calculate_line_range(line_idx + 1, line);
187
188 let fixed_line = self
191 .fix_table_row_content(row_content, expected_count, flavor, table_block, i, line)
192 .unwrap_or_else(|| line.to_string());
193 let row_range = ctx.line_column_byte_range_with_length(line_idx + 1, 1, line.chars().count());
194
195 warnings.push(LintWarning {
196 rule_name: Some(self.name().to_string()),
197 message: format!("Table row has {count} cells, but expected {expected_count}"),
198 line: start_line,
199 column: start_col,
200 end_line,
201 end_column: end_col,
202 severity: Severity::Warning,
203 fix: Some(Fix::new(row_range, fixed_line)),
204 });
205 }
206 }
207 }
208
209 Ok(warnings)
210 }
211
212 fn fix(&self, ctx: &crate::lint_context::LintContext) -> Result<String, LintError> {
213 if self.should_skip(ctx) {
214 return Ok(ctx.content.to_string());
215 }
216 let warnings = self.check(ctx)?;
217 if warnings.is_empty() {
218 return Ok(ctx.content.to_string());
219 }
220 let warnings =
221 crate::utils::fix_utils::filter_warnings_by_inline_config(warnings, ctx.inline_config(), self.name());
222 crate::utils::fix_utils::apply_warning_fixes(ctx.content, &warnings).map_err(LintError::InvalidInput)
223 }
224
225 fn as_any(&self) -> &dyn std::any::Any {
226 self
227 }
228
229 fn from_config(_config: &crate::config::Config) -> Box<dyn Rule>
230 where
231 Self: Sized,
232 {
233 Box::new(MD056TableColumnCount)
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240 use crate::lint_context::LintContext;
241
242 #[test]
243 fn test_valid_table() {
244 let rule = MD056TableColumnCount;
245 let content = "| Header 1 | Header 2 | Header 3 |
246|----------|----------|----------|
247| Cell 1 | Cell 2 | Cell 3 |
248| Cell 4 | Cell 5 | Cell 6 |";
249 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
250 let result = rule.check(&ctx).unwrap();
251
252 assert_eq!(result.len(), 0);
253 }
254
255 #[test]
256 fn test_too_few_columns() {
257 let rule = MD056TableColumnCount;
258 let content = "| Header 1 | Header 2 | Header 3 |
259|----------|----------|----------|
260| Cell 1 | Cell 2 |
261| Cell 4 | Cell 5 | Cell 6 |";
262 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
263 let result = rule.check(&ctx).unwrap();
264
265 assert_eq!(result.len(), 1);
266 assert_eq!(result[0].line, 3);
267 assert!(result[0].message.contains("has 2 cells, but expected 3"));
268 }
269
270 #[test]
271 fn test_too_many_columns() {
272 let rule = MD056TableColumnCount;
273 let content = "| Header 1 | Header 2 |
274|----------|----------|
275| Cell 1 | Cell 2 | Cell 3 | Cell 4 |
276| Cell 5 | Cell 6 |";
277 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
278 let result = rule.check(&ctx).unwrap();
279
280 assert_eq!(result.len(), 1);
281 assert_eq!(result[0].line, 3);
282 assert!(result[0].message.contains("has 4 cells, but expected 2"));
283 }
284
285 #[test]
286 fn test_delimiter_row_mismatch() {
287 let rule = MD056TableColumnCount;
288 let content = "| Header 1 | Header 2 | Header 3 |
289|----------|----------|
290| Cell 1 | Cell 2 | Cell 3 |";
291 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
292 let result = rule.check(&ctx).unwrap();
293
294 assert_eq!(result.len(), 1);
295 assert_eq!(result[0].line, 2);
296 assert!(result[0].message.contains("has 2 cells, but expected 3"));
297 }
298
299 #[test]
300 fn test_fix_too_few_columns() {
301 let rule = MD056TableColumnCount;
302 let content = "| Header 1 | Header 2 | Header 3 |
303|----------|----------|----------|
304| Cell 1 | Cell 2 |
305| Cell 4 | Cell 5 | Cell 6 |";
306 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
307 let fixed = rule.fix(&ctx).unwrap();
308
309 assert!(fixed.contains("| Cell 1 | Cell 2 | |"));
310 }
311
312 #[test]
313 fn test_fix_too_many_columns() {
314 let rule = MD056TableColumnCount;
315 let content = "| Header 1 | Header 2 |
316|----------|----------|
317| Cell 1 | Cell 2 | Cell 3 | Cell 4 |
318| Cell 5 | Cell 6 |";
319 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
320 let fixed = rule.fix(&ctx).unwrap();
321
322 assert!(fixed.contains("| Cell 1 | Cell 2 |"));
323 assert!(!fixed.contains("Cell 3"));
324 assert!(!fixed.contains("Cell 4"));
325 }
326
327 #[test]
328 fn test_no_leading_pipe() {
329 let rule = MD056TableColumnCount;
330 let content = "Header 1 | Header 2 | Header 3 |
331---------|----------|----------|
332Cell 1 | Cell 2 |
333Cell 4 | Cell 5 | Cell 6 |";
334 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
335 let result = rule.check(&ctx).unwrap();
336
337 assert_eq!(result.len(), 1);
338 assert_eq!(result[0].line, 3);
339 }
340
341 #[test]
342 fn test_no_trailing_pipe() {
343 let rule = MD056TableColumnCount;
344 let content = "| Header 1 | Header 2 | Header 3
345|----------|----------|----------
346| Cell 1 | Cell 2
347| Cell 4 | Cell 5 | Cell 6";
348 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
349 let result = rule.check(&ctx).unwrap();
350
351 assert_eq!(result.len(), 1);
352 assert_eq!(result[0].line, 3);
353 }
354
355 #[test]
356 fn test_no_pipes_at_all() {
357 let rule = MD056TableColumnCount;
358 let content = "This is not a table
359Just regular text
360No pipes here";
361 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
362 let result = rule.check(&ctx).unwrap();
363
364 assert_eq!(result.len(), 0);
365 }
366
367 #[test]
368 fn test_empty_cells() {
369 let rule = MD056TableColumnCount;
370 let content = "| Header 1 | Header 2 | Header 3 |
371|----------|----------|----------|
372| | | |
373| Cell 1 | | Cell 3 |";
374 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
375 let result = rule.check(&ctx).unwrap();
376
377 assert_eq!(result.len(), 0);
378 }
379
380 #[test]
381 fn test_multiple_tables() {
382 let rule = MD056TableColumnCount;
383 let content = "| Table 1 Col 1 | Table 1 Col 2 |
384|----------------|----------------|
385| Data 1 | Data 2 |
386
387Some text in between.
388
389| Table 2 Col 1 | Table 2 Col 2 | Table 2 Col 3 |
390|----------------|----------------|----------------|
391| Data 3 | Data 4 |
392| Data 5 | Data 6 | Data 7 |";
393 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
394 let result = rule.check(&ctx).unwrap();
395
396 assert_eq!(result.len(), 1);
397 assert_eq!(result[0].line, 9);
398 assert!(result[0].message.contains("has 2 cells, but expected 3"));
399 }
400
401 #[test]
402 fn test_table_with_escaped_pipes() {
403 let rule = MD056TableColumnCount;
404
405 let content = "| Command | Description |
407|---------|-------------|
408| `echo \\| grep` | Pipe example |
409| `ls` | List files |";
410 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
411 let result = rule.check(&ctx).unwrap();
412 assert_eq!(result.len(), 0, "escaped pipe \\| should not split cells");
413
414 let content_double = "| Command | Description |
416|---------|-------------|
417| `echo \\\\| grep` | Pipe example |
418| `ls` | List files |";
419 let ctx2 = LintContext::new(content_double, crate::config::MarkdownFlavor::Standard, None);
420 let result2 = rule.check(&ctx2).unwrap();
421 assert_eq!(result2.len(), 0, "pipes inside code spans should not split cells");
423 }
424
425 #[test]
429 fn padding_a_row_without_a_closing_pipe_closes_it() {
430 let rule = MD056TableColumnCount;
431 for (content, expected) in [
432 ("a | b | c\n--|--|--\nx | y\n", "a | b | c\n--|--|--\n x | y | |\n"),
433 ("| | |\n| --- | --- |\n`|`\n", "| | |\n| --- | --- |\n `|` | |\n"),
434 (
435 "| a | b | c |\n|--|--|--|\n| x | y |\n",
436 "| a | b | c |\n|--|--|--|\n| x | y | |\n",
437 ),
438 ] {
439 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
440 let fixed = rule.fix(&ctx).unwrap();
441 assert_eq!(fixed, expected, "{content:?}");
442 let ctx = LintContext::new(&fixed, crate::config::MarkdownFlavor::Standard, None);
443 assert_eq!(rule.fix(&ctx).unwrap(), fixed, "second pass over {fixed:?}");
444 }
445 }
446
447 #[test]
448 fn test_empty_content() {
449 let rule = MD056TableColumnCount;
450 let content = "";
451 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
452 let result = rule.check(&ctx).unwrap();
453
454 assert_eq!(result.len(), 0);
455 }
456
457 #[test]
458 fn test_code_block_with_table() {
459 let rule = MD056TableColumnCount;
460 let content = "```
461| This | Is | Code |
462|------|----|----|
463| Not | A | Table |
464```
465
466| Real | Table |
467|------|-------|
468| Data | Here |";
469 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
470 let result = rule.check(&ctx).unwrap();
471
472 assert_eq!(result.len(), 0);
474 }
475
476 #[test]
477 fn test_fix_preserves_pipe_style() {
478 let rule = MD056TableColumnCount;
479 let content = "| Header 1 | Header 2
481|----------|----------
482| Cell 1 | Cell 2 | Cell 3";
483 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
484 let fixed = rule.fix(&ctx).unwrap();
485
486 let lines: Vec<&str> = fixed.lines().collect();
487 assert_eq!(lines[2], "| Cell 1 | Cell 2 ");
488 }
489
490 #[test]
491 fn test_single_column_table() {
492 let rule = MD056TableColumnCount;
493 let content = "| Header |
494|---------|
495| Cell 1 |
496| Cell 2 |";
497 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
498 let result = rule.check(&ctx).unwrap();
499
500 assert_eq!(result.len(), 0);
501 }
502
503 #[test]
504 fn test_complex_delimiter_row() {
505 let rule = MD056TableColumnCount;
506 let content = "| Left | Center | Right |
507|:-----|:------:|------:|
508| L | C | R |
509| Left | Center |";
510 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
511 let result = rule.check(&ctx).unwrap();
512
513 assert_eq!(result.len(), 1);
514 assert_eq!(result[0].line, 4);
515 }
516
517 #[test]
518 fn test_unicode_content() {
519 let rule = MD056TableColumnCount;
520 let content = "| 名前 | 年齢 | 都市 |
521|------|------|------|
522| 田中 | 25 | 東京 |
523| 佐藤 | 30 |";
524 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
525 let result = rule.check(&ctx).unwrap();
526
527 assert_eq!(result.len(), 1);
528 assert_eq!(result[0].line, 4);
529 }
530
531 #[test]
532 fn test_very_long_cells() {
533 let rule = MD056TableColumnCount;
534 let content = "| Short | Very very very very very very very very very very long header | Another |
535|-------|--------------------------------------------------------------|---------|
536| Data | This is an extremely long cell content that goes on and on |";
537 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
538 let result = rule.check(&ctx).unwrap();
539
540 assert_eq!(result.len(), 1);
541 assert!(result[0].message.contains("has 2 cells, but expected 3"));
542 }
543
544 #[test]
545 fn test_fix_with_newline_ending() {
546 let rule = MD056TableColumnCount;
547 let content = "| A | B | C |
548|---|---|---|
549| 1 | 2 |
550";
551 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
552 let fixed = rule.fix(&ctx).unwrap();
553
554 assert!(fixed.ends_with('\n'));
555 assert!(fixed.contains("| 1 | 2 | |"));
556 }
557
558 #[test]
559 fn test_fix_without_newline_ending() {
560 let rule = MD056TableColumnCount;
561 let content = "| A | B | C |
562|---|---|---|
563| 1 | 2 |";
564 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
565 let fixed = rule.fix(&ctx).unwrap();
566
567 assert!(!fixed.ends_with('\n'));
568 assert!(fixed.contains("| 1 | 2 | |"));
569 }
570
571 #[test]
572 fn test_blockquote_table_column_mismatch() {
573 let rule = MD056TableColumnCount;
574 let content = "> | Header 1 | Header 2 | Header 3 |
575> |----------|----------|----------|
576> | Cell 1 | Cell 2 |";
577 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
578 let result = rule.check(&ctx).unwrap();
579
580 assert_eq!(result.len(), 1);
581 assert_eq!(result[0].line, 3);
582 assert!(result[0].message.contains("has 2 cells, but expected 3"));
583 }
584
585 #[test]
586 fn test_fix_blockquote_table_preserves_prefix() {
587 let rule = MD056TableColumnCount;
588 let content = "> | Header 1 | Header 2 | Header 3 |
589> |----------|----------|----------|
590> | Cell 1 | Cell 2 |
591> | Cell 4 | Cell 5 | Cell 6 |";
592 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
593 let fixed = rule.fix(&ctx).unwrap();
594
595 for line in fixed.lines() {
597 assert!(line.starts_with("> "), "Line should preserve blockquote prefix: {line}");
598 }
599 assert!(fixed.contains("> | Cell 1 | Cell 2 | |"));
601 }
602
603 #[test]
604 fn test_fix_nested_blockquote_table() {
605 let rule = MD056TableColumnCount;
606 let content = ">> | A | B | C |
607>> |---|---|---|
608>> | 1 | 2 |";
609 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
610 let fixed = rule.fix(&ctx).unwrap();
611
612 for line in fixed.lines() {
614 assert!(
615 line.starts_with(">> "),
616 "Line should preserve nested blockquote prefix: {line}"
617 );
618 }
619 assert!(fixed.contains(">> | 1 | 2 | |"));
620 }
621
622 #[test]
623 fn test_blockquote_table_too_many_columns() {
624 let rule = MD056TableColumnCount;
625 let content = "> | A | B |
626> |---|---|
627> | 1 | 2 | 3 | 4 |";
628 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
629 let fixed = rule.fix(&ctx).unwrap();
630
631 assert!(fixed.lines().nth(2).unwrap().starts_with("> "));
633 assert!(fixed.contains("> | 1 | 2 |"));
634 assert!(!fixed.contains("| 3 |"));
635 }
636
637 fn assert_fix_roundtrip(content: &str) {
640 let rule = MD056TableColumnCount;
641 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
642 let fixed = rule.fix(&ctx).unwrap();
643 let ctx2 = LintContext::new(&fixed, crate::config::MarkdownFlavor::Standard, None);
644 let remaining = rule.check(&ctx2).unwrap();
645 assert!(
646 remaining.is_empty(),
647 "After fix(), check() should find 0 violations.\nOriginal: {content:?}\nFixed: {fixed:?}\nRemaining: {remaining:?}"
648 );
649 }
650
651 #[test]
652 fn test_roundtrip_too_few_columns() {
653 assert_fix_roundtrip("| A | B | C |\n|---|---|---|\n| 1 | 2 |");
654 }
655
656 #[test]
657 fn test_roundtrip_too_many_columns() {
658 assert_fix_roundtrip("| A | B |\n|---|---|\n| 1 | 2 | 3 | 4 |");
659 }
660
661 #[test]
662 fn test_roundtrip_with_trailing_newline() {
663 assert_fix_roundtrip("| A | B | C |\n|---|---|---|\n| 1 | 2 |\n");
664 }
665
666 #[test]
667 fn test_roundtrip_blockquote_table() {
668 assert_fix_roundtrip("> | A | B | C |\n> |---|---|---|\n> | 1 | 2 |");
669 }
670
671 #[test]
672 fn test_roundtrip_clean_table() {
673 assert_fix_roundtrip("| A | B |\n|---|---|\n| 1 | 2 |");
674 }
675
676 #[test]
677 fn test_roundtrip_multiple_tables() {
678 assert_fix_roundtrip("| A | B |\n|---|---|\n| 1 | 2 |\n\nText\n\n| C | D | E |\n|---|---|---|\n| 3 | 4 |");
679 }
680
681 #[test]
697 fn md056_pandoc_grid_tables_not_flagged() {
698 let rule = MD056TableColumnCount;
699 let content = "\
700+---+---+
701| a | b |
702+===+===+
703| 1 | 2 |
704+---+---+
705";
706 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Pandoc, None);
709 let result = rule.check(&ctx).unwrap();
710 assert!(
711 result.is_empty(),
712 "MD056 should not flag Pandoc grid tables (excluded by table_blocks): {result:?}"
713 );
714
715 let ctx_std = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
717 let result_std = rule.check(&ctx_std).unwrap();
718 assert!(
719 result_std.is_empty(),
720 "MD056 should not flag grid-table-like content under Standard either: {result_std:?}"
721 );
722 }
723
724 #[test]
725 fn md056_pandoc_multi_line_tables_not_flagged() {
726 let rule = MD056TableColumnCount;
727 let content = "\
728--------- ----------- ------
729Header 1 Header 2 Header 3
730--------- ----------- ------
731Cell 1 Cell 2 Cell 3
732--------- ----------- ------
733";
734 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Pandoc, None);
735 let result = rule.check(&ctx).unwrap();
736 assert!(
737 result.is_empty(),
738 "MD056 should not flag Pandoc multi-line tables: {result:?}"
739 );
740
741 let ctx_std = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
742 let result_std = rule.check(&ctx_std).unwrap();
743 assert!(
744 result_std.is_empty(),
745 "MD056 should not flag multi-line table content under Standard: {result_std:?}"
746 );
747 }
748
749 #[test]
750 fn md056_pandoc_line_blocks_not_flagged() {
751 let rule = MD056TableColumnCount;
752 let content = "| First line\n| Second line\n";
755 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Pandoc, None);
756 let result = rule.check(&ctx).unwrap();
757 assert!(
758 result.is_empty(),
759 "MD056 should not treat Pandoc line blocks as tables: {result:?}"
760 );
761
762 let ctx_std = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
763 let result_std = rule.check(&ctx_std).unwrap();
764 assert!(
765 result_std.is_empty(),
766 "MD056 should not treat line-block-like content as tables under Standard: {result_std:?}"
767 );
768 }
769
770 #[test]
771 fn md056_pandoc_pipe_table_captions_not_flagged() {
772 let rule = MD056TableColumnCount;
773 let content = "\
775| H1 | H2 |
776|----|-----|
777| a | b |
778
779: My table caption
780";
781 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Pandoc, None);
782 let result = rule.check(&ctx).unwrap();
783 assert!(
784 result.is_empty(),
785 "MD056 should not flag the pipe-table caption line: {result:?}"
786 );
787
788 let ctx_std = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
790 let result_std = rule.check(&ctx_std).unwrap();
791 assert!(
792 result_std.is_empty(),
793 "MD056 already-valid table with caption should have no warnings under Standard: {result_std:?}"
794 );
795 }
796}