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>
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
<TargetFrameworks>net9.0;net8.0</TargetFrameworks>
|
<TargetFrameworks>net9.0;net8.0</TargetFrameworks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="README.md" Pack="true" PackagePath="" />
|
<None Include="README.md" Pack="true" PackagePath="" />
|
||||||
|
<None Remove="GFramework.Generator\**" />
|
||||||
|
<None Remove="GFramework.Generator.Attributes\**" />
|
||||||
</ItemGroup>
|
</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>
|
</Project>
|
||||||
@ -2,6 +2,10 @@
|
|||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework ", "GFramework.csproj", "{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework ", "GFramework.csproj", "{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}"
|
||||||
EndProject
|
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
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
using GFramework.framework.events;
|
using GFramework.framework.events;
|
||||||
|
|
||||||
namespace GFramework.framework.property;
|
namespace GFramework.framework.property;
|
||||||
|
|
||||||
@ -8,14 +8,14 @@ namespace GFramework.framework.property;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">属性值的类型</typeparam>
|
/// <typeparam name="T">属性值的类型</typeparam>
|
||||||
/// <param name="defaultValue">属性的默认值</param>
|
/// <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;
|
protected T MValue = defaultValue;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取或设置属性值比较器,默认使用Equals方法进行比较
|
/// 获取或设置属性值比较器,默认使用Equals方法进行比较
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// 设置自定义比较器
|
/// 设置自定义比较器
|
||||||
@ -37,12 +37,12 @@ public class BindableProperty<T>(T defaultValue = default) : IBindableProperty<T
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
// 使用 default(T) 替代 null 比较,避免 SonarQube 警告
|
// 使用 default(T) 替代 null 比较,避免 SonarQube 警告
|
||||||
if (EqualityComparer<T>.Default.Equals(value, default) &&
|
if (EqualityComparer<T>.Default.Equals(value, default!) &&
|
||||||
EqualityComparer<T>.Default.Equals(MValue, default))
|
EqualityComparer<T>.Default.Equals(MValue, default!))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// 若新值与旧值相等则不执行后续操作
|
// 若新值与旧值相等则不执行后续操作
|
||||||
if (!EqualityComparer<T>.Default.Equals(value, default) && Comparer(value, MValue))
|
if (!EqualityComparer<T>.Default.Equals(value, default!) && Comparer(value, MValue))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SetValue(value);
|
SetValue(value);
|
||||||
@ -68,7 +68,7 @@ public class BindableProperty<T>(T defaultValue = default) : IBindableProperty<T
|
|||||||
/// <param name="newValue">新的属性值</param>
|
/// <param name="newValue">新的属性值</param>
|
||||||
public void SetValueWithoutEvent(T newValue) => MValue = newValue;
|
public void SetValueWithoutEvent(T newValue) => MValue = newValue;
|
||||||
|
|
||||||
private Action<T> _mOnValueChanged = (_) => { };
|
private Action<T>? _mOnValueChanged = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 注册属性值变化事件回调
|
/// 注册属性值变化事件回调
|
||||||
@ -106,13 +106,15 @@ public class BindableProperty<T>(T defaultValue = default) : IBindableProperty<T
|
|||||||
IUnRegister IEasyEvent.Register(Action onEvent)
|
IUnRegister IEasyEvent.Register(Action onEvent)
|
||||||
{
|
{
|
||||||
return Register(Action);
|
return Register(Action);
|
||||||
void Action(T _) => onEvent();
|
void Action(T _)
|
||||||
|
{
|
||||||
|
onEvent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 返回属性值的字符串表示形式
|
/// 返回属性值的字符串表示形式
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>属性值的字符串表示</returns>
|
/// <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