From 2414e7bafa76e2ab86bbde619be3d4c0acba49bb Mon Sep 17 00:00:00 2001 From: Artem Dzhemesiuk Date: Tue, 9 Nov 2021 16:15:07 +0100 Subject: [PATCH] added ability to nest classes and interfaces, explicitly restricted nesting of the async entities --- .../AsyncEntityGenerator.cs | 80 +++++++++++++++++-- api/AltV.Net.Async.CodeGen/Diagnostics.cs | 10 +++ 2 files changed, 83 insertions(+), 7 deletions(-) diff --git a/api/AltV.Net.Async.CodeGen/AsyncEntityGenerator.cs b/api/AltV.Net.Async.CodeGen/AsyncEntityGenerator.cs index fba9d46d4b..d3685731a5 100644 --- a/api/AltV.Net.Async.CodeGen/AsyncEntityGenerator.cs +++ b/api/AltV.Net.Async.CodeGen/AsyncEntityGenerator.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.ComponentModel.Design.Serialization; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; @@ -53,6 +54,48 @@ public void Initialize(GeneratorInitializationContext context) context.RegisterForSyntaxNotifications(() => new SyntaxTreeReceiver()); } + private static IEnumerable GetInterfaceMembers(INamedTypeSymbol @interface) + { + var members = @interface.GetMembers().ToList(); + foreach (var namedTypeSymbol in @interface.Interfaces) + { + if (namedTypeSymbol.ToString().StartsWith("AltV.Net.Elements.Entities.")) continue; + foreach (var interfaceMember in GetInterfaceMembers(namedTypeSymbol)) + { + if (members.All(e => e.Name != interfaceMember.Name)) members.Add(interfaceMember); + } + } + + return members; + } + + private static IEnumerable GetClassMembers(INamedTypeSymbol @class) + { + var members = @class.GetMembers().ToList(); + if (@class.BaseType is null || @class.BaseType.ToString().StartsWith("AltV.Net.Elements.Entities.")) + return members; + foreach (var classMember in GetClassMembers(@class.BaseType)) + { + if (members.All(e => e.Name != classMember.Name)) members.Add(classMember); + } + + return members; + } + + private static IList GetBaseTypes(INamedTypeSymbol @class) + { + var list = new List(); + var currentClass = @class; + + while (currentClass.BaseType is { } type) + { + list.Add(type); + currentClass = type; + } + + return list; + } + public void Execute(GeneratorExecutionContext context) { var receiver = (SyntaxTreeReceiver) context.SyntaxReceiver!; @@ -66,6 +109,8 @@ public void Execute(GeneratorExecutionContext context) var attributeSymbol = compilation.GetTypeByMetadataName("AltV.Net.Async.CodeGen.AsyncEntityAttribute"); + var validClasses = new List(); + foreach (var classSyntax in receiver.Classes) { var @class = compilation.GetSemanticModel(classSyntax.SyntaxTree).GetDeclaredSymbol(classSyntax)!; @@ -99,22 +144,33 @@ public void Execute(GeneratorExecutionContext context) } var baseType = @class.BaseType; - if (baseType is null || !baseType.ToString().StartsWith("AltV.Net.Elements.Entities.")) + + if (baseType is not null) + while (!baseType!.ToString().StartsWith("AltV.Net.Elements.Entities.")) + baseType = baseType.BaseType; + + if (baseType is null) { context.ReportDiagnostic(Diagnostic.Create(Diagnostics.AsyncEntityShouldImplementEntity, @class.Locations.FirstOrDefault(), @class.ToString())); continue; } + validClasses.Add(@class); + + var classBaseDeclaration = @class.BaseType!.ToString().StartsWith("AltV.Net.Elements.Entities.") + ? "" + : " : " + @class.BaseType; + var members = new List(); - var interfaceMembers = @interface.GetMembers(); - var classMembers = @class.GetMembers(); + var interfaceMembers = GetInterfaceMembers(@interface); + var classMembers = GetClassMembers(@class).ToArray(); foreach (var member in interfaceMembers) { var classMember = classMembers.FirstOrDefault(m => m.Name == member.Name); - + // does member hide base class property (new keyword) var isNew = classMember?.DeclaringSyntaxReferences.Any(s => s.GetSyntax() is MemberDeclarationSyntax declarationSyntax && @@ -163,7 +219,7 @@ public void Execute(GeneratorExecutionContext context) var formattedAttributes = attributes.Length == 0 ? "" : FormatAttributes(attributes) + "\n"; var @new = isNew ? "new " : ""; - + members.Add(formattedAttributes + $"public {@new}{property.Type} {member.Name} {{ {propertyValue}}}"); } @@ -219,7 +275,7 @@ public void Execute(GeneratorExecutionContext context) var attributes = classMethod.GetAttributes(); var formattedAttributes = attributes.Length == 0 ? "" : FormatAttributes(attributes) + "\n"; var @new = isNew ? "new " : ""; - + members.Add(formattedAttributes + $"public {@new}{classMethod.ReturnType} {name}({arguments})\n{{\n {returnAction}BaseObject.{name}({callArguments});\n}}"); @@ -236,7 +292,7 @@ public void Execute(GeneratorExecutionContext context) interfaceDeclaration = $"namespace {interfaceNamespace} {{\n{Indent(interfaceDeclaration)}\n}}"; var classDeclaration = $@" -public partial class {@class.Name} {{ +public partial class {@class.Name}{classBaseDeclaration} {{ public {@interface} ToAsync(AltV.Net.Async.IAsyncContext asyncContext) {{ return new Async(this, asyncContext); }} @@ -257,6 +313,16 @@ private class Async : {asyncEntityType}<{@interface}>, {@interface} {{ sourceBuilder.Append($"\n\n{interfaceDeclaration}\n{classDeclaration}"); } + foreach (var @class in validClasses) + { + var types = GetBaseTypes(@class); + var invalid = types.FirstOrDefault(e => validClasses.Contains(e, SymbolEqualityComparer.Default)); + if (invalid is not { } invalidClass) continue; + + context.ReportDiagnostic(Diagnostic.Create(Diagnostics.AsyncEntityCannotBeNested, + @class.Locations.FirstOrDefault(), @class.ToString(), invalidClass.ToString())); + } + context.AddSource("GeneratedAsyncEntities.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); } } diff --git a/api/AltV.Net.Async.CodeGen/Diagnostics.cs b/api/AltV.Net.Async.CodeGen/Diagnostics.cs index 43777bd407..1b30d2f5dc 100644 --- a/api/AltV.Net.Async.CodeGen/Diagnostics.cs +++ b/api/AltV.Net.Async.CodeGen/Diagnostics.cs @@ -85,5 +85,15 @@ public static class Diagnostics DiagnosticSeverity.Error, true ); + + public static DiagnosticDescriptor AsyncEntityCannotBeNested = + new( + "ALTV0009", + "Invalid async entity", + "Async entity class {0} cannot extend another async entity class {1}, async entities cannot be nested", + "AsyncEntity", + DiagnosticSeverity.Error, + true + ); } } \ No newline at end of file