Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
fd69ae3
meh
obvEve Jul 11, 2025
3021a01
idk!!!!
obvEve Jul 11, 2025
aec1d0b
i hate this ???
obvEve Jul 13, 2025
e6b27fa
properenum validation
obvEve Jul 13, 2025
9516c89
stoopid
obvEve Jul 13, 2025
0ebf5a6
ok slight more
obvEve Aug 6, 2025
ecf4152
rahhh
obvEve Aug 6, 2025
ba7b32c
command example
obvEve Aug 6, 2025
7775cd2
idfk
obvEve Aug 6, 2025
2fb2ca6
.Parsing & .Attributes namespaces
obvEve Dec 17, 2025
b8b5e5d
Default to no alias
obvEve Dec 17, 2025
fc9ef22
Source generator
obvEve Dec 18, 2025
646d74e
Source generator part 2
obvEve Dec 18, 2025
7b6b396
Merge branch 'dev' into commands-makes-me-sad
obvEve Dec 18, 2025
e5369ca
Add some extra stuff (some of it works!!)
obvEve Dec 18, 2025
db54a92
Update workflows
obvEve Dec 18, 2025
6c97fcc
Update workflow path
obvEve Dec 18, 2025
f015de8
Update PR workflow
obvEve Dec 18, 2025
d74b60b
Update workflow for PRs
obvEve Dec 18, 2025
53e24f8
Minor improvements
obvEve Dec 18, 2025
b2be9b4
Guh
obvEve Dec 18, 2025
c4c5537
Use SyntaxFactory
obvEve Dec 21, 2025
05e4c05
Commit some bits
obvEve Dec 21, 2025
89884e4
Working CallOnLoadGenerator
obvEve Dec 21, 2025
22efa3c
Commit
obvEve Dec 21, 2025
1680519
Validators
obvEve Jan 3, 2026
24c5551
Merge branch '3.0' into commands-makes-me-sad
obvEve Jan 6, 2026
db7bd31
Namespace change
obvEve Jan 6, 2026
8ed9738
Validate using isnt already added
obvEve Jan 6, 2026
22d568f
Remove unused WritingUtils
obvEve Jan 6, 2026
cefadc9
ShouldAutoGenerate disabling + Fix SecretAPI nuget
obvEve Jan 6, 2026
a0046b1
Merge branch '3.0' into commands-makes-me-sad
obvEve Jan 6, 2026
d12dfa1
Validator start
obvEve Jan 6, 2026
105aacd
Player argument validator
obvEve Jan 6, 2026
005fc99
ValidatorSingleton
obvEve Jan 6, 2026
379cd15
Minor changes
obvEve Jan 7, 2026
1e144cb
Merge branch '3.0' into commands-makes-me-sad
obvEve Jan 7, 2026
212e9ba
Merge branch '3.0' into commands-makes-me-sad
obvEve Jan 7, 2026
545aa3b
Fix generator namespace
obvEve Jan 7, 2026
0a77a00
Seperate utils
obvEve Jan 8, 2026
b5696da
Improvements
obvEve Jan 8, 2026
5ac0e34
Analyse
obvEve Jan 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions SecretAPI.CodeGeneration/AnalyzerReleases.Shipped.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

5 changes: 5 additions & 0 deletions SecretAPI.CodeGeneration/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
### New Rules

Rule ID | Category | Severity | Notes
------------|----------|----------|---------------------
SecretGen0 | Usage | Error | CA6000_AnalyzerName
60 changes: 60 additions & 0 deletions SecretAPI.CodeGeneration/CodeBuilders/ClassBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
namespace SecretAPI.CodeGeneration.CodeBuilders;

internal class ClassBuilder : CodeBuilder<ClassBuilder>
{
private NamespaceDeclarationSyntax _namespaceDeclaration;
private ClassDeclarationSyntax _classDeclaration;

private readonly List<UsingDirectiveSyntax> _usings = new();
private readonly List<MethodDeclarationSyntax> _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<MemberDeclarationSyntax>().ToArray());

_namespaceDeclaration = _namespaceDeclaration
.AddUsings(_usings.ToArray())
.AddMembers(_classDeclaration);

return CompilationUnit()
.AddMembers(_namespaceDeclaration)
.NormalizeWhitespace()
.WithLeadingTrivia(Comment("// <auto-generated>"), LineFeed, Comment("#pragma warning disable"), LineFeed, LineFeed);
}

internal void Build(SourceProductionContext context, string name) => context.AddSource(name, Build().ToFullString());
}
19 changes: 19 additions & 0 deletions SecretAPI.CodeGeneration/CodeBuilders/CodeBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace SecretAPI.CodeGeneration.CodeBuilders;

/// <summary>
/// Base of a code builder.
/// </summary>
/// <typeparam name="TCodeBuilder">The <see cref="CodeBuilder{TCodeBuilder}"/> this is handling.</typeparam>
internal abstract class CodeBuilder<TCodeBuilder>
where TCodeBuilder : CodeBuilder<TCodeBuilder>
{
protected readonly List<SyntaxToken> _modifiers = new();

internal TCodeBuilder AddModifiers(params SyntaxKind[] modifiers)
{
foreach (SyntaxKind token in modifiers)
_modifiers.Add(Token(token));

return (TCodeBuilder)this;
}
}
44 changes: 44 additions & 0 deletions SecretAPI.CodeGeneration/CodeBuilders/MethodBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
namespace SecretAPI.CodeGeneration.CodeBuilders;

internal class MethodBuilder : CodeBuilder<MethodBuilder>
{
private readonly ClassBuilder _classBuilder;
private readonly List<ParameterSyntax> _parameters = new();
private readonly List<StatementSyntax> _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;
}
}
12 changes: 12 additions & 0 deletions SecretAPI.CodeGeneration/Diagnostics/CommandDiagnostics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace SecretAPI.CodeGeneration.Diagnostics;

internal static class CommandDiagnostics
{
internal static readonly DiagnosticDescriptor InvalidExecuteMethod = new(
"SecretGen0",
"Invalid ExecuteCommand method",
"Method '{0}' marked with [ExecuteCommand] is invalid: {1}",
"Usage",
DiagnosticSeverity.Error,
true);
}
122 changes: 122 additions & 0 deletions SecretAPI.CodeGeneration/Generators/CallOnLoadGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
namespace SecretAPI.CodeGeneration.Generators;

/// <summary>
/// Code generator for CallOnLoad/CallOnUnload
/// </summary>
[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";

/// <inheritdoc/>
public void Initialize(IncrementalGeneratorInitializationContext context)
{
IncrementalValuesProvider<IMethodSymbol> 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<INamedTypeSymbol?> pluginClassProvider =
context.SyntaxProvider.CreateSyntaxProvider(
static (node, _) => node is ClassDeclarationSyntax,
static (ctx, _) =>
ctx.SemanticModel.GetDeclaredSymbol(ctx.Node) as INamedTypeSymbol)
.Where(static c => c != null && !c.IsAbstract && c.BaseType?.Name == PluginBaseClassName &&
c.BaseType.ContainingNamespace.ToDisplayString() == PluginNamespace);

context.RegisterSourceOutput(pluginClassProvider.Combine(callProvider.Collect()), static (context, data) =>
{
Generate(context, data.Left, 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 ShouldAutogenerate(IMethodSymbol method, string attributeLocation)
{
AttributeData? attribute = method.GetAttributes()
.FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == attributeLocation);

if (attribute is { ConstructorArguments.Length: >= 2 })
return (bool)attribute.ConstructorArguments[1].Value!;

return false;
}

private static void Generate(
SourceProductionContext context,
INamedTypeSymbol? pluginClassSymbol,
ImmutableArray<(IMethodSymbol method, bool isLoad, bool isUnload)> methods)
{
if (pluginClassSymbol == null || methods.IsEmpty)
return;

IMethodSymbol[] loadCalls = methods
.Where(m => m.isLoad && ShouldAutogenerate(m.method, CallOnLoadAttributeLocation))
.Select(m => m.method)
.OrderBy(m => GetPriority(m, CallOnLoadAttributeLocation))
.ToArray();

IMethodSymbol[] unloadCalls = methods
.Where(m => m.isUnload && ShouldAutogenerate(m.method, CallOnUnloadAttributeLocation))
.Select(m => m.method)
.OrderBy(m => GetPriority(m, CallOnUnloadAttributeLocation))
.ToArray();

if (!loadCalls.Any() && !unloadCalls.Any())
return;

ClassBuilder classBuilder = ClassBuilder.CreateBuilder(pluginClassSymbol)
.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, $"{pluginClassSymbol.Name}.g.cs");
}
}
Loading
Loading