feat: add abstract factory generator#224
Conversation
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
Test Results 1 files 1 suites 3m 29s ⏱️ Results for commit 6b491a5. ♻️ This comment has been updated with latest results. |
🔍 PR Validation ResultsVersion: `` ✅ Validation Steps
📊 ArtifactsDry-run artifacts have been uploaded and will be available for 7 days. This comment was automatically generated by the PR validation workflow. |
There was a problem hiding this comment.
Pull request overview
Adds a dedicated source-generated Abstract Factory “family matrix” path to complement the existing fluent AbstractFactory<TKey> runtime API, and wires an end-to-end example + docs + coverage updates to close #207.
Changes:
- Introduces
[GenerateAbstractFactory]/[AbstractFactoryProduct]attributes and anAbstractFactoryGeneratorwith PKAF00x diagnostics. - Adds generator tests and updates the AbstractFactory widget-family example to use the generated topology (including DI registration).
- Updates docs/catalogs/coverage checks to reflect the new generated Abstract Factory path.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| test/PatternKit.Generators.Tests/AbstractionsAttributeCoverageTests.cs | Adds coverage assertions for the new abstract-factory attributes (and factory attribute coverage entries). |
| test/PatternKit.Generators.Tests/AbstractFactoryGeneratorTests.cs | Adds generator smoke + diagnostic tests for abstract factory generation. |
| test/PatternKit.Examples.Tests/ProductionReadiness/PatternKitPatternCatalogTests.cs | Removes the “tracked gap” assertion for Abstract Factory (now implemented). |
| test/PatternKit.Examples.Tests/DependencyInjection/PatternKitExampleDependencyInjectionTests.cs | Verifies the new Abstract Factory example resolves/works via IServiceCollection registration. |
| test/PatternKit.Examples.Tests/AbstractFactoryDemo/AbstractFactoryDemoTests.cs | Adds coverage for generated factory creation and service-provider overload behavior. |
| src/PatternKit.Generators/Factories/FactoriesAttributes.cs | Adds generator-side definitions for new Abstract Factory attributes. |
| src/PatternKit.Generators/Factories/AbstractFactoryGenerator.cs | Implements the new incremental generator + PKAF diagnostics. |
| src/PatternKit.Generators/AnalyzerReleases.Unshipped.md | Registers PKAF001–PKAF004 diagnostics in analyzer release notes. |
| src/PatternKit.Generators.Abstractions/Factories/FactoriesAttributes.cs | Adds public attribute definitions for consumers (GenerateAbstractFactory, AbstractFactoryProduct). |
| src/PatternKit.Examples/ProductionReadiness/PatternKitPatternCatalog.cs | Updates catalog paths to include generator/doc/test/example for Abstract Factory. |
| src/PatternKit.Examples/ProductionReadiness/PatternKitExampleCatalog.cs | Adds a descriptor entry for the new Abstract Factory widget-family example. |
| src/PatternKit.Examples/DependencyInjection/PatternKitExampleServiceCollectionExtensions.cs | Registers the Abstract Factory example and exposes it as an importable DI surface. |
| src/PatternKit.Examples/AbstractFactoryDemo/AbstractFactoryDemo.cs | Switches demo from fluent matrix to generated matrix + adds CreateUIFactory(IServiceProvider) overload. |
| docs/patterns/creational/abstract-factory/index.md | Links to the new generator path and shows attribute usage snippet. |
| docs/guides/pattern-coverage.md | Updates coverage table to show Abstract Factory generator exists. |
| docs/generators/index.md | Adds Abstract Factory generator to the generator index and overview snippet list. |
| docs/generators/abstract-factory.md | New generator documentation page for Abstract Factory. |
| docs/examples/abstract-factory-widget-families.md | New example documentation page for generated widget families + DI import. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| context.AddSource($"{type.Name}.AbstractFactory.g.cs", SourceText.From( | ||
| GenerateSource(type, keyType, products, factoryMethodName, serviceProviderFactoryMethodName), | ||
| Encoding.UTF8)); | ||
| } | ||
|
|
| var ns = type.ContainingNamespace.IsGlobalNamespace ? null : type.ContainingNamespace.ToDisplayString(); | ||
| if (ns is not null) | ||
| { | ||
| sb.Append("namespace ").Append(ns).AppendLine(";"); | ||
| sb.AppendLine(); | ||
| } | ||
|
|
||
| sb.Append("partial ").Append(type.TypeKind == TypeKind.Struct ? "struct" : "class").Append(' ').Append(type.Name).AppendLine(); | ||
| sb.AppendLine("{"); |
| if (!implementationType.Constructors.Any(static ctor => | ||
| ctor.DeclaredAccessibility == Accessibility.Public && | ||
| ctor.Parameters.Length == 0)) |
| if (!string.IsNullOrWhiteSpace(serviceProviderFactoryMethodName)) | ||
| { | ||
| sb.AppendLine(); | ||
| EmitFactoryMethod(sb, keyTypeName, products, serviceProviderFactoryMethodName!, useServiceProvider: true); | ||
| } |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #224 +/- ##
==========================================
+ Coverage 91.59% 96.37% +4.78%
==========================================
Files 269 270 +1
Lines 25546 25798 +252
Branches 3514 3569 +55
==========================================
+ Hits 23399 24864 +1465
+ Misses 940 934 -6
+ Partials 1207 0 -1207
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| private static bool IsKeyCompatible(INamedTypeSymbol keyType, TypedConstant key) | ||
| { | ||
| if (key.IsNull || key.Value is null) | ||
| return !keyType.IsValueType; |
| if (key.Type is INamedTypeSymbol enumType && enumType.TypeKind == TypeKind.Enum) | ||
| { | ||
| var enumMember = enumType.GetMembers() | ||
| .OfType<IFieldSymbol>() | ||
| .FirstOrDefault(field => field.HasConstantValue && Equals(field.ConstantValue, constantValue)); | ||
| if (enumMember is not null) | ||
| { | ||
| expression = enumType.ToDisplayString(TypeFormat) + "." + enumMember.Name; | ||
| text = enumMember.Name; | ||
| return true; | ||
| } | ||
|
|
||
| expression = string.Empty; | ||
| text = string.Empty; | ||
| return false; | ||
| } | ||
|
|
||
| expression = key.Value switch | ||
| { | ||
| string value => "\"" + Escape(value) + "\"", | ||
| char value => "'" + EscapeChar(value) + "'", | ||
| bool value => value ? "true" : "false", | ||
| byte value => value.ToString(CultureInfo.InvariantCulture), | ||
| sbyte value => value.ToString(CultureInfo.InvariantCulture), | ||
| short value => value.ToString(CultureInfo.InvariantCulture), | ||
| ushort value => value.ToString(CultureInfo.InvariantCulture), | ||
| int value => value.ToString(CultureInfo.InvariantCulture), | ||
| uint value => value.ToString(CultureInfo.InvariantCulture) + "u", | ||
| long value => value.ToString(CultureInfo.InvariantCulture) + "L", | ||
| ulong value => value.ToString(CultureInfo.InvariantCulture) + "UL", | ||
| _ => string.Empty | ||
| }; | ||
|
|
||
| text = constantValue.ToString() ?? string.Empty; | ||
| return expression.Length > 0; |
| var ns = type.ContainingNamespace.IsGlobalNamespace ? null : type.ContainingNamespace.ToDisplayString(); | ||
| if (ns is not null) | ||
| { | ||
| sb.Append("namespace ").Append(ns).AppendLine(";"); | ||
| sb.AppendLine(); | ||
| } | ||
|
|
||
| sb.Append("partial ").Append(type.TypeKind == TypeKind.Struct ? "struct" : "class").Append(' ').Append(type.Name).AppendLine(); | ||
| sb.AppendLine("{"); |
| [Scenario("Reports diagnostic for non-partial abstract factory host")] | ||
| [Fact] | ||
| public void ReportsDiagnosticForNonPartialAbstractFactoryHost() | ||
| { | ||
| var source = """ | ||
| using PatternKit.Generators.Factories; | ||
|
|
||
| namespace Demo; | ||
|
|
||
| [GenerateAbstractFactory(typeof(string))] | ||
| public static class WidgetFactory; | ||
| """; | ||
|
|
||
| var comp = CreateCompilation(source, nameof(ReportsDiagnosticForNonPartialAbstractFactoryHost)); | ||
| var gen = new AbstractFactoryGenerator(); | ||
| _ = RoslynTestHelpers.Run(comp, gen, out var run, out _); | ||
|
|
||
| var diagnostic = ScenarioExpect.Single(run.Results.SelectMany(result => result.Diagnostics)); | ||
| ScenarioExpect.Equal("PKAF001", diagnostic.Id); | ||
| } | ||
|
|
Code Coverage |
Summary
Closes #207
Validation
Note: focused local test builds are blocked on this machine by the existing Roslyn compiler/analyzer mismatch (CS9057/CS1705); PR CI should be authoritative.