同步
This commit is contained in:
1025
Packages/dev.yarnspinner.unity/Editor/Analysis/Action.cs
Normal file
1025
Packages/dev.yarnspinner.unity/Editor/Analysis/Action.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ad1e0218baeab43be922ae40dbfb3ade
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
|
||||
*/
|
||||
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Yarn.Unity;
|
||||
|
||||
namespace Yarn.Unity.Editor
|
||||
{
|
||||
public static class ActionSourceCodeGenerator
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Get a path in the current project that can be used for storing
|
||||
/// manually-generated Yarn Action registration code.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property checks to see if a file exists in the Assets folder
|
||||
/// that is both named "YarnActionRegistration.cs", and contains a
|
||||
/// marker indicating that it was generated by Yarn Spinner's code
|
||||
/// generation systems. If this is found, the path to the file is
|
||||
/// returned. Otherwise, the path
|
||||
/// <c>Assets/YarnActionRegistration.cs</c> is returned.
|
||||
/// </remarks>
|
||||
public static string GeneratedSourcePath
|
||||
{
|
||||
get
|
||||
{
|
||||
const string YarnRegistrationFileName = "YarnActionRegistration.cs";
|
||||
const string DefaultOutputFilePath = "Assets/" + YarnRegistrationFileName;
|
||||
|
||||
// Note the lack of a closing parenthesis in this string - we
|
||||
// only want to check to see if it was generated by
|
||||
// "YarnActionAnalyzer", not any specific version of that
|
||||
// analyzer
|
||||
const string YarnGeneratedCodeSignature = "GeneratedCode(\"YarnActionAnalyzer\"";
|
||||
|
||||
var existingFile = System.IO.Directory.EnumerateFiles(System.Environment.CurrentDirectory, YarnRegistrationFileName, System.IO.SearchOption.AllDirectories).FirstOrDefault();
|
||||
|
||||
if (existingFile == null)
|
||||
{
|
||||
return DefaultOutputFilePath;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var text = System.IO.File.ReadAllText(existingFile);
|
||||
return text.Contains(YarnGeneratedCodeSignature)
|
||||
? existingFile
|
||||
: DefaultOutputFilePath;
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
// Something happened while checking the file. Return
|
||||
// our default, and log that we encountered a problem.
|
||||
Debug.LogWarning($"Can't check to see if {existingFile} is a valid action registration script, using {DefaultOutputFilePath} instead: {e}");
|
||||
return DefaultOutputFilePath;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates and imports a C# source code file in the project
|
||||
/// containing Yarn Action registration code at the path indicated by
|
||||
/// <see cref="GeneratedSourcePath"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method should not be called in projects where Unity has support
|
||||
/// for source generators (i.e. Unity 2021.2 and later).
|
||||
/// </remarks>
|
||||
public static void GenerateYarnActionSourceCode()
|
||||
{
|
||||
var analysis = new Yarn.Unity.ActionAnalyser.Analyser("Assets");
|
||||
try
|
||||
{
|
||||
var actions = analysis.GetActions();
|
||||
var source = Yarn.Unity.ActionAnalyser.Analyser.GenerateRegistrationFileSource(actions);
|
||||
|
||||
var path = GeneratedSourcePath;
|
||||
|
||||
System.IO.File.WriteAllText(path, source);
|
||||
UnityEditor.AssetDatabase.ImportAsset(path);
|
||||
|
||||
Debug.Log($"Generated Yarn command and function registration code at {path}");
|
||||
}
|
||||
catch (Yarn.Unity.ActionAnalyser.AnalyserException e)
|
||||
{
|
||||
Debug.LogError($"Error generating source code: " + e.InnerException.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dabf0558de47a486199baa9e568783ce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,625 @@
|
||||
/*
|
||||
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Yarn.Unity.ActionAnalyser;
|
||||
using YarnAction = Yarn.Unity.ActionAnalyser.Action;
|
||||
|
||||
#nullable enable
|
||||
|
||||
[Generator]
|
||||
public class ActionRegistrationSourceGenerator : ISourceGenerator
|
||||
{
|
||||
const string YarnSpinnerUnityAssemblyName = "YarnSpinner.Unity";
|
||||
const string DebugLoggingPreprocessorSymbol = "YARN_SOURCE_GENERATION_DEBUG_LOGGING";
|
||||
const string IncludeTestCommands = "YARN_INCLUDE_TEST_COMMANDS";
|
||||
const string MinimumUnityVersionPreprocessorSymbol = "UNITY_2021_2_OR_NEWER";
|
||||
|
||||
public static string? GetProjectRoot(GeneratorExecutionContext context)
|
||||
{
|
||||
// We need to know if the settings are configured to not perform codegen
|
||||
// to link attributed methods. This is kinda annoying because the path
|
||||
// root of the project settings and the root path of this process are
|
||||
// *very* different. So, what we do is we use the included Compilation
|
||||
// Assembly additional file that Unity gives us. This file, if opened,
|
||||
// has the path of the Unity project, which we can then use to get the
|
||||
// settings. If any stage of this fails, then we bail out and assume
|
||||
// that codegen is desired.
|
||||
|
||||
// Try and find any additional files passed to the context
|
||||
if (!context.AdditionalFiles.Any())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// One of those files is (AssemblyName).[Unity]AdditionalFile.txt, and it
|
||||
// contains the path to the project
|
||||
var relevantFiles = context.AdditionalFiles.Where(
|
||||
i => i.Path.Contains($"{context.Compilation.AssemblyName}.AdditionalFile.txt")
|
||||
|| i.Path.Contains($"{context.Compilation.AssemblyName}.UnityAdditionalFile.txt")
|
||||
);
|
||||
|
||||
if (!relevantFiles.Any())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var assemblyRelevantFile = relevantFiles.First();
|
||||
|
||||
// The file needs to exist on disk
|
||||
if (!File.Exists(assemblyRelevantFile.Path))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Attempt to read it - it should contain the path to the project directory
|
||||
var projectPath = File.ReadAllText(assemblyRelevantFile.Path);
|
||||
if (Directory.Exists(projectPath))
|
||||
{
|
||||
// If this directory exists, we're done
|
||||
return projectPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// We encountered a problem while testing
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Execute(GeneratorExecutionContext context)
|
||||
{
|
||||
using var output = GetOutput(context);
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
output.WriteLine(DateTime.Now);
|
||||
|
||||
Yarn.Unity.Editor.YarnSpinnerProjectSettings? settings = null;
|
||||
var projectPath = GetProjectRoot(context);
|
||||
|
||||
if (projectPath != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fullPath = Path.Combine(projectPath, Yarn.Unity.Editor.YarnSpinnerProjectSettings.YarnSpinnerProjectSettingsPath);
|
||||
output.WriteLine($"Attempting to read settings file at {fullPath}");
|
||||
|
||||
settings = Yarn.Unity.Editor.YarnSpinnerProjectSettings.GetOrCreateSettings(projectPath, output);
|
||||
if (!settings.automaticallyLinkAttributedYarnCommandsAndFunctions)
|
||||
{
|
||||
output.WriteLine("Skipping codegen due to settings.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
output.WriteLine($"Unable to determine Yarn settings, settings values will be ignored and codegen will occur: {e.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
output.WriteLine($"Unable to determine project location on disk. Settings values will be ignored and codegen will occur");
|
||||
}
|
||||
|
||||
bool hasCriticalActionErrors = false;
|
||||
try
|
||||
{
|
||||
output.WriteLine("Source code generation for assembly " + context.Compilation.AssemblyName);
|
||||
|
||||
if (context.AdditionalFiles.Any())
|
||||
{
|
||||
output.WriteLine($"Additional files:");
|
||||
foreach (var item in context.AdditionalFiles)
|
||||
{
|
||||
output.WriteLine(" " + item.Path);
|
||||
}
|
||||
}
|
||||
|
||||
output.WriteLine("Referenced assemblies for this compilation:");
|
||||
foreach (var referencedAssembly in context.Compilation.ReferencedAssemblyNames)
|
||||
{
|
||||
output.WriteLine(" - " + referencedAssembly.Name);
|
||||
}
|
||||
|
||||
bool compilationReferencesYarnSpinner = context.Compilation.ReferencedAssemblyNames
|
||||
.Any(name => name.Name == YarnSpinnerUnityAssemblyName);
|
||||
|
||||
if (compilationReferencesYarnSpinner == false)
|
||||
{
|
||||
// This compilation doesn't reference YarnSpinner.Unity. Any
|
||||
// code that we generate that references symbols in that
|
||||
// assembly won't work.
|
||||
output.WriteLine($"Assembly {context.Compilation.AssemblyName} doesn't reference {YarnSpinnerUnityAssemblyName}. Not generating any code for it.");
|
||||
return;
|
||||
}
|
||||
|
||||
output.WriteLine("Preprocessor Symbols: ");
|
||||
foreach (var symbol in context.ParseOptions.PreprocessorSymbolNames)
|
||||
{
|
||||
output.WriteLine("- " + symbol);
|
||||
}
|
||||
|
||||
// Don't generate source code if we're not targeting at least Unity
|
||||
// 2021.2. (Unity will not invoke this DLL as a source code
|
||||
// generator until at least this version, but other tools like
|
||||
// OmniSharp might.)
|
||||
if (!context.ParseOptions.PreprocessorSymbolNames.Contains(MinimumUnityVersionPreprocessorSymbol))
|
||||
{
|
||||
output.WriteLine($"Not generating code for assembly {context.Compilation.AssemblyName} because this assembly is not being built for Unity 2021.2 or newer");
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't generate source code for certain Yarn Spinner provided
|
||||
// assemblies - these always manually register any actions in them.
|
||||
var prefixesToIgnore = new List<string>()
|
||||
{
|
||||
"YarnSpinner.Unity",
|
||||
"YarnSpinner.Editor",
|
||||
};
|
||||
|
||||
// But DO generate source code for the Samples assembly and the Test assembly
|
||||
var prefixesToKeep = new List<string>()
|
||||
{
|
||||
"YarnSpinner.Unity.Samples",
|
||||
};
|
||||
|
||||
// Additionally, if we're building for unit tests, include the Yarn
|
||||
// Spinner unit tests assembly.
|
||||
if (context.ParseOptions.PreprocessorSymbolNames.Contains(IncludeTestCommands))
|
||||
{
|
||||
prefixesToKeep.Add("YarnSpinner.Unity.Tests");
|
||||
}
|
||||
|
||||
if (context.Compilation.AssemblyName == null)
|
||||
{
|
||||
output.WriteLine("Not generating registration code, because the provided AssemblyName is null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (prefixesToIgnore.Any(prefix => context.Compilation.AssemblyName.StartsWith(prefix)) && !prefixesToKeep.Any(prefix => context.Compilation.AssemblyName.StartsWith(prefix)))
|
||||
{
|
||||
output.WriteLine($"Not generating registration code for {context.Compilation.AssemblyName}: we've been told to exclude it, because its name begins with one of these prefixes: {string.Join(", ", prefixesToIgnore)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(context.Compilation is CSharpCompilation compilation))
|
||||
{
|
||||
// This is not a C# compilation, so we can't do analysis.
|
||||
output.WriteLine($"Stopping code generation because compilation is not a {nameof(CSharpCompilation)}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var actions = new List<YarnAction>();
|
||||
foreach (var tree in compilation.SyntaxTrees)
|
||||
{
|
||||
actions.AddRange(Analyser.GetActions(compilation, tree, output));
|
||||
}
|
||||
|
||||
if (actions.Count() == 0)
|
||||
{
|
||||
output.WriteLine($"Didn't find any Yarn Actions in {context.Compilation.AssemblyName}. Not generating any source code for it.");
|
||||
return;
|
||||
}
|
||||
|
||||
// validating and logging all the actions
|
||||
foreach (var action in actions)
|
||||
{
|
||||
if (action == null)
|
||||
{
|
||||
output.WriteLine($"Action is null??");
|
||||
continue;
|
||||
}
|
||||
|
||||
var diagnostics = action.Validate(compilation, output);
|
||||
foreach (var diagnostic in diagnostics)
|
||||
{
|
||||
context.ReportDiagnostic(diagnostic);
|
||||
if (diagnostic.Severity == DiagnosticSeverity.Warning || diagnostic.Severity == DiagnosticSeverity.Error)
|
||||
{
|
||||
output.WriteLine($"Flagging '{action.Name}' ({action.MethodName}): {diagnostic}");
|
||||
action.ContainsErrors = true;
|
||||
|
||||
if (diagnostic.Severity == DiagnosticSeverity.Error)
|
||||
{
|
||||
hasCriticalActionErrors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Commands are parsed as whitespace, so spaces in the command name
|
||||
// would render the command un-callable.
|
||||
if (action.Name.Any(x => Char.IsWhiteSpace(x)))
|
||||
{
|
||||
var descriptor = new DiagnosticDescriptor(
|
||||
"YS1002",
|
||||
$"Yarn {action.Type} methods must have a valid name",
|
||||
"YarnCommand and YarnFunction methods follow existing ID rules for Yarn. \"{0}\" is invalid.",
|
||||
"Yarn Spinner",
|
||||
DiagnosticSeverity.Warning,
|
||||
true,
|
||||
"[YarnCommand] and [YarnFunction] attributed methods must follow Yarn ID rules so that Yarn scripts can reference them.",
|
||||
"https://docs.yarnspinner.dev/using-yarnspinner-with-unity/creating-commands-functions");
|
||||
context.ReportDiagnostic(Microsoft.CodeAnalysis.Diagnostic.Create(
|
||||
descriptor,
|
||||
action.Declaration?.GetLocation(),
|
||||
action.Name
|
||||
));
|
||||
action.ContainsErrors = true;
|
||||
output.WriteLine($"Action {action.MethodIdentifierName} will be flagged due to it's name {action.Name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
output.WriteLine($"Action {action.Name}: {action.SourceFileName}:{action.Declaration?.GetLocation()?.GetLineSpan().StartLinePosition.Line} ({action.Type})");
|
||||
}
|
||||
|
||||
if (hasCriticalActionErrors)
|
||||
{
|
||||
stopwatch.Stop();
|
||||
output.WriteLine($"Critical issues were encountered in the actions, aborting code generation, stopping analysis after {stopwatch.Elapsed.TotalMilliseconds}ms");
|
||||
return;
|
||||
}
|
||||
|
||||
output.Write($"Generating source code...");
|
||||
|
||||
var source = Analyser.GenerateRegistrationFileSource(actions);
|
||||
|
||||
output.WriteLine($"Done.");
|
||||
|
||||
SourceText sourceText = SourceText.From(source, Encoding.UTF8);
|
||||
|
||||
output.Write($"Writing generated source...");
|
||||
|
||||
DumpGeneratedFile(context, source);
|
||||
|
||||
output.WriteLine($"Done.");
|
||||
|
||||
context.AddSource($"YarnActionRegistration-{compilation.AssemblyName}.Generated.cs", sourceText);
|
||||
|
||||
if (settings != null)
|
||||
{
|
||||
if (settings.generateYSLSFile)
|
||||
{
|
||||
output.Write($"Generating ysls...");
|
||||
// generating the ysls
|
||||
|
||||
IEnumerable<string> commandJSON = actions.Where(a => a.Type == ActionType.Command).Select(a => a.ToJSON());
|
||||
IEnumerable<string> functionJSON = actions.Where(a => a.Type == ActionType.Function).Select(a => a.ToJSON());
|
||||
|
||||
var ysls = "{" +
|
||||
@"""version"":2," +
|
||||
$@"""commands"":[{string.Join(",", commandJSON)}]," +
|
||||
$@"""functions"":[{string.Join(",", functionJSON)}]" +
|
||||
"}";
|
||||
|
||||
output.WriteLine($"Done.");
|
||||
|
||||
if (!string.IsNullOrEmpty(projectPath))
|
||||
{
|
||||
output.Write($"Writing generated ysls...");
|
||||
|
||||
var fullPath = Path.Combine(projectPath, Yarn.Unity.Editor.YarnSpinnerProjectSettings.YarnSpinnerAssemblyGeneratedYSLSPath(compilation.AssemblyName));
|
||||
try
|
||||
{
|
||||
System.IO.File.WriteAllText(fullPath, ysls);
|
||||
output.WriteLine($"Done.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
output.WriteLine($"Unable to write ysls to disk: {e.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
output.WriteLine("unable to identify project path, ysls will not be written to disk");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
output.WriteLine($"skipping ysls generation due to settings");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
output.WriteLine($"skipping ysls generation due to settings not being found");
|
||||
}
|
||||
|
||||
stopwatch.Stop();
|
||||
output.WriteLine($"Source code generation completed in {stopwatch.Elapsed.TotalMilliseconds}ms");
|
||||
return;
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
output.WriteLine($"{e}");
|
||||
}
|
||||
}
|
||||
|
||||
private MethodDeclarationSyntax GenerateLoggingMethod(string methodName, string sourceExpression, string prefix)
|
||||
{
|
||||
return SyntaxFactory.MethodDeclaration(
|
||||
SyntaxFactory.PredefinedType(
|
||||
SyntaxFactory.Token(SyntaxKind.VoidKeyword)),
|
||||
SyntaxFactory.Identifier(methodName))
|
||||
.WithModifiers(
|
||||
SyntaxFactory.TokenList(
|
||||
new[]{
|
||||
SyntaxFactory.Token(SyntaxKind.PublicKeyword),
|
||||
SyntaxFactory.Token(SyntaxKind.StaticKeyword)}))
|
||||
.WithBody(
|
||||
SyntaxFactory.Block(
|
||||
SyntaxFactory.LocalDeclarationStatement(
|
||||
SyntaxFactory.VariableDeclaration(
|
||||
SyntaxFactory.GenericName(
|
||||
SyntaxFactory.Identifier("IEnumerable"))
|
||||
.WithTypeArgumentList(
|
||||
SyntaxFactory.TypeArgumentList(
|
||||
SyntaxFactory.SingletonSeparatedList<TypeSyntax>(
|
||||
SyntaxFactory.PredefinedType(
|
||||
SyntaxFactory.Token(SyntaxKind.StringKeyword))))))
|
||||
.WithVariables(
|
||||
SyntaxFactory.SingletonSeparatedList<VariableDeclaratorSyntax>(
|
||||
SyntaxFactory.VariableDeclarator(
|
||||
SyntaxFactory.Identifier("source"))
|
||||
.WithInitializer(
|
||||
SyntaxFactory.EqualsValueClause(
|
||||
SyntaxFactory.ParseExpression(sourceExpression)))))),
|
||||
SyntaxFactory.LocalDeclarationStatement(
|
||||
SyntaxFactory.VariableDeclaration(
|
||||
SyntaxFactory.IdentifierName(
|
||||
SyntaxFactory.Identifier(
|
||||
SyntaxFactory.TriviaList(),
|
||||
SyntaxKind.VarKeyword,
|
||||
"var",
|
||||
"var",
|
||||
SyntaxFactory.TriviaList())))
|
||||
.WithVariables(
|
||||
SyntaxFactory.SingletonSeparatedList<VariableDeclaratorSyntax>(
|
||||
SyntaxFactory.VariableDeclarator(
|
||||
SyntaxFactory.Identifier("prefix"))
|
||||
.WithInitializer(
|
||||
SyntaxFactory.EqualsValueClause(
|
||||
SyntaxFactory.LiteralExpression(
|
||||
SyntaxKind.StringLiteralExpression,
|
||||
SyntaxFactory.Literal(prefix))))))),
|
||||
SyntaxFactory.ExpressionStatement(
|
||||
SyntaxFactory.InvocationExpression(
|
||||
SyntaxFactory.MemberAccessExpression(
|
||||
SyntaxKind.SimpleMemberAccessExpression,
|
||||
SyntaxFactory.IdentifierName("Debug"),
|
||||
SyntaxFactory.IdentifierName("Log")
|
||||
)
|
||||
)
|
||||
.WithArgumentList(
|
||||
SyntaxFactory.ArgumentList(
|
||||
SyntaxFactory.SingletonSeparatedList<ArgumentSyntax>(
|
||||
SyntaxFactory.Argument(
|
||||
SyntaxFactory.InterpolatedStringExpression(
|
||||
SyntaxFactory.Token(SyntaxKind.InterpolatedVerbatimStringStartToken)
|
||||
)
|
||||
.WithContents(
|
||||
SyntaxFactory.List<InterpolatedStringContentSyntax>(
|
||||
new InterpolatedStringContentSyntax[]{
|
||||
SyntaxFactory.Interpolation(
|
||||
SyntaxFactory.IdentifierName("prefix")
|
||||
),
|
||||
SyntaxFactory.InterpolatedStringText()
|
||||
.WithTextToken(
|
||||
SyntaxFactory.Token(
|
||||
SyntaxFactory.TriviaList(),
|
||||
SyntaxKind.InterpolatedStringTextToken,
|
||||
" ",
|
||||
" ",
|
||||
SyntaxFactory.TriviaList()
|
||||
)
|
||||
),
|
||||
SyntaxFactory.Interpolation(
|
||||
SyntaxFactory.InvocationExpression(
|
||||
SyntaxFactory.MemberAccessExpression(
|
||||
SyntaxKind.SimpleMemberAccessExpression,
|
||||
SyntaxFactory.PredefinedType(
|
||||
SyntaxFactory.Token(SyntaxKind.StringKeyword)
|
||||
),
|
||||
SyntaxFactory.IdentifierName("Join")
|
||||
)
|
||||
)
|
||||
.WithArgumentList(
|
||||
SyntaxFactory.ArgumentList(
|
||||
SyntaxFactory.SeparatedList<ArgumentSyntax>(
|
||||
new SyntaxNodeOrToken[]{
|
||||
SyntaxFactory.Argument(
|
||||
SyntaxFactory.LiteralExpression(
|
||||
SyntaxKind.CharacterLiteralExpression,
|
||||
SyntaxFactory.Literal(';')
|
||||
)
|
||||
),
|
||||
SyntaxFactory.Token(SyntaxKind.CommaToken),
|
||||
SyntaxFactory.Argument(
|
||||
SyntaxFactory.IdentifierName("source")
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
.NormalizeWhitespace();
|
||||
}
|
||||
|
||||
public static MethodDeclarationSyntax GenerateSingleLogMethod(string methodName, string text, string prefix)
|
||||
{
|
||||
return SyntaxFactory.MethodDeclaration(
|
||||
SyntaxFactory.PredefinedType(
|
||||
SyntaxFactory.Token(SyntaxKind.VoidKeyword)
|
||||
),
|
||||
SyntaxFactory.Identifier(methodName)
|
||||
)
|
||||
.WithModifiers(
|
||||
SyntaxFactory.TokenList(
|
||||
new[]{
|
||||
SyntaxFactory.Token(SyntaxKind.PublicKeyword),
|
||||
SyntaxFactory.Token(SyntaxKind.StaticKeyword)
|
||||
}
|
||||
)
|
||||
)
|
||||
.WithBody(
|
||||
SyntaxFactory.Block(
|
||||
SyntaxFactory.SingletonList<StatementSyntax>(
|
||||
SyntaxFactory.ExpressionStatement(
|
||||
SyntaxFactory.InvocationExpression(
|
||||
SyntaxFactory.MemberAccessExpression(
|
||||
SyntaxKind.SimpleMemberAccessExpression,
|
||||
SyntaxFactory.IdentifierName("Debug"),
|
||||
SyntaxFactory.IdentifierName("Log")
|
||||
)
|
||||
)
|
||||
.WithArgumentList(
|
||||
SyntaxFactory.ArgumentList(
|
||||
SyntaxFactory.SingletonSeparatedList<ArgumentSyntax>(
|
||||
SyntaxFactory.Argument(
|
||||
SyntaxFactory.InterpolatedStringExpression(
|
||||
SyntaxFactory.Token(SyntaxKind.InterpolatedStringStartToken)
|
||||
)
|
||||
.WithContents(
|
||||
SyntaxFactory.List<InterpolatedStringContentSyntax>(
|
||||
new InterpolatedStringContentSyntax[]{
|
||||
SyntaxFactory.Interpolation(
|
||||
SyntaxFactory.LiteralExpression(
|
||||
SyntaxKind.StringLiteralExpression,
|
||||
SyntaxFactory.Literal(prefix)
|
||||
)
|
||||
),
|
||||
SyntaxFactory.InterpolatedStringText()
|
||||
.WithTextToken(
|
||||
SyntaxFactory.Token(
|
||||
SyntaxFactory.TriviaList(),
|
||||
SyntaxKind.InterpolatedStringTextToken,
|
||||
" ",
|
||||
" ",
|
||||
SyntaxFactory.TriviaList()
|
||||
)
|
||||
),
|
||||
SyntaxFactory.Interpolation(
|
||||
SyntaxFactory.LiteralExpression(
|
||||
SyntaxKind.StringLiteralExpression,
|
||||
SyntaxFactory.Literal(text)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
.NormalizeWhitespace();
|
||||
}
|
||||
|
||||
public void Initialize(GeneratorInitializationContext context)
|
||||
{
|
||||
context.RegisterForSyntaxNotifications(() => new ClassDeclarationSyntaxReceiver());
|
||||
}
|
||||
|
||||
static string GetTemporaryPath(GeneratorExecutionContext context)
|
||||
{
|
||||
string tempPath;
|
||||
var rootPath = GetProjectRoot(context);
|
||||
if (rootPath != null)
|
||||
{
|
||||
tempPath = Path.Combine(rootPath, "Logs", "Packages", "dev.yarnspinner.unity");
|
||||
}
|
||||
else
|
||||
{
|
||||
tempPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "dev.yarnspinner.logs");
|
||||
}
|
||||
|
||||
// we need to make the logs folder, but this can potentially fail
|
||||
// if it does fail then we will just chuck the logs inside the tmp folder
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(tempPath))
|
||||
{
|
||||
Directory.CreateDirectory(tempPath);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
tempPath = System.IO.Path.GetTempPath();
|
||||
}
|
||||
return tempPath;
|
||||
}
|
||||
|
||||
public Yarn.Unity.ILogger GetOutput(GeneratorExecutionContext context)
|
||||
{
|
||||
if (GetShouldLogToFile(context))
|
||||
{
|
||||
var tempPath = ActionRegistrationSourceGenerator.GetTemporaryPath(context);
|
||||
|
||||
var path = System.IO.Path.Combine(tempPath, $"{nameof(ActionRegistrationSourceGenerator)}-{context.Compilation.AssemblyName}.txt");
|
||||
var outFile = System.IO.File.Open(path, System.IO.FileMode.Create);
|
||||
|
||||
return new Yarn.Unity.FileLogger(new System.IO.StreamWriter(outFile));
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Yarn.Unity.NullLogger();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool GetShouldLogToFile(GeneratorExecutionContext context)
|
||||
{
|
||||
return context.ParseOptions.PreprocessorSymbolNames.Contains(DebugLoggingPreprocessorSymbol);
|
||||
}
|
||||
|
||||
public void DumpGeneratedFile(GeneratorExecutionContext context, string text)
|
||||
{
|
||||
if (GetShouldLogToFile(context))
|
||||
{
|
||||
var tempPath = ActionRegistrationSourceGenerator.GetTemporaryPath(context);
|
||||
var path = System.IO.Path.Combine(tempPath, $"{nameof(ActionRegistrationSourceGenerator)}-{context.Compilation.AssemblyName}.cs");
|
||||
System.IO.File.WriteAllText(path, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class ClassDeclarationSyntaxReceiver : ISyntaxReceiver
|
||||
{
|
||||
public List<ClassDeclarationSyntax> Classes { get; private set; } = new List<ClassDeclarationSyntax>();
|
||||
|
||||
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
||||
{
|
||||
// Business logic to decide what we're interested in goes here
|
||||
if (syntaxNode is ClassDeclarationSyntax cds)
|
||||
{
|
||||
Classes.Add(cds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Actions Registration Generator for Yarn Spinner
|
||||
|
||||
This folder contains the source code for the [source generator](https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview) that produces registration code for Yarn Spinner actions (for example, `YarnCommand` and `YarnAction`).
|
||||
|
||||
This folder ends with a tilde `~` to make Unity not aware of it. To build the source code generator, install the .NET SDK and use `dotnet-build`. The built DLL will be placed at the following path:
|
||||
|
||||
```
|
||||
(path to Yarn Spinner)/SourceGenerator/YarnSpinner.Unity.SourceCodeGenerator.dll
|
||||
```
|
||||
1165
Packages/dev.yarnspinner.unity/Editor/Analysis/Analyser.cs
Normal file
1165
Packages/dev.yarnspinner.unity/Editor/Analysis/Analyser.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee966ea0c2700450080c5d533c3c5afc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Yarn.Unity.ActionAnalyser
|
||||
{
|
||||
[System.Serializable]
|
||||
public class AnalyserException : System.Exception
|
||||
{
|
||||
public AnalyserException() { }
|
||||
public AnalyserException(string message) : base(message) { }
|
||||
public AnalyserException(string message, System.Exception inner) : base(message, inner) { }
|
||||
public AnalyserException(string message, System.Exception inner, IEnumerable<Microsoft.CodeAnalysis.Diagnostic> diagnostics) : base(message, inner)
|
||||
{
|
||||
this.Diagnostics = diagnostics;
|
||||
}
|
||||
protected AnalyserException(
|
||||
System.Runtime.Serialization.SerializationInfo info,
|
||||
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
|
||||
|
||||
public IEnumerable<Microsoft.CodeAnalysis.Diagnostic> Diagnostics { get; } = new List<Microsoft.CodeAnalysis.Diagnostic>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4318b63e7e2584e3895f7b9c8782154b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Packages/dev.yarnspinner.unity/Editor/Analysis/DLLs.meta
Normal file
8
Packages/dev.yarnspinner.unity/Editor/Analysis/DLLs.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38292618b0e0e46e2a23aa0b7ab05be9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e596e8d36f4949e49a276da7dcf74d0
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d0d5eb061e5a94e61918a4bcc8343d0f
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05b4f6ee4402b4befa8b93e0b0f30072
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 1
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d36ed78c475f14eada798a8881d84f4a
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f124754956f07408dafc0d384a437712
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 1
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33c2c99bdbd9f48f1bfe7333e3c7f401
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8807786a5b21e4484ae712f0f92620db
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b1c9dc69df6f47a38bd1ad37d4319bd
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 1
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
111
Packages/dev.yarnspinner.unity/Editor/Analysis/Diagnostics.cs
Normal file
111
Packages/dev.yarnspinner.unity/Editor/Analysis/Diagnostics.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
|
||||
public static class Diagnostics
|
||||
{
|
||||
public static readonly DiagnosticDescriptor YS1000UnknownError = new DiagnosticDescriptor(
|
||||
"YS0000",
|
||||
title: $"Internal unknown error",
|
||||
messageFormat: "An internal error was encountered while processing this action: {0}",
|
||||
category: "Yarn Spinner",
|
||||
defaultSeverity: DiagnosticSeverity.Warning,
|
||||
isEnabledByDefault: true);
|
||||
public static readonly DiagnosticDescriptor YS1001ActionMethodsMustBePublic = new DiagnosticDescriptor(
|
||||
"YS1001",
|
||||
title: $"Yarn action methods must be public",
|
||||
messageFormat: "YarnCommand and YarnFunction methods must be public. \"{0}\" is {1}.",
|
||||
category: "Yarn Spinner",
|
||||
defaultSeverity: DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true,
|
||||
description: "[YarnCommand] and [YarnFunction] attributed methods must be public so that the codegen can reference them.",
|
||||
helpLinkUri: "https://docs.yarnspinner.dev/using-yarnspinner-with-unity/creating-commands-functions");
|
||||
|
||||
public static readonly DiagnosticDescriptor YS1002ActionMethodsMustHaveAValidName = new DiagnosticDescriptor(
|
||||
"YS1002",
|
||||
title: $"Yarn action methods must have a valid name",
|
||||
messageFormat: "YarnCommand and YarnFunction methods must follow existing ID rules for Yarn. \"{0}\" is invalid.",
|
||||
category: "Yarn Spinner",
|
||||
defaultSeverity: DiagnosticSeverity.Warning,
|
||||
isEnabledByDefault: true,
|
||||
description: "[YarnCommand] and [YarnFunction] attributed methods must follow Yarn ID rules so that Yarn scripts can reference them.",
|
||||
helpLinkUri: "https://docs.yarnspinner.dev/using-yarnspinner-with-unity/creating-commands-functions");
|
||||
public static readonly DiagnosticDescriptor YS1003CommandMethodsMustHaveAValidReturnType = new DiagnosticDescriptor(
|
||||
"YS1003",
|
||||
title: $"YarnCommand methods must return a valid type",
|
||||
messageFormat: $"YarnCommand methods must return a valid type (either void, a coroutine, or a task). \"{{0}}\"'s return type is {{1}}.",
|
||||
category: "Yarn Spinner",
|
||||
defaultSeverity: DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true,
|
||||
helpLinkUri: "https://docs.yarnspinner.dev/using-yarnspinner-with-unity/creating-commands-functions");
|
||||
public static readonly DiagnosticDescriptor YS1004FunctionMethodsMustHaveAValidReturnType = new DiagnosticDescriptor(
|
||||
"YS1004",
|
||||
title: $"YarnFunction methods must return a valid type",
|
||||
messageFormat: $"YarnFunction methods must return a valid type (either bool, string, or a numeric type). \"{{0}}\"'s return type is {{1}}.",
|
||||
category: "Yarn Spinner",
|
||||
defaultSeverity: DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true,
|
||||
helpLinkUri: "https://docs.yarnspinner.dev/using-yarnspinner-with-unity/creating-commands-functions");
|
||||
public static readonly DiagnosticDescriptor YS1005ActionMethodsMustHaveOneActionAttribute = new DiagnosticDescriptor(
|
||||
"YS1005",
|
||||
title: $"Yarn action methods must have a single YarnCommand or YarnAction attribute",
|
||||
messageFormat: $"YarnCommand and YarnFunction methods must have a single attribute. \"{{0}}\" has {{1}}.",
|
||||
category: "Yarn Spinner",
|
||||
defaultSeverity: DiagnosticSeverity.Warning,
|
||||
isEnabledByDefault: true,
|
||||
helpLinkUri: "https://docs.yarnspinner.dev/using-yarnspinner-with-unity/creating-commands-functions");
|
||||
|
||||
public static readonly DiagnosticDescriptor YS1006YarnFunctionsMustBeStatic = new DiagnosticDescriptor(
|
||||
"YS1006",
|
||||
title: $"YarnFunction methods be static",
|
||||
messageFormat: $"YarnFunction methods are required to be static.",
|
||||
category: "Yarn Spinner",
|
||||
defaultSeverity: DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true,
|
||||
helpLinkUri: "https://docs.yarnspinner.dev/using-yarnspinner-with-unity/creating-commands-functions");
|
||||
|
||||
public static readonly DiagnosticDescriptor YS1008ActionsParamsArraysMustBeOfYarnTypes = new DiagnosticDescriptor(
|
||||
"YS1008",
|
||||
title: "Params arrays must be of a Yarn compatible type",
|
||||
messageFormat: "Params arrays must be of a Yarn compatible type, but {0} is of type \"{1}\"",
|
||||
category: "Yarn Spinner",
|
||||
defaultSeverity: DiagnosticSeverity.Warning,
|
||||
isEnabledByDefault: true,
|
||||
helpLinkUri: "https://docs.yarnspinner.dev/yarn-spinner-for-unity/creating-commands-functions");
|
||||
|
||||
public static readonly DiagnosticDescriptor YS1009ActionsEnumAttributedParameterIsOfIncompatibleType = new DiagnosticDescriptor(
|
||||
"YS1009",
|
||||
title: "Yarn Enum attributed parameters must be of a Yarn compatible type",
|
||||
messageFormat: "Yarn Enum attributed parameters must be of a Yarn compatible type, but {0} is of type \"{1}\"",
|
||||
category: "Yarn Spinner",
|
||||
defaultSeverity: DiagnosticSeverity.Warning,
|
||||
isEnabledByDefault: true,
|
||||
helpLinkUri: "https://docs.yarnspinner.dev/yarn-spinner-for-unity/creating-commands-functions");
|
||||
|
||||
public static readonly DiagnosticDescriptor YS1010ActionsNodeAttributedParameterIsOfIncompatibleType = new DiagnosticDescriptor(
|
||||
"YS1010",
|
||||
title: "Yarn Node attributed parameters must be a string",
|
||||
messageFormat: "Yarn Node attributed parameters must be a string, but {0} is of type \"{1}\"",
|
||||
category: "Yarn Spinner",
|
||||
defaultSeverity: DiagnosticSeverity.Warning,
|
||||
isEnabledByDefault: true,
|
||||
helpLinkUri: "https://docs.yarnspinner.dev/yarn-spinner-for-unity/creating-commands-functions");
|
||||
|
||||
public static readonly DiagnosticDescriptor YS1011ActionsParameterIsAnIncompatibleType = new DiagnosticDescriptor(
|
||||
"YS1011",
|
||||
title: "Yarn action parameters must be of a Yarn compatible type",
|
||||
messageFormat: "Yarn action parameters must be of a Yarn compatible type, but {0} is of type \"{1}\"",
|
||||
category: "Yarn Spinner",
|
||||
defaultSeverity: DiagnosticSeverity.Warning,
|
||||
isEnabledByDefault: true,
|
||||
helpLinkUri: "https://docs.yarnspinner.dev/yarn-spinner-for-unity/creating-commands-functions");
|
||||
|
||||
public static readonly DiagnosticDescriptor YS1012ActionIsALambda = new DiagnosticDescriptor(
|
||||
"YS1012",
|
||||
title: "Yarn actions can be lambdas but this generally isn't recommended",
|
||||
messageFormat: "Yarn actions can be lambdas but this generally isn't recommended. Lambda based actions cannot be unregistered and are more difficult to debug",
|
||||
category: "Yarn Spinner",
|
||||
defaultSeverity: DiagnosticSeverity.Info,
|
||||
isEnabledByDefault: true,
|
||||
helpLinkUri: "https://docs.yarnspinner.dev/yarn-spinner-for-unity/creating-commands-functions");
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ef837099f8664a9ab951ed2c6ba6e72
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#nullable enable
|
||||
|
||||
static class EnumerableExtensions
|
||||
{
|
||||
public static IEnumerable<T> NonNull<T>(this IEnumerable<T?> collection, bool throwIfAnyNull = false) where T : class
|
||||
{
|
||||
foreach (var item in collection)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (throwIfAnyNull)
|
||||
{
|
||||
throw new NullReferenceException("Collection contains a null item");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a3d39b6f28ae4a31989cb67e5aa3d98
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
132
Packages/dev.yarnspinner.unity/Editor/Analysis/ILogger.cs
Normal file
132
Packages/dev.yarnspinner.unity/Editor/Analysis/ILogger.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
|
||||
*/
|
||||
|
||||
using System;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
#endif
|
||||
|
||||
namespace Yarn.Unity
|
||||
{
|
||||
#nullable enable
|
||||
public interface ILogger : IDisposable
|
||||
{
|
||||
void Write(object obj);
|
||||
void WriteLine(object obj);
|
||||
void WriteException(System.Exception ex, string? message = null);
|
||||
|
||||
void Inc();
|
||||
void Dec();
|
||||
void SetDepth(int depth);
|
||||
}
|
||||
|
||||
public class FileLogger : ILogger
|
||||
{
|
||||
System.IO.TextWriter writer;
|
||||
private int depth = 0;
|
||||
|
||||
public FileLogger(System.IO.TextWriter writer)
|
||||
{
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
writer.Flush();
|
||||
writer.Dispose();
|
||||
}
|
||||
|
||||
public void Write(object text)
|
||||
{
|
||||
var tabs = new String('\t', depth);
|
||||
writer.Write(tabs + text);
|
||||
}
|
||||
|
||||
public void WriteLine(object text)
|
||||
{
|
||||
var tabs = new String('\t', depth);
|
||||
writer.WriteLine(tabs + text);
|
||||
}
|
||||
public void WriteException(System.Exception ex, string? message)
|
||||
{
|
||||
var tabs = new String('\t', depth);
|
||||
if (message == null)
|
||||
{
|
||||
writer.WriteLine($"{tabs}Exception: {ex.Message}");
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteLine($"{tabs}{message}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Inc()
|
||||
{
|
||||
depth +=1 ;
|
||||
}
|
||||
public void Dec()
|
||||
{
|
||||
depth = Math.Max(depth - 1, 0);
|
||||
}
|
||||
public void SetDepth(int depth)
|
||||
{
|
||||
this.depth = Math.Max(depth, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public class UnityLogger : ILogger
|
||||
{
|
||||
public void Dispose() { }
|
||||
|
||||
public void Write(object text)
|
||||
{
|
||||
WriteLine(text);
|
||||
}
|
||||
|
||||
public void WriteLine(object text)
|
||||
{
|
||||
var tabs = new String('\t', depth);
|
||||
#if UNITY_EDITOR
|
||||
Debug.LogWarning(tabs + text.ToString());
|
||||
#endif
|
||||
}
|
||||
|
||||
public void WriteException(System.Exception ex, string? message = null)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
Debug.LogException(ex);
|
||||
#endif
|
||||
}
|
||||
|
||||
private int depth = 0;
|
||||
public void Inc()
|
||||
{
|
||||
depth +=1 ;
|
||||
}
|
||||
public void Dec()
|
||||
{
|
||||
depth = Math.Max(depth - 1, 0);
|
||||
}
|
||||
public void SetDepth(int depth)
|
||||
{
|
||||
this.depth = Math.Max(depth, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public class NullLogger : ILogger
|
||||
{
|
||||
public void Dispose() { }
|
||||
|
||||
public void Write(object text) { }
|
||||
|
||||
public void WriteLine(object text) { }
|
||||
|
||||
public void WriteException(System.Exception ex, string? message = null) { }
|
||||
|
||||
public void Inc(){}
|
||||
public void Dec(){}
|
||||
public void SetDepth(int depth) {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 45e3ce698d3364e5790443887a66daff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
|
||||
*/
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
#nullable enable
|
||||
|
||||
static class SymbolExtensions
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// If the <paramref name="symbol"/> is a method symbol, returns <see langword="true"/> if the method's return type is "awaitable", but not if it's <see langword="dynamic"/>.
|
||||
/// If the <paramref name="symbol"/> is a type symbol, returns <see langword="true"/> if that type is "awaitable".
|
||||
/// An "awaitable" is any type that exposes a GetAwaiter method which returns a valid "awaiter". This GetAwaiter method may be an instance method or an extension method.
|
||||
/// </summary>
|
||||
public static bool IsAwaitableNonDynamic(this ISymbol symbol, SemanticModel semanticModel, int position)
|
||||
{
|
||||
IMethodSymbol? methodSymbol = symbol as IMethodSymbol;
|
||||
ITypeSymbol? typeSymbol = null;
|
||||
|
||||
if (methodSymbol == null)
|
||||
{
|
||||
typeSymbol = symbol as ITypeSymbol;
|
||||
if (typeSymbol == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (methodSymbol.ReturnType == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise: needs valid GetAwaiter
|
||||
var potentialGetAwaiters = semanticModel.LookupSymbols(position,
|
||||
container: typeSymbol ?? methodSymbol?.ReturnType.OriginalDefinition,
|
||||
name: WellKnownMemberNames.GetAwaiter,
|
||||
includeReducedExtensionMethods: true);
|
||||
var getAwaiters = potentialGetAwaiters.OfType<IMethodSymbol>().Where(x => !x.Parameters.Any());
|
||||
return getAwaiters.Any(VerifyGetAwaiter);
|
||||
}
|
||||
|
||||
private static bool VerifyGetAwaiter(IMethodSymbol getAwaiter)
|
||||
{
|
||||
var returnType = getAwaiter.ReturnType;
|
||||
if (returnType == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// bool IsCompleted { get }
|
||||
if (!returnType.GetMembers().OfType<IPropertySymbol>().Any(p => p.Name == WellKnownMemberNames.IsCompleted && p.Type.SpecialType == SpecialType.System_Boolean && p.GetMethod != null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var methods = returnType.GetMembers().OfType<IMethodSymbol>();
|
||||
|
||||
// NOTE: (vladres) The current version of C# Spec, §7.7.7.3 'Runtime evaluation of await expressions', requires that
|
||||
// NOTE: the interface method INotifyCompletion.OnCompleted or ICriticalNotifyCompletion.UnsafeOnCompleted is invoked
|
||||
// NOTE: (rather than any OnCompleted method conforming to a certain pattern).
|
||||
// NOTE: Should this code be updated to match the spec?
|
||||
|
||||
// void OnCompleted(Action)
|
||||
// Actions are delegates, so we'll just check for delegates.
|
||||
if (!methods.Any(x => x.Name == WellKnownMemberNames.OnCompleted && x.ReturnsVoid && x.Parameters.Length == 1 && x.Parameters.First().Type.TypeKind == TypeKind.Delegate))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// void GetResult() || T GetResult()
|
||||
return methods.Any(m => m.Name == WellKnownMemberNames.GetResult && !m.Parameters.Any());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 245397f2263474149856dba1cfc50f19
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user