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