254 lines
11 KiB
C#
254 lines
11 KiB
C#
using Microsoft.CodeAnalysis;
|
||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||
using System.Collections.Generic;
|
||
using System.Diagnostics;
|
||
using System.Linq;
|
||
using System.Text;
|
||
|
||
namespace JiShe.CollectBus.IncrementalGenerator
|
||
{
|
||
/// <summary>
|
||
/// 复杂类型源生成器
|
||
/// </summary>
|
||
[Generator(LanguageNames.CSharp)]
|
||
public class ComplexTypeSourceAnalyzers : IIncrementalGenerator
|
||
{
|
||
private const string AttributeFullName = "JiShe.CollectBus.Analyzers.Shared.GenerateAccessorsAttribute";
|
||
|
||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||
{
|
||
//Debugger.Launch();
|
||
|
||
context.RegisterPostInitializationOutput(ctx =>
|
||
{
|
||
ctx.AddSource("GeneratorInit.g.cs", "// Initialization Marker");
|
||
});
|
||
|
||
// 步骤1:筛选带有 [GenerateAccessors] 的类
|
||
var classDeclarations = context.SyntaxProvider
|
||
.CreateSyntaxProvider(
|
||
predicate: static (s, _) => IsClassWithAttribute(s),
|
||
transform: static (ctx, _) => GetClassDeclaration(ctx))
|
||
.Where(static c => c is not null);
|
||
|
||
// 步骤2:合并编译信息
|
||
var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect());
|
||
|
||
context.RegisterSourceOutput(compilationAndClasses, (spc, source) =>
|
||
GenerateCode(source.Left, source.Right!, spc));
|
||
}
|
||
|
||
private static bool IsClassWithAttribute(SyntaxNode node) => node is ClassDeclarationSyntax cds && cds.AttributeLists.Count > 0;
|
||
|
||
|
||
private static ClassDeclarationSyntax GetClassDeclaration(GeneratorSyntaxContext context)
|
||
{
|
||
var classDecl = (ClassDeclarationSyntax)context.Node;
|
||
var attributeType = context.SemanticModel.Compilation.GetTypeByMetadataName(AttributeFullName);
|
||
|
||
foreach (var attribute in classDecl.AttributeLists.SelectMany(al => al.Attributes))
|
||
{
|
||
var symbol = context.SemanticModel.GetSymbolInfo(attribute).Symbol;
|
||
if (symbol is IMethodSymbol ctor &&
|
||
SymbolEqualityComparer.Default.Equals(ctor.ContainingType, attributeType))
|
||
{
|
||
return classDecl;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 递归获取所有层级的属性
|
||
/// </summary>
|
||
/// <param name="classSymbol"></param>
|
||
/// <returns></returns>
|
||
private static IEnumerable<IPropertySymbol> GetAllPropertiesInHierarchy(INamedTypeSymbol classSymbol)
|
||
{
|
||
var currentSymbol = classSymbol;
|
||
while (currentSymbol != null)
|
||
{
|
||
foreach (var prop in currentSymbol.GetMembers().OfType<IPropertySymbol>())
|
||
{
|
||
yield return prop;
|
||
}
|
||
currentSymbol = currentSymbol.BaseType; // 向上遍历基类
|
||
}
|
||
}
|
||
|
||
private static void GenerateCode(
|
||
Compilation compilation,
|
||
IEnumerable<ClassDeclarationSyntax> classes,
|
||
SourceProductionContext context)
|
||
{
|
||
var processedTypes = new HashSet<ITypeSymbol>(SymbolEqualityComparer.Default);
|
||
|
||
if (!classes.Any())
|
||
{
|
||
context.ReportDiagnostic(Diagnostic.Create(
|
||
new DiagnosticDescriptor("GEN002", "No Targets",
|
||
"No classes with [GenerateAccessors] found", "Debug", DiagnosticSeverity.Warning, true),
|
||
Location.None));
|
||
}
|
||
|
||
foreach (var classDecl in classes.Distinct())
|
||
{
|
||
var model = compilation.GetSemanticModel(classDecl.SyntaxTree);
|
||
var classSymbol = model.GetDeclaredSymbol(classDecl) as INamedTypeSymbol;
|
||
|
||
if (classSymbol == null || !processedTypes.Add(classSymbol))
|
||
{
|
||
context.ReportDiagnostic(Diagnostic.Create(
|
||
new DiagnosticDescriptor("GEN003", "Invalid Symbol",
|
||
$"Class symbol is null for {classDecl.Identifier.Text}", "Error", DiagnosticSeverity.Error, true),
|
||
Location.None));
|
||
continue;
|
||
}
|
||
|
||
context.ReportDiagnostic(Diagnostic.Create(
|
||
new DiagnosticDescriptor(
|
||
"PA001",
|
||
"Generated Accessors",
|
||
$"Generating accessors for {classSymbol.Name}",
|
||
"Performance",
|
||
DiagnosticSeverity.Info,
|
||
true),
|
||
Location.None));
|
||
|
||
// 新增:输出继承链信息
|
||
context.ReportDiagnostic(Diagnostic.Create(
|
||
new DiagnosticDescriptor("HIERARCHY", "Class Hierarchy",
|
||
$"Processing class: {classSymbol.Name}, BaseType: {classSymbol.BaseType?.Name}",
|
||
"Debug", DiagnosticSeverity.Info, true),
|
||
Location.None));
|
||
|
||
context.ReportDiagnostic(Diagnostic.Create(
|
||
new DiagnosticDescriptor("PA002", "Class Found",
|
||
$"Processing class: {classSymbol.Name}", "Debug", DiagnosticSeverity.Warning, true),
|
||
Location.None));
|
||
|
||
var code = BuildAccessorsForType(classSymbol, compilation, processedTypes);
|
||
|
||
System.Diagnostics.Debug.WriteLine($"Generated code for {classSymbol.Name}:\n{code}"); // 调试输出
|
||
|
||
context.AddSource($"{classSymbol.Name}Extension.g.cs", code);
|
||
}
|
||
}
|
||
|
||
private static string BuildAccessorsForType(
|
||
INamedTypeSymbol classSymbol,
|
||
Compilation compilation,
|
||
HashSet<ITypeSymbol> processedTypes)
|
||
{
|
||
var code = new StringBuilder();
|
||
code.AppendLine("#pragma warning disable CS0419 // 禁用警告");
|
||
code.AppendLine("// Generated code for " + classSymbol.Name);
|
||
code.AppendLine("// <auto-generated/>");
|
||
code.AppendLine("#nullable enable");
|
||
code.AppendLine($"namespace {classSymbol.ContainingNamespace.ToDisplayString()};");
|
||
code.AppendLine();
|
||
|
||
code.AppendLine($"public static class {classSymbol.Name}Extension{GetGenericParams(classSymbol)}");
|
||
code.AppendLine("{");
|
||
|
||
//foreach (var prop in classSymbol.GetMembers().OfType<IPropertySymbol>())
|
||
//{
|
||
// if (prop.IsIndexer) continue;
|
||
|
||
// GeneratePropertyAccessors(prop, code, compilation, processedTypes);
|
||
//}
|
||
|
||
foreach (var prop in GetAllPropertiesInHierarchy(classSymbol))
|
||
{
|
||
if (prop.IsIndexer) continue;
|
||
GeneratePropertyAccessors(prop, code, compilation, processedTypes);
|
||
}
|
||
|
||
code.AppendLine("}");
|
||
return code.ToString();
|
||
}
|
||
|
||
//private static string GetGenericParams(INamedTypeSymbol symbol)
|
||
// => symbol.IsGenericType ? $"<{string.Join(", ", symbol.TypeParameters.Select(t => t.Name))}>" : "";
|
||
public static string GetGenericParams(INamedTypeSymbol symbol)
|
||
{
|
||
if (!symbol.IsGenericType) return "";
|
||
var parameters = symbol.TypeParameters.Select(t => t.Name);
|
||
return $"<{string.Join(", ", parameters)}>";
|
||
}
|
||
|
||
private static void GeneratePropertyAccessors(
|
||
IPropertySymbol prop,
|
||
StringBuilder code,
|
||
Compilation compilation,
|
||
HashSet<ITypeSymbol> processedTypes)
|
||
{
|
||
// 关键修复点1:安全类型转换
|
||
if (prop.Type is not ITypeSymbol propType) return;
|
||
|
||
code.AppendLine($" // Processing property: {prop.Name}");
|
||
|
||
// 处理元组类型
|
||
if (propType is INamedTypeSymbol { IsTupleType: true } tupleType)
|
||
{
|
||
GenerateTupleAccessors(prop, tupleType, code);
|
||
}
|
||
else if (propType is INamedTypeSymbol namedType)
|
||
{
|
||
GenerateStandardAccessors(prop, namedType, code);
|
||
ProcessNestedType(namedType, compilation, processedTypes);
|
||
}
|
||
}
|
||
|
||
private static void GenerateTupleAccessors(IPropertySymbol prop, INamedTypeSymbol tupleType, StringBuilder code)
|
||
{
|
||
var elements = tupleType.TupleElements;
|
||
var parentType = prop.ContainingType.ToDisplayString();
|
||
|
||
for (int i = 0; i < elements.Length; i++)
|
||
{
|
||
var element = elements[i];
|
||
if (element.Type is not ITypeSymbol elementType) continue;
|
||
|
||
var elementName = element.CorrespondingTupleField?.Name ?? $"Item{i + 1}";
|
||
code.AppendLine($" public static {elementType.ToDisplayString()} Get{prop.Name}_{elementName}({parentType} obj) => obj.{prop.Name}.{elementName};");
|
||
|
||
if (prop.SetMethod != null)
|
||
{
|
||
var assignments = elements.Select((e, idx) =>
|
||
idx == i ? "value" : $"obj.{prop.Name}.{e.Name}");
|
||
code.AppendLine($" public static void Set{prop.Name}_{elementName}({parentType} obj, {elementType.ToDisplayString()} value) => obj.{prop.Name} = ({string.Join(", ", assignments)});");
|
||
}
|
||
}
|
||
}
|
||
|
||
private static void GenerateStandardAccessors(IPropertySymbol prop, INamedTypeSymbol propType, StringBuilder code)
|
||
{
|
||
var parentType = prop.ContainingType.ToDisplayString();
|
||
code.AppendLine($" public static {propType.ToDisplayString()} Get{prop.Name}({parentType} obj) => obj.{prop.Name};");
|
||
|
||
if (prop.SetMethod != null)
|
||
{
|
||
code.AppendLine($" public static void Set{prop.Name}({parentType} obj, {propType.ToDisplayString()} value) => obj.{prop.Name} = value;");
|
||
}
|
||
}
|
||
|
||
private static void ProcessNestedType(ITypeSymbol typeSymbol, Compilation compilation, HashSet<ITypeSymbol> processedTypes)
|
||
{
|
||
if (typeSymbol is not INamedTypeSymbol namedType) return;
|
||
if (!ShouldProcessNestedType(namedType)) return;
|
||
if (!processedTypes.Add(namedType)) return;
|
||
|
||
var code = BuildAccessorsForType(namedType, compilation, processedTypes);
|
||
}
|
||
|
||
private static bool ShouldProcessNestedType(INamedTypeSymbol symbol)
|
||
{
|
||
return symbol.DeclaredAccessibility == Accessibility.Public &&
|
||
!symbol.IsTupleType &&
|
||
!symbol.IsAnonymousType &&
|
||
!symbol.IsImplicitlyDeclared &&
|
||
!symbol.Name.StartsWith("<");
|
||
}
|
||
}
|
||
} |