1use thiserror::Error;
9
10use crate::{Span, parser::SourceLine};
11
12#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd, Hash)]
29#[non_exhaustive]
30pub enum WarningSeverity {
31 Debug,
37
38 Warning,
42}
43
44impl std::fmt::Debug for WarningSeverity {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 match self {
47 WarningSeverity::Debug => write!(f, "WarningSeverity::Debug"),
48 WarningSeverity::Warning => write!(f, "WarningSeverity::Warning"),
49 }
50 }
51}
52
53#[derive(Clone, Debug, Eq, PartialEq)]
58#[non_exhaustive]
59pub struct Warning<'src> {
60 pub source: Span<'src>,
62
63 pub warning: WarningType,
65
66 pub severity: WarningSeverity,
76
77 pub origin: Option<SourceLine>,
97}
98
99impl<'src> Warning<'src> {
100 pub(crate) fn new(source: Span<'src>, warning: WarningType) -> Self {
106 let severity = warning.severity();
107
108 Self {
109 source,
110 warning,
111 severity,
112 origin: None,
113 }
114 }
115
116 pub(crate) fn with_origin(
122 source: Span<'src>,
123 warning: WarningType,
124 origin: Option<SourceLine>,
125 ) -> Self {
126 let severity = warning.severity();
127
128 Self {
129 source,
130 warning,
131 severity,
132 origin,
133 }
134 }
135}
136
137#[derive(Clone, Eq, Error, Hash, PartialEq)]
142#[non_exhaustive]
143pub enum WarningType {
144 #[error("an attribute value is missing its terminating quote")]
147 AttributeValueMissingTerminatingQuote,
148
149 #[error(
152 "document header wasn't terminated by a blank line (this line can't be parsed as part of a document header)"
153 )]
154 DocumentHeaderNotTerminated,
155
156 #[error(
159 "no inline candidate; use the inline doctype to convert a single paragraph, verbatim, or raw block"
160 )]
161 NoInlineDoctypeCandidate,
162
163 #[error("an empty attribute value was detected")]
165 EmptyAttributeValue,
166
167 #[error(
170 "a shorthand element attribute marker ('.', '#', or '%') was found with no subsequent text"
171 )]
172 EmptyShorthandName,
173
174 #[error("macro name is not a valid identifier")]
176 InvalidMacroName,
177
178 #[error("media macro missing target")]
181 MediaMacroMissingTarget,
182
183 #[error("macro missing attribute list")]
185 MacroMissingAttributeList,
186
187 #[error("macro missing :: separator")]
190 MacroMissingSeparator,
191
192 #[error("missing comma after quoted attribute value")]
195 MissingCommaAfterQuotedAttributeValue,
196
197 #[error("closing marker for delimited block not found")]
200 UnterminatedDelimitedBlock,
201
202 #[error("a block title or attribute list was found without a subsequent block")]
206 MissingBlockAfterTitleOrAttributeList,
207
208 #[error("block anchor name is empty")]
210 EmptyBlockAnchorName,
211
212 #[error("block anchor name contains invalid name characters")]
215 InvalidBlockAnchorName,
216
217 #[error("attribute {0:?} can not be modified by document")]
220 AttributeValueIsLocked(String),
221
222 #[error("duplicate ID: {0:?} is already registered")]
225 DuplicateId(String),
226
227 #[error("level 0 section headings not supported")]
230 Level0SectionHeadingNotSupported,
231
232 #[error("section heading level skipped (expected {0}, found {1})")]
235 SectionHeadingLevelSkipped(usize, usize),
236
237 #[error("section heading level exceeds maximum (maximum 5, found {0})")]
240 SectionHeadingLevelExceedsMaximum(usize),
241
242 #[error("section heading level {0} is outside the supported range 1-5; clamped to {1}")]
246 SectionHeadingLevelOutOfRange(i32, usize),
247
248 #[error("leveloffset {0} places every section heading outside the supported range 1-5")]
251 LeveloffsetExcludesAllHeadingLevels(i32),
252
253 #[error("{0} sections do not support nested sections")]
259 SpecialSectionCannotHaveNestedSections(String),
260
261 #[error("list item index: expected {0}, got {1}")]
264 ListItemOutOfSequence(String, String),
265
266 #[error("no callout found for <{0}>")]
269 NoCalloutFound(usize),
270
271 #[error("callout list item index: expected {0}, got {1}")]
274 CalloutListItemOutOfSequence(usize, usize),
275
276 #[error("dropping table cell because it exceeds the specified number of columns")]
279 TableCellExceedsColumnCount,
280
281 #[error("unclosed quote in CSV data; setting cell to empty")]
284 TableCsvDataHasUnclosedQuote,
285
286 #[error("table is missing a leading separator; recovering automatically")]
289 TableMissingLeadingSeparator,
290
291 #[error("dropping cells from incomplete row; detected end of table")]
294 TableIncompleteRowAtEndOfTable,
295
296 #[error("skipping reference to missing attribute: {0}")]
299 SkippingReferenceToMissingAttribute(String),
300
301 #[error("invalid substitution type for stem macro: {0}")]
304 InvalidSubstitutionTypeForStemMacro(String),
305
306 #[error("invalid substitution type for passthrough macro: {0}")]
309 InvalidSubstitutionTypeForPassthroughMacro(String),
310
311 #[error("invalid substitution type for block: {0}")]
315 InvalidSubstitutionTypeForBlock(String),
316
317 #[error("invalid footnote reference: {0}")]
320 InvalidFootnoteReference(String),
321
322 #[error("found deprecated footnoteref macro: {0}; use footnote macro with target instead")]
325 DeprecatedFootnoterefMacro(String),
326
327 #[error("include file not found: {0}")]
330 IncludeFileNotFound(String),
331
332 #[error("include file not readable: {0}")]
338 IncludeFileNotReadable(String),
339
340 #[error("include file not decodable (invalid byte sequence in UTF-8): {0}")]
350 IncludeFileNotDecodable(String),
351
352 #[error("include dropped due to missing attribute: {0}")]
357 IncludeDroppedDueToMissingAttribute(String),
358
359 #[error("maximum include depth of {0} exceeded")]
366 MaxIncludeDepthExceeded(usize),
367
368 #[error("maximum block nesting depth of {0} exceeded")]
376 MaxBlockNestingExceeded(usize),
377
378 #[error("include encoding is not supported (only UTF-8 is supported): {0}")]
382 NonUtf8IncludeEncoding(String),
383
384 #[error("malformed preprocessor directive - {0}: {1}")]
390 MalformedConditionalDirective(String, String),
391
392 #[error("unmatched preprocessor directive: {0}")]
395 UnmatchedConditionalDirective(String),
396
397 #[error("mismatched preprocessor directive: {0}")]
401 MismatchedConditionalDirective(String),
402
403 #[error("detected unterminated preprocessor conditional directive: {0}")]
407 UnterminatedConditionalDirective(String),
408
409 #[error("{0} not found in include file")]
415 IncludeTagNotFound(String),
416
417 #[error("detected unclosed tag in include file: {0}")]
420 IncludeTagUnclosed(String),
421
422 #[error("mismatched end tag in include file (expected {0} but found {1})")]
426 IncludeTagMismatchedEnd(String, String),
427
428 #[error("unexpected end tag in include file: {0}")]
431 IncludeTagUnexpectedEnd(String),
432
433 #[error(
437 "abstract block cannot be used in a document without a doctitle when doctype is book. Excluding block content."
438 )]
439 AbstractBlockInBookWithoutDoctitle,
440
441 #[error("possible invalid reference: {0}")]
450 PossibleInvalidReference(String),
451
452 #[error("rejected link with potentially unsafe scheme (rendered as text): {0}")]
458 UnsafeLinkSchemeRejected(String),
459
460 #[error("unknown style for {0} block: {1}")]
473 UnknownBlockStyle(String, String),
474}
475
476impl WarningType {
477 pub fn severity(&self) -> WarningSeverity {
497 match self {
498 WarningType::UnknownBlockStyle(..) => WarningSeverity::Debug,
499 _ => WarningSeverity::Warning,
500 }
501 }
502}
503
504impl std::fmt::Debug for WarningType {
505 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
506 match self {
507 WarningType::AttributeValueMissingTerminatingQuote => {
508 write!(f, "WarningType::AttributeValueMissingTerminatingQuote")
509 }
510
511 WarningType::DocumentHeaderNotTerminated => {
512 write!(f, "WarningType::DocumentHeaderNotTerminated")
513 }
514
515 WarningType::NoInlineDoctypeCandidate => {
516 write!(f, "WarningType::NoInlineDoctypeCandidate")
517 }
518
519 WarningType::EmptyAttributeValue => write!(f, "WarningType::EmptyAttributeValue"),
520 WarningType::EmptyShorthandName => write!(f, "WarningType::EmptyShorthandName"),
521 WarningType::InvalidMacroName => write!(f, "WarningType::InvalidMacroName"),
522
523 WarningType::MediaMacroMissingTarget => {
524 write!(f, "WarningType::MediaMacroMissingTarget")
525 }
526
527 WarningType::MacroMissingAttributeList => {
528 write!(f, "WarningType::MacroMissingAttributeList")
529 }
530
531 WarningType::MacroMissingSeparator => {
532 write!(f, "WarningType::MacroMissingSeparator")
533 }
534
535 WarningType::MissingCommaAfterQuotedAttributeValue => {
536 write!(f, "WarningType::MissingCommaAfterQuotedAttributeValue")
537 }
538
539 WarningType::UnterminatedDelimitedBlock => {
540 write!(f, "WarningType::UnterminatedDelimitedBlock")
541 }
542
543 WarningType::MissingBlockAfterTitleOrAttributeList => {
544 write!(f, "WarningType::MissingBlockAfterTitleOrAttributeList")
545 }
546
547 WarningType::EmptyBlockAnchorName => write!(f, "WarningType::EmptyBlockAnchorName"),
548 WarningType::InvalidBlockAnchorName => write!(f, "WarningType::InvalidBlockAnchorName"),
549
550 WarningType::AttributeValueIsLocked(value) => f
551 .debug_tuple("WarningType::AttributeValueIsLocked")
552 .field(value)
553 .finish(),
554
555 WarningType::DuplicateId(id) => {
556 f.debug_tuple("WarningType::DuplicateId").field(id).finish()
557 }
558
559 WarningType::Level0SectionHeadingNotSupported => {
560 write!(f, "WarningType::Level0SectionHeadingNotSupported")
561 }
562
563 WarningType::SectionHeadingLevelSkipped(expected, found) => f
564 .debug_tuple("WarningType::SectionHeadingLevelSkipped")
565 .field(expected)
566 .field(found)
567 .finish(),
568
569 WarningType::SectionHeadingLevelExceedsMaximum(found) => f
570 .debug_tuple("WarningType::SectionHeadingLevelExceedsMaximum")
571 .field(found)
572 .finish(),
573
574 WarningType::SectionHeadingLevelOutOfRange(computed, clamped) => f
575 .debug_tuple("WarningType::SectionHeadingLevelOutOfRange")
576 .field(computed)
577 .field(clamped)
578 .finish(),
579
580 WarningType::LeveloffsetExcludesAllHeadingLevels(offset) => f
581 .debug_tuple("WarningType::LeveloffsetExcludesAllHeadingLevels")
582 .field(offset)
583 .finish(),
584
585 WarningType::SpecialSectionCannotHaveNestedSections(style) => f
586 .debug_tuple("WarningType::SpecialSectionCannotHaveNestedSections")
587 .field(style)
588 .finish(),
589
590 WarningType::ListItemOutOfSequence(expected, actual) => f
591 .debug_tuple("WarningType::ListItemOutOfSequence")
592 .field(expected)
593 .field(actual)
594 .finish(),
595
596 WarningType::NoCalloutFound(number) => f
597 .debug_tuple("WarningType::NoCalloutFound")
598 .field(number)
599 .finish(),
600
601 WarningType::CalloutListItemOutOfSequence(expected, actual) => f
602 .debug_tuple("WarningType::CalloutListItemOutOfSequence")
603 .field(expected)
604 .field(actual)
605 .finish(),
606
607 WarningType::TableCellExceedsColumnCount => {
608 write!(f, "WarningType::TableCellExceedsColumnCount")
609 }
610
611 WarningType::TableCsvDataHasUnclosedQuote => {
612 write!(f, "WarningType::TableCsvDataHasUnclosedQuote")
613 }
614
615 WarningType::TableMissingLeadingSeparator => {
616 write!(f, "WarningType::TableMissingLeadingSeparator")
617 }
618
619 WarningType::TableIncompleteRowAtEndOfTable => {
620 write!(f, "WarningType::TableIncompleteRowAtEndOfTable")
621 }
622
623 WarningType::SkippingReferenceToMissingAttribute(name) => f
624 .debug_tuple("WarningType::SkippingReferenceToMissingAttribute")
625 .field(name)
626 .finish(),
627
628 WarningType::InvalidSubstitutionTypeForStemMacro(subs) => f
629 .debug_tuple("WarningType::InvalidSubstitutionTypeForStemMacro")
630 .field(subs)
631 .finish(),
632
633 WarningType::InvalidSubstitutionTypeForPassthroughMacro(subs) => f
634 .debug_tuple("WarningType::InvalidSubstitutionTypeForPassthroughMacro")
635 .field(subs)
636 .finish(),
637
638 WarningType::InvalidSubstitutionTypeForBlock(subs) => f
639 .debug_tuple("WarningType::InvalidSubstitutionTypeForBlock")
640 .field(subs)
641 .finish(),
642
643 WarningType::InvalidFootnoteReference(id) => f
644 .debug_tuple("WarningType::InvalidFootnoteReference")
645 .field(id)
646 .finish(),
647
648 WarningType::DeprecatedFootnoterefMacro(macro_text) => f
649 .debug_tuple("WarningType::DeprecatedFootnoterefMacro")
650 .field(macro_text)
651 .finish(),
652
653 WarningType::IncludeFileNotFound(target) => f
654 .debug_tuple("WarningType::IncludeFileNotFound")
655 .field(target)
656 .finish(),
657
658 WarningType::IncludeFileNotReadable(target) => f
659 .debug_tuple("WarningType::IncludeFileNotReadable")
660 .field(target)
661 .finish(),
662
663 WarningType::IncludeFileNotDecodable(target) => f
664 .debug_tuple("WarningType::IncludeFileNotDecodable")
665 .field(target)
666 .finish(),
667
668 WarningType::IncludeDroppedDueToMissingAttribute(directive) => f
669 .debug_tuple("WarningType::IncludeDroppedDueToMissingAttribute")
670 .field(directive)
671 .finish(),
672
673 WarningType::MaxIncludeDepthExceeded(depth) => f
674 .debug_tuple("WarningType::MaxIncludeDepthExceeded")
675 .field(depth)
676 .finish(),
677
678 WarningType::MaxBlockNestingExceeded(depth) => f
679 .debug_tuple("WarningType::MaxBlockNestingExceeded")
680 .field(depth)
681 .finish(),
682
683 WarningType::NonUtf8IncludeEncoding(encoding) => f
684 .debug_tuple("WarningType::NonUtf8IncludeEncoding")
685 .field(encoding)
686 .finish(),
687
688 WarningType::MalformedConditionalDirective(reason, directive) => f
689 .debug_tuple("WarningType::MalformedConditionalDirective")
690 .field(reason)
691 .field(directive)
692 .finish(),
693
694 WarningType::UnmatchedConditionalDirective(directive) => f
695 .debug_tuple("WarningType::UnmatchedConditionalDirective")
696 .field(directive)
697 .finish(),
698
699 WarningType::MismatchedConditionalDirective(directive) => f
700 .debug_tuple("WarningType::MismatchedConditionalDirective")
701 .field(directive)
702 .finish(),
703
704 WarningType::UnterminatedConditionalDirective(directive) => f
705 .debug_tuple("WarningType::UnterminatedConditionalDirective")
706 .field(directive)
707 .finish(),
708
709 WarningType::IncludeTagNotFound(tag) => f
710 .debug_tuple("WarningType::IncludeTagNotFound")
711 .field(tag)
712 .finish(),
713
714 WarningType::IncludeTagUnclosed(tag) => f
715 .debug_tuple("WarningType::IncludeTagUnclosed")
716 .field(tag)
717 .finish(),
718
719 WarningType::IncludeTagMismatchedEnd(expected, found) => f
720 .debug_tuple("WarningType::IncludeTagMismatchedEnd")
721 .field(expected)
722 .field(found)
723 .finish(),
724
725 WarningType::IncludeTagUnexpectedEnd(tag) => f
726 .debug_tuple("WarningType::IncludeTagUnexpectedEnd")
727 .field(tag)
728 .finish(),
729
730 WarningType::AbstractBlockInBookWithoutDoctitle => {
731 write!(f, "WarningType::AbstractBlockInBookWithoutDoctitle")
732 }
733
734 WarningType::PossibleInvalidReference(target) => f
735 .debug_tuple("WarningType::PossibleInvalidReference")
736 .field(target)
737 .finish(),
738
739 WarningType::UnsafeLinkSchemeRejected(target) => f
740 .debug_tuple("WarningType::UnsafeLinkSchemeRejected")
741 .field(target)
742 .finish(),
743
744 WarningType::UnknownBlockStyle(context, style) => f
745 .debug_tuple("WarningType::UnknownBlockStyle")
746 .field(context)
747 .field(style)
748 .finish(),
749 }
750 }
751}
752
753#[derive(Clone, Debug, Eq, PartialEq)]
755pub(crate) struct MatchAndWarnings<'src, T> {
756 pub(crate) item: T,
759
760 pub(crate) warnings: Vec<Warning<'src>>,
762}
763
764impl<T> MatchAndWarnings<'_, T> {
765 #[cfg(test)]
766 #[inline(always)]
767 #[track_caller]
768 #[allow(clippy::panic)] pub(crate) fn unwrap_if_no_warnings(self) -> T {
770 if self.warnings.is_empty() {
771 self.item
772 } else {
773 panic!(
774 "expected self.warnings to be empty\n\nfound warnings = {warnings:#?}\n",
775 warnings = self.warnings
776 );
777 }
778 }
779}
780
781#[cfg(test)]
782mod tests {
783 #![allow(clippy::unwrap_used)]
784
785 mod warning {
786 use crate::warnings::{Warning, WarningType};
787
788 #[test]
789 fn impl_clone() {
790 let w1 = Warning::new(crate::Span::new("abc"), WarningType::EmptyAttributeValue);
792
793 let w2 = w1.clone();
794 assert_eq!(w1, w2);
795 }
796 }
797
798 mod warning_type {
799 mod impl_debug {
800 use crate::warnings::WarningType;
801
802 #[test]
803 fn attribute_value_missing_terminating_quote() {
804 let warning = WarningType::AttributeValueMissingTerminatingQuote;
805 let debug_output = format!("{:?}", warning);
806 assert_eq!(
807 debug_output,
808 "WarningType::AttributeValueMissingTerminatingQuote"
809 );
810 }
811
812 #[test]
813 fn document_header_not_terminated() {
814 let warning = WarningType::DocumentHeaderNotTerminated;
815 let debug_output = format!("{:?}", warning);
816 assert_eq!(debug_output, "WarningType::DocumentHeaderNotTerminated");
817 }
818
819 #[test]
820 fn no_inline_doctype_candidate() {
821 let warning = WarningType::NoInlineDoctypeCandidate;
822 let debug_output = format!("{:?}", warning);
823 assert_eq!(debug_output, "WarningType::NoInlineDoctypeCandidate");
824 }
825
826 #[test]
827 fn empty_attribute_value() {
828 let warning = WarningType::EmptyAttributeValue;
829 let debug_output = format!("{:?}", warning);
830 assert_eq!(debug_output, "WarningType::EmptyAttributeValue");
831 }
832
833 #[test]
834 fn empty_shorthand_name() {
835 let warning = WarningType::EmptyShorthandName;
836 let debug_output = format!("{:?}", warning);
837 assert_eq!(debug_output, "WarningType::EmptyShorthandName");
838 }
839
840 #[test]
841 fn invalid_macro_name() {
842 let warning = WarningType::InvalidMacroName;
843 let debug_output = format!("{:?}", warning);
844 assert_eq!(debug_output, "WarningType::InvalidMacroName");
845 }
846
847 #[test]
848 fn media_macro_missing_target() {
849 let warning = WarningType::MediaMacroMissingTarget;
850 let debug_output = format!("{:?}", warning);
851 assert_eq!(debug_output, "WarningType::MediaMacroMissingTarget");
852 }
853
854 #[test]
855 fn macro_missing_attribute_list() {
856 let warning = WarningType::MacroMissingAttributeList;
857 let debug_output = format!("{:?}", warning);
858 assert_eq!(debug_output, "WarningType::MacroMissingAttributeList");
859 }
860
861 #[test]
862 fn macro_missing_separator() {
863 let warning = WarningType::MacroMissingSeparator;
864 let debug_output = format!("{:?}", warning);
865 assert_eq!(debug_output, "WarningType::MacroMissingSeparator");
866 }
867
868 #[test]
869 fn missing_comma_after_quoted_attribute_value() {
870 let warning = WarningType::MissingCommaAfterQuotedAttributeValue;
871 let debug_output = format!("{:?}", warning);
872 assert_eq!(
873 debug_output,
874 "WarningType::MissingCommaAfterQuotedAttributeValue"
875 );
876 }
877
878 #[test]
879 fn unterminated_delimited_block() {
880 let warning = WarningType::UnterminatedDelimitedBlock;
881 let debug_output = format!("{:?}", warning);
882 assert_eq!(debug_output, "WarningType::UnterminatedDelimitedBlock");
883 }
884
885 #[test]
886 fn missing_block_after_title_or_attribute_list() {
887 let warning = WarningType::MissingBlockAfterTitleOrAttributeList;
888 let debug_output = format!("{:?}", warning);
889 assert_eq!(
890 debug_output,
891 "WarningType::MissingBlockAfterTitleOrAttributeList"
892 );
893 }
894
895 #[test]
896 fn empty_block_anchor_name() {
897 let warning = WarningType::EmptyBlockAnchorName;
898 let debug_output = format!("{:?}", warning);
899 assert_eq!(debug_output, "WarningType::EmptyBlockAnchorName");
900 }
901
902 #[test]
903 fn invalid_block_anchor_name() {
904 let warning = WarningType::InvalidBlockAnchorName;
905 let debug_output = format!("{:?}", warning);
906 assert_eq!(debug_output, "WarningType::InvalidBlockAnchorName");
907 }
908
909 #[test]
910 fn attribute_value_is_locked_simple_string() {
911 let warning = WarningType::AttributeValueIsLocked("test-attribute".to_string());
912 let debug_output = format!("{:?}", warning);
913 assert_eq!(
914 debug_output,
915 "WarningType::AttributeValueIsLocked(\"test-attribute\")"
916 );
917 }
918
919 #[test]
920 fn attribute_value_is_locked_empty_string() {
921 let warning = WarningType::AttributeValueIsLocked("".to_string());
922 let debug_output = format!("{:?}", warning);
923 assert_eq!(debug_output, "WarningType::AttributeValueIsLocked(\"\")");
924 }
925
926 #[test]
927 fn attribute_value_is_locked_string_with_special_chars() {
928 let warning =
929 WarningType::AttributeValueIsLocked("attr-with-special!@#$%^&*()".to_string());
930 let debug_output = format!("{:?}", warning);
931 assert_eq!(
932 debug_output,
933 "WarningType::AttributeValueIsLocked(\"attr-with-special!@#$%^&*()\")"
934 );
935 }
936
937 #[test]
938 fn attribute_value_is_locked_string_with_quotes() {
939 let warning = WarningType::AttributeValueIsLocked("attr\"with'quotes".to_string());
940 let debug_output = format!("{:?}", warning);
941 assert_eq!(
942 debug_output,
943 "WarningType::AttributeValueIsLocked(\"attr\\\"with'quotes\")"
944 );
945 }
946
947 #[test]
948 fn attribute_value_is_locked_string_with_newlines() {
949 let warning =
950 WarningType::AttributeValueIsLocked("attr\nwith\nnewlines".to_string());
951 let debug_output = format!("{:?}", warning);
952 assert_eq!(
953 debug_output,
954 "WarningType::AttributeValueIsLocked(\"attr\\nwith\\nnewlines\")"
955 );
956 }
957
958 #[test]
959 fn duplicate_id() {
960 let warning = WarningType::DuplicateId("foo".to_owned());
961 let debug_output = format!("{:?}", warning);
962 assert_eq!(debug_output, "WarningType::DuplicateId(\"foo\")");
963 }
964
965 #[test]
966 fn level0_section_heading_not_supported() {
967 let warning = WarningType::Level0SectionHeadingNotSupported;
968 let debug_output = format!("{:?}", warning);
969 assert_eq!(
970 debug_output,
971 "WarningType::Level0SectionHeadingNotSupported"
972 );
973 }
974
975 #[test]
976 fn section_heading_level_skipped() {
977 let warning = WarningType::SectionHeadingLevelSkipped(2, 4);
978 let debug_output = format!("{:?}", warning);
979 assert_eq!(
980 debug_output,
981 "WarningType::SectionHeadingLevelSkipped(2, 4)"
982 );
983 }
984
985 #[test]
986 fn section_heading_level_exceeds_maximum() {
987 let warning = WarningType::SectionHeadingLevelExceedsMaximum(6);
988 let debug_output = format!("{:?}", warning);
989 assert_eq!(
990 debug_output,
991 "WarningType::SectionHeadingLevelExceedsMaximum(6)"
992 );
993 }
994
995 #[test]
996 fn section_heading_level_out_of_range() {
997 let warning = WarningType::SectionHeadingLevelOutOfRange(-3, 1);
998 let debug_output = format!("{:?}", warning);
999 assert_eq!(
1000 debug_output,
1001 "WarningType::SectionHeadingLevelOutOfRange(-3, 1)"
1002 );
1003 }
1004
1005 #[test]
1006 fn leveloffset_excludes_all_heading_levels() {
1007 let warning = WarningType::LeveloffsetExcludesAllHeadingLevels(2147483647);
1008 let debug_output = format!("{:?}", warning);
1009 assert_eq!(
1010 debug_output,
1011 "WarningType::LeveloffsetExcludesAllHeadingLevels(2147483647)"
1012 );
1013 }
1014
1015 #[test]
1016 fn special_section_cannot_have_nested_sections() {
1017 let warning =
1018 WarningType::SpecialSectionCannotHaveNestedSections("glossary".to_string());
1019 let debug_output = format!("{:?}", warning);
1020 assert_eq!(
1021 debug_output,
1022 "WarningType::SpecialSectionCannotHaveNestedSections(\"glossary\")"
1023 );
1024 }
1025
1026 #[test]
1027 fn list_item_out_of_sequence() {
1028 let warning = WarningType::ListItemOutOfSequence("y".to_string(), "z".to_string());
1029 let debug_output = format!("{:?}", warning);
1030 assert_eq!(
1031 debug_output,
1032 "WarningType::ListItemOutOfSequence(\"y\", \"z\")"
1033 );
1034 }
1035
1036 #[test]
1037 fn no_callout_found() {
1038 let warning = WarningType::NoCalloutFound(2);
1039 let debug_output = format!("{:?}", warning);
1040 assert_eq!(debug_output, "WarningType::NoCalloutFound(2)");
1041 }
1042
1043 #[test]
1044 fn callout_list_item_out_of_sequence() {
1045 let warning = WarningType::CalloutListItemOutOfSequence(2, 3);
1046 let debug_output = format!("{:?}", warning);
1047 assert_eq!(
1048 debug_output,
1049 "WarningType::CalloutListItemOutOfSequence(2, 3)"
1050 );
1051 }
1052
1053 #[test]
1054 fn table_cell_exceeds_column_count() {
1055 let warning = WarningType::TableCellExceedsColumnCount;
1056 let debug_output = format!("{:?}", warning);
1057 assert_eq!(debug_output, "WarningType::TableCellExceedsColumnCount");
1058 }
1059
1060 #[test]
1061 fn table_csv_data_has_unclosed_quote() {
1062 let warning = WarningType::TableCsvDataHasUnclosedQuote;
1063 let debug_output = format!("{:?}", warning);
1064 assert_eq!(debug_output, "WarningType::TableCsvDataHasUnclosedQuote");
1065 }
1066
1067 #[test]
1068 fn table_missing_leading_separator() {
1069 let warning = WarningType::TableMissingLeadingSeparator;
1070 let debug_output = format!("{:?}", warning);
1071 assert_eq!(debug_output, "WarningType::TableMissingLeadingSeparator");
1072 }
1073
1074 #[test]
1075 fn table_incomplete_row_at_end_of_table() {
1076 let warning = WarningType::TableIncompleteRowAtEndOfTable;
1077 let debug_output = format!("{:?}", warning);
1078 assert_eq!(debug_output, "WarningType::TableIncompleteRowAtEndOfTable");
1079 }
1080
1081 #[test]
1082 fn skipping_reference_to_missing_attribute() {
1083 let warning = WarningType::SkippingReferenceToMissingAttribute("name".to_string());
1084 let debug_output = format!("{:?}", warning);
1085 assert_eq!(
1086 debug_output,
1087 "WarningType::SkippingReferenceToMissingAttribute(\"name\")"
1088 );
1089 }
1090
1091 #[test]
1092 fn invalid_substitution_type_for_stem_macro() {
1093 let warning = WarningType::InvalidSubstitutionTypeForStemMacro("bogus".to_string());
1094 let debug_output = format!("{:?}", warning);
1095 assert_eq!(
1096 debug_output,
1097 "WarningType::InvalidSubstitutionTypeForStemMacro(\"bogus\")"
1098 );
1099 }
1100
1101 #[test]
1102 fn invalid_substitution_type_for_passthrough_macro() {
1103 let warning =
1104 WarningType::InvalidSubstitutionTypeForPassthroughMacro("bogus".to_string());
1105 let debug_output = format!("{:?}", warning);
1106 assert_eq!(
1107 debug_output,
1108 "WarningType::InvalidSubstitutionTypeForPassthroughMacro(\"bogus\")"
1109 );
1110 }
1111
1112 #[test]
1113 fn invalid_substitution_type_for_block() {
1114 let warning = WarningType::InvalidSubstitutionTypeForBlock("bogus".to_string());
1115 let debug_output = format!("{:?}", warning);
1116 assert_eq!(
1117 debug_output,
1118 "WarningType::InvalidSubstitutionTypeForBlock(\"bogus\")"
1119 );
1120 }
1121
1122 #[test]
1123 fn invalid_footnote_reference() {
1124 let warning = WarningType::InvalidFootnoteReference("fn1".to_string());
1125 let debug_output = format!("{:?}", warning);
1126 assert_eq!(
1127 debug_output,
1128 "WarningType::InvalidFootnoteReference(\"fn1\")"
1129 );
1130 }
1131
1132 #[test]
1133 fn deprecated_footnoteref_macro() {
1134 let warning =
1135 WarningType::DeprecatedFootnoterefMacro("footnoteref:[fn1]".to_string());
1136 let debug_output = format!("{:?}", warning);
1137 assert_eq!(
1138 debug_output,
1139 "WarningType::DeprecatedFootnoterefMacro(\"footnoteref:[fn1]\")"
1140 );
1141 }
1142
1143 #[test]
1144 fn include_file_not_found() {
1145 let warning = WarningType::IncludeFileNotFound("content.adoc".to_string());
1146 let debug_output = format!("{:?}", warning);
1147 assert_eq!(
1148 debug_output,
1149 "WarningType::IncludeFileNotFound(\"content.adoc\")"
1150 );
1151 }
1152
1153 #[test]
1154 fn include_file_not_readable() {
1155 let warning = WarningType::IncludeFileNotReadable("content.adoc".to_string());
1156 let debug_output = format!("{:?}", warning);
1157 assert_eq!(
1158 debug_output,
1159 "WarningType::IncludeFileNotReadable(\"content.adoc\")"
1160 );
1161 }
1162
1163 #[test]
1164 fn include_file_not_decodable() {
1165 let warning = WarningType::IncludeFileNotDecodable("content.adoc".to_string());
1166 let debug_output = format!("{:?}", warning);
1167 assert_eq!(
1168 debug_output,
1169 "WarningType::IncludeFileNotDecodable(\"content.adoc\")"
1170 );
1171 }
1172
1173 #[test]
1174 fn include_dropped_due_to_missing_attribute() {
1175 let warning = WarningType::IncludeDroppedDueToMissingAttribute(
1176 "include::{foodir}/include-file.adoc[]".to_string(),
1177 );
1178
1179 let debug_output = format!("{:?}", warning);
1180
1181 assert_eq!(
1182 debug_output,
1183 "WarningType::IncludeDroppedDueToMissingAttribute(\"include::{foodir}/include-file.adoc[]\")"
1184 );
1185 }
1186
1187 #[test]
1188 fn max_include_depth_exceeded() {
1189 let warning = WarningType::MaxIncludeDepthExceeded(64);
1190 let debug_output = format!("{:?}", warning);
1191 assert_eq!(debug_output, "WarningType::MaxIncludeDepthExceeded(64)");
1192 }
1193
1194 #[test]
1195 fn max_block_nesting_exceeded() {
1196 let warning = WarningType::MaxBlockNestingExceeded(64);
1197 let debug_output = format!("{:?}", warning);
1198 assert_eq!(debug_output, "WarningType::MaxBlockNestingExceeded(64)");
1199 }
1200
1201 #[test]
1202 fn non_utf8_include_encoding() {
1203 let warning = WarningType::NonUtf8IncludeEncoding("iso-8859-1".to_string());
1204 let debug_output = format!("{:?}", warning);
1205 assert_eq!(
1206 debug_output,
1207 "WarningType::NonUtf8IncludeEncoding(\"iso-8859-1\")"
1208 );
1209 }
1210
1211 #[test]
1212 fn malformed_conditional_directive() {
1213 let warning = WarningType::MalformedConditionalDirective(
1214 "missing target".to_string(),
1215 "ifdef::[]".to_string(),
1216 );
1217 let debug_output = format!("{:?}", warning);
1218 assert_eq!(
1219 debug_output,
1220 "WarningType::MalformedConditionalDirective(\"missing target\", \"ifdef::[]\")"
1221 );
1222 }
1223
1224 #[test]
1225 fn unmatched_conditional_directive() {
1226 let warning =
1227 WarningType::UnmatchedConditionalDirective("endif::on-quest[]".to_string());
1228 let debug_output = format!("{:?}", warning);
1229 assert_eq!(
1230 debug_output,
1231 "WarningType::UnmatchedConditionalDirective(\"endif::on-quest[]\")"
1232 );
1233 }
1234
1235 #[test]
1236 fn mismatched_conditional_directive() {
1237 let warning =
1238 WarningType::MismatchedConditionalDirective("endif::on-journey[]".to_string());
1239 let debug_output = format!("{:?}", warning);
1240 assert_eq!(
1241 debug_output,
1242 "WarningType::MismatchedConditionalDirective(\"endif::on-journey[]\")"
1243 );
1244 }
1245
1246 #[test]
1247 fn unterminated_conditional_directive() {
1248 let warning =
1249 WarningType::UnterminatedConditionalDirective("ifdef::on-quest[]".to_string());
1250 let debug_output = format!("{:?}", warning);
1251 assert_eq!(
1252 debug_output,
1253 "WarningType::UnterminatedConditionalDirective(\"ifdef::on-quest[]\")"
1254 );
1255 }
1256
1257 #[test]
1258 fn include_tag_not_found() {
1259 let warning = WarningType::IncludeTagNotFound("tag 'no-such-tag'".to_string());
1260 let debug_output = format!("{:?}", warning);
1261 assert_eq!(
1262 debug_output,
1263 "WarningType::IncludeTagNotFound(\"tag 'no-such-tag'\")"
1264 );
1265 }
1266
1267 #[test]
1268 fn include_tag_unclosed() {
1269 let warning = WarningType::IncludeTagUnclosed("'a'".to_string());
1270 let debug_output = format!("{:?}", warning);
1271 assert_eq!(debug_output, "WarningType::IncludeTagUnclosed(\"'a'\")");
1272 }
1273
1274 #[test]
1275 fn include_tag_mismatched_end() {
1276 let warning =
1277 WarningType::IncludeTagMismatchedEnd("'b'".to_string(), "'a'".to_string());
1278 let debug_output = format!("{:?}", warning);
1279 assert_eq!(
1280 debug_output,
1281 "WarningType::IncludeTagMismatchedEnd(\"'b'\", \"'a'\")"
1282 );
1283 }
1284
1285 #[test]
1286 fn include_tag_unexpected_end() {
1287 let warning = WarningType::IncludeTagUnexpectedEnd("'a'".to_string());
1288 let debug_output = format!("{:?}", warning);
1289 assert_eq!(
1290 debug_output,
1291 "WarningType::IncludeTagUnexpectedEnd(\"'a'\")"
1292 );
1293 }
1294
1295 #[test]
1296 fn abstract_block_in_book_without_doctitle() {
1297 let warning = WarningType::AbstractBlockInBookWithoutDoctitle;
1298 let debug_output = format!("{:?}", warning);
1299 assert_eq!(
1300 debug_output,
1301 "WarningType::AbstractBlockInBookWithoutDoctitle"
1302 );
1303 }
1304
1305 #[test]
1306 fn possible_invalid_reference() {
1307 let warning = WarningType::PossibleInvalidReference("foobaz".to_string());
1308 let debug_output = format!("{:?}", warning);
1309 assert_eq!(
1310 debug_output,
1311 "WarningType::PossibleInvalidReference(\"foobaz\")"
1312 );
1313 }
1314
1315 #[test]
1316 fn unsafe_link_scheme_rejected() {
1317 let warning =
1318 WarningType::UnsafeLinkSchemeRejected("javascript:alert(1)".to_string());
1319 let debug_output = format!("{:?}", warning);
1320 assert_eq!(
1321 debug_output,
1322 "WarningType::UnsafeLinkSchemeRejected(\"javascript:alert(1)\")"
1323 );
1324 }
1325
1326 #[test]
1327 fn unknown_block_style() {
1328 let warning = WarningType::UnknownBlockStyle("open".to_string(), "foo".to_string());
1329 let debug_output = format!("{:?}", warning);
1330 assert_eq!(
1331 debug_output,
1332 "WarningType::UnknownBlockStyle(\"open\", \"foo\")"
1333 );
1334 }
1335 }
1336
1337 mod severity {
1338 use crate::warnings::{WarningSeverity, WarningType};
1339
1340 #[test]
1341 fn unknown_block_style_is_debug() {
1342 let warning = WarningType::UnknownBlockStyle("open".to_string(), "foo".to_string());
1343 assert_eq!(warning.severity(), WarningSeverity::Debug);
1344 }
1345
1346 #[test]
1347 fn most_types_are_warning() {
1348 assert_eq!(
1349 WarningType::EmptyAttributeValue.severity(),
1350 WarningSeverity::Warning
1351 );
1352 }
1353
1354 #[test]
1355 fn debug_is_less_severe_than_warning() {
1356 assert!(WarningSeverity::Debug < WarningSeverity::Warning);
1357 }
1358
1359 #[test]
1360 fn impl_debug() {
1361 assert_eq!(
1362 format!("{:?}", WarningSeverity::Debug),
1363 "WarningSeverity::Debug"
1364 );
1365 assert_eq!(
1366 format!("{:?}", WarningSeverity::Warning),
1367 "WarningSeverity::Warning"
1368 );
1369 }
1370 }
1371 }
1372
1373 mod match_and_warnings {
1374 use crate::warnings::{MatchAndWarnings, Warning, WarningType};
1375
1376 #[test]
1377 fn impl_clone() {
1378 let maw1 = MatchAndWarnings {
1380 item: "xyz",
1381 warnings: vec![Warning::new(
1382 crate::Span::new("abc"),
1383 WarningType::EmptyAttributeValue,
1384 )],
1385 };
1386
1387 let maw2 = maw1.clone();
1388 assert_eq!(maw1, maw2);
1389 }
1390
1391 #[test]
1392 fn unwrap_if_no_warnings() {
1393 let maw = MatchAndWarnings {
1394 item: "xyz",
1395 warnings: vec![],
1396 };
1397
1398 let item = maw.unwrap_if_no_warnings();
1399 assert_eq!(item, "xyz");
1400 }
1401
1402 #[test]
1403 #[should_panic]
1404 fn unwrap_if_no_warnings_panic() {
1405 let maw = MatchAndWarnings {
1406 item: "xyz",
1407 warnings: vec![Warning::new(
1408 crate::Span::new("abc"),
1409 WarningType::EmptyAttributeValue,
1410 )],
1411 };
1412
1413 let _ = maw.unwrap_if_no_warnings();
1414
1415 }
1417 }
1418}