Skip to content

Commit

Permalink
2.0.22 - Fix component partial generator for generic types
Browse files Browse the repository at this point in the history
  • Loading branch information
adospace committed Jan 23, 2024
1 parent d724730 commit cf57667
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
env:
Solution_Name: ./src/MauiReactor.Build.sln
TemplatePack_Name: ./src/MauiReactor.TemplatePack/MauiReactor.TemplatePack.csproj
Version: 2.0.21
Version: 2.0.22

steps:
- name: Checkout
Expand Down
141 changes: 141 additions & 0 deletions samples/UnitTests/TestScaffoldGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,147 @@ public MyComponent MyProp9(Action<int?> propValue)

}

[Test]
public void TestPropAttributeGeneratorsForGenericClasses()
{
// Create the 'input' compilation that the generator will act on
Compilation inputCompilation = CreateCompilation("""
using MauiReactor;
namespace MyCode;
partial class MyComponent<T>
{
[Prop]
int _myProp;
[Inject]
IService _myService;
}
""");

// directly create an instance of the generator
// (Note: in the compiler this is loaded from an assembly, and created via reflection at runtime)
var generator = new ComponentPartialClassSourceGenerator();

// Create the driver that will control the generation, passing in our generator
GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);

// Run the generation pass
// (Note: the generator driver itself is immutable, and all calls return an updated version of the driver that you should use for subsequent calls)
driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics);

// We can now assert things about the resulting compilation:
Debug.Assert(diagnostics.IsEmpty); // there were no diagnostics created by the generators
Debug.Assert(outputCompilation.SyntaxTrees.Count() == 3);
//Debug.Assert(outputCompilation.GetDiagnostics().IsEmpty); // verify the compilation with the added source has no diagnostics

// Or we can look at the results directly:
GeneratorDriverRunResult runResult = driver.GetRunResult();

// The runResult contains the combined results of all generators passed to the driver
Debug.Assert(runResult.GeneratedTrees.Length == 2);
Debug.Assert(runResult.Diagnostics.IsEmpty);

// Or you can access the individual results on a by-generator basis
GeneratorRunResult generatorResult = runResult.Results[0];
Debug.Assert(generatorResult.Generator == generator);
Debug.Assert(generatorResult.Diagnostics.IsEmpty);
Debug.Assert(generatorResult.GeneratedSources.Length == 2);
Debug.Assert(generatorResult.Exception is null);


generatorResult.GeneratedSources[1].SourceText.ToString().ShouldBe("""
// <auto-generated />
using Microsoft.Extensions.DependencyInjection;
#nullable enable
namespace MyCode
{
partial class MyComponent<T>
{
public MyComponent()
{
_myService = Services.GetRequiredService<IService>();
}
public MyComponent<T> MyProp(int propValue)
{
_myProp = propValue;
return this;
}
}
}
""");

}

[Test]
public void TestPropAttributeGeneratorsForGenericClassesMultipleArguments()
{
// Create the 'input' compilation that the generator will act on
Compilation inputCompilation = CreateCompilation("""
using MauiReactor;
namespace MyCode;
partial class MyComponent<T1, T2>
{
[Prop]
int _myProp;
}
""");

// directly create an instance of the generator
// (Note: in the compiler this is loaded from an assembly, and created via reflection at runtime)
var generator = new ComponentPartialClassSourceGenerator();

// Create the driver that will control the generation, passing in our generator
GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);

// Run the generation pass
// (Note: the generator driver itself is immutable, and all calls return an updated version of the driver that you should use for subsequent calls)
driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics);

// We can now assert things about the resulting compilation:
Debug.Assert(diagnostics.IsEmpty); // there were no diagnostics created by the generators
Debug.Assert(outputCompilation.SyntaxTrees.Count() == 3);
//Debug.Assert(outputCompilation.GetDiagnostics().IsEmpty); // verify the compilation with the added source has no diagnostics

// Or we can look at the results directly:
GeneratorDriverRunResult runResult = driver.GetRunResult();

// The runResult contains the combined results of all generators passed to the driver
Debug.Assert(runResult.GeneratedTrees.Length == 2);
Debug.Assert(runResult.Diagnostics.IsEmpty);

// Or you can access the individual results on a by-generator basis
GeneratorRunResult generatorResult = runResult.Results[0];
Debug.Assert(generatorResult.Generator == generator);
Debug.Assert(generatorResult.Diagnostics.IsEmpty);
Debug.Assert(generatorResult.GeneratedSources.Length == 2);
Debug.Assert(generatorResult.Exception is null);


generatorResult.GeneratedSources[1].SourceText.ToString().ShouldBe("""
// <auto-generated />
#nullable enable
namespace MyCode
{
partial class MyComponent<T1, T2>
{
public MyComponent<T1, T2> MyProp(int propValue)
{
_myProp = propValue;
return this;
}
}
}
""");

}


[Test]
public void TestInjectAttributeGenerators()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public virtual string TransformText()
this.Write("\r\n{\r\n\tpartial class ");

#line 14 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ComponentPartialClassGenerator.tt"
this.Write(this.ToStringHelper.ToStringWithCulture(_classItem.ClassName));
this.Write(this.ToStringHelper.ToStringWithCulture(_classItem.FullyQualifiedClassName));

#line default
#line hidden
Expand Down Expand Up @@ -177,7 +177,7 @@ public virtual string TransformText()
this.Write("\t\tpublic ");

#line 34 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ComponentPartialClassGenerator.tt"
this.Write(this.ToStringHelper.ToStringWithCulture(_classItem.ClassName));
this.Write(this.ToStringHelper.ToStringWithCulture(_classItem.FullyQualifiedClassName));

#line default
#line hidden
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ using Microsoft.Extensions.DependencyInjection;

namespace <#= _classItem.Namespace #>
{
partial class <#= _classItem.ClassName #>
partial class <#= _classItem.FullyQualifiedClassName #>
{
<# if (GetInjectFields().Any() || GetParameterFields().Any()) { #>
public <#= _classItem.ClassName #>()
Expand All @@ -31,7 +31,7 @@ namespace <#= _classItem.Namespace #>
<# } #>

<# foreach (var fieldItem in GetPropFields()) { #>
public <#= _classItem.ClassName #> <#= fieldItem.GetPropMethodName() #>(<#= fieldItem.FieldTypeFullyQualifiedName #> propValue)
public <#= _classItem.FullyQualifiedClassName #> <#= fieldItem.GetPropMethodName() #>(<#= fieldItem.FieldTypeFullyQualifiedName #> propValue)
{
<#= fieldItem.FieldName #> = propValue;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,10 @@ void generateClassItem(FieldDeclarationSyntax fieldDeclaration, FieldAttributeTy

string fullyQualifiedTypeName = classTypeSymbol.ToDisplayString(qualifiedFormat);
string namespaceName = classTypeSymbol.ContainingNamespace.ToDisplayString();
string className = classTypeSymbol.Name;

if (!generatingClassItems.TryGetValue(fullyQualifiedTypeName, out var generatingClassItem))
{
generatingClassItems[fullyQualifiedTypeName] = generatingClassItem = new GeneratorClassItem(namespaceName, className);
generatingClassItems[fullyQualifiedTypeName] = generatingClassItem = new GeneratorClassItem(namespaceName, classTypeSymbol);
}

foreach (var variableFieldSyntax in fieldDeclaration.Declaration.Variables)
Expand Down Expand Up @@ -250,16 +249,18 @@ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)

public class GeneratorClassItem
{
public GeneratorClassItem(string @namespace, string className)
public GeneratorClassItem(string @namespace, INamedTypeSymbol symbol)
{
Namespace = @namespace;
ClassName = className;
ClassName = symbol.Name;
FullyQualifiedClassName = symbol.GetFullyQualifiedTypeName();
}

public string Namespace { get; }
public string ClassName { get; }

public string FullyQualifiedClassName { get; }
public Dictionary<string, GeneratorFieldItem> FieldItems { get; } = new();

}

public class GeneratorFieldItem
Expand Down
21 changes: 20 additions & 1 deletion src/MauiReactor.ScaffoldGenerator/Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.CodeAnalysis;
using System;
using System.Linq;
using System.Collections.Generic;

namespace MauiReactor.ScaffoldGenerator;

Expand Down Expand Up @@ -79,8 +80,26 @@ public static bool TryGetNamespace(SyntaxNode? syntaxNode, out string? result)
}
}


public static T EnsureNotNull<T>(this T? value)
=> value ?? throw new InvalidOperationException();

public static string GetFullyQualifiedTypeName(this INamedTypeSymbol namedTypeSymbol)
{
// Get the name of the class
string className = namedTypeSymbol.Name;

// Check if the class is generic
if (namedTypeSymbol.IsGenericType)
{
// Construct the generic type parameter list (e.g., "<T>")
string typeParameters = string.Join(", ", namedTypeSymbol.TypeParameters.Select(p => p.Name));
return $"{className}<{typeParameters}>";
}
else
{
// If it's not a generic type, just return the class name
return className;
}
}

}

0 comments on commit cf57667

Please sign in to comment.