using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text; using XFEExtension.NetCore.ServerInteractive.SourceGenerator.Models; namespace XFEExtension.NetCore.ServerInteractive.SourceGenerator; /// /// 次级入口点增量生成器 /// 用于自动生成IServerCoreStandardService的入口点字典 /// [Generator] public class EntryPointGenerator : IIncrementalGenerator { private static readonly DiagnosticDescriptor NonPartialClassRule = new( id: "XFE0003", title: "包含EntryPoint方法的类必须为partial", messageFormat: "类'{0}'必须声明为partial以便增量生成器可以生成入口点代码", category: "XFEServerInteractive", defaultSeverity: DiagnosticSeverity.Error, helpLinkUri: "https://docs.xfegzs.com/View/Errors%2FServerInteractive%2FXFE0003", isEnabledByDefault: true); private static readonly DiagnosticDescriptor MethodMustBeParameterlessRule = new( id: "XFE0004", title: "EntryPoint方法不能有参数", messageFormat: "方法'{0}'标记了[EntryPoint]但包含参数,入口点方法必须是无参数的", category: "XFEServerInteractive", defaultSeverity: DiagnosticSeverity.Error, helpLinkUri: "https://docs.xfegzs.com/View/Errors%2FServerInteractive%2FXFE0004", isEnabledByDefault: true); private static readonly DiagnosticDescriptor InvalidReturnTypeRule = new( id: "XFE0005", title: "EntryPoint方法返回类型无效", messageFormat: "方法'{0}'的返回类型'{1}'无效,入口点方法必须返回void或Task", category: "XFEServerInteractive", defaultSeverity: DiagnosticSeverity.Error, helpLinkUri: "https://docs.xfegzs.com/View/Errors%2FServerInteractive%2FXFE0005", isEnabledByDefault: true); private static readonly DiagnosticDescriptor InvalidPathCharactersRule = new( id: "XFE0006", title: "EntryPoint路径包含无效字符", messageFormat: "入口点路径'{0}'包含无效字符(引号或反斜杠),这些字符不允许在路径中使用", category: "XFEServerInteractive", defaultSeverity: DiagnosticSeverity.Error, helpLinkUri: "https://docs.xfegzs.com/View/Errors%2FServerInteractive%2FXFE0006", isEnabledByDefault: true); private static readonly DiagnosticDescriptor DuplicatePathRule = new( id: "XFE0012", title: "EntryPoint路径重复注册", messageFormat: "入口点路径'{0}'在类'{1}'中重复注册,每个路径只能对应一个处理方法", category: "XFEServerInteractive", defaultSeverity: DiagnosticSeverity.Error, helpLinkUri: "https://docs.xfegzs.com/View/Errors%2FServerInteractive%2FXFE0012", isEnabledByDefault: true); private static readonly DiagnosticDescriptor InvalidWildcardUsageRule = new( id: "XFE0013", title: "EntryPoint通配符使用无效", messageFormat: "入口点路径'{0}'中的通配符'*'必须作为完整的路径段使用(例如:v1/*/test),不能与其他字符混合(例如:v1/a*b)", category: "XFEServerInteractive", defaultSeverity: DiagnosticSeverity.Error, helpLinkUri: "https://docs.xfegzs.com/View/Errors%2FServerInteractive%2FXFE0013", isEnabledByDefault: true); public void Initialize(IncrementalGeneratorInitializationContext context) { // 找到所有标记了EntryPointAttribute的方法 var methodDeclarations = context.SyntaxProvider .CreateSyntaxProvider( predicate: static (s, _) => IsCandidateMethod(s), transform: static (ctx, _) => GetMethodsForGeneration(ctx)) .Where(static m => m is { IsDefault: false, Length: > 0 }) .SelectMany(static (m, _) => m); // 按类分组 var compilationAndMethods = context.CompilationProvider.Combine(methodDeclarations.Collect()); // 生成源代码 context.RegisterSourceOutput(compilationAndMethods, static (spc, source) => Execute(source.Left, source.Right, spc)); } private static bool IsCandidateMethod(SyntaxNode node) => node is MethodDeclarationSyntax { AttributeLists.Count: > 0 }; private static ImmutableArray GetMethodsForGeneration(GeneratorSyntaxContext context) { var methodDeclaration = (MethodDeclarationSyntax)context.Node; var methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodDeclaration); if (methodSymbol is null) return default; // 获取所有EntryPointAttribute var entryPointAttributes = methodSymbol.GetAttributes() .Where(a => a.AttributeClass?.Name == "EntryPointAttribute") .ToList(); if (entryPointAttributes.Count == 0) return default; // 检查返回类型:根据返回类型(而非async关键字)判断同步/异步 // 注意:Task和Task都是有效的异步返回类型(Task可通过协变赋值给Func) var returnType = methodSymbol.ReturnType; var isVoid = returnType.SpecialType == SpecialType.System_Void; var isTaskLike = returnType.Name == "Task" && returnType.ContainingNamespace?.ToDisplayString() == "System.Threading.Tasks"; var hasValidReturnType = isVoid || isTaskLike; var isAsync = isTaskLike; // 检查包含类型是否为partial var classDeclaration = methodDeclaration.Parent as ClassDeclarationSyntax; var isContainingTypePartial = classDeclaration?.Modifiers.Any(SyntaxKind.PartialKeyword) ?? false; // 获取泛型类型参数和约束(从语法节点获取以保留原始文本,并去除多余空白) var typeParameters = classDeclaration?.TypeParameterList?.ToString().Trim() ?? ""; var typeConstraints = classDeclaration?.ConstraintClauses.ToString().Trim() ?? ""; var containingType = methodSymbol.ContainingType; // 获取位置信息(使用轻量结构避免在增量缓存中持有SyntaxTree引用) var methodLocation = LocationInfo.From(methodDeclaration.GetLocation()); var classLocation = classDeclaration is not null ? LocationInfo.From(classDeclaration.Identifier.GetLocation()) : methodLocation; var results = (from attr in entryPointAttributes select attr.ConstructorArguments.FirstOrDefault().Value?.ToString() into rawPath select string.IsNullOrEmpty(rawPath) ? "*" : rawPath!.Trim('/') into path select new MethodCandidate(containingType.ContainingNamespace.ToDisplayString(), containingType.Name, methodSymbol.Name, path, isAsync, isContainingTypePartial, methodSymbol.Parameters.Length, hasValidReturnType, returnType.ToDisplayString(), methodLocation, classLocation, typeParameters, typeConstraints)).ToList(); return results.Count == 0 ? default : [..results]; } private static void Execute(Compilation compilation, ImmutableArray methods, SourceProductionContext context) { if (methods.IsDefaultOrEmpty) return; var validMethods = new List(); // 校验并报告诊断信息 foreach (var method in methods) { var isValid = true; // 校验:包含类型必须为partial if (!method.IsContainingTypePartial) { context.ReportDiagnostic(Diagnostic.Create( NonPartialClassRule, method.ClassLocation.ToLocation(), method.ClassName)); isValid = false; } // 校验:方法不能有参数 if (method.ParameterCount > 0) { context.ReportDiagnostic(Diagnostic.Create( MethodMustBeParameterlessRule, method.MethodLocation.ToLocation(), method.MethodName)); isValid = false; } // 校验:返回类型必须为void或Task if (!method.HasValidReturnType) { context.ReportDiagnostic(Diagnostic.Create( InvalidReturnTypeRule, method.MethodLocation.ToLocation(), method.MethodName, method.ReturnTypeName)); isValid = false; } // 校验:路径不能包含引号或反斜杠 if (method.Path.Contains("\"") || method.Path.Contains("\\")) { context.ReportDiagnostic(Diagnostic.Create( InvalidPathCharactersRule, method.MethodLocation.ToLocation(), method.Path)); isValid = false; } // 校验:通配符 '*' 必须作为完整路径段使用 if (method.Path.Contains("*") && method.Path != "*") { var segments = method.Path.Split('/'); if (segments.Any(segment => segment.Contains("*") && segment != "*")) { context.ReportDiagnostic(Diagnostic.Create( InvalidWildcardUsageRule, method.MethodLocation.ToLocation(), method.Path)); isValid = false; } } if (isValid) { validMethods.Add(method); } } if (validMethods.Count == 0) return; // 按类分组并生成代码 var methodsByClass = validMethods.GroupBy(m => (m.Namespace, m.ClassName, m.TypeParameters, m.TypeConstraints)); foreach (var group in methodsByClass) { var (namespaceName, className, typeParameters, typeConstraints) = group.Key; var methodInfos = group.ToList(); var constraintsSuffix = string.IsNullOrEmpty(typeConstraints) ? "" : $" {typeConstraints}"; // 检查重复路径(路径相同的任意两个方法都报错,无论同步/异步) var hasDuplicateError = false; var pathToMethods = new Dictionary>(); foreach (var method in methodInfos) { if (!pathToMethods.TryGetValue(method.Path, out var list)) { list = []; pathToMethods[method.Path] = list; } list.Add(method); } foreach (var kvp in pathToMethods.Where(kvp => kvp.Value.Count > 1)) { hasDuplicateError = true; foreach (var dup in kvp.Value) { context.ReportDiagnostic(Diagnostic.Create( DuplicatePathRule, dup.MethodLocation.ToLocation(), kvp.Key, className)); } } if (hasDuplicateError) continue; var sourceBuilder = new StringBuilder(); sourceBuilder.AppendLine($@"// #nullable enable using System; using System.Collections.Generic; using System.Threading.Tasks; namespace {namespaceName} {{ /// /// {className}的自动生成入口点字典部分类 /// public partial class {className}{typeParameters}{constraintsSuffix} {{ /// /// 本类型的入口点路径列表(覆盖基类的空列表) /// public override List EntryPointList {{ get; }} = new() {{"); // 添加所有入口点到静态列表 foreach (var method in methodInfos) { sourceBuilder.AppendLine($" \"{EscapeStringLiteral(method.Path)}\","); } sourceBuilder.AppendLine($@" }}; private Dictionary? _generatedSyncEntryPoints; /// public override Dictionary SyncEntryPoints {{ get => _generatedSyncEntryPoints ??= new Dictionary() {{"); // 添加同步入口点 foreach (var method in methodInfos.Where(m => !m.IsAsync)) { sourceBuilder.AppendLine($" {{ \"{EscapeStringLiteral(method.Path)}\", {method.MethodName} }},"); } sourceBuilder.AppendLine($@" }}; }} private Dictionary>? _generatedAsyncEntryPoints; /// public override Dictionary> AsyncEntryPoints {{ get => _generatedAsyncEntryPoints ??= new Dictionary>() {{"); // 添加异步入口点 foreach (var method in methodInfos.Where(m => m.IsAsync)) { sourceBuilder.AppendLine($" {{ \"{EscapeStringLiteral(method.Path)}\", {method.MethodName} }},"); } sourceBuilder.AppendLine(@" }; } } }"); context.AddSource($"{className}.EntryPoints.g.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); } } /// /// 转义字符串字面量中的特殊字符 /// private static string EscapeStringLiteral(string value) { return value.Replace("\\", @"\\").Replace("\"", "\\\""); } }