Skip to content

Commit 81330e6

Browse files
committed
Refactor source generation and add AddEmbeddedAttributeDefinition
1 parent 0bdcd8d commit 81330e6

File tree

8 files changed

+190
-213
lines changed

8 files changed

+190
-213
lines changed

EndpointHelpers.sln.DotSettings.user

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,11 @@
22
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIUrlHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F9db6ab8b57c6427fb466b41d82b9a49e3b800_003Fe9_003F9062a969_003FIUrlHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
33
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUrlActionContext_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F9db6ab8b57c6427fb466b41d82b9a49e3b800_003Fb4_003Fedd75550_003FUrlActionContext_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
44
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUrlHelperExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fbf04822e50794b9f8c84efb0930c84331d3e00_003Ff8_003F05da825c_003FUrlHelperExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
5-
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=34c17a9d_002Dc80e_002D462d_002Da0f6_002D16c940e2f0c1/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" Name="All tests from &amp;lt;UrlHelperGenerator.Tests&amp;gt; #2" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
6-
&lt;Project Location="/home/gumbarros/RiderProjects/Barros.UrlHelperGenerator/src/Barros.UrlHelperGenerator/UrlHelperGenerator.Tests" Presentation="&amp;lt;UrlHelperGenerator.Tests&amp;gt;" /&gt;
7-
&lt;/SessionState&gt;</s:String>
8-
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=cf08f83a_002D9f7a_002D4be8_002D8c08_002D341d1ac42e25/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" Name="All tests from &amp;lt;UrlHelperGenerator.Tests&amp;gt;" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
9-
&lt;TestAncestor&gt;
10-
&lt;TestId&gt;xUnit::46EACCA7-6725-63F5-9B15-558D3870DD63::net10.0::Barros.UrlHelperGenerator.Tests.UrlHelperGeneratorTests&lt;/TestId&gt;
11-
&lt;/TestAncestor&gt;
12-
&lt;/SessionState&gt;</s:String>
13-
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=d74964df_002D8abf_002D4130_002Db992_002D0130f266c108/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="UrlHelperGeneratorTests" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
14-
&lt;Project Location="/home/gumbarros/RiderProjects/Barros.UrlHelperGenerator/test/UrlHelperGenerator.Tests" Presentation="&amp;lt;UrlHelperGenerator.Tests&amp;gt;" /&gt;
5+
6+
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=cf08f83a_002D9f7a_002D4be8_002D8c08_002D341d1ac42e25/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from &amp;lt;UrlHelperGenerator.Tests&amp;gt;" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
7+
&lt;Project Location="/home/gumbarros/RiderProjects/Barros.UrlHelperGenerator/test/EndpointHelpers.Tests" Presentation="&amp;lt;EndpointHelpers.Tests&amp;gt;" /&gt;
158
&lt;/SessionState&gt;</s:String>
9+
1610
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AImmutableArrayExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fd9f00e70fec1492f94bcec239fe696d5b6c00_003F1f_003Fefd2ec43_003FImmutableArrayExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
1711
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAreaAttribute_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fbf04822e50794b9f8c84efb0930c84331d3e00_003Ffa_003F8b393dda_003FAreaAttribute_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
1812
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AControllerBase_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fbf04822e50794b9f8c84efb0930c84331d3e00_003F4e_003F09322978_003FControllerBase_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Add the NuGet package:
3333

3434
```xml
3535
<ItemGroup>
36-
<PackageReference Include="EndpointHelpers" Version="1.0.2"/>
36+
<PackageReference Include="EndpointHelpers" Version="1.0.3"/>
3737
</ItemGroup>
3838
```
3939

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
using EndpointHelpers;
22

3+
[assembly: GenerateUrlHelper]
4+
[assembly: GenerateLinkGenerator]
5+
[assembly: GenerateRedirectToAction]
6+
7+
//or...
8+
39
[assembly: GenerateEndpointHelpers]

src/EndpointHelpers/EndpointHelpers.csproj

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<RepositoryType>git</RepositoryType>
2424
<PackageReadmeFile>README.md</PackageReadmeFile>
2525

26-
<Version>1.0.2</Version>
26+
<Version>1.0.3</Version>
2727
<PackageVersion>$(Version)</PackageVersion>
2828
<AssemblyVersion>$(Version)</AssemblyVersion>
2929
<FileVersion>$(Version)</FileVersion>
@@ -41,12 +41,11 @@
4141
</ItemGroup>
4242

4343
<ItemGroup>
44-
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
44+
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0">
4545
<PrivateAssets>all</PrivateAssets>
4646
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
4747
</PackageReference>
48-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0"/>
49-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.3.0"/>
48+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
5049
</ItemGroup>
5150

5251
</Project>

src/EndpointHelpers/LinkGeneratorGenerator.cs

Lines changed: 44 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using Microsoft.CodeAnalysis.Text;
2+
13
namespace EndpointHelpers;
24

35
using System.Collections.Generic;
@@ -6,8 +8,6 @@ namespace EndpointHelpers;
68
using System.Text;
79
using Microsoft.CodeAnalysis;
810
using Microsoft.CodeAnalysis.CSharp.Syntax;
9-
using Microsoft.CodeAnalysis.Text;
10-
1111

1212
[Generator]
1313
public sealed class LinkGeneratorGenerator : IIncrementalGenerator
@@ -18,77 +18,46 @@ public sealed class LinkGeneratorGenerator : IIncrementalGenerator
1818
private const string UnifiedGenerateAttributeName = "GenerateEndpointHelpersAttribute";
1919
private const string NonActionAttributeName = "Microsoft.AspNetCore.Mvc.NonActionAttribute";
2020

21-
//lang=cs
22-
private const string AttributeSourceCode = $"""
23-
// <auto-generated/>
24-
25-
namespace {Namespace};
26-
27-
[System.AttributeUsage(
28-
System.AttributeTargets.Method |
29-
System.AttributeTargets.Class |
30-
System.AttributeTargets.Assembly)]
31-
public sealed class {GenerateAttributeName} : System.Attribute;
32-
33-
[System.AttributeUsage(System.AttributeTargets.Method)]
34-
public sealed class {IgnoreAttributeName} : System.Attribute;
35-
""";
36-
3721
public void Initialize(IncrementalGeneratorInitializationContext context)
3822
{
39-
context.RegisterPostInitializationOutput(ctx =>
40-
ctx.AddSource(
41-
"Attributes.g.cs",
42-
SourceText.From(AttributeSourceCode, Encoding.UTF8)));
43-
44-
var provider = context.SyntaxProvider
45-
.CreateSyntaxProvider(
46-
static (s, _) => s is ClassDeclarationSyntax or MethodDeclarationSyntax,
47-
static (ctx, _) => GetTarget(ctx))
23+
var generateTargets = context.SyntaxProvider
24+
.ForAttributeWithMetadataName(
25+
$"{Namespace}.{GenerateAttributeName}",
26+
static (node, _) => node is ClassDeclarationSyntax or MethodDeclarationSyntax,
27+
static (ctx, _) => GetControllerMetadataName(ctx.TargetSymbol))
4828
.Where(static t => t is not null);
4929

30+
var unifiedTargets = context.SyntaxProvider
31+
.ForAttributeWithMetadataName(
32+
$"{Namespace}.{UnifiedGenerateAttributeName}",
33+
static (node, _) => node is ClassDeclarationSyntax or MethodDeclarationSyntax,
34+
static (ctx, _) => GetControllerMetadataName(ctx.TargetSymbol))
35+
.Where(static t => t is not null);
36+
37+
var targets = generateTargets
38+
.Collect()
39+
.Combine(unifiedTargets.Collect())
40+
.Select(static (x, _) => x.Left.AddRange(x.Right));
41+
5042
context.RegisterSourceOutput(
51-
context.CompilationProvider.Combine(provider.Collect()),
43+
context.CompilationProvider.Combine(targets),
5244
static (ctx, t) => Generate(ctx, t.Left, t.Right!));
5345
}
5446

55-
private static MemberDeclarationSyntax? GetTarget(GeneratorSyntaxContext context)
47+
private static string? GetControllerMetadataName(ISymbol symbol)
5648
{
57-
if (context.Node is ClassDeclarationSyntax cls)
58-
{
59-
foreach (var list in cls.AttributeLists)
60-
foreach (var attr in list.Attributes)
61-
{
62-
if (context.SemanticModel.GetSymbolInfo(attr).Symbol is IMethodSymbol symbol &&
63-
(symbol.ContainingType.ToDisplayString() ==
64-
$"{Namespace}.{GenerateAttributeName}" ||
65-
symbol.ContainingType.ToDisplayString() ==
66-
$"{Namespace}.{UnifiedGenerateAttributeName}"))
67-
return cls;
68-
}
69-
}
70-
71-
if (context.Node is MethodDeclarationSyntax method)
49+
return symbol switch
7250
{
73-
foreach (var list in method.AttributeLists)
74-
foreach (var attr in list.Attributes)
75-
{
76-
if (context.SemanticModel.GetSymbolInfo(attr).Symbol is IMethodSymbol symbol &&
77-
(symbol.ContainingType.ToDisplayString() ==
78-
$"{Namespace}.{GenerateAttributeName}" ||
79-
symbol.ContainingType.ToDisplayString() ==
80-
$"{Namespace}.{UnifiedGenerateAttributeName}"))
81-
return method;
82-
}
83-
}
84-
85-
return null;
51+
INamedTypeSymbol type => GetMetadataName(type),
52+
IMethodSymbol method => method.ContainingType is null ? null : GetMetadataName(method.ContainingType),
53+
_ => null
54+
};
8655
}
8756

8857
private static void Generate(
8958
SourceProductionContext context,
9059
Compilation compilation,
91-
ImmutableArray<MemberDeclarationSyntax> members)
60+
ImmutableArray<string?> members)
9261
{
9362
var assemblyHasGenerate = compilation.Assembly.GetAttributes()
9463
.Any(a => a.AttributeClass?.ToDisplayString() ==
@@ -113,18 +82,8 @@ private static void Generate(
11382
return;
11483

11584
controllers = members
116-
.Select(m =>
117-
{
118-
if (m is ClassDeclarationSyntax cls)
119-
return compilation.GetSemanticModel(cls.SyntaxTree)
120-
.GetDeclaredSymbol(cls) as INamedTypeSymbol;
121-
122-
if (m is MethodDeclarationSyntax method)
123-
return (compilation.GetSemanticModel(method.SyntaxTree)
124-
.GetDeclaredSymbol(method) as IMethodSymbol)?.ContainingType;
125-
126-
return null;
127-
})
85+
.Distinct()
86+
.Select(name => compilation.GetTypeByMetadataName(name!))
12887
.Where(s => s is not null)
12988
.Distinct(SymbolEqualityComparer.Default)
13089
.Cast<INamedTypeSymbol>()
@@ -268,4 +227,19 @@ private static IEnumerable<INamedTypeSymbol> GetAllTypes(INamespaceSymbol ns)
268227
foreach (var type in GetAllTypes(nested))
269228
yield return type;
270229
}
230+
231+
private static string GetMetadataName(INamedTypeSymbol type)
232+
{
233+
var name = type.MetadataName;
234+
var current = type.ContainingType;
235+
236+
while (current is not null)
237+
{
238+
name = $"{current.MetadataName}+{name}";
239+
current = current.ContainingType;
240+
}
241+
242+
var ns = type.ContainingNamespace?.ToDisplayString();
243+
return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}";
244+
}
271245
}

src/EndpointHelpers/RedirectToActionGenerator.cs

Lines changed: 42 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -16,76 +16,47 @@ public sealed class RedirectToActionGenerator : IIncrementalGenerator
1616
private const string IgnoreAttributeName = "RedirectToActionIgnoreAttribute";
1717
private const string UnifiedGenerateAttributeName = "GenerateEndpointHelpersAttribute";
1818
private const string NonActionAttributeName = "Microsoft.AspNetCore.Mvc.NonActionAttribute";
19-
20-
private const string AttributeSourceCode = $"""
21-
// <auto-generated/>
22-
23-
namespace {Namespace};
24-
25-
[System.AttributeUsage(
26-
System.AttributeTargets.Method |
27-
System.AttributeTargets.Class |
28-
System.AttributeTargets.Assembly)]
29-
public sealed class {GenerateAttributeName} : System.Attribute;
30-
31-
[System.AttributeUsage(System.AttributeTargets.Method)]
32-
public sealed class {IgnoreAttributeName} : System.Attribute;
33-
""";
3419

3520
public void Initialize(IncrementalGeneratorInitializationContext context)
3621
{
37-
context.RegisterPostInitializationOutput(ctx =>
38-
ctx.AddSource(
39-
"Attributes.g.cs",
40-
SourceText.From(AttributeSourceCode, Encoding.UTF8)));
41-
42-
var provider = context.SyntaxProvider
43-
.CreateSyntaxProvider(
44-
static (s, _) => s is ClassDeclarationSyntax or MethodDeclarationSyntax,
45-
static (ctx, _) => GetTarget(ctx))
22+
var generateTargets = context.SyntaxProvider
23+
.ForAttributeWithMetadataName(
24+
$"{Namespace}.{GenerateAttributeName}",
25+
static (node, _) => node is ClassDeclarationSyntax or MethodDeclarationSyntax,
26+
static (ctx, _) => GetControllerMetadataName(ctx.TargetSymbol))
4627
.Where(static t => t is not null);
4728

29+
var unifiedTargets = context.SyntaxProvider
30+
.ForAttributeWithMetadataName(
31+
$"{Namespace}.{UnifiedGenerateAttributeName}",
32+
static (node, _) => node is ClassDeclarationSyntax or MethodDeclarationSyntax,
33+
static (ctx, _) => GetControllerMetadataName(ctx.TargetSymbol))
34+
.Where(static t => t is not null);
35+
36+
var targets = generateTargets
37+
.Collect()
38+
.Combine(unifiedTargets.Collect())
39+
.Select(static (x, _) => x.Left.AddRange(x.Right));
40+
4841
context.RegisterSourceOutput(
49-
context.CompilationProvider.Combine(provider.Collect()),
42+
context.CompilationProvider.Combine(targets),
5043
static (ctx, t) => Generate(ctx, t.Left, t.Right!));
5144
}
5245

53-
private static MemberDeclarationSyntax? GetTarget(GeneratorSyntaxContext context)
46+
private static string? GetControllerMetadataName(ISymbol symbol)
5447
{
55-
if (context.Node is ClassDeclarationSyntax cls)
48+
return symbol switch
5649
{
57-
foreach (var attr in cls.AttributeLists.SelectMany(list => list.Attributes))
58-
{
59-
if (context.SemanticModel.GetSymbolInfo(attr).Symbol is IMethodSymbol symbol &&
60-
(symbol.ContainingType.ToDisplayString() ==
61-
$"{Namespace}.{GenerateAttributeName}" ||
62-
symbol.ContainingType.ToDisplayString() ==
63-
$"{Namespace}.{UnifiedGenerateAttributeName}"))
64-
return cls;
65-
}
66-
}
67-
68-
if (context.Node is not MethodDeclarationSyntax method)
69-
return null;
70-
{
71-
foreach (var attr in method.AttributeLists.SelectMany(list => list.Attributes))
72-
{
73-
if (context.SemanticModel.GetSymbolInfo(attr).Symbol is IMethodSymbol symbol &&
74-
(symbol.ContainingType.ToDisplayString() ==
75-
$"{Namespace}.{GenerateAttributeName}" ||
76-
symbol.ContainingType.ToDisplayString() ==
77-
$"{Namespace}.{UnifiedGenerateAttributeName}"))
78-
return method;
79-
}
80-
}
81-
82-
return null;
50+
INamedTypeSymbol type => GetMetadataName(type),
51+
IMethodSymbol method => method.ContainingType is null ? null : GetMetadataName(method.ContainingType),
52+
_ => null
53+
};
8354
}
8455

8556
private static void Generate(
8657
SourceProductionContext context,
8758
Compilation compilation,
88-
ImmutableArray<MemberDeclarationSyntax> members)
59+
ImmutableArray<string?> members)
8960
{
9061
var assemblyHasGenerate = compilation.Assembly.GetAttributes()
9162
.Any(a => a.AttributeClass?.ToDisplayString() ==
@@ -110,18 +81,8 @@ private static void Generate(
11081
return;
11182

11283
controllers = members
113-
.Select(m =>
114-
{
115-
if (m is ClassDeclarationSyntax cls)
116-
return compilation.GetSemanticModel(cls.SyntaxTree)
117-
.GetDeclaredSymbol(cls) as INamedTypeSymbol;
118-
119-
if (m is MethodDeclarationSyntax method)
120-
return (compilation.GetSemanticModel(method.SyntaxTree)
121-
.GetDeclaredSymbol(method) as IMethodSymbol)?.ContainingType;
122-
123-
return null;
124-
})
84+
.Distinct()
85+
.Select(name => compilation.GetTypeByMetadataName(name!))
12586
.Where(s => s is not null)
12687
.Distinct(SymbolEqualityComparer.Default)
12788
.Cast<INamedTypeSymbol>()
@@ -234,4 +195,19 @@ private static IEnumerable<INamedTypeSymbol> GetAllTypes(INamespaceSymbol ns)
234195
foreach (var type in GetAllTypes(nested))
235196
yield return type;
236197
}
198+
199+
private static string GetMetadataName(INamedTypeSymbol type)
200+
{
201+
var name = type.MetadataName;
202+
var current = type.ContainingType;
203+
204+
while (current is not null)
205+
{
206+
name = $"{current.MetadataName}+{name}";
207+
current = current.ContainingType;
208+
}
209+
210+
var ns = type.ContainingNamespace?.ToDisplayString();
211+
return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}";
212+
}
237213
}

0 commit comments

Comments
 (0)