mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
refactor(GFramework.Generator): 重构代码生成器结构并添加Godot日志生成器
- 移除原有的LoggerGenerator.cs和相关README文档 - 重命名GFramework.Generator为GFramework.SourceGenerators - 重命名GFramework.Generator.Attributes为GFramework.SourceGenerators.Attributes - 添加新的Godot日志生成器(GodotLoggerGenerator)及对应属性(GodotLogAttribute) - 创建GFramework.Godot.SourceGenerators新项目用于Godot特定功能 - 修改日志生成器使用GodotLoggerFactory而非ConsoleLoggerFactory
This commit is contained in:
parent
7fa2a1e4cb
commit
763b460575
@ -1,4 +0,0 @@
|
||||
项目额外的简单代码生成器
|
||||
目前已有的功能
|
||||
|
||||
- 为枚举添加两个扩展方法方便判断枚举值
|
||||
@ -1,185 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace GFramework.Generator.generator.logging
|
||||
{
|
||||
/// <summary>
|
||||
/// 日志生成器,用于为标记了LogAttribute的类自动生成日志字段
|
||||
/// </summary>
|
||||
[Generator]
|
||||
public sealed class LoggerGenerator : IIncrementalGenerator
|
||||
{
|
||||
// 请确保这里的命名空间和类名与 Attributes 项目中定义的完全一致(注意大小写!)
|
||||
private const string AttributeMetadataName = "GFramework.Generator.Attributes.generator.logging.LogAttribute";
|
||||
private const string AttributeShortName = "LogAttribute";
|
||||
private const string AttributeShortNameWithoutSuffix = "Log";
|
||||
|
||||
/// <summary>
|
||||
/// 初始化生成器,设置语法过滤和代码生成逻辑
|
||||
/// </summary>
|
||||
/// <param name="context">增量生成器初始化上下文</param>
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
// 1. 语法过滤:快速筛选候选类
|
||||
var targets = context.SyntaxProvider.CreateSyntaxProvider(
|
||||
static (node, _) =>
|
||||
{
|
||||
if (node is not ClassDeclarationSyntax cls) return false;
|
||||
// 只要包含 Log 字眼的 Attribute 就先放行
|
||||
return cls.AttributeLists.SelectMany(a => a.Attributes).Any(a =>
|
||||
{
|
||||
var name = a.Name.ToString();
|
||||
// 简单的字符串匹配,防止错过别名情况
|
||||
return name.Contains(AttributeShortNameWithoutSuffix);
|
||||
});
|
||||
},
|
||||
static (ctx, _) =>
|
||||
{
|
||||
var classDecl = (ClassDeclarationSyntax)ctx.Node;
|
||||
var symbol = ctx.SemanticModel.GetDeclaredSymbol(classDecl);
|
||||
return (ClassDecl: classDecl, Symbol: symbol);
|
||||
})
|
||||
.Where(x => x.Symbol is not null);
|
||||
|
||||
// 2. 生成代码
|
||||
context.RegisterSourceOutput(targets, (spc, pair) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var classDecl = pair.ClassDecl;
|
||||
var classSymbol = pair.Symbol!;
|
||||
|
||||
// 再次确认是否真的含有目标 Attribute (语义检查)
|
||||
var attr = GetAttribute(classSymbol);
|
||||
if (attr == null) return; // 可能是名字相似但不是我们要的 Attribute
|
||||
|
||||
// 检查 partial
|
||||
if (!classDecl.Modifiers.Any(SyntaxKind.PartialKeyword))
|
||||
{
|
||||
spc.ReportDiagnostic(Diagnostic.Create(
|
||||
Diagnostics.MustBePartial,
|
||||
classDecl.Identifier.GetLocation(),
|
||||
classSymbol.Name));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var source = Generate(classSymbol, attr);
|
||||
var hintName = $"{classSymbol.Name}.Logger.g.cs";
|
||||
spc.AddSource(hintName, SourceText.From(source, Encoding.UTF8));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// === 关键修复:生成错误报告文件 ===
|
||||
var errorSource = $"// source generator error: {ex.Message}\n// StackTrace:\n// {ex.StackTrace}";
|
||||
// 替换非法字符以防文件名报错
|
||||
var safeName = pair.Symbol?.Name ?? "Unknown";
|
||||
spc.AddSource($"{safeName}.Logger.Error.g.cs", SourceText.From(errorSource, Encoding.UTF8));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取类符号上的LogAttribute特性
|
||||
/// </summary>
|
||||
/// <param name="classSymbol">类符号</param>
|
||||
/// <returns>LogAttribute特性数据,如果不存在则返回null</returns>
|
||||
private static AttributeData? GetAttribute(INamedTypeSymbol classSymbol)
|
||||
{
|
||||
return classSymbol.GetAttributes().FirstOrDefault(a =>
|
||||
{
|
||||
var cls = a.AttributeClass;
|
||||
if (cls == null) return false;
|
||||
|
||||
// 宽松匹配:全名匹配 OR 名字匹配
|
||||
return cls.ToDisplayString() == AttributeMetadataName ||
|
||||
cls.Name == AttributeShortName;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成日志字段代码
|
||||
/// </summary>
|
||||
/// <param name="classSymbol">类符号</param>
|
||||
/// <param name="attr">LogAttribute特性数据</param>
|
||||
/// <returns>生成的C#代码字符串</returns>
|
||||
private static string Generate(INamedTypeSymbol classSymbol, AttributeData attr)
|
||||
{
|
||||
var ns = classSymbol.ContainingNamespace.IsGlobalNamespace
|
||||
? null
|
||||
: classSymbol.ContainingNamespace.ToDisplayString();
|
||||
|
||||
var className = classSymbol.Name;
|
||||
|
||||
// === 解析 Name ===
|
||||
var name = className; // 默认使用类名
|
||||
|
||||
// 检查是否有构造函数参数
|
||||
if (attr.ConstructorArguments.Length > 0)
|
||||
{
|
||||
var argValue = attr.ConstructorArguments[0].Value;
|
||||
|
||||
name = argValue switch
|
||||
{
|
||||
// 情况 1: 参数存在,但值为 null (例如 [Log] 且构造函数有默认值 null)
|
||||
null => className,
|
||||
// 情况 2: 参数存在,且是有效的字符串 (例如 [Log("MyCategory")])
|
||||
string s when !string.IsNullOrWhiteSpace(s) => s,
|
||||
_ => $"{className}_InvalidArg"
|
||||
};
|
||||
}
|
||||
|
||||
// === 解析 Named Arguments (更加安全的获取方式) ===
|
||||
var fieldName = GetNamedArg(attr, "FieldName")?.ToString() ?? "_log";
|
||||
var access =
|
||||
GetNamedArg(attr, "AccessModifier")?.ToString() ??
|
||||
"private"; // 注意:如果你的 AccessModifier 是枚举,这里得到的可能是 int 或枚举名
|
||||
|
||||
// 处理 bool 类型
|
||||
var isStaticObj = GetNamedArg(attr, "IsStatic");
|
||||
var isStatic = isStaticObj is not bool b || b; // 默认为 true
|
||||
|
||||
var staticKeyword = isStatic ? "static " : "";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("// <auto-generated />");
|
||||
sb.AppendLine("using GFramework.Core.logging;"); // 确保这里引用了 ILog 和 Log 类
|
||||
|
||||
if (ns is not null)
|
||||
{
|
||||
sb.AppendLine($"namespace {ns}");
|
||||
sb.AppendLine("{");
|
||||
}
|
||||
|
||||
sb.AppendLine($" public partial class {className}");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine($" /// <summary>Auto-generated logger</summary>");
|
||||
sb.AppendLine(
|
||||
$" {access} {staticKeyword}readonly ILogger {fieldName} = " +
|
||||
$"new ConsoleLoggerFactory.GetLogger(\"{name}\");");
|
||||
sb.AppendLine(" }");
|
||||
|
||||
if (ns is not null)
|
||||
sb.AppendLine("}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从特性数据中获取命名参数的值
|
||||
/// </summary>
|
||||
/// <param name="attr">特性数据</param>
|
||||
/// <param name="name">参数名称</param>
|
||||
/// <returns>参数值,如果不存在则返回null</returns>
|
||||
private static object? GetNamedArg(AttributeData attr, string name)
|
||||
{
|
||||
return (from kv in attr.NamedArguments where kv.Key == name select kv.Value.Value).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,443 +0,0 @@
|
||||
# GFramework 日志代码生成器 (LogAttribute)
|
||||
|
||||
## 概述
|
||||
|
||||
GFramework 提供了一个强大的日志代码生成器,类似于 Java 的 `@Slf4j` 注解。通过在类上使用 `[Log]` 特性,编译器会自动为该类生成一个日志记录器字段,让您在类的任何地方都能方便地使用日志记录功能。
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 基本使用
|
||||
|
||||
在类上添加 `[Log]` 特性:
|
||||
|
||||
```csharp
|
||||
using GFramework.Generator.Attributes.generator.logging;
|
||||
|
||||
[Log]
|
||||
public partial class MyService
|
||||
{
|
||||
public void DoSomething()
|
||||
{
|
||||
// 自动生成的 Log 字段可以直接使用
|
||||
Log.Info("开始执行操作");
|
||||
|
||||
try
|
||||
{
|
||||
// 业务逻辑
|
||||
Log.Debug("执行业务逻辑", new { Operation = "DoSomething" });
|
||||
Log.Info("操作执行成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("操作执行失败", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
编译后,生成器会自动为该类添加:
|
||||
|
||||
```csharp
|
||||
public partial class MyService
|
||||
{
|
||||
private static ILog Log = Log.CreateLogger("MyService");
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 自定义类别名称
|
||||
|
||||
```csharp
|
||||
[Log("CustomService")]
|
||||
public partial class MyService
|
||||
{
|
||||
// 生成的 logger 类别为 "CustomService" 而不是 "MyService"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 自定义字段配置
|
||||
|
||||
```csharp
|
||||
[Log(FieldName = "Logger", AccessModifier = "protected", IsStatic = false)]
|
||||
public partial class MyService
|
||||
{
|
||||
// 生成: protected ILog Logger = Log.CreateLogger("MyService");
|
||||
}
|
||||
```
|
||||
|
||||
## 特性参数说明
|
||||
|
||||
### LogAttribute 构造函数参数
|
||||
|
||||
- **category** (string, 可选): 指定日志类别,默认为类名
|
||||
|
||||
### 命名参数
|
||||
|
||||
- **FieldName** (string, 默认 "Log"): 指定生成的字段名称
|
||||
- **AccessModifier** (string, 默认 "private"): 指定字段的访问修饰符
|
||||
- **IsStatic** (bool, 默认 true): 指定字段是否为静态的
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 在系统中的使用
|
||||
|
||||
```csharp
|
||||
[Log("System")]
|
||||
public partial class GameSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
Log.Info("GameSystem 初始化开始");
|
||||
|
||||
// 初始化逻辑
|
||||
Log.Debug("正在加载游戏数据...");
|
||||
Log.Info("GameSystem 初始化完成");
|
||||
}
|
||||
|
||||
protected override void OnUpdate(float deltaTime)
|
||||
{
|
||||
Log.Trace("系统更新", new { DeltaTime = deltaTime });
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
Log.Info("GameSystem 销毁");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 在模型中的使用
|
||||
|
||||
```csharp
|
||||
[Log("Model")]
|
||||
public partial class UserModel : AbstractModel
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
|
||||
public void SetUserName(string userName)
|
||||
{
|
||||
Log.Debug("设置用户名", new { OldValue = UserName, NewValue = userName });
|
||||
UserName = userName;
|
||||
Log.Info("用户名设置完成", new { UserName = userName });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 在工具类中的使用
|
||||
|
||||
```csharp
|
||||
[Log("Utility")]
|
||||
public partial class FileUtility
|
||||
{
|
||||
public void SaveFile(string filePath, string content)
|
||||
{
|
||||
Log.Debug("开始保存文件", new { FilePath = filePath });
|
||||
|
||||
try
|
||||
{
|
||||
// 文件保存逻辑
|
||||
Log.Info("文件保存成功", new { FilePath = filePath });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("文件保存失败", ex, new { FilePath = filePath });
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 网络服务中的使用
|
||||
|
||||
```csharp
|
||||
[Log("Network")]
|
||||
public partial class NetworkService
|
||||
{
|
||||
public async Task<string> GetDataAsync(string url)
|
||||
{
|
||||
Log.Debug("发起网络请求", new { Url = url, Method = "GET" });
|
||||
|
||||
try
|
||||
{
|
||||
var response = await httpClient.GetStringAsync(url);
|
||||
Log.Info("网络请求成功", new { Url = url, ResponseLength = response.Length });
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("网络请求失败", ex, new { Url = url });
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 数据库服务中的使用
|
||||
|
||||
```csharp
|
||||
[Log("Database")]
|
||||
public partial class DatabaseService
|
||||
{
|
||||
public async Task<User> GetUserAsync(int userId)
|
||||
{
|
||||
Log.Debug("查询用户信息", new { UserId = userId });
|
||||
|
||||
try
|
||||
{
|
||||
var user = await _dbContext.Users.FindAsync(userId);
|
||||
if (user != null)
|
||||
{
|
||||
Log.Info("用户查询成功", new { UserId = userId, UserName = user.Name });
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warn("用户不存在", new { UserId = userId });
|
||||
}
|
||||
return user;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("用户查询失败", ex, new { UserId = userId });
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 高级配置示例
|
||||
|
||||
### 使用静态字段
|
||||
|
||||
```csharp
|
||||
[Log(IsStatic = true)] // 默认配置
|
||||
public partial class StaticService
|
||||
{
|
||||
public static void StaticMethod()
|
||||
{
|
||||
Log.Info("静态方法调用");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 使用实例字段
|
||||
|
||||
```csharp
|
||||
[Log(IsStatic = false)]
|
||||
public partial class InstanceService
|
||||
{
|
||||
private readonly string _instanceId;
|
||||
|
||||
public InstanceService(string instanceId)
|
||||
{
|
||||
_instanceId = instanceId;
|
||||
}
|
||||
|
||||
public void InstanceMethod()
|
||||
{
|
||||
Log.Info("实例方法调用", new { InstanceId = _instanceId });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 使用受保护的字段
|
||||
|
||||
```csharp
|
||||
[Log(AccessModifier = "protected")]
|
||||
public partial class BaseService
|
||||
{
|
||||
// 子类可以访问 protected 字段
|
||||
}
|
||||
|
||||
[Log] // 派生类也可以有自己的日志记录器
|
||||
public partial class DerivedService : BaseService
|
||||
{
|
||||
public void DerivedMethod()
|
||||
{
|
||||
Log.Info("派生类方法");
|
||||
// 也可以访问基类的 protected Log 字段
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 使用合适的日志类别名称
|
||||
|
||||
```csharp
|
||||
// 好的做法:使用有意义的类别名称
|
||||
[Log("UserService")]
|
||||
public partial class UserService { }
|
||||
|
||||
// 避免:使用过于通用的类别名称
|
||||
[Log("Service")]
|
||||
public partial class UserService { }
|
||||
```
|
||||
|
||||
### 2. 合理设置日志级别
|
||||
|
||||
```csharp
|
||||
[Log("BusinessLogic")]
|
||||
public partial class BusinessService
|
||||
{
|
||||
public void ProcessOrder(Order order)
|
||||
{
|
||||
// 使用 Info 记录重要的业务流程
|
||||
Log.Info("开始处理订单", new { OrderId = order.Id });
|
||||
|
||||
// 使用 Debug 记录调试信息
|
||||
Log.Debug("订单验证通过", new { OrderId = order.Id });
|
||||
|
||||
// 使用 Warning 记录异常情况
|
||||
if (order.Amount > 10000)
|
||||
{
|
||||
Log.Warn("大额订单", new { OrderId = order.Id, Amount = order.Amount });
|
||||
}
|
||||
|
||||
// 使用 Error 记录错误
|
||||
try
|
||||
{
|
||||
// 业务逻辑
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("订单处理失败", ex, new { OrderId = order.Id });
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 记录上下文信息
|
||||
|
||||
```csharp
|
||||
[Log("UserManagement")]
|
||||
public partial class UserManager
|
||||
{
|
||||
public void UpdateUserProfile(int userId, UserProfile profile)
|
||||
{
|
||||
Log.Info("更新用户资料", new
|
||||
{
|
||||
UserId = userId,
|
||||
Changes = profile.GetChanges(),
|
||||
Timestamp = DateTime.Now
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 在异常处理中使用日志
|
||||
|
||||
```csharp
|
||||
[Log("DataAccess")]
|
||||
public partial class UserRepository
|
||||
{
|
||||
public async Task<User> GetUserAsync(int userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _context.Users.FirstOrDefaultAsync(u => u.Id == userId);
|
||||
}
|
||||
catch (DbException ex)
|
||||
{
|
||||
Log.Error("数据库查询失败", ex, new { UserId = userId });
|
||||
throw new DataAccessException("无法获取用户信息", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 框架组件集成
|
||||
|
||||
### 在 GFramework 架构中使用
|
||||
|
||||
```csharp
|
||||
[Log("Architecture")]
|
||||
public partial class ExampleArchitecture : AbstractArchitecture
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
Log.Info("ExampleArchitecture 初始化开始");
|
||||
|
||||
// 注册系统
|
||||
RegisterSystem(new GameSystem());
|
||||
RegisterSystem(new UISystem());
|
||||
|
||||
// 注册模型
|
||||
RegisterModel(new UserModel());
|
||||
RegisterModel(new GameModel());
|
||||
|
||||
// 注册工具
|
||||
RegisterUtility(new FileUtility());
|
||||
|
||||
Log.Info("ExampleArchitecture 初始化完成");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 在事件处理中使用
|
||||
|
||||
```csharp
|
||||
[Log("Event")]
|
||||
public partial class UserEventHandler
|
||||
{
|
||||
[Log("EventHandler")]
|
||||
public void OnUserLoginEvent(UserLoginEvent evt)
|
||||
{
|
||||
Log.Info("用户登录事件", new
|
||||
{
|
||||
UserId = evt.UserId,
|
||||
LoginTime = evt.LoginTime,
|
||||
IpAddress = evt.IpAddress
|
||||
});
|
||||
}
|
||||
|
||||
[Log("EventHandler")]
|
||||
public void OnUserLogoutEvent(UserLogoutEvent evt)
|
||||
{
|
||||
Log.Info("用户登出事件", new
|
||||
{
|
||||
UserId = evt.UserId,
|
||||
LogoutTime = evt.LogoutTime,
|
||||
SessionDuration = evt.SessionDuration
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **部分类**: 使用 `[Log]` 特性的类必须是 `partial` 类,因为生成器会添加字段到同一个类中。
|
||||
|
||||
2. **命名空间引用**: 确保项目中引用了 `GFramework.Generator.Attributes` 包。
|
||||
|
||||
3. **编译时生成**: 日志字段是在编译时生成的,不会影响运行时性能。
|
||||
|
||||
4. **多线程安全**: 生成的 `ILog` 实例是线程安全的,可以在多线程环境中使用。
|
||||
|
||||
5. **继承**: 派生类可以有自己的 `[Log]` 特性,也可以访问基类的受保护日志字段。
|
||||
|
||||
## 性能考虑
|
||||
|
||||
- **编译时生成**: 字段在编译时生成,没有运行时开销
|
||||
- **延迟初始化**: 日志记录器按需创建,避免不必要的对象创建
|
||||
- **级别检查**: 生成的代码包含级别检查,性能与手动创建的日志记录器相同
|
||||
|
||||
## 与现有日志系统的兼容性
|
||||
|
||||
生成的代码完全兼容现有的 `GFramework.Core.logging` 系统:
|
||||
|
||||
```csharp
|
||||
// 现有的方式仍然可以使用
|
||||
var manualLogger = Log.CreateLogger("Manual");
|
||||
|
||||
// 新生成的字段也可以正常使用
|
||||
[Log]
|
||||
public partial class MyService
|
||||
{
|
||||
public void Method()
|
||||
{
|
||||
// 两种方式都可以使用
|
||||
manualLogger.Info("手动创建的日志");
|
||||
Log.Info("自动生成的日志");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
通过使用 `[Log]` 特性,您可以显著减少样板代码,提高开发效率,同时保持日志记录的一致性和灵活性。
|
||||
@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,37 @@
|
||||
#nullable enable
|
||||
namespace GFramework.SourceGenerators.Attributes.logging;
|
||||
|
||||
/// <summary>
|
||||
/// Godot日志特性,用于在类上标记以自动生成日志字段
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public sealed class GodotLogAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化 GodotLogAttribute 类的新实例
|
||||
/// </summary>
|
||||
public GodotLogAttribute()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 GodotLogAttribute 类的新实例
|
||||
/// </summary>
|
||||
/// <param name="name">日志分类名</param>
|
||||
public GodotLogAttribute(string? name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>日志分类名(默认使用类名)</summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>生成字段名</summary>
|
||||
public string FieldName { get; set; } = "_log";
|
||||
|
||||
/// <summary>是否生成 static 字段</summary>
|
||||
public bool IsStatic { get; set; } = true;
|
||||
|
||||
/// <summary>访问修饰符</summary>
|
||||
public string AccessModifier { get; set; } = "private";
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
; Unshipped analyzer release
|
||||
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
|
||||
|
||||
### New Rules
|
||||
|
||||
Rule ID | Category | Severity | Notes
|
||||
------------|--------------------------|----------|------------------------
|
||||
GFLOG001 | GFramework.Godot.Logging | Error | GodotLoggerDiagnostics
|
||||
GFW_LOG001 | GFramework.Godot.Logging | Warning | GodotLoggerDiagnostics
|
||||
@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.14.0"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="AnalyzerReleases.Shipped.md"/>
|
||||
<AdditionalFiles Include="AnalyzerReleases.Unshipped.md"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
44
GFramework.Godot.SourceGenerators/logging/Diagnostic.cs
Normal file
44
GFramework.Godot.SourceGenerators/logging/Diagnostic.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.logging;
|
||||
|
||||
/// <summary>
|
||||
/// 提供诊断描述符的静态类,用于GFramework日志生成器的编译时检查
|
||||
/// </summary>
|
||||
internal static class GodotLoggerDiagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// 诊断描述符:标识使用[GodotLog]特性的类必须声明为partial
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// ID: GFLOG001
|
||||
/// 严重性: Error
|
||||
/// 分类: GFramework.Godot.Logging
|
||||
/// </remarks>
|
||||
public static readonly DiagnosticDescriptor MustBePartial =
|
||||
new(
|
||||
id: "GFLOG001",
|
||||
title: "Class must be partial",
|
||||
messageFormat: "Class '{0}' must be declared as partial to use [GodotLog]",
|
||||
category: "GFramework.Godot.Logging",
|
||||
DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true
|
||||
);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 诊断描述符:标识GodotLogAttribute无法在指定类上生成Logger
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// ID: GFW_LOG001
|
||||
/// 严重性: Warning
|
||||
/// 分类: GFramework.Godot.Logging
|
||||
/// </remarks>
|
||||
public static readonly DiagnosticDescriptor LogAttributeInvalid = new(
|
||||
id: "GFW_LOG001",
|
||||
title: "GodotLogAttribute cannot generate Logger",
|
||||
messageFormat: "GodotLogAttribute on class '{0}' is ineffective: {1}",
|
||||
category: "GFramework.Godot.Logging",
|
||||
DiagnosticSeverity.Warning,
|
||||
isEnabledByDefault: true);
|
||||
}
|
||||
@ -0,0 +1,181 @@
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.logging;
|
||||
|
||||
/// <summary>
|
||||
/// 日志生成器,用于为标记了LogAttribute的类自动生成日志字段
|
||||
/// </summary>
|
||||
[Generator]
|
||||
public sealed class GodotLoggerGenerator : IIncrementalGenerator
|
||||
{
|
||||
private const string AttributeMetadataName = "GFramework.SourceGenerators.Attributes.logging.GodotLogAttribute";
|
||||
private const string AttributeShortName = "GodotLogAttribute";
|
||||
private const string AttributeShortNameWithoutSuffix = "Log";
|
||||
|
||||
/// <summary>
|
||||
/// 初始化生成器,设置语法过滤和代码生成逻辑
|
||||
/// </summary>
|
||||
/// <param name="context">增量生成器初始化上下文</param>
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
// 1. 语法过滤:快速筛选候选类
|
||||
var targets = context.SyntaxProvider.CreateSyntaxProvider(
|
||||
static (node, _) =>
|
||||
{
|
||||
if (node is not ClassDeclarationSyntax cls) return false;
|
||||
// 只要包含 Log 字眼的 Attribute 就先放行
|
||||
return cls.AttributeLists.SelectMany(a => a.Attributes).Any(a =>
|
||||
{
|
||||
var name = a.Name.ToString();
|
||||
// 简单的字符串匹配,防止错过别名情况
|
||||
return name.Contains(AttributeShortNameWithoutSuffix);
|
||||
});
|
||||
},
|
||||
static (ctx, _) =>
|
||||
{
|
||||
var classDecl = (ClassDeclarationSyntax)ctx.Node;
|
||||
var symbol = ctx.SemanticModel.GetDeclaredSymbol(classDecl);
|
||||
return (ClassDecl: classDecl, Symbol: symbol);
|
||||
})
|
||||
.Where(x => x.Symbol is not null);
|
||||
|
||||
// 2. 生成代码
|
||||
context.RegisterSourceOutput(targets, (spc, pair) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var classDecl = pair.ClassDecl;
|
||||
var classSymbol = pair.Symbol!;
|
||||
|
||||
// 再次确认是否真的含有目标 Attribute (语义检查)
|
||||
var attr = GetAttribute(classSymbol);
|
||||
if (attr == null) return; // 可能是名字相似但不是我们要的 Attribute
|
||||
|
||||
// 检查 partial
|
||||
if (!classDecl.Modifiers.Any(SyntaxKind.PartialKeyword))
|
||||
{
|
||||
spc.ReportDiagnostic(Diagnostic.Create(
|
||||
GodotLoggerDiagnostics.MustBePartial,
|
||||
classDecl.Identifier.GetLocation(),
|
||||
classSymbol.Name));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var source = Generate(classSymbol, attr);
|
||||
var hintName = $"{classSymbol.Name}.Logger.g.cs";
|
||||
spc.AddSource(hintName, SourceText.From(source, Encoding.UTF8));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// === 关键修复:生成错误报告文件 ===
|
||||
var errorSource = $"// source generator error: {ex.Message}\n// StackTrace:\n// {ex.StackTrace}";
|
||||
// 替换非法字符以防文件名报错
|
||||
var safeName = pair.Symbol?.Name ?? "Unknown";
|
||||
spc.AddSource($"{safeName}.Logger.Error.g.cs", SourceText.From(errorSource, Encoding.UTF8));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取类符号上的LogAttribute特性
|
||||
/// </summary>
|
||||
/// <param name="classSymbol">类符号</param>
|
||||
/// <returns>LogAttribute特性数据,如果不存在则返回null</returns>
|
||||
private static AttributeData? GetAttribute(INamedTypeSymbol classSymbol)
|
||||
{
|
||||
return classSymbol.GetAttributes().FirstOrDefault(a =>
|
||||
{
|
||||
var cls = a.AttributeClass;
|
||||
if (cls == null) return false;
|
||||
|
||||
// 宽松匹配:全名匹配 OR 名字匹配
|
||||
return cls.ToDisplayString() == AttributeMetadataName ||
|
||||
cls.Name == AttributeShortName;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成日志字段代码
|
||||
/// </summary>
|
||||
/// <param name="classSymbol">类符号</param>
|
||||
/// <param name="attr">LogAttribute特性数据</param>
|
||||
/// <returns>生成的C#代码字符串</returns>
|
||||
private static string Generate(INamedTypeSymbol classSymbol, AttributeData attr)
|
||||
{
|
||||
var ns = classSymbol.ContainingNamespace.IsGlobalNamespace
|
||||
? null
|
||||
: classSymbol.ContainingNamespace.ToDisplayString();
|
||||
|
||||
var className = classSymbol.Name;
|
||||
|
||||
// === 解析 Name ===
|
||||
var name = className; // 默认使用类名
|
||||
|
||||
// 检查是否有构造函数参数
|
||||
if (attr.ConstructorArguments.Length > 0)
|
||||
{
|
||||
var argValue = attr.ConstructorArguments[0].Value;
|
||||
|
||||
name = argValue switch
|
||||
{
|
||||
// 情况 1: 参数存在,但值为 null (例如 [GodotLog] 且构造函数有默认值 null)
|
||||
null => className,
|
||||
// 情况 2: 参数存在,且是有效的字符串 (例如 [GodotLog("MyCategory")])
|
||||
string s when !string.IsNullOrWhiteSpace(s) => s,
|
||||
_ => $"{className}_InvalidArg"
|
||||
};
|
||||
}
|
||||
|
||||
// === 解析 Named Arguments (更加安全的获取方式) ===
|
||||
var fieldName = GetNamedArg(attr, "FieldName")?.ToString() ?? "_log";
|
||||
var access =
|
||||
GetNamedArg(attr, "AccessModifier")?.ToString() ??
|
||||
"private"; // 注意:如果你的 AccessModifier 是枚举,这里得到的可能是 int 或枚举名
|
||||
|
||||
// 处理 bool 类型
|
||||
var isStaticObj = GetNamedArg(attr, "IsStatic");
|
||||
var isStatic = isStaticObj is not bool b || b; // 默认为 true
|
||||
|
||||
var staticKeyword = isStatic ? "static " : "";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("// <auto-generated />");
|
||||
sb.AppendLine("using GFramework.Core.logging;"); // 确保这里引用了 ILogger
|
||||
sb.AppendLine("using GFramework.Godot.logging;"); // 确保这里引用了 GodotLoggerFactory
|
||||
|
||||
if (ns is not null)
|
||||
{
|
||||
sb.AppendLine($"namespace {ns}");
|
||||
sb.AppendLine("{");
|
||||
}
|
||||
|
||||
sb.AppendLine($" public partial class {className}");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" /// <summary>Auto-generated logger</summary>");
|
||||
sb.AppendLine(
|
||||
$" {access} {staticKeyword}readonly ILogger {fieldName} = " +
|
||||
$"new GodotLoggerFactory.GetLogger(\"{name}\");");
|
||||
sb.AppendLine(" }");
|
||||
|
||||
if (ns is not null)
|
||||
sb.AppendLine("}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从特性数据中获取命名参数的值
|
||||
/// </summary>
|
||||
/// <param name="attr">特性数据</param>
|
||||
/// <param name="name">参数名称</param>
|
||||
/// <returns>参数值,如果不存在则返回null</returns>
|
||||
private static object? GetNamedArg(AttributeData attr, string name)
|
||||
{
|
||||
return (from kv in attr.NamedArguments where kv.Key == name select kv.Value.Value).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace GFramework.Generator.Attributes.generator.enums
|
||||
namespace GFramework.SourceGenerators.Attributes.enums
|
||||
{
|
||||
/// <summary>
|
||||
/// 标注在 enum 上,Source Generator 会为该 enum 生成扩展方法。
|
||||
@ -1,7 +1,7 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
|
||||
namespace GFramework.Generator.Attributes.generator.logging;
|
||||
namespace GFramework.SourceGenerators.Attributes.logging;
|
||||
|
||||
/// <summary>
|
||||
/// 标注在类上,Source Generator 会为该类自动生成一个日志记录器字段。
|
||||
@ -9,6 +9,19 @@ namespace GFramework.Generator.Attributes.generator.logging;
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public sealed class LogAttribute : Attribute
|
||||
{
|
||||
public LogAttribute()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 GodotLogAttribute 类的新实例
|
||||
/// </summary>
|
||||
/// <param name="name">日志分类名,默认使用类名</param>
|
||||
public LogAttribute(string? name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>日志分类名(默认使用类名)</summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
@ -20,13 +33,4 @@ public sealed class LogAttribute : Attribute
|
||||
|
||||
/// <summary>访问修饰符</summary>
|
||||
public string AccessModifier { get; set; } = "private";
|
||||
public LogAttribute() { }
|
||||
/// <summary>
|
||||
/// 初始化 LogAttribute 类的新实例
|
||||
/// </summary>
|
||||
/// <param name="name">日志分类名,默认使用类名</param>
|
||||
public LogAttribute(string? name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
3
GFramework.SourceGenerators/AnalyzerReleases.Shipped.md
Normal file
3
GFramework.SourceGenerators/AnalyzerReleases.Shipped.md
Normal file
@ -0,0 +1,3 @@
|
||||
; Shipped analyzer releases
|
||||
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageId>GeWuYou.GFramework.Generator</PackageId>
|
||||
<PackageId>GeWuYou.GFramework.SourceGenerators</PackageId>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<IsRoslynAnalyzer>true</IsRoslynAnalyzer>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
@ -26,7 +26,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Generator 需要引用 Attributes 项目,但不作为运行时依赖 -->
|
||||
<ProjectReference Include="..\GFramework.Generator.Attributes\GFramework.Generator.Attributes.csproj"
|
||||
<ProjectReference Include="..\GFramework.SourceGenerators.Attributes\GFramework.SourceGenerators.Attributes.csproj"
|
||||
PrivateAssets="all"/>
|
||||
</ItemGroup>
|
||||
|
||||
@ -35,12 +35,8 @@
|
||||
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true"
|
||||
PackagePath="analyzers/dotnet/cs" Visible="false"/>
|
||||
<None Include="$(OutputPath)$(AssemblyName).dll" Pack="true" PackagePath="lib/netstandard2.0"/>
|
||||
<None Include="$(OutputPath)\GFramework.Generator.Attributes.dll" Pack="true"
|
||||
<None Include="$(OutputPath)\GFramework.SourceGenerators.Attributes.dll" Pack="true"
|
||||
PackagePath="analyzers/dotnet/cs" Visible="false"/>
|
||||
<None Update="README.md">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath></PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -5,12 +5,13 @@ using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace GFramework.Generator.generator.enums;
|
||||
namespace GFramework.SourceGenerators.enums;
|
||||
|
||||
[Generator]
|
||||
public class EnumExtensionsGenerator : IIncrementalGenerator
|
||||
{
|
||||
private const string AttributeFullName = "GFramework.Generator.Attributes.generator.enums.GenerateEnumExtensionsAttribute";
|
||||
private const string AttributeFullName =
|
||||
"GFramework.SourceGenerators.Attributes.generator.enums.GenerateEnumExtensionsAttribute";
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
@ -1,13 +1,12 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace GFramework.Generator.generator.logging;
|
||||
namespace GFramework.SourceGenerators.logging;
|
||||
|
||||
/// <summary>
|
||||
/// 提供诊断描述符的静态类,用于GFramework日志生成器的编译时检查
|
||||
/// </summary>
|
||||
internal static class Diagnostics
|
||||
internal static class LoggerDiagnostics
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 定义诊断描述符:要求使用[Log]特性的类必须声明为partial
|
||||
/// </summary>
|
||||
@ -23,17 +22,16 @@ internal static class Diagnostics
|
||||
DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true
|
||||
);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 定义诊断描述符:LogAttribute无法生成Logger的错误情况
|
||||
/// </summary>
|
||||
public static readonly DiagnosticDescriptor LogAttributeInvalid =
|
||||
new(
|
||||
id: "GFW_LOG001",
|
||||
title: "LogAttribute 无法生成 Logger",
|
||||
messageFormat: "类 '{0}' 上的 LogAttribute 无法生效:{1}",
|
||||
category: "GFramework.Logging",
|
||||
title: "LogAttribute cannot generate Logger",
|
||||
messageFormat: "LogAttribute on class '{0}' is ineffective: {1}",
|
||||
category: "GFramework.Godot.Logging",
|
||||
DiagnosticSeverity.Warning,
|
||||
isEnabledByDefault: true);
|
||||
|
||||
}
|
||||
}
|
||||
185
GFramework.SourceGenerators/logging/LoggerGenerator.cs
Normal file
185
GFramework.SourceGenerators/logging/LoggerGenerator.cs
Normal file
@ -0,0 +1,185 @@
|
||||
#nullable enable
|
||||
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace GFramework.SourceGenerators.logging;
|
||||
|
||||
/// <summary>
|
||||
/// 日志生成器,用于为标记了LogAttribute的类自动生成日志字段
|
||||
/// </summary>
|
||||
[Generator]
|
||||
public sealed class LoggerGenerator : IIncrementalGenerator
|
||||
{
|
||||
private const string AttributeMetadataName = "GFramework.SourceGenerators.Attributes.logging.GodotLogAttribute";
|
||||
private const string AttributeShortName = "GodotLogAttribute";
|
||||
private const string AttributeShortNameWithoutSuffix = "Log";
|
||||
|
||||
/// <summary>
|
||||
/// 初始化生成器,设置语法过滤和代码生成逻辑
|
||||
/// </summary>
|
||||
/// <param name="context">增量生成器初始化上下文</param>
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
// 1. 语法过滤:快速筛选候选类
|
||||
var targets = context.SyntaxProvider.CreateSyntaxProvider(
|
||||
static (node, _) =>
|
||||
{
|
||||
if (node is not ClassDeclarationSyntax cls) return false;
|
||||
// 只要包含 Log 字眼的 Attribute 就先放行
|
||||
return cls.AttributeLists.SelectMany(a => a.Attributes).Any(a =>
|
||||
{
|
||||
var name = a.Name.ToString();
|
||||
// 简单的字符串匹配,防止错过别名情况
|
||||
return name.Contains(AttributeShortNameWithoutSuffix);
|
||||
});
|
||||
},
|
||||
static (ctx, _) =>
|
||||
{
|
||||
var classDecl = (ClassDeclarationSyntax)ctx.Node;
|
||||
var symbol = ctx.SemanticModel.GetDeclaredSymbol(classDecl);
|
||||
return (ClassDecl: classDecl, Symbol: symbol);
|
||||
})
|
||||
.Where(x => x.Symbol is not null);
|
||||
|
||||
// 2. 生成代码
|
||||
context.RegisterSourceOutput(targets, (spc, pair) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var classDecl = pair.ClassDecl;
|
||||
var classSymbol = pair.Symbol!;
|
||||
|
||||
// 再次确认是否真的含有目标 Attribute (语义检查)
|
||||
var attr = GetAttribute(classSymbol);
|
||||
if (attr == null) return; // 可能是名字相似但不是我们要的 Attribute
|
||||
|
||||
// 检查 partial
|
||||
if (!classDecl.Modifiers.Any(SyntaxKind.PartialKeyword))
|
||||
{
|
||||
spc.ReportDiagnostic(Diagnostic.Create(
|
||||
LoggerDiagnostics.MustBePartial,
|
||||
classDecl.Identifier.GetLocation(),
|
||||
classSymbol.Name));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var source = Generate(classSymbol, attr);
|
||||
var hintName = $"{classSymbol.Name}.Logger.g.cs";
|
||||
spc.AddSource(hintName, SourceText.From(source, Encoding.UTF8));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// === 关键修复:生成错误报告文件 ===
|
||||
var errorSource = $"// source generator error: {ex.Message}\n// StackTrace:\n// {ex.StackTrace}";
|
||||
// 替换非法字符以防文件名报错
|
||||
var safeName = pair.Symbol?.Name ?? "Unknown";
|
||||
spc.AddSource($"{safeName}.Logger.Error.g.cs", SourceText.From(errorSource, Encoding.UTF8));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取类符号上的LogAttribute特性
|
||||
/// </summary>
|
||||
/// <param name="classSymbol">类符号</param>
|
||||
/// <returns>LogAttribute特性数据,如果不存在则返回null</returns>
|
||||
private static AttributeData? GetAttribute(INamedTypeSymbol classSymbol)
|
||||
{
|
||||
return classSymbol.GetAttributes().FirstOrDefault(a =>
|
||||
{
|
||||
var cls = a.AttributeClass;
|
||||
if (cls == null) return false;
|
||||
|
||||
// 宽松匹配:全名匹配 OR 名字匹配
|
||||
return cls.ToDisplayString() == AttributeMetadataName ||
|
||||
cls.Name == AttributeShortName;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成日志字段代码
|
||||
/// </summary>
|
||||
/// <param name="classSymbol">类符号</param>
|
||||
/// <param name="attr">LogAttribute特性数据</param>
|
||||
/// <returns>生成的C#代码字符串</returns>
|
||||
private static string Generate(INamedTypeSymbol classSymbol, AttributeData attr)
|
||||
{
|
||||
var ns = classSymbol.ContainingNamespace.IsGlobalNamespace
|
||||
? null
|
||||
: classSymbol.ContainingNamespace.ToDisplayString();
|
||||
|
||||
var className = classSymbol.Name;
|
||||
|
||||
// === 解析 Name ===
|
||||
var name = className; // 默认使用类名
|
||||
|
||||
// 检查是否有构造函数参数
|
||||
if (attr.ConstructorArguments.Length > 0)
|
||||
{
|
||||
var argValue = attr.ConstructorArguments[0].Value;
|
||||
|
||||
name = argValue switch
|
||||
{
|
||||
// 情况 1: 参数存在,但值为 null (例如 [Log] 且构造函数有默认值 null)
|
||||
null => className,
|
||||
// 情况 2: 参数存在,且是有效的字符串 (例如 [Log("MyCategory")])
|
||||
string s when !string.IsNullOrWhiteSpace(s) => s,
|
||||
_ => $"{className}_InvalidArg"
|
||||
};
|
||||
}
|
||||
|
||||
// === 解析 Named Arguments (更加安全的获取方式) ===
|
||||
var fieldName = GetNamedArg(attr, "FieldName")?.ToString() ?? "_log";
|
||||
var access =
|
||||
GetNamedArg(attr, "AccessModifier")?.ToString() ??
|
||||
"private"; // 注意:如果你的 AccessModifier 是枚举,这里得到的可能是 int 或枚举名
|
||||
|
||||
// 处理 bool 类型
|
||||
var isStaticObj = GetNamedArg(attr, "IsStatic");
|
||||
var isStatic = isStaticObj is not bool b || b; // 默认为 true
|
||||
|
||||
var staticKeyword = isStatic ? "static " : "";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("// <auto-generated />");
|
||||
sb.AppendLine("using GFramework.Core.logging;"); // 确保这里引用了 ILog 和 Log 类
|
||||
|
||||
if (ns is not null)
|
||||
{
|
||||
sb.AppendLine($"namespace {ns}");
|
||||
sb.AppendLine("{");
|
||||
}
|
||||
|
||||
sb.AppendLine($" public partial class {className}");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine($" /// <summary>Auto-generated logger</summary>");
|
||||
sb.AppendLine(
|
||||
$" {access} {staticKeyword}readonly ILogger {fieldName} = " +
|
||||
$"new ConsoleLoggerFactory.GetLogger(\"{name}\");");
|
||||
sb.AppendLine(" }");
|
||||
|
||||
if (ns is not null)
|
||||
sb.AppendLine("}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从特性数据中获取命名参数的值
|
||||
/// </summary>
|
||||
/// <param name="attr">特性数据</param>
|
||||
/// <param name="name">参数名称</param>
|
||||
/// <returns>参数值,如果不存在则返回null</returns>
|
||||
private static object? GetNamedArg(AttributeData attr, string name)
|
||||
{
|
||||
return (from kv in attr.NamedArguments where kv.Key == name select kv.Value.Value).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
@ -23,9 +23,27 @@
|
||||
<None Include="README.md" Pack="true" PackagePath="" />
|
||||
<None Remove="GFramework.Core\**" />
|
||||
<None Remove="GFramework.Game\**" />
|
||||
<None Remove="GFramework.Generator\**" />
|
||||
<None Remove="GFramework.Generator.Attributes\**" />
|
||||
<None Remove="GFramework.Godot\**" />
|
||||
<None Update="GFramework.SourceGenerators\bin\Debug\netstandard2.0\GFramework.Generator.Attributes.dll">
|
||||
<Link>GFramework.SorceGenerators\bin\Debug\netstandard2.0\GFramework.Generator.Attributes.dll</Link>
|
||||
</None>
|
||||
<None Update="GFramework.SourceGenerators\logging\README.md">
|
||||
<Link>GFramework.SorceGenerators\logging\README.md</Link>
|
||||
</None>
|
||||
<None Update="GFramework.SourceGenerators\README.md">
|
||||
<Link>GFramework.SorceGenerators\README.md</Link>
|
||||
</None>
|
||||
<None Update="GFramework.SourceGenerators\AnalyzerReleases.Shipped.md">
|
||||
<Link>GFramework.SorceGenerators\AnalyzerReleases.Shipped.md</Link>
|
||||
</None>
|
||||
<None Update="GFramework.SourceGenerators\AnalyzerReleases.Unshipped.md">
|
||||
<Link>GFramework.SorceGenerators\AnalyzerReleases.Unshipped.md</Link>
|
||||
</None>
|
||||
<None Remove="GFramework.Godot.SourceGenerators\**"/>
|
||||
<None Remove="GFramework.Godot.SourceGenerators.Attributes\**"/>
|
||||
<None Remove="GFramework.SorceGenerators\**"/>
|
||||
<None Remove="GFramework.SourceGenerators\**"/>
|
||||
<None Remove="GFramework.SourceGenerators.Attributes\**"/>
|
||||
</ItemGroup>
|
||||
<!-- 聚合核心模块 -->
|
||||
<ItemGroup>
|
||||
@ -35,15 +53,30 @@
|
||||
<ItemGroup>
|
||||
<Compile Remove="GFramework.Core\**" />
|
||||
<Compile Remove="GFramework.Game\**" />
|
||||
<Compile Remove="GFramework.Generator\**" />
|
||||
<Compile Remove="GFramework.Generator.Attributes\**" />
|
||||
<Compile Remove="GFramework.Godot\**" />
|
||||
<Compile Update="GFramework.SourceGenerators\enums\EnumExtensionsGenerator.cs">
|
||||
<Link>GFramework.SorceGenerators\enums\EnumExtensionsGenerator.cs</Link>
|
||||
</Compile>
|
||||
<Compile Update="GFramework.SourceGenerators\logging\Diagnostic.cs">
|
||||
<Link>GFramework.SorceGenerators\logging\Diagnostic.cs</Link>
|
||||
</Compile>
|
||||
<Compile Update="GFramework.SourceGenerators\logging\LoggerGenerator.cs">
|
||||
<Link>GFramework.SorceGenerators\logging\LoggerGenerator.cs</Link>
|
||||
</Compile>
|
||||
<Compile Remove="GFramework.Godot.SourceGenerators\**"/>
|
||||
<Compile Remove="GFramework.Godot.SourceGenerators.Attributes\**"/>
|
||||
<Compile Remove="GFramework.SorceGenerators\**"/>
|
||||
<Compile Remove="GFramework.SourceGenerators\**"/>
|
||||
<Compile Remove="GFramework.SourceGenerators.Attributes\**"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="GFramework.Core\**" />
|
||||
<EmbeddedResource Remove="GFramework.Game\**" />
|
||||
<EmbeddedResource Remove="GFramework.Generator\**" />
|
||||
<EmbeddedResource Remove="GFramework.Generator.Attributes\**" />
|
||||
<EmbeddedResource Remove="GFramework.Godot\**" />
|
||||
<EmbeddedResource Remove="GFramework.Godot.SourceGenerators\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Godot.SourceGenerators.Attributes\**"/>
|
||||
<EmbeddedResource Remove="GFramework.SorceGenerators\**"/>
|
||||
<EmbeddedResource Remove="GFramework.SourceGenerators\**"/>
|
||||
<EmbeddedResource Remove="GFramework.SourceGenerators.Attributes\**"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework ", "GFramework.csproj", "{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Generator", "GFramework.Generator\GFramework.Generator.csproj", "{E9D51809-0351-4B83-B85B-B5F469AAB3B8}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.SourceGenerators", "GFramework.SourceGenerators\GFramework.SourceGenerators.csproj", "{E9D51809-0351-4B83-B85B-B5F469AAB3B8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Generator.Attributes", "GFramework.Generator.Attributes\GFramework.Generator.Attributes.csproj", "{84C5C3C9-5620-4924-BA04-92F813F2B70F}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.SourceGenerators.Attributes", "GFramework.SourceGenerators.Attributes\GFramework.SourceGenerators.Attributes.csproj", "{84C5C3C9-5620-4924-BA04-92F813F2B70F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Core", "GFramework.Core\GFramework.Core.csproj", "{A6D5854D-79EA-487A-9ED9-396E6A1F8031}"
|
||||
EndProject
|
||||
@ -12,6 +12,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Godot", "GFramew
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Game", "GFramework.Game\GFramework.Game.csproj", "{0B00816B-E8B2-4562-8C11-0C06CE761638}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Godot.SourceGenerators", "GFramework.Godot.SourceGenerators\GFramework.Godot.SourceGenerators.csproj", "{C56FD287-CBC6-4C44-B3DF-103FA3660CA0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Godot.SourceGenerators.Attributes", "GFramework.Godot.SourceGenerators.Attributes\GFramework.Godot.SourceGenerators.Attributes.csproj", "{3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -42,5 +46,13 @@ Global
|
||||
{0B00816B-E8B2-4562-8C11-0C06CE761638}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0B00816B-E8B2-4562-8C11-0C06CE761638}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0B00816B-E8B2-4562-8C11-0C06CE761638}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user