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("list item index: expected {0}, got {1}")]
166 ListItemOutOfSequence(String, String),
167
168 #[error("no callout found for <{0}>")]
171 NoCalloutFound(usize),
172
173 #[error("callout list item index: expected {0}, got {1}")]
176 CalloutListItemOutOfSequence(usize, usize),
177
178 #[error("dropping table cell because it exceeds the specified number of columns")]
181 TableCellExceedsColumnCount,
182
183 #[error("unclosed quote in CSV data; setting cell to empty")]
186 TableCsvDataHasUnclosedQuote,
187
188 #[error("table is missing a leading separator; recovering automatically")]
191 TableMissingLeadingSeparator,
192
193 #[error("dropping cells from incomplete row; detected end of table")]
196 TableIncompleteRowAtEndOfTable,
197
198 #[error("skipping reference to missing attribute: {0}")]
201 SkippingReferenceToMissingAttribute(String),
202
203 #[error("invalid substitution type for stem macro: {0}")]
206 InvalidSubstitutionTypeForStemMacro(String),
207
208 #[error("invalid substitution type for passthrough macro: {0}")]
211 InvalidSubstitutionTypeForPassthroughMacro(String),
212
213 #[error("invalid substitution type for block: {0}")]
217 InvalidSubstitutionTypeForBlock(String),
218
219 #[error("invalid footnote reference: {0}")]
222 InvalidFootnoteReference(String),
223
224 #[error("found deprecated footnoteref macro: {0}; use footnote macro with target instead")]
227 DeprecatedFootnoterefMacro(String),
228
229 #[error("include file not found: {0}")]
232 IncludeFileNotFound(String),
233
234 #[error("include file not readable: {0}")]
240 IncludeFileNotReadable(String),
241
242 #[error("include dropped due to missing attribute: {0}")]
247 IncludeDroppedDueToMissingAttribute(String),
248
249 #[error("maximum include depth of {0} exceeded")]
256 MaxIncludeDepthExceeded(usize),
257
258 #[error("maximum block nesting depth of {0} exceeded")]
266 MaxBlockNestingExceeded(usize),
267
268 #[error("include encoding is not supported (only UTF-8 is supported): {0}")]
272 NonUtf8IncludeEncoding(String),
273
274 #[error("malformed preprocessor directive - {0}: {1}")]
280 MalformedConditionalDirective(String, String),
281
282 #[error("unmatched preprocessor directive: {0}")]
285 UnmatchedConditionalDirective(String),
286
287 #[error("mismatched preprocessor directive: {0}")]
291 MismatchedConditionalDirective(String),
292
293 #[error("detected unterminated preprocessor conditional directive: {0}")]
297 UnterminatedConditionalDirective(String),
298
299 #[error("{0} not found in include file")]
305 IncludeTagNotFound(String),
306
307 #[error("detected unclosed tag in include file: {0}")]
310 IncludeTagUnclosed(String),
311
312 #[error("mismatched end tag in include file (expected {0} but found {1})")]
316 IncludeTagMismatchedEnd(String, String),
317
318 #[error("unexpected end tag in include file: {0}")]
321 IncludeTagUnexpectedEnd(String),
322
323 #[error(
327 "abstract block cannot be used in a document without a doctitle when doctype is book. Excluding block content."
328 )]
329 AbstractBlockInBookWithoutDoctitle,
330
331 #[error("possible invalid reference: {0}")]
340 PossibleInvalidReference(String),
341
342 #[error("rejected link with potentially unsafe scheme (rendered as text): {0}")]
348 UnsafeLinkSchemeRejected(String),
349}
350
351impl std::fmt::Debug for WarningType {
352 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
353 match self {
354 WarningType::AttributeValueMissingTerminatingQuote => {
355 write!(f, "WarningType::AttributeValueMissingTerminatingQuote")
356 }
357
358 WarningType::DocumentHeaderNotTerminated => {
359 write!(f, "WarningType::DocumentHeaderNotTerminated")
360 }
361
362 WarningType::NoInlineDoctypeCandidate => {
363 write!(f, "WarningType::NoInlineDoctypeCandidate")
364 }
365
366 WarningType::EmptyAttributeValue => write!(f, "WarningType::EmptyAttributeValue"),
367 WarningType::EmptyShorthandName => write!(f, "WarningType::EmptyShorthandName"),
368 WarningType::InvalidMacroName => write!(f, "WarningType::InvalidMacroName"),
369
370 WarningType::MediaMacroMissingTarget => {
371 write!(f, "WarningType::MediaMacroMissingTarget")
372 }
373
374 WarningType::MacroMissingAttributeList => {
375 write!(f, "WarningType::MacroMissingAttributeList")
376 }
377
378 WarningType::MacroMissingSeparator => {
379 write!(f, "WarningType::MacroMissingSeparator")
380 }
381
382 WarningType::MissingCommaAfterQuotedAttributeValue => {
383 write!(f, "WarningType::MissingCommaAfterQuotedAttributeValue")
384 }
385
386 WarningType::UnterminatedDelimitedBlock => {
387 write!(f, "WarningType::UnterminatedDelimitedBlock")
388 }
389
390 WarningType::MissingBlockAfterTitleOrAttributeList => {
391 write!(f, "WarningType::MissingBlockAfterTitleOrAttributeList")
392 }
393
394 WarningType::EmptyBlockAnchorName => write!(f, "WarningType::EmptyBlockAnchorName"),
395 WarningType::InvalidBlockAnchorName => write!(f, "WarningType::InvalidBlockAnchorName"),
396
397 WarningType::AttributeValueIsLocked(value) => f
398 .debug_tuple("WarningType::AttributeValueIsLocked")
399 .field(value)
400 .finish(),
401
402 WarningType::DuplicateId(id) => {
403 f.debug_tuple("WarningType::DuplicateId").field(id).finish()
404 }
405
406 WarningType::Level0SectionHeadingNotSupported => {
407 write!(f, "WarningType::Level0SectionHeadingNotSupported")
408 }
409
410 WarningType::SectionHeadingLevelSkipped(expected, found) => f
411 .debug_tuple("WarningType::SectionHeadingLevelSkipped")
412 .field(expected)
413 .field(found)
414 .finish(),
415
416 WarningType::SectionHeadingLevelExceedsMaximum(found) => f
417 .debug_tuple("WarningType::SectionHeadingLevelExceedsMaximum")
418 .field(found)
419 .finish(),
420
421 WarningType::SectionHeadingLevelOutOfRange(computed, clamped) => f
422 .debug_tuple("WarningType::SectionHeadingLevelOutOfRange")
423 .field(computed)
424 .field(clamped)
425 .finish(),
426
427 WarningType::LeveloffsetExcludesAllHeadingLevels(offset) => f
428 .debug_tuple("WarningType::LeveloffsetExcludesAllHeadingLevels")
429 .field(offset)
430 .finish(),
431
432 WarningType::ListItemOutOfSequence(expected, actual) => f
433 .debug_tuple("WarningType::ListItemOutOfSequence")
434 .field(expected)
435 .field(actual)
436 .finish(),
437
438 WarningType::NoCalloutFound(number) => f
439 .debug_tuple("WarningType::NoCalloutFound")
440 .field(number)
441 .finish(),
442
443 WarningType::CalloutListItemOutOfSequence(expected, actual) => f
444 .debug_tuple("WarningType::CalloutListItemOutOfSequence")
445 .field(expected)
446 .field(actual)
447 .finish(),
448
449 WarningType::TableCellExceedsColumnCount => {
450 write!(f, "WarningType::TableCellExceedsColumnCount")
451 }
452
453 WarningType::TableCsvDataHasUnclosedQuote => {
454 write!(f, "WarningType::TableCsvDataHasUnclosedQuote")
455 }
456
457 WarningType::TableMissingLeadingSeparator => {
458 write!(f, "WarningType::TableMissingLeadingSeparator")
459 }
460
461 WarningType::TableIncompleteRowAtEndOfTable => {
462 write!(f, "WarningType::TableIncompleteRowAtEndOfTable")
463 }
464
465 WarningType::SkippingReferenceToMissingAttribute(name) => f
466 .debug_tuple("WarningType::SkippingReferenceToMissingAttribute")
467 .field(name)
468 .finish(),
469
470 WarningType::InvalidSubstitutionTypeForStemMacro(subs) => f
471 .debug_tuple("WarningType::InvalidSubstitutionTypeForStemMacro")
472 .field(subs)
473 .finish(),
474
475 WarningType::InvalidSubstitutionTypeForPassthroughMacro(subs) => f
476 .debug_tuple("WarningType::InvalidSubstitutionTypeForPassthroughMacro")
477 .field(subs)
478 .finish(),
479
480 WarningType::InvalidSubstitutionTypeForBlock(subs) => f
481 .debug_tuple("WarningType::InvalidSubstitutionTypeForBlock")
482 .field(subs)
483 .finish(),
484
485 WarningType::InvalidFootnoteReference(id) => f
486 .debug_tuple("WarningType::InvalidFootnoteReference")
487 .field(id)
488 .finish(),
489
490 WarningType::DeprecatedFootnoterefMacro(macro_text) => f
491 .debug_tuple("WarningType::DeprecatedFootnoterefMacro")
492 .field(macro_text)
493 .finish(),
494
495 WarningType::IncludeFileNotFound(target) => f
496 .debug_tuple("WarningType::IncludeFileNotFound")
497 .field(target)
498 .finish(),
499
500 WarningType::IncludeFileNotReadable(target) => f
501 .debug_tuple("WarningType::IncludeFileNotReadable")
502 .field(target)
503 .finish(),
504
505 WarningType::IncludeDroppedDueToMissingAttribute(directive) => f
506 .debug_tuple("WarningType::IncludeDroppedDueToMissingAttribute")
507 .field(directive)
508 .finish(),
509
510 WarningType::MaxIncludeDepthExceeded(depth) => f
511 .debug_tuple("WarningType::MaxIncludeDepthExceeded")
512 .field(depth)
513 .finish(),
514
515 WarningType::MaxBlockNestingExceeded(depth) => f
516 .debug_tuple("WarningType::MaxBlockNestingExceeded")
517 .field(depth)
518 .finish(),
519
520 WarningType::NonUtf8IncludeEncoding(encoding) => f
521 .debug_tuple("WarningType::NonUtf8IncludeEncoding")
522 .field(encoding)
523 .finish(),
524
525 WarningType::MalformedConditionalDirective(reason, directive) => f
526 .debug_tuple("WarningType::MalformedConditionalDirective")
527 .field(reason)
528 .field(directive)
529 .finish(),
530
531 WarningType::UnmatchedConditionalDirective(directive) => f
532 .debug_tuple("WarningType::UnmatchedConditionalDirective")
533 .field(directive)
534 .finish(),
535
536 WarningType::MismatchedConditionalDirective(directive) => f
537 .debug_tuple("WarningType::MismatchedConditionalDirective")
538 .field(directive)
539 .finish(),
540
541 WarningType::UnterminatedConditionalDirective(directive) => f
542 .debug_tuple("WarningType::UnterminatedConditionalDirective")
543 .field(directive)
544 .finish(),
545
546 WarningType::IncludeTagNotFound(tag) => f
547 .debug_tuple("WarningType::IncludeTagNotFound")
548 .field(tag)
549 .finish(),
550
551 WarningType::IncludeTagUnclosed(tag) => f
552 .debug_tuple("WarningType::IncludeTagUnclosed")
553 .field(tag)
554 .finish(),
555
556 WarningType::IncludeTagMismatchedEnd(expected, found) => f
557 .debug_tuple("WarningType::IncludeTagMismatchedEnd")
558 .field(expected)
559 .field(found)
560 .finish(),
561
562 WarningType::IncludeTagUnexpectedEnd(tag) => f
563 .debug_tuple("WarningType::IncludeTagUnexpectedEnd")
564 .field(tag)
565 .finish(),
566
567 WarningType::AbstractBlockInBookWithoutDoctitle => {
568 write!(f, "WarningType::AbstractBlockInBookWithoutDoctitle")
569 }
570
571 WarningType::PossibleInvalidReference(target) => f
572 .debug_tuple("WarningType::PossibleInvalidReference")
573 .field(target)
574 .finish(),
575
576 WarningType::UnsafeLinkSchemeRejected(target) => f
577 .debug_tuple("WarningType::UnsafeLinkSchemeRejected")
578 .field(target)
579 .finish(),
580 }
581 }
582}
583
584#[derive(Clone, Debug, Eq, PartialEq)]
586pub(crate) struct MatchAndWarnings<'src, T> {
587 pub(crate) item: T,
590
591 pub(crate) warnings: Vec<Warning<'src>>,
593}
594
595impl<T> MatchAndWarnings<'_, T> {
596 #[cfg(test)]
597 #[inline(always)]
598 #[track_caller]
599 #[allow(clippy::panic)] pub(crate) fn unwrap_if_no_warnings(self) -> T {
601 if self.warnings.is_empty() {
602 self.item
603 } else {
604 panic!(
605 "expected self.warnings to be empty\n\nfound warnings = {warnings:#?}\n",
606 warnings = self.warnings
607 );
608 }
609 }
610}
611
612#[cfg(test)]
613mod tests {
614 #![allow(clippy::unwrap_used)]
615
616 mod warning {
617 use crate::warnings::{Warning, WarningType};
618
619 #[test]
620 fn impl_clone() {
621 let w1 = Warning {
623 source: crate::Span::new("abc"),
624 warning: WarningType::EmptyAttributeValue,
625 origin: None,
626 };
627
628 let w2 = w1.clone();
629 assert_eq!(w1, w2);
630 }
631 }
632
633 mod warning_type {
634 mod impl_debug {
635 use crate::warnings::WarningType;
636
637 #[test]
638 fn attribute_value_missing_terminating_quote() {
639 let warning = WarningType::AttributeValueMissingTerminatingQuote;
640 let debug_output = format!("{:?}", warning);
641 assert_eq!(
642 debug_output,
643 "WarningType::AttributeValueMissingTerminatingQuote"
644 );
645 }
646
647 #[test]
648 fn document_header_not_terminated() {
649 let warning = WarningType::DocumentHeaderNotTerminated;
650 let debug_output = format!("{:?}", warning);
651 assert_eq!(debug_output, "WarningType::DocumentHeaderNotTerminated");
652 }
653
654 #[test]
655 fn no_inline_doctype_candidate() {
656 let warning = WarningType::NoInlineDoctypeCandidate;
657 let debug_output = format!("{:?}", warning);
658 assert_eq!(debug_output, "WarningType::NoInlineDoctypeCandidate");
659 }
660
661 #[test]
662 fn empty_attribute_value() {
663 let warning = WarningType::EmptyAttributeValue;
664 let debug_output = format!("{:?}", warning);
665 assert_eq!(debug_output, "WarningType::EmptyAttributeValue");
666 }
667
668 #[test]
669 fn empty_shorthand_name() {
670 let warning = WarningType::EmptyShorthandName;
671 let debug_output = format!("{:?}", warning);
672 assert_eq!(debug_output, "WarningType::EmptyShorthandName");
673 }
674
675 #[test]
676 fn invalid_macro_name() {
677 let warning = WarningType::InvalidMacroName;
678 let debug_output = format!("{:?}", warning);
679 assert_eq!(debug_output, "WarningType::InvalidMacroName");
680 }
681
682 #[test]
683 fn media_macro_missing_target() {
684 let warning = WarningType::MediaMacroMissingTarget;
685 let debug_output = format!("{:?}", warning);
686 assert_eq!(debug_output, "WarningType::MediaMacroMissingTarget");
687 }
688
689 #[test]
690 fn macro_missing_attribute_list() {
691 let warning = WarningType::MacroMissingAttributeList;
692 let debug_output = format!("{:?}", warning);
693 assert_eq!(debug_output, "WarningType::MacroMissingAttributeList");
694 }
695
696 #[test]
697 fn macro_missing_separator() {
698 let warning = WarningType::MacroMissingSeparator;
699 let debug_output = format!("{:?}", warning);
700 assert_eq!(debug_output, "WarningType::MacroMissingSeparator");
701 }
702
703 #[test]
704 fn missing_comma_after_quoted_attribute_value() {
705 let warning = WarningType::MissingCommaAfterQuotedAttributeValue;
706 let debug_output = format!("{:?}", warning);
707 assert_eq!(
708 debug_output,
709 "WarningType::MissingCommaAfterQuotedAttributeValue"
710 );
711 }
712
713 #[test]
714 fn unterminated_delimited_block() {
715 let warning = WarningType::UnterminatedDelimitedBlock;
716 let debug_output = format!("{:?}", warning);
717 assert_eq!(debug_output, "WarningType::UnterminatedDelimitedBlock");
718 }
719
720 #[test]
721 fn missing_block_after_title_or_attribute_list() {
722 let warning = WarningType::MissingBlockAfterTitleOrAttributeList;
723 let debug_output = format!("{:?}", warning);
724 assert_eq!(
725 debug_output,
726 "WarningType::MissingBlockAfterTitleOrAttributeList"
727 );
728 }
729
730 #[test]
731 fn empty_block_anchor_name() {
732 let warning = WarningType::EmptyBlockAnchorName;
733 let debug_output = format!("{:?}", warning);
734 assert_eq!(debug_output, "WarningType::EmptyBlockAnchorName");
735 }
736
737 #[test]
738 fn invalid_block_anchor_name() {
739 let warning = WarningType::InvalidBlockAnchorName;
740 let debug_output = format!("{:?}", warning);
741 assert_eq!(debug_output, "WarningType::InvalidBlockAnchorName");
742 }
743
744 #[test]
745 fn attribute_value_is_locked_simple_string() {
746 let warning = WarningType::AttributeValueIsLocked("test-attribute".to_string());
747 let debug_output = format!("{:?}", warning);
748 assert_eq!(
749 debug_output,
750 "WarningType::AttributeValueIsLocked(\"test-attribute\")"
751 );
752 }
753
754 #[test]
755 fn attribute_value_is_locked_empty_string() {
756 let warning = WarningType::AttributeValueIsLocked("".to_string());
757 let debug_output = format!("{:?}", warning);
758 assert_eq!(debug_output, "WarningType::AttributeValueIsLocked(\"\")");
759 }
760
761 #[test]
762 fn attribute_value_is_locked_string_with_special_chars() {
763 let warning =
764 WarningType::AttributeValueIsLocked("attr-with-special!@#$%^&*()".to_string());
765 let debug_output = format!("{:?}", warning);
766 assert_eq!(
767 debug_output,
768 "WarningType::AttributeValueIsLocked(\"attr-with-special!@#$%^&*()\")"
769 );
770 }
771
772 #[test]
773 fn attribute_value_is_locked_string_with_quotes() {
774 let warning = WarningType::AttributeValueIsLocked("attr\"with'quotes".to_string());
775 let debug_output = format!("{:?}", warning);
776 assert_eq!(
777 debug_output,
778 "WarningType::AttributeValueIsLocked(\"attr\\\"with'quotes\")"
779 );
780 }
781
782 #[test]
783 fn attribute_value_is_locked_string_with_newlines() {
784 let warning =
785 WarningType::AttributeValueIsLocked("attr\nwith\nnewlines".to_string());
786 let debug_output = format!("{:?}", warning);
787 assert_eq!(
788 debug_output,
789 "WarningType::AttributeValueIsLocked(\"attr\\nwith\\nnewlines\")"
790 );
791 }
792
793 #[test]
794 fn duplicate_id() {
795 let warning = WarningType::DuplicateId("foo".to_owned());
796 let debug_output = format!("{:?}", warning);
797 assert_eq!(debug_output, "WarningType::DuplicateId(\"foo\")");
798 }
799
800 #[test]
801 fn level0_section_heading_not_supported() {
802 let warning = WarningType::Level0SectionHeadingNotSupported;
803 let debug_output = format!("{:?}", warning);
804 assert_eq!(
805 debug_output,
806 "WarningType::Level0SectionHeadingNotSupported"
807 );
808 }
809
810 #[test]
811 fn section_heading_level_skipped() {
812 let warning = WarningType::SectionHeadingLevelSkipped(2, 4);
813 let debug_output = format!("{:?}", warning);
814 assert_eq!(
815 debug_output,
816 "WarningType::SectionHeadingLevelSkipped(2, 4)"
817 );
818 }
819
820 #[test]
821 fn section_heading_level_exceeds_maximum() {
822 let warning = WarningType::SectionHeadingLevelExceedsMaximum(6);
823 let debug_output = format!("{:?}", warning);
824 assert_eq!(
825 debug_output,
826 "WarningType::SectionHeadingLevelExceedsMaximum(6)"
827 );
828 }
829
830 #[test]
831 fn section_heading_level_out_of_range() {
832 let warning = WarningType::SectionHeadingLevelOutOfRange(-3, 1);
833 let debug_output = format!("{:?}", warning);
834 assert_eq!(
835 debug_output,
836 "WarningType::SectionHeadingLevelOutOfRange(-3, 1)"
837 );
838 }
839
840 #[test]
841 fn leveloffset_excludes_all_heading_levels() {
842 let warning = WarningType::LeveloffsetExcludesAllHeadingLevels(2147483647);
843 let debug_output = format!("{:?}", warning);
844 assert_eq!(
845 debug_output,
846 "WarningType::LeveloffsetExcludesAllHeadingLevels(2147483647)"
847 );
848 }
849
850 #[test]
851 fn list_item_out_of_sequence() {
852 let warning = WarningType::ListItemOutOfSequence("y".to_string(), "z".to_string());
853 let debug_output = format!("{:?}", warning);
854 assert_eq!(
855 debug_output,
856 "WarningType::ListItemOutOfSequence(\"y\", \"z\")"
857 );
858 }
859
860 #[test]
861 fn no_callout_found() {
862 let warning = WarningType::NoCalloutFound(2);
863 let debug_output = format!("{:?}", warning);
864 assert_eq!(debug_output, "WarningType::NoCalloutFound(2)");
865 }
866
867 #[test]
868 fn callout_list_item_out_of_sequence() {
869 let warning = WarningType::CalloutListItemOutOfSequence(2, 3);
870 let debug_output = format!("{:?}", warning);
871 assert_eq!(
872 debug_output,
873 "WarningType::CalloutListItemOutOfSequence(2, 3)"
874 );
875 }
876
877 #[test]
878 fn table_cell_exceeds_column_count() {
879 let warning = WarningType::TableCellExceedsColumnCount;
880 let debug_output = format!("{:?}", warning);
881 assert_eq!(debug_output, "WarningType::TableCellExceedsColumnCount");
882 }
883
884 #[test]
885 fn table_csv_data_has_unclosed_quote() {
886 let warning = WarningType::TableCsvDataHasUnclosedQuote;
887 let debug_output = format!("{:?}", warning);
888 assert_eq!(debug_output, "WarningType::TableCsvDataHasUnclosedQuote");
889 }
890
891 #[test]
892 fn table_missing_leading_separator() {
893 let warning = WarningType::TableMissingLeadingSeparator;
894 let debug_output = format!("{:?}", warning);
895 assert_eq!(debug_output, "WarningType::TableMissingLeadingSeparator");
896 }
897
898 #[test]
899 fn table_incomplete_row_at_end_of_table() {
900 let warning = WarningType::TableIncompleteRowAtEndOfTable;
901 let debug_output = format!("{:?}", warning);
902 assert_eq!(debug_output, "WarningType::TableIncompleteRowAtEndOfTable");
903 }
904
905 #[test]
906 fn skipping_reference_to_missing_attribute() {
907 let warning = WarningType::SkippingReferenceToMissingAttribute("name".to_string());
908 let debug_output = format!("{:?}", warning);
909 assert_eq!(
910 debug_output,
911 "WarningType::SkippingReferenceToMissingAttribute(\"name\")"
912 );
913 }
914
915 #[test]
916 fn invalid_substitution_type_for_stem_macro() {
917 let warning = WarningType::InvalidSubstitutionTypeForStemMacro("bogus".to_string());
918 let debug_output = format!("{:?}", warning);
919 assert_eq!(
920 debug_output,
921 "WarningType::InvalidSubstitutionTypeForStemMacro(\"bogus\")"
922 );
923 }
924
925 #[test]
926 fn invalid_substitution_type_for_passthrough_macro() {
927 let warning =
928 WarningType::InvalidSubstitutionTypeForPassthroughMacro("bogus".to_string());
929 let debug_output = format!("{:?}", warning);
930 assert_eq!(
931 debug_output,
932 "WarningType::InvalidSubstitutionTypeForPassthroughMacro(\"bogus\")"
933 );
934 }
935
936 #[test]
937 fn invalid_substitution_type_for_block() {
938 let warning = WarningType::InvalidSubstitutionTypeForBlock("bogus".to_string());
939 let debug_output = format!("{:?}", warning);
940 assert_eq!(
941 debug_output,
942 "WarningType::InvalidSubstitutionTypeForBlock(\"bogus\")"
943 );
944 }
945
946 #[test]
947 fn invalid_footnote_reference() {
948 let warning = WarningType::InvalidFootnoteReference("fn1".to_string());
949 let debug_output = format!("{:?}", warning);
950 assert_eq!(
951 debug_output,
952 "WarningType::InvalidFootnoteReference(\"fn1\")"
953 );
954 }
955
956 #[test]
957 fn deprecated_footnoteref_macro() {
958 let warning =
959 WarningType::DeprecatedFootnoterefMacro("footnoteref:[fn1]".to_string());
960 let debug_output = format!("{:?}", warning);
961 assert_eq!(
962 debug_output,
963 "WarningType::DeprecatedFootnoterefMacro(\"footnoteref:[fn1]\")"
964 );
965 }
966
967 #[test]
968 fn include_file_not_found() {
969 let warning = WarningType::IncludeFileNotFound("content.adoc".to_string());
970 let debug_output = format!("{:?}", warning);
971 assert_eq!(
972 debug_output,
973 "WarningType::IncludeFileNotFound(\"content.adoc\")"
974 );
975 }
976
977 #[test]
978 fn include_file_not_readable() {
979 let warning = WarningType::IncludeFileNotReadable("content.adoc".to_string());
980 let debug_output = format!("{:?}", warning);
981 assert_eq!(
982 debug_output,
983 "WarningType::IncludeFileNotReadable(\"content.adoc\")"
984 );
985 }
986
987 #[test]
988 fn include_dropped_due_to_missing_attribute() {
989 let warning = WarningType::IncludeDroppedDueToMissingAttribute(
990 "include::{foodir}/include-file.adoc[]".to_string(),
991 );
992
993 let debug_output = format!("{:?}", warning);
994
995 assert_eq!(
996 debug_output,
997 "WarningType::IncludeDroppedDueToMissingAttribute(\"include::{foodir}/include-file.adoc[]\")"
998 );
999 }
1000
1001 #[test]
1002 fn max_include_depth_exceeded() {
1003 let warning = WarningType::MaxIncludeDepthExceeded(64);
1004 let debug_output = format!("{:?}", warning);
1005 assert_eq!(debug_output, "WarningType::MaxIncludeDepthExceeded(64)");
1006 }
1007
1008 #[test]
1009 fn max_block_nesting_exceeded() {
1010 let warning = WarningType::MaxBlockNestingExceeded(64);
1011 let debug_output = format!("{:?}", warning);
1012 assert_eq!(debug_output, "WarningType::MaxBlockNestingExceeded(64)");
1013 }
1014
1015 #[test]
1016 fn non_utf8_include_encoding() {
1017 let warning = WarningType::NonUtf8IncludeEncoding("iso-8859-1".to_string());
1018 let debug_output = format!("{:?}", warning);
1019 assert_eq!(
1020 debug_output,
1021 "WarningType::NonUtf8IncludeEncoding(\"iso-8859-1\")"
1022 );
1023 }
1024
1025 #[test]
1026 fn malformed_conditional_directive() {
1027 let warning = WarningType::MalformedConditionalDirective(
1028 "missing target".to_string(),
1029 "ifdef::[]".to_string(),
1030 );
1031 let debug_output = format!("{:?}", warning);
1032 assert_eq!(
1033 debug_output,
1034 "WarningType::MalformedConditionalDirective(\"missing target\", \"ifdef::[]\")"
1035 );
1036 }
1037
1038 #[test]
1039 fn unmatched_conditional_directive() {
1040 let warning =
1041 WarningType::UnmatchedConditionalDirective("endif::on-quest[]".to_string());
1042 let debug_output = format!("{:?}", warning);
1043 assert_eq!(
1044 debug_output,
1045 "WarningType::UnmatchedConditionalDirective(\"endif::on-quest[]\")"
1046 );
1047 }
1048
1049 #[test]
1050 fn mismatched_conditional_directive() {
1051 let warning =
1052 WarningType::MismatchedConditionalDirective("endif::on-journey[]".to_string());
1053 let debug_output = format!("{:?}", warning);
1054 assert_eq!(
1055 debug_output,
1056 "WarningType::MismatchedConditionalDirective(\"endif::on-journey[]\")"
1057 );
1058 }
1059
1060 #[test]
1061 fn unterminated_conditional_directive() {
1062 let warning =
1063 WarningType::UnterminatedConditionalDirective("ifdef::on-quest[]".to_string());
1064 let debug_output = format!("{:?}", warning);
1065 assert_eq!(
1066 debug_output,
1067 "WarningType::UnterminatedConditionalDirective(\"ifdef::on-quest[]\")"
1068 );
1069 }
1070
1071 #[test]
1072 fn include_tag_not_found() {
1073 let warning = WarningType::IncludeTagNotFound("tag 'no-such-tag'".to_string());
1074 let debug_output = format!("{:?}", warning);
1075 assert_eq!(
1076 debug_output,
1077 "WarningType::IncludeTagNotFound(\"tag 'no-such-tag'\")"
1078 );
1079 }
1080
1081 #[test]
1082 fn include_tag_unclosed() {
1083 let warning = WarningType::IncludeTagUnclosed("'a'".to_string());
1084 let debug_output = format!("{:?}", warning);
1085 assert_eq!(debug_output, "WarningType::IncludeTagUnclosed(\"'a'\")");
1086 }
1087
1088 #[test]
1089 fn include_tag_mismatched_end() {
1090 let warning =
1091 WarningType::IncludeTagMismatchedEnd("'b'".to_string(), "'a'".to_string());
1092 let debug_output = format!("{:?}", warning);
1093 assert_eq!(
1094 debug_output,
1095 "WarningType::IncludeTagMismatchedEnd(\"'b'\", \"'a'\")"
1096 );
1097 }
1098
1099 #[test]
1100 fn include_tag_unexpected_end() {
1101 let warning = WarningType::IncludeTagUnexpectedEnd("'a'".to_string());
1102 let debug_output = format!("{:?}", warning);
1103 assert_eq!(
1104 debug_output,
1105 "WarningType::IncludeTagUnexpectedEnd(\"'a'\")"
1106 );
1107 }
1108
1109 #[test]
1110 fn abstract_block_in_book_without_doctitle() {
1111 let warning = WarningType::AbstractBlockInBookWithoutDoctitle;
1112 let debug_output = format!("{:?}", warning);
1113 assert_eq!(
1114 debug_output,
1115 "WarningType::AbstractBlockInBookWithoutDoctitle"
1116 );
1117 }
1118
1119 #[test]
1120 fn possible_invalid_reference() {
1121 let warning = WarningType::PossibleInvalidReference("foobaz".to_string());
1122 let debug_output = format!("{:?}", warning);
1123 assert_eq!(
1124 debug_output,
1125 "WarningType::PossibleInvalidReference(\"foobaz\")"
1126 );
1127 }
1128
1129 #[test]
1130 fn unsafe_link_scheme_rejected() {
1131 let warning =
1132 WarningType::UnsafeLinkSchemeRejected("javascript:alert(1)".to_string());
1133 let debug_output = format!("{:?}", warning);
1134 assert_eq!(
1135 debug_output,
1136 "WarningType::UnsafeLinkSchemeRejected(\"javascript:alert(1)\")"
1137 );
1138 }
1139 }
1140 }
1141
1142 mod match_and_warnings {
1143 use crate::warnings::{MatchAndWarnings, Warning, WarningType};
1144
1145 #[test]
1146 fn impl_clone() {
1147 let maw1 = MatchAndWarnings {
1149 item: "xyz",
1150 warnings: vec![Warning {
1151 source: crate::Span::new("abc"),
1152 warning: WarningType::EmptyAttributeValue,
1153 origin: None,
1154 }],
1155 };
1156
1157 let maw2 = maw1.clone();
1158 assert_eq!(maw1, maw2);
1159 }
1160
1161 #[test]
1162 fn unwrap_if_no_warnings() {
1163 let maw = MatchAndWarnings {
1164 item: "xyz",
1165 warnings: vec![],
1166 };
1167
1168 let item = maw.unwrap_if_no_warnings();
1169 assert_eq!(item, "xyz");
1170 }
1171
1172 #[test]
1173 #[should_panic]
1174 fn unwrap_if_no_warnings_panic() {
1175 let maw = MatchAndWarnings {
1176 item: "xyz",
1177 warnings: vec![Warning {
1178 source: crate::Span::new("abc"),
1179 warning: WarningType::EmptyAttributeValue,
1180 origin: None,
1181 }],
1182 };
1183
1184 let _ = maw.unwrap_if_no_warnings();
1185
1186 }
1188 }
1189}