Skip to content

Commit

Permalink
Resolve #77 Add support for structs and records
Browse files Browse the repository at this point in the history
  • Loading branch information
virzak committed May 21, 2024
1 parent da5ca52 commit 75bfa04
Show file tree
Hide file tree
Showing 16 changed files with 164 additions and 35 deletions.
4 changes: 2 additions & 2 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
"isRoot": true,
"tools": {
"dotnet-format": {
"version": "9.0.515801",
"version": "9.0.520307",
"commands": [
"dotnet-format"
]
},
"dotnet-reportgenerator-globaltool": {
"version": "5.2.3",
"version": "5.3.0",
"commands": [
"reportgenerator"
]
Expand Down
10 changes: 5 additions & 5 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@
<PackageVersion Include="Fody" Version="6.6.4" />
<PackageVersion Include="IsExternalInit" Version="1.0.3" />
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="1.1.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0-preview.24165.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0-preview.24216.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="N.SourceGenerators.UnionTypes" Version="0.27.0" />
<PackageVersion Include="N.SourceGenerators.UnionTypes" Version="0.28.0" />
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.6.133" />
<PackageVersion Include="PolySharp" Version="1.14.1" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
<PackageVersion Include="System.Runtime.Experimental" Version="6.0.2" />
<PackageVersion Include="Verify.SourceGenerators" Version="2.2.0" />
<PackageVersion Include="Verify.XUnit" Version="24.1.0" />
<PackageVersion Include="xunit" Version="2.7.1" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.8" />
<PackageVersion Include="Verify.XUnit" Version="24.2.0" />
<PackageVersion Include="xunit" Version="2.8.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.0" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ This [.NET source generator](https://learn.microsoft.com/en-us/dotnet/csharp/ros

### CreateSyncVersionAttribute

Decorate your async method with `CreateSyncVersionAttribute` in your `partial` class
Decorate your async method with `CreateSyncVersionAttribute` in your `partial` class, struct or record

```cs
[Zomp.SyncMethodGenerator.CreateSyncVersion]
Expand Down
9 changes: 0 additions & 9 deletions src/Zomp.SyncMethodGenerator/ClassDeclaration.cs

This file was deleted.

8 changes: 8 additions & 0 deletions src/Zomp.SyncMethodGenerator/MethodParent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Zomp.SyncMethodGenerator;

internal enum MethodParent
{
Class,
Struct,
Record,
}
11 changes: 11 additions & 0 deletions src/Zomp.SyncMethodGenerator/MethodParentDeclaration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Zomp.SyncMethodGenerator;

/// <summary>
/// Represents a class a <see cref="MethodToGenerate"/> belongs to.
/// </summary>
/// <param name="MethodParent">Type of container.</param>
/// <param name="ClassOrStructKeyword">Indicates whether struct or class are explicitly specified for a record.</param>
/// <param name="ParentName">Class name.</param>
/// <param name="Modifiers">A list of modifiers.</param>
/// <param name="TypeParameterListSyntax">A list of type parameters.</param>
internal sealed record MethodParentDeclaration(MethodParent MethodParent, SyntaxToken ClassOrStructKeyword, string ParentName, EquatableArray<ushort> Modifiers, EquatableArray<string> TypeParameterListSyntax);
4 changes: 2 additions & 2 deletions src/Zomp.SyncMethodGenerator/MethodToGenerate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
/// <param name="Index">Index of the method in the source file.</param>
/// <param name="Namespaces">List of namespaces this method is under.</param>
/// <param name="IsNamespaceFileScoped">True if namespace is file scoped.</param>
/// <param name="Classes">List of classes this method belongs to starting from the outer-most class.</param>
/// <param name="Parents">List of classes/structs/records this method belongs to starting from the outer-most class.</param>
/// <param name="MethodName">Name of the method.</param>
/// <param name="Implementation">Implementation.</param>
/// <param name="DisableNullable">Disables nullable for the method.</param>
Expand All @@ -16,7 +16,7 @@ internal sealed record MethodToGenerate(
int Index,
EquatableArray<string> Namespaces,
bool IsNamespaceFileScoped,
EquatableArray<ClassDeclaration> Classes,
EquatableArray<MethodParentDeclaration> Parents,
string MethodName,
string Implementation,
bool DisableNullable,
Expand Down
22 changes: 18 additions & 4 deletions src/Zomp.SyncMethodGenerator/SourceGenerationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,27 @@ internal static string GenerateExtensionClass(MethodToGenerate methodToGenerate)
}

// Handle classes
foreach (var @class in methodToGenerate.Classes)
foreach (var parent in methodToGenerate.Parents)
{
var indent = new string(' ', 4 * i);

var modifiers = string.Join(string.Empty, @class.Modifiers.Select(z => GetKeyword((SyntaxKind)z) + " "));
var classDeclarationLine = $"{modifiers}partial class {@class.ClassName}{(@class.TypeParameterListSyntax.IsEmpty ? string.Empty
: "<" + string.Join(", ", @class.TypeParameterListSyntax) + ">")}";
var modifiers = string.Join(string.Empty, parent.Modifiers.Select(z => GetKeyword((SyntaxKind)z) + " "));
var parentType = parent.MethodParent switch
{
MethodParent.Class => "class",
MethodParent.Struct => "struct",
MethodParent.Record => "record"
+ parent.ClassOrStructKeyword.Kind() switch
{
SyntaxKind.StructKeyword => " struct",
SyntaxKind.ClassKeyword => " class",
_ => string.Empty,
},
_ => throw new NotImplementedException("Cannot handle the parent of the method"),
};

var classDeclarationLine = $"{modifiers}partial {parentType} {parent.ParentName}{(parent.TypeParameterListSyntax.IsEmpty ? string.Empty
: "<" + string.Join(", ", parent.TypeParameterListSyntax) + ">")}";

_ = sbBegin.Append($$"""
{{indent}}{{classDeclarationLine}}
Expand Down
47 changes: 38 additions & 9 deletions src/Zomp.SyncMethodGenerator/SyncMethodSourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ private static bool IsSyntaxTargetForGeneration(SyntaxNode node)

private static (MethodToGenerate MethodToGenerate, string Path, string Content) GenerateSource(MethodToGenerate m)
{
static string BuildClassName(ClassDeclaration c)
static string BuildClassName(MethodParentDeclaration c)
=> c.TypeParameterListSyntax.IsEmpty
? c.ClassName
: c.ClassName + "{" + string.Join(",", c.TypeParameterListSyntax) + "}";
? c.ParentName
: c.ParentName + "{" + string.Join(",", c.TypeParameterListSyntax) + "}";

var sourcePath = $"{string.Join(".", m.Namespaces)}" +
$".{string.Join(".", m.Classes.Select(BuildClassName))}" +
$".{string.Join(".", m.Parents.Select(BuildClassName))}" +
$".{m.MethodName + (m.Index == 1 ? string.Empty : "_" + m.Index)}.g.cs";

var source = SourceGenerationHelper.GenerateExtensionClass(m);
Expand Down Expand Up @@ -138,19 +138,48 @@ static string BuildClassName(ClassDeclaration c)
var explicitDisableNullable = syncMethodGeneratorAttributeData.NamedArguments.FirstOrDefault(c => c.Key == OmitNullableDirective) is { Value.Value: true };
disableNullable |= explicitDisableNullable;

var classes = ImmutableArray.CreateBuilder<ClassDeclaration>();
var classes = ImmutableArray.CreateBuilder<MethodParentDeclaration>();
SyntaxNode? node = methodDeclarationSyntax;
while (node.Parent is not null)
{
node = node.Parent;
if (node is not ClassDeclarationSyntax classSyntax)
SyntaxTokenList originalModifiers;
SyntaxToken identifier;
SyntaxToken classOrStructKeyword = default;
TypeParameterListSyntax? typeParameterList;
MethodParent methodParent;
if (node is ClassDeclarationSyntax classSyntax)
{
originalModifiers = classSyntax.Modifiers;
typeParameterList = classSyntax.TypeParameterList;
identifier = classSyntax.Identifier;
methodParent = MethodParent.Class;
}
else if (node is StructDeclarationSyntax structSyntax)
{
originalModifiers = structSyntax.Modifiers;
typeParameterList = structSyntax.TypeParameterList;
identifier = structSyntax.Identifier;
methodParent = MethodParent.Struct;
}
else if (node is RecordDeclarationSyntax recordSyntax)
{
originalModifiers = recordSyntax.Modifiers;
typeParameterList = recordSyntax.TypeParameterList;
identifier = recordSyntax.Identifier;
methodParent = MethodParent.Record;
classOrStructKeyword = recordSyntax.ClassOrStructKeyword;
}
else
{
break;
}

////var modifiers = classSyntax?.Modifiers ?? structSyntax.Modifiers;

var modifiers = ImmutableArray.CreateBuilder<ushort>();

foreach (var mod in classSyntax.Modifiers)
foreach (var mod in originalModifiers)
{
var kind = mod.RawKind;
if (kind == (int)SyntaxKind.PartialKeyword)
Expand All @@ -163,12 +192,12 @@ static string BuildClassName(ClassDeclaration c)

var typeParameters = ImmutableArray.CreateBuilder<string>();

foreach (var typeParameter in classSyntax.TypeParameterList?.Parameters ?? default)
foreach (var typeParameter in typeParameterList?.Parameters ?? default)
{
typeParameters.Add(typeParameter.Identifier.ValueText);
}

classes.Insert(0, new(classSyntax.Identifier.ValueText, modifiers.ToImmutable(), typeParameters.ToImmutable()));
classes.Insert(0, new(methodParent, classOrStructKeyword, identifier.ValueText, modifiers.ToImmutable(), typeParameters.ToImmutable()));
}

if (classes.Count == 0)
Expand Down
4 changes: 2 additions & 2 deletions tests/GenerationSandbox.Tests/GenerationSandbox.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

<PropertyGroup>
<!--Suppress warninigs alphabetically and numerically-->
<NoWarn>$(NoWarn);CA1303;CA1515;CA1812;CA1822;CA1852;CA5394</NoWarn>
<NoWarn>$(NoWarn);CA1303;CA1515;CA1812;CA1815;CA1822;CA1852;CA5394</NoWarn>
<NoWarn>$(NoWarn);CS0219;CS0162;CS1998;CS8603;CS8619</NoWarn>
<NoWarn>$(NoWarn);IDE0005;IDE0011;IDE0035;IDE0058;IDE0060;IDE0065</NoWarn>
<NoWarn>$(NoWarn);RS1035</NoWarn>
<NoWarn>$(NoWarn);SA1200;SA1201;SA1400;SA1402;SA1403;SA1404</NoWarn>
<NoWarn>$(NoWarn);SA1200;SA1201;SA1400;SA1402;SA1403;SA1404;SA1601</NoWarn>
<ImplicitUsings>false</ImplicitUsings>
<TargetFrameworks>net7.0;net6.0</TargetFrameworks>
<TargetFrameworks Condition="'$(OS)' == 'Windows_NT'">$(TargetFrameworks);net472</TargetFrameworks>
Expand Down
44 changes: 44 additions & 0 deletions tests/Generator.Tests/ParentTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
namespace Generator.Tests;

public class ParentTests
{
[Fact]
public Task Struct() => """
namespace Test;
public partial struct Struct
{
[CreateSyncVersion]
public readonly async Task MethodAsync() => await Task.Delay(1000);
}
""".Verify(sourceType: SourceType.Full);

[Fact]
public Task Record() => """
namespace Test;
public partial record Record
{
[CreateSyncVersion]
public async Task MethodAsync() => await Task.Delay(1000);
}
""".Verify(sourceType: SourceType.Full);

[Fact]
public Task RecordStruct() => """
namespace Test;
public partial record struct RecordStruct
{
[CreateSyncVersion]
public readonly async Task MethodAsync() => await Task.Delay(1000);
}
""".Verify(sourceType: SourceType.Full);

[Fact]
public Task RecordClass() => """
namespace Test;
public partial record class Record
{
[CreateSyncVersion]
public async Task MethodAsync() => await Task.Delay(1000);
}
""".Verify(sourceType: SourceType.Full);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//HintName: Test.Record.MethodAsync.g.cs
// <auto-generated/>
#nullable enable
namespace Test;
public partial record Record
{
public void Method() => global::System.Threading.Thread.Sleep(1000);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//HintName: Test.Record.MethodAsync.g.cs
// <auto-generated/>
#nullable enable
namespace Test;
public partial record class Record
{
public void Method() => global::System.Threading.Thread.Sleep(1000);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//HintName: Test.RecordStruct.MethodAsync.g.cs
// <auto-generated/>
#nullable enable
namespace Test;
public partial record struct RecordStruct
{
public readonly void Method() => global::System.Threading.Thread.Sleep(1000);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//HintName: Test.Struct.MethodAsync.g.cs
// <auto-generated/>
#nullable enable
namespace Test;
public partial struct Struct
{
public readonly void Method() => global::System.Threading.Thread.Sleep(1000);
}
2 changes: 1 addition & 1 deletion version.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
"version": "1.3",
"version": "1.4",
"assemblyVersion": {
"precision": "revision"
},
Expand Down

0 comments on commit 75bfa04

Please sign in to comment.