@@ -38,6 +38,7 @@ public static partial class OrderDataMapper
3838 ScenarioExpect . Contains ( "CreateMapper()" , source ) ;
3939 ScenarioExpect . Contains ( ".MapToData(ToData)" , source ) ;
4040 ScenarioExpect . Contains ( ".MapToDomain(ToDomain)" , source ) ;
41+ ScenarioExpect . True ( result . EmitSuccess , string . Join ( Environment . NewLine , result . EmitDiagnostics ) ) ;
4142 } )
4243 . AssertPassed ( ) ;
4344
@@ -65,25 +66,73 @@ public static class OrderDataMapper
6566 . AssertPassed ( ) ;
6667
6768 [ Scenario ( "Generator reports missing mapper projections" ) ]
68- [ Fact ]
69- public Task Generator_Reports_Missing_Mapper_Projections ( )
70- => Given ( "a mapper declaration without projections" , ( ) => Compile ( """
69+ [ Theory ]
70+ [ InlineData ( "" ) ]
71+ [ InlineData ( """
72+ [DataMapperToData]
73+ private static OrderRow ToData(DomainOrder order) => new(order.Id);
74+ [DataMapperToData]
75+ private static OrderRow ToDataAgain(DomainOrder order) => new(order.Id);
76+ [DataMapperToDomain]
77+ private static DomainOrder ToDomain(OrderRow row) => new(row.OrderId);
78+ """ ) ]
79+ [ InlineData ( """
80+ [DataMapperToData]
81+ private static OrderRow ToData(DomainOrder order) => new(order.Id);
82+ [DataMapperToDomain]
83+ private static DomainOrder ToDomain(OrderRow row) => new(row.OrderId);
84+ [DataMapperToDomain]
85+ private static DomainOrder ToDomainAgain(OrderRow row) => new(row.OrderId);
86+ """ ) ]
87+ public Task Generator_Reports_Missing_Mapper_Projections ( string projections )
88+ => Given ( "a mapper declaration with missing or duplicate projections" , ( ) => Compile ( $$ """
7189 using PatternKit.Generators.DataMapping;
7290
7391 public sealed record DomainOrder(string Id);
7492 public sealed record OrderRow(string OrderId);
7593
7694 [GenerateDataMapper(typeof(DomainOrder), typeof(OrderRow))]
77- public static partial class OrderDataMapper;
95+ public static partial class OrderDataMapper
96+ {
97+ {{ projections }}
98+ }
7899 """ ) )
79100 . Then ( "PKMAP002 is reported" , result =>
80101 ScenarioExpect . Contains ( result . Diagnostics , static diagnostic => diagnostic . Id == "PKMAP002" ) )
81102 . AssertPassed ( ) ;
82103
83104 [ Scenario ( "Generator reports invalid mapper projection signatures" ) ]
105+ [ Theory ]
106+ [ InlineData ( "[DataMapperToData] private OrderRow ToData(DomainOrder order) => new(order.Id);" ) ]
107+ [ InlineData ( "[DataMapperToData] private static T ToData<T>(DomainOrder order) => default!;" ) ]
108+ [ InlineData ( "[DataMapperToData] private static OrderRow ToData() => new(\" missing\" );" ) ]
109+ [ InlineData ( "[DataMapperToData] private static OrderRow ToData(DomainOrder order, string tenant) => new(order.Id);" ) ]
110+ [ InlineData ( "[DataMapperToData] private static OrderRow ToData(OrderRow row) => row;" ) ]
111+ [ InlineData ( "[DataMapperToData] private static string ToData(DomainOrder order) => order.Id;" ) ]
112+ public Task Generator_Reports_Invalid_To_Data_Projection_Signatures ( string projection )
113+ => Given ( "a mapper declaration with an invalid to-data projection" , ( ) => Compile ( $$ """
114+ using PatternKit.Generators.DataMapping;
115+
116+ public sealed record DomainOrder(string Id);
117+ public sealed record OrderRow(string OrderId);
118+
119+ [GenerateDataMapper(typeof(DomainOrder), typeof(OrderRow))]
120+ public static partial class OrderDataMapper
121+ {
122+ {{ projection }}
123+
124+ [DataMapperToDomain]
125+ private static DomainOrder ToDomain(OrderRow row) => new(row.OrderId);
126+ }
127+ """ ) )
128+ . Then ( "PKMAP003 is reported" , result =>
129+ ScenarioExpect . Contains ( result . Diagnostics , static diagnostic => diagnostic . Id == "PKMAP003" ) )
130+ . AssertPassed ( ) ;
131+
132+ [ Scenario ( "Generator reports invalid to-domain mapper projection signatures" ) ]
84133 [ Fact ]
85- public Task Generator_Reports_Invalid_Mapper_Projection_Signatures ( )
86- => Given ( "a mapper declaration with an invalid projection" , ( ) => Compile ( """
134+ public Task Generator_Reports_Invalid_To_Domain_Mapper_Projection_Signatures ( )
135+ => Given ( "a mapper declaration with an invalid to-domain projection" , ( ) => Compile ( """
87136 using PatternKit.Generators.DataMapping;
88137
89138 public sealed record DomainOrder(string Id);
@@ -93,28 +142,177 @@ public sealed record OrderRow(string OrderId);
93142 public static partial class OrderDataMapper
94143 {
95144 [DataMapperToData]
96- private static string ToData(DomainOrder order) => order.Id;
145+ private static OrderRow ToData(DomainOrder order) => new( order.Id) ;
97146
98147 [DataMapperToDomain]
99- private static DomainOrder ToDomain(OrderRow row) => new( row.OrderId) ;
148+ private static OrderRow ToDomain(OrderRow row) => row;
100149 }
101150 """ ) )
102151 . Then ( "PKMAP003 is reported" , result =>
103152 ScenarioExpect . Contains ( result . Diagnostics , static diagnostic => diagnostic . Id == "PKMAP003" ) )
104153 . AssertPassed ( ) ;
105154
155+ [ Scenario ( "Generator emits mapper defaults and type shapes" ) ]
156+ [ Fact ]
157+ public Task Generator_Emits_Mapper_Defaults_And_Type_Shapes ( )
158+ => Given ( "mapper declarations using default names and different host shapes" , ( ) => Compile ( """
159+ using PatternKit.Generators.DataMapping;
160+
161+ namespace Demo;
162+
163+ public sealed record DomainOrder(string Id);
164+ public sealed record OrderRow(string OrderId);
165+
166+ [GenerateDataMapper(typeof(DomainOrder), typeof(OrderRow))]
167+ internal abstract partial class AbstractMapper
168+ {
169+ [DataMapperToData]
170+ private static OrderRow ToData(DomainOrder order) => new(order.Id);
171+
172+ [DataMapperToDomain]
173+ private static DomainOrder ToDomain(OrderRow row) => new(row.OrderId);
174+ }
175+
176+ [GenerateDataMapper(typeof(DomainOrder), typeof(OrderRow))]
177+ public sealed partial class SealedMapper
178+ {
179+ [DataMapperToData]
180+ private static OrderRow ToData(DomainOrder order) => new(order.Id);
181+
182+ [DataMapperToDomain]
183+ private static DomainOrder ToDomain(OrderRow row) => new(row.OrderId);
184+ }
185+
186+ [GenerateDataMapper(typeof(DomainOrder), typeof(OrderRow))]
187+ internal partial struct StructMapper
188+ {
189+ [DataMapperToData]
190+ private static OrderRow ToData(DomainOrder order) => new(order.Id);
191+
192+ [DataMapperToDomain]
193+ private static DomainOrder ToDomain(OrderRow row) => new(row.OrderId);
194+ }
195+ """ ) )
196+ . Then ( "generated sources preserve host shape and default factory names" , result =>
197+ {
198+ ScenarioExpect . Empty ( result . Diagnostics . Where ( static d => d . Severity == DiagnosticSeverity . Error ) ) ;
199+ ScenarioExpect . Equal ( 3 , result . GeneratedSources . Count ) ;
200+
201+ var combined = string . Join ( "\n " , result . GeneratedSources ) ;
202+ ScenarioExpect . Contains ( "internal abstract partial class AbstractMapper" , combined ) ;
203+ ScenarioExpect . Contains ( "DataMapper<global::Demo.DomainOrder, global::Demo.OrderRow> Create()" , combined ) ;
204+ ScenarioExpect . Contains ( "public sealed partial class SealedMapper" , combined ) ;
205+ ScenarioExpect . Contains ( "internal partial struct StructMapper" , combined ) ;
206+ ScenarioExpect . True ( result . EmitSuccess , string . Join ( Environment . NewLine , result . EmitDiagnostics ) ) ;
207+ } )
208+ . AssertPassed ( ) ;
209+
210+ [ Scenario ( "Generator emits nested mapper host wrappers" ) ]
211+ [ Fact ]
212+ public Task Generator_Emits_Nested_Mapper_Host_Wrappers ( )
213+ => Given ( "nested mapper declarations with non-public accessibility" , ( ) => Compile ( """
214+ using PatternKit.Generators.DataMapping;
215+
216+ namespace Demo;
217+
218+ public sealed record DomainOrder(string Id);
219+ public sealed record OrderRow(string OrderId);
220+
221+ public partial class MapperContainer
222+ {
223+ private partial class PrivateHost
224+ {
225+ [GenerateDataMapper(typeof(DomainOrder), typeof(OrderRow))]
226+ protected partial class ProtectedMapper
227+ {
228+ [DataMapperToData]
229+ private static OrderRow ToData(DomainOrder order) => new(order.Id);
230+
231+ [DataMapperToDomain]
232+ private static DomainOrder ToDomain(OrderRow row) => new(row.OrderId);
233+ }
234+
235+ [GenerateDataMapper(typeof(DomainOrder), typeof(OrderRow))]
236+ private protected partial class PrivateProtectedMapper
237+ {
238+ [DataMapperToData]
239+ private static OrderRow ToData(DomainOrder order) => new(order.Id);
240+
241+ [DataMapperToDomain]
242+ private static DomainOrder ToDomain(OrderRow row) => new(row.OrderId);
243+ }
244+
245+ [GenerateDataMapper(typeof(DomainOrder), typeof(OrderRow))]
246+ protected internal partial class ProtectedInternalMapper
247+ {
248+ [DataMapperToData]
249+ private static OrderRow ToData(DomainOrder order) => new(order.Id);
250+
251+ [DataMapperToDomain]
252+ private static DomainOrder ToDomain(OrderRow row) => new(row.OrderId);
253+ }
254+ }
255+ }
256+ """ ) )
257+ . Then ( "generated sources preserve containing partial type wrappers" , result =>
258+ {
259+ ScenarioExpect . Empty ( result . Diagnostics . Where ( static d => d . Severity == DiagnosticSeverity . Error ) ) ;
260+ ScenarioExpect . Equal ( 3 , result . GeneratedSources . Count ) ;
261+
262+ var combined = string . Join ( "\n " , result . GeneratedSources ) ;
263+ ScenarioExpect . Contains ( "public partial class MapperContainer" , combined ) ;
264+ ScenarioExpect . Contains ( "private partial class PrivateHost" , combined ) ;
265+ ScenarioExpect . Contains ( "protected partial class ProtectedMapper" , combined ) ;
266+ ScenarioExpect . Contains ( "private protected partial class PrivateProtectedMapper" , combined ) ;
267+ ScenarioExpect . Contains ( "protected internal partial class ProtectedInternalMapper" , combined ) ;
268+ ScenarioExpect . True ( result . EmitSuccess , string . Join ( Environment . NewLine , result . EmitDiagnostics ) ) ;
269+ } )
270+ . AssertPassed ( ) ;
271+
272+ [ Scenario ( "Generator skips malformed mapper type arguments" ) ]
273+ [ Theory ]
274+ [ InlineData ( "null!" , "typeof(OrderRow)" ) ]
275+ [ InlineData ( "typeof(DomainOrder)" , "null!" ) ]
276+ public Task Generator_Skips_Malformed_Mapper_Type_Arguments ( string domainType , string dataType )
277+ => Given ( "a mapper declaration with a null type argument" , ( ) => Compile ( $$ """
278+ using PatternKit.Generators.DataMapping;
279+
280+ public sealed record DomainOrder(string Id);
281+ public sealed record OrderRow(string OrderId);
282+
283+ [GenerateDataMapper({{ domainType }} , {{ dataType }} )]
284+ public static partial class OrderDataMapper
285+ {
286+ [DataMapperToData]
287+ private static OrderRow ToData(DomainOrder order) => new(order.Id);
288+
289+ [DataMapperToDomain]
290+ private static DomainOrder ToDomain(OrderRow row) => new(row.OrderId);
291+ }
292+ """ ) )
293+ . Then ( "no source is generated" , result =>
294+ ScenarioExpect . Empty ( result . GeneratedSources ) )
295+ . AssertPassed ( ) ;
296+
106297 private static GeneratorResult Compile ( string source )
107298 {
108299 var compilation = RoslynTestHelpers . CreateCompilation (
109300 source ,
110301 "DataMapperGeneratorTests" ,
111302 extra : MetadataReference . CreateFromFile ( typeof ( DataMapper < , > ) . Assembly . Location ) ) ;
112- _ = RoslynTestHelpers . Run ( compilation , new DataMapperGenerator ( ) , out var run , out _ ) ;
303+ _ = RoslynTestHelpers . Run ( compilation , new DataMapperGenerator ( ) , out var run , out var updated ) ;
113304 var result = run . Results . Single ( ) ;
305+ var emit = updated . Emit ( Stream . Null ) ;
114306 return new GeneratorResult (
115307 result . Diagnostics . ToArray ( ) ,
116- result . GeneratedSources . Select ( static source => source . SourceText . ToString ( ) ) . ToArray ( ) ) ;
308+ result . GeneratedSources . Select ( static source => source . SourceText . ToString ( ) ) . ToArray ( ) ,
309+ emit . Success ,
310+ emit . Diagnostics . Select ( static diagnostic => diagnostic . ToString ( ) ) . ToArray ( ) ) ;
117311 }
118312
119- private sealed record GeneratorResult ( IReadOnlyList < Diagnostic > Diagnostics , IReadOnlyList < string > GeneratedSources ) ;
313+ private sealed record GeneratorResult (
314+ IReadOnlyList < Diagnostic > Diagnostics ,
315+ IReadOnlyList < string > GeneratedSources ,
316+ bool EmitSuccess ,
317+ IReadOnlyList < string > EmitDiagnostics ) ;
120318}
0 commit comments