@@ -10,12 +10,12 @@ Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
1010using System . IO ;
1111using System . Linq ;
1212using System . Xml . Linq ;
13+ using System . Diagnostics . CodeAnalysis ;
1314
1415#nullable enable
1516
1617namespace Yarn . Unity . ActionAnalyser
1718{
18-
1919 static class EnumerableExtensions
2020 {
2121 private struct Comparer < TItem , TKey > : IEqualityComparer < TItem >
@@ -462,10 +462,10 @@ public static IEnumerable<Action> GetActions(CSharpCompilation compilation, Synt
462462 return Array . Empty < Action > ( ) ;
463463 }
464464
465- return GetAttributeActions ( root , model , logger ) . Concat ( GetRuntimeDefinedActions ( root , model ) ) ;
465+ return GetAttributeActions ( root , model , logger ) . Concat ( GetRuntimeDefinedActions ( root , model , logger ) ) ;
466466 }
467467
468- private static IEnumerable < Action > GetRuntimeDefinedActions ( CompilationUnitSyntax root , SemanticModel model )
468+ private static IEnumerable < Action > GetRuntimeDefinedActions ( CompilationUnitSyntax root , SemanticModel model , ILogger ? logger )
469469 {
470470 var classes = root . DescendantNodes ( ) . OfType < ClassDeclarationSyntax > ( ) ;
471471 classes = classes . Where ( c =>
@@ -565,7 +565,7 @@ private static IEnumerable<Action> GetRuntimeDefinedActions(CompilationUnitSynta
565565
566566 var declaringSyntax = targetSymbol . DeclaringSyntaxReferences . FirstOrDefault ( ) ? . GetSyntax ( ) ;
567567
568- TryGetDocumentation ( targetSymbol , out XElement ? documentationXML , out string ? summary ) ;
568+ TryGetDocumentation ( targetSymbol , logger , out XElement ? documentationXML , out string ? summary ) ;
569569
570570 yield return new Action ( name , methodCall . Type , targetSymbol )
571571 {
@@ -581,24 +581,88 @@ private static IEnumerable<Action> GetRuntimeDefinedActions(CompilationUnitSynta
581581 }
582582 }
583583
584- private static bool TryGetDocumentation ( IMethodSymbol targetSymbol , out XElement ? documentationXML , out string ? summary )
584+ private static bool TryGetDocumentation ( IMethodSymbol targetSymbol , ILogger ? logger , out XElement ? documentationXML , out string ? summary )
585585 {
586586 documentationXML = null ;
587587 summary = null ;
588- try
588+
589+ var documentationComments = targetSymbol . GetDocumentationCommentXml ( ) ;
590+ if ( string . IsNullOrEmpty ( documentationComments ) )
591+ {
592+ documentationComments = null ;
593+ logger ? . WriteLine ( $ "Unable to find any xml documentation for { targetSymbol . Name } , attempting to load it syntactically instead.") ;
594+
595+ foreach ( var reference in targetSymbol . DeclaringSyntaxReferences )
596+ {
597+ var method = reference . GetSyntax ( ) as MethodDeclarationSyntax ;
598+ if ( method != null )
599+ {
600+ var comment = GetActionTrivia ( method , logger ) ;
601+ if ( ! string . IsNullOrEmpty ( comment ) )
602+ {
603+ documentationComments = comment ;
604+ break ;
605+ }
606+ }
607+ }
608+ }
609+
610+ // at this point we still don't have a doc string
611+ // going to have to just give up
612+ if ( documentationComments == null || string . IsNullOrWhiteSpace ( documentationComments ) )
613+ {
614+ logger ? . WriteLine ( $ "Unable to find any xml documentation for { targetSymbol . Name } , syntactically either.") ;
615+ return false ;
616+ }
617+ logger ? . WriteLine ( $ "Found a potential documentation candidate:\" { documentationComments } \" ") ;
618+
619+ // there are three different situations:
620+ // 1. This is a correctly structured docs string that has come from GetDocumentationCommentXml
621+ if ( TryGetXMLFromDocumentString ( documentationComments , out documentationXML , logger ) )
622+ {
623+ var summaryNode = documentationXML ? . Element ( "summary" ) ;
624+ if ( summaryNode != null )
625+ {
626+ summary = string . Join ( "" , summaryNode . DescendantNodes ( ) . OfType < XText > ( ) . Select ( n => n . ToString ( ) ) ) . Trim ( ) ;
627+ logger ? . WriteLine ( "Found the GetDocumentationCommentXml comments and parsed it successfully" ) ;
628+
629+ return true ;
630+ }
631+ }
632+
633+ // 2. This is a syntactically determined string that happens to also be XML, but it will be missing the synthesised member root
634+ // so we add the missing root node on and try again
635+ if ( TryGetXMLFromDocumentString ( $ "<member name=\" M:{ targetSymbol . ToString ( ) } \" >{ documentationComments } </member>", out documentationXML , logger ) )
589636 {
590- var documentationComments = targetSymbol . GetDocumentationCommentXml ( ) ;
591- documentationXML = XElement . Parse ( documentationComments ) ;
592- var summaryNode = documentationXML . Element ( "summary" ) ;
637+ // so we wrap this node and try again
638+ var summaryNode = documentationXML ? . Element ( "summary" ) ;
593639 if ( summaryNode != null )
594640 {
595641 summary = string . Join ( "" , summaryNode . DescendantNodes ( ) . OfType < XText > ( ) . Select ( n => n . ToString ( ) ) ) . Trim ( ) ;
642+ logger ? . WriteLine ( "Found the unrooted XML comments and parsed it successfully" ) ;
643+
644+ return true ;
596645 }
646+ }
647+
648+ // 3. This is not doc XML and just happens to be a comment above a command/function
649+ summary = documentationComments ;
650+ logger ? . WriteLine ( "Unable to determine XML, returning the comment as is" ) ;
651+ return true ;
652+ }
653+
654+ private static bool TryGetXMLFromDocumentString ( string comment , out XElement ? element , ILogger ? logger )
655+ {
656+ try
657+ {
658+ element = XElement . Parse ( comment ) ;
597659 return true ;
598660 }
599- catch ( System . Xml . XmlException )
661+ catch ( System . Xml . XmlException ex )
600662 {
601- // XML parse error; no documentation available
663+ logger ? . WriteLine ( "Failed to parse comments as XML" ) ;
664+ logger ? . WriteException ( ex ) ;
665+ element = null ;
602666 return false ;
603667 }
604668 }
@@ -677,7 +741,7 @@ private static IEnumerable<Action> GetAttributeActions(CompilationUnitSyntax roo
677741 continue ;
678742 }
679743
680- TryGetDocumentation ( methodSymbol , out XElement ? documentationXML , out string ? summary ) ;
744+ TryGetDocumentation ( methodSymbol , logger , out XElement ? documentationXML , out string ? summary ) ;
681745
682746 var containerName = container ? . ToDisplayString ( SymbolDisplayFormat . FullyQualifiedFormat ) ?? "<unknown>" ;
683747
@@ -729,10 +793,19 @@ private static AsyncType GetAsyncType(IMethodSymbol symbol)
729793 return AsyncType . MaybeAsyncCoroutine ;
730794 }
731795
732- // If it's anything else, then this action is invalid. Return the
733- // default value; other parts of the action detection process will throw
734- // errors.
735- return default ;
796+ // now checking for the various different awaiter types
797+ // later on it might be worth seeing if there is a good way to check if the return type is something that can be awaited
798+ // but we only have four types so it's probably fine this way
799+ switch ( returnType . ToDisplayString ( SymbolDisplayFormat . FullyQualifiedFormat ) )
800+ {
801+ case "global::Yarn.Unity.YarnTask" :
802+ case "global::System.Threading.Tasks.Task" :
803+ case "global::Cysharp.Threading.Tasks.UniTask" : // double check this one
804+ case "global::UnityEngine.Awaitable" :
805+ return AsyncType . AsyncTask ;
806+ default :
807+ return default ;
808+ } ;
736809 }
737810
738811 private static IEnumerable < Parameter > GetParameters ( IMethodSymbol symbol , XElement ? documentationXML )
@@ -754,7 +827,6 @@ private static IEnumerable<Parameter> GetParameters(IMethodSymbol symbol, XEleme
754827 if ( ! parameterDocumentation . ContainsKey ( name . Value ) )
755828 {
756829 parameterDocumentation . Add ( name . Value , text ) ;
757-
758830 }
759831 }
760832 }
@@ -881,7 +953,7 @@ internal static IEnumerable<string> GetSourceFiles(string sourcePath)
881953
882954 // these are basically just ripped straight from the LSP
883955 // should maybe look at making these more accessible, for now the code dupe is fine IMO
884- public static string ? GetActionTrivia ( MethodDeclarationSyntax method , ILogger logger )
956+ public static string ? GetActionTrivia ( MethodDeclarationSyntax method , ILogger ? logger )
885957 {
886958 // The main string to use as the function's documentation.
887959 if ( method . HasLeadingTrivia )
@@ -892,24 +964,25 @@ internal static IEnumerable<string> GetSourceFiles(string sourcePath)
892964 {
893965 // The method contains structured trivia. Extract the
894966 // documentation for it.
895- logger . WriteLine ( "trivia is structured" ) ;
967+ logger ? . WriteLine ( $ "trivia for { method . Identifier } is structured") ;
896968 return GetDocumentationFromStructuredTrivia ( structuredTrivia ) ;
897969 }
898970 else
899971 {
900972 // There isn't any structured trivia, but perhaps there's a
901973 // comment above the method, which we can use as our
902974 // documentation.
903- logger . WriteLine ( "trivia is unstructured" ) ;
904- return GetDocumentationFromUnstructuredTrivia ( trivias ) ;
975+ logger ? . WriteLine ( $ "trivia for { method . Identifier } is unstructured") ;
976+ return GetDocumentationFromUnstructuredTrivia ( trivias , logger ) ;
905977 }
906978 }
907979 else
908980 {
981+ logger ? . WriteLine ( $ "{ method . Identifier } has no trivia") ;
909982 return null ;
910983 }
911984 }
912- private static string GetDocumentationFromUnstructuredTrivia ( SyntaxTriviaList trivias )
985+ private static string GetDocumentationFromUnstructuredTrivia ( SyntaxTriviaList trivias , ILogger ? logger )
913986 {
914987 string documentation ;
915988 bool emptyLineFlag = false ;
@@ -923,7 +996,11 @@ private static string GetDocumentationFromUnstructuredTrivia(SyntaxTriviaList tr
923996 {
924997 case SyntaxKind . EndOfLineTrivia :
925998 // if we hit two lines in a row without a comment/attribute inbetween, we're done collecting trivia
926- if ( emptyLineFlag == true ) { doneWithTrivia = true ; }
999+ if ( emptyLineFlag == true )
1000+ {
1001+ logger ? . WriteLine ( "have hit two empty lines in a row, done collecting unstructured trivia" ) ;
1002+ doneWithTrivia = true ;
1003+ }
9271004 emptyLineFlag = true ;
9281005 break ;
9291006 case SyntaxKind . WhitespaceTrivia :
0 commit comments