Skip to content

Commit 94f7b98

Browse files
committed
新增(CodeInject): 支持嵌入资源和优化代码结构
新增版权声明和嵌入资源处理逻辑,支持从程序集嵌入资源中动态加载 `RegionInjectAttribute` 源代码。 新增 `RegionInjectAttribute` 特性类,支持文件路径、区域名称和占位符注入。 优化代码风格,调整方法访问修饰符,显式使用 `this`,提升代码可读性。 更新项目文件,嵌入 `RegionInjectAttribute.cs` 为资源,版本号更新至 1.0.0。 修复潜在空引用问题,新增 XML 注释,增强代码稳定性和可维护性。
1 parent 7b279f5 commit 94f7b98

File tree

4 files changed

+184
-32
lines changed

4 files changed

+184
-32
lines changed

src/CodeInjectSourceGenerator/CodeInjectIncrementalSourceGenerator.cs

Lines changed: 113 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
1+
// ------------------------------------------------------------------------------
2+
// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
3+
// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
4+
// CSDN博客:https://blog.csdn.net/qq_40374647
5+
// 哔哩哔哩视频:https://space.bilibili.com/94253567
6+
// Gitee源代码仓库:https://gitee.com/RRQM_Home
7+
// Github源代码仓库:https://github.com/RRQM
8+
// API首页:https://touchsocket.net/
9+
// 交流QQ群:234762506
10+
// 感谢您的下载和使用
11+
// ------------------------------------------------------------------------------
12+
113
using Microsoft.CodeAnalysis;
214
using Microsoft.CodeAnalysis.CSharp;
315
using Microsoft.CodeAnalysis.CSharp.Syntax;
416
using Microsoft.CodeAnalysis.Text;
517
using System;
618
using System.Collections.Generic;
719
using System.Collections.Immutable;
8-
using System.Diagnostics;
920
using System.IO;
1021
using System.Linq;
22+
using System.Reflection;
1123
using System.Text;
1224

1325
namespace CodeInject;
@@ -17,16 +29,28 @@ public class CodeInjectIncrementalSourceGenerator : IIncrementalGenerator
1729
{
1830
public void Initialize(IncrementalGeneratorInitializationContext context)
1931
{
32+
// 获取项目中的源文件
33+
var sourceFiles = context.SyntaxProvider
34+
.CreateSyntaxProvider(
35+
predicate: static (s, _) => s is CompilationUnitSyntax,
36+
transform: static (ctx, _) => ctx)
37+
.Where(static ctx => ctx.Node is CompilationUnitSyntax);
38+
2039
// 添加 attribute 源码到消费项目
21-
context.RegisterPostInitializationOutput(ctx => ctx.AddSource(
22-
"RegionInjectAttribute.g.cs",
23-
SourceText.From(AttributeSource, Encoding.UTF8)));
40+
context.RegisterSourceOutput(sourceFiles.Collect(), (ctx, sources) =>
41+
{
42+
var attributeSource = GetAttributeSourceFromEmbeddedResource();
43+
if (!string.IsNullOrEmpty(attributeSource))
44+
{
45+
ctx.AddSource("RegionInjectAttribute.g.cs", SourceText.From(attributeSource, Encoding.UTF8));
46+
}
47+
});
2448

2549
// 获取所有的 AdditionalFiles
26-
IncrementalValuesProvider<AdditionalText> additionalFiles = context.AdditionalTextsProvider;
50+
var additionalFiles = context.AdditionalTextsProvider;
2751

2852
// 查找所有标记了 RegionInjectAttribute 的类
29-
IncrementalValuesProvider<ClassToGenerate> classDeclarations = context.SyntaxProvider
53+
var classDeclarations = context.SyntaxProvider
3054
.CreateSyntaxProvider(
3155
predicate: static (s, _) => IsSyntaxTargetForGeneration(s),
3256
transform: static (ctx, _) => GetSemanticTargetForGeneration(ctx))
@@ -39,10 +63,69 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
3963
static (spc, source) => Execute(source.Left, source.Right, spc));
4064
}
4165

42-
static bool IsSyntaxTargetForGeneration(SyntaxNode node)
66+
/// <summary>
67+
/// 获取程序集中的嵌入资源内容
68+
/// </summary>
69+
/// <param name="resourceName">资源名称,如果为null则返回所有资源名称</param>
70+
/// <returns>资源内容或资源名称列表</returns>
71+
private static string GetEmbeddedResourceContent(string resourceName = null)
72+
{
73+
var assembly = Assembly.GetExecutingAssembly();
74+
var resourceNames = assembly.GetManifestResourceNames();
75+
76+
// 如果没有指定资源名称,返回所有资源名称
77+
if (string.IsNullOrEmpty(resourceName))
78+
{
79+
return string.Join(Environment.NewLine, resourceNames);
80+
}
81+
82+
// 查找匹配的资源
83+
var targetResource = resourceNames.FirstOrDefault(name =>
84+
name.Equals(resourceName, StringComparison.OrdinalIgnoreCase) ||
85+
name.EndsWith($".{resourceName}", StringComparison.OrdinalIgnoreCase));
86+
87+
if (string.IsNullOrEmpty(targetResource))
88+
{
89+
return null;
90+
}
91+
92+
try
93+
{
94+
using (var stream = assembly.GetManifestResourceStream(targetResource))
95+
{
96+
if (stream == null)
97+
return null;
98+
99+
using (var reader = new StreamReader(stream, Encoding.UTF8))
100+
{
101+
return reader.ReadToEnd();
102+
}
103+
}
104+
}
105+
catch
106+
{
107+
return null;
108+
}
109+
}
110+
111+
private static string GetAttributeSourceFromEmbeddedResource()
112+
{
113+
// 尝试从嵌入资源中获取 RegionInjectAttribute 的源代码
114+
var resourceContent = GetEmbeddedResourceContent("RegionInjectAttribute.cs");
115+
116+
// 如果找不到嵌入资源,使用默认的源代码
117+
if (string.IsNullOrEmpty(resourceContent))
118+
{
119+
return DefaultAttributeSource;
120+
}
121+
122+
return resourceContent;
123+
}
124+
125+
private static bool IsSyntaxTargetForGeneration(SyntaxNode node)
43126
=> node is ClassDeclarationSyntax c && c.AttributeLists.Count > 0;
44127

45-
static ClassToGenerate GetSemanticTargetForGeneration(GeneratorSyntaxContext context)
128+
private static ClassToGenerate GetSemanticTargetForGeneration(GeneratorSyntaxContext context)
46129
{
47130
try
48131
{
@@ -75,7 +158,7 @@ static ClassToGenerate GetSemanticTargetForGeneration(GeneratorSyntaxContext con
75158
// 从构造函数参数获取占位符
76159
if (attributeData.ConstructorArguments.Length > 2)
77160
{
78-
for (int i = 2; i < attributeData.ConstructorArguments.Length; i++)
161+
for (var i = 2; i < attributeData.ConstructorArguments.Length; i++)
79162
{
80163
var value = attributeData.ConstructorArguments[i].Value?.ToString();
81164
if (!string.IsNullOrEmpty(value))
@@ -123,7 +206,7 @@ static ClassToGenerate GetSemanticTargetForGeneration(GeneratorSyntaxContext con
123206
}
124207
}
125208

126-
static string GetNamespace(ClassDeclarationSyntax classDeclaration)
209+
private static string GetNamespace(ClassDeclarationSyntax classDeclaration)
127210
{
128211
var namespaceDeclaration = classDeclaration.Ancestors().OfType<NamespaceDeclarationSyntax>().FirstOrDefault();
129212
if (namespaceDeclaration != null)
@@ -136,7 +219,7 @@ static string GetNamespace(ClassDeclarationSyntax classDeclaration)
136219
return string.Empty;
137220
}
138221

139-
static void Execute(ImmutableArray<ClassToGenerate> classes, ImmutableArray<AdditionalText> additionalFiles, SourceProductionContext context)
222+
private static void Execute(ImmutableArray<ClassToGenerate> classes, ImmutableArray<AdditionalText> additionalFiles, SourceProductionContext context)
140223
{
141224
if (classes.IsDefaultOrEmpty)
142225
return;
@@ -151,7 +234,7 @@ static void Execute(ImmutableArray<ClassToGenerate> classes, ImmutableArray<Addi
151234
}
152235
}
153236

154-
static string GenerateClass(ClassToGenerate classInfo, ImmutableArray<AdditionalText> additionalFiles, SourceProductionContext context)
237+
private static string GenerateClass(ClassToGenerate classInfo, ImmutableArray<AdditionalText> additionalFiles, SourceProductionContext context)
155238
{
156239
var sb = new StringBuilder();
157240

@@ -192,7 +275,7 @@ static string GenerateClass(ClassToGenerate classInfo, ImmutableArray<Additional
192275
return sb.ToString();
193276
}
194277

195-
static string FormatInjectedCode(string code, string indentation)
278+
private static string FormatInjectedCode(string code, string indentation)
196279
{
197280
if (string.IsNullOrEmpty(code))
198281
return string.Empty;
@@ -218,26 +301,26 @@ static string FormatInjectedCode(string code, string indentation)
218301
return string.Join("\r\n", formattedLines);
219302
}
220303

221-
static string ExtractAndProcessRegion(RegionInjectData attribute, ImmutableArray<AdditionalText> additionalFiles, SourceProductionContext context)
304+
private static string ExtractAndProcessRegion(RegionInjectData attribute, ImmutableArray<AdditionalText> additionalFiles, SourceProductionContext context)
222305
{
223306
// 首先尝试从 AdditionalFiles 中查找文件
224307
string fileContent = null;
225308
AdditionalText targetFile = null;
226309

227310
// 规范化路径以进行比较
228311
var normalizedTargetPath = attribute.FilePath.Replace('\\', '/');
229-
312+
230313
foreach (var file in additionalFiles)
231314
{
232315
var filePath = file.Path.Replace('\\', '/');
233-
316+
234317
// 检查完整路径匹配
235318
if (filePath.EndsWith(normalizedTargetPath, StringComparison.OrdinalIgnoreCase))
236319
{
237320
targetFile = file;
238321
break;
239322
}
240-
323+
241324
// 检查文件名匹配
242325
var fileName = Path.GetFileName(normalizedTargetPath);
243326
if (Path.GetFileName(filePath).Equals(fileName, StringComparison.OrdinalIgnoreCase))
@@ -301,17 +384,17 @@ static string ExtractAndProcessRegion(RegionInjectData attribute, ImmutableArray
301384
return ProcessPlaceholders(regionContent, attribute.Placeholders);
302385
}
303386

304-
static string ExtractRegion(string fileContent, string regionName)
387+
private static string ExtractRegion(string fileContent, string regionName)
305388
{
306389
var lines = fileContent.Split(new[] { '\r', '\n' }, StringSplitOptions.None);
307390
var regionStart = -1;
308391
var regionEnd = -1;
309392
var nestedLevel = 0;
310393

311-
for (int i = 0; i < lines.Length; i++)
394+
for (var i = 0; i < lines.Length; i++)
312395
{
313396
var line = lines[i].Trim();
314-
397+
315398
if (line.StartsWith("#region"))
316399
{
317400
if (regionStart == -1 && line.Contains(regionName))
@@ -360,15 +443,15 @@ static string ExtractRegion(string fileContent, string regionName)
360443
return string.Join("\r\n", processedLines);
361444
}
362445

363-
static string ProcessPlaceholders(string content, string[] placeholders)
446+
private static string ProcessPlaceholders(string content, string[] placeholders)
364447
{
365448
if (placeholders.Length == 0)
366449
return content;
367450

368451
var result = content;
369452

370453
// 处理成对的占位符 (key, value, key, value, ...)
371-
for (int i = 0; i < placeholders.Length - 1; i += 2)
454+
for (var i = 0; i < placeholders.Length - 1; i += 2)
372455
{
373456
if (i + 1 < placeholders.Length)
374457
{
@@ -386,7 +469,7 @@ static string ProcessPlaceholders(string content, string[] placeholders)
386469
return result;
387470
}
388471

389-
private const string AttributeSource = @"
472+
private const string DefaultAttributeSource = @"
390473
using System;
391474
392475
namespace CodeInject
@@ -420,9 +503,9 @@ internal class ClassToGenerate
420503

421504
public ClassToGenerate(string name, string @namespace, RegionInjectData[] attributes)
422505
{
423-
Name = name;
424-
Namespace = @namespace;
425-
Attributes = attributes;
506+
this.Name = name;
507+
this.Namespace = @namespace;
508+
this.Attributes = attributes;
426509
}
427510
}
428511

@@ -435,10 +518,10 @@ internal class RegionInjectData
435518

436519
public RegionInjectData(string filePath, string regionName, string[] placeholders, Location location)
437520
{
438-
FilePath = filePath;
439-
RegionName = regionName;
440-
Placeholders = placeholders;
441-
Location = location;
521+
this.FilePath = filePath;
522+
this.RegionName = regionName;
523+
this.Placeholders = placeholders;
524+
this.Location = location;
442525
}
443526
}
444527
}

src/CodeInjectSourceGenerator/CodeInjectSourceGenerator.csproj

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFramework>netstandard2.0</TargetFramework>
@@ -10,4 +10,12 @@
1010
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" PrivateAssets="all" />
1111
</ItemGroup>
1212

13+
<ItemGroup>
14+
<ApplicationDefinition Include="RegionInjectAttribute.cs" />
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<EmbeddedResource Include="RegionInjectAttribute.cs" />
19+
</ItemGroup>
20+
1321
</Project>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// ------------------------------------------------------------------------------
2+
// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
3+
// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
4+
// CSDN博客:https://blog.csdn.net/qq_40374647
5+
// 哔哩哔哩视频:https://space.bilibili.com/94253567
6+
// Gitee源代码仓库:https://gitee.com/RRQM_Home
7+
// Github源代码仓库:https://github.com/RRQM
8+
// API首页:https://touchsocket.net/
9+
// 交流QQ群:234762506
10+
// 感谢您的下载和使用
11+
// ------------------------------------------------------------------------------
12+
13+
using System;
14+
15+
namespace CodeInject
16+
{
17+
/// <summary>
18+
/// 用于指定要注入的文件路径、区域名称和占位符的特性。
19+
/// </summary>
20+
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
21+
public sealed class RegionInjectAttribute : Attribute
22+
{
23+
/// <summary>
24+
/// 获取要注入的文件路径。
25+
/// </summary>
26+
public string FilePath { get; }
27+
28+
/// <summary>
29+
/// 获取要注入的区域名称。
30+
/// </summary>
31+
public string RegionName { get; }
32+
33+
/// <summary>
34+
/// 获取或设置用于替换的占位符数组。
35+
/// </summary>
36+
public string[] Placeholders { get; set; } = new string[0];
37+
38+
/// <summary>
39+
/// 初始化 <see cref="RegionInjectAttribute"/> 类的新实例。
40+
/// </summary>
41+
/// <param name="filePath">要注入的文件路径。</param>
42+
/// <param name="regionName">要注入的区域名称。</param>
43+
public RegionInjectAttribute(string filePath, string regionName)
44+
{
45+
this.FilePath = filePath ?? throw new ArgumentNullException(nameof(filePath));
46+
this.RegionName = regionName ?? throw new ArgumentNullException(nameof(regionName));
47+
}
48+
49+
/// <summary>
50+
/// 初始化 <see cref="RegionInjectAttribute"/> 类的新实例,并指定占位符。
51+
/// </summary>
52+
/// <param name="filePath">要注入的文件路径。</param>
53+
/// <param name="regionName">要注入的区域名称。</param>
54+
/// <param name="placeholders">用于替换的占位符数组。</param>
55+
public RegionInjectAttribute(string filePath, string regionName, params string[] placeholders)
56+
: this(filePath, regionName)
57+
{
58+
this.Placeholders = placeholders ?? new string[0];
59+
}
60+
}
61+
}

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>0.0.5</Version>
3+
<Version>1.0.0</Version>
44
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
55
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
66
<LangVersion>preview</LangVersion>

0 commit comments

Comments
 (0)