1+ using System . Text . RegularExpressions ;
12using XafLogicExplainer . Core . Generators ;
23using XafLogicExplainer . Core . Models ;
34
@@ -19,6 +20,11 @@ namespace XafLogicExplainer.Tests;
1920/// pipe tables, fenced code, lists, bold, inline code — maps to a real Word equivalent, so this one
2021/// call site was the whole difference between an extraction and a document someone can hand over.
2122/// </para>
23+ /// <para>
24+ /// The second offender was found the same way, by him, one release later: a generic base type was
25+ /// printed bare, and <c><DetailView></c> is an <em>inline</em> tag rather than a block. The
26+ /// guard below missed it because it was written to the shape of the first one.
27+ /// </para>
2228/// </remarks>
2329public class PortableMarkdownTests
2430{
@@ -32,6 +38,7 @@ private static readonly (string Name, ExtractedProject Project)[] Samples =
3238 ( "DeepXpo" , SampleProjects . DeepXpo ) ,
3339 ( "AuditedXpo" , SampleProjects . AuditedXpo ) ,
3440 ( "Demo" , SampleProjects . Demo ) ,
41+ ( "Walkthrough" , SampleProjects . Walkthrough ) ,
3542 ] ;
3643
3744 private static string Markdown ( ExtractedProject project , string language ) =>
@@ -41,10 +48,20 @@ private static string Markdown(ExtractedProject project, string language) =>
4148 . Replace ( "\r " , "" ) ;
4249
4350 /// <summary>
44- /// Lines that begin a CommonMark HTML block: outside a fence, a line whose first character is
45- /// <c><</c>. Inside a fence the same line is source code and is left alone, which is why this
46- /// tracks the fence rather than matching the whole document at once.
51+ /// Every tag CommonMark would treat as HTML, wherever on the line it sits.
4752 /// </summary>
53+ /// <remarks>
54+ /// The first version checked only whether a line <em>opened</em> with <c><</c>, which was the
55+ /// shape of the <c><details></c> block it was written for. A generic type in the middle of
56+ /// a sentence walked straight past it: <c>ViewController<DetailView></c> is an inline HTML
57+ /// tag to every CommonMark parser, so the reader was shown <c>ViewController</c> and nothing
58+ /// else — on github.com, where the sanitizer strips the unknown tag, as much as in an export.
59+ /// <para>
60+ /// Two things are excluded rather than matched. A fenced block is source code, where
61+ /// <c>CreateObject<Customer>()</c> is exactly right. An inline code span is the remedy
62+ /// itself, so it has to be allowed or the guard would reject its own fix.
63+ /// </para>
64+ /// </remarks>
4865 private static List < string > RawHtmlLines ( string markdown )
4966 {
5067 var offenders = new List < string > ( ) ;
@@ -58,7 +75,12 @@ private static List<string> RawHtmlLines(string markdown)
5875 continue ;
5976 }
6077
61- if ( ! insideFence && line . TrimStart ( ) . StartsWith ( '<' ) )
78+ if ( insideFence )
79+ continue ;
80+
81+ var bare = Regex . Replace ( line , "`[^`]*`" , "" ) ;
82+
83+ if ( Regex . IsMatch ( bare , "</?[A-Za-z][A-Za-z0-9-]*[^<>]*/?>" ) )
6284 offenders . Add ( line . Trim ( ) ) ;
6385 }
6486
@@ -75,11 +97,25 @@ public void NoGeneratedPageOpensALineWithRawHtml(string language)
7597 var offenders = RawHtmlLines ( Markdown ( project , language ) ) ;
7698
7799 Assert . True ( offenders . Count == 0 ,
78- $ "{ name } ({ language } ) emits raw HTML outside a code fence, which renders as literal "
79- + $ "text everywhere but a browser: { string . Join ( " | " , offenders ) } ") ;
100+ $ "{ name } ({ language } ) emits a tag CommonMark reads as HTML. A block renders as "
101+ + "literal text everywhere but a browser; an inline tag is dropped and takes the "
102+ + $ "type name with it: { string . Join ( " | " , offenders ) } ") ;
80103 }
81104 }
82105
106+ [ Fact ]
107+ public void AGenericTypeSurvivesBecauseItIsWrittenAsCode ( )
108+ {
109+ // Found by @MBrekhof pointing a real Word converter at the output. `ViewController<DetailView>`
110+ // was printed bare, and `<DetailView>` is an inline HTML tag to every CommonMark parser: an
111+ // export drops it and github.com's sanitizer strips it, so the reader is told the base class
112+ // is `ViewController`. That is a different answer rather than a missing one.
113+ var english = Markdown ( SampleProjects . Xpo , "en" ) ;
114+
115+ Assert . Contains ( "`ViewController<DetailView>`" , english , StringComparison . Ordinal ) ;
116+ Assert . DoesNotContain ( "** ViewController<DetailView>" , english , StringComparison . Ordinal ) ;
117+ }
118+
83119 [ Fact ]
84120 public void TheSeedSourceIsIntroducedByAHeadingRatherThanAFold ( )
85121 {
0 commit comments