Skip to content

Commit bbe5c03

Browse files
committed
refactor(generator): overhaul pipeline again..
* replace the legacy generator core with the new model/parser/formatter pipeline and special-type aliases * emit fully-qualified matcher usage identifiers and updated matcher-usage fixtures * add ref project build tests and align ref project config/property visibility for the project-referenced source generator
1 parent 0c2b93a commit bbe5c03

35 files changed

Lines changed: 2248 additions & 2108 deletions

ref/Tenekon.MethodOverloads.AcceptanceCriterias/.editorconfig

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// This is part of the acceptance criterias testing.
2+
// Add here diagnostic that are errors and not suppressable.
3+
dotnet_diagnostic.MOG007.severity = warning
4+
dotnet_diagnostic.MOG009.severity = warning
5+
dotnet_diagnostic.MOG010.severity = warning

ref/Tenekon.MethodOverloads.AcceptanceCriterias/MatcherUsage/Class_1.cs renamed to ref/Tenekon.MethodOverloads.AcceptanceCriterias/MatcherUsage/Class_MatcherUsage_1.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ namespace Tenekon.MethodOverloads.AcceptanceCriterias.MatcherUsage;
66
/// <summary>
77
/// Matcher matches target, but overload generation is skipped due to defaults inside window.
88
/// </summary>
9-
internal interface Class_1_Matcher
9+
internal interface Class_MatcherUsage_1_Matcher
1010
{
1111
[GenerateOverloads(nameof(optional))]
1212
void Match(int required, int optional);
1313
}
1414

15-
[GenerateMethodOverloads(Matchers = [typeof(Class_1_Matcher)])]
16-
public class Class_33
15+
[GenerateMethodOverloads(Matchers = [typeof(Class_MatcherUsage_1_Matcher)])]
16+
public class Class_MatcherUsage_1
1717
{
1818
[SuppressMessage("MethodOverloadsGenerator", "MOG003")]
1919
public void Case_1(int required, int optional = 42) { }
Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3-
<PropertyGroup>
4-
<TargetFramework>net10.0</TargetFramework>
5-
<ImplicitUsings>enable</ImplicitUsings>
6-
<Nullable>enable</Nullable>
7-
<IsPackable>false</IsPackable>
8-
<TenekonMethodOverloadsSourceGeneratorAttributesOnly>true</TenekonMethodOverloadsSourceGeneratorAttributesOnly>
9-
</PropertyGroup>
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<IsPackable>false</IsPackable>
8+
</PropertyGroup>
109

11-
<ItemGroup>
12-
<ProjectReference Include="..\..\src\Tenekon.MethodOverloads.SourceGenerator\Tenekon.MethodOverloads.SourceGenerator.csproj"
13-
OutputItemType="Analyzer"
14-
ReferenceOutputAssembly="false" />
15-
</ItemGroup>
10+
<PropertyGroup>
11+
<TenekonMethodOverloadsSourceGeneratorAttributesOnly Condition="'$(TenekonMethodOverloadsSourceGeneratorAttributesOnly)' == ''">true</TenekonMethodOverloadsSourceGeneratorAttributesOnly>
12+
</PropertyGroup>
13+
14+
<Import Project="..\..\buildTransitive\Tenekon.MethodOverloads.SourceGenerator.props" />
15+
16+
<ItemGroup>
17+
<ProjectReference Include="..\..\src\Tenekon.MethodOverloads.SourceGenerator\Tenekon.MethodOverloads.SourceGenerator.csproj"
18+
OutputItemType="Analyzer"
19+
ReferenceOutputAssembly="false"/>
20+
<None Include=".globalconfig" />
21+
</ItemGroup>
1622

1723
</Project>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace Tenekon.MethodOverloads.SourceGenerator;
2+
3+
internal static class AttributeNames
4+
{
5+
public const string GenerateOverloadsAttribute = "Tenekon.MethodOverloads.SourceGenerator.GenerateOverloadsAttribute";
6+
public const string GenerateMethodOverloadsAttribute = "Tenekon.MethodOverloads.SourceGenerator.GenerateMethodOverloadsAttribute";
7+
public const string OverloadGenerationOptionsAttribute = "Tenekon.MethodOverloads.SourceGenerator.OverloadGenerationOptionsAttribute";
8+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System.Diagnostics;
2+
3+
namespace Tenekon.MethodOverloads.SourceGenerator;
4+
5+
internal static class DebugGuard
6+
{
7+
private const string LaunchDebuggerOnStartEnvVar = "TENEKON_MOG_LAUNCH_DEBUGGER_ON_START";
8+
private const string LaunchDebuggerOnExceptionEnvVar = "TENEKON_MOG_LAUNCH_DEBUGGER_ON_EXCEPTION";
9+
10+
public static void MaybeLaunchDebuggerOnStartup()
11+
{
12+
if (GetBooleanEnvironmentVariable(LaunchDebuggerOnStartEnvVar))
13+
{
14+
Debugger.Launch();
15+
}
16+
}
17+
18+
public static void Invoke<T1, T2>(this Action<T1, T2> action, T1 arg1, T2 arg2)
19+
{
20+
try
21+
{
22+
action(arg1, arg2);
23+
}
24+
catch
25+
{
26+
MaybeLaunchDebuggerOnException();
27+
throw;
28+
}
29+
}
30+
31+
public static TResult Invoke<T1, T2, T3, TResult>(Func<T1, T2, T3, TResult> func, T1 arg1, T2 arg2, T3 arg3)
32+
{
33+
try
34+
{
35+
return func(arg1, arg2, arg3);
36+
}
37+
catch
38+
{
39+
MaybeLaunchDebuggerOnException();
40+
throw;
41+
}
42+
}
43+
44+
private static void MaybeLaunchDebuggerOnException()
45+
{
46+
if (GetBooleanEnvironmentVariable(LaunchDebuggerOnExceptionEnvVar))
47+
{
48+
Debugger.Launch();
49+
}
50+
}
51+
52+
private static bool GetBooleanEnvironmentVariable(string envVarName)
53+
{
54+
#pragma warning disable RS1035 // Do not use APIs banned for analyzers
55+
if (Environment.GetEnvironmentVariable(envVarName) is not string value)
56+
#pragma warning restore RS1035 // Do not use APIs banned for analyzers
57+
{
58+
return false;
59+
}
60+
61+
return int.TryParse(value, out int intResult) ? intResult is not 0 :
62+
bool.TryParse(value, out bool boolResult) && boolResult;
63+
}
64+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System.Collections.Immutable;
2+
3+
namespace Tenekon.MethodOverloads.SourceGenerator.Helpers;
4+
5+
internal readonly record struct EquatableArray<T>(ImmutableArray<T> Items)
6+
{
7+
public static EquatableArray<T> Empty => new(ImmutableArray<T>.Empty);
8+
9+
public bool Equals(EquatableArray<T> other)
10+
{
11+
var left = Items;
12+
var right = other.Items;
13+
if (left.Length != right.Length)
14+
{
15+
return false;
16+
}
17+
18+
for (var i = 0; i < left.Length; i++)
19+
{
20+
if (!EqualityComparer<T>.Default.Equals(left[i], right[i]))
21+
{
22+
return false;
23+
}
24+
}
25+
26+
return true;
27+
}
28+
29+
public override int GetHashCode()
30+
{
31+
var hash = 17;
32+
foreach (var item in Items)
33+
{
34+
hash = (hash * 31) + (item is null ? 0 : item.GetHashCode());
35+
}
36+
37+
return hash;
38+
}
39+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Microsoft.CodeAnalysis;
2+
3+
namespace Tenekon.MethodOverloads.SourceGenerator.Helpers;
4+
5+
internal static class IncrementalValuesExtensions
6+
{
7+
public static IncrementalValuesProvider<TResult> GroupBy<TSource, TKey, TResult>(
8+
this IncrementalValuesProvider<TSource> source,
9+
Func<TSource, TKey> keySelector,
10+
Func<TKey, IEnumerable<TSource>, TResult> resultSelector,
11+
IEqualityComparer<TKey>? keyComparer = null)
12+
{
13+
keyComparer ??= EqualityComparer<TKey>.Default;
14+
return source.Collect().SelectMany((values, _) => values.GroupBy(keySelector, resultSelector, keyComparer));
15+
}
16+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using Microsoft.CodeAnalysis;
2+
3+
namespace Tenekon.MethodOverloads.SourceGenerator.Helpers;
4+
5+
internal static class RoslynHelpers
6+
{
7+
public static readonly SymbolDisplayFormat TypeDisplayFormat =
8+
SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions(
9+
SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier |
10+
SymbolDisplayMiscellaneousOptions.UseSpecialTypes);
11+
12+
public static readonly SymbolDisplayFormat SignatureDisplayFormat = SymbolDisplayFormat.FullyQualifiedFormat;
13+
14+
public static AttributeData? GetAttribute(ISymbol symbol, string attributeName)
15+
{
16+
foreach (var attribute in symbol.GetAttributes())
17+
{
18+
var name = attribute.AttributeClass?.Name;
19+
if (name is null)
20+
{
21+
continue;
22+
}
23+
24+
if (string.Equals(name, attributeName, StringComparison.Ordinal) ||
25+
(attributeName.EndsWith("Attribute", StringComparison.Ordinal) &&
26+
string.Equals(name, attributeName.Substring(0, attributeName.Length - "Attribute".Length), StringComparison.Ordinal)))
27+
{
28+
return attribute;
29+
}
30+
}
31+
32+
return null;
33+
}
34+
35+
public static bool IsAttributeNameMatch(string name, string expected)
36+
{
37+
if (string.Equals(name, expected, StringComparison.Ordinal) ||
38+
string.Equals(name, expected + "Attribute", StringComparison.Ordinal))
39+
{
40+
return true;
41+
}
42+
43+
return name.EndsWith("." + expected, StringComparison.Ordinal) ||
44+
name.EndsWith("." + expected + "Attribute", StringComparison.Ordinal);
45+
}
46+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp;
3+
using Microsoft.CodeAnalysis.Text;
4+
5+
namespace Tenekon.MethodOverloads.SourceGenerator.Helpers;
6+
7+
internal readonly record struct SourceFileModel(string Path, string Text, LanguageVersion LanguageVersion)
8+
{
9+
public SyntaxTree CreateSyntaxTree()
10+
{
11+
var options = new CSharpParseOptions(LanguageVersion);
12+
return CSharpSyntaxTree.ParseText(Text, options, Path);
13+
}
14+
}
15+
16+
internal readonly record struct SourceLocationModel(
17+
string Path,
18+
int SpanStart,
19+
int SpanLength,
20+
int StartLine,
21+
int StartCharacter,
22+
int EndLine,
23+
int EndCharacter)
24+
{
25+
public Location ToLocation()
26+
{
27+
var span = new TextSpan(SpanStart, SpanLength);
28+
if (string.IsNullOrEmpty(Path))
29+
{
30+
return Location.None;
31+
}
32+
33+
var lineSpan = new LinePositionSpan(
34+
new LinePosition(StartLine, StartCharacter),
35+
new LinePosition(EndLine, EndCharacter));
36+
return Location.Create(Path, span, lineSpan);
37+
}
38+
39+
public static SourceLocationModel FromSyntaxNode(SyntaxNode node)
40+
{
41+
return FromLocation(node.GetLocation());
42+
}
43+
44+
public static SourceLocationModel FromSyntaxToken(SyntaxToken token)
45+
{
46+
return FromLocation(token.GetLocation());
47+
}
48+
49+
private static SourceLocationModel FromLocation(Location location)
50+
{
51+
var lineSpan = location.GetLineSpan();
52+
return new SourceLocationModel(
53+
location.SourceTree?.FilePath ?? string.Empty,
54+
location.SourceSpan.Start,
55+
location.SourceSpan.Length,
56+
lineSpan.StartLinePosition.Line,
57+
lineSpan.StartLinePosition.Character,
58+
lineSpan.EndLinePosition.Line,
59+
lineSpan.EndLinePosition.Character);
60+
}
61+
}

0 commit comments

Comments
 (0)