Files
Infratest/Assets/SourceGeneration/InfraGens~/InfraGens/InfraGens/IngrementalConfigGenerator.cs
T
2026-01-20 20:38:20 +03:00

399 lines
18 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
[Generator]
public class StateMachineGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 1. Собираем все типы с атрибутом [StateMachine]
var stateMachineTypes = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (s, _) => IsStateMachineCandidate(s),
transform: static (ctx, _) => GetStateMachineType(ctx))
.Where(static m => m is not null)
.Collect();
// 2. Собираем все типы с атрибутом [State]
var stateTypes = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (s, _) => IsStateCandidate(s),
transform: static (ctx, _) => GetStateType(ctx))
.Where(static m => m is not null)
.Collect();
// 3. Получаем компиляцию для семантического анализа
var compilation = context.CompilationProvider;
// 4. Объединяем все данные
var combinedData = stateMachineTypes.Combine(stateTypes).Combine(compilation);
// 5. Регистрируем генерацию кода
context.RegisterSourceOutput(combinedData, static (spc, source) =>
Execute(spc, source.Left.Left, source.Left.Right, source.Right));
}
private static bool IsStateMachineCandidate(SyntaxNode node)
{
return node is ClassDeclarationSyntax classDecl &&
classDecl.AttributeLists.Count > 0;
}
private static bool IsStateCandidate(SyntaxNode node)
{
return node is ClassDeclarationSyntax classDecl &&
classDecl.AttributeLists.Count > 0;
}
private static INamedTypeSymbol? GetStateMachineType(GeneratorSyntaxContext context)
{
var classDecl = (ClassDeclarationSyntax)context.Node;
var classSymbol = context.SemanticModel.GetDeclaredSymbol(classDecl);
if (classSymbol == null) return null;
// Проверяем наличие атрибута [StateMachine]
var stateMachineAttribute = context.SemanticModel.Compilation.GetTypeByMetadataName("StateMachine");
if (stateMachineAttribute == null) return null;
return classSymbol.GetAttributes()
.Any(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, stateMachineAttribute))
? classSymbol
: null;
}
private static INamedTypeSymbol? GetStateType(GeneratorSyntaxContext context)
{
var classDecl = (ClassDeclarationSyntax)context.Node;
var classSymbol = context.SemanticModel.GetDeclaredSymbol(classDecl);
if (classSymbol == null) return null;
// Проверяем наличие атрибута [State]
var stateAttribute = context.SemanticModel.Compilation.GetTypeByMetadataName("State");
if (stateAttribute == null) return null;
return classSymbol.GetAttributes()
.Any(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, stateAttribute))
? classSymbol
: null;
}
private static void Execute(
SourceProductionContext context,
ImmutableArray<INamedTypeSymbol> stateMachineTypes,
ImmutableArray<INamedTypeSymbol> stateTypes,
Compilation compilation)
{
if (stateMachineTypes.IsDefaultOrEmpty && stateTypes.IsDefaultOrEmpty)
return;
var validStateMachines = stateMachineTypes.Where(t => t != null).ToList();
var validStates = stateTypes.Where(t => t != null).ToList();
if (!validStateMachines.Any() && !validStates.Any())
return;
// Получаем символы атрибутов
var stateMachineAttributeSymbol = compilation.GetTypeByMetadataName("StateMachine");
var stateAttributeSymbol = compilation.GetTypeByMetadataName("State");
var stateRaisingEventAttributeSymbol = compilation.GetTypeByMetadataName("StateRaisingEvent");
if (stateMachineAttributeSymbol == null || stateAttributeSymbol == null)
{
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor("SMG001", "Missing attributes",
"StateMachine or State attributes not found", "StateMachine", DiagnosticSeverity.Error, true),
Location.None));
return;
}
// Генерируем код для StateMachine и States
GenerateStateClasses(context, validStates, stateAttributeSymbol, stateRaisingEventAttributeSymbol, compilation);
GenerateStateMachineClasses(context, validStateMachines, validStates, stateMachineAttributeSymbol, stateAttributeSymbol, compilation);
}
private static void GenerateStateMachineClasses(
SourceProductionContext context,
List<INamedTypeSymbol> stateMachines,
List<INamedTypeSymbol> allStates,
INamedTypeSymbol stateMachineAttributeSymbol,
INamedTypeSymbol stateAttributeSymbol,
Compilation compilation)
{
// if (allStates.Count == 0) throw new Exception("RETARDED");
foreach (var sm in stateMachines)
{
// Получаем параметры атрибута [StateMachine]
var smAttribute = sm.GetAttributes()
.FirstOrDefault(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, stateMachineAttributeSymbol));
if (smAttribute == null) continue;
var stateMachineContextType = GetNamedTypeArgument(smAttribute, "StateMachineContextType");
var stateMachineEnumType = GetNamedTypeArgument(smAttribute, "StateMachineEnumType");
if (stateMachineContextType == null || stateMachineEnumType == null)
{
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor("SMG002", "Invalid StateMachine attribute",
$"StateMachine {sm.Name} has invalid attribute parameters", "StateMachine", DiagnosticSeverity.Error, true),
sm.Locations.FirstOrDefault()));
continue;
}
// Находим все states для этого state machine
var statesForThisMachine = allStates
.Where(st =>
{
var stAttribute = st.GetAttributes()
.FirstOrDefault(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, stateAttributeSymbol));
if (stAttribute == null) return false;
var stContextType = GetNamedTypeArgument(stAttribute, "StateMachineEnumType"); // БЛЯТЬ ИДИ НАХУЙ ПИДАРАС ТРАНСВИСТИТНЫЙ ПИЗДУЙ В ТАЙЛАНД
// Сравниваем по полному имени типа, как в оригинальном коде
return stContextType != null &&
stContextType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) ==
stateMachineContextType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
})
.ToList();
// Собираем пространства имен для states
var stateNamespaces = new HashSet<string>();
foreach (var st in statesForThisMachine)
{
if (st.ContainingNamespace != null && !st.ContainingNamespace.IsGlobalNamespace)
{
stateNamespaces.Add(st.ContainingNamespace.ToDisplayString());
}
}
// Отладка: проверяем, найдены ли стейты
if (statesForThisMachine.Count == 0)
{
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor("SMG004", "No states found",
$"No states found for StateMachine {sm.Name} with context {stateMachineContextType.Name}",
"StateMachine", DiagnosticSeverity.Warning, true),
sm.Locations.FirstOrDefault()));
}
// Генерируем код для StateMachine
var stateMachineCode = GenerateStateMachineClassCode(
sm, stateMachineContextType, stateMachineEnumType, statesForThisMachine, stateNamespaces);
var fileName = $"{sm.ContainingNamespace?.ToDisplayString() ?? ""}.{sm.Name}.StateMachine.Generated.cs"
.Replace("<", "_").Replace(">", "_").Replace("+", "_");
context.AddSource(fileName, SourceText.From(stateMachineCode, Encoding.UTF8));
}
}
private static void GenerateStateClasses(
SourceProductionContext context,
List<INamedTypeSymbol> states,
INamedTypeSymbol stateAttributeSymbol,
INamedTypeSymbol? stateRaisingEventAttributeSymbol,
Compilation compilation)
{
foreach (var st in states)
{
var stateAttribute = st.GetAttributes()
.FirstOrDefault(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, stateAttributeSymbol));
if (stateAttribute == null) continue;
var stateEnumType = GetNamedTypeArgument(stateAttribute, "StateEnum");
var stateMachineContextType = GetNamedTypeArgument(stateAttribute, "StateMachineContext");
var isSilent = GetBooleanArgument(stateAttribute, "SilentBitch");
var hasRaisingEvent = stateRaisingEventAttributeSymbol != null &&
st.HasAttribute(stateRaisingEventAttributeSymbol);
if (stateEnumType == null || stateMachineContextType == null)
{
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor("SMG003", "Invalid State attribute",
$"State {st.Name} has invalid attribute parameters", "StateMachine", DiagnosticSeverity.Error, true),
st.Locations.FirstOrDefault()));
continue;
}
// Генерируем код для State
var stateCode = GenerateStateClassCode(
st, stateEnumType, stateMachineContextType, isSilent, hasRaisingEvent);
var fileName = $"{st.ContainingNamespace?.ToDisplayString() ?? ""}.{st.Name}.State.Generated.cs"
.Replace("<", "_").Replace(">", "_").Replace("+", "_");
context.AddSource(fileName, SourceText.From(stateCode, Encoding.UTF8));
}
}
private static string GenerateStateMachineClassCode(
INamedTypeSymbol sm,
INamedTypeSymbol contextType,
INamedTypeSymbol enumType,
List<INamedTypeSymbol> states,
HashSet<string> stateNamespaces)
{
var sb = new StringBuilder();
sb.AppendLine("// <auto-generated/>");
sb.AppendLine("#pragma warning disable CS0105");
sb.AppendLine("using Code.Infrastructure.StateMachine;");
// Добавляем using для всех пространств имен states
foreach (var ns in stateNamespaces.OrderBy(n => n))
{
if (!string.IsNullOrEmpty(ns) && ns != sm.ContainingNamespace?.ToDisplayString())
{
sb.AppendLine($"using {ns};");
}
}
sb.AppendLine("#pragma warning restore CS0105");
sb.AppendLine();
if (sm.ContainingNamespace != null && !sm.ContainingNamespace.IsGlobalNamespace)
{
sb.AppendLine($"namespace {sm.ContainingNamespace.ToDisplayString()}");
sb.AppendLine("{");
}
sb.AppendLine($"public partial class {sm.Name} : AbstractStateMachine<{enumType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}, {contextType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>");
sb.AppendLine("{");
// Конструктор с добавлением всех стейтов
sb.AppendLine($" public {sm.Name}({contextType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} context, EventBus.EventBus eventBus) : base(context)");
sb.AppendLine(" {");
if (states.Count == 0) sb.AppendLine("//I am retarded");
foreach (var state in states)
{
sb.AppendLine($" var state{state.Name} = new {state.Name}(StateMachineContext, eventBus);");
sb.AppendLine($" States.Add(state{state.Name}.myState, state{state.Name});");
}
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine($" protected override bool CheckTransitionAllowed({enumType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} nextState) => AllowedStates.HasFlag(nextState);");
sb.AppendLine(" protected override void ResetAllowedTransitions() => AllowedStates = 0;");
sb.AppendLine("}");
sb.AppendLine();
sb.AppendLine($"public static class {sm.Name}Extensions");
sb.AppendLine("{");
sb.AppendLine($" public static AbstractStateMachine<{enumType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}, {contextType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}> AllowStateTransition(");
sb.AppendLine($" this AbstractStateMachine<{enumType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}, {contextType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}> stateMachine, {enumType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} state)");
sb.AppendLine(" {");
sb.AppendLine(" if (stateMachine == null) return null;");
sb.AppendLine(" stateMachine.AllowedStates |= state;");
sb.AppendLine(" return stateMachine;");
sb.AppendLine(" }");
sb.AppendLine("}");
if (sm.ContainingNamespace != null && !sm.ContainingNamespace.IsGlobalNamespace)
{
sb.AppendLine("}");
}
return sb.ToString();
}
private static string GenerateStateClassCode(
INamedTypeSymbol state,
INamedTypeSymbol stateEnumType,
INamedTypeSymbol contextType,
bool isSilent,
bool hasRaisingEvent)
{
var sb = new StringBuilder();
sb.AppendLine("// <auto-generated/>");
sb.AppendLine("using Code.Infrastructure.StateMachine;");
sb.AppendLine();
if (state.ContainingNamespace != null && !state.ContainingNamespace.IsGlobalNamespace)
{
sb.AppendLine($"namespace {state.ContainingNamespace.ToDisplayString()}");
sb.AppendLine("{");
}
var fireStatesCode = "";
if (hasRaisingEvent)
{
fireStatesCode = $@"
protected override void _fireEnterState()
{{
EventBus.RaiseEvent<IEnterStateHandle<{state.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>>(h => h.EnterState());
}}
protected override void _fireExitState()
{{
EventBus.RaiseEvent<IExitStateHandle<{state.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>>(h => h.ExitState());
}}
";
}
var genetics = "";
if (!isSilent)
{
// ИСПРАВЛЕНО: Поменяны местами аргументы шаблона - сначала контекст, потом enum
genetics = $@" : BaseState<{contextType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}, {stateEnumType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>";
}
sb.AppendLine($"public partial class {state.Name}{genetics}");
sb.AppendLine("{");
sb.AppendLine(fireStatesCode);
sb.AppendLine("}");
if (state.ContainingNamespace != null && !state.ContainingNamespace.IsGlobalNamespace)
{
sb.AppendLine("}");
}
return sb.ToString();
}
private static INamedTypeSymbol? GetNamedTypeArgument(AttributeData attribute, string argumentName)
{
// Проверяем именованные аргументы
foreach (var namedArg in attribute.NamedArguments)
{
if (namedArg.Key == argumentName && namedArg.Value.Value is INamedTypeSymbol namedType)
return namedType;
}
// Проверяем конструкторные аргументы
var constructorArgs = attribute.ConstructorArguments;
if (constructorArgs.Length >= 2 && constructorArgs[0].Value is INamedTypeSymbol firstType &&
constructorArgs[1].Value is INamedTypeSymbol secondType)
{
// Для StateMachineAttribute и StateAttribute порядок параметров в конструкторе:
// [StateMachine(typeof(ContextType), typeof(EnumType))]
// [State(typeof(ContextType), typeof(EnumType))]
if (argumentName == "StateMachineContextType" || argumentName == "StateMachineContext")
return firstType;
if (argumentName == "StateMachineEnumType" || argumentName == "StateEnum")
return secondType;
}
return null;
}
private static bool GetBooleanArgument(AttributeData attribute, string argumentName)
{
foreach (var namedArg in attribute.NamedArguments)
{
if (namedArg.Key == argumentName && namedArg.Value.Value is bool boolValue)
return boolValue;
}
return false;
}
}