1use thiserror::Error;
9
10use crate::{Span, parser::SourceLine};
11
12#[derive(Clone, Debug, Eq, PartialEq)]
17#[non_exhaustive]
18pub struct Warning<'src> {
19 pub source: Span<'src>,
21
22 pub warning: WarningType,
24
25 pub origin: Option<SourceLine>,
45}
46
47#[derive(Clone, Eq, Error, Hash, PartialEq)]
52#[non_exhaustive]
53pub enum WarningType {
54 #[error("an attribute value is missing its terminating quote")]
57 AttributeValueMissingTerminatingQuote,
58
59 #[error(
62 "document header wasn't terminated by a blank line (this line can't be parsed as part of a document header)"
63 )]
64 DocumentHeaderNotTerminated,
65
66 #[error(
69 "no inline candidate; use the inline doctype to convert a single paragraph, verbatim, or raw block"
70 )]
71 NoInlineDoctypeCandidate,
72
73 #[error("an empty attribute value was detected")]
75 EmptyAttributeValue,
76
77 #[error(
80 "a shorthand element attribute marker ('.', '#', or '%') was found with no subsequent text"
81 )]
82 EmptyShorthandName,
83
84 #[error("macro name is not a valid identifier")]
86 InvalidMacroName,
87
88 #[error("media macro missing target")]
91 MediaMacroMissingTarget,
92
93 #[error("macro missing attribute list")]
95 MacroMissingAttributeList,
96
97 #[error("macro missing :: separator")]
100 MacroMissingSeparator,
101
102 #[error("missing comma after quoted attribute value")]
105 MissingCommaAfterQuotedAttributeValue,
106
107 #[error("closing marker for delimited block not found")]
110 UnterminatedDelimitedBlock,
111
112 #[error("a block title or attribute list was found without a subsequent block")]
116 MissingBlockAfterTitleOrAttributeList,
117
118 #[error("block anchor name is empty")]
120 EmptyBlockAnchorName,
121
122 #[error("block anchor name contains invalid name characters")]
125 InvalidBlockAnchorName,
126
127 #[error("attribute {0:?} can not be modified by document")]
130 AttributeValueIsLocked(String),
131
132 #[error("duplicate ID: {0:?} is already registered")]
135 DuplicateId(String),
136
137 #[error("level 0 section headings not supported")]
140 Level0SectionHeadingNotSupported,
141
142 #[error("section heading level skipped (expected {0}, found {1})")]
145 SectionHeadingLevelSkipped(usize, usize),
146
147 #[error("section heading level exceeds maximum (maximum 5, found {0})")]
150 SectionHeadingLevelExceedsMaximum(usize),
151
152 #[error("section heading level {0} is outside the supported range 1-5; clamped to {1}")]
156 SectionHeadingLevelOutOfRange(i32, usize),
157
158 #[error("leveloffset {0} places every section heading outside the supported range 1-5")]
161 LeveloffsetExcludesAllHeadingLevels(i32),
162
163 #[error("{0} sections do not support nested sections")]
169 SpecialSectionCannotHaveNestedSections(String),
170
171 #[error("list item index: expected {0}, got {1}")]
174 ListItemOutOfSequence(String, String),
175
176 #[error("no callout found for <{0}>")]
179 NoCalloutFound(usize),
180
181 #[error("callout list item index: expected {0}, got {1}")]
184 CalloutListItemOutOfSequence(usize, usize),
185
186 #[error("dropping table cell because it exceeds the specified number of columns")]
189 TableCellExceedsColumnCount,
190
191 #[error("unclosed quote in CSV data; setting cell to empty")]
194 TableCsvDataHasUnclosedQuote,
195
196 #[error("table is missing a leading separator; recovering automatically")]
199 TableMissingLeadingSeparator,
200
201 #[error("dropping cells from incomplete row; detected end of table")]
204 TableIncompleteRowAtEndOfTable,
205
206 #[error("skipping reference to missing attribute: {0}")]
209 SkippingReferenceToMissingAttribute(String),
210
211 #[error("invalid substitution type for stem macro: {0}")]
214 InvalidSubstitutionTypeForStemMacro(String),
215
216 #[error("invalid substitution type for passthrough macro: {0}")]
219 InvalidSubstitutionTypeForPassthroughMacro(String),
220
221 #[error("invalid substitution type for block: {0}")]
225 InvalidSubstitutionTypeForBlock(String),
226
227 #[error("invalid footnote reference: {0}")]
230 InvalidFootnoteReference(String),
231
232 #[error("found deprecated footnoteref macro: {0}; use footnote macro with target instead")]
235 DeprecatedFootnoterefMacro(String),
236
237 #[error("include file not found: {0}")]
240 IncludeFileNotFound(String),
241
242 #[error("include file not readable: {0}")]
248 IncludeFileNotReadable(String),
249
250 #[error("include file not decodable (invalid byte sequence in UTF-8): {0}")]
260 IncludeFileNotDecodable(String),
261
262 #[error("include dropped due to missing attribute: {0}")]
267 IncludeDroppedDueToMissingAttribute(String),
268
269 #[error("maximum include depth of {0} exceeded")]
276 MaxIncludeDepthExceeded(usize),
277
278 #[error("maximum block nesting depth of {0} exceeded")]
286 MaxBlockNestingExceeded(usize),
287
288 #[error("include encoding is not supported (only UTF-8 is supported): {0}")]
292 NonUtf8IncludeEncoding(String),
293
294 #[error("malformed preprocessor directive - {0}: {1}")]
300 MalformedConditionalDirective(String, String),
301
302 #[error("unmatched preprocessor directive: {0}")]
305 UnmatchedConditionalDirective(String),
306
307 #[error("mismatched preprocessor directive: {0}")]
311 MismatchedConditionalDirective(String),
312
313 #[error("detected unterminated preprocessor conditional directive: {0}")]
317 UnterminatedConditionalDirective(String),
318
319 #[error("{0} not found in include file")]
325 IncludeTagNotFound(String),
326
327 #[error("detected unclosed tag in include file: {0}")]
330 IncludeTagUnclosed(String),
331
332 #[error("mismatched end tag in include file (expected {0} but found {1})")]
336 IncludeTagMismatchedEnd(String, String),
337
338 #[error("unexpected end tag in include file: {0}")]
341 IncludeTagUnexpectedEnd(String),
342
343 #[error(
347 "abstract block cannot be used in a document without a doctitle when doctype is book. Excluding block content."
348 )]
349 AbstractBlockInBookWithoutDoctitle,
350
351 #[error("possible invalid reference: {0}")]
360 PossibleInvalidReference(String),
361
362 #[error("rejected link with potentially unsafe scheme (rendered as text): {0}")]
368 UnsafeLinkSchemeRejected(String),
369}
370
371impl std::fmt::Debug for WarningType {
372 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
373 match self {
374 WarningType::AttributeValueMissingTerminatingQuote => {
375 write!(f, "WarningType::AttributeValueMissingTerminatingQuote")
376 }
377
378 WarningType::DocumentHeaderNotTerminated => {
379 write!(f, "WarningType::DocumentHeaderNotTerminated")
380 }
381
382 WarningType::NoInlineDoctypeCandidate => {
383 write!(f, "WarningType::NoInlineDoctypeCandidate")
384 }
385
386 WarningType::EmptyAttributeValue => write!(f, "WarningType::EmptyAttributeValue"),
387 WarningType::EmptyShorthandName => write!(f, "WarningType::EmptyShorthandName"),
388 WarningType::InvalidMacroName => write!(f, "WarningType::InvalidMacroName"),
389
390 WarningType::MediaMacroMissingTarget => {
391 write!(f, "WarningType::MediaMacroMissingTarget")
392 }
393
394 WarningType::MacroMissingAttributeList => {
395 write!(f, "WarningType::MacroMissingAttributeList")
396 }
397
398 WarningType::MacroMissingSeparator => {
399 write!(f, "WarningType::MacroMissingSeparator")
400 }
401
402 WarningType::MissingCommaAfterQuotedAttributeValue => {
403 write!(f, "WarningType::MissingCommaAfterQuotedAttributeValue")
404 }
405
406 WarningType::UnterminatedDelimitedBlock => {
407 write!(f, "WarningType::UnterminatedDelimitedBlock")
408 }
409
410 WarningType::MissingBlockAfterTitleOrAttributeList => {
411 write!(f, "WarningType::MissingBlockAfterTitleOrAttributeList")
412 }
413
414 WarningType::EmptyBlockAnchorName => write!(f, "WarningType::EmptyBlockAnchorName"),
415 WarningType::InvalidBlockAnchorName => write!(f, "WarningType::InvalidBlockAnchorName"),
416
417 WarningType::AttributeValueIsLocked(value) => f
418 .debug_tuple("WarningType::AttributeValueIsLocked")
419 .field(value)
420 .finish(),
421
422 WarningType::DuplicateId(id) => {
423 f.debug_tuple("WarningType::DuplicateId").field(id).finish()
424 }
425
426 WarningType::Level0SectionHeadingNotSupported => {
427 write!(f, "WarningType::Level0SectionHeadingNotSupported")
428 }
429
430 WarningType::SectionHeadingLevelSkipped(expected, found) => f
431 .debug_tuple("WarningType::SectionHeadingLevelSkipped")
432 .field(expected)
433 .field(found)
434 .finish(),
435
436 WarningType::SectionHeadingLevelExceedsMaximum(found) => f
437 .debug_tuple("WarningType::SectionHeadingLevelExceedsMaximum")
438 .field(found)
439 .finish(),
440
441 WarningType::SectionHeadingLevelOutOfRange(computed, clamped) => f
442 .debug_tuple("WarningType::SectionHeadingLevelOutOfRange")
443 .field(computed)
444 .field(clamped)
445 .finish(),
446
447 WarningType::LeveloffsetExcludesAllHeadingLevels(offset) => f
448 .debug_tuple("WarningType::LeveloffsetExcludesAllHeadingLevels")
449 .field(offset)
450 .finish(),
451
452 WarningType::SpecialSectionCannotHaveNestedSections(style) => f
453 .debug_tuple("WarningType::SpecialSectionCannotHaveNestedSections")
454 .field(style)
455 .finish(),
456
457 WarningType::ListItemOutOfSequence(expected, actual) => f
458 .debug_tuple("WarningType::ListItemOutOfSequence")
459 .field(expected)
460 .field(actual)
461 .finish(),
462
463 WarningType::NoCalloutFound(number) => f
464 .debug_tuple("WarningType::NoCalloutFound")
465 .field(number)
466 .finish(),
467
468 WarningType::CalloutListItemOutOfSequence(expected, actual) => f
469 .debug_tuple("WarningType::CalloutListItemOutOfSequence")
470 .field(expected)
471 .field(actual)
472 .finish(),
473
474 WarningType::TableCellExceedsColumnCount => {
475 write!(f, "WarningType::TableCellExceedsColumnCount")
476 }
477
478 WarningType::TableCsvDataHasUnclosedQuote => {
479 write!(f, "WarningType::TableCsvDataHasUnclosedQuote")
480 }
481
482 WarningType::TableMissingLeadingSeparator => {
483 write!(f, "WarningType::TableMissingLeadingSeparator")
484 }
485
486 WarningType::TableIncompleteRowAtEndOfTable => {
487 write!(f, "WarningType::TableIncompleteRowAtEndOfTable")
488 }
489
490 WarningType::SkippingReferenceToMissingAttribute(name) => f
491 .debug_tuple("WarningType::SkippingReferenceToMissingAttribute")
492 .field(name)
493 .finish(),
494
495 WarningType::InvalidSubstitutionTypeForStemMacro(subs) => f
496 .debug_tuple("WarningType::InvalidSubstitutionTypeForStemMacro")
497 .field(subs)
498 .finish(),
499
500 WarningType::InvalidSubstitutionTypeForPassthroughMacro(subs) => f
501 .debug_tuple("WarningType::InvalidSubstitutionTypeForPassthroughMacro")
502 .field(subs)
503 .finish(),
504
505 WarningType::InvalidSubstitutionTypeForBlock(subs) => f
506 .debug_tuple("WarningType::InvalidSubstitutionTypeForBlock")
507 .field(subs)
508 .finish(),
509
510 WarningType::InvalidFootnoteReference(id) => f
511 .debug_tuple("WarningType::InvalidFootnoteReference")
512 .field(id)
513 .finish(),
514
515 WarningType::DeprecatedFootnoterefMacro(macro_text) => f
516 .debug_tuple("WarningType::DeprecatedFootnoterefMacro")
517 .field(macro_text)
518 .finish(),
519
520 WarningType::IncludeFileNotFound(target) => f
521 .debug_tuple("WarningType::IncludeFileNotFound")
522 .field(target)
523 .finish(),
524
525 WarningType::IncludeFileNotReadable(target) => f
526 .debug_tuple("WarningType::IncludeFileNotReadable")
527 .field(target)
528 .finish(),
529
530 WarningType::IncludeFileNotDecodable(target) => f
531 .debug_tuple("WarningType::IncludeFileNotDecodable")
532 .field(target)
533 .finish(),
534
535 WarningType::IncludeDroppedDueToMissingAttribute(directive) => f
536 .debug_tuple("WarningType::IncludeDroppedDueToMissingAttribute")
537 .field(directive)
538 .finish(),
539
540 WarningType::MaxIncludeDepthExceeded(depth) => f
541 .debug_tuple("WarningType::MaxIncludeDepthExceeded")
542 .field(depth)
543 .finish(),
544
545 WarningType::MaxBlockNestingExceeded(depth) => f
546 .debug_tuple("WarningType::MaxBlockNestingExceeded")
547 .field(depth)
548 .finish(),
549
550 WarningType::NonUtf8IncludeEncoding(encoding) => f
551 .debug_tuple("WarningType::NonUtf8IncludeEncoding")
552 .field(encoding)
553 .finish(),
554
555 WarningType::MalformedConditionalDirective(reason, directive) => f
556 .debug_tuple("WarningType::MalformedConditionalDirective")
557 .field(reason)
558 .field(directive)
559 .finish(),
560
561 WarningType::UnmatchedConditionalDirective(directive) => f
562 .debug_tuple("WarningType::UnmatchedConditionalDirective")
563 .field(directive)
564 .finish(),
565
566 WarningType::MismatchedConditionalDirective(directive) => f
567 .debug_tuple("WarningType::MismatchedConditionalDirective")
568 .field(directive)
569 .finish(),
570
571 WarningType::UnterminatedConditionalDirective(directive) => f
572 .debug_tuple("WarningType::UnterminatedConditionalDirective")
573 .field(directive)
574 .finish(),
575
576 WarningType::IncludeTagNotFound(tag) => f
577 .debug_tuple("WarningType::IncludeTagNotFound")
578 .field(tag)
579 .finish(),
580
581 WarningType::IncludeTagUnclosed(tag) => f
582 .debug_tuple("WarningType::IncludeTagUnclosed")
583 .field(tag)
584 .finish(),
585
586 WarningType::IncludeTagMismatchedEnd(expected, found) => f
587 .debug_tuple("WarningType::IncludeTagMismatchedEnd")
588 .field(expected)
589 .field(found)
590 .finish(),
591
592 WarningType::IncludeTagUnexpectedEnd(tag) => f
593 .debug_tuple("WarningType::IncludeTagUnexpectedEnd")
594 .field(tag)
595 .finish(),
596
597 WarningType::AbstractBlockInBookWithoutDoctitle => {
598 write!(f, "WarningType::AbstractBlockInBookWithoutDoctitle")
599 }
600
601 WarningType::PossibleInvalidReference(target) => f
602 .debug_tuple("WarningType::PossibleInvalidReference")
603 .field(target)
604 .finish(),
605
606 WarningType::UnsafeLinkSchemeRejected(target) => f
607 .debug_tuple("WarningType::UnsafeLinkSchemeRejected")
608 .field(target)
609 .finish(),
610 }
611 }
612}
613
614#[derive(Clone, Debug, Eq, PartialEq)]
616pub(crate) struct MatchAndWarnings<'src, T> {
617 pub(crate) item: T,
620
621 pub(crate) warnings: Vec<Warning<'src>>,
623}
624
625impl<T> MatchAndWarnings<'_, T> {
626 #[cfg(test)]
627 #[inline(always)]
628 #[track_caller]
629 #[allow(clippy::panic)] pub(crate) fn unwrap_if_no_warnings(self) -> T {
631 if self.warnings.is_empty() {
632 self.item
633 } else {
634 panic!(
635 "expected self.warnings to be empty\n\nfound warnings = {warnings:#?}\n",
636 warnings = self.warnings
637 );
638 }
639 }
640}
641
642#[cfg(test)]
643mod tests {
644 #![allow(clippy::unwrap_used)]
645
646 mod warning {
647 use crate::warnings::{Warning, WarningType};
648
649 #[test]
650 fn impl_clone() {
651 let w1 = Warning {
653 source: crate::Span::new("abc"),
654 warning: WarningType::EmptyAttributeValue,
655 origin: None,
656 };
657
658 let w2 = w1.clone();
659 assert_eq!(w1, w2);
660 }
661 }
662
663 mod warning_type {
664 mod impl_debug {
665 use crate::warnings::WarningType;
666
667 #[test]
668 fn attribute_value_missing_terminating_quote() {
669 let warning = WarningType::AttributeValueMissingTerminatingQuote;
670 let debug_output = format!("{:?}", warning);
671 assert_eq!(
672 debug_output,
673 "WarningType::AttributeValueMissingTerminatingQuote"
674 );
675 }
676
677 #[test]
678 fn document_header_not_terminated() {
679 let warning = WarningType::DocumentHeaderNotTerminated;
680 let debug_output = format!("{:?}", warning);
681 assert_eq!(debug_output, "WarningType::DocumentHeaderNotTerminated");
682 }
683
684 #[test]
685 fn no_inline_doctype_candidate() {
686 let warning = WarningType::NoInlineDoctypeCandidate;
687 let debug_output = format!("{:?}", warning);
688 assert_eq!(debug_output, "WarningType::NoInlineDoctypeCandidate");
689 }
690
691 #[test]
692 fn empty_attribute_value() {
693 let warning = WarningType::EmptyAttributeValue;
694 let debug_output = format!("{:?}", warning);
695 assert_eq!(debug_output, "WarningType::EmptyAttributeValue");
696 }
697
698 #[test]
699 fn empty_shorthand_name() {
700 let warning = WarningType::EmptyShorthandName;
701 let debug_output = format!("{:?}", warning);
702 assert_eq!(debug_output, "WarningType::EmptyShorthandName");
703 }
704
705 #[test]
706 fn invalid_macro_name() {
707 let warning = WarningType::InvalidMacroName;
708 let debug_output = format!("{:?}", warning);
709 assert_eq!(debug_output, "WarningType::InvalidMacroName");
710 }
711
712 #[test]
713 fn media_macro_missing_target() {
714 let warning = WarningType::MediaMacroMissingTarget;
715 let debug_output = format!("{:?}", warning);
716 assert_eq!(debug_output, "WarningType::MediaMacroMissingTarget");
717 }
718
719 #[test]
720 fn macro_missing_attribute_list() {
721 let warning = WarningType::MacroMissingAttributeList;
722 let debug_output = format!("{:?}", warning);
723 assert_eq!(debug_output, "WarningType::MacroMissingAttributeList");
724 }
725
726 #[test]
727 fn macro_missing_separator() {
728 let warning = WarningType::MacroMissingSeparator;
729 let debug_output = format!("{:?}", warning);
730 assert_eq!(debug_output, "WarningType::MacroMissingSeparator");
731 }
732
733 #[test]
734 fn missing_comma_after_quoted_attribute_value() {
735 let warning = WarningType::MissingCommaAfterQuotedAttributeValue;
736 let debug_output = format!("{:?}", warning);
737 assert_eq!(
738 debug_output,
739 "WarningType::MissingCommaAfterQuotedAttributeValue"
740 );
741 }
742
743 #[test]
744 fn unterminated_delimited_block() {
745 let warning = WarningType::UnterminatedDelimitedBlock;
746 let debug_output = format!("{:?}", warning);
747 assert_eq!(debug_output, "WarningType::UnterminatedDelimitedBlock");
748 }
749
750 #[test]
751 fn missing_block_after_title_or_attribute_list() {
752 let warning = WarningType::MissingBlockAfterTitleOrAttributeList;
753 let debug_output = format!("{:?}", warning);
754 assert_eq!(
755 debug_output,
756 "WarningType::MissingBlockAfterTitleOrAttributeList"
757 );
758 }
759
760 #[test]
761 fn empty_block_anchor_name() {
762 let warning = WarningType::EmptyBlockAnchorName;
763 let debug_output = format!("{:?}", warning);
764 assert_eq!(debug_output, "WarningType::EmptyBlockAnchorName");
765 }
766
767 #[test]
768 fn invalid_block_anchor_name() {
769 let warning = WarningType::InvalidBlockAnchorName;
770 let debug_output = format!("{:?}", warning);
771 assert_eq!(debug_output, "WarningType::InvalidBlockAnchorName");
772 }
773
774 #[test]
775 fn attribute_value_is_locked_simple_string() {
776 let warning = WarningType::AttributeValueIsLocked("test-attribute".to_string());
777 let debug_output = format!("{:?}", warning);
778 assert_eq!(
779 debug_output,
780 "WarningType::AttributeValueIsLocked(\"test-attribute\")"
781 );
782 }
783
784 #[test]
785 fn attribute_value_is_locked_empty_string() {
786 let warning = WarningType::AttributeValueIsLocked("".to_string());
787 let debug_output = format!("{:?}", warning);
788 assert_eq!(debug_output, "WarningType::AttributeValueIsLocked(\"\")");
789 }
790
791 #[test]
792 fn attribute_value_is_locked_string_with_special_chars() {
793 let warning =
794 WarningType::AttributeValueIsLocked("attr-with-special!@#$%^&*()".to_string());
795 let debug_output = format!("{:?}", warning);
796 assert_eq!(
797 debug_output,
798 "WarningType::AttributeValueIsLocked(\"attr-with-special!@#$%^&*()\")"
799 );
800 }
801
802 #[test]
803 fn attribute_value_is_locked_string_with_quotes() {
804 let warning = WarningType::AttributeValueIsLocked("attr\"with'quotes".to_string());
805 let debug_output = format!("{:?}", warning);
806 assert_eq!(
807 debug_output,
808 "WarningType::AttributeValueIsLocked(\"attr\\\"with'quotes\")"
809 );
810 }
811
812 #[test]
813 fn attribute_value_is_locked_string_with_newlines() {
814 let warning =
815 WarningType::AttributeValueIsLocked("attr\nwith\nnewlines".to_string());
816 let debug_output = format!("{:?}", warning);
817 assert_eq!(
818 debug_output,
819 "WarningType::AttributeValueIsLocked(\"attr\\nwith\\nnewlines\")"
820 );
821 }
822
823 #[test]
824 fn duplicate_id() {
825 let warning = WarningType::DuplicateId("foo".to_owned());
826 let debug_output = format!("{:?}", warning);
827 assert_eq!(debug_output, "WarningType::DuplicateId(\"foo\")");
828 }
829
830 #[test]
831 fn level0_section_heading_not_supported() {
832 let warning = WarningType::Level0SectionHeadingNotSupported;
833 let debug_output = format!("{:?}", warning);
834 assert_eq!(
835 debug_output,
836 "WarningType::Level0SectionHeadingNotSupported"
837 );
838 }
839
840 #[test]
841 fn section_heading_level_skipped() {
842 let warning = WarningType::SectionHeadingLevelSkipped(2, 4);
843 let debug_output = format!("{:?}", warning);
844 assert_eq!(
845 debug_output,
846 "WarningType::SectionHeadingLevelSkipped(2, 4)"
847 );
848 }
849
850 #[test]
851 fn section_heading_level_exceeds_maximum() {
852 let warning = WarningType::SectionHeadingLevelExceedsMaximum(6);
853 let debug_output = format!("{:?}", warning);
854 assert_eq!(
855 debug_output,
856 "WarningType::SectionHeadingLevelExceedsMaximum(6)"
857 );
858 }
859
860 #[test]
861 fn section_heading_level_out_of_range() {
862 let warning = WarningType::SectionHeadingLevelOutOfRange(-3, 1);
863 let debug_output = format!("{:?}", warning);
864 assert_eq!(
865 debug_output,
866 "WarningType::SectionHeadingLevelOutOfRange(-3, 1)"
867 );
868 }
869
870 #[test]
871 fn leveloffset_excludes_all_heading_levels() {
872 let warning = WarningType::LeveloffsetExcludesAllHeadingLevels(2147483647);
873 let debug_output = format!("{:?}", warning);
874 assert_eq!(
875 debug_output,
876 "WarningType::LeveloffsetExcludesAllHeadingLevels(2147483647)"
877 );
878 }
879
880 #[test]
881 fn special_section_cannot_have_nested_sections() {
882 let warning =
883 WarningType::SpecialSectionCannotHaveNestedSections("glossary".to_string());
884 let debug_output = format!("{:?}", warning);
885 assert_eq!(
886 debug_output,
887 "WarningType::SpecialSectionCannotHaveNestedSections(\"glossary\")"
888 );
889 }
890
891 #[test]
892 fn list_item_out_of_sequence() {
893 let warning = WarningType::ListItemOutOfSequence("y".to_string(), "z".to_string());
894 let debug_output = format!("{:?}", warning);
895 assert_eq!(
896 debug_output,
897 "WarningType::ListItemOutOfSequence(\"y\", \"z\")"
898 );
899 }
900
901 #[test]
902 fn no_callout_found() {
903 let warning = WarningType::NoCalloutFound(2);
904 let debug_output = format!("{:?}", warning);
905 assert_eq!(debug_output, "WarningType::NoCalloutFound(2)");
906 }
907
908 #[test]
909 fn callout_list_item_out_of_sequence() {
910 let warning = WarningType::CalloutListItemOutOfSequence(2, 3);
911 let debug_output = format!("{:?}", warning);
912 assert_eq!(
913 debug_output,
914 "WarningType::CalloutListItemOutOfSequence(2, 3)"
915 );
916 }
917
918 #[test]
919 fn table_cell_exceeds_column_count() {
920 let warning = WarningType::TableCellExceedsColumnCount;
921 let debug_output = format!("{:?}", warning);
922 assert_eq!(debug_output, "WarningType::TableCellExceedsColumnCount");
923 }
924
925 #[test]
926 fn table_csv_data_has_unclosed_quote() {
927 let warning = WarningType::TableCsvDataHasUnclosedQuote;
928 let debug_output = format!("{:?}", warning);
929 assert_eq!(debug_output, "WarningType::TableCsvDataHasUnclosedQuote");
930 }
931
932 #[test]
933 fn table_missing_leading_separator() {
934 let warning = WarningType::TableMissingLeadingSeparator;
935 let debug_output = format!("{:?}", warning);
936 assert_eq!(debug_output, "WarningType::TableMissingLeadingSeparator");
937 }
938
939 #[test]
940 fn table_incomplete_row_at_end_of_table() {
941 let warning = WarningType::TableIncompleteRowAtEndOfTable;
942 let debug_output = format!("{:?}", warning);
943 assert_eq!(debug_output, "WarningType::TableIncompleteRowAtEndOfTable");
944 }
945
946 #[test]
947 fn skipping_reference_to_missing_attribute() {
948 let warning = WarningType::SkippingReferenceToMissingAttribute("name".to_string());
949 let debug_output = format!("{:?}", warning);
950 assert_eq!(
951 debug_output,
952 "WarningType::SkippingReferenceToMissingAttribute(\"name\")"
953 );
954 }
955
956 #[test]
957 fn invalid_substitution_type_for_stem_macro() {
958 let warning = WarningType::InvalidSubstitutionTypeForStemMacro("bogus".to_string());
959 let debug_output = format!("{:?}", warning);
960 assert_eq!(
961 debug_output,
962 "WarningType::InvalidSubstitutionTypeForStemMacro(\"bogus\")"
963 );
964 }
965
966 #[test]
967 fn invalid_substitution_type_for_passthrough_macro() {
968 let warning =
969 WarningType::InvalidSubstitutionTypeForPassthroughMacro("bogus".to_string());
970 let debug_output = format!("{:?}", warning);
971 assert_eq!(
972 debug_output,
973 "WarningType::InvalidSubstitutionTypeForPassthroughMacro(\"bogus\")"
974 );
975 }
976
977 #[test]
978 fn invalid_substitution_type_for_block() {
979 let warning = WarningType::InvalidSubstitutionTypeForBlock("bogus".to_string());
980 let debug_output = format!("{:?}", warning);
981 assert_eq!(
982 debug_output,
983 "WarningType::InvalidSubstitutionTypeForBlock(\"bogus\")"
984 );
985 }
986
987 #[test]
988 fn invalid_footnote_reference() {
989 let warning = WarningType::InvalidFootnoteReference("fn1".to_string());
990 let debug_output = format!("{:?}", warning);
991 assert_eq!(
992 debug_output,
993 "WarningType::InvalidFootnoteReference(\"fn1\")"
994 );
995 }
996
997 #[test]
998 fn deprecated_footnoteref_macro() {
999 let warning =
1000 WarningType::DeprecatedFootnoterefMacro("footnoteref:[fn1]".to_string());
1001 let debug_output = format!("{:?}", warning);
1002 assert_eq!(
1003 debug_output,
1004 "WarningType::DeprecatedFootnoterefMacro(\"footnoteref:[fn1]\")"
1005 );
1006 }
1007
1008 #[test]
1009 fn include_file_not_found() {
1010 let warning = WarningType::IncludeFileNotFound("content.adoc".to_string());
1011 let debug_output = format!("{:?}", warning);
1012 assert_eq!(
1013 debug_output,
1014 "WarningType::IncludeFileNotFound(\"content.adoc\")"
1015 );
1016 }
1017
1018 #[test]
1019 fn include_file_not_readable() {
1020 let warning = WarningType::IncludeFileNotReadable("content.adoc".to_string());
1021 let debug_output = format!("{:?}", warning);
1022 assert_eq!(
1023 debug_output,
1024 "WarningType::IncludeFileNotReadable(\"content.adoc\")"
1025 );
1026 }
1027
1028 #[test]
1029 fn include_file_not_decodable() {
1030 let warning = WarningType::IncludeFileNotDecodable("content.adoc".to_string());
1031 let debug_output = format!("{:?}", warning);
1032 assert_eq!(
1033 debug_output,
1034 "WarningType::IncludeFileNotDecodable(\"content.adoc\")"
1035 );
1036 }
1037
1038 #[test]
1039 fn include_dropped_due_to_missing_attribute() {
1040 let warning = WarningType::IncludeDroppedDueToMissingAttribute(
1041 "include::{foodir}/include-file.adoc[]".to_string(),
1042 );
1043
1044 let debug_output = format!("{:?}", warning);
1045
1046 assert_eq!(
1047 debug_output,
1048 "WarningType::IncludeDroppedDueToMissingAttribute(\"include::{foodir}/include-file.adoc[]\")"
1049 );
1050 }
1051
1052 #[test]
1053 fn max_include_depth_exceeded() {
1054 let warning = WarningType::MaxIncludeDepthExceeded(64);
1055 let debug_output = format!("{:?}", warning);
1056 assert_eq!(debug_output, "WarningType::MaxIncludeDepthExceeded(64)");
1057 }
1058
1059 #[test]
1060 fn max_block_nesting_exceeded() {
1061 let warning = WarningType::MaxBlockNestingExceeded(64);
1062 let debug_output = format!("{:?}", warning);
1063 assert_eq!(debug_output, "WarningType::MaxBlockNestingExceeded(64)");
1064 }
1065
1066 #[test]
1067 fn non_utf8_include_encoding() {
1068 let warning = WarningType::NonUtf8IncludeEncoding("iso-8859-1".to_string());
1069 let debug_output = format!("{:?}", warning);
1070 assert_eq!(
1071 debug_output,
1072 "WarningType::NonUtf8IncludeEncoding(\"iso-8859-1\")"
1073 );
1074 }
1075
1076 #[test]
1077 fn malformed_conditional_directive() {
1078 let warning = WarningType::MalformedConditionalDirective(
1079 "missing target".to_string(),
1080 "ifdef::[]".to_string(),
1081 );
1082 let debug_output = format!("{:?}", warning);
1083 assert_eq!(
1084 debug_output,
1085 "WarningType::MalformedConditionalDirective(\"missing target\", \"ifdef::[]\")"
1086 );
1087 }
1088
1089 #[test]
1090 fn unmatched_conditional_directive() {
1091 let warning =
1092 WarningType::UnmatchedConditionalDirective("endif::on-quest[]".to_string());
1093 let debug_output = format!("{:?}", warning);
1094 assert_eq!(
1095 debug_output,
1096 "WarningType::UnmatchedConditionalDirective(\"endif::on-quest[]\")"
1097 );
1098 }
1099
1100 #[test]
1101 fn mismatched_conditional_directive() {
1102 let warning =
1103 WarningType::MismatchedConditionalDirective("endif::on-journey[]".to_string());
1104 let debug_output = format!("{:?}", warning);
1105 assert_eq!(
1106 debug_output,
1107 "WarningType::MismatchedConditionalDirective(\"endif::on-journey[]\")"
1108 );
1109 }
1110
1111 #[test]
1112 fn unterminated_conditional_directive() {
1113 let warning =
1114 WarningType::UnterminatedConditionalDirective("ifdef::on-quest[]".to_string());
1115 let debug_output = format!("{:?}", warning);
1116 assert_eq!(
1117 debug_output,
1118 "WarningType::UnterminatedConditionalDirective(\"ifdef::on-quest[]\")"
1119 );
1120 }
1121
1122 #[test]
1123 fn include_tag_not_found() {
1124 let warning = WarningType::IncludeTagNotFound("tag 'no-such-tag'".to_string());
1125 let debug_output = format!("{:?}", warning);
1126 assert_eq!(
1127 debug_output,
1128 "WarningType::IncludeTagNotFound(\"tag 'no-such-tag'\")"
1129 );
1130 }
1131
1132 #[test]
1133 fn include_tag_unclosed() {
1134 let warning = WarningType::IncludeTagUnclosed("'a'".to_string());
1135 let debug_output = format!("{:?}", warning);
1136 assert_eq!(debug_output, "WarningType::IncludeTagUnclosed(\"'a'\")");
1137 }
1138
1139 #[test]
1140 fn include_tag_mismatched_end() {
1141 let warning =
1142 WarningType::IncludeTagMismatchedEnd("'b'".to_string(), "'a'".to_string());
1143 let debug_output = format!("{:?}", warning);
1144 assert_eq!(
1145 debug_output,
1146 "WarningType::IncludeTagMismatchedEnd(\"'b'\", \"'a'\")"
1147 );
1148 }
1149
1150 #[test]
1151 fn include_tag_unexpected_end() {
1152 let warning = WarningType::IncludeTagUnexpectedEnd("'a'".to_string());
1153 let debug_output = format!("{:?}", warning);
1154 assert_eq!(
1155 debug_output,
1156 "WarningType::IncludeTagUnexpectedEnd(\"'a'\")"
1157 );
1158 }
1159
1160 #[test]
1161 fn abstract_block_in_book_without_doctitle() {
1162 let warning = WarningType::AbstractBlockInBookWithoutDoctitle;
1163 let debug_output = format!("{:?}", warning);
1164 assert_eq!(
1165 debug_output,
1166 "WarningType::AbstractBlockInBookWithoutDoctitle"
1167 );
1168 }
1169
1170 #[test]
1171 fn possible_invalid_reference() {
1172 let warning = WarningType::PossibleInvalidReference("foobaz".to_string());
1173 let debug_output = format!("{:?}", warning);
1174 assert_eq!(
1175 debug_output,
1176 "WarningType::PossibleInvalidReference(\"foobaz\")"
1177 );
1178 }
1179
1180 #[test]
1181 fn unsafe_link_scheme_rejected() {
1182 let warning =
1183 WarningType::UnsafeLinkSchemeRejected("javascript:alert(1)".to_string());
1184 let debug_output = format!("{:?}", warning);
1185 assert_eq!(
1186 debug_output,
1187 "WarningType::UnsafeLinkSchemeRejected(\"javascript:alert(1)\")"
1188 );
1189 }
1190 }
1191 }
1192
1193 mod match_and_warnings {
1194 use crate::warnings::{MatchAndWarnings, Warning, WarningType};
1195
1196 #[test]
1197 fn impl_clone() {
1198 let maw1 = MatchAndWarnings {
1200 item: "xyz",
1201 warnings: vec![Warning {
1202 source: crate::Span::new("abc"),
1203 warning: WarningType::EmptyAttributeValue,
1204 origin: None,
1205 }],
1206 };
1207
1208 let maw2 = maw1.clone();
1209 assert_eq!(maw1, maw2);
1210 }
1211
1212 #[test]
1213 fn unwrap_if_no_warnings() {
1214 let maw = MatchAndWarnings {
1215 item: "xyz",
1216 warnings: vec![],
1217 };
1218
1219 let item = maw.unwrap_if_no_warnings();
1220 assert_eq!(item, "xyz");
1221 }
1222
1223 #[test]
1224 #[should_panic]
1225 fn unwrap_if_no_warnings_panic() {
1226 let maw = MatchAndWarnings {
1227 item: "xyz",
1228 warnings: vec![Warning {
1229 source: crate::Span::new("abc"),
1230 warning: WarningType::EmptyAttributeValue,
1231 origin: None,
1232 }],
1233 };
1234
1235 let _ = maw.unwrap_if_no_warnings();
1236
1237 }
1239 }
1240}