1use toml;
2
3use crate::rule::{FixCapability, LintError, LintResult, LintWarning, Rule, RuleCategory, Severity};
4use crate::rule_config_serde::RuleConfig;
5use crate::utils::range_utils::calculate_match_range;
6use std::collections::{HashMap, HashSet};
7
8mod md024_config;
9use md024_config::MD024Config;
10
11#[derive(Clone, Debug, Default)]
12pub struct MD024NoDuplicateHeading {
13 config: MD024Config,
14}
15
16impl MD024NoDuplicateHeading {
17 pub fn new(allow_different_nesting: bool, siblings_only: bool) -> Self {
18 Self {
19 config: MD024Config {
20 allow_different_nesting,
21 siblings_only,
22 },
23 }
24 }
25
26 pub fn from_config_struct(config: MD024Config) -> Self {
27 Self { config }
28 }
29}
30
31impl Rule for MD024NoDuplicateHeading {
32 fn name(&self) -> &'static str {
33 "MD024"
34 }
35
36 fn description(&self) -> &'static str {
37 "Multiple headings with the same content"
38 }
39
40 fn fix_capability(&self) -> FixCapability {
41 FixCapability::Unfixable
42 }
43
44 fn check(&self, ctx: &crate::lint_context::LintContext) -> LintResult {
45 if ctx.lines.is_empty() {
47 return Ok(Vec::new());
48 }
49
50 let mut warnings = Vec::new();
51 let mut seen_headings: HashSet<String> = HashSet::new();
52 let mut seen_headings_per_level: HashMap<u8, HashSet<String>> = HashMap::new();
53
54 let mut current_section_path: Vec<(u8, String)> = Vec::new(); let mut seen_siblings: HashMap<String, HashSet<String>> = HashMap::new(); let is_mkdocs = ctx.flavor == crate::config::MarkdownFlavor::MkDocs;
60 let mut in_snippet_section = false;
61
62 for (line_num, line_info) in ctx.lines.iter().enumerate() {
64 if is_mkdocs {
66 if crate::utils::mkdocs_snippets::is_snippet_section_start(line_info.content(ctx.content)) {
67 in_snippet_section = true;
68 continue; } else if crate::utils::mkdocs_snippets::is_snippet_section_end(line_info.content(ctx.content)) {
70 in_snippet_section = false;
71 continue; }
73 }
74
75 if is_mkdocs && in_snippet_section {
77 continue;
78 }
79
80 if let Some(heading) = &line_info.heading {
81 if !heading.is_valid {
83 continue;
84 }
85
86 if heading.text.is_empty() {
88 continue;
89 }
90
91 let heading_key = heading.text.clone();
92 let level = heading.level;
93
94 let text_start_in_line = if let Some(pos) = line_info.content(ctx.content).find(&heading.text) {
96 pos
97 } else {
98 let trimmed = line_info.content(ctx.content).trim_start();
100 let hash_count = trimmed.chars().take_while(|&c| c == '#').count();
101 let after_hashes = &trimmed[hash_count..];
102 let text_start_in_trimmed = after_hashes.find(&heading.text).unwrap_or(0);
103 (line_info.byte_len - trimmed.len()) + hash_count + text_start_in_trimmed
104 };
105
106 let (start_line, start_col, end_line, end_col) = calculate_match_range(
107 line_num + 1,
108 line_info.content(ctx.content),
109 text_start_in_line,
110 heading.text.len(),
111 );
112
113 if self.config.siblings_only {
114 while !current_section_path.is_empty() && current_section_path.last().unwrap().0 >= level {
116 current_section_path.pop();
117 }
118
119 let parent_path = current_section_path
121 .iter()
122 .map(|(_, text)| text.as_str())
123 .collect::<Vec<_>>()
124 .join("/");
125
126 let siblings = seen_siblings.entry(parent_path.clone()).or_default();
128 if siblings.contains(&heading_key) {
129 warnings.push(LintWarning {
130 rule_name: Some(self.name().to_string()),
131 message: format!("Duplicate heading: '{}'.", heading.text),
132 line: start_line,
133 column: start_col,
134 end_line,
135 end_column: end_col,
136 severity: Severity::Error,
137 fix: None,
138 });
139 } else {
140 siblings.insert(heading_key.clone());
141 }
142
143 current_section_path.push((level, heading_key.clone()));
145 } else if self.config.allow_different_nesting {
146 let seen = seen_headings_per_level.entry(level).or_default();
148 if seen.contains(&heading_key) {
149 warnings.push(LintWarning {
150 rule_name: Some(self.name().to_string()),
151 message: format!("Duplicate heading: '{}'.", heading.text),
152 line: start_line,
153 column: start_col,
154 end_line,
155 end_column: end_col,
156 severity: Severity::Error,
157 fix: None,
158 });
159 } else {
160 seen.insert(heading_key.clone());
161 }
162 } else {
163 if seen_headings.contains(&heading_key) {
165 warnings.push(LintWarning {
166 rule_name: Some(self.name().to_string()),
167 message: format!("Duplicate heading: '{}'.", heading.text),
168 line: start_line,
169 column: start_col,
170 end_line,
171 end_column: end_col,
172 severity: Severity::Error,
173 fix: None,
174 });
175 } else {
176 seen_headings.insert(heading_key.clone());
177 }
178 }
179 }
180 }
181
182 Ok(warnings)
183 }
184
185 fn fix(&self, ctx: &crate::lint_context::LintContext) -> Result<String, LintError> {
186 Ok(ctx.content.to_string())
188 }
189
190 fn category(&self) -> RuleCategory {
192 RuleCategory::Heading
193 }
194
195 fn should_skip(&self, ctx: &crate::lint_context::LintContext) -> bool {
197 if !ctx.likely_has_headings() {
199 return true;
200 }
201 ctx.lines.iter().all(|line| line.heading.is_none())
203 }
204
205 fn as_any(&self) -> &dyn std::any::Any {
206 self
207 }
208
209 fn default_config_section(&self) -> Option<(String, toml::Value)> {
210 let default_config = MD024Config::default();
211 let json_value = serde_json::to_value(&default_config).ok()?;
212 let toml_value = crate::rule_config_serde::json_to_toml_value(&json_value)?;
213
214 if let toml::Value::Table(table) = toml_value {
215 if !table.is_empty() {
216 Some((MD024Config::RULE_NAME.to_string(), toml::Value::Table(table)))
217 } else {
218 None
219 }
220 } else {
221 None
222 }
223 }
224
225 fn from_config(config: &crate::config::Config) -> Box<dyn Rule>
226 where
227 Self: Sized,
228 {
229 let rule_config = crate::rule_config_serde::load_rule_config::<MD024Config>(config);
230 Box::new(Self::from_config_struct(rule_config))
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use crate::lint_context::LintContext;
238
239 fn run_test(content: &str, config: MD024Config) -> LintResult {
240 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
241 let rule = MD024NoDuplicateHeading::from_config_struct(config);
242 rule.check(&ctx)
243 }
244
245 fn run_fix_test(content: &str, config: MD024Config) -> Result<String, LintError> {
246 let ctx = LintContext::new(content, crate::config::MarkdownFlavor::Standard, None);
247 let rule = MD024NoDuplicateHeading::from_config_struct(config);
248 rule.fix(&ctx)
249 }
250
251 #[test]
252 fn test_no_duplicate_headings() {
253 let content = r#"# First Heading
254
255Some content here.
256
257## Second Heading
258
259More content.
260
261### Third Heading
262
263Even more content.
264
265## Fourth Heading
266
267Final content."#;
268
269 let config = MD024Config::default();
270 let result = run_test(content, config);
271 assert!(result.is_ok());
272 let warnings = result.unwrap();
273 assert_eq!(warnings.len(), 0);
274 }
275
276 #[test]
277 fn test_duplicate_headings_same_level() {
278 let content = r#"# First Heading
279
280Some content here.
281
282## Second Heading
283
284More content.
285
286## Second Heading
287
288This is a duplicate."#;
289
290 let config = MD024Config::default();
291 let result = run_test(content, config);
292 assert!(result.is_ok());
293 let warnings = result.unwrap();
294 assert_eq!(warnings.len(), 1);
295 assert_eq!(warnings[0].message, "Duplicate heading: 'Second Heading'.");
296 assert_eq!(warnings[0].line, 9);
297 }
298
299 #[test]
300 fn test_duplicate_headings_different_levels_default() {
301 let content = r#"# Main Title
302
303Some content.
304
305## Main Title
306
307This has the same text but different level."#;
308
309 let config = MD024Config {
310 allow_different_nesting: false,
311 siblings_only: false,
312 };
313 let result = run_test(content, config);
314 assert!(result.is_ok());
315 let warnings = result.unwrap();
316 assert_eq!(warnings.len(), 1);
317 assert_eq!(warnings[0].message, "Duplicate heading: 'Main Title'.");
318 assert_eq!(warnings[0].line, 5);
319 }
320
321 #[test]
322 fn test_duplicate_headings_different_levels_allow_different_nesting() {
323 let content = r#"# Main Title
324
325Some content.
326
327## Main Title
328
329This has the same text but different level."#;
330
331 let config = MD024Config {
332 allow_different_nesting: true,
333 siblings_only: false,
334 };
335 let result = run_test(content, config);
336 assert!(result.is_ok());
337 let warnings = result.unwrap();
338 assert_eq!(warnings.len(), 0);
339 }
340
341 #[test]
342 fn test_case_sensitivity() {
343 let content = r#"# First Heading
344
345Some content.
346
347## first heading
348
349Different case.
350
351### FIRST HEADING
352
353All caps."#;
354
355 let config = MD024Config::default();
356 let result = run_test(content, config);
357 assert!(result.is_ok());
358 let warnings = result.unwrap();
359 assert_eq!(warnings.len(), 0);
361 }
362
363 #[test]
364 fn test_headings_with_trailing_punctuation() {
365 let content = r#"# First Heading!
366
367Some content.
368
369## First Heading!
370
371Same with punctuation.
372
373### First Heading
374
375Without punctuation."#;
376
377 let config = MD024Config {
378 allow_different_nesting: false,
379 siblings_only: false,
380 };
381 let result = run_test(content, config);
382 assert!(result.is_ok());
383 let warnings = result.unwrap();
384 assert_eq!(warnings.len(), 1);
385 assert_eq!(warnings[0].message, "Duplicate heading: 'First Heading!'.");
386 }
387
388 #[test]
389 fn test_headings_with_inline_formatting() {
390 let content = r#"# **Bold Heading**
391
392Some content.
393
394## *Italic Heading*
395
396More content.
397
398### **Bold Heading**
399
400Duplicate with same formatting.
401
402#### `Code Heading`
403
404Code formatted.
405
406##### `Code Heading`
407
408Duplicate code formatted."#;
409
410 let config = MD024Config {
411 allow_different_nesting: false,
412 siblings_only: false,
413 };
414 let result = run_test(content, config);
415 assert!(result.is_ok());
416 let warnings = result.unwrap();
417 assert_eq!(warnings.len(), 2);
418 assert_eq!(warnings[0].message, "Duplicate heading: '**Bold Heading**'.");
419 assert_eq!(warnings[1].message, "Duplicate heading: '`Code Heading`'.");
420 }
421
422 #[test]
423 fn test_headings_in_different_sections() {
424 let content = r#"# Section One
425
426## Subsection
427
428Some content.
429
430# Section Two
431
432## Subsection
433
434Same subsection name in different section."#;
435
436 let config = MD024Config {
437 allow_different_nesting: false,
438 siblings_only: false,
439 };
440 let result = run_test(content, config);
441 assert!(result.is_ok());
442 let warnings = result.unwrap();
443 assert_eq!(warnings.len(), 1);
444 assert_eq!(warnings[0].message, "Duplicate heading: 'Subsection'.");
445 assert_eq!(warnings[0].line, 9);
446 }
447
448 #[test]
449 fn test_multiple_duplicates() {
450 let content = r#"# Title
451
452## Subtitle
453
454### Title
455
456#### Subtitle
457
458## Title
459
460### Subtitle"#;
461
462 let config = MD024Config {
463 allow_different_nesting: false,
464 siblings_only: false,
465 };
466 let result = run_test(content, config);
467 assert!(result.is_ok());
468 let warnings = result.unwrap();
469 assert_eq!(warnings.len(), 4);
470 assert_eq!(warnings[0].message, "Duplicate heading: 'Title'.");
472 assert_eq!(warnings[0].line, 5);
473 assert_eq!(warnings[1].message, "Duplicate heading: 'Subtitle'.");
475 assert_eq!(warnings[1].line, 7);
476 assert_eq!(warnings[2].message, "Duplicate heading: 'Title'.");
478 assert_eq!(warnings[2].line, 9);
479 assert_eq!(warnings[3].message, "Duplicate heading: 'Subtitle'.");
481 assert_eq!(warnings[3].line, 11);
482 }
483
484 #[test]
485 fn test_empty_headings() {
486 let content = r#"#
487
488Some content.
489
490##
491
492More content.
493
494### Non-empty
495
496####
497
498Another empty."#;
499
500 let config = MD024Config::default();
501 let result = run_test(content, config);
502 assert!(result.is_ok());
503 let warnings = result.unwrap();
504 assert_eq!(warnings.len(), 0);
506 }
507
508 #[test]
509 fn test_unicode_and_special_characters() {
510 let content = r#"# 你好世界
511
512Some content.
513
514## Émojis 🎉🎊
515
516More content.
517
518### 你好世界
519
520Duplicate Chinese.
521
522#### Émojis 🎉🎊
523
524Duplicate emojis.
525
526##### Special <chars> & symbols!
527
528###### Special <chars> & symbols!
529
530Duplicate special chars."#;
531
532 let config = MD024Config {
533 allow_different_nesting: false,
534 siblings_only: false,
535 };
536 let result = run_test(content, config);
537 assert!(result.is_ok());
538 let warnings = result.unwrap();
539 assert_eq!(warnings.len(), 3);
540 assert_eq!(warnings[0].message, "Duplicate heading: '你好世界'.");
541 assert_eq!(warnings[1].message, "Duplicate heading: 'Émojis 🎉🎊'.");
542 assert_eq!(warnings[2].message, "Duplicate heading: 'Special <chars> & symbols!'.");
543 }
544
545 #[test]
546 fn test_allow_different_nesting_with_same_level_duplicates() {
547 let content = r#"# Section One
548
549## Title
550
551### Subsection
552
553## Title
554
555This is a duplicate at the same level.
556
557# Section Two
558
559## Title
560
561Different section, but still a duplicate when allow_different_nesting is true."#;
562
563 let config = MD024Config {
564 allow_different_nesting: true,
565 siblings_only: false,
566 };
567 let result = run_test(content, config);
568 assert!(result.is_ok());
569 let warnings = result.unwrap();
570 assert_eq!(warnings.len(), 2);
571 assert_eq!(warnings[0].message, "Duplicate heading: 'Title'.");
572 assert_eq!(warnings[0].line, 7);
573 assert_eq!(warnings[1].message, "Duplicate heading: 'Title'.");
574 assert_eq!(warnings[1].line, 13);
575 }
576
577 #[test]
578 fn test_atx_style_headings_with_closing_hashes() {
579 let content = r#"# Heading One #
580
581Some content.
582
583## Heading Two ##
584
585More content.
586
587### Heading One ###
588
589Duplicate with different style."#;
590
591 let config = MD024Config {
592 allow_different_nesting: false,
593 siblings_only: false,
594 };
595 let result = run_test(content, config);
596 assert!(result.is_ok());
597 let warnings = result.unwrap();
598 assert_eq!(warnings.len(), 1);
600 assert_eq!(warnings[0].message, "Duplicate heading: 'Heading One'.");
601 assert_eq!(warnings[0].line, 9);
602 }
603
604 #[test]
605 fn test_fix_method_returns_unchanged() {
606 let content = r#"# Duplicate
607
608## Duplicate
609
610This has duplicates."#;
611
612 let config = MD024Config::default();
613 let result = run_fix_test(content, config);
614 assert!(result.is_ok());
615 assert_eq!(result.unwrap(), content);
616 }
617
618 #[test]
619 fn test_empty_content() {
620 let content = "";
621 let config = MD024Config::default();
622 let result = run_test(content, config);
623 assert!(result.is_ok());
624 let warnings = result.unwrap();
625 assert_eq!(warnings.len(), 0);
626 }
627
628 #[test]
629 fn test_no_headings() {
630 let content = r#"This is just regular text.
631
632No headings anywhere.
633
634Just paragraphs."#;
635
636 let config = MD024Config::default();
637 let result = run_test(content, config);
638 assert!(result.is_ok());
639 let warnings = result.unwrap();
640 assert_eq!(warnings.len(), 0);
641 }
642
643 #[test]
644 fn test_whitespace_differences() {
645 let content = r#"# Heading with spaces
646
647Some content.
648
649## Heading with spaces
650
651Different amount of spaces.
652
653### Heading with spaces
654
655Exact match."#;
656
657 let config = MD024Config {
658 allow_different_nesting: false,
659 siblings_only: false,
660 };
661 let result = run_test(content, config);
662 assert!(result.is_ok());
663 let warnings = result.unwrap();
664 assert_eq!(warnings.len(), 2);
666 assert_eq!(warnings[0].message, "Duplicate heading: 'Heading with spaces'.");
667 assert_eq!(warnings[0].line, 5);
668 assert_eq!(warnings[1].message, "Duplicate heading: 'Heading with spaces'.");
669 assert_eq!(warnings[1].line, 9);
670 }
671
672 #[test]
673 fn test_column_positions() {
674 let content = r#"# First
675
676## Second
677
678### First"#;
679
680 let config = MD024Config {
681 allow_different_nesting: false,
682 siblings_only: false,
683 };
684 let result = run_test(content, config);
685 assert!(result.is_ok());
686 let warnings = result.unwrap();
687 assert_eq!(warnings.len(), 1);
688 assert_eq!(warnings[0].line, 5);
689 assert_eq!(warnings[0].column, 5); assert_eq!(warnings[0].end_line, 5);
691 assert_eq!(warnings[0].end_column, 10); }
693
694 #[test]
695 fn test_complex_nesting_scenario() {
696 let content = r#"# Main Document
697
698## Introduction
699
700### Overview
701
702## Implementation
703
704### Overview
705
706This Overview is in a different section.
707
708## Conclusion
709
710### Overview
711
712Another Overview in yet another section."#;
713
714 let config = MD024Config {
715 allow_different_nesting: true,
716 siblings_only: false,
717 };
718 let result = run_test(content, config);
719 assert!(result.is_ok());
720 let warnings = result.unwrap();
721 assert_eq!(warnings.len(), 2);
723 assert_eq!(warnings[0].message, "Duplicate heading: 'Overview'.");
724 assert_eq!(warnings[0].line, 9);
725 assert_eq!(warnings[1].message, "Duplicate heading: 'Overview'.");
726 assert_eq!(warnings[1].line, 15);
727 }
728
729 #[test]
730 fn test_setext_style_headings() {
731 let content = r#"Main Title
732==========
733
734Some content.
735
736Second Title
737------------
738
739More content.
740
741Main Title
742==========
743
744Duplicate setext."#;
745
746 let config = MD024Config::default();
747 let result = run_test(content, config);
748 assert!(result.is_ok());
749 let warnings = result.unwrap();
750 assert_eq!(warnings.len(), 1);
751 assert_eq!(warnings[0].message, "Duplicate heading: 'Main Title'.");
752 assert_eq!(warnings[0].line, 11);
753 }
754
755 #[test]
756 fn test_mixed_heading_styles() {
757 let content = r#"# ATX Title
758
759Some content.
760
761ATX Title
762=========
763
764Same text, different style."#;
765
766 let config = MD024Config::default();
767 let result = run_test(content, config);
768 assert!(result.is_ok());
769 let warnings = result.unwrap();
770 assert_eq!(warnings.len(), 1);
771 assert_eq!(warnings[0].message, "Duplicate heading: 'ATX Title'.");
772 assert_eq!(warnings[0].line, 5);
773 }
774
775 #[test]
776 fn test_heading_with_links() {
777 let content = r#"# [Link Text](http://example.com)
778
779Some content.
780
781## [Link Text](http://example.com)
782
783Duplicate heading with link.
784
785### [Different Link](http://example.com)
786
787Not a duplicate."#;
788
789 let config = MD024Config {
790 allow_different_nesting: false,
791 siblings_only: false,
792 };
793 let result = run_test(content, config);
794 assert!(result.is_ok());
795 let warnings = result.unwrap();
796 assert_eq!(warnings.len(), 1);
797 assert_eq!(
798 warnings[0].message,
799 "Duplicate heading: '[Link Text](http://example.com)'."
800 );
801 assert_eq!(warnings[0].line, 5);
802 }
803
804 #[test]
805 fn test_consecutive_duplicates() {
806 let content = r#"# Title
807
808## Title
809
810### Title
811
812Three in a row."#;
813
814 let config = MD024Config {
815 allow_different_nesting: false,
816 siblings_only: false,
817 };
818 let result = run_test(content, config);
819 assert!(result.is_ok());
820 let warnings = result.unwrap();
821 assert_eq!(warnings.len(), 2);
822 assert_eq!(warnings[0].message, "Duplicate heading: 'Title'.");
823 assert_eq!(warnings[0].line, 3);
824 assert_eq!(warnings[1].message, "Duplicate heading: 'Title'.");
825 assert_eq!(warnings[1].line, 5);
826 }
827
828 #[test]
829 fn test_siblings_only_config() {
830 let content = r#"# Section One
831
832## Subsection
833
834### Details
835
836# Section Two
837
838## Subsection
839
840Different parent sections, so not siblings - no warning expected."#;
841
842 let config = MD024Config {
843 allow_different_nesting: false,
844 siblings_only: true,
845 };
846 let result = run_test(content, config);
847 assert!(result.is_ok());
848 let warnings = result.unwrap();
849 assert_eq!(warnings.len(), 0);
851 }
852
853 #[test]
854 fn test_siblings_only_with_actual_siblings() {
855 let content = r#"# Main Section
856
857## First Subsection
858
859### Details
860
861## Second Subsection
862
863### Details
864
865The two 'Details' headings are siblings under different subsections - no warning.
866
867## First Subsection
868
869This 'First Subsection' IS a sibling duplicate."#;
870
871 let config = MD024Config {
872 allow_different_nesting: false,
873 siblings_only: true,
874 };
875 let result = run_test(content, config);
876 assert!(result.is_ok());
877 let warnings = result.unwrap();
878 assert_eq!(warnings.len(), 1);
880 assert_eq!(warnings[0].message, "Duplicate heading: 'First Subsection'.");
881 assert_eq!(warnings[0].line, 13);
882 }
883
884 #[test]
885 fn test_code_spans_in_headings() {
886 let content = r#"# `code` in heading
887
888Some content.
889
890## `code` in heading
891
892Duplicate with code span."#;
893
894 let config = MD024Config {
895 allow_different_nesting: false,
896 siblings_only: false,
897 };
898 let result = run_test(content, config);
899 assert!(result.is_ok());
900 let warnings = result.unwrap();
901 assert_eq!(warnings.len(), 1);
902 assert_eq!(warnings[0].message, "Duplicate heading: '`code` in heading'.");
903 assert_eq!(warnings[0].line, 5);
904 }
905
906 #[test]
907 fn test_very_long_heading() {
908 let long_text = "This is a very long heading that goes on and on and on and contains many words to test how the rule handles long headings";
909 let content = format!("# {long_text}\n\nSome content.\n\n## {long_text}\n\nDuplicate long heading.");
910
911 let config = MD024Config {
912 allow_different_nesting: false,
913 siblings_only: false,
914 };
915 let result = run_test(&content, config);
916 assert!(result.is_ok());
917 let warnings = result.unwrap();
918 assert_eq!(warnings.len(), 1);
919 assert_eq!(warnings[0].message, format!("Duplicate heading: '{long_text}'."));
920 assert_eq!(warnings[0].line, 5);
921 }
922
923 #[test]
924 fn test_heading_with_html_entities() {
925 let content = r#"# Title & More
926
927Some content.
928
929## Title & More
930
931Duplicate with HTML entity."#;
932
933 let config = MD024Config {
934 allow_different_nesting: false,
935 siblings_only: false,
936 };
937 let result = run_test(content, config);
938 assert!(result.is_ok());
939 let warnings = result.unwrap();
940 assert_eq!(warnings.len(), 1);
941 assert_eq!(warnings[0].message, "Duplicate heading: 'Title & More'.");
942 assert_eq!(warnings[0].line, 5);
943 }
944
945 #[test]
946 fn test_three_duplicates_different_nesting() {
947 let content = r#"# Main
948
949## Main
950
951### Main
952
953#### Main
954
955All same text, different levels."#;
956
957 let config = MD024Config {
958 allow_different_nesting: true,
959 siblings_only: false,
960 };
961 let result = run_test(content, config);
962 assert!(result.is_ok());
963 let warnings = result.unwrap();
964 assert_eq!(warnings.len(), 0);
966 }
967}