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