Skip to content

Commit 804c28c

Browse files
committed
made the ysls generator hopefully more resiliant
1 parent dfe6d5d commit 804c28c

File tree

8 files changed

+109
-216
lines changed

8 files changed

+109
-216
lines changed

Editor/Analysis/Action.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,12 @@ Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
1313

1414
namespace Yarn.Unity.ActionAnalyser
1515
{
16-
1716
public struct Position
1817
{
1918
public int Line;
2019
public int Column;
2120
}
2221

23-
2422
public struct Range
2523
{
2624
public Position Start;
@@ -45,7 +43,6 @@ public static implicit operator Range(FileLinePositionSpan span)
4543
}
4644
}
4745

48-
4946
public enum ActionType
5047
{
5148
/// <summary>
@@ -101,6 +98,11 @@ public enum AsyncType
10198
/// The action operates asynchronously using a coroutine.
10299
/// </summary>
103100
AsyncCoroutine,
101+
102+
/// <summary>
103+
/// The action operates asynchronously through c# async infrastructure
104+
/// </summary>
105+
AsyncTask,
104106
}
105107

106108
static class ITypeSymbolExtension
@@ -236,6 +238,7 @@ public string ToJSON()
236238
result["Documentation"] = this.Description;
237239
}
238240
result["Language"] = "csharp";
241+
result["Async"] = this.AsyncType != AsyncType.Sync;
239242

240243
if (this.Declaration != null)
241244
{

Editor/Analysis/ActionsRegistrationGenerator~/ActionsGenerator.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,6 @@ public void Execute(GeneratorExecutionContext context)
164164
return;
165165
}
166166

167-
168167
// Don't generate source code for certain Yarn Spinner provided
169168
// assemblies - these always manually register any actions in them.
170169
var prefixesToIgnore = new List<string>()

Editor/Analysis/Analyser.cs

Lines changed: 100 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
1010
using System.IO;
1111
using System.Linq;
1212
using System.Xml.Linq;
13+
using System.Diagnostics.CodeAnalysis;
1314

1415
#nullable enable
1516

1617
namespace 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:

Editor/AssemblyInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
1010
[assembly: AssemblyInformationalVersion("3.1.4.Branch.hotfix/textanim-build-errors.Sha.c2b119c5eda7fdd3cd0b13a689f95d54d456fb69")]
1111

1212
[assembly: InternalsVisibleTo("YarnSpinner.Unity.Editor")]
13-
[assembly: InternalsVisibleTo("YarnSpinner.Editor.Tests")]
13+
[assembly: InternalsVisibleTo("YarnSpinner.Unity.Tests.Editor")]

Runtime/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
88
[assembly: AssemblyVersion("3.1.4.0")]
99
[assembly: AssemblyFileVersion("3.1.4.0")]
1010
[assembly: AssemblyInformationalVersion("3.1.4.Branch.hotfix/textanim-build-errors.Sha.c2b119c5eda7fdd3cd0b13a689f95d54d456fb69")]
11-
[assembly: InternalsVisibleTo("YarnSpinnerTests")]
12-
[assembly: InternalsVisibleTo("YarnSpinner.Editor.Tests")]
11+
[assembly: InternalsVisibleTo("YarnSpinner.Unity.Tests")]
12+
[assembly: InternalsVisibleTo("YarnSpinner.Unity.Tests.Editor")]
1313
[assembly: InternalsVisibleTo("YarnSpinner.Unity.Editor")]
1414

2.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)