diff --git a/.editorconfig b/.editorconfig index c468c5b..d7c6042 100644 --- a/.editorconfig +++ b/.editorconfig @@ -156,7 +156,7 @@ dotnet_diagnostic.IDE0301.severity = warning # IDE0305: Use collection expression for fluent dotnet_diagnostic.IDE0305.severity = warning -[{CellValueTruncateAttribute.cs,ColumnHeaderAttribute.cs,ColumnOrderAttribute.cs,CellStyleAttribute.cs,ColumnWidthAttribute.cs,WorksheetRowAttribute.cs}] +[{CellStyleAttribute.cs,CellValueConverterAttribute.cs,CellValueTruncateAttribute.cs,ColumnHeaderAttribute.cs,ColumnOrderAttribute.cs,ColumnWidthAttribute.cs,WorksheetRowAttribute.cs}] # CA1019: Define accessors for attribute arguments dotnet_diagnostic.CA1019.severity = none # CS9113: Parameter is unread @@ -266,6 +266,10 @@ dotnet_diagnostic.S3925.severity = none # S4136: Method overloads should be grouped together dotnet_diagnostic.S4136.severity = warning +[{CellValueConverter.cs}] +# S1694: An abstract class should have both abstract and concrete methods +dotnet_diagnostic.S1694.severity = none + [{*Test,*.Benchmark}/**.cs] # S927: Parameter names should match base declaration dotnet_diagnostic.S927.severity = none diff --git a/SpreadCheetah.SourceGenerator.CSharp8Test/Models/BaseClass.cs b/SpreadCheetah.SourceGenerator.CSharp8Test/Models/BaseClass.cs index 6ed216d..325b4d5 100644 --- a/SpreadCheetah.SourceGenerator.CSharp8Test/Models/BaseClass.cs +++ b/SpreadCheetah.SourceGenerator.CSharp8Test/Models/BaseClass.cs @@ -5,6 +5,7 @@ namespace SpreadCheetah.SourceGenerator.CSharp8Test.Models public class BaseClass { [CellStyle("Id style")] + [CellValueConverter(typeof(RemoveDashesValueConverter))] public string Id { get; } protected BaseClass(string id) diff --git a/SpreadCheetah.SourceGenerator.CSharp8Test/Models/ClassWithMultipleProperties.cs b/SpreadCheetah.SourceGenerator.CSharp8Test/Models/ClassWithMultipleProperties.cs index 6a8af5a..7af8230 100644 --- a/SpreadCheetah.SourceGenerator.CSharp8Test/Models/ClassWithMultipleProperties.cs +++ b/SpreadCheetah.SourceGenerator.CSharp8Test/Models/ClassWithMultipleProperties.cs @@ -11,6 +11,7 @@ public class ClassWithMultipleProperties : BaseClass public string LastName { get; } [ColumnOrder(1)] + [CellValueConverter(typeof(UpperCaseValueConverter))] public string FirstName { get; } [ColumnWidth(5)] diff --git a/SpreadCheetah.SourceGenerator.CSharp8Test/Models/RemoveDashesValueConverter.cs b/SpreadCheetah.SourceGenerator.CSharp8Test/Models/RemoveDashesValueConverter.cs new file mode 100644 index 0000000..2671e53 --- /dev/null +++ b/SpreadCheetah.SourceGenerator.CSharp8Test/Models/RemoveDashesValueConverter.cs @@ -0,0 +1,12 @@ +using SpreadCheetah.SourceGeneration; + +namespace SpreadCheetah.SourceGenerator.CSharp8Test.Models +{ + internal class RemoveDashesValueConverter : CellValueConverter + { + public override DataCell ConvertToDataCell(string value) + { + return new DataCell(value.Replace("-", "")); + } + } +} diff --git a/SpreadCheetah.SourceGenerator.CSharp8Test/Models/UpperCaseValueConverter.cs b/SpreadCheetah.SourceGenerator.CSharp8Test/Models/UpperCaseValueConverter.cs new file mode 100644 index 0000000..ee43c26 --- /dev/null +++ b/SpreadCheetah.SourceGenerator.CSharp8Test/Models/UpperCaseValueConverter.cs @@ -0,0 +1,12 @@ +using SpreadCheetah.SourceGeneration; + +namespace SpreadCheetah.SourceGenerator.CSharp8Test.Models +{ + public class UpperCaseValueConverter : CellValueConverter + { + public override DataCell ConvertToDataCell(string value) + { + return new DataCell(value.ToUpperInvariant()); + } + } +} diff --git a/SpreadCheetah.SourceGenerator.CSharp8Test/Tests/WorksheetRowGeneratorTests.cs b/SpreadCheetah.SourceGenerator.CSharp8Test/Tests/WorksheetRowGeneratorTests.cs index f481b2f..9ea84be 100644 --- a/SpreadCheetah.SourceGenerator.CSharp8Test/Tests/WorksheetRowGeneratorTests.cs +++ b/SpreadCheetah.SourceGenerator.CSharp8Test/Tests/WorksheetRowGeneratorTests.cs @@ -14,8 +14,9 @@ public class WorksheetRowGeneratorTests public async Task Spreadsheet_AddAsRow_ClassWithMultipleProperties() { // Arrange + const string id = "2cc56ae0-32b2-46c2-b6d2-27c6ce8568ec"; var obj = new ClassWithMultipleProperties( - id: Guid.NewGuid().ToString(), + id: id, firstName: "Ola", lastName: "Nordmann", age: 30); @@ -36,8 +37,8 @@ public async Task Spreadsheet_AddAsRow_ClassWithMultipleProperties() // Assert using var sheet = SpreadsheetAssert.SingleSheet(stream); - Assert.Equal(obj.FirstName, sheet["A1"].StringValue); - Assert.Equal(obj.Id, sheet["B1"].StringValue); + Assert.Equal(obj.FirstName.ToUpperInvariant(), sheet["A1"].StringValue); + Assert.Equal(obj.Id.Replace("-", ""), sheet["B1"].StringValue); Assert.Equal(obj.LastName, sheet["C1"].StringValue); Assert.Equal(obj.Age, sheet["D1"].IntValue); Assert.Equal(4, sheet.CellCount); diff --git a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellStyle_ClassWithEmptyCellStyle.verified.txt b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellStyle_ClassWithEmptyCellStyle.verified.txt index c9f7769..0059bfc 100644 --- a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellStyle_ClassWithEmptyCellStyle.verified.txt +++ b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellStyle_ClassWithEmptyCellStyle.verified.txt @@ -6,8 +6,8 @@ Severity: Error, WarningLevel: 0, Location: : (6,5)-(6,18), - MessageFormat: '{0}' is an invalid argument for attribute '{1}', - Message: '' is an invalid argument for attribute 'SpreadCheetah.SourceGeneration.CellStyleAttribute', + MessageFormat: '{0}' is an invalid argument for {1}, + Message: '' is an invalid argument for CellStyleAttribute, Category: SpreadCheetah.SourceGenerator } ] diff --git a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithDuplicateColumnOrdering#MyNamespace.MyGenRowContext.g.verified.cs b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassPropertyWithConverterAndCellStyle#MyNamespace.MyGenRowContext.g.verified.cs similarity index 57% rename from SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithDuplicateColumnOrdering#MyNamespace.MyGenRowContext.g.verified.cs rename to SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassPropertyWithConverterAndCellStyle#MyNamespace.MyGenRowContext.g.verified.cs index f64ff8e..3e14aa9 100644 --- a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithDuplicateColumnOrdering#MyNamespace.MyGenRowContext.g.verified.cs +++ b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassPropertyWithConverterAndCellStyle#MyNamespace.MyGenRowContext.g.verified.cs @@ -20,18 +20,28 @@ public partial class MyGenRowContext public MyGenRowContext() { } + private static readonly MyNamespace.StringValueConverter _valueConverter0 = new MyNamespace.StringValueConverter(); - private WorksheetRowTypeInfo? _ClassWithDuplicateColumnOrdering; - public WorksheetRowTypeInfo ClassWithDuplicateColumnOrdering => _ClassWithDuplicateColumnOrdering - ??= WorksheetRowMetadataServices.CreateObjectInfo( - AddHeaderRow0Async, AddAsRowAsync, AddRangeAsRowsAsync, null); + private WorksheetRowTypeInfo? _ClassPropertyWithConverterAndCellStyle; + public WorksheetRowTypeInfo ClassPropertyWithConverterAndCellStyle => _ClassPropertyWithConverterAndCellStyle + ??= WorksheetRowMetadataServices.CreateObjectInfo( + AddHeaderRow0Async, AddAsRowAsync, AddRangeAsRowsAsync, null, CreateWorksheetRowDependencyInfo0); + + private static WorksheetRowDependencyInfo CreateWorksheetRowDependencyInfo0(Spreadsheet spreadsheet) + { + var styleIds = new[] + { + spreadsheet.GetStyleId("My style"), + }; + return new WorksheetRowDependencyInfo(styleIds); + } private static async ValueTask AddHeaderRow0Async(SpreadCheetah.Spreadsheet spreadsheet, SpreadCheetah.Styling.StyleId? styleId, CancellationToken token) { var cells = ArrayPool.Shared.Rent(1); try { - cells[0] = new StyledCell("PropertyA", styleId); + cells[0] = new StyledCell("Property", styleId); await spreadsheet.AddRowAsync(cells.AsMemory(0, 1), token).ConfigureAwait(false); } finally @@ -40,17 +50,17 @@ private static async ValueTask AddHeaderRow0Async(SpreadCheetah.Spreadsheet spre } } - private static ValueTask AddAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, MyNamespace.ClassWithDuplicateColumnOrdering? obj, CancellationToken token) + private static ValueTask AddAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, MyNamespace.ClassPropertyWithConverterAndCellStyle? obj, CancellationToken token) { if (spreadsheet is null) throw new ArgumentNullException(nameof(spreadsheet)); if (obj is null) - return spreadsheet.AddRowAsync(ReadOnlyMemory.Empty, token); + return spreadsheet.AddRowAsync(ReadOnlyMemory.Empty, token); return AddAsRowInternalAsync(spreadsheet, obj, token); } private static ValueTask AddRangeAsRowsAsync(SpreadCheetah.Spreadsheet spreadsheet, - IEnumerable objs, + IEnumerable objs, CancellationToken token) { if (spreadsheet is null) @@ -61,29 +71,31 @@ private static ValueTask AddRangeAsRowsAsync(SpreadCheetah.Spreadsheet spreadshe } private static async ValueTask AddAsRowInternalAsync(SpreadCheetah.Spreadsheet spreadsheet, - MyNamespace.ClassWithDuplicateColumnOrdering obj, + MyNamespace.ClassPropertyWithConverterAndCellStyle obj, CancellationToken token) { - var cells = ArrayPool.Shared.Rent(1); + var cells = ArrayPool.Shared.Rent(1); try { - var styleIds = Array.Empty(); + var worksheetRowDependencyInfo = spreadsheet.GetOrCreateWorksheetRowDependencyInfo(Default.ClassPropertyWithConverterAndCellStyle); + var styleIds = worksheetRowDependencyInfo.StyleIds; await AddCellsAsRowAsync(spreadsheet, obj, cells, styleIds, token).ConfigureAwait(false); } finally { - ArrayPool.Shared.Return(cells, true); + ArrayPool.Shared.Return(cells, true); } } private static async ValueTask AddRangeAsRowsInternalAsync(SpreadCheetah.Spreadsheet spreadsheet, - IEnumerable objs, + IEnumerable objs, CancellationToken token) { - var cells = ArrayPool.Shared.Rent(1); + var cells = ArrayPool.Shared.Rent(1); try { - var styleIds = Array.Empty(); + var worksheetRowDependencyInfo = spreadsheet.GetOrCreateWorksheetRowDependencyInfo(Default.ClassPropertyWithConverterAndCellStyle); + var styleIds = worksheetRowDependencyInfo.StyleIds; foreach (var obj in objs) { await AddCellsAsRowAsync(spreadsheet, obj, cells, styleIds, token).ConfigureAwait(false); @@ -91,18 +103,18 @@ private static async ValueTask AddRangeAsRowsInternalAsync(SpreadCheetah.Spreads } finally { - ArrayPool.Shared.Return(cells, true); + ArrayPool.Shared.Return(cells, true); } } private static ValueTask AddCellsAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, - MyNamespace.ClassWithDuplicateColumnOrdering? obj, - DataCell[] cells, IReadOnlyList styleIds, CancellationToken token) + MyNamespace.ClassPropertyWithConverterAndCellStyle? obj, + StyledCell[] cells, IReadOnlyList styleIds, CancellationToken token) { if (obj is null) - return spreadsheet.AddRowAsync(ReadOnlyMemory.Empty, token); + return spreadsheet.AddRowAsync(ReadOnlyMemory.Empty, token); - cells[0] = new DataCell(obj.PropertyA); + cells[0] = new StyledCell(_valueConverter0.ConvertToDataCell(obj.Property), styleIds[0]); return spreadsheet.AddRowAsync(cells.AsMemory(0, 1), token); } diff --git a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassPropertyWithConverterAndCellValueTruncate.verified.txt b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassPropertyWithConverterAndCellValueTruncate.verified.txt new file mode 100644 index 0000000..5e0db99 --- /dev/null +++ b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassPropertyWithConverterAndCellValueTruncate.verified.txt @@ -0,0 +1,14 @@ +{ + Diagnostics: [ + { + Id: SPCH1008, + Title: Attribute combination not supported, + Severity: Error, + WarningLevel: 0, + Location: : (7,19)-(7,27), + MessageFormat: Having both the {0} and the {1} attributes on a property is not supported, + Message: Having both the CellValueConverter and the CellValueTruncate attributes on a property is not supported, + Category: SpreadCheetah.SourceGenerator + } + ] +} \ No newline at end of file diff --git a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassWithConverterThatDoesNotInheritCellValueConverter.verified.txt b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassWithConverterThatDoesNotInheritCellValueConverter.verified.txt new file mode 100644 index 0000000..7756c10 --- /dev/null +++ b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassWithConverterThatDoesNotInheritCellValueConverter.verified.txt @@ -0,0 +1,14 @@ +{ + Diagnostics: [ + { + Id: SPCH1007, + Title: Invalid attribute type argument, + Severity: Error, + WarningLevel: 0, + Location: : (5,5)-(5,54), + MessageFormat: Type '{0}' is an invalid argument for {1} because it does not inherit {2}, + Message: Type 'MyNamespace.DecimalValueConverter' is an invalid argument for CellValueConverterAttribute because it does not inherit CellValueConverter, + Category: SpreadCheetah.SourceGenerator + } + ] +} \ No newline at end of file diff --git a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassWithInvalidConverter.verified.txt b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassWithInvalidConverter.verified.txt new file mode 100644 index 0000000..b8648df --- /dev/null +++ b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassWithInvalidConverter.verified.txt @@ -0,0 +1,14 @@ +{ + Diagnostics: [ + { + Id: SPCH1007, + Title: Invalid attribute type argument, + Severity: Error, + WarningLevel: 0, + Location: : (5,5)-(5,54), + MessageFormat: Type '{0}' is an invalid argument for {1} because it does not inherit {2}, + Message: Type 'MyNamespace.DecimalValueConverter' is an invalid argument for CellValueConverterAttribute because it does not inherit CellValueConverter, + Category: SpreadCheetah.SourceGenerator + } + ] +} \ No newline at end of file diff --git a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithDuplicateColumnOrderingAcrossInheritance#MyNamespace.MyGenRowContext.g.verified.cs b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassWithMultipleConverters#MyNamespace.MyGenRowContext.g.verified.cs similarity index 67% rename from SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithDuplicateColumnOrderingAcrossInheritance#MyNamespace.MyGenRowContext.g.verified.cs rename to SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassWithMultipleConverters#MyNamespace.MyGenRowContext.g.verified.cs index 6ac93e1..745941b 100644 --- a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithDuplicateColumnOrderingAcrossInheritance#MyNamespace.MyGenRowContext.g.verified.cs +++ b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassWithMultipleConverters#MyNamespace.MyGenRowContext.g.verified.cs @@ -20,19 +20,24 @@ public partial class MyGenRowContext public MyGenRowContext() { } + private static readonly MyNamespace.StringValueConverter _valueConverter0 = new MyNamespace.StringValueConverter(); + private static readonly MyNamespace.NullableIntValueConverter _valueConverter1 = new MyNamespace.NullableIntValueConverter(); + private static readonly MyNamespace.DecimalValueConverter _valueConverter2 = new MyNamespace.DecimalValueConverter(); - private WorksheetRowTypeInfo? _ClassWithDuplicateColumnOrdering; - public WorksheetRowTypeInfo ClassWithDuplicateColumnOrdering => _ClassWithDuplicateColumnOrdering - ??= WorksheetRowMetadataServices.CreateObjectInfo( + private WorksheetRowTypeInfo? _ClassWithMultipleConverters; + public WorksheetRowTypeInfo ClassWithMultipleConverters => _ClassWithMultipleConverters + ??= WorksheetRowMetadataServices.CreateObjectInfo( AddHeaderRow0Async, AddAsRowAsync, AddRangeAsRowsAsync, null); private static async ValueTask AddHeaderRow0Async(SpreadCheetah.Spreadsheet spreadsheet, SpreadCheetah.Styling.StyleId? styleId, CancellationToken token) { - var cells = ArrayPool.Shared.Rent(1); + var cells = ArrayPool.Shared.Rent(3); try { - cells[0] = new StyledCell("PropertyB", styleId); - await spreadsheet.AddRowAsync(cells.AsMemory(0, 1), token).ConfigureAwait(false); + cells[0] = new StyledCell("Property", styleId); + cells[1] = new StyledCell("Property1", styleId); + cells[2] = new StyledCell("Property2", styleId); + await spreadsheet.AddRowAsync(cells.AsMemory(0, 3), token).ConfigureAwait(false); } finally { @@ -40,7 +45,7 @@ private static async ValueTask AddHeaderRow0Async(SpreadCheetah.Spreadsheet spre } } - private static ValueTask AddAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, MyNamespace.ClassWithDuplicateColumnOrdering? obj, CancellationToken token) + private static ValueTask AddAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, MyNamespace.ClassWithMultipleConverters? obj, CancellationToken token) { if (spreadsheet is null) throw new ArgumentNullException(nameof(spreadsheet)); @@ -50,7 +55,7 @@ private static ValueTask AddAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, My } private static ValueTask AddRangeAsRowsAsync(SpreadCheetah.Spreadsheet spreadsheet, - IEnumerable objs, + IEnumerable objs, CancellationToken token) { if (spreadsheet is null) @@ -61,10 +66,10 @@ private static ValueTask AddRangeAsRowsAsync(SpreadCheetah.Spreadsheet spreadshe } private static async ValueTask AddAsRowInternalAsync(SpreadCheetah.Spreadsheet spreadsheet, - MyNamespace.ClassWithDuplicateColumnOrdering obj, + MyNamespace.ClassWithMultipleConverters obj, CancellationToken token) { - var cells = ArrayPool.Shared.Rent(1); + var cells = ArrayPool.Shared.Rent(3); try { var styleIds = Array.Empty(); @@ -77,10 +82,10 @@ private static async ValueTask AddAsRowInternalAsync(SpreadCheetah.Spreadsheet s } private static async ValueTask AddRangeAsRowsInternalAsync(SpreadCheetah.Spreadsheet spreadsheet, - IEnumerable objs, + IEnumerable objs, CancellationToken token) { - var cells = ArrayPool.Shared.Rent(1); + var cells = ArrayPool.Shared.Rent(3); try { var styleIds = Array.Empty(); @@ -96,14 +101,16 @@ private static async ValueTask AddRangeAsRowsInternalAsync(SpreadCheetah.Spreads } private static ValueTask AddCellsAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, - MyNamespace.ClassWithDuplicateColumnOrdering? obj, + MyNamespace.ClassWithMultipleConverters? obj, DataCell[] cells, IReadOnlyList styleIds, CancellationToken token) { if (obj is null) return spreadsheet.AddRowAsync(ReadOnlyMemory.Empty, token); - cells[0] = new DataCell(obj.PropertyB); - return spreadsheet.AddRowAsync(cells.AsMemory(0, 1), token); + cells[0] = _valueConverter0.ConvertToDataCell(obj.Property); + cells[1] = _valueConverter1.ConvertToDataCell(obj.Property1); + cells[2] = _valueConverter2.ConvertToDataCell(obj.Property2); + return spreadsheet.AddRowAsync(cells.AsMemory(0, 3), token); } private static DataCell ConstructTruncatedDataCell(string? value, int truncateLength) diff --git a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithColumnWidthWithInvalidWidth#MyNamespace.MyGenRowContext.g.verified.cs b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassWithReusedConverter#MyNamespace.MyGenRowContext.g.verified.cs similarity index 69% rename from SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithColumnWidthWithInvalidWidth#MyNamespace.MyGenRowContext.g.verified.cs rename to SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassWithReusedConverter#MyNamespace.MyGenRowContext.g.verified.cs index 62358e0..5c87bed 100644 --- a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithColumnWidthWithInvalidWidth#MyNamespace.MyGenRowContext.g.verified.cs +++ b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassWithReusedConverter#MyNamespace.MyGenRowContext.g.verified.cs @@ -20,19 +20,23 @@ public partial class MyGenRowContext public MyGenRowContext() { } + private static readonly MyNamespace.StringValueConverter _valueConverter0 = new MyNamespace.StringValueConverter(); + private static readonly MyNamespace.DecimalValueConverter _valueConverter1 = new MyNamespace.DecimalValueConverter(); - private WorksheetRowTypeInfo? _ClassWithColumnWidth; - public WorksheetRowTypeInfo ClassWithColumnWidth => _ClassWithColumnWidth - ??= WorksheetRowMetadataServices.CreateObjectInfo( + private WorksheetRowTypeInfo? _ClassWithReusedConverter; + public WorksheetRowTypeInfo ClassWithReusedConverter => _ClassWithReusedConverter + ??= WorksheetRowMetadataServices.CreateObjectInfo( AddHeaderRow0Async, AddAsRowAsync, AddRangeAsRowsAsync, null); private static async ValueTask AddHeaderRow0Async(SpreadCheetah.Spreadsheet spreadsheet, SpreadCheetah.Styling.StyleId? styleId, CancellationToken token) { - var cells = ArrayPool.Shared.Rent(1); + var cells = ArrayPool.Shared.Rent(3); try { - cells[0] = new StyledCell("Name", styleId); - await spreadsheet.AddRowAsync(cells.AsMemory(0, 1), token).ConfigureAwait(false); + cells[0] = new StyledCell("Property1", styleId); + cells[1] = new StyledCell("Property2", styleId); + cells[2] = new StyledCell("Property3", styleId); + await spreadsheet.AddRowAsync(cells.AsMemory(0, 3), token).ConfigureAwait(false); } finally { @@ -40,7 +44,7 @@ private static async ValueTask AddHeaderRow0Async(SpreadCheetah.Spreadsheet spre } } - private static ValueTask AddAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, MyNamespace.ClassWithColumnWidth? obj, CancellationToken token) + private static ValueTask AddAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, MyNamespace.ClassWithReusedConverter? obj, CancellationToken token) { if (spreadsheet is null) throw new ArgumentNullException(nameof(spreadsheet)); @@ -50,7 +54,7 @@ private static ValueTask AddAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, My } private static ValueTask AddRangeAsRowsAsync(SpreadCheetah.Spreadsheet spreadsheet, - IEnumerable objs, + IEnumerable objs, CancellationToken token) { if (spreadsheet is null) @@ -61,10 +65,10 @@ private static ValueTask AddRangeAsRowsAsync(SpreadCheetah.Spreadsheet spreadshe } private static async ValueTask AddAsRowInternalAsync(SpreadCheetah.Spreadsheet spreadsheet, - MyNamespace.ClassWithColumnWidth obj, + MyNamespace.ClassWithReusedConverter obj, CancellationToken token) { - var cells = ArrayPool.Shared.Rent(1); + var cells = ArrayPool.Shared.Rent(3); try { var styleIds = Array.Empty(); @@ -77,10 +81,10 @@ private static async ValueTask AddAsRowInternalAsync(SpreadCheetah.Spreadsheet s } private static async ValueTask AddRangeAsRowsInternalAsync(SpreadCheetah.Spreadsheet spreadsheet, - IEnumerable objs, + IEnumerable objs, CancellationToken token) { - var cells = ArrayPool.Shared.Rent(1); + var cells = ArrayPool.Shared.Rent(3); try { var styleIds = Array.Empty(); @@ -96,14 +100,16 @@ private static async ValueTask AddRangeAsRowsInternalAsync(SpreadCheetah.Spreads } private static ValueTask AddCellsAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, - MyNamespace.ClassWithColumnWidth? obj, + MyNamespace.ClassWithReusedConverter? obj, DataCell[] cells, IReadOnlyList styleIds, CancellationToken token) { if (obj is null) return spreadsheet.AddRowAsync(ReadOnlyMemory.Empty, token); - cells[0] = new DataCell(obj.Name); - return spreadsheet.AddRowAsync(cells.AsMemory(0, 1), token); + cells[0] = _valueConverter0.ConvertToDataCell(obj.Property1); + cells[1] = _valueConverter1.ConvertToDataCell(obj.Property2); + cells[2] = _valueConverter0.ConvertToDataCell(obj.Property3); + return spreadsheet.AddRowAsync(cells.AsMemory(0, 3), token); } private static DataCell ConstructTruncatedDataCell(string? value, int truncateLength) diff --git a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassWithoutParameterlessConstructor.verified.txt b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassWithoutParameterlessConstructor.verified.txt new file mode 100644 index 0000000..299c032 --- /dev/null +++ b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_ClassWithoutParameterlessConstructor.verified.txt @@ -0,0 +1,14 @@ +{ + Diagnostics: [ + { + Id: SPCH1009, + Title: Type must have a public parameterless constructor, + Severity: Error, + WarningLevel: 0, + Location: : (5,5)-(5,52), + MessageFormat: Type '{0}' must have a public parameterless constructor, + Message: Type 'MyNamespace.FixedValueConverter' must have a public parameterless constructor, + Category: SpreadCheetah.SourceGenerator + } + ] +} \ No newline at end of file diff --git a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_TwoClassesUsingTheSameConverter#MyNamespace.MyGenRowContext.g.verified.cs b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_TwoClassesUsingTheSameConverter#MyNamespace.MyGenRowContext.g.verified.cs new file mode 100644 index 0000000..8f169a5 --- /dev/null +++ b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.CellValueConverter_TwoClassesUsingTheSameConverter#MyNamespace.MyGenRowContext.g.verified.cs @@ -0,0 +1,202 @@ +//HintName: MyNamespace.MyGenRowContext.g.cs +// +#nullable enable +using SpreadCheetah; +using SpreadCheetah.SourceGeneration; +using SpreadCheetah.Styling; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MyNamespace +{ + public partial class MyGenRowContext + { + private static MyGenRowContext? _default; + public static MyGenRowContext Default => _default ??= new MyGenRowContext(); + + public MyGenRowContext() + { + } + private static readonly MyNamespace.StringValueConverter _valueConverter0 = new MyNamespace.StringValueConverter(); + + private WorksheetRowTypeInfo? _Person; + public WorksheetRowTypeInfo Person => _Person + ??= WorksheetRowMetadataServices.CreateObjectInfo( + AddHeaderRow0Async, AddAsRowAsync, AddRangeAsRowsAsync, null); + + private static async ValueTask AddHeaderRow0Async(SpreadCheetah.Spreadsheet spreadsheet, SpreadCheetah.Styling.StyleId? styleId, CancellationToken token) + { + var cells = ArrayPool.Shared.Rent(1); + try + { + cells[0] = new StyledCell("Name", styleId); + await spreadsheet.AddRowAsync(cells.AsMemory(0, 1), token).ConfigureAwait(false); + } + finally + { + ArrayPool.Shared.Return(cells, true); + } + } + + private static ValueTask AddAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, MyNamespace.Person? obj, CancellationToken token) + { + if (spreadsheet is null) + throw new ArgumentNullException(nameof(spreadsheet)); + if (obj is null) + return spreadsheet.AddRowAsync(ReadOnlyMemory.Empty, token); + return AddAsRowInternalAsync(spreadsheet, obj, token); + } + + private static ValueTask AddRangeAsRowsAsync(SpreadCheetah.Spreadsheet spreadsheet, + IEnumerable objs, + CancellationToken token) + { + if (spreadsheet is null) + throw new ArgumentNullException(nameof(spreadsheet)); + if (objs is null) + throw new ArgumentNullException(nameof(objs)); + return AddRangeAsRowsInternalAsync(spreadsheet, objs, token); + } + + private static async ValueTask AddAsRowInternalAsync(SpreadCheetah.Spreadsheet spreadsheet, + MyNamespace.Person obj, + CancellationToken token) + { + var cells = ArrayPool.Shared.Rent(1); + try + { + var styleIds = Array.Empty(); + await AddCellsAsRowAsync(spreadsheet, obj, cells, styleIds, token).ConfigureAwait(false); + } + finally + { + ArrayPool.Shared.Return(cells, true); + } + } + + private static async ValueTask AddRangeAsRowsInternalAsync(SpreadCheetah.Spreadsheet spreadsheet, + IEnumerable objs, + CancellationToken token) + { + var cells = ArrayPool.Shared.Rent(1); + try + { + var styleIds = Array.Empty(); + foreach (var obj in objs) + { + await AddCellsAsRowAsync(spreadsheet, obj, cells, styleIds, token).ConfigureAwait(false); + } + } + finally + { + ArrayPool.Shared.Return(cells, true); + } + } + + private static ValueTask AddCellsAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, + MyNamespace.Person? obj, + DataCell[] cells, IReadOnlyList styleIds, CancellationToken token) + { + if (obj is null) + return spreadsheet.AddRowAsync(ReadOnlyMemory.Empty, token); + + cells[0] = _valueConverter0.ConvertToDataCell(obj.Name); + return spreadsheet.AddRowAsync(cells.AsMemory(0, 1), token); + } + + private WorksheetRowTypeInfo? _Car; + public WorksheetRowTypeInfo Car => _Car + ??= WorksheetRowMetadataServices.CreateObjectInfo( + AddHeaderRow1Async, AddAsRowAsync, AddRangeAsRowsAsync, null); + + private static async ValueTask AddHeaderRow1Async(SpreadCheetah.Spreadsheet spreadsheet, SpreadCheetah.Styling.StyleId? styleId, CancellationToken token) + { + var cells = ArrayPool.Shared.Rent(1); + try + { + cells[0] = new StyledCell("Model", styleId); + await spreadsheet.AddRowAsync(cells.AsMemory(0, 1), token).ConfigureAwait(false); + } + finally + { + ArrayPool.Shared.Return(cells, true); + } + } + + private static ValueTask AddAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, MyNamespace.Car? obj, CancellationToken token) + { + if (spreadsheet is null) + throw new ArgumentNullException(nameof(spreadsheet)); + if (obj is null) + return spreadsheet.AddRowAsync(ReadOnlyMemory.Empty, token); + return AddAsRowInternalAsync(spreadsheet, obj, token); + } + + private static ValueTask AddRangeAsRowsAsync(SpreadCheetah.Spreadsheet spreadsheet, + IEnumerable objs, + CancellationToken token) + { + if (spreadsheet is null) + throw new ArgumentNullException(nameof(spreadsheet)); + if (objs is null) + throw new ArgumentNullException(nameof(objs)); + return AddRangeAsRowsInternalAsync(spreadsheet, objs, token); + } + + private static async ValueTask AddAsRowInternalAsync(SpreadCheetah.Spreadsheet spreadsheet, + MyNamespace.Car obj, + CancellationToken token) + { + var cells = ArrayPool.Shared.Rent(1); + try + { + var styleIds = Array.Empty(); + await AddCellsAsRowAsync(spreadsheet, obj, cells, styleIds, token).ConfigureAwait(false); + } + finally + { + ArrayPool.Shared.Return(cells, true); + } + } + + private static async ValueTask AddRangeAsRowsInternalAsync(SpreadCheetah.Spreadsheet spreadsheet, + IEnumerable objs, + CancellationToken token) + { + var cells = ArrayPool.Shared.Rent(1); + try + { + var styleIds = Array.Empty(); + foreach (var obj in objs) + { + await AddCellsAsRowAsync(spreadsheet, obj, cells, styleIds, token).ConfigureAwait(false); + } + } + finally + { + ArrayPool.Shared.Return(cells, true); + } + } + + private static ValueTask AddCellsAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, + MyNamespace.Car? obj, + DataCell[] cells, IReadOnlyList styleIds, CancellationToken token) + { + if (obj is null) + return spreadsheet.AddRowAsync(ReadOnlyMemory.Empty, token); + + cells[0] = _valueConverter0.ConvertToDataCell(obj.Model); + return spreadsheet.AddRowAsync(cells.AsMemory(0, 1), token); + } + + private static DataCell ConstructTruncatedDataCell(string? value, int truncateLength) + { + return value is null || value.Length <= truncateLength + ? new DataCell(value) + : new DataCell(value.AsMemory(0, truncateLength)); + } + } +} diff --git a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithCellValueTruncateOnInvalidType#MyNamespace.MyGenRowContext.g.verified.cs b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithCellValueTruncateOnInvalidType#MyNamespace.MyGenRowContext.g.verified.cs deleted file mode 100644 index 1362233..0000000 --- a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithCellValueTruncateOnInvalidType#MyNamespace.MyGenRowContext.g.verified.cs +++ /dev/null @@ -1,116 +0,0 @@ -//HintName: MyNamespace.MyGenRowContext.g.cs -// -#nullable enable -using SpreadCheetah; -using SpreadCheetah.SourceGeneration; -using SpreadCheetah.Styling; -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace MyNamespace -{ - public partial class MyGenRowContext - { - private static MyGenRowContext? _default; - public static MyGenRowContext Default => _default ??= new MyGenRowContext(); - - public MyGenRowContext() - { - } - - private WorksheetRowTypeInfo? _ClassWithCellValueTruncate; - public WorksheetRowTypeInfo ClassWithCellValueTruncate => _ClassWithCellValueTruncate - ??= WorksheetRowMetadataServices.CreateObjectInfo( - AddHeaderRow0Async, AddAsRowAsync, AddRangeAsRowsAsync, null); - - private static async ValueTask AddHeaderRow0Async(SpreadCheetah.Spreadsheet spreadsheet, SpreadCheetah.Styling.StyleId? styleId, CancellationToken token) - { - var cells = ArrayPool.Shared.Rent(1); - try - { - cells[0] = new StyledCell("Year", styleId); - await spreadsheet.AddRowAsync(cells.AsMemory(0, 1), token).ConfigureAwait(false); - } - finally - { - ArrayPool.Shared.Return(cells, true); - } - } - - private static ValueTask AddAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, MyNamespace.ClassWithCellValueTruncate? obj, CancellationToken token) - { - if (spreadsheet is null) - throw new ArgumentNullException(nameof(spreadsheet)); - if (obj is null) - return spreadsheet.AddRowAsync(ReadOnlyMemory.Empty, token); - return AddAsRowInternalAsync(spreadsheet, obj, token); - } - - private static ValueTask AddRangeAsRowsAsync(SpreadCheetah.Spreadsheet spreadsheet, - IEnumerable objs, - CancellationToken token) - { - if (spreadsheet is null) - throw new ArgumentNullException(nameof(spreadsheet)); - if (objs is null) - throw new ArgumentNullException(nameof(objs)); - return AddRangeAsRowsInternalAsync(spreadsheet, objs, token); - } - - private static async ValueTask AddAsRowInternalAsync(SpreadCheetah.Spreadsheet spreadsheet, - MyNamespace.ClassWithCellValueTruncate obj, - CancellationToken token) - { - var cells = ArrayPool.Shared.Rent(1); - try - { - var styleIds = Array.Empty(); - await AddCellsAsRowAsync(spreadsheet, obj, cells, styleIds, token).ConfigureAwait(false); - } - finally - { - ArrayPool.Shared.Return(cells, true); - } - } - - private static async ValueTask AddRangeAsRowsInternalAsync(SpreadCheetah.Spreadsheet spreadsheet, - IEnumerable objs, - CancellationToken token) - { - var cells = ArrayPool.Shared.Rent(1); - try - { - var styleIds = Array.Empty(); - foreach (var obj in objs) - { - await AddCellsAsRowAsync(spreadsheet, obj, cells, styleIds, token).ConfigureAwait(false); - } - } - finally - { - ArrayPool.Shared.Return(cells, true); - } - } - - private static ValueTask AddCellsAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, - MyNamespace.ClassWithCellValueTruncate? obj, - DataCell[] cells, IReadOnlyList styleIds, CancellationToken token) - { - if (obj is null) - return spreadsheet.AddRowAsync(ReadOnlyMemory.Empty, token); - - cells[0] = new DataCell(obj.Year); - return spreadsheet.AddRowAsync(cells.AsMemory(0, 1), token); - } - - private static DataCell ConstructTruncatedDataCell(string? value, int truncateLength) - { - return value is null || value.Length <= truncateLength - ? new DataCell(value) - : new DataCell(value.AsMemory(0, truncateLength)); - } - } -} diff --git a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithCellValueTruncateOnInvalidType.verified.txt b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithCellValueTruncateOnInvalidType.verified.txt index 5f9c55b..5cb5699 100644 --- a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithCellValueTruncateOnInvalidType.verified.txt +++ b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithCellValueTruncateOnInvalidType.verified.txt @@ -2,12 +2,12 @@ Diagnostics: [ { Id: SPCH1005, - Title: Unsupported type for CellValueLengthLimit attribute, + Title: Unsupported type for attribute, Severity: Error, WarningLevel: 0, Location: : (6,5)-(6,26), - MessageFormat: The CellValueLengthLimit attribute is not supported on properties of type '{0}', - Message: The CellValueLengthLimit attribute is not supported on properties of type 'int', + MessageFormat: {0} is not supported on properties of type '{1}', + Message: CellValueTruncateAttribute is not supported on properties of type 'int', Category: SpreadCheetah.SourceGenerator } ] diff --git a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithCellValueTruncateWithInvalidLength#MyNamespace.MyGenRowContext.g.verified.cs b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithCellValueTruncateWithInvalidLength#MyNamespace.MyGenRowContext.g.verified.cs deleted file mode 100644 index 661850d..0000000 --- a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithCellValueTruncateWithInvalidLength#MyNamespace.MyGenRowContext.g.verified.cs +++ /dev/null @@ -1,116 +0,0 @@ -//HintName: MyNamespace.MyGenRowContext.g.cs -// -#nullable enable -using SpreadCheetah; -using SpreadCheetah.SourceGeneration; -using SpreadCheetah.Styling; -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace MyNamespace -{ - public partial class MyGenRowContext - { - private static MyGenRowContext? _default; - public static MyGenRowContext Default => _default ??= new MyGenRowContext(); - - public MyGenRowContext() - { - } - - private WorksheetRowTypeInfo? _ClassWithCellValueTruncate; - public WorksheetRowTypeInfo ClassWithCellValueTruncate => _ClassWithCellValueTruncate - ??= WorksheetRowMetadataServices.CreateObjectInfo( - AddHeaderRow0Async, AddAsRowAsync, AddRangeAsRowsAsync, null); - - private static async ValueTask AddHeaderRow0Async(SpreadCheetah.Spreadsheet spreadsheet, SpreadCheetah.Styling.StyleId? styleId, CancellationToken token) - { - var cells = ArrayPool.Shared.Rent(1); - try - { - cells[0] = new StyledCell("Name", styleId); - await spreadsheet.AddRowAsync(cells.AsMemory(0, 1), token).ConfigureAwait(false); - } - finally - { - ArrayPool.Shared.Return(cells, true); - } - } - - private static ValueTask AddAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, MyNamespace.ClassWithCellValueTruncate? obj, CancellationToken token) - { - if (spreadsheet is null) - throw new ArgumentNullException(nameof(spreadsheet)); - if (obj is null) - return spreadsheet.AddRowAsync(ReadOnlyMemory.Empty, token); - return AddAsRowInternalAsync(spreadsheet, obj, token); - } - - private static ValueTask AddRangeAsRowsAsync(SpreadCheetah.Spreadsheet spreadsheet, - IEnumerable objs, - CancellationToken token) - { - if (spreadsheet is null) - throw new ArgumentNullException(nameof(spreadsheet)); - if (objs is null) - throw new ArgumentNullException(nameof(objs)); - return AddRangeAsRowsInternalAsync(spreadsheet, objs, token); - } - - private static async ValueTask AddAsRowInternalAsync(SpreadCheetah.Spreadsheet spreadsheet, - MyNamespace.ClassWithCellValueTruncate obj, - CancellationToken token) - { - var cells = ArrayPool.Shared.Rent(1); - try - { - var styleIds = Array.Empty(); - await AddCellsAsRowAsync(spreadsheet, obj, cells, styleIds, token).ConfigureAwait(false); - } - finally - { - ArrayPool.Shared.Return(cells, true); - } - } - - private static async ValueTask AddRangeAsRowsInternalAsync(SpreadCheetah.Spreadsheet spreadsheet, - IEnumerable objs, - CancellationToken token) - { - var cells = ArrayPool.Shared.Rent(1); - try - { - var styleIds = Array.Empty(); - foreach (var obj in objs) - { - await AddCellsAsRowAsync(spreadsheet, obj, cells, styleIds, token).ConfigureAwait(false); - } - } - finally - { - ArrayPool.Shared.Return(cells, true); - } - } - - private static ValueTask AddCellsAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, - MyNamespace.ClassWithCellValueTruncate? obj, - DataCell[] cells, IReadOnlyList styleIds, CancellationToken token) - { - if (obj is null) - return spreadsheet.AddRowAsync(ReadOnlyMemory.Empty, token); - - cells[0] = new DataCell(obj.Name); - return spreadsheet.AddRowAsync(cells.AsMemory(0, 1), token); - } - - private static DataCell ConstructTruncatedDataCell(string? value, int truncateLength) - { - return value is null || value.Length <= truncateLength - ? new DataCell(value) - : new DataCell(value.AsMemory(0, truncateLength)); - } - } -} diff --git a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithCellValueTruncateWithInvalidLength.verified.txt b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithCellValueTruncateWithInvalidLength.verified.txt index f99082d..c7952cc 100644 --- a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithCellValueTruncateWithInvalidLength.verified.txt +++ b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithCellValueTruncateWithInvalidLength.verified.txt @@ -6,8 +6,8 @@ Severity: Error, WarningLevel: 0, Location: : (6,5)-(6,25), - MessageFormat: '{0}' is an invalid argument for attribute '{1}', - Message: '0' is an invalid argument for attribute 'SpreadCheetah.SourceGeneration.CellValueTruncateAttribute', + MessageFormat: '{0}' is an invalid argument for {1}, + Message: '0' is an invalid argument for CellValueTruncateAttribute, Category: SpreadCheetah.SourceGenerator } ] diff --git a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithColumnWidthWithInvalidWidth.verified.txt b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithColumnWidthWithInvalidWidth.verified.txt index 3a28258..497de01 100644 --- a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithColumnWidthWithInvalidWidth.verified.txt +++ b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithColumnWidthWithInvalidWidth.verified.txt @@ -6,8 +6,8 @@ Severity: Error, WarningLevel: 0, Location: : (6,5)-(6,21), - MessageFormat: '{0}' is an invalid argument for attribute '{1}', - Message: '300' is an invalid argument for attribute 'SpreadCheetah.SourceGeneration.ColumnWidthAttribute', + MessageFormat: '{0}' is an invalid argument for {1}, + Message: '300' is an invalid argument for ColumnWidthAttribute, Category: SpreadCheetah.SourceGenerator } ] diff --git a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithInvalidPropertyReferenceColumnHeaders#MyNamespace.MyGenRowContext.g.verified.cs b/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithInvalidPropertyReferenceColumnHeaders#MyNamespace.MyGenRowContext.g.verified.cs deleted file mode 100644 index a614472..0000000 --- a/SpreadCheetah.SourceGenerator.SnapshotTest/Snapshots/G.WorksheetRowGenerator_Generate_ClassWithInvalidPropertyReferenceColumnHeaders#MyNamespace.MyGenRowContext.g.verified.cs +++ /dev/null @@ -1,128 +0,0 @@ -//HintName: MyNamespace.MyGenRowContext.g.cs -// -#nullable enable -using SpreadCheetah; -using SpreadCheetah.SourceGeneration; -using SpreadCheetah.Styling; -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace MyNamespace -{ - public partial class MyGenRowContext - { - private static MyGenRowContext? _default; - public static MyGenRowContext Default => _default ??= new MyGenRowContext(); - - public MyGenRowContext() - { - } - - private WorksheetRowTypeInfo? _ClassWithInvalidPropertyReferenceColumnHeaders; - public WorksheetRowTypeInfo ClassWithInvalidPropertyReferenceColumnHeaders => _ClassWithInvalidPropertyReferenceColumnHeaders - ??= WorksheetRowMetadataServices.CreateObjectInfo( - AddHeaderRow0Async, AddAsRowAsync, AddRangeAsRowsAsync, null); - - private static async ValueTask AddHeaderRow0Async(SpreadCheetah.Spreadsheet spreadsheet, SpreadCheetah.Styling.StyleId? styleId, CancellationToken token) - { - var cells = ArrayPool.Shared.Rent(7); - try - { - cells[0] = new StyledCell("PropertyA", styleId); - cells[1] = new StyledCell("PropertyB", styleId); - cells[2] = new StyledCell("PropertyC", styleId); - cells[3] = new StyledCell("PropertyD", styleId); - cells[4] = new StyledCell("PropertyE", styleId); - cells[5] = new StyledCell("PropertyF", styleId); - cells[6] = new StyledCell("PropertyG", styleId); - await spreadsheet.AddRowAsync(cells.AsMemory(0, 7), token).ConfigureAwait(false); - } - finally - { - ArrayPool.Shared.Return(cells, true); - } - } - - private static ValueTask AddAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, MyNamespace.ClassWithInvalidPropertyReferenceColumnHeaders? obj, CancellationToken token) - { - if (spreadsheet is null) - throw new ArgumentNullException(nameof(spreadsheet)); - if (obj is null) - return spreadsheet.AddRowAsync(ReadOnlyMemory.Empty, token); - return AddAsRowInternalAsync(spreadsheet, obj, token); - } - - private static ValueTask AddRangeAsRowsAsync(SpreadCheetah.Spreadsheet spreadsheet, - IEnumerable objs, - CancellationToken token) - { - if (spreadsheet is null) - throw new ArgumentNullException(nameof(spreadsheet)); - if (objs is null) - throw new ArgumentNullException(nameof(objs)); - return AddRangeAsRowsInternalAsync(spreadsheet, objs, token); - } - - private static async ValueTask AddAsRowInternalAsync(SpreadCheetah.Spreadsheet spreadsheet, - MyNamespace.ClassWithInvalidPropertyReferenceColumnHeaders obj, - CancellationToken token) - { - var cells = ArrayPool.Shared.Rent(7); - try - { - var styleIds = Array.Empty(); - await AddCellsAsRowAsync(spreadsheet, obj, cells, styleIds, token).ConfigureAwait(false); - } - finally - { - ArrayPool.Shared.Return(cells, true); - } - } - - private static async ValueTask AddRangeAsRowsInternalAsync(SpreadCheetah.Spreadsheet spreadsheet, - IEnumerable objs, - CancellationToken token) - { - var cells = ArrayPool.Shared.Rent(7); - try - { - var styleIds = Array.Empty(); - foreach (var obj in objs) - { - await AddCellsAsRowAsync(spreadsheet, obj, cells, styleIds, token).ConfigureAwait(false); - } - } - finally - { - ArrayPool.Shared.Return(cells, true); - } - } - - private static ValueTask AddCellsAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, - MyNamespace.ClassWithInvalidPropertyReferenceColumnHeaders? obj, - DataCell[] cells, IReadOnlyList styleIds, CancellationToken token) - { - if (obj is null) - return spreadsheet.AddRowAsync(ReadOnlyMemory.Empty, token); - - cells[0] = new DataCell(obj.PropertyA); - cells[1] = new DataCell(obj.PropertyB); - cells[2] = new DataCell(obj.PropertyC); - cells[3] = new DataCell(obj.PropertyD); - cells[4] = new DataCell(obj.PropertyE); - cells[5] = new DataCell(obj.PropertyF); - cells[6] = new DataCell(obj.PropertyG); - return spreadsheet.AddRowAsync(cells.AsMemory(0, 7), token); - } - - private static DataCell ConstructTruncatedDataCell(string? value, int truncateLength) - { - return value is null || value.Length <= truncateLength - ? new DataCell(value) - : new DataCell(value.AsMemory(0, truncateLength)); - } - } -} diff --git a/SpreadCheetah.SourceGenerator.SnapshotTest/Tests/CellValueConverterTests.cs b/SpreadCheetah.SourceGenerator.SnapshotTest/Tests/CellValueConverterTests.cs new file mode 100644 index 0000000..def593e --- /dev/null +++ b/SpreadCheetah.SourceGenerator.SnapshotTest/Tests/CellValueConverterTests.cs @@ -0,0 +1,259 @@ +using SpreadCheetah.SourceGenerator.SnapshotTest.Helpers; +using SpreadCheetah.SourceGenerators; + +namespace SpreadCheetah.SourceGenerator.SnapshotTest.Tests; + +public class CellValueConverterTests +{ + [Fact] + public Task CellValueConverter_ClassWithMultipleConverters() + { + // Arrange + const string source = """ + using SpreadCheetah.SourceGeneration; + + namespace MyNamespace; + public class ClassWithMultipleConverters + { + [CellValueConverter(typeof(StringValueConverter))] + public string? Property { get; set; } + + [CellValueConverter(typeof(NullableIntValueConverter))] + public int? Property1 { get; set; } + + [CellValueConverter(typeof(DecimalValueConverter))] + public decimal Property2 { get; set; } + } + + internal class StringValueConverter : CellValueConverter + { + public override DataCell ConvertToCell(string value) => new(value); + } + + internal class NullableIntValueConverter : CellValueConverter + { + public override DataCell ConvertToCell(int? value) => new(value); + } + + internal class DecimalValueConverter : CellValueConverter + { + public override DataCell ConvertToCell(decimal value) => new(value); + } + + [WorksheetRow(typeof(ClassWithMultipleConverters))] + public partial class MyGenRowContext : WorksheetRowContext; + """; + + // Act & Assert + return TestHelper.CompileAndVerify(source); + } + + [Fact] + public Task CellValueConverter_ClassWithReusedConverter() + { + // Arrange + const string source = """ + using SpreadCheetah.SourceGeneration; + + namespace MyNamespace; + public class ClassWithReusedConverter + { + [CellValueConverter(typeof(StringValueConverter))] + public string? Property1 { get; set; } + + [CellValueConverter(typeof(DecimalValueConverter))] + public decimal Property2 { get; set; } + + [CellValueConverter(typeof(StringValueConverter))] + public string? Property3 { get; set; } + } + + internal class StringValueConverter : CellValueConverter + { + public override DataCell ConvertToCell(string value) => new(value); + } + + internal class DecimalValueConverter : CellValueConverter + { + public override DataCell ConvertToCell(decimal value) => new(value); + } + + [WorksheetRow(typeof(ClassWithReusedConverter))] + public partial class MyGenRowContext : WorksheetRowContext; + """; + + // Act & Assert + return TestHelper.CompileAndVerify(source); + } + + [Fact] + public Task CellValueConverter_ClassWithInvalidConverter() + { + // Arrange + const string source = """ + using SpreadCheetah.SourceGeneration; + + namespace MyNamespace; + public class ClassWithInvalidConverter + { + [CellValueConverter(typeof(DecimalValueConverter))] + public string? Property { get; set; } + } + + internal class DecimalValueConverter : CellValueConverter + { + public override DataCell ConvertToCell(decimal value) => new(value); + } + + [WorksheetRow(typeof(ClassWithInvalidConverter))] + public partial class MyGenRowContext : WorksheetRowContext; + """; + + // Act & Assert + return TestHelper.CompileAndVerify(source, onlyDiagnostics: true); + } + + [Fact] + public Task CellValueConverter_TwoClassesUsingTheSameConverter() + { + // Arrange + const string source = """ + using SpreadCheetah.SourceGeneration; + + namespace MyNamespace; + public class Person + { + [CellValueConverter(typeof(StringValueConverter))] + public string? Name { get; set; } + } + + public class Car + { + [CellValueConverter(typeof(StringValueConverter))] + public string? Model { get; set; } + } + + internal class StringValueConverter : CellValueConverter + { + public override DataCell ConvertToCell(string value) => new(value); + } + + [WorksheetRow(typeof(Person))] + [WorksheetRow(typeof(Car))] + public partial class MyGenRowContext : WorksheetRowContext; + """; + + // Act & Assert + return TestHelper.CompileAndVerify(source); + } + + [Fact] + public Task CellValueConverter_ClassWithConverterThatDoesNotInheritCellValueConverter() + { + // Arrange + const string source = """ + using SpreadCheetah.SourceGeneration; + + namespace MyNamespace; + public class ClassWithConverterThatDoesNotInheritCellValueConverter + { + [CellValueConverter(typeof(DecimalValueConverter))] + public decimal Property { get; set; } + } + + internal class DecimalValueConverter + { + public override DataCell ConvertToCell(decimal value) => new(value); + } + + [WorksheetRow(typeof(ClassWithConverterThatDoesNotInheritCellValueConverter))] + public partial class MyGenRowContext : WorksheetRowContext; + """; + + // Act & Assert + return TestHelper.CompileAndVerify(source, onlyDiagnostics: true); + } + + [Fact] + public Task CellValueConverter_ClassPropertyWithConverterAndCellStyle() + { + // Arrange + const string source = """ + using SpreadCheetah.SourceGeneration; + + namespace MyNamespace; + public class ClassPropertyWithConverterAndCellStyle + { + [CellValueConverter(typeof(StringValueConverter))] + [CellStyle("My style")] + public string? Property { get; set; } + } + + internal class StringValueConverter : CellValueConverter + { + public override DataCell ConvertToCell(string value) => new(value); + } + + [WorksheetRow(typeof(ClassPropertyWithConverterAndCellStyle))] + public partial class MyGenRowContext : WorksheetRowContext; + """; + + // Act & Assert + return TestHelper.CompileAndVerify(source); + } + + [Fact] + public Task CellValueConverter_ClassPropertyWithConverterAndCellValueTruncate() + { + // Arrange + const string source = """ + using SpreadCheetah.SourceGeneration; + + namespace MyNamespace; + public class ClassPropertyWithConverterAndCellValueTruncate + { + [CellValueConverter(typeof(StringValueConverter))] + [CellValueTruncate(20)] + public string? Property { get; set; } + } + + internal class StringValueConverter : CellValueConverter + { + public override DataCell ConvertToCell(string value) => new(value); + } + + [WorksheetRow(typeof(ClassPropertyWithConverterAndCellValueTruncate))] + public partial class MyGenRowContext : WorksheetRowContext; + """; + + // Act & Assert + return TestHelper.CompileAndVerify(source, onlyDiagnostics: true); + } + + [Fact] + public Task CellValueConverter_ClassWithoutParameterlessConstructor() + { + // Arrange + const string source = """ + using SpreadCheetah.SourceGeneration; + + namespace MyNamespace; + public class ClassWithoutParameterlessConstructor + { + [CellValueConverter(typeof(FixedValueConverter))] + public string? Value { get; set; } + } + + internal class FixedValueConverter(string fixedValue) : CellValueConverter + { + public override DataCell ConvertToCell(string value) => new(fixedValue); + } + + [WorksheetRow(typeof(ClassWithoutParameterlessConstructor))] + public partial class MyGenRowContext : WorksheetRowContext; + """; + + // Act & Assert + return TestHelper.CompileAndVerify(source, onlyDiagnostics: true); + } +} \ No newline at end of file diff --git a/SpreadCheetah.SourceGenerator.SnapshotTest/Tests/WorksheetRowGeneratorCellValueTruncateTests.cs b/SpreadCheetah.SourceGenerator.SnapshotTest/Tests/WorksheetRowGeneratorCellValueTruncateTests.cs index b9a0dc2..01d1d3b 100644 --- a/SpreadCheetah.SourceGenerator.SnapshotTest/Tests/WorksheetRowGeneratorCellValueTruncateTests.cs +++ b/SpreadCheetah.SourceGenerator.SnapshotTest/Tests/WorksheetRowGeneratorCellValueTruncateTests.cs @@ -79,7 +79,7 @@ public partial class MyGenRowContext : WorksheetRowContext; """; // Act & Assert - return TestHelper.CompileAndVerify(source); + return TestHelper.CompileAndVerify(source, onlyDiagnostics: true); } [Fact] @@ -102,6 +102,6 @@ public partial class MyGenRowContext : WorksheetRowContext; """; // Act & Assert - return TestHelper.CompileAndVerify(source); + return TestHelper.CompileAndVerify(source, onlyDiagnostics: true); } } diff --git a/SpreadCheetah.SourceGenerator.SnapshotTest/Tests/WorksheetRowGeneratorColumnHeaderTests.cs b/SpreadCheetah.SourceGenerator.SnapshotTest/Tests/WorksheetRowGeneratorColumnHeaderTests.cs index 35af3b4..550e895 100644 --- a/SpreadCheetah.SourceGenerator.SnapshotTest/Tests/WorksheetRowGeneratorColumnHeaderTests.cs +++ b/SpreadCheetah.SourceGenerator.SnapshotTest/Tests/WorksheetRowGeneratorColumnHeaderTests.cs @@ -101,6 +101,6 @@ public partial class MyGenRowContext : WorksheetRowContext; """; // Act & Assert - return TestHelper.CompileAndVerify(source); + return TestHelper.CompileAndVerify(source, onlyDiagnostics: true); } } diff --git a/SpreadCheetah.SourceGenerator.SnapshotTest/Tests/WorksheetRowGeneratorColumnOrderTests.cs b/SpreadCheetah.SourceGenerator.SnapshotTest/Tests/WorksheetRowGeneratorColumnOrderTests.cs index 58db71a..8da2759 100644 --- a/SpreadCheetah.SourceGenerator.SnapshotTest/Tests/WorksheetRowGeneratorColumnOrderTests.cs +++ b/SpreadCheetah.SourceGenerator.SnapshotTest/Tests/WorksheetRowGeneratorColumnOrderTests.cs @@ -63,7 +63,7 @@ public partial class MyGenRowContext : WorksheetRowContext; """; // Act & Assert - return TestHelper.CompileAndVerify(source); + return TestHelper.CompileAndVerify(source, onlyDiagnostics: true); } [Fact] @@ -93,6 +93,6 @@ public partial class MyGenRowContext : WorksheetRowContext; """; // Act & Assert - return TestHelper.CompileAndVerify(source); + return TestHelper.CompileAndVerify(source, onlyDiagnostics: true); } } diff --git a/SpreadCheetah.SourceGenerator.SnapshotTest/Tests/WorksheetRowGeneratorColumnWidthTests.cs b/SpreadCheetah.SourceGenerator.SnapshotTest/Tests/WorksheetRowGeneratorColumnWidthTests.cs index bec6a9a..e5e87e7 100644 --- a/SpreadCheetah.SourceGenerator.SnapshotTest/Tests/WorksheetRowGeneratorColumnWidthTests.cs +++ b/SpreadCheetah.SourceGenerator.SnapshotTest/Tests/WorksheetRowGeneratorColumnWidthTests.cs @@ -109,6 +109,6 @@ public partial class MyGenRowContext : WorksheetRowContext; """; // Act & Assert - return TestHelper.CompileAndVerify(source); + return TestHelper.CompileAndVerify(source, onlyDiagnostics: true); } } diff --git a/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/CellValueConverterContext.cs b/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/CellValueConverterContext.cs new file mode 100644 index 0000000..3f02a39 --- /dev/null +++ b/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/CellValueConverterContext.cs @@ -0,0 +1,9 @@ +using SpreadCheetah.SourceGeneration; + +namespace SpreadCheetah.SourceGenerator.Test.Models.CellValueConverter; + +[WorksheetRow(typeof(ClassWithCellValueConverter))] +[WorksheetRow(typeof(ClassWithCellValueConverterAndCellStyle))] +[WorksheetRow(typeof(ClassWithGenericConverter))] +[WorksheetRow(typeof(ClassWithReusedConverter))] +internal partial class CellValueConverterContext : WorksheetRowContext; \ No newline at end of file diff --git a/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/ClassWithCellValueConverter.cs b/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/ClassWithCellValueConverter.cs new file mode 100644 index 0000000..a9c0876 --- /dev/null +++ b/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/ClassWithCellValueConverter.cs @@ -0,0 +1,9 @@ +using SpreadCheetah.SourceGeneration; + +namespace SpreadCheetah.SourceGenerator.Test.Models.CellValueConverter; + +internal class ClassWithCellValueConverter +{ + [CellValueConverter(typeof(UpperCaseValueConverter))] + public string? Name { get; set; } +} diff --git a/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/ClassWithCellValueConverterAndCellStyle.cs b/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/ClassWithCellValueConverterAndCellStyle.cs new file mode 100644 index 0000000..3da92de --- /dev/null +++ b/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/ClassWithCellValueConverterAndCellStyle.cs @@ -0,0 +1,10 @@ +using SpreadCheetah.SourceGeneration; + +namespace SpreadCheetah.SourceGenerator.Test.Models.CellValueConverter; + +internal class ClassWithCellValueConverterAndCellStyle +{ + [CellStyle("ID style")] + [CellValueConverter(typeof(UpperCaseValueConverter))] + public required string Id { get; set; } +} diff --git a/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/ClassWithGenericConverter.cs b/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/ClassWithGenericConverter.cs new file mode 100644 index 0000000..dd7c151 --- /dev/null +++ b/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/ClassWithGenericConverter.cs @@ -0,0 +1,16 @@ +using SpreadCheetah.SourceGeneration; + +namespace SpreadCheetah.SourceGenerator.Test.Models.CellValueConverter; + +internal class ClassWithGenericConverter +{ + public required string FirstName { get; init; } + + [CellValueConverter(typeof(NullToDashValueConverter))] + public required string? MiddleName { get; init; } + + public required string LastName { get; init; } + + [CellValueConverter(typeof(NullToDashValueConverter))] + public required decimal? Gpa { get; init; } +} diff --git a/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/ClassWithReusedConverter.cs b/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/ClassWithReusedConverter.cs new file mode 100644 index 0000000..395cb09 --- /dev/null +++ b/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/ClassWithReusedConverter.cs @@ -0,0 +1,16 @@ +using SpreadCheetah.SourceGeneration; + +namespace SpreadCheetah.SourceGenerator.Test.Models.CellValueConverter; + +internal class ClassWithReusedConverter +{ + [CellValueConverter(typeof(UpperCaseValueConverter))] + public required string FirstName { get; init; } + + public required string? MiddleName { get; init; } + + [CellValueConverter(typeof(UpperCaseValueConverter))] + public required string LastName { get; init; } + + public required decimal? Gpa { get; init; } +} diff --git a/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/NullToDashValueConverter.cs b/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/NullToDashValueConverter.cs new file mode 100644 index 0000000..a8bc5b5 --- /dev/null +++ b/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/NullToDashValueConverter.cs @@ -0,0 +1,11 @@ +using SpreadCheetah.SourceGeneration; + +namespace SpreadCheetah.SourceGenerator.Test.Models.CellValueConverter; + +internal sealed class NullToDashValueConverter : CellValueConverter +{ + public override DataCell ConvertToDataCell(T? value) + { + return value is null ? new("-") : new(value.ToString()); + } +} diff --git a/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/UpperCaseValueConverter.cs b/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/UpperCaseValueConverter.cs new file mode 100644 index 0000000..b5d98a6 --- /dev/null +++ b/SpreadCheetah.SourceGenerator.Test/Models/CellValueConverter/UpperCaseValueConverter.cs @@ -0,0 +1,8 @@ +using SpreadCheetah.SourceGeneration; + +namespace SpreadCheetah.SourceGenerator.Test.Models.CellValueConverter; + +public class UpperCaseValueConverter : CellValueConverter +{ + public override DataCell ConvertToDataCell(string? value) => new(value?.ToUpperInvariant()); +} diff --git a/SpreadCheetah.SourceGenerator.Test/Models/Combinations/ClassWithColumnAttributes.cs b/SpreadCheetah.SourceGenerator.Test/Models/Combinations/ClassWithColumnAttributes.cs index b58b720..d3b59b0 100644 --- a/SpreadCheetah.SourceGenerator.Test/Models/Combinations/ClassWithColumnAttributes.cs +++ b/SpreadCheetah.SourceGenerator.Test/Models/Combinations/ClassWithColumnAttributes.cs @@ -17,6 +17,7 @@ public class ClassWithColumnAttributes(string id, string countryOfOrigin, string [ColumnOrder(1)] [ColumnWidth(10)] [CellStyle("Year style")] + [CellValueConverter(typeof(LeapYearValueConverter))] public int Year { get; } = year; #pragma warning disable IDE1006 // Naming Styles @@ -24,6 +25,7 @@ public class ClassWithColumnAttributes(string id, string countryOfOrigin, string #pragma warning restore IDE1006 // Naming Styles [ColumnHeader(typeof(ColumnHeaderResources), nameof(ColumnHeaderResources.Header_Length))] + [CellValueConverter(typeof(LengthInCmValueConverter))] public decimal Length { get; } = length; } diff --git a/SpreadCheetah.SourceGenerator.Test/Models/Combinations/LeapYearValueConverter.cs b/SpreadCheetah.SourceGenerator.Test/Models/Combinations/LeapYearValueConverter.cs new file mode 100644 index 0000000..b7b3694 --- /dev/null +++ b/SpreadCheetah.SourceGenerator.Test/Models/Combinations/LeapYearValueConverter.cs @@ -0,0 +1,13 @@ +using SpreadCheetah.SourceGeneration; + +namespace SpreadCheetah.SourceGenerator.Test.Models.Combinations; + +public class LeapYearValueConverter : CellValueConverter +{ + public override DataCell ConvertToDataCell(int value) + { + return DateTime.IsLeapYear(value) + ? new DataCell($"{value} (leap year)") + : new DataCell(value); + } +} \ No newline at end of file diff --git a/SpreadCheetah.SourceGenerator.Test/Models/Combinations/LengthInCmValueConverter.cs b/SpreadCheetah.SourceGenerator.Test/Models/Combinations/LengthInCmValueConverter.cs new file mode 100644 index 0000000..4f1361c --- /dev/null +++ b/SpreadCheetah.SourceGenerator.Test/Models/Combinations/LengthInCmValueConverter.cs @@ -0,0 +1,8 @@ +using SpreadCheetah.SourceGeneration; + +namespace SpreadCheetah.SourceGenerator.Test.Models.Combinations; + +internal class LengthInCmValueConverter : CellValueConverter +{ + public override DataCell ConvertToDataCell(decimal value) => new($"{value} cm"); +} diff --git a/SpreadCheetah.SourceGenerator.Test/Tests/CellValueConverterTests.cs b/SpreadCheetah.SourceGenerator.Test/Tests/CellValueConverterTests.cs new file mode 100644 index 0000000..19e598c --- /dev/null +++ b/SpreadCheetah.SourceGenerator.Test/Tests/CellValueConverterTests.cs @@ -0,0 +1,110 @@ +using SpreadCheetah.SourceGenerator.Test.Models.CellValueConverter; +using SpreadCheetah.Styling; +using SpreadCheetah.TestHelpers.Assertions; +using System.Globalization; +using Xunit; + +namespace SpreadCheetah.SourceGenerator.Test.Tests; + +public class CellValueConverterTests +{ + [Fact] + public async Task CellValueConverter_ClassWithReusedConverter() + { + // Arrange + using var stream = new MemoryStream(); + await using var spreadsheet = await Spreadsheet.CreateNewAsync(stream); + await spreadsheet.StartWorksheetAsync("Sheet"); + var obj = new ClassWithReusedConverter + { + FirstName = "Ola", + MiddleName = null, + LastName = "Nordmann", + Gpa = 3.1m + }; + + // Act + await spreadsheet.AddAsRowAsync(obj, CellValueConverterContext.Default.ClassWithReusedConverter); + await spreadsheet.FinishAsync(); + + // Assert + using var sheet = SpreadsheetAssert.SingleSheet(stream); + Assert.Equal("OLA", sheet["A1"].StringValue); + Assert.Equal("NORDMANN", sheet["C1"].StringValue); + } + + [Fact] + public async Task CellValueConverter_ClassWithGenericConverter() + { + // Arrange + using var stream = new MemoryStream(); + await using var spreadsheet = await Spreadsheet.CreateNewAsync(stream); + await spreadsheet.StartWorksheetAsync("Sheet"); + var obj = new ClassWithGenericConverter + { + FirstName = "Ola", + MiddleName = null, + LastName = "Nordmann", + Gpa = 3.1m + }; + + // Act + CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; + await spreadsheet.AddAsRowAsync(obj, CellValueConverterContext.Default.ClassWithGenericConverter); + await spreadsheet.FinishAsync(); + + // Assert + using var sheet = SpreadsheetAssert.SingleSheet(stream); + Assert.Equal("-", sheet["B1"].StringValue); + Assert.Equal("3.1", sheet["D1"].StringValue); + } + + [Fact] + public async Task CellValueConverter_TwoClassesUsingTheSameConverter() + { + // Arrange + using var stream = new MemoryStream(); + await using var spreadsheet = await Spreadsheet.CreateNewAsync(stream); + await spreadsheet.StartWorksheetAsync("Sheet"); + var obj1 = new ClassWithCellValueConverter { Name = "John" }; + var obj2 = new ClassWithReusedConverter + { + FirstName = "Ola", + MiddleName = null, + LastName = "Nordmann", + Gpa = 3.1m + }; + + // Act + await spreadsheet.AddAsRowAsync(obj1, CellValueConverterContext.Default.ClassWithCellValueConverter); + await spreadsheet.AddAsRowAsync(obj2, CellValueConverterContext.Default.ClassWithReusedConverter); + await spreadsheet.FinishAsync(); + + // Assert + using var sheet = SpreadsheetAssert.SingleSheet(stream); + Assert.Equal("JOHN", sheet["A1"].StringValue); + Assert.Equal("OLA", sheet["A2"].StringValue); + Assert.Equal("NORDMANN", sheet["C2"].StringValue); + } + + [Fact] + public async Task CellValueConverter_ClassWithConverterAndCellStyle() + { + // Arrange + using var stream = new MemoryStream(); + await using var spreadsheet = await Spreadsheet.CreateNewAsync(stream); + await spreadsheet.StartWorksheetAsync("Sheet"); + var style = new Style { Font = { Bold = true } }; + spreadsheet.AddStyle(style, "ID style"); + var obj = new ClassWithCellValueConverterAndCellStyle { Id = "Abc123" }; + + // Act + await spreadsheet.AddAsRowAsync(obj, CellValueConverterContext.Default.ClassWithCellValueConverterAndCellStyle); + await spreadsheet.FinishAsync(); + + // Assert + using var sheet = SpreadsheetAssert.SingleSheet(stream); + Assert.Equal("ABC123", sheet["A1"].StringValue); + Assert.True(sheet["A1"].Style.Font.Bold); + } +} diff --git a/SpreadCheetah.SourceGenerator.Test/Tests/WorksheetRowGeneratorTests.cs b/SpreadCheetah.SourceGenerator.Test/Tests/WorksheetRowGeneratorTests.cs index 3e9df4c..65caf2c 100644 --- a/SpreadCheetah.SourceGenerator.Test/Tests/WorksheetRowGeneratorTests.cs +++ b/SpreadCheetah.SourceGenerator.Test/Tests/WorksheetRowGeneratorTests.cs @@ -762,7 +762,7 @@ public async Task Spreadsheet_AddAsRow_ObjectWithMultipleColumnAttributes() countryOfOrigin: "Germany", model: "Golf", make: "Volkswagen", - year: 1990, + year: 1992, kW: 96, length: 428.4m); @@ -772,12 +772,12 @@ public async Task Spreadsheet_AddAsRow_ObjectWithMultipleColumnAttributes() // Assert using var sheet = SpreadsheetAssert.SingleSheet(stream); - Assert.Equal(obj.Year, sheet["A1"].IntValue); + Assert.Equal($"{obj.Year} (leap year)", sheet["A1"].StringValue); Assert.Equal(obj.Make[..8], sheet["B1"].StringValue); Assert.Equal(obj.CountryOfOrigin, sheet["C1"].StringValue); Assert.Equal(obj.Model, sheet["D1"].StringValue); Assert.Equal(obj.kW, sheet["E1"].DecimalValue); - Assert.Equal(obj.Length, sheet["F1"].DecimalValue); + Assert.Equal($"{obj.Length} cm", sheet["F1"].StringValue); Assert.Equal(obj.Id, sheet["G1"].StringValue); Assert.True(sheet["A1"].Style.Font.Bold); diff --git a/SpreadCheetah.SourceGenerator/AnalyzerReleases.Shipped.md b/SpreadCheetah.SourceGenerator/AnalyzerReleases.Shipped.md index 6a27a1b..0ee8798 100644 --- a/SpreadCheetah.SourceGenerator/AnalyzerReleases.Shipped.md +++ b/SpreadCheetah.SourceGenerator/AnalyzerReleases.Shipped.md @@ -35,5 +35,5 @@ SPCH1004 | SpreadCheetah.SourceGenerator | Error | InvalidColumnHeaderPropertyRe ### New Rules Rule ID | Category | Severity | Notes --------|----------|----------|------- -SPCH1005 | SpreadCheetah.SourceGenerator | Error | UnsupportedTypeForCellValueLengthLimit +SPCH1005 | SpreadCheetah.SourceGenerator | Error | UnsupportedTypeForAttribute SPCH1006 | SpreadCheetah.SourceGenerator | Error | InvalidAttributeArgument \ No newline at end of file diff --git a/SpreadCheetah.SourceGenerator/AnalyzerReleases.Unshipped.md b/SpreadCheetah.SourceGenerator/AnalyzerReleases.Unshipped.md index e69de29..76ad3ed 100644 --- a/SpreadCheetah.SourceGenerator/AnalyzerReleases.Unshipped.md +++ b/SpreadCheetah.SourceGenerator/AnalyzerReleases.Unshipped.md @@ -0,0 +1,7 @@ +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +SPCH1007 | SpreadCheetah.SourceGenerator | Error | AttributeTypeArgumentMustInherit +SPCH1008 | SpreadCheetah.SourceGenerator | Error | AttributeCombinationNotSupported +SPCH1009 | SpreadCheetah.SourceGenerator | Error | AttributeTypeArgumentMustHaveDefaultConstructor \ No newline at end of file diff --git a/SpreadCheetah.SourceGenerator/Diagnostics.cs b/SpreadCheetah.SourceGenerator/Diagnostics.cs index a7805a7..b8dc6a4 100644 --- a/SpreadCheetah.SourceGenerator/Diagnostics.cs +++ b/SpreadCheetah.SourceGenerator/Diagnostics.cs @@ -1,4 +1,6 @@ using Microsoft.CodeAnalysis; +using SpreadCheetah.SourceGenerator.Extensions; +using SpreadCheetah.SourceGenerator.Models; namespace SpreadCheetah.SourceGenerator; @@ -6,7 +8,10 @@ internal static class Diagnostics { private const string Category = "SpreadCheetah.SourceGenerator"; - public static readonly DiagnosticDescriptor NoPropertiesFound = new( + public static Diagnostic NoPropertiesFound(Location? location, string rowTypeName) + => Diagnostic.Create(NoPropertiesFoundDescriptor, location, rowTypeName); + + private static readonly DiagnosticDescriptor NoPropertiesFoundDescriptor = new( id: "SPCH1001", title: "Missing properties with public getters", messageFormat: "The type '{0}' has no properties with public getters. This will cause an empty row to be added.", @@ -14,7 +19,10 @@ internal static class Diagnostics defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); - public static readonly DiagnosticDescriptor UnsupportedTypeForCellValue = new( + public static Diagnostic UnsupportedTypeForCellValue(Location? location, string rowTypeName, string unsupportedPropertyTypeName) + => Diagnostic.Create(UnsupportedTypeForCellValueDescriptor, location, rowTypeName, unsupportedPropertyTypeName); + + private static readonly DiagnosticDescriptor UnsupportedTypeForCellValueDescriptor = new( id: "SPCH1002", title: "Unsupported type for cell value", messageFormat: "The type '{0}' has a property of type '{1}' which is not supported as a cell value. The property will be ignored when creating the row.", @@ -22,7 +30,10 @@ internal static class Diagnostics defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); - public static readonly DiagnosticDescriptor DuplicateColumnOrder = new( + public static DiagnosticInfo DuplicateColumnOrder(LocationInfo? location, string className) + => new(DuplicateColumnOrderDescriptor, location, new([className])); + + private static readonly DiagnosticDescriptor DuplicateColumnOrderDescriptor = new( id: "SPCH1003", title: "Duplicate column ordering", messageFormat: "The type '{0}' has two or more properties with the same column order", @@ -30,7 +41,10 @@ internal static class Diagnostics defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); - public static readonly DiagnosticDescriptor InvalidColumnHeaderPropertyReference = new( + public static DiagnosticInfo InvalidColumnHeaderPropertyReference(LocationInfo? location, string propertyName, string typeFullName) + => new(InvalidColumnHeaderPropertyReferenceDescriptor, location, new([propertyName, typeFullName])); + + private static readonly DiagnosticDescriptor InvalidColumnHeaderPropertyReferenceDescriptor = new( id: "SPCH1004", title: "Invalid ColumnHeader property reference", messageFormat: "'{0}' on type '{1}' is not a valid property reference. It must be a static property, have a public getter, and the return type must be a string (or string?).", @@ -38,19 +52,60 @@ internal static class Diagnostics defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); - public static readonly DiagnosticDescriptor UnsupportedTypeForCellValueLengthLimit = new( + public static DiagnosticInfo UnsupportedTypeForAttribute(AttributeData attribute, string typeFullName, CancellationToken token) + => new(UnsupportedTypeForAttributeDescriptor, attribute.GetLocation(token), new([attribute.Name(), typeFullName])); + + private static readonly DiagnosticDescriptor UnsupportedTypeForAttributeDescriptor = new( id: "SPCH1005", - title: "Unsupported type for CellValueLengthLimit attribute", - messageFormat: "The CellValueLengthLimit attribute is not supported on properties of type '{0}'", + title: "Unsupported type for attribute", + messageFormat: "{0} is not supported on properties of type '{1}'", category: Category, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); - public static readonly DiagnosticDescriptor InvalidAttributeArgument = new( + public static DiagnosticInfo InvalidAttributeArgument(AttributeData attribute, string attributeArgument, CancellationToken token) + => new(InvalidAttributeArgumentDescriptor, attribute.GetLocation(token), new([attributeArgument, attribute.Name()])); + + private static readonly DiagnosticDescriptor InvalidAttributeArgumentDescriptor = new( id: "SPCH1006", title: "Invalid attribute argument", - messageFormat: "'{0}' is an invalid argument for attribute '{1}'", + messageFormat: "'{0}' is an invalid argument for {1}", + category: Category, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticInfo AttributeTypeArgumentMustInherit(AttributeData attribute, string typeName, string baseClassName, CancellationToken token) + => new(AttributeTypeArgumentMustInheritDescriptor, attribute.GetLocation(token), new([typeName, attribute.Name(), baseClassName])); + + private static readonly DiagnosticDescriptor AttributeTypeArgumentMustInheritDescriptor = new( + id: "SPCH1007", + title: "Invalid attribute type argument", + messageFormat: "Type '{0}' is an invalid argument for {1} because it does not inherit {2}", category: Category, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); + + public static DiagnosticInfo AttributeCombinationNotSupported(LocationInfo? location, string attribute1, string attribute2) + => new(AttributeCombinationNotSupportedDescriptor, location, new([attribute1, attribute2])); + + private static readonly DiagnosticDescriptor AttributeCombinationNotSupportedDescriptor = new( + id: "SPCH1008", + title: "Attribute combination not supported", + messageFormat: "Having both the {0} and the {1} attributes on a property is not supported", + category: Category, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticInfo AttributeTypeArgumentMustHaveDefaultConstructor(AttributeData attribute, string typeName, CancellationToken token) + => new(AttributeTypeArgumentMustHaveDefaultConstructorDescriptor, attribute.GetLocation(token), new([typeName])); + + private static readonly DiagnosticDescriptor AttributeTypeArgumentMustHaveDefaultConstructorDescriptor = new( + id: "SPCH1009", + title: "Type must have a public parameterless constructor", + messageFormat: "Type '{0}' must have a public parameterless constructor", + category: Category, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + private static string Name(this AttributeData attribute) => attribute.AttributeClass?.Name ?? ""; } diff --git a/SpreadCheetah.SourceGenerator/Extensions/AttributeDataExtensions.cs b/SpreadCheetah.SourceGenerator/Extensions/AttributeDataExtensions.cs index b03facb..5510547 100644 --- a/SpreadCheetah.SourceGenerator/Extensions/AttributeDataExtensions.cs +++ b/SpreadCheetah.SourceGenerator/Extensions/AttributeDataExtensions.cs @@ -31,6 +31,7 @@ public static PropertyAttributeData MapToPropertyAttributeData( Attributes.ColumnHeader => attribute.TryGetColumnHeaderAttribute(diagnosticInfos, token, ref result), Attributes.ColumnOrder => attribute.TryGetColumnOrderAttribute(token, ref result), Attributes.ColumnWidth => attribute.TryGetColumnWidthAttribute(diagnosticInfos, token, ref result), + Attributes.CellValueConverter => attribute.TryGetCellValueConverterAttribute(propertyType, diagnosticInfos, token, ref result), _ => false }; } @@ -138,7 +139,7 @@ private static bool TryGetColumnHeaderAttribute(this AttributeData attribute, } var location = attribute.GetLocation(token); - diagnosticInfos.Add(new DiagnosticInfo(Diagnostics.InvalidColumnHeaderPropertyReference, location, new([propertyName, typeFullName]))); + diagnosticInfos.Add(Diagnostics.InvalidColumnHeaderPropertyReference(location, propertyName, typeFullName)); return null; } @@ -171,8 +172,7 @@ private static bool TryGetCellStyleAttribute(this AttributeData attribute, || char.IsWhiteSpace(value[0]) || char.IsWhiteSpace(value[^1])) { - var location = attribute.GetLocation(token); - diagnosticInfos.Add(new DiagnosticInfo(Diagnostics.InvalidAttributeArgument, location, new([value, Attributes.CellStyle]))); + diagnosticInfos.Add(Diagnostics.InvalidAttributeArgument(attribute, value, token)); return false; } @@ -192,9 +192,8 @@ private static bool TryGetColumnWidthAttribute(this AttributeData attribute, if (attributeValue is <= 0 or > 255) { - var location = attribute.GetLocation(token); var stringValue = attributeValue.ToString(CultureInfo.InvariantCulture); - diagnosticInfos.Add(new DiagnosticInfo(Diagnostics.InvalidAttributeArgument, location, new([stringValue, Attributes.ColumnWidth]))); + diagnosticInfos.Add(Diagnostics.InvalidAttributeArgument(attribute, stringValue, token)); return false; } @@ -210,9 +209,8 @@ private static bool TryGetCellValueTruncateAttribute(this AttributeData attribut if (propertyType.SpecialType != SpecialType.System_String) { - var location = attribute.GetLocation(token); var typeFullName = propertyType.ToDisplayString(); - diagnosticInfos.Add(new DiagnosticInfo(Diagnostics.UnsupportedTypeForCellValueLengthLimit, location, new([typeFullName]))); + diagnosticInfos.Add(Diagnostics.UnsupportedTypeForAttribute(attribute, typeFullName, token)); return false; } @@ -222,9 +220,8 @@ private static bool TryGetCellValueTruncateAttribute(this AttributeData attribut if (attributeValue <= 0) { - var location = attribute.GetLocation(token); var stringValue = attributeValue.ToString(CultureInfo.InvariantCulture); - diagnosticInfos.Add(new DiagnosticInfo(Diagnostics.InvalidAttributeArgument, location, new([stringValue, Attributes.CellValueTruncate]))); + diagnosticInfos.Add(Diagnostics.InvalidAttributeArgument(attribute, stringValue, token)); return false; } @@ -232,7 +229,39 @@ private static bool TryGetCellValueTruncateAttribute(this AttributeData attribut return true; } - private static LocationInfo? GetLocation(this AttributeData attribute, CancellationToken token) + private static bool TryGetCellValueConverterAttribute(this AttributeData attribute, + ITypeSymbol propertyType, + List diagnosticInfos, CancellationToken token, + ref PropertyAttributeData data) + { + var args = attribute.ConstructorArguments; + if (args is not [{ Value: INamedTypeSymbol converterTypeSymbol }]) + return false; + + var typeName = converterTypeSymbol.ToDisplayString(); + var propertyTypeName = propertyType.OriginalDefinition.ToDisplayString(); + + if (converterTypeSymbol.BaseType is not { Name: "CellValueConverter", TypeArguments: [INamedTypeSymbol typeArgument] } + || !string.Equals(typeArgument.OriginalDefinition.ToDisplayString(), propertyTypeName, StringComparison.Ordinal)) + { + diagnosticInfos.Add(Diagnostics.AttributeTypeArgumentMustInherit(attribute, typeName, $"CellValueConverter<{propertyTypeName}>", token)); + return false; + } + + var hasPublicConstructor = converterTypeSymbol.Constructors.Any(ctor => + ctor is { Parameters.Length: 0, DeclaredAccessibility: Accessibility.Public }); + + if (!hasPublicConstructor) + { + diagnosticInfos.Add(Diagnostics.AttributeTypeArgumentMustHaveDefaultConstructor(attribute, typeName, token)); + return false; + } + + data.CellValueConverter = new CellValueConverter(typeName); + return true; + } + + public static LocationInfo? GetLocation(this AttributeData attribute, CancellationToken token) { return attribute .ApplicationSyntaxReference? diff --git a/SpreadCheetah.SourceGenerator/Helpers/Attributes.cs b/SpreadCheetah.SourceGenerator/Helpers/Attributes.cs index 18d0374..1e98ebd 100644 --- a/SpreadCheetah.SourceGenerator/Helpers/Attributes.cs +++ b/SpreadCheetah.SourceGenerator/Helpers/Attributes.cs @@ -6,6 +6,7 @@ internal static class Attributes public const string CellValueTruncate = "SpreadCheetah.SourceGeneration.CellValueTruncateAttribute"; public const string ColumnHeader = "SpreadCheetah.SourceGeneration.ColumnHeaderAttribute"; public const string ColumnOrder = "SpreadCheetah.SourceGeneration.ColumnOrderAttribute"; + public const string CellValueConverter = "SpreadCheetah.SourceGeneration.CellValueConverterAttribute"; public const string ColumnWidth = "SpreadCheetah.SourceGeneration.ColumnWidthAttribute"; public const string InheritColumns = "SpreadCheetah.SourceGeneration.InheritColumnsAttribute"; public const string GenerationOptions = "SpreadCheetah.SourceGeneration.WorksheetRowGenerationOptionsAttribute"; diff --git a/SpreadCheetah.SourceGenerator/Models/CellValueConverter.cs b/SpreadCheetah.SourceGenerator/Models/CellValueConverter.cs new file mode 100644 index 0000000..b0a978e --- /dev/null +++ b/SpreadCheetah.SourceGenerator/Models/CellValueConverter.cs @@ -0,0 +1,3 @@ +namespace SpreadCheetah.SourceGenerator.Models; + +internal readonly record struct CellValueConverter(string ConverterTypeName); \ No newline at end of file diff --git a/SpreadCheetah.SourceGenerator/Models/PropertyAttributeData.cs b/SpreadCheetah.SourceGenerator/Models/PropertyAttributeData.cs index a24743b..cfba7e7 100644 --- a/SpreadCheetah.SourceGenerator/Models/PropertyAttributeData.cs +++ b/SpreadCheetah.SourceGenerator/Models/PropertyAttributeData.cs @@ -10,4 +10,5 @@ internal record struct PropertyAttributeData public ColumnHeader? ColumnHeader { get; set; } public ColumnOrder? ColumnOrder { get; set; } public ColumnWidth? ColumnWidth { get; set; } + public CellValueConverter? CellValueConverter { get; set; } } \ No newline at end of file diff --git a/SpreadCheetah.SourceGenerator/Models/RowTypeProperty.cs b/SpreadCheetah.SourceGenerator/Models/RowTypeProperty.cs index e370f86..5bd40d3 100644 --- a/SpreadCheetah.SourceGenerator/Models/RowTypeProperty.cs +++ b/SpreadCheetah.SourceGenerator/Models/RowTypeProperty.cs @@ -5,4 +5,5 @@ internal sealed record RowTypeProperty( ColumnHeaderInfo? ColumnHeader, CellStyle? CellStyle, ColumnWidth? ColumnWidth, - CellValueTruncate? CellValueTruncate); \ No newline at end of file + CellValueTruncate? CellValueTruncate, + CellValueConverter? CellValueConverter); \ No newline at end of file diff --git a/SpreadCheetah.SourceGenerator/WorksheetRowGenerator.cs b/SpreadCheetah.SourceGenerator/WorksheetRowGenerator.cs index d0dac91..dd6e602 100644 --- a/SpreadCheetah.SourceGenerator/WorksheetRowGenerator.cs +++ b/SpreadCheetah.SourceGenerator/WorksheetRowGenerator.cs @@ -111,14 +111,21 @@ private static RowType AnalyzeTypeProperties(ITypeSymbol classType, LocationInfo ColumnHeader: data.ColumnHeader?.ToColumnHeaderInfo(), CellStyle: data.CellStyle, ColumnWidth: data.ColumnWidth, - CellValueTruncate: data.CellValueTruncate); + CellValueTruncate: data.CellValueTruncate, + CellValueConverter: data.CellValueConverter); + + if (data is { CellValueConverter: not null, CellValueTruncate: not null }) + { + var location = property.Locations.FirstOrDefault()?.ToLocationInfo(); + diagnosticInfos.Add(Diagnostics.AttributeCombinationNotSupported(location, "CellValueConverter", "CellValueTruncate")); + } if (data.ColumnOrder is not { } order) implicitOrderProperties.Add(rowTypeProperty); else if (!explicitOrderProperties.ContainsKey(order.Value)) explicitOrderProperties.Add(order.Value, rowTypeProperty); else - diagnosticInfos.Add(new DiagnosticInfo(Diagnostics.DuplicateColumnOrder, order.Location, new([classType.Name]))); + diagnosticInfos.Add(Diagnostics.DuplicateColumnOrder(order.Location, classType.Name)); } explicitOrderProperties.AddWithImplicitKeys(implicitOrderProperties); @@ -216,6 +223,8 @@ private static void GenerateCode(StringBuilder sb, ContextClass contextClass, So } """); + var cellValueConverters = GenerateCellValueConverters(sb, contextClass.RowTypes); + var rowTypeNames = new HashSet(StringComparer.Ordinal); var typeIndex = 0; @@ -225,7 +234,7 @@ private static void GenerateCode(StringBuilder sb, ContextClass contextClass, So if (!rowTypeNames.Add(rowTypeName)) continue; - GenerateCodeForType(sb, typeIndex, rowType, contextClass, context); + GenerateCodeForType(sb, typeIndex, cellValueConverters, rowType, contextClass, context); ++typeIndex; } @@ -237,7 +246,8 @@ private static void GenerateCode(StringBuilder sb, ContextClass contextClass, So """); } - private static void GenerateCodeForType(StringBuilder sb, int typeIndex, RowType rowType, + private static void GenerateCodeForType(StringBuilder sb, int typeIndex, + Dictionary cellValueConverters, RowType rowType, ContextClass contextClass, SourceProductionContext context) { ReportDiagnostics(rowType, rowType.WorksheetRowAttributeLocation, contextClass.Options, context); @@ -287,7 +297,7 @@ private static void GenerateCodeForType(StringBuilder sb, int typeIndex, RowType GenerateAddRangeAsRows(sb, rowType); GenerateAddAsRowInternal(sb, rowType); GenerateAddRangeAsRowsInternal(sb, rowType); - GenerateAddCellsAsRow(sb, rowType, cellStyleToStyleIdIndex); + GenerateAddCellsAsRow(sb, rowType, cellStyleToStyleIdIndex, cellValueConverters); } private static void ReportDiagnostics(RowType rowType, LocationInfo? locationInfo, GeneratorOptions? options, SourceProductionContext context) @@ -308,10 +318,10 @@ private static void ReportDiagnostics(RowType rowType, LocationInfo? locationInf var location = locationInfo?.ToLocation(); if (rowType.Properties.Count == 0) - context.ReportDiagnostic(Diagnostic.Create(Diagnostics.NoPropertiesFound, location, rowType.Name)); + context.ReportDiagnostic(Diagnostics.NoPropertiesFound(location, rowType.Name)); if (rowType.UnsupportedPropertyTypeNames.FirstOrDefault() is { } unsupportedPropertyTypeName) - context.ReportDiagnostic(Diagnostic.Create(Diagnostics.UnsupportedTypeForCellValue, location, rowType.Name, unsupportedPropertyTypeName)); + context.ReportDiagnostic(Diagnostics.UnsupportedTypeForCellValue(location, rowType.Name, unsupportedPropertyTypeName)); } private static void GenerateCreateWorksheetOptions(StringBuilder sb, int typeIndex, EquatableArray properties) @@ -383,6 +393,33 @@ private static Dictionary GenerateCreateWorksheetRowDependencyIn return cellStyleToStyleIdIndex; } + private static Dictionary GenerateCellValueConverters(StringBuilder sb, EquatableArray rowTypes) + { + var converterTypeToFieldMap = new Dictionary(StringComparer.Ordinal); + + foreach (var rowType in rowTypes) + { + foreach (var property in rowType.Properties) + { + if (property.CellValueConverter is not { } cellValueConverter) + continue; + + var converterTypeName = cellValueConverter.ConverterTypeName; + if (converterTypeToFieldMap.ContainsKey(converterTypeName)) + continue; + + var fieldName = FormattableString.Invariant($"_valueConverter{converterTypeToFieldMap.Count}"); + converterTypeToFieldMap[converterTypeName] = fieldName; + + sb.AppendLine($$""" + private static readonly {{converterTypeName}} {{fieldName}} = new {{converterTypeName}}(); + """); + } + } + + return converterTypeToFieldMap; + } + private static void GenerateAddHeaderRow(StringBuilder sb, int typeIndex, EquatableArray properties) { Debug.Assert(properties.Count > 0); @@ -556,7 +593,8 @@ private static async ValueTask AddRangeAsRowsInternalAsync(SpreadCheetah.Spreads """); } - private static void GenerateAddCellsAsRow(StringBuilder sb, RowType rowType, Dictionary cellStyleToStyleIdIndex) + private static void GenerateAddCellsAsRow(StringBuilder sb, RowType rowType, + Dictionary cellStyleToStyleIdIndex, Dictionary valueConverters) { var properties = rowType.Properties; Debug.Assert(properties.Count > 0); @@ -580,8 +618,12 @@ private static ValueTask AddCellsAsRowAsync(SpreadCheetah.Spreadsheet spreadshee foreach (var (i, property) in properties.Index()) { + int? styleIdIndex = property.CellStyle is { } cellStyle + ? cellStyleToStyleIdIndex[cellStyle] + : null; + sb.AppendLine(FormattableString.Invariant($""" - cells[{i}] = {ConstructCell(property, $"obj.{property.Name}")}; + cells[{i}] = {ConstructCell(property, styleIdIndex)}; """)); } @@ -590,24 +632,28 @@ private static ValueTask AddCellsAsRowAsync(SpreadCheetah.Spreadsheet spreadshee } """); - string ConstructCell(RowTypeProperty property, string value) + string ConstructCell(RowTypeProperty property, int? styleIdIndex) { - var constructDataCell = property.CellValueTruncate is { } truncate - ? FormattableString.Invariant($"ConstructTruncatedDataCell({value}, {truncate.Value})") - : $"new DataCell({value})"; + var value = $"obj.{property.Name}"; - int? styleIdIndex = property.CellStyle is { } cellStyle - ? cellStyleToStyleIdIndex[cellStyle] - : null; + var constructDataCell = (property.CellValueConverter, property.CellValueTruncate) switch + { + (null, null) => $"new DataCell({value})", + ({ } converter, _) => $"{valueConverters[converter.ConverterTypeName]}.ConvertToDataCell({value})", + (null, { } truncate) => FormattableString.Invariant($"ConstructTruncatedDataCell({value}, {truncate.Value})") + }; var styledCell = rowType.PropertiesWithStyleAttributes > 0; - - return (styledCell, styleIdIndex) switch + var styleId = (styledCell, styleIdIndex) switch { - (true, { } i) => FormattableString.Invariant($"new StyledCell({constructDataCell}, styleIds[{i}])"), - (true, null) => $"new StyledCell({constructDataCell}, null)", - (false, _) => constructDataCell + (true, { } i) => FormattableString.Invariant($"styleIds[{i}]"), + (true, _) => "null", + _ => null }; + + return styleId is null + ? constructDataCell + : $"new StyledCell({constructDataCell}, {styleId})"; } } diff --git a/SpreadCheetah.Test/Tests/PublicApiTests.PublicApi_Generate.DotNet6_0.verified.txt b/SpreadCheetah.Test/Tests/PublicApiTests.PublicApi_Generate.DotNet6_0.verified.txt index 3287b2e..98d4581 100644 --- a/SpreadCheetah.Test/Tests/PublicApiTests.PublicApi_Generate.DotNet6_0.verified.txt +++ b/SpreadCheetah.Test/Tests/PublicApiTests.PublicApi_Generate.DotNet6_0.verified.txt @@ -191,6 +191,16 @@ namespace SpreadCheetah.SourceGeneration public CellStyleAttribute(string styleName) { } } [System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple=false)] + public sealed class CellValueConverterAttribute : System.Attribute + { + public CellValueConverterAttribute(System.Type converterType) { } + } + public abstract class CellValueConverter + { + protected CellValueConverter() { } + public abstract SpreadCheetah.DataCell ConvertToDataCell(T value); + } + [System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple=false)] public sealed class CellValueTruncateAttribute : System.Attribute { public CellValueTruncateAttribute(int length) { } diff --git a/SpreadCheetah.Test/Tests/PublicApiTests.PublicApi_Generate.DotNet7_0.verified.txt b/SpreadCheetah.Test/Tests/PublicApiTests.PublicApi_Generate.DotNet7_0.verified.txt index cc45cc5..a5a4c7b 100644 --- a/SpreadCheetah.Test/Tests/PublicApiTests.PublicApi_Generate.DotNet7_0.verified.txt +++ b/SpreadCheetah.Test/Tests/PublicApiTests.PublicApi_Generate.DotNet7_0.verified.txt @@ -191,6 +191,16 @@ namespace SpreadCheetah.SourceGeneration public CellStyleAttribute(string styleName) { } } [System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple=false)] + public sealed class CellValueConverterAttribute : System.Attribute + { + public CellValueConverterAttribute(System.Type converterType) { } + } + public abstract class CellValueConverter + { + protected CellValueConverter() { } + public abstract SpreadCheetah.DataCell ConvertToDataCell(T value); + } + [System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple=false)] public sealed class CellValueTruncateAttribute : System.Attribute { public CellValueTruncateAttribute(int length) { } diff --git a/SpreadCheetah.Test/Tests/PublicApiTests.PublicApi_Generate.DotNet8_0.verified.txt b/SpreadCheetah.Test/Tests/PublicApiTests.PublicApi_Generate.DotNet8_0.verified.txt index 142ffde..ff83060 100644 --- a/SpreadCheetah.Test/Tests/PublicApiTests.PublicApi_Generate.DotNet8_0.verified.txt +++ b/SpreadCheetah.Test/Tests/PublicApiTests.PublicApi_Generate.DotNet8_0.verified.txt @@ -191,6 +191,16 @@ namespace SpreadCheetah.SourceGeneration public CellStyleAttribute(string styleName) { } } [System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple=false)] + public sealed class CellValueConverterAttribute : System.Attribute + { + public CellValueConverterAttribute(System.Type converterType) { } + } + public abstract class CellValueConverter + { + protected CellValueConverter() { } + public abstract SpreadCheetah.DataCell ConvertToDataCell(T value); + } + [System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple=false)] public sealed class CellValueTruncateAttribute : System.Attribute { public CellValueTruncateAttribute(int length) { } diff --git a/SpreadCheetah.Test/Tests/PublicApiTests.PublicApi_Generate.Net4_7.verified.txt b/SpreadCheetah.Test/Tests/PublicApiTests.PublicApi_Generate.Net4_7.verified.txt index 2f76062..890e6fe 100644 --- a/SpreadCheetah.Test/Tests/PublicApiTests.PublicApi_Generate.Net4_7.verified.txt +++ b/SpreadCheetah.Test/Tests/PublicApiTests.PublicApi_Generate.Net4_7.verified.txt @@ -190,6 +190,16 @@ namespace SpreadCheetah.SourceGeneration public CellStyleAttribute(string styleName) { } } [System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple=false)] + public sealed class CellValueConverterAttribute : System.Attribute + { + public CellValueConverterAttribute(System.Type converterType) { } + } + public abstract class CellValueConverter + { + protected CellValueConverter() { } + public abstract SpreadCheetah.DataCell ConvertToDataCell(T value); + } + [System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple=false)] public sealed class CellValueTruncateAttribute : System.Attribute { public CellValueTruncateAttribute(int length) { } diff --git a/SpreadCheetah/SourceGeneration/CellValueConverter.cs b/SpreadCheetah/SourceGeneration/CellValueConverter.cs new file mode 100644 index 0000000..403a583 --- /dev/null +++ b/SpreadCheetah/SourceGeneration/CellValueConverter.cs @@ -0,0 +1,12 @@ +namespace SpreadCheetah.SourceGeneration; + +/// +/// Converts a value to a . Used in conjunction with . +/// +public abstract class CellValueConverter +{ + /// + /// Converts a value to a . + /// + public abstract DataCell ConvertToDataCell(T value); +} \ No newline at end of file diff --git a/SpreadCheetah/SourceGeneration/CellValueConverterAttribute.cs b/SpreadCheetah/SourceGeneration/CellValueConverterAttribute.cs new file mode 100644 index 0000000..9cc198b --- /dev/null +++ b/SpreadCheetah/SourceGeneration/CellValueConverterAttribute.cs @@ -0,0 +1,9 @@ +namespace SpreadCheetah.SourceGeneration; + +/// +/// Instructs the SpreadCheetah source generator to convert property values to cells using the specified type. +/// The specified converter type must derive from , +/// and its type parameter must match the property type. +/// +[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] +public sealed class CellValueConverterAttribute(Type converterType) : Attribute; \ No newline at end of file