|
@@ -0,0 +1,236 @@
|
|
|
+using System.Text;
|
|
|
+using Microsoft.CodeAnalysis;
|
|
|
+using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
|
+
|
|
|
+namespace PixiEditor.ChangeableDocument.Gen;
|
|
|
+
|
|
|
+internal static class Helpers
|
|
|
+{
|
|
|
+ public static string CreateMakeChangeAction(MethodInfo changeConstructorInfo)
|
|
|
+ {
|
|
|
+ string actionName = changeConstructorInfo.ContainingClass.Name.Split('_')[0] + "_Action";
|
|
|
+ List<TypeWithName> constructorArgs = changeConstructorInfo.Arguments;
|
|
|
+ List<TypeWithName> properties = constructorArgs.Select(static typeWithName =>
|
|
|
+ {
|
|
|
+ return new TypeWithName(typeWithName.Type, typeWithName.FullNamespace, VariableNameIntoPropertyName(typeWithName.Name));
|
|
|
+ }).ToList();
|
|
|
+
|
|
|
+ var propToVar = MatchMembers(properties, constructorArgs);
|
|
|
+
|
|
|
+ StringBuilder sb = new();
|
|
|
+
|
|
|
+ sb.AppendLine("namespace PixiEditor.ChangeableDocument.Actions.Generated;");
|
|
|
+ sb.AppendLine($"public record class {actionName} : PixiEditor.ChangeableDocument.Actions.IMakeChangeAction");
|
|
|
+ sb.AppendLine("{");
|
|
|
+ sb.Append($"public {actionName}");
|
|
|
+ AppendArgumentList(sb, constructorArgs);
|
|
|
+ AppendConstructorBody(sb, propToVar);
|
|
|
+ AppendProperties(sb, properties);
|
|
|
+ AppendCreateCorrespondingChange(sb, changeConstructorInfo.ContainingClass, properties);
|
|
|
+ sb.AppendLine("}");
|
|
|
+
|
|
|
+ return sb.ToString();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static Result<string> CreateStartUpdateChangeAction(MethodInfo changeConstructorInfo, MethodInfo updateMethodInfo)
|
|
|
+ {
|
|
|
+ string actionName = changeConstructorInfo.ContainingClass.Name.Split('_')[0] + "_Action";
|
|
|
+ List<TypeWithName> constructorArgs = changeConstructorInfo.Arguments;
|
|
|
+ List<TypeWithName> properties = constructorArgs.Select(static typeWithName =>
|
|
|
+ {
|
|
|
+ return new TypeWithName(typeWithName.Type, typeWithName.FullNamespace, VariableNameIntoPropertyName(typeWithName.Name));
|
|
|
+ }).ToList();
|
|
|
+
|
|
|
+ var constructorAssignments = MatchMembers(properties, constructorArgs);
|
|
|
+ var updatePropsToPass = MatchMembers(updateMethodInfo.Arguments, properties).Select(pair => pair.Item2).ToList();
|
|
|
+ if (updatePropsToPass.Count != updateMethodInfo.Arguments.Count)
|
|
|
+ return Result<string>.Error("Couldn't match update method arguments with constructor arguments");
|
|
|
+
|
|
|
+ StringBuilder sb = new();
|
|
|
+
|
|
|
+ sb.AppendLine("namespace PixiEditor.ChangeableDocument.Actions.Generated;");
|
|
|
+ sb.AppendLine($"public record class {actionName} : PixiEditor.ChangeableDocument.Actions.IStartOrUpdateChangeAction");
|
|
|
+ sb.AppendLine("{");
|
|
|
+ sb.Append($"public {actionName}");
|
|
|
+ AppendArgumentList(sb, constructorArgs);
|
|
|
+ AppendConstructorBody(sb, constructorAssignments);
|
|
|
+ AppendProperties(sb, properties);
|
|
|
+ AppendCreateUpdateableCorrespondingChange(sb, changeConstructorInfo.ContainingClass, properties);
|
|
|
+ AppendUpdateCorrespondingChange(sb, updateMethodInfo.Name, changeConstructorInfo.ContainingClass, updatePropsToPass);
|
|
|
+ sb.AppendLine("}");
|
|
|
+
|
|
|
+ return sb.ToString();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static string CreateEndChangeAction(MethodInfo changeConstructorInfo)
|
|
|
+ {
|
|
|
+ string actionName = "End" + changeConstructorInfo.ContainingClass.Name.Split('_')[0] + "_Action";
|
|
|
+ return $@"
|
|
|
+namespace PixiEditor.ChangeableDocument.Actions.Generated;
|
|
|
+
|
|
|
+public record class {actionName} : PixiEditor.ChangeableDocument.Actions.IEndChangeAction
|
|
|
+{{
|
|
|
+ bool PixiEditor.ChangeableDocument.Actions.IEndChangeAction.IsChangeTypeMatching(PixiEditor.ChangeableDocument.Changes.Change change)
|
|
|
+ {{
|
|
|
+ return change is {changeConstructorInfo.ContainingClass.NameWithNamespace};
|
|
|
+ }}
|
|
|
+}}
|
|
|
+";
|
|
|
+ }
|
|
|
+
|
|
|
+ public static MethodInfo ExtractMethodInfo(IMethodSymbol method)
|
|
|
+ {
|
|
|
+ List<TypeWithName> variables = method.Parameters.Select(static parameter =>
|
|
|
+ {
|
|
|
+ return new TypeWithName(
|
|
|
+ parameter.Type.Name,
|
|
|
+ parameter.Type.ContainingNamespace.ToDisplayString(),
|
|
|
+ parameter.Name
|
|
|
+ );
|
|
|
+ }).ToList();
|
|
|
+ string changeName = method.ContainingType.Name;
|
|
|
+ string changeFullNamespace = method.ContainingNamespace.ToDisplayString();
|
|
|
+ return new MethodInfo(method.Name, variables, new NamespacedType(changeName, changeFullNamespace));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void AppendConstructorBody(StringBuilder sb, List<(TypeWithName, TypeWithName)> assignments)
|
|
|
+ {
|
|
|
+ sb.AppendLine("{");
|
|
|
+ foreach (var (property, variable) in assignments)
|
|
|
+ {
|
|
|
+ sb.Append("this.").Append(property.Name).Append(" = ").Append(variable.Name).AppendLine(";");
|
|
|
+ }
|
|
|
+ sb.AppendLine("}");
|
|
|
+ }
|
|
|
+
|
|
|
+ private static List<(TypeWithName, TypeWithName)> MatchMembers(List<TypeWithName> list1, List<TypeWithName> list2)
|
|
|
+ {
|
|
|
+ List<(TypeWithName, TypeWithName)> paired = new();
|
|
|
+ for (int i = list1.Count - 1; i >= 0; i--)
|
|
|
+ {
|
|
|
+ for (int j = list2.Count - 1; j >= 0; j--)
|
|
|
+ {
|
|
|
+ if (list1[i].TypeWithNamespace == list2[j].TypeWithNamespace &&
|
|
|
+ list1[i].Name.ToLower() == list2[j].Name.ToLower())
|
|
|
+ {
|
|
|
+ paired.Add((list1[i], list2[j]));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return paired;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void AppendArgumentList(StringBuilder sb, List<TypeWithName> variables)
|
|
|
+ {
|
|
|
+ sb.Append("(");
|
|
|
+ for (int i = 0; i < variables.Count; i++)
|
|
|
+ {
|
|
|
+ sb.Append(variables[i].TypeWithNamespace).Append(" ").Append(variables[i].Name);
|
|
|
+ if (i != variables.Count - 1)
|
|
|
+ sb.Append(", ");
|
|
|
+ }
|
|
|
+ sb.AppendLine(")");
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void AppendUpdateCorrespondingChange
|
|
|
+ (StringBuilder sb, string updateMethodName, NamespacedType corrChangeType, List<TypeWithName> propertiesToPass)
|
|
|
+ {
|
|
|
+ sb.AppendLine("void PixiEditor.ChangeableDocument.Actions.IStartOrUpdateChangeAction.UpdateCorrespodingChange(PixiEditor.ChangeableDocument.Changes.UpdateableChange change)");
|
|
|
+ sb.AppendLine("{");
|
|
|
+ sb.Append($"(({corrChangeType.NameWithNamespace})change).{updateMethodName}(");
|
|
|
+ for (int i = 0; i < propertiesToPass.Count; i++)
|
|
|
+ {
|
|
|
+ sb.Append(propertiesToPass[i].Name);
|
|
|
+ if (i != propertiesToPass.Count - 1)
|
|
|
+ sb.Append(", ");
|
|
|
+ }
|
|
|
+ sb.AppendLine(");");
|
|
|
+ sb.AppendLine("}");
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void AppendCreateUpdateableCorrespondingChange
|
|
|
+ (StringBuilder sb, NamespacedType corrChangeType, List<TypeWithName> propertiesToPass)
|
|
|
+ {
|
|
|
+ sb.AppendLine("PixiEditor.ChangeableDocument.Changes.UpdateableChange PixiEditor.ChangeableDocument.Actions.IStartOrUpdateChangeAction.CreateCorrespondingChange()");
|
|
|
+ sb.AppendLine("{");
|
|
|
+ sb.Append($"return new {corrChangeType.NameWithNamespace}(");
|
|
|
+ for (int i = 0; i < propertiesToPass.Count; i++)
|
|
|
+ {
|
|
|
+ sb.Append(propertiesToPass[i].Name);
|
|
|
+ if (i != propertiesToPass.Count - 1)
|
|
|
+ sb.Append(", ");
|
|
|
+ }
|
|
|
+ sb.AppendLine(");");
|
|
|
+ sb.AppendLine("}");
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void AppendCreateCorrespondingChange
|
|
|
+ (StringBuilder sb, NamespacedType corrChangeType, List<TypeWithName> propertiesToPass)
|
|
|
+ {
|
|
|
+ sb.AppendLine("PixiEditor.ChangeableDocument.Changes.Change PixiEditor.ChangeableDocument.Actions.IMakeChangeAction.CreateCorrespondingChange()");
|
|
|
+ sb.AppendLine("{");
|
|
|
+ sb.Append($"return new {corrChangeType.NameWithNamespace}(");
|
|
|
+ for (int i = 0; i < propertiesToPass.Count; i++)
|
|
|
+ {
|
|
|
+ sb.Append(propertiesToPass[i].Name);
|
|
|
+ if (i != propertiesToPass.Count - 1)
|
|
|
+ sb.Append(", ");
|
|
|
+ }
|
|
|
+ sb.AppendLine(");");
|
|
|
+ sb.AppendLine("}");
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void AppendProperties(StringBuilder sb, List<TypeWithName> properties)
|
|
|
+ {
|
|
|
+ foreach (var typeWithName in properties)
|
|
|
+ {
|
|
|
+ sb.AppendLine($"public {typeWithName.TypeWithNamespace} {typeWithName.Name} {{ get; init; }}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static string VariableNameIntoPropertyName(string varName)
|
|
|
+ {
|
|
|
+ string lowerCaseName = varName.Substring(0, 1).ToUpperInvariant() + varName.Substring(1);
|
|
|
+ return lowerCaseName;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static bool IsConstructorWithAttribute(SyntaxNode node, CancellationToken token)
|
|
|
+ {
|
|
|
+ return node is ConstructorDeclarationSyntax constructor && constructor.AttributeLists.Count > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static bool IsMethodWithAttribute(SyntaxNode node, CancellationToken token)
|
|
|
+ {
|
|
|
+ return node is MethodDeclarationSyntax method && method.AttributeLists.Count > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static bool IsInheritedFrom(INamedTypeSymbol classSymbol, NamespacedType type)
|
|
|
+ {
|
|
|
+ while (classSymbol.BaseType is not null)
|
|
|
+ {
|
|
|
+ if (classSymbol.BaseType.ToDisplayString() == type.NameWithNamespace)
|
|
|
+ return true;
|
|
|
+ classSymbol = classSymbol.BaseType;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static bool MethodHasAttribute
|
|
|
+ (GeneratorSyntaxContext context, CancellationToken cancelToken, BaseMethodDeclarationSyntax method, NamespacedType attributeType)
|
|
|
+ {
|
|
|
+ foreach (var attrList in method.AttributeLists)
|
|
|
+ {
|
|
|
+ foreach (var attribute in attrList.Attributes)
|
|
|
+ {
|
|
|
+ cancelToken.ThrowIfCancellationRequested();
|
|
|
+ var symbol = context.SemanticModel.GetSymbolInfo(attribute, cancelToken);
|
|
|
+ if (symbol.Symbol is not IMethodSymbol methodSymbol)
|
|
|
+ continue;
|
|
|
+ if (methodSymbol.ContainingType.ToDisplayString() != attributeType.NameWithNamespace)
|
|
|
+ continue;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+}
|