Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/PatternKit.Generators/Composite/CompositeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ private static string RenderComposite(INamedTypeSymbol component, string compone
sb.AppendLine("{");
foreach (var property in GetContractProperties(component))
{
sb.Append(" public abstract ").Append(property.Type.ToDisplayString(TypeFormat)).Append(' ').Append(property.Name).Append(" { get; }").AppendLine();
sb.Append(component.TypeKind == TypeKind.Class ? " public abstract override " : " public abstract ")
.Append(property.Type.ToDisplayString(TypeFormat)).Append(' ').Append(property.Name).Append(" { get; }").AppendLine();
Comment on lines +134 to +135
sb.AppendLine();
}
sb.AppendLine(" public virtual bool IsLeaf => true;");
Expand Down
92 changes: 92 additions & 0 deletions test/PatternKit.Generators.Tests/CompositeGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,47 @@ public sealed class CategoryNode : CategoryCompositeBase
ScenarioExpect.True(emit.Success, string.Join("\n", emit.Diagnostics));
}

[Scenario("GeneratesCompositeBasesForAbstractClassWithCustomNames")]
[Fact]
public void GeneratesCompositeBasesForAbstractClassWithCustomNames()
{
const string source = """
using PatternKit.Generators.Composite;

namespace TestNamespace;

[CompositeComponent(
ComponentBaseName = "MenuItemBase",
CompositeBaseName = "MenuGroupBase",
ChildrenPropertyName = "Items")]
public abstract partial class MenuItem
{
public abstract string Name { get; }

[CompositeIgnore]
public abstract int SortOrder { get; }
}
""";

var comp = RoslynTestHelpers.CreateCompilation(source, nameof(GeneratesCompositeBasesForAbstractClassWithCustomNames));
var gen = new CompositeGenerator();
_ = RoslynTestHelpers.Run(comp, gen, out var result, out var updated);

ScenarioExpect.All(result.Results, r => ScenarioExpect.Empty(r.Diagnostics));

var generated = ScenarioExpect.Single(result.Results.SelectMany(r => r.GeneratedSources));
var text = generated.SourceText.ToString();
ScenarioExpect.Equal("MenuItem.Composite.g.cs", generated.HintName);
ScenarioExpect.Contains("public abstract partial class MenuItemBase : global::TestNamespace.MenuItem", text);
ScenarioExpect.Contains("public virtual global::System.Collections.Generic.IReadOnlyList<global::TestNamespace.MenuItem> Items", text);
ScenarioExpect.Contains("public abstract override string Name { get; }", text);
ScenarioExpect.DoesNotContain("SortOrder", text);
ScenarioExpect.Contains("public abstract partial class MenuGroupBase : MenuItemBase", text);

var emit = updated.Emit(Stream.Null);
ScenarioExpect.True(emit.Success, string.Join("\n", emit.Diagnostics));
}

[Scenario("ReportsDiagnosticWhenComponentIsNotPartial")]
[Fact]
public void ReportsDiagnosticWhenComponentIsNotPartial()
Expand Down Expand Up @@ -97,4 +138,55 @@ public partial class Category
var diags = result.Results.SelectMany(r => r.Diagnostics);
ScenarioExpect.Contains(diags, d => d.Id == "PKCMP002");
}

[Scenario("ReportsDiagnosticWhenCompositeContractHasEvent")]
[Fact]
public void ReportsDiagnosticWhenCompositeContractHasEvent()
{
const string source = """
using System;
using PatternKit.Generators.Composite;

namespace TestNamespace;

[CompositeComponent]
public partial interface ICategory
{
event EventHandler? Changed;
}
""";

var comp = RoslynTestHelpers.CreateCompilation(source, nameof(ReportsDiagnosticWhenCompositeContractHasEvent));
var gen = new CompositeGenerator();
_ = RoslynTestHelpers.Run(comp, gen, out var result, out _);

var diags = result.Results.SelectMany(r => r.Diagnostics);
ScenarioExpect.Contains(diags, d => d.Id == "PKCMP004");
}

[Scenario("ReportsDiagnosticWhenCompositeGeneratedNameConflicts")]
[Fact]
public void ReportsDiagnosticWhenCompositeGeneratedNameConflicts()
{
const string source = """
using PatternKit.Generators.Composite;

namespace TestNamespace;

public abstract class CategoryComponentBase;

[CompositeComponent]
public partial interface ICategory
{
string Name { get; }
}
""";

var comp = RoslynTestHelpers.CreateCompilation(source, nameof(ReportsDiagnosticWhenCompositeGeneratedNameConflicts));
var gen = new CompositeGenerator();
_ = RoslynTestHelpers.Run(comp, gen, out var result, out _);

var diags = result.Results.SelectMany(r => r.Diagnostics);
ScenarioExpect.Contains(diags, d => d.Id == "PKCMP003");
}
}
120 changes: 120 additions & 0 deletions test/PatternKit.Generators.Tests/FlyweightGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,39 @@ public readonly partial record struct Glyph(char Value)
ScenarioExpect.True(emit.Success, string.Join("\n", emit.Diagnostics));
}

[Scenario("GeneratesFlyweightCacheWithoutTryGet")]
[Fact]
public void GeneratesFlyweightCacheWithoutTryGet()
{
const string source = """
using PatternKit.Generators.Flyweight;

namespace TestNamespace;

[Flyweight(typeof(string), CacheTypeName = "TokenCache", GenerateTryGet = false)]
public sealed partial record class Token(string Value)
{
[FlyweightFactory]
private static Token Create(string key) => new(key);
}
""";

var comp = RoslynTestHelpers.CreateCompilation(source, nameof(GeneratesFlyweightCacheWithoutTryGet));
var gen = new FlyweightGenerator();
_ = RoslynTestHelpers.Run(comp, gen, out var result, out var updated);

ScenarioExpect.All(result.Results, r => ScenarioExpect.Empty(r.Diagnostics));

var generated = result.Results.SelectMany(r => r.GeneratedSources).Single(s => s.HintName == "Token.Flyweight.g.cs").SourceText.ToString();
ScenarioExpect.Contains("partial record class Token", generated);
ScenarioExpect.Contains("public sealed partial class TokenCache", generated);
ScenarioExpect.Contains("public global::TestNamespace.Token Get(string key)", generated);
ScenarioExpect.DoesNotContain("public bool TryGet(", generated);

var emit = updated.Emit(Stream.Null);
ScenarioExpect.True(emit.Success, string.Join("\n", emit.Diagnostics));
}

[Scenario("ReportsMissingFactory")]
[Fact]
public void ReportsMissingFactory()
Expand Down Expand Up @@ -83,6 +116,61 @@ public readonly partial record struct Glyph(char Value)
ScenarioExpect.Contains(diags, d => d.Id == "PKFLY006");
}

[Scenario("ReportsMultipleFlyweightFactories")]
[Fact]
public void ReportsMultipleFlyweightFactories()
{
const string source = """
using PatternKit.Generators.Flyweight;

namespace TestNamespace;

[Flyweight(typeof(string))]
public readonly partial record struct Glyph(char Value)
{
[FlyweightFactory]
private static Glyph Create(string key) => new(key[0]);

[FlyweightFactory]
private static Glyph CreateAlternate(string key) => new(key[^1]);
}
""";

var comp = RoslynTestHelpers.CreateCompilation(source, nameof(ReportsMultipleFlyweightFactories));
var gen = new FlyweightGenerator();
_ = RoslynTestHelpers.Run(comp, gen, out var result, out _);

var diags = result.Results.SelectMany(r => r.Diagnostics);
ScenarioExpect.Contains(diags, d => d.Id == "PKFLY003");
}

[Scenario("ReportsFlyweightCacheNameConflict")]
[Fact]
public void ReportsFlyweightCacheNameConflict()
{
const string source = """
using PatternKit.Generators.Flyweight;

namespace TestNamespace;

public sealed class GlyphFlyweightCache;

[Flyweight(typeof(string))]
public readonly partial record struct Glyph(char Value)
{
[FlyweightFactory]
private static Glyph Create(string key) => new(key[0]);
}
""";

var comp = RoslynTestHelpers.CreateCompilation(source, nameof(ReportsFlyweightCacheNameConflict));
var gen = new FlyweightGenerator();
_ = RoslynTestHelpers.Run(comp, gen, out var result, out _);

var diags = result.Results.SelectMany(r => r.Diagnostics);
ScenarioExpect.Contains(diags, d => d.Id == "PKFLY005");
}

[Scenario("ReportsNonPartialAndNonStaticFactory")]
[Fact]
public void ReportsNonPartialAndNonStaticFactory()
Expand Down Expand Up @@ -115,4 +203,36 @@ public readonly partial record struct NonStaticFactoryGlyph(char Value)
ScenarioExpect.Contains(diags, d => d.Id == "PKFLY001");
ScenarioExpect.Contains(diags, d => d.Id == "PKFLY004");
}

[Scenario("ReportsInvalidFlyweightFactoryForWrongKeyAndReturnTypes")]
[Fact]
public void ReportsInvalidFlyweightFactoryForWrongKeyAndReturnTypes()
{
const string source = """
using PatternKit.Generators.Flyweight;

namespace TestNamespace;

[Flyweight(typeof(string))]
public readonly partial record struct WrongKeyGlyph(char Value)
{
[FlyweightFactory]
private static WrongKeyGlyph Create(int key) => new((char)key);
}

[Flyweight(typeof(string))]
public readonly partial record struct WrongReturnGlyph(char Value)
{
[FlyweightFactory]
private static string Create(string key) => key;
}
""";

var comp = RoslynTestHelpers.CreateCompilation(source, nameof(ReportsInvalidFlyweightFactoryForWrongKeyAndReturnTypes));
var gen = new FlyweightGenerator();
_ = RoslynTestHelpers.Run(comp, gen, out var result, out _);

var diags = result.Results.SelectMany(r => r.Diagnostics).ToArray();
ScenarioExpect.Equal(2, diags.Count(d => d.Id == "PKFLY004"));
}
}
Loading