6
6
using System . Diagnostics ;
7
7
using System . Diagnostics . CodeAnalysis ;
8
8
using System . Linq ;
9
+ using System . Xml . Linq ;
9
10
using ApiDocsSync . PortToTripleSlash . Docs ;
10
11
using Microsoft . CodeAnalysis ;
11
12
using Microsoft . CodeAnalysis . CSharp ;
@@ -164,10 +165,14 @@ internal class TripleSlashSyntaxRewriter : CSharpSyntaxRewriter
164
165
private const string Space = " " ;
165
166
private const string NewLine = "\n " ;
166
167
168
+ private static readonly char [ ] _NewLineSeparators = [ '\n ' , '\r ' ] ;
169
+ private const StringSplitOptions _NewLineSplitOptions = StringSplitOptions . RemoveEmptyEntries | StringSplitOptions . TrimEntries ;
170
+
167
171
private DocsCommentsContainer DocsComments { get ; }
168
172
private ResolvedLocation Location { get ; }
169
173
private SemanticModel Model => Location . Model ;
170
174
175
+
171
176
public TripleSlashSyntaxRewriter ( DocsCommentsContainer docsComments , ResolvedLocation resolvedLocation ) : base ( visitIntoStructuredTrivia : false )
172
177
{
173
178
DocsComments = docsComments ;
@@ -422,105 +427,117 @@ internal static SyntaxList<SyntaxNode> GetOrCreateXmlNodes(IDocsAPI api, SyntaxL
422
427
{
423
428
List < SyntaxNode > updated = new ( ) ;
424
429
425
- if ( TryGetOrCreateXmlNode ( originalXmls , SummaryTag , api . Summary , attributeValue : null , out XmlNodeSyntax ? summaryNode , out _ ) )
430
+ if ( TryGetOrCreateXmlRows ( originalXmls , SummaryTag , api . Summary , attributeValue : null , indentationTrivia , out List < XmlNodeSyntax > summaryNodes , out _ ) )
426
431
{
427
- updated . AddRange ( GetXmlRow ( summaryNode , indentationTrivia ) ) ;
432
+ updated . AddRange ( summaryNodes ) ;
428
433
}
429
434
430
- if ( TryGetOrCreateXmlNode ( originalXmls , ValueTag , api . Value , attributeValue : null , out XmlNodeSyntax ? valueNode , out _ ) )
435
+ if ( TryGetOrCreateXmlRows ( originalXmls , ValueTag , api . Value , attributeValue : null , indentationTrivia , out List < XmlNodeSyntax > valueNodes , out _ ) )
431
436
{
432
- updated . AddRange ( GetXmlRow ( valueNode , indentationTrivia ) ) ;
437
+ updated . AddRange ( valueNodes ) ;
433
438
}
434
439
435
440
foreach ( DocsTypeParam typeParam in api . TypeParams )
436
441
{
437
- if ( TryGetOrCreateXmlNode ( originalXmls , TypeParamTag , typeParam . Value , attributeValue : typeParam . Name , out XmlNodeSyntax ? typeParamNode , out _ ) )
442
+ if ( TryGetOrCreateXmlRows ( originalXmls , TypeParamTag , typeParam . Value , attributeValue : typeParam . Name , indentationTrivia , out List < XmlNodeSyntax > typeParamNodes , out _ ) )
438
443
{
439
- updated . AddRange ( GetXmlRow ( typeParamNode , indentationTrivia ) ) ;
444
+ updated . AddRange ( typeParamNodes ) ;
440
445
}
441
446
}
442
447
443
448
foreach ( DocsParam param in api . Params )
444
449
{
445
- if ( TryGetOrCreateXmlNode ( originalXmls , ParamTag , param . Value , attributeValue : param . Name , out XmlNodeSyntax ? paramNode , out _ ) )
450
+ if ( TryGetOrCreateXmlRows ( originalXmls , ParamTag , param . Value , attributeValue : param . Name , indentationTrivia , out List < XmlNodeSyntax > paramNodes , out _ ) )
446
451
{
447
- updated . AddRange ( GetXmlRow ( paramNode , indentationTrivia ) ) ;
452
+ updated . AddRange ( paramNodes ) ;
448
453
}
449
454
}
450
455
451
- if ( TryGetOrCreateXmlNode ( originalXmls , ReturnsTag , api . Returns , attributeValue : null , out XmlNodeSyntax ? returnsNode , out _ ) )
456
+ if ( TryGetOrCreateXmlRows ( originalXmls , ReturnsTag , api . Returns , attributeValue : null , indentationTrivia , out List < XmlNodeSyntax > returnsNodes , out _ ) )
452
457
{
453
- updated . AddRange ( GetXmlRow ( returnsNode , indentationTrivia ) ) ;
458
+ updated . AddRange ( returnsNodes ) ;
454
459
}
455
460
456
461
foreach ( DocsException exception in api . Exceptions )
457
462
{
458
- if ( TryGetOrCreateXmlNode ( originalXmls , ExceptionTag , exception . Value , attributeValue : exception . Cref [ 2 ..] , out XmlNodeSyntax ? exceptionNode , out _ ) )
463
+ if ( TryGetOrCreateXmlRows ( originalXmls , ExceptionTag , exception . Value , attributeValue : exception . Cref [ 2 ..] , indentationTrivia , out List < XmlNodeSyntax > exceptionNodes , out _ ) )
459
464
{
460
- updated . AddRange ( GetXmlRow ( exceptionNode , indentationTrivia ) ) ;
465
+ updated . AddRange ( exceptionNodes ) ;
461
466
}
462
467
}
463
468
464
- if ( TryGetOrCreateXmlNode ( originalXmls , RemarksTag , api . Remarks , attributeValue : null , out XmlNodeSyntax ? remarksNode , out bool isBackported ) &&
469
+ if ( TryGetOrCreateXmlRows ( originalXmls , RemarksTag , api . Remarks , attributeValue : null , indentationTrivia , out List < XmlNodeSyntax > remarksNodes , out bool isBackported ) &&
465
470
( ! isBackported || ( isBackported && ! skipRemarks ) ) )
466
471
{
467
- updated . AddRange ( GetXmlRow ( remarksNode ! , indentationTrivia ) ) ;
472
+ updated . AddRange ( remarksNodes ) ;
468
473
}
469
474
470
475
return new SyntaxList < SyntaxNode > ( updated ) ;
471
476
}
472
477
473
- private static IEnumerable < XmlNodeSyntax > GetXmlRow ( XmlNodeSyntax item , SyntaxTrivia ? indentationTrivia )
478
+ private static bool TryGetOrCreateXmlRows ( SyntaxList < XmlNodeSyntax > originalXmls , string tagName ,
479
+ string apiDocsText , string ? attributeValue , SyntaxTrivia ? indentationTrivia , out List < XmlNodeSyntax > rows , out bool isBackported )
474
480
{
475
- yield return GetIndentationNode ( indentationTrivia ) ;
476
- yield return GetTripleSlashNode ( ) ;
477
- yield return item ;
478
- yield return GetNewLineNode ( ) ;
479
- }
481
+ rows = [ ] ;
480
482
481
- private static bool TryGetOrCreateXmlNode ( SyntaxList < XmlNodeSyntax > originalXmls , string tagName ,
482
- string apiDocsText , string ? attributeValue , [ NotNullWhen ( returnValue : true ) ] out XmlNodeSyntax ? node , out bool isBackported )
483
- {
484
- SyntaxTokenList contentTokens ;
483
+ if ( apiDocsText . IsDocsEmpty ( ) )
484
+ {
485
+ isBackported = false ;
485
486
486
- isBackported = false ;
487
+ // Not yet documented in api docs, so try to see if it was already documented in triple slash
488
+ XmlNodeSyntax ? xmlNode = originalXmls . FirstOrDefault ( xmlNode => DoesNodeHaveTag ( xmlNode , tagName ) ) ;
487
489
488
- if ( ! apiDocsText . IsDocsEmpty ( ) )
489
- {
490
- isBackported = true ;
490
+ if ( xmlNode != null )
491
+ {
492
+ rows . Add ( xmlNode ) ;
493
+ return true ;
494
+ }
495
+
496
+ // We don't want to add an empty xml item. We want don't want to add one in this case, it needs
497
+ // to be missing on purpose so the developer sees the build error and adds it manually.
498
+
499
+ return false ;
500
+ }
491
501
502
+ isBackported = true ;
503
+
504
+ List < XmlNodeSyntax > nodes = new ( ) ;
505
+
506
+ bool first = true ;
507
+ foreach ( string line in apiDocsText . Split ( _NewLineSeparators , _NewLineSplitOptions ) )
508
+ {
492
509
// Overwrite the current triple slash with the text that comes from api docs
493
510
SyntaxToken textLiteral = SyntaxFactory . XmlTextLiteral (
494
511
leading : SyntaxFactory . TriviaList ( ) ,
495
- text : apiDocsText ,
496
- value : apiDocsText ,
512
+ text : line ,
513
+ value : line ,
497
514
trailing : SyntaxFactory . TriviaList ( ) ) ;
498
515
499
- contentTokens = SyntaxFactory . TokenList ( textLiteral ) ;
500
- }
501
- else
502
- {
503
- // Not yet documented in api docs, so try to see if it was documented in triple slash
504
- XmlNodeSyntax ? xmlNode = originalXmls . FirstOrDefault ( xmlNode => DoesNodeHasTag ( xmlNode , tagName ) ) ;
516
+ XmlTextSyntax xmlText = SyntaxFactory . XmlText ( ) . WithTextTokens ( SyntaxFactory . TokenList ( textLiteral ) ) ;
505
517
506
- if ( xmlNode != null )
518
+ if ( first )
507
519
{
508
- XmlElementSyntax xmlElement = ( XmlElementSyntax ) xmlNode ;
509
- XmlTextSyntax xmlText = ( XmlTextSyntax ) xmlElement . Content . Single ( ) ;
510
- contentTokens = xmlText . TextTokens ;
520
+ nodes . Add ( xmlText ) ;
521
+ first = false ;
511
522
}
512
523
else
513
524
{
514
- // We don't want to add an empty xml item. We want don't want to add one in this case, it needs
515
- // to be missing on purpose so the developer sees the build error and adds it manually.
516
- node = null ;
517
- return false ;
525
+ IEnumerable < XmlNodeSyntax > content = GetXmlRow ( xmlText , indentationTrivia ) ;
526
+ nodes . AddRange ( content ) ;
518
527
}
519
528
}
520
529
521
- node = CreateXmlNode ( tagName , contentTokens , attributeValue ) ;
530
+ rows . AddRange ( GetXmlRow ( CreateXmlNode ( tagName , nodes . ToArray ( ) , attributeValue ) , indentationTrivia ) ) ;
531
+
522
532
return true ;
523
533
}
534
+ private static IEnumerable < XmlNodeSyntax > GetXmlRow ( XmlNodeSyntax item , SyntaxTrivia ? indentationTrivia )
535
+ {
536
+ yield return GetIndentationNode ( indentationTrivia ) ;
537
+ yield return GetTripleSlashNode ( ) ;
538
+ yield return item ;
539
+ yield return GetNewLineNode ( ) ;
540
+ }
524
541
525
542
private static XmlTextSyntax GetTripleSlashNode ( )
526
543
{
@@ -566,10 +583,8 @@ private static XmlTextSyntax GetNewLineNode()
566
583
return SyntaxFactory . XmlText ( ) . WithTextTokens ( SyntaxFactory . TokenList ( tokens ) ) ;
567
584
}
568
585
569
- private static XmlElementSyntax CreateXmlNode ( string tagName , SyntaxTokenList contentTokens , string ? attributeValue = null )
586
+ private static XmlElementSyntax CreateXmlNode ( string tagName , XmlNodeSyntax [ ] content , string ? attributeValue = null )
570
587
{
571
- SyntaxList < XmlNodeSyntax > content = SyntaxFactory . SingletonList < XmlNodeSyntax > ( SyntaxFactory . XmlText ( ) . WithTextTokens ( contentTokens ) ) ;
572
-
573
588
XmlElementSyntax result ;
574
589
575
590
switch ( tagName )
@@ -614,7 +629,7 @@ private static XmlElementSyntax CreateXmlNode(string tagName, SyntaxTokenList co
614
629
return result ;
615
630
}
616
631
617
- private static XmlElementSyntax GetXmlAttributedElement ( SyntaxList < XmlNodeSyntax > content , string tagName , string attributeName , string attributeValue )
632
+ private static XmlElementSyntax GetXmlAttributedElement ( IEnumerable < XmlNodeSyntax > content , string tagName , string attributeName , string attributeValue )
618
633
{
619
634
Debug . Assert ( ! string . IsNullOrWhiteSpace ( tagName ) ) ;
620
635
Debug . Assert ( ! string . IsNullOrWhiteSpace ( attributeName ) ) ;
@@ -639,10 +654,10 @@ private static XmlElementSyntax GetXmlAttributedElement(SyntaxList<XmlNodeSyntax
639
654
640
655
XmlElementEndTagSyntax endTag = SyntaxFactory . XmlElementEndTag ( SyntaxFactory . XmlName ( SyntaxFactory . Identifier ( tagName ) ) ) ;
641
656
642
- return SyntaxFactory . XmlElement ( startTag , content , endTag ) ;
657
+ return SyntaxFactory . XmlElement ( startTag , SyntaxFactory . List ( content ) , endTag ) ;
643
658
}
644
659
645
- private static bool DoesNodeHasTag ( SyntaxNode xmlNode , string tagName )
660
+ private static bool DoesNodeHaveTag ( SyntaxNode xmlNode , string tagName )
646
661
{
647
662
if ( tagName == ExceptionTag )
648
663
{
0 commit comments