Skip to content

Commit cb2ae33

Browse files
committed
重构(attribute): 重构RegionInject属性用法
重构RegionInject属性为属性初始化器语法,移除构造函数,提升代码可读性与灵活性。 更新README.md,增加属性初始化器用法示例,建议最佳实践。 优化CodeInjectIncrementalSourceGenerator逻辑,优先解析命名参数,保留旧版兼容性。 简化RegionInjectAttribute设计,移除构造函数,改用无参构造与属性初始化器。 更新Program.cs,替换构造函数调用为属性初始化器。
1 parent afc9ca5 commit cb2ae33

File tree

4 files changed

+111
-135
lines changed

4 files changed

+111
-135
lines changed

README.md

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,10 @@ public async Task<{ReturnType}> Create{EntityName}Async({ReturnType} entity)
6161
### 2. Apply the attribute
6262

6363
```csharp
64-
using CodeRegionSourceGenerator;
64+
using CodeInject;
6565

66-
[RegionInject("Templates/ApiTemplate.cs", "ApiMethods",
67-
"ReturnType", "User",
68-
"EntityName", "User")]
66+
[RegionInject(FilePath = "Templates/ApiTemplate.cs", RegionName = "ApiMethods",
67+
Placeholders = new[] { "ReturnType", "User", "EntityName", "User" })]
6968
public partial class UserService
7069
{
7170
private readonly IRepository _repository;
@@ -105,19 +104,34 @@ partial class UserService
105104
### Multiple Injections
106105

107106
```csharp
108-
[RegionInject("Templates/CrudTemplate.cs", "CreateMethods", "Entity", "Product")]
109-
[RegionInject("Templates/CrudTemplate.cs", "UpdateMethods", "Entity", "Product")]
110-
[RegionInject("Templates/ValidationTemplate.cs", "Validators", "Type", "Product")]
107+
[RegionInject(FilePath = "Templates/CrudTemplate.cs", RegionName = "CreateMethods",
108+
Placeholders = new[] { "Entity", "Product" })]
109+
[RegionInject(FilePath = "Templates/CrudTemplate.cs", RegionName = "UpdateMethods",
110+
Placeholders = new[] { "Entity", "Product" })]
111+
[RegionInject(FilePath = "Templates/ValidationTemplate.cs", RegionName = "Validators",
112+
Placeholders = new[] { "Type", "Product" })]
111113
public partial class ProductService
112114
{
113115
// Multiple code regions will be injected
114116
}
115117
```
116118

117-
### Using Placeholders Property
119+
### Search All Files for Region
120+
121+
If you don't specify the `FilePath`, the generator will search all available files for the specified region:
118122

119123
```csharp
120-
[RegionInject("Templates/ApiTemplate.cs", "ApiMethods",
124+
[RegionInject(RegionName = "CommonMethods")]
125+
public partial class BaseService
126+
{
127+
// Generator will search all files for "CommonMethods" region
128+
}
129+
```
130+
131+
### Using Property Initializers
132+
133+
```csharp
134+
[RegionInject(FilePath = "Templates/ApiTemplate.cs", RegionName = "ApiMethods",
121135
Placeholders = new[] { "ReturnType", "Order", "EntityName", "Order" })]
122136
public partial class OrderService
123137
{
@@ -176,6 +190,16 @@ public async Task<ActionResult<{EntityType}>> Create{EntityName}({EntityType} {e
176190
#endregion
177191
```
178192

193+
Usage:
194+
```csharp
195+
[RegionInject(FilePath = "Templates/ControllerTemplate.cs", RegionName = "CrudActions",
196+
Placeholders = new[] { "EntityType", "Product", "EntityName", "Product", "entityName", "product" })]
197+
public partial class ProductController : ControllerBase
198+
{
199+
// Generated CRUD actions will be injected here
200+
}
201+
```
202+
179203
### 2. Repository Pattern Templates
180204

181205
```csharp
@@ -200,6 +224,16 @@ public async Task<{EntityType}> Create{EntityName}Async({EntityType} entity)
200224
#endregion
201225
```
202226

227+
Usage:
228+
```csharp
229+
[RegionInject(FilePath = "Templates/RepositoryTemplate.cs", RegionName = "RepositoryMethods",
230+
Placeholders = new[] { "EntityType", "User", "EntityName", "User" })]
231+
public partial class UserRepository
232+
{
233+
// Generated repository methods will be injected here
234+
}
235+
```
236+
203237
## 🔍 Diagnostics
204238

205239
The source generator provides the following diagnostic information:
@@ -212,8 +246,9 @@ The source generator provides the following diagnostic information:
212246

213247
1. **Organize templates**: Keep template files in a dedicated `Templates` folder
214248
2. **Naming conventions**: Use descriptive region names like `CrudMethods`, `ValidationRules`
215-
3. **Placeholder naming**: Use uppercase placeholder names like `ENTITY_NAME`, `RETURN_TYPE`
249+
3. **Placeholder naming**: Use consistent placeholder names like `EntityType`, `EntityName`
216250
4. **Modularization**: Group related functionality into different regions
251+
5. **Property-based syntax**: Use the new property-based initialization for better readability
217252

218253
## 📋 Requirements
219254

demo/CodeRegionExamplesConsoleApp/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515

1616
namespace CodeRegionExamplesConsoleApp;
1717

18-
[RegionInject("./CodeRegionTemplateConsoleApp/Program.cs", "Show")]
19-
[RegionInject("./CodeRegionTemplateConsoleApp/Program.cs", "Show1","Show123","Show222")]
18+
[RegionInject(RegionName = "Show")]
19+
[RegionInject(RegionName = "Show1", Placeholders = new[] { "Show123", "Show222" })]
2020
internal partial class ExampleProgram
2121
{
2222
static void Main(string[] args)

src/CodeInjectSourceGenerator/CodeInjectIncrementalSourceGenerator.cs

Lines changed: 60 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,39 @@ private static ClassToGenerate GetSemanticTargetForGeneration(GeneratorSyntaxCon
151151

152152
string filePath = null;
153153
string regionName = null;
154+
var placeholders = new List<string>();
155+
156+
// 首先从命名参数(属性)获取值
157+
foreach (var namedArgument in attributeData.NamedArguments)
158+
{
159+
switch (namedArgument.Key)
160+
{
161+
case "FilePath":
162+
filePath = namedArgument.Value.Value?.ToString();
163+
break;
164+
case "RegionName":
165+
regionName = namedArgument.Value.Value?.ToString();
166+
break;
167+
case "Placeholders":
168+
var value = namedArgument.Value;
169+
if (value.Kind == TypedConstantKind.Array && !value.IsNull)
170+
{
171+
var arrayValues = value.Values;
172+
foreach (var item in arrayValues)
173+
{
174+
var stringValue = item.Value?.ToString();
175+
if (!string.IsNullOrEmpty(stringValue))
176+
{
177+
placeholders.Add(stringValue);
178+
}
179+
}
180+
}
181+
break;
182+
}
183+
}
154184

155-
// 处理不同的构造函数重载
156-
if (attributeData.ConstructorArguments.Length >= 1)
185+
// 如果没有从属性获取到值,尝试从构造函数参数获取(向后兼容)
186+
if (string.IsNullOrEmpty(regionName) && attributeData.ConstructorArguments.Length >= 1)
157187
{
158188
var firstArg = attributeData.ConstructorArguments[0].Value?.ToString();
159189

@@ -169,62 +199,34 @@ private static ClassToGenerate GetSemanticTargetForGeneration(GeneratorSyntaxCon
169199
filePath = firstArg;
170200
regionName = attributeData.ConstructorArguments[1].Value?.ToString();
171201
}
172-
}
173202

174-
if (string.IsNullOrEmpty(regionName))
175-
{
176-
continue;
177-
}
178-
179-
var placeholders = new List<string>();
180-
181-
// 从构造函数参数获取占位符
182-
if (attributeData.ConstructorArguments.Length >= 3)
183-
{
184-
// 第3个参数可能是 params string[] placeholders
185-
var placeholdersArg = attributeData.ConstructorArguments[2];
186-
if (placeholdersArg.Kind == TypedConstantKind.Array && !placeholdersArg.IsNull)
187-
{
188-
var arrayValues = placeholdersArg.Values;
189-
foreach (var item in arrayValues)
190-
{
191-
var stringValue = item.Value?.ToString();
192-
if (!string.IsNullOrEmpty(stringValue))
193-
{
194-
placeholders.Add(stringValue);
195-
}
196-
}
197-
}
198-
}
199-
else if (attributeData.ConstructorArguments.Length == 2 && string.IsNullOrEmpty(filePath))
200-
{
201-
// 可能是 RegionInjectAttribute(regionName, params string[] placeholders)
202-
var secondArg = attributeData.ConstructorArguments[1];
203-
if (secondArg.Kind == TypedConstantKind.Array && !secondArg.IsNull)
203+
// 从构造函数参数获取占位符(如果属性中没有设置)
204+
if (placeholders.Count == 0)
204205
{
205-
var arrayValues = secondArg.Values;
206-
foreach (var item in arrayValues)
206+
if (attributeData.ConstructorArguments.Length >= 3)
207207
{
208-
var stringValue = item.Value?.ToString();
209-
if (!string.IsNullOrEmpty(stringValue))
208+
// 第3个参数可能是 params string[] placeholders
209+
var placeholdersArg = attributeData.ConstructorArguments[2];
210+
if (placeholdersArg.Kind == TypedConstantKind.Array && !placeholdersArg.IsNull)
210211
{
211-
placeholders.Add(stringValue);
212+
var arrayValues = placeholdersArg.Values;
213+
foreach (var item in arrayValues)
214+
{
215+
var stringValue = item.Value?.ToString();
216+
if (!string.IsNullOrEmpty(stringValue))
217+
{
218+
placeholders.Add(stringValue);
219+
}
220+
}
212221
}
213222
}
214-
}
215-
}
216-
217-
// 如果构造函数中没有占位符,再从 Placeholders 属性获取
218-
if (placeholders.Count == 0)
219-
{
220-
foreach (var namedArgument in attributeData.NamedArguments)
221-
{
222-
if (namedArgument.Key == "Placeholders")
223+
else if (attributeData.ConstructorArguments.Length == 2 && string.IsNullOrEmpty(filePath))
223224
{
224-
var value = namedArgument.Value;
225-
if (value.Kind == TypedConstantKind.Array && !value.IsNull)
225+
// 可能是 RegionInjectAttribute(regionName, params string[] placeholders)
226+
var secondArg = attributeData.ConstructorArguments[1];
227+
if (secondArg.Kind == TypedConstantKind.Array && !secondArg.IsNull)
226228
{
227-
var arrayValues = value.Values;
229+
var arrayValues = secondArg.Values;
228230
foreach (var item in arrayValues)
229231
{
230232
var stringValue = item.Value?.ToString();
@@ -238,6 +240,11 @@ private static ClassToGenerate GetSemanticTargetForGeneration(GeneratorSyntaxCon
238240
}
239241
}
240242

243+
if (string.IsNullOrEmpty(regionName))
244+
{
245+
continue;
246+
}
247+
241248
// 获取属性的位置信息
242249
var attributeLocation = attributeData.ApplicationSyntaxReference?.GetSyntax().GetLocation() ?? Location.None;
243250

@@ -701,32 +708,12 @@ namespace CodeInject
701708
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
702709
public class RegionInjectAttribute : Attribute
703710
{
704-
public string FilePath { get; }
705-
public string RegionName { get; }
711+
public string FilePath { get; set; }
712+
public string RegionName { get; set; }
706713
public string[] Placeholders { get; set; } = new string[0];
707714
708-
public RegionInjectAttribute(string regionName)
709-
{
710-
FilePath = null;
711-
RegionName = regionName ?? throw new ArgumentNullException(nameof(regionName));
712-
}
713-
714-
public RegionInjectAttribute(string regionName, params string[] placeholders)
715-
: this(regionName)
716-
{
717-
Placeholders = placeholders ?? new string[0];
718-
}
719-
720-
public RegionInjectAttribute(string filePath, string regionName)
721-
{
722-
FilePath = filePath ?? throw new ArgumentNullException(nameof(filePath));
723-
RegionName = regionName ?? throw new ArgumentNullException(nameof(regionName));
724-
}
725-
726-
public RegionInjectAttribute(string filePath, string regionName, params string[] placeholders)
727-
: this(filePath, regionName)
715+
public RegionInjectAttribute()
728716
{
729-
Placeholders = placeholders ?? new string[0];
730717
}
731718
}
732719
}";

src/CodeInjectSourceGenerator/RegionInjectAttribute.cs

Lines changed: 4 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -21,64 +21,18 @@ namespace CodeInject
2121
public sealed class RegionInjectAttribute : Attribute
2222
{
2323
/// <summary>
24-
/// 获取要注入的文件路径。如果为null或空字符串,则搜索所有可用文件。
24+
/// 获取或设置要注入的文件路径。如果为null或空字符串,则搜索所有可用文件。
2525
/// </summary>
26-
public string FilePath { get; }
26+
public string FilePath { get; set; }
2727

2828
/// <summary>
29-
/// 获取要注入的区域名称
29+
/// 获取或设置要注入的区域名称
3030
/// </summary>
31-
public string RegionName { get; }
31+
public string RegionName { get; set; }
3232

3333
/// <summary>
3434
/// 获取或设置用于替换的占位符数组。
3535
/// </summary>
3636
public string[] Placeholders { get; set; } = new string[0];
37-
38-
/// <summary>
39-
/// 初始化 <see cref="RegionInjectAttribute"/> 类的新实例,仅指定区域名称。
40-
/// 当不指定文件路径时,生成器将搜索所有AdditionalFiles和编译文件。
41-
/// </summary>
42-
/// <param name="regionName">要注入的区域名称。</param>
43-
public RegionInjectAttribute(string regionName)
44-
{
45-
this.FilePath = null;
46-
this.RegionName = regionName ?? throw new ArgumentNullException(nameof(regionName));
47-
}
48-
49-
/// <summary>
50-
/// 初始化 <see cref="RegionInjectAttribute"/> 类的新实例,指定区域名称和占位符。
51-
/// 当不指定文件路径时,生成器将搜索所有AdditionalFiles和编译文件。
52-
/// </summary>
53-
/// <param name="regionName">要注入的区域名称。</param>
54-
/// <param name="placeholders">用于替换的占位符数组。</param>
55-
public RegionInjectAttribute(string regionName, params string[] placeholders)
56-
: this(regionName)
57-
{
58-
this.Placeholders = placeholders ?? new string[0];
59-
}
60-
61-
/// <summary>
62-
/// 初始化 <see cref="RegionInjectAttribute"/> 类的新实例。
63-
/// </summary>
64-
/// <param name="filePath">要注入的文件路径。</param>
65-
/// <param name="regionName">要注入的区域名称。</param>
66-
public RegionInjectAttribute(string filePath, string regionName)
67-
{
68-
this.FilePath = filePath ?? throw new ArgumentNullException(nameof(filePath));
69-
this.RegionName = regionName ?? throw new ArgumentNullException(nameof(regionName));
70-
}
71-
72-
/// <summary>
73-
/// 初始化 <see cref="RegionInjectAttribute"/> 类的新实例,并指定占位符。
74-
/// </summary>
75-
/// <param name="filePath">要注入的文件路径。</param>
76-
/// <param name="regionName">要注入的区域名称。</param>
77-
/// <param name="placeholders">用于替换的占位符数组。</param>
78-
public RegionInjectAttribute(string filePath, string regionName, params string[] placeholders)
79-
: this(filePath, regionName)
80-
{
81-
this.Placeholders = placeholders ?? new string[0];
82-
}
8337
}
8438
}

0 commit comments

Comments
 (0)