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 mut result = String::new();
110 if has_leading_pipe {
111 result.push('|');
112 }
113
114 for (i, cell) in cell_contents.iter().enumerate() {
115 result.push_str(&format!(" {cell} "));
116 if i < cell_contents.len() - 1 || has_trailing_pipe {
117 result.push('|');
118 }
119 }
120
121 Some(result)
122 }
123}
124
125impl Rule for MD056TableColumnCount {
126 fn name(&self) -> &'static str {
127 "MD056"
128 }
129
130 fn description(&self) -> &'static str {
131 "Table column count should be consistent"
132 }
133
134 fn category(&self) -> RuleCategory {
135 RuleCategory::Table
136 }
137
138 fn should_skip(&self, ctx: &crate::lint_context::LintContext) -> bool {
139 !ctx.likely_has_tables()
141 }
142
143 fn check(&self, ctx: &crate::lint_context::LintContext) -> LintResult {
144 let content = ctx.content;
145 let flavor = ctx.flavor;
146 let mut warnings = Vec::new();
147
148 if content.is_empty() || !content.contains('|') {
150 return Ok(Vec::new());
151 }
152
153 let lines = ctx.raw_lines();
154
155 let table_blocks = &ctx.table_blocks;
157
158 for table_block in table_blocks {
159 let all_line_indices: Vec<usize> = std::iter::once(table_block.header_line)
161 .chain(std::iter::once(table_block.delimiter_line))
162 .chain(table_block.content_lines.iter().copied())
163 .collect();
164
165 let header_content = TableUtils::extract_table_row_content(lines[table_block.header_line], table_block, 0);
167 let expected_count = TableUtils::count_cells_with_flavor(header_content, flavor);
168
169 if expected_count == 0 {
170 continue; }
172
173 for (i, &line_idx) in all_line_indices.iter().enumerate() {
177 let line = lines[line_idx];
178 let row_content = TableUtils::extract_table_row_content(line, table_block, i);
179 let count = TableUtils::count_cells_with_flavor(row_content, flavor);
180
181 if count > 0 && count != expected_count {
182 let (start_line, start_col, end_line, end_col) = calculate_line_range(line_idx + 1, line);
183
184 let fixed_line = self
187 .fix_table_row_content(row_content, expected_count, flavor, table_block, i, line)
188 .unwrap_or_else(|| line.to_string());
189 let row_range = ctx.line_column_byte_range_with_length(line_idx + 1, 1, line.chars().count());
190
191 warnings.push(LintWarning {
192 rule_name: Some(self.name().to_string()),
193 message: format!("Table row has {count} cells, but expected {expected_count}"),
194 line: start_line,
195 column: start_col,
196 end_line,
197 end_column: end_col,
198 severity: Severity::Warning,
199 fix: Some(Fix::new(row_range, fixed_line)),
200 });
201 }
202 }
203 }
204
205 Ok(warnings)
206 }
207
208 fn fix(&self, ctx: &crate::lint_context::LintContext) -> Result<String, LintError> {
209 if self.should_skip(ctx) {
210 return Ok(ctx.content.to_string());
211 }
212 let warnings = self.check(ctx)?;
213 if warnings.is_empty() {
214 return Ok(ctx.content.to_string());
215 }
216 let warnings =
217 crate::utils::fix_utils::filter_warnings_by_inline_config(warnings, ctx.inline_config(), self.name());
218 crate::utils::fix_utils::apply_warning_fixes(ctx.content, &warnings).map_err(LintError::InvalidInput)
219 }
220
221 fn as_any(&self) -> &dyn std::any::Any {
222 self
223 }
224
225 fn from_config(_config: &crate::config::Config) -> Box<dyn Rule>
226 where
227 Self: Sized,
228 {
229 Box::new(MD056TableColumnCount)
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236 use crate::lint_context::LintContext;
237
238 #[test]
239 fn test_valid_table() {
240 let rule = MD056TableColumnCount;
241 let content = "| Header 1 | Header 2 | Header 3 |
242|----------|----------|----------|
243| Cell 1 | Cell 2 | Cell 3 |
244| Cell 4 | Cell 5 | Cell 6 |";
245 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
246 let result = rule.check(&ctx).unwrap();
247
248 assert_eq!(result.len(), 0);
249 }
250
251 #[test]
252 fn test_too_few_columns() {
253 let rule = MD056TableColumnCount;
254 let content = "| Header 1 | Header 2 | Header 3 |
255|----------|----------|----------|
256| Cell 1 | Cell 2 |
257| Cell 4 | Cell 5 | Cell 6 |";
258 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
259 let result = rule.check(&ctx).unwrap();
260
261 assert_eq!(result.len(), 1);
262 assert_eq!(result[0].line, 3);
263 assert!(result[0].message.contains("has 2 cells, but expected 3"));
264 }
265
266 #[test]
267 fn test_too_many_columns() {
268 let rule = MD056TableColumnCount;
269 let content = "| Header 1 | Header 2 |
270|----------|----------|
271| Cell 1 | Cell 2 | Cell 3 | Cell 4 |
272| Cell 5 | Cell 6 |";
273 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
274 let result = rule.check(&ctx).unwrap();
275
276 assert_eq!(result.len(), 1);
277 assert_eq!(result[0].line, 3);
278 assert!(result[0].message.contains("has 4 cells, but expected 2"));
279 }
280
281 #[test]
282 fn test_delimiter_row_mismatch() {
283 let rule = MD056TableColumnCount;
284 let content = "| Header 1 | Header 2 | Header 3 |
285|----------|----------|
286| Cell 1 | Cell 2 | Cell 3 |";
287 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
288 let result = rule.check(&ctx).unwrap();
289
290 assert_eq!(result.len(), 1);
291 assert_eq!(result[0].line, 2);
292 assert!(result[0].message.contains("has 2 cells, but expected 3"));
293 }
294
295 #[test]
296 fn test_fix_too_few_columns() {
297 let rule = MD056TableColumnCount;
298 let content = "| Header 1 | Header 2 | Header 3 |
299|----------|----------|----------|
300| Cell 1 | Cell 2 |
301| Cell 4 | Cell 5 | Cell 6 |";
302 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
303 let fixed = rule.fix(&ctx).unwrap();
304
305 assert!(fixed.contains("| Cell 1 | Cell 2 | |"));
306 }
307
308 #[test]
309 fn test_fix_too_many_columns() {
310 let rule = MD056TableColumnCount;
311 let content = "| Header 1 | Header 2 |
312|----------|----------|
313| Cell 1 | Cell 2 | Cell 3 | Cell 4 |
314| Cell 5 | Cell 6 |";
315 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
316 let fixed = rule.fix(&ctx).unwrap();
317
318 assert!(fixed.contains("| Cell 1 | Cell 2 |"));
319 assert!(!fixed.contains("Cell 3"));
320 assert!(!fixed.contains("Cell 4"));
321 }
322
323 #[test]
324 fn test_no_leading_pipe() {
325 let rule = MD056TableColumnCount;
326 let content = "Header 1 | Header 2 | Header 3 |
327---------|----------|----------|
328Cell 1 | Cell 2 |
329Cell 4 | Cell 5 | Cell 6 |";
330 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
331 let result = rule.check(&ctx).unwrap();
332
333 assert_eq!(result.len(), 1);
334 assert_eq!(result[0].line, 3);
335 }
336
337 #[test]
338 fn test_no_trailing_pipe() {
339 let rule = MD056TableColumnCount;
340 let content = "| Header 1 | Header 2 | Header 3
341|----------|----------|----------
342| Cell 1 | Cell 2
343| Cell 4 | Cell 5 | Cell 6";
344 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
345 let result = rule.check(&ctx).unwrap();
346
347 assert_eq!(result.len(), 1);
348 assert_eq!(result[0].line, 3);
349 }
350
351 #[test]
352 fn test_no_pipes_at_all() {
353 let rule = MD056TableColumnCount;
354 let content = "This is not a table
355Just regular text
356No pipes here";
357 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
358 let result = rule.check(&ctx).unwrap();
359
360 assert_eq!(result.len(), 0);
361 }
362
363 #[test]
364 fn test_empty_cells() {
365 let rule = MD056TableColumnCount;
366 let content = "| Header 1 | Header 2 | Header 3 |
367|----------|----------|----------|
368| | | |
369| Cell 1 | | Cell 3 |";
370 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
371 let result = rule.check(&ctx).unwrap();
372
373 assert_eq!(result.len(), 0);
374 }
375
376 #[test]
377 fn test_multiple_tables() {
378 let rule = MD056TableColumnCount;
379 let content = "| Table 1 Col 1 | Table 1 Col 2 |
380|----------------|----------------|
381| Data 1 | Data 2 |
382
383Some text in between.
384
385| Table 2 Col 1 | Table 2 Col 2 | Table 2 Col 3 |
386|----------------|----------------|----------------|
387| Data 3 | Data 4 |
388| Data 5 | Data 6 | Data 7 |";
389 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
390 let result = rule.check(&ctx).unwrap();
391
392 assert_eq!(result.len(), 1);
393 assert_eq!(result[0].line, 9);
394 assert!(result[0].message.contains("has 2 cells, but expected 3"));
395 }
396
397 #[test]
398 fn test_table_with_escaped_pipes() {
399 let rule = MD056TableColumnCount;
400
401 let content = "| Command | Description |
403|---------|-------------|
404| `echo \\| grep` | Pipe example |
405| `ls` | List files |";
406 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
407 let result = rule.check(&ctx).unwrap();
408 assert_eq!(result.len(), 0, "escaped pipe \\| should not split cells");
409
410 let content_double = "| Command | Description |
412|---------|-------------|
413| `echo \\\\| grep` | Pipe example |
414| `ls` | List files |";
415 let ctx2 = LintContext::new(content_double, crate::config::MarkdownFlavor::Standard, None);
416 let result2 = rule.check(&ctx2).unwrap();
417 assert_eq!(result2.len(), 0, "pipes inside code spans should not split cells");
419 }
420
421 #[test]
422 fn test_empty_content() {
423 let rule = MD056TableColumnCount;
424 let content = "";
425 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
426 let result = rule.check(&ctx).unwrap();
427
428 assert_eq!(result.len(), 0);
429 }
430
431 #[test]
432 fn test_code_block_with_table() {
433 let rule = MD056TableColumnCount;
434 let content = "```
435| This | Is | Code |
436|------|----|----|
437| Not | A | Table |
438```
439
440| Real | Table |
441|------|-------|
442| Data | Here |";
443 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
444 let result = rule.check(&ctx).unwrap();
445
446 assert_eq!(result.len(), 0);
448 }
449
450 #[test]
451 fn test_fix_preserves_pipe_style() {
452 let rule = MD056TableColumnCount;
453 let content = "| Header 1 | Header 2 | Header 3
455|----------|----------|----------
456| Cell 1 | Cell 2";
457 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
458 let fixed = rule.fix(&ctx).unwrap();
459
460 let lines: Vec<&str> = fixed.lines().collect();
461 assert!(!lines[2].ends_with('|'));
462 assert!(lines[2].contains("Cell 1"));
463 assert!(lines[2].contains("Cell 2"));
464 }
465
466 #[test]
467 fn test_single_column_table() {
468 let rule = MD056TableColumnCount;
469 let content = "| Header |
470|---------|
471| Cell 1 |
472| Cell 2 |";
473 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
474 let result = rule.check(&ctx).unwrap();
475
476 assert_eq!(result.len(), 0);
477 }
478
479 #[test]
480 fn test_complex_delimiter_row() {
481 let rule = MD056TableColumnCount;
482 let content = "| Left | Center | Right |
483|:-----|:------:|------:|
484| L | C | R |
485| Left | Center |";
486 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
487 let result = rule.check(&ctx).unwrap();
488
489 assert_eq!(result.len(), 1);
490 assert_eq!(result[0].line, 4);
491 }
492
493 #[test]
494 fn test_unicode_content() {
495 let rule = MD056TableColumnCount;
496 let content = "| 名前 | 年齢 | 都市 |
497|------|------|------|
498| 田中 | 25 | 東京 |
499| 佐藤 | 30 |";
500 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
501 let result = rule.check(&ctx).unwrap();
502
503 assert_eq!(result.len(), 1);
504 assert_eq!(result[0].line, 4);
505 }
506
507 #[test]
508 fn test_very_long_cells() {
509 let rule = MD056TableColumnCount;
510 let content = "| Short | Very very very very very very very very very very long header | Another |
511|-------|--------------------------------------------------------------|---------|
512| Data | This is an extremely long cell content that goes on and on |";
513 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
514 let result = rule.check(&ctx).unwrap();
515
516 assert_eq!(result.len(), 1);
517 assert!(result[0].message.contains("has 2 cells, but expected 3"));
518 }
519
520 #[test]
521 fn test_fix_with_newline_ending() {
522 let rule = MD056TableColumnCount;
523 let content = "| A | B | C |
524|---|---|---|
525| 1 | 2 |
526";
527 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
528 let fixed = rule.fix(&ctx).unwrap();
529
530 assert!(fixed.ends_with('\n'));
531 assert!(fixed.contains("| 1 | 2 | |"));
532 }
533
534 #[test]
535 fn test_fix_without_newline_ending() {
536 let rule = MD056TableColumnCount;
537 let content = "| A | B | C |
538|---|---|---|
539| 1 | 2 |";
540 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
541 let fixed = rule.fix(&ctx).unwrap();
542
543 assert!(!fixed.ends_with('\n'));
544 assert!(fixed.contains("| 1 | 2 | |"));
545 }
546
547 #[test]
548 fn test_blockquote_table_column_mismatch() {
549 let rule = MD056TableColumnCount;
550 let content = "> | Header 1 | Header 2 | Header 3 |
551> |----------|----------|----------|
552> | Cell 1 | Cell 2 |";
553 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
554 let result = rule.check(&ctx).unwrap();
555
556 assert_eq!(result.len(), 1);
557 assert_eq!(result[0].line, 3);
558 assert!(result[0].message.contains("has 2 cells, but expected 3"));
559 }
560
561 #[test]
562 fn test_fix_blockquote_table_preserves_prefix() {
563 let rule = MD056TableColumnCount;
564 let content = "> | Header 1 | Header 2 | Header 3 |
565> |----------|----------|----------|
566> | Cell 1 | Cell 2 |
567> | Cell 4 | Cell 5 | Cell 6 |";
568 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
569 let fixed = rule.fix(&ctx).unwrap();
570
571 for line in fixed.lines() {
573 assert!(line.starts_with("> "), "Line should preserve blockquote prefix: {line}");
574 }
575 assert!(fixed.contains("> | Cell 1 | Cell 2 | |"));
577 }
578
579 #[test]
580 fn test_fix_nested_blockquote_table() {
581 let rule = MD056TableColumnCount;
582 let content = ">> | A | B | C |
583>> |---|---|---|
584>> | 1 | 2 |";
585 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
586 let fixed = rule.fix(&ctx).unwrap();
587
588 for line in fixed.lines() {
590 assert!(
591 line.starts_with(">> "),
592 "Line should preserve nested blockquote prefix: {line}"
593 );
594 }
595 assert!(fixed.contains(">> | 1 | 2 | |"));
596 }
597
598 #[test]
599 fn test_blockquote_table_too_many_columns() {
600 let rule = MD056TableColumnCount;
601 let content = "> | A | B |
602> |---|---|
603> | 1 | 2 | 3 | 4 |";
604 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
605 let fixed = rule.fix(&ctx).unwrap();
606
607 assert!(fixed.lines().nth(2).unwrap().starts_with("> "));
609 assert!(fixed.contains("> | 1 | 2 |"));
610 assert!(!fixed.contains("| 3 |"));
611 }
612
613 fn assert_fix_roundtrip(content: &str) {
616 let rule = MD056TableColumnCount;
617 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
618 let fixed = rule.fix(&ctx).unwrap();
619 let ctx2 = LintContext::new(&fixed, crate::config::MarkdownFlavor::Standard, None);
620 let remaining = rule.check(&ctx2).unwrap();
621 assert!(
622 remaining.is_empty(),
623 "After fix(), check() should find 0 violations.\nOriginal: {content:?}\nFixed: {fixed:?}\nRemaining: {remaining:?}"
624 );
625 }
626
627 #[test]
628 fn test_roundtrip_too_few_columns() {
629 assert_fix_roundtrip("| A | B | C |\n|---|---|---|\n| 1 | 2 |");
630 }
631
632 #[test]
633 fn test_roundtrip_too_many_columns() {
634 assert_fix_roundtrip("| A | B |\n|---|---|\n| 1 | 2 | 3 | 4 |");
635 }
636
637 #[test]
638 fn test_roundtrip_with_trailing_newline() {
639 assert_fix_roundtrip("| A | B | C |\n|---|---|---|\n| 1 | 2 |\n");
640 }
641
642 #[test]
643 fn test_roundtrip_blockquote_table() {
644 assert_fix_roundtrip("> | A | B | C |\n> |---|---|---|\n> | 1 | 2 |");
645 }
646
647 #[test]
648 fn test_roundtrip_clean_table() {
649 assert_fix_roundtrip("| A | B |\n|---|---|\n| 1 | 2 |");
650 }
651
652 #[test]
653 fn test_roundtrip_multiple_tables() {
654 assert_fix_roundtrip("| A | B |\n|---|---|\n| 1 | 2 |\n\nText\n\n| C | D | E |\n|---|---|---|\n| 3 | 4 |");
655 }
656
657 #[test]
673 fn md056_pandoc_grid_tables_not_flagged() {
674 let rule = MD056TableColumnCount;
675 let content = "\
676+---+---+
677| a | b |
678+===+===+
679| 1 | 2 |
680+---+---+
681";
682 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Pandoc, None);
685 let result = rule.check(&ctx).unwrap();
686 assert!(
687 result.is_empty(),
688 "MD056 should not flag Pandoc grid tables (excluded by table_blocks): {result:?}"
689 );
690
691 let ctx_std = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
693 let result_std = rule.check(&ctx_std).unwrap();
694 assert!(
695 result_std.is_empty(),
696 "MD056 should not flag grid-table-like content under Standard either: {result_std:?}"
697 );
698 }
699
700 #[test]
701 fn md056_pandoc_multi_line_tables_not_flagged() {
702 let rule = MD056TableColumnCount;
703 let content = "\
704--------- ----------- ------
705Header 1 Header 2 Header 3
706--------- ----------- ------
707Cell 1 Cell 2 Cell 3
708--------- ----------- ------
709";
710 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Pandoc, None);
711 let result = rule.check(&ctx).unwrap();
712 assert!(
713 result.is_empty(),
714 "MD056 should not flag Pandoc multi-line tables: {result:?}"
715 );
716
717 let ctx_std = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
718 let result_std = rule.check(&ctx_std).unwrap();
719 assert!(
720 result_std.is_empty(),
721 "MD056 should not flag multi-line table content under Standard: {result_std:?}"
722 );
723 }
724
725 #[test]
726 fn md056_pandoc_line_blocks_not_flagged() {
727 let rule = MD056TableColumnCount;
728 let content = "| First line\n| Second line\n";
731 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Pandoc, None);
732 let result = rule.check(&ctx).unwrap();
733 assert!(
734 result.is_empty(),
735 "MD056 should not treat Pandoc line blocks as tables: {result:?}"
736 );
737
738 let ctx_std = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
739 let result_std = rule.check(&ctx_std).unwrap();
740 assert!(
741 result_std.is_empty(),
742 "MD056 should not treat line-block-like content as tables under Standard: {result_std:?}"
743 );
744 }
745
746 #[test]
747 fn md056_pandoc_pipe_table_captions_not_flagged() {
748 let rule = MD056TableColumnCount;
749 let content = "\
751| H1 | H2 |
752|----|-----|
753| a | b |
754
755: My table caption
756";
757 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Pandoc, None);
758 let result = rule.check(&ctx).unwrap();
759 assert!(
760 result.is_empty(),
761 "MD056 should not flag the pipe-table caption line: {result:?}"
762 );
763
764 let ctx_std = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
766 let result_std = rule.check(&ctx_std).unwrap();
767 assert!(
768 result_std.is_empty(),
769 "MD056 already-valid table with caption should have no warnings under Standard: {result_std:?}"
770 );
771 }
772}