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