From e0137647f01f4b093053d7adc08300af28009e3c Mon Sep 17 00:00:00 2001 From: Eve <85962933+obvEve@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:59:56 +0200 Subject: [PATCH 1/6] SourceGeneration --- .../AnalyzerReleases.Shipped.md | 3 + .../AnalyzerReleases.Unshipped.md | 7 + .../Builders/Builder.cs | 19 +++ .../Builders/ClassBuilder.cs | 60 +++++++ .../Builders/MethodBuilder.cs | 44 +++++ SecretAPI.SourceGenerators/Diagnostics.cs | 28 ++++ .../Generators/CallOnLoadGenerator.cs | 152 ++++++++++++++++++ SecretAPI.SourceGenerators/GlobalUsings.cs | 19 +++ .../SecretAPI.SourceGenerators.csproj | 26 +++ .../Utils/GeneratedIdentifyUtils.cs | 19 +++ .../Utils/GenericTypeUtils.cs | 12 ++ .../Utils/MethodParameter.cs | 40 +++++ .../Utils/MethodUtils.cs | 20 +++ SecretAPI.SourceGenerators/Utils/TypeUtils.cs | 10 ++ SecretAPI.sln | 6 + SecretAPI/SecretAPI.csproj | 2 + SecretAPI/SecretApi.cs | 2 +- 17 files changed, 468 insertions(+), 1 deletion(-) create mode 100644 SecretAPI.SourceGenerators/AnalyzerReleases.Shipped.md create mode 100644 SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md create mode 100644 SecretAPI.SourceGenerators/Builders/Builder.cs create mode 100644 SecretAPI.SourceGenerators/Builders/ClassBuilder.cs create mode 100644 SecretAPI.SourceGenerators/Builders/MethodBuilder.cs create mode 100644 SecretAPI.SourceGenerators/Diagnostics.cs create mode 100644 SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs create mode 100644 SecretAPI.SourceGenerators/GlobalUsings.cs create mode 100644 SecretAPI.SourceGenerators/SecretAPI.SourceGenerators.csproj create mode 100644 SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs create mode 100644 SecretAPI.SourceGenerators/Utils/GenericTypeUtils.cs create mode 100644 SecretAPI.SourceGenerators/Utils/MethodParameter.cs create mode 100644 SecretAPI.SourceGenerators/Utils/MethodUtils.cs create mode 100644 SecretAPI.SourceGenerators/Utils/TypeUtils.cs diff --git a/SecretAPI.SourceGenerators/AnalyzerReleases.Shipped.md b/SecretAPI.SourceGenerators/AnalyzerReleases.Shipped.md new file mode 100644 index 0000000..60b59dd --- /dev/null +++ b/SecretAPI.SourceGenerators/AnalyzerReleases.Shipped.md @@ -0,0 +1,3 @@ +; Shipped analyzer releases +; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + diff --git a/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md b/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md new file mode 100644 index 0000000..ad9f9dc --- /dev/null +++ b/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md @@ -0,0 +1,7 @@ +### New Rules + + Rule ID | Category | Severity | Notes +------------|----------|----------|--------------------- + SecretGen0 | Usage | Error | CA6000_AnalyzerName + SecretGen1 | Usage | Error | CA6000_AnalyzerName + SecretGen2 | Usage | Error | CA6000_AnalyzerName \ No newline at end of file diff --git a/SecretAPI.SourceGenerators/Builders/Builder.cs b/SecretAPI.SourceGenerators/Builders/Builder.cs new file mode 100644 index 0000000..4cc8b63 --- /dev/null +++ b/SecretAPI.SourceGenerators/Builders/Builder.cs @@ -0,0 +1,19 @@ +namespace SecretAPI.SourceGenerators.Builders; + +/// +/// Base of a builder. +/// +/// The this is handling. +internal abstract class Builder + where TBuilder : Builder +{ + protected readonly List _modifiers = new(); + + internal TBuilder AddModifiers(params SyntaxKind[] modifiers) + { + foreach (SyntaxKind token in modifiers) + _modifiers.Add(Token(token)); + + return (TBuilder)this; + } +} \ No newline at end of file diff --git a/SecretAPI.SourceGenerators/Builders/ClassBuilder.cs b/SecretAPI.SourceGenerators/Builders/ClassBuilder.cs new file mode 100644 index 0000000..671cf80 --- /dev/null +++ b/SecretAPI.SourceGenerators/Builders/ClassBuilder.cs @@ -0,0 +1,60 @@ +namespace SecretAPI.SourceGenerators.Builders; + +internal class ClassBuilder : Builder +{ + private NamespaceDeclarationSyntax _namespaceDeclaration; + private ClassDeclarationSyntax _classDeclaration; + + private readonly List _usings = new(); + private readonly List _methods = new(); + + private ClassBuilder(NamespaceDeclarationSyntax namespaceDeclaration, ClassDeclarationSyntax classDeclaration) + { + _namespaceDeclaration = namespaceDeclaration; + _classDeclaration = classDeclaration; + + AddUsingStatements("System.CodeDom.Compiler"); + } + + internal static ClassBuilder CreateBuilder(INamedTypeSymbol namedClass) + => CreateBuilder(NamespaceDeclaration(ParseName(namedClass.ContainingNamespace.ToDisplayString())), ClassDeclaration(namedClass.Name)); + + internal static ClassBuilder CreateBuilder(NamespaceDeclarationSyntax namespaceDeclaration, ClassDeclarationSyntax classDeclaration) + => new(namespaceDeclaration, classDeclaration); + + internal ClassBuilder AddUsingStatements(params string[] usingStatements) + { + foreach (string statement in usingStatements) + { + UsingDirectiveSyntax usings = UsingDirective(ParseName(statement)); + if (!_usings.Any(existing => existing.IsEquivalentTo(usings))) + _usings.Add(usings); + } + + return this; + } + + internal MethodBuilder StartMethodCreation(string methodName, TypeSyntax returnType) => new(this, methodName, returnType); + internal MethodBuilder StartMethodCreation(string methodName, SyntaxKind returnType) => StartMethodCreation(methodName, GetPredefinedTypeSyntax(returnType)); + + internal void AddMethodDefinition(MethodDeclarationSyntax method) => _methods.Add(method); + + internal CompilationUnitSyntax Build() + { + _classDeclaration = _classDeclaration + .AddAttributeLists(GetGeneratedCodeAttributeListSyntax()) + .AddModifiers(_modifiers.ToArray()) + .AddMembers(_methods.Cast().ToArray()); + + _namespaceDeclaration = _namespaceDeclaration + .AddUsings(_usings.ToArray()) + .AddMembers(_classDeclaration); + + return CompilationUnit() + .AddMembers(_namespaceDeclaration) + .NormalizeWhitespace() + .WithLeadingTrivia(Comment("// "), LineFeed, Comment("#pragma warning disable"), LineFeed, Comment("#nullable enable"), LineFeed, LineFeed); + } + + internal void Build(SourceProductionContext context, string name) => context.AddSource(name, Build().ToFullString()); +} \ No newline at end of file diff --git a/SecretAPI.SourceGenerators/Builders/MethodBuilder.cs b/SecretAPI.SourceGenerators/Builders/MethodBuilder.cs new file mode 100644 index 0000000..6538e4a --- /dev/null +++ b/SecretAPI.SourceGenerators/Builders/MethodBuilder.cs @@ -0,0 +1,44 @@ +namespace SecretAPI.SourceGenerators.Builders; + +internal class MethodBuilder : Builder +{ + private readonly ClassBuilder _classBuilder; + private readonly List _parameters = new(); + private readonly List _statements = new(); + private readonly string _methodName; + private readonly TypeSyntax _returnType; + + internal MethodBuilder(ClassBuilder classBuilder, string methodName, TypeSyntax returnType) + { + _classBuilder = classBuilder; + _methodName = methodName; + _returnType = returnType; + } + + internal MethodBuilder AddStatements(params StatementSyntax[] statements) + { + _statements.AddRange(statements); + return this; + } + + internal MethodBuilder AddParameters(params MethodParameter[] parameters) + { + foreach (MethodParameter parameter in parameters) + _parameters.Add(parameter.Syntax); + + return this; + } + + internal ClassBuilder FinishMethodBuild() + { + BlockSyntax body = _statements.Any() ? Block(_statements) : Block(); + + MethodDeclarationSyntax methodDeclaration = MethodDeclaration(_returnType, _methodName) + .AddModifiers(_modifiers.ToArray()) + .AddParameterListParameters(_parameters.ToArray()) + .WithBody(body); + + _classBuilder.AddMethodDefinition(methodDeclaration); + return _classBuilder; + } +} \ No newline at end of file diff --git a/SecretAPI.SourceGenerators/Diagnostics.cs b/SecretAPI.SourceGenerators/Diagnostics.cs new file mode 100644 index 0000000..8b3e2b4 --- /dev/null +++ b/SecretAPI.SourceGenerators/Diagnostics.cs @@ -0,0 +1,28 @@ +namespace SecretAPI.SourceGenerators; + +internal static class Diagnostics +{ + internal static readonly DiagnosticDescriptor MustBePartialPluginClass = new( + "SecretGen0", + "Plugin class must be partial", + "Plugin class '{0}' is missing partial modifier", + "Usage", + DiagnosticSeverity.Error, + true); + + internal static readonly DiagnosticDescriptor MustBeAccessibleMethod = new( + "SecretGen1", + "Method must be accessible", + "Method '{0}' has accessibility '{1}', which is not supported for generated calls", + "Usage", + DiagnosticSeverity.Error, + true); + + internal static readonly DiagnosticDescriptor MustBeStaticMethod = new( + "SecretGen2", + "Method must be static", + "Method '{0}' is not marked as static", + "Usage", + DiagnosticSeverity.Error, + true); +} \ No newline at end of file diff --git a/SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs b/SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs new file mode 100644 index 0000000..7147eb0 --- /dev/null +++ b/SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs @@ -0,0 +1,152 @@ +namespace SecretAPI.SourceGenerators.Generators; + +/// +/// Code generator for CallOnLoad/CallOnUnload +/// +[Generator] +public class CallOnLoadGenerator : IIncrementalGenerator +{ + private const string PluginNamespace = "LabApi.Loader.Features.Plugins"; + private const string PluginBaseClassName = "Plugin"; + private const string CallOnLoadAttributeLocation = "SecretAPI.Attributes.CallOnLoadAttribute"; + private const string CallOnUnloadAttributeLocation = "SecretAPI.Attributes.CallOnUnloadAttribute"; + + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + IncrementalValuesProvider methodProvider = + context.SyntaxProvider.CreateSyntaxProvider( + static (node, _) => node is MethodDeclarationSyntax { AttributeLists.Count: > 0 }, + static (ctx, _) => + ctx.SemanticModel.GetDeclaredSymbol(ctx.Node) as IMethodSymbol) + .Where(static m => m is not null)!; + + IncrementalValuesProvider<(IMethodSymbol method, bool isLoad, bool isUnload)> callProvider = + methodProvider.Select(static (method, _) => ( + method, + HasAttribute(method, CallOnLoadAttributeLocation), + HasAttribute(method, CallOnUnloadAttributeLocation))) + .Where(static m => m.Item2 || m.Item3); + + IncrementalValuesProvider<(ClassDeclarationSyntax?, INamedTypeSymbol?)> pluginClassProvider = + context.SyntaxProvider.CreateSyntaxProvider( + static (node, _) => node is ClassDeclarationSyntax, + static (ctx, _) => ( + ctx.Node as ClassDeclarationSyntax, ctx.SemanticModel.GetDeclaredSymbol(ctx.Node) as INamedTypeSymbol)) + .Where(static c => c.Item2 != null && !c.Item2.IsAbstract && c.Item2.BaseType?.Name == PluginBaseClassName && + c.Item2.BaseType.ContainingNamespace.ToDisplayString() == PluginNamespace); + + context.RegisterSourceOutput(pluginClassProvider.Combine(callProvider.Collect()), static (context, data) => + { + Generate(context, new Tuple(data.Left.Item1, data.Left.Item2), data.Right); + }); + } + + private static bool HasAttribute(IMethodSymbol? method, string attributeLocation) + { + if (method == null) + return false; + + foreach (AttributeData attribute in method.GetAttributes()) + { + if (attribute.AttributeClass?.ToDisplayString() == attributeLocation) + return true; + } + + return false; + } + + private static int GetPriority(IMethodSymbol method, string attributeLocation) + { + AttributeData? attribute = method.GetAttributes() + .FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == attributeLocation); + if (attribute == null) + return 0; + + if (attribute.ConstructorArguments.Length > 0) + return (int)attribute.ConstructorArguments[0].Value!; + + return 0; + } + + private static bool ValidateMethod(SourceProductionContext context, Tuple pluginInfo, IMethodSymbol method) + { + bool isValid = true; + + if (!method.IsStatic) + { + context.ReportDiagnostic( + Diagnostic.Create( + Diagnostics.MustBeStaticMethod, + method.Locations.FirstOrDefault(), + method.Name)); + + isValid = false; + } + + if (method.DeclaredAccessibility is Accessibility.Private) + { + context.ReportDiagnostic( + Diagnostic.Create( + Diagnostics.MustBeAccessibleMethod, + method.Locations.FirstOrDefault(), + method.Name, + method.DeclaredAccessibility)); + + isValid = false; + } + + return isValid; + } + + private static void Generate( + SourceProductionContext context, + Tuple pluginInfo, + ImmutableArray<(IMethodSymbol method, bool isLoad, bool isUnload)> methods) + { + if (pluginInfo.Item1 == null || pluginInfo.Item2 == null || methods.IsEmpty) + return; + + if (!pluginInfo.Item1.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) + { + context.ReportDiagnostic( + Diagnostic.Create( + Diagnostics.MustBePartialPluginClass, + pluginInfo.Item1.GetLocation(), + pluginInfo.Item1.Identifier.Text + ) + ); + } + + IMethodSymbol[] loadCalls = methods + .Where(m => m.isLoad && ValidateMethod(context, pluginInfo, m.method)) + .Select(m => m.method) + .OrderBy(m => GetPriority(m, CallOnLoadAttributeLocation)) + .ToArray(); + + IMethodSymbol[] unloadCalls = methods + .Where(m => m.isUnload && ValidateMethod(context, pluginInfo, m.method)) + .Select(m => m.method) + .OrderBy(m => GetPriority(m, CallOnUnloadAttributeLocation)) + .ToArray(); + + if (!loadCalls.Any() && !unloadCalls.Any()) + return; + + ClassBuilder classBuilder = ClassBuilder.CreateBuilder(pluginInfo.Item2) + .AddUsingStatements("System") + .AddModifiers(SyntaxKind.PartialKeyword); + + classBuilder.StartMethodCreation("OnLoad", SyntaxKind.VoidKeyword) + .AddModifiers(SyntaxKind.PublicKeyword) + .AddStatements(MethodCallStatements(loadCalls)) + .FinishMethodBuild(); + + classBuilder.StartMethodCreation("OnUnload", SyntaxKind.VoidKeyword) + .AddModifiers(SyntaxKind.PublicKeyword) + .AddStatements(MethodCallStatements(unloadCalls)) + .FinishMethodBuild(); + + classBuilder.Build(context, $"{pluginInfo.Item2.Name}.g.cs"); + } +} \ No newline at end of file diff --git a/SecretAPI.SourceGenerators/GlobalUsings.cs b/SecretAPI.SourceGenerators/GlobalUsings.cs new file mode 100644 index 0000000..eb69204 --- /dev/null +++ b/SecretAPI.SourceGenerators/GlobalUsings.cs @@ -0,0 +1,19 @@ +//? Utils from other places +global using Microsoft.CodeAnalysis; +global using Microsoft.CodeAnalysis.CSharp; +global using Microsoft.CodeAnalysis.CSharp.Syntax; +global using System.Collections.Immutable; + +//? Static utils from other places +global using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +global using static Microsoft.CodeAnalysis.CSharp.SyntaxFacts; + +//? Utils from SecretAPI +global using SecretAPI.SourceGenerators.Builders; +global using SecretAPI.SourceGenerators.Utils; + +//? Static utils from SecretAPI +global using static SecretAPI.SourceGenerators.Utils.GenericTypeUtils; +global using static SecretAPI.SourceGenerators.Utils.GeneratedIdentifyUtils; +global using static SecretAPI.SourceGenerators.Utils.MethodUtils; +global using static SecretAPI.SourceGenerators.Utils.TypeUtils; \ No newline at end of file diff --git a/SecretAPI.SourceGenerators/SecretAPI.SourceGenerators.csproj b/SecretAPI.SourceGenerators/SecretAPI.SourceGenerators.csproj new file mode 100644 index 0000000..b6bd4b5 --- /dev/null +++ b/SecretAPI.SourceGenerators/SecretAPI.SourceGenerators.csproj @@ -0,0 +1,26 @@ + + + + netstandard2.0 + 14 + true + enable + + + + true + false + Library + true + + + + + + + + + + + + diff --git a/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs b/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs new file mode 100644 index 0000000..063d3b1 --- /dev/null +++ b/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs @@ -0,0 +1,19 @@ +namespace SecretAPI.SourceGenerators.Utils; + +internal static class GeneratedIdentifyUtils +{ + private static AttributeSyntax GetGeneratedCodeAttributeSyntax() + => Attribute(IdentifierName("GeneratedCode")) + .WithArgumentList( + AttributeArgumentList( + SeparatedList( + new SyntaxNodeOrToken[] + { + AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("SecretAPI.CodeGeneration"))), + Token(SyntaxKind.CommaToken), + AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("1.0.0"))), + }))); + + internal static AttributeListSyntax GetGeneratedCodeAttributeListSyntax() + => AttributeList(SingletonSeparatedList(GetGeneratedCodeAttributeSyntax())); +} \ No newline at end of file diff --git a/SecretAPI.SourceGenerators/Utils/GenericTypeUtils.cs b/SecretAPI.SourceGenerators/Utils/GenericTypeUtils.cs new file mode 100644 index 0000000..f530632 --- /dev/null +++ b/SecretAPI.SourceGenerators/Utils/GenericTypeUtils.cs @@ -0,0 +1,12 @@ +namespace SecretAPI.SourceGenerators.Utils; + +internal static class GenericTypeUtils +{ + internal static TypeSyntax GetSingleGenericTypeSyntax(string genericName, SyntaxKind predefinedType) + => GenericName(genericName) + .WithTypeArgumentList( + TypeArgumentList( + SingletonSeparatedList( + PredefinedType( + Token(predefinedType))))); +} \ No newline at end of file diff --git a/SecretAPI.SourceGenerators/Utils/MethodParameter.cs b/SecretAPI.SourceGenerators/Utils/MethodParameter.cs new file mode 100644 index 0000000..b2dd8db --- /dev/null +++ b/SecretAPI.SourceGenerators/Utils/MethodParameter.cs @@ -0,0 +1,40 @@ +namespace SecretAPI.SourceGenerators.Utils; + +/// +/// Represents a method parameter used during code generation. +/// +internal readonly struct MethodParameter +{ + private readonly SyntaxList _attributeLists; + private readonly SyntaxTokenList _modifiers; + private readonly TypeSyntax? _type; + private readonly SyntaxToken _identifier; + private readonly EqualsValueClauseSyntax? _default; + + /// + /// Creates a new instance of . + /// + /// The name of the parameter. + /// The parameter type. May be for implicitly-typed parameters. + /// Optional parameter modifiers (e.g. ref, out, in). + /// Optional attribute lists applied to the parameter. + /// Optional default value. + internal MethodParameter( + string identifier, + TypeSyntax? type = null, + SyntaxTokenList modifiers = default, + SyntaxList attributeLists = default, + EqualsValueClauseSyntax? @default = null) + { + _identifier = IsValidIdentifier(identifier) + ? Identifier(identifier) + : throw new ArgumentException("Identifier is not valid.", nameof(identifier)); + + _type = type; + _modifiers = modifiers; + _attributeLists = attributeLists; + _default = @default; + } + + public ParameterSyntax Syntax => Parameter(_attributeLists, _modifiers, _type, _identifier, _default); +} \ No newline at end of file diff --git a/SecretAPI.SourceGenerators/Utils/MethodUtils.cs b/SecretAPI.SourceGenerators/Utils/MethodUtils.cs new file mode 100644 index 0000000..d0f4b05 --- /dev/null +++ b/SecretAPI.SourceGenerators/Utils/MethodUtils.cs @@ -0,0 +1,20 @@ +namespace SecretAPI.SourceGenerators.Utils; + +internal static class MethodUtils +{ + internal static StatementSyntax MethodCallStatement(string typeName, string methodName) => + MethodCallStatement(ParseTypeName(typeName), IdentifierName(methodName)); + + internal static StatementSyntax MethodCallStatement(TypeSyntax type, IdentifierNameSyntax method) + => ExpressionStatement( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + type, method))); + + internal static StatementSyntax[] MethodCallStatements(IMethodSymbol[] methodCalls) + { + IEnumerable statements = methodCalls.Select(s => MethodCallStatement(s.ContainingType.ToDisplayString(), s.Name)); + return statements.ToArray(); + } +} \ No newline at end of file diff --git a/SecretAPI.SourceGenerators/Utils/TypeUtils.cs b/SecretAPI.SourceGenerators/Utils/TypeUtils.cs new file mode 100644 index 0000000..1b59b10 --- /dev/null +++ b/SecretAPI.SourceGenerators/Utils/TypeUtils.cs @@ -0,0 +1,10 @@ +namespace SecretAPI.SourceGenerators.Utils; + +internal static class TypeUtils +{ + internal static PredefinedTypeSyntax GetPredefinedTypeSyntax(SyntaxKind kind) + => PredefinedType(Token(kind)); + + internal static TypeSyntax GetTypeSyntax(string typeIdentifier) + => IdentifierName(typeIdentifier); +} \ No newline at end of file diff --git a/SecretAPI.sln b/SecretAPI.sln index 4ed659a..ad97fde 100644 --- a/SecretAPI.sln +++ b/SecretAPI.sln @@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecretAPI", "SecretAPI\Secr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecretAPI.Examples", "SecretAPI.Examples\SecretAPI.Examples.csproj", "{0064C982-5FE1-4B65-82F9-2EEF85651188}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecretAPI.SourceGenerators", "SecretAPI.SourceGenerators\SecretAPI.SourceGenerators.csproj", "{15C8D708-16DE-4533-AEB6-003C3EDCAD45}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +20,9 @@ Global {0064C982-5FE1-4B65-82F9-2EEF85651188}.Debug|Any CPU.Build.0 = Debug|Any CPU {0064C982-5FE1-4B65-82F9-2EEF85651188}.Release|Any CPU.ActiveCfg = Release|Any CPU {0064C982-5FE1-4B65-82F9-2EEF85651188}.Release|Any CPU.Build.0 = Release|Any CPU + {15C8D708-16DE-4533-AEB6-003C3EDCAD45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {15C8D708-16DE-4533-AEB6-003C3EDCAD45}.Debug|Any CPU.Build.0 = Debug|Any CPU + {15C8D708-16DE-4533-AEB6-003C3EDCAD45}.Release|Any CPU.ActiveCfg = Release|Any CPU + {15C8D708-16DE-4533-AEB6-003C3EDCAD45}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/SecretAPI/SecretAPI.csproj b/SecretAPI/SecretAPI.csproj index 0302f70..055b9de 100644 --- a/SecretAPI/SecretAPI.csproj +++ b/SecretAPI/SecretAPI.csproj @@ -31,6 +31,8 @@ + + diff --git a/SecretAPI/SecretApi.cs b/SecretAPI/SecretApi.cs index 9033e9a..941f7b3 100644 --- a/SecretAPI/SecretApi.cs +++ b/SecretAPI/SecretApi.cs @@ -11,7 +11,7 @@ /// /// Main class handling loading API. /// -public class SecretApi : Plugin +public partial class SecretApi : Plugin { /// public override string Name => "SecretAPI"; From ff689db62795fd254cf33305e3409e957d4eec06 Mon Sep 17 00:00:00 2001 From: Eve <85962933+obvEve@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:01:24 +0200 Subject: [PATCH 2/6] Removed unused class --- SecretAPI.SourceGenerators/GlobalUsings.cs | 1 - .../Utils/GeneratedIdentifyUtils.cs | 2 +- SecretAPI.SourceGenerators/Utils/GenericTypeUtils.cs | 12 ------------ 3 files changed, 1 insertion(+), 14 deletions(-) delete mode 100644 SecretAPI.SourceGenerators/Utils/GenericTypeUtils.cs diff --git a/SecretAPI.SourceGenerators/GlobalUsings.cs b/SecretAPI.SourceGenerators/GlobalUsings.cs index eb69204..8869bf3 100644 --- a/SecretAPI.SourceGenerators/GlobalUsings.cs +++ b/SecretAPI.SourceGenerators/GlobalUsings.cs @@ -13,7 +13,6 @@ global using SecretAPI.SourceGenerators.Utils; //? Static utils from SecretAPI -global using static SecretAPI.SourceGenerators.Utils.GenericTypeUtils; global using static SecretAPI.SourceGenerators.Utils.GeneratedIdentifyUtils; global using static SecretAPI.SourceGenerators.Utils.MethodUtils; global using static SecretAPI.SourceGenerators.Utils.TypeUtils; \ No newline at end of file diff --git a/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs b/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs index 063d3b1..da236f0 100644 --- a/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs +++ b/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs @@ -13,7 +13,7 @@ private static AttributeSyntax GetGeneratedCodeAttributeSyntax() Token(SyntaxKind.CommaToken), AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("1.0.0"))), }))); - + internal static AttributeListSyntax GetGeneratedCodeAttributeListSyntax() => AttributeList(SingletonSeparatedList(GetGeneratedCodeAttributeSyntax())); } \ No newline at end of file diff --git a/SecretAPI.SourceGenerators/Utils/GenericTypeUtils.cs b/SecretAPI.SourceGenerators/Utils/GenericTypeUtils.cs deleted file mode 100644 index f530632..0000000 --- a/SecretAPI.SourceGenerators/Utils/GenericTypeUtils.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace SecretAPI.SourceGenerators.Utils; - -internal static class GenericTypeUtils -{ - internal static TypeSyntax GetSingleGenericTypeSyntax(string genericName, SyntaxKind predefinedType) - => GenericName(genericName) - .WithTypeArgumentList( - TypeArgumentList( - SingletonSeparatedList( - PredefinedType( - Token(predefinedType))))); -} \ No newline at end of file From a9decf7a499b0b90b8c20fa932b5326e8a089e73 Mon Sep 17 00:00:00 2001 From: Eve <85962933+obvEve@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:18:00 +0200 Subject: [PATCH 3/6] Remove unused pluginInfo parameter --- .../Generators/CallOnLoadGenerator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs b/SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs index 7147eb0..6171c97 100644 --- a/SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs +++ b/SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs @@ -69,7 +69,7 @@ private static int GetPriority(IMethodSymbol method, string attributeLocation) return 0; } - private static bool ValidateMethod(SourceProductionContext context, Tuple pluginInfo, IMethodSymbol method) + private static bool ValidateMethod(SourceProductionContext context, IMethodSymbol method) { bool isValid = true; @@ -119,13 +119,13 @@ private static void Generate( } IMethodSymbol[] loadCalls = methods - .Where(m => m.isLoad && ValidateMethod(context, pluginInfo, m.method)) + .Where(m => m.isLoad && ValidateMethod(context, m.method)) .Select(m => m.method) .OrderBy(m => GetPriority(m, CallOnLoadAttributeLocation)) .ToArray(); IMethodSymbol[] unloadCalls = methods - .Where(m => m.isUnload && ValidateMethod(context, pluginInfo, m.method)) + .Where(m => m.isUnload && ValidateMethod(context, m.method)) .Select(m => m.method) .OrderBy(m => GetPriority(m, CallOnUnloadAttributeLocation)) .ToArray(); From f6322bcc01477ec88c8a32d50b05abb4a5d00753 Mon Sep 17 00:00:00 2001 From: Eve <85962933+obvEve@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:26:55 +0200 Subject: [PATCH 4/6] Rules --- SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md | 6 +++--- SecretAPI.SourceGenerators/Diagnostics.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md b/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md index ad9f9dc..08f562d 100644 --- a/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md +++ b/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md @@ -2,6 +2,6 @@ Rule ID | Category | Severity | Notes ------------|----------|----------|--------------------- - SecretGen0 | Usage | Error | CA6000_AnalyzerName - SecretGen1 | Usage | Error | CA6000_AnalyzerName - SecretGen2 | Usage | Error | CA6000_AnalyzerName \ No newline at end of file + SG001 | Usage | Error | MustBePartialPluginClass + SG002 | Usage | Error | MustBeAccessibleMethod + SG003 | Usage | Error | MustBeStaticMethod \ No newline at end of file diff --git a/SecretAPI.SourceGenerators/Diagnostics.cs b/SecretAPI.SourceGenerators/Diagnostics.cs index 8b3e2b4..21d8073 100644 --- a/SecretAPI.SourceGenerators/Diagnostics.cs +++ b/SecretAPI.SourceGenerators/Diagnostics.cs @@ -3,7 +3,7 @@ internal static class Diagnostics { internal static readonly DiagnosticDescriptor MustBePartialPluginClass = new( - "SecretGen0", + "SG001", "Plugin class must be partial", "Plugin class '{0}' is missing partial modifier", "Usage", @@ -11,7 +11,7 @@ internal static class Diagnostics true); internal static readonly DiagnosticDescriptor MustBeAccessibleMethod = new( - "SecretGen1", + "SG002", "Method must be accessible", "Method '{0}' has accessibility '{1}', which is not supported for generated calls", "Usage", @@ -19,7 +19,7 @@ internal static class Diagnostics true); internal static readonly DiagnosticDescriptor MustBeStaticMethod = new( - "SecretGen2", + "SG003", "Method must be static", "Method '{0}' is not marked as static", "Usage", From de7fb2f34a504161ea0024ac3cd061dccac2de00 Mon Sep 17 00:00:00 2001 From: Eve <85962933+obvEve@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:37:47 +0200 Subject: [PATCH 5/6] Fix version number & OnLoad() not being utilized --- SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs | 6 ++++-- SecretAPI/SecretApi.cs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs b/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs index da236f0..02a4988 100644 --- a/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs +++ b/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs @@ -2,6 +2,8 @@ internal static class GeneratedIdentifyUtils { + private static SyntaxToken CurrentVersion => Literal(typeof(GeneratedIdentifyUtils).Assembly.GetName().Version.ToString()); + private static AttributeSyntax GetGeneratedCodeAttributeSyntax() => Attribute(IdentifierName("GeneratedCode")) .WithArgumentList( @@ -9,9 +11,9 @@ private static AttributeSyntax GetGeneratedCodeAttributeSyntax() SeparatedList( new SyntaxNodeOrToken[] { - AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("SecretAPI.CodeGeneration"))), + AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("SecretAPI.SourceGenerators"))), Token(SyntaxKind.CommaToken), - AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("1.0.0"))), + AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, CurrentVersion)), }))); internal static AttributeListSyntax GetGeneratedCodeAttributeListSyntax() diff --git a/SecretAPI/SecretApi.cs b/SecretAPI/SecretApi.cs index 941f7b3..7c5931d 100644 --- a/SecretAPI/SecretApi.cs +++ b/SecretAPI/SecretApi.cs @@ -48,7 +48,7 @@ public partial class SecretApi : Plugin /// public override void Enable() { - CallOnLoadAttribute.Load(Assembly); + OnLoad(); } /// From 34cf80c9832fea052be2fbc41ba7f922c33d19e7 Mon Sep 17 00:00:00 2001 From: Eve <85962933+obvEve@users.noreply.github.com> Date: Wed, 29 Apr 2026 21:54:33 +0200 Subject: [PATCH 6/6] Fix: Add SourceGenerators as a project to solution --- SecretAPI.slnx | 1 + 1 file changed, 1 insertion(+) diff --git a/SecretAPI.slnx b/SecretAPI.slnx index 99c20fd..6995b49 100644 --- a/SecretAPI.slnx +++ b/SecretAPI.slnx @@ -1,4 +1,5 @@ +