mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
feat(generator): 添加枚举扩展方法生成器及相关属性
- 新增 EnumExtensionsGenerator 源生成器 - 实现 GenerateEnumExtensionsAttribute 特性标注 - 为标记的枚举自动生成 IsXXX 和 IsIn 扩展方法 - 配置项目引用及 Analyzer 打包设置 - 更新解决方案文件包含新增项目 - 调整主项目配置排除生成器相关文件编译
This commit is contained in:
parent
569713b41d
commit
50a71deaa7
@ -0,0 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<PackageId>GeWuYou.GFramework.Generator.Attributes</PackageId>
|
||||
<Version>1.0.0</Version>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@ -0,0 +1,22 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace GFramework.Generator.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// 标注在 enum 上,Source Generator 会为该 enum 生成扩展方法。
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Enum)]
|
||||
public sealed class GenerateEnumExtensionsAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否为每个枚举项生成单独的 IsXXX 方法(默认 true)。
|
||||
/// </summary>
|
||||
public bool GenerateIsMethods { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否生成一个 IsIn(params T[]) 方法以简化多值判断(默认 true)。
|
||||
/// </summary>
|
||||
public bool GenerateIsInMethod { get; set; } = true;
|
||||
}
|
||||
}
|
||||
39
GFramework.Generator/GFramework.Generator.csproj
Normal file
39
GFramework.Generator/GFramework.Generator.csproj
Normal file
@ -0,0 +1,39 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<PackageId>GeWuYou.GFramework.Generator</PackageId>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
|
||||
<!-- 对 generator 项目要启用扩展规则 -->
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
|
||||
<!-- 不把输出当作运行时库 -->
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
|
||||
<!-- 有助于调试生成的代码(可选) -->
|
||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Generator 需要引用 Attributes 项目,但不作为运行时依赖 -->
|
||||
<ProjectReference Include="..\GFramework.Generator.Attributes\GFramework.Generator.Attributes.csproj"
|
||||
PrivateAssets="all"/>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- 将 Generator 和 Attributes DLL 打包为 Analyzer -->
|
||||
<ItemGroup>
|
||||
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true"
|
||||
PackagePath="analyzers/dotnet/cs" Visible="false"/>
|
||||
<None Include="$(OutputPath)\GFramework.Generator.Attributes.dll" Pack="true"
|
||||
PackagePath="analyzers/dotnet/cs" Visible="false"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
125
GFramework.Generator/generator/enums/EnumExtensionsGenerator.cs
Normal file
125
GFramework.Generator/generator/enums/EnumExtensionsGenerator.cs
Normal file
@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace GFramework.Generator.generator.enums
|
||||
{
|
||||
[Generator]
|
||||
public class EnumExtensionsGenerator : IIncrementalGenerator
|
||||
{
|
||||
private const string AttributeFullName = "GFramework.Generator.Attributes.GenerateEnumExtensionsAttribute";
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
// 1. 找到所有 EnumDeclarationSyntax 节点
|
||||
var enumDecls = context.SyntaxProvider
|
||||
.CreateSyntaxProvider(
|
||||
predicate: (s, _) => s is EnumDeclarationSyntax,
|
||||
transform: (ctx, _) =>
|
||||
(EnumDecl: (EnumDeclarationSyntax)ctx.Node, ctx.SemanticModel))
|
||||
.Where(t => t.EnumDecl != null);
|
||||
|
||||
// 2. 解析为 symbol 并过滤带 Attribute 的 enum
|
||||
var enumSymbols = enumDecls
|
||||
.Select((t, _) =>
|
||||
{
|
||||
var model = t.SemanticModel;
|
||||
var enumDecl = t.EnumDecl;
|
||||
var symbol = model.GetDeclaredSymbol(enumDecl) as INamedTypeSymbol;
|
||||
return symbol;
|
||||
})
|
||||
.Where(symbol => symbol != null)
|
||||
.Select((symbol, _) =>
|
||||
{
|
||||
// 判断是否包含我们的 Attribute
|
||||
var hasAttr = symbol.GetAttributes().Any(ad =>
|
||||
ad.AttributeClass?.ToDisplayString() == AttributeFullName ||
|
||||
ad.AttributeClass?.ToDisplayString().EndsWith(".GenerateEnumExtensionsAttribute") == true);
|
||||
return (Symbol: symbol, HasAttr: hasAttr);
|
||||
})
|
||||
.Where(x => x.HasAttr)
|
||||
.Collect();
|
||||
|
||||
// 3. 为每个 enum 生成代码
|
||||
context.RegisterSourceOutput(enumSymbols, (spc, list) =>
|
||||
{
|
||||
foreach (var enumSymbol in list.Select(item => item.Symbol))
|
||||
{
|
||||
try
|
||||
{
|
||||
var src = GenerateForEnum(enumSymbol);
|
||||
var hintName = $"{enumSymbol.Name}.EnumExtensions.g.cs";
|
||||
spc.AddSource(hintName, SourceText.From(src, Encoding.UTF8));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 发生异常时生成一个注释文件(避免完全静默失败)
|
||||
var err = $"// EnumExtensionsGenerator failed for {enumSymbol?.Name}: {ex.Message}";
|
||||
spc.AddSource($"{enumSymbol?.Name}.EnumExtensions.Error.g.cs",
|
||||
SourceText.From(err, Encoding.UTF8));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static string GenerateForEnum(INamedTypeSymbol enumSymbol)
|
||||
{
|
||||
var ns = enumSymbol.ContainingNamespace.IsGlobalNamespace
|
||||
? null
|
||||
: enumSymbol.ContainingNamespace.ToDisplayString();
|
||||
var enumName = enumSymbol.Name;
|
||||
var fullEnumName = enumSymbol.ToDisplayString(); // 包含命名空间
|
||||
var members = enumSymbol.GetMembers().OfType<IFieldSymbol>().Where(f => f.ConstantValue != null).ToArray();
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("// <auto-generated />");
|
||||
sb.AppendLine("using System;");
|
||||
if (!string.IsNullOrEmpty(ns))
|
||||
{
|
||||
sb.AppendLine($"namespace {ns}");
|
||||
sb.AppendLine("{");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine("namespace EnumExtensionsGenerated");
|
||||
sb.AppendLine("{");
|
||||
}
|
||||
|
||||
sb.AppendLine($" public static partial class {enumName}Extensions");
|
||||
sb.AppendLine(" {");
|
||||
|
||||
// 1. 单项 IsX 方法
|
||||
// 替换原第93行开始的 foreach 块
|
||||
var memberChecks = members.Select(m =>
|
||||
{
|
||||
var memberName = m.Name;
|
||||
var safeMethodName = $"Is{memberName}";
|
||||
return $@" /// <summary>Auto-generated: 是否为 {memberName}</summary>
|
||||
public static bool {safeMethodName}(this {fullEnumName} value) => value == {fullEnumName}.{memberName};
|
||||
|
||||
";
|
||||
}).ToArray();
|
||||
|
||||
sb.Append(string.Join("", memberChecks));
|
||||
|
||||
|
||||
// 2. IsIn(params ...) 方法
|
||||
sb.AppendLine($" /// <summary>Auto-generated: 判断是否属于指定集合</summary>");
|
||||
sb.AppendLine(
|
||||
$" public static bool IsIn(this {fullEnumName} value, params {fullEnumName}[] values)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" if (values == null) return false;");
|
||||
sb.AppendLine(" foreach (var v in values) if (value == v) return true;");
|
||||
sb.AppendLine(" return false;");
|
||||
sb.AppendLine(" }");
|
||||
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine("}"); // namespace
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -22,7 +22,29 @@
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<TargetFrameworks>net9.0;net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="README.md" Pack="true" PackagePath="" />
|
||||
<None Remove="GFramework.Generator\**" />
|
||||
<None Remove="GFramework.Generator.Attributes\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="GFramework.Generator\**" />
|
||||
<Compile Remove="GFramework.Generator.Attributes\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="GFramework.Generator\**" />
|
||||
<EmbeddedResource Remove="GFramework.Generator.Attributes\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- 引用 Source Generator -->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="GFramework.Generator\GFramework.Generator.csproj"
|
||||
OutputItemType="Analyzer"
|
||||
ReferenceOutputAssembly="false"/>
|
||||
<ProjectReference Include="GFramework.Generator.Attributes\GFramework.Generator.Attributes.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -2,6 +2,10 @@
|
||||
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}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Generator.Attributes", "GFramework.Generator.Attributes\GFramework.Generator.Attributes.csproj", "{84C5C3C9-5620-4924-BA04-92F813F2B70F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -12,5 +16,13 @@ Global
|
||||
{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using GFramework.framework.events;
|
||||
using GFramework.framework.events;
|
||||
|
||||
namespace GFramework.framework.property;
|
||||
|
||||
@ -8,14 +8,14 @@ namespace GFramework.framework.property;
|
||||
/// </summary>
|
||||
/// <typeparam name="T">属性值的类型</typeparam>
|
||||
/// <param name="defaultValue">属性的默认值</param>
|
||||
public class BindableProperty<T>(T defaultValue = default) : IBindableProperty<T>
|
||||
public class BindableProperty<T>(T defaultValue = default!) : IBindableProperty<T>
|
||||
{
|
||||
protected T MValue = defaultValue;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置属性值比较器,默认使用Equals方法进行比较
|
||||
/// </summary>
|
||||
public static Func<T, T, bool> Comparer { get; set; } = (a, b) => a.Equals(b);
|
||||
public static Func<T, T, bool> Comparer { get; set; } = (a, b) => a!.Equals(b)!;
|
||||
|
||||
/// <summary>
|
||||
/// 设置自定义比较器
|
||||
@ -37,12 +37,12 @@ public class BindableProperty<T>(T defaultValue = default) : IBindableProperty<T
|
||||
set
|
||||
{
|
||||
// 使用 default(T) 替代 null 比较,避免 SonarQube 警告
|
||||
if (EqualityComparer<T>.Default.Equals(value, default) &&
|
||||
EqualityComparer<T>.Default.Equals(MValue, default))
|
||||
if (EqualityComparer<T>.Default.Equals(value, default!) &&
|
||||
EqualityComparer<T>.Default.Equals(MValue, default!))
|
||||
return;
|
||||
|
||||
// 若新值与旧值相等则不执行后续操作
|
||||
if (!EqualityComparer<T>.Default.Equals(value, default) && Comparer(value, MValue))
|
||||
if (!EqualityComparer<T>.Default.Equals(value, default!) && Comparer(value, MValue))
|
||||
return;
|
||||
|
||||
SetValue(value);
|
||||
@ -68,7 +68,7 @@ public class BindableProperty<T>(T defaultValue = default) : IBindableProperty<T
|
||||
/// <param name="newValue">新的属性值</param>
|
||||
public void SetValueWithoutEvent(T newValue) => MValue = newValue;
|
||||
|
||||
private Action<T> _mOnValueChanged = (_) => { };
|
||||
private Action<T>? _mOnValueChanged = null;
|
||||
|
||||
/// <summary>
|
||||
/// 注册属性值变化事件回调
|
||||
@ -106,13 +106,15 @@ public class BindableProperty<T>(T defaultValue = default) : IBindableProperty<T
|
||||
IUnRegister IEasyEvent.Register(Action onEvent)
|
||||
{
|
||||
return Register(Action);
|
||||
void Action(T _) => onEvent();
|
||||
void Action(T _)
|
||||
{
|
||||
onEvent();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回属性值的字符串表示形式
|
||||
/// </summary>
|
||||
/// <returns>属性值的字符串表示</returns>
|
||||
public override string ToString() => Value.ToString();
|
||||
}
|
||||
|
||||
public override string ToString() => Value?.ToString() ?? string.Empty;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user