Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow property with custom type if it has CellValueConverter. #74

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//HintName: MyNamespace.MyGenRowContext.g.cs
// <auto-generated />
#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.ObjectConverter _valueConverter0 = new MyNamespace.ObjectConverter();
private static readonly MyNamespace.StringConverter _valueConverter1 = new MyNamespace.StringConverter();

private WorksheetRowTypeInfo<MyNamespace.ClassWithoutParameterlessConstructor>? _ClassWithoutParameterlessConstructor;
public WorksheetRowTypeInfo<MyNamespace.ClassWithoutParameterlessConstructor> ClassWithoutParameterlessConstructor => _ClassWithoutParameterlessConstructor
??= WorksheetRowMetadataServices.CreateObjectInfo<MyNamespace.ClassWithoutParameterlessConstructor>(
AddHeaderRow0Async, AddAsRowAsync, AddRangeAsRowsAsync, null, CreateWorksheetRowDependencyInfo0);

private static WorksheetRowDependencyInfo CreateWorksheetRowDependencyInfo0(Spreadsheet spreadsheet)
{
var styleIds = new[]
{
spreadsheet.GetStyleId("object style"),
};
return new WorksheetRowDependencyInfo(styleIds);
}

private static async ValueTask AddHeaderRow0Async(SpreadCheetah.Spreadsheet spreadsheet, SpreadCheetah.Styling.StyleId? styleId, CancellationToken token)
{
var cells = ArrayPool<StyledCell>.Shared.Rent(4);
try
{
cells[0] = new StyledCell("Value", styleId);
cells[1] = new StyledCell("Value1", styleId);
cells[2] = new StyledCell("StringProp", styleId);
cells[3] = new StyledCell("StringProp1", styleId);
await spreadsheet.AddRowAsync(cells.AsMemory(0, 4), token).ConfigureAwait(false);
}
finally
{
ArrayPool<StyledCell>.Shared.Return(cells, true);
}
}

private static ValueTask AddAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, MyNamespace.ClassWithoutParameterlessConstructor? obj, CancellationToken token)
{
if (spreadsheet is null)
throw new ArgumentNullException(nameof(spreadsheet));
if (obj is null)
return spreadsheet.AddRowAsync(ReadOnlyMemory<StyledCell>.Empty, token);
return AddAsRowInternalAsync(spreadsheet, obj, token);
}

private static ValueTask AddRangeAsRowsAsync(SpreadCheetah.Spreadsheet spreadsheet,
IEnumerable<MyNamespace.ClassWithoutParameterlessConstructor?> 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.ClassWithoutParameterlessConstructor obj,
CancellationToken token)
{
var cells = ArrayPool<StyledCell>.Shared.Rent(4);
try
{
var worksheetRowDependencyInfo = spreadsheet.GetOrCreateWorksheetRowDependencyInfo(Default.ClassWithoutParameterlessConstructor);
var styleIds = worksheetRowDependencyInfo.StyleIds;
await AddCellsAsRowAsync(spreadsheet, obj, cells, styleIds, token).ConfigureAwait(false);
}
finally
{
ArrayPool<StyledCell>.Shared.Return(cells, true);
}
}

private static async ValueTask AddRangeAsRowsInternalAsync(SpreadCheetah.Spreadsheet spreadsheet,
IEnumerable<MyNamespace.ClassWithoutParameterlessConstructor?> objs,
CancellationToken token)
{
var cells = ArrayPool<StyledCell>.Shared.Rent(4);
try
{
var worksheetRowDependencyInfo = spreadsheet.GetOrCreateWorksheetRowDependencyInfo(Default.ClassWithoutParameterlessConstructor);
var styleIds = worksheetRowDependencyInfo.StyleIds;
foreach (var obj in objs)
{
await AddCellsAsRowAsync(spreadsheet, obj, cells, styleIds, token).ConfigureAwait(false);
}
}
finally
{
ArrayPool<StyledCell>.Shared.Return(cells, true);
}
}

private static ValueTask AddCellsAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet,
MyNamespace.ClassWithoutParameterlessConstructor? obj,
StyledCell[] cells, IReadOnlyList<StyleId> styleIds, CancellationToken token)
{
if (obj is null)
return spreadsheet.AddRowAsync(ReadOnlyMemory<StyledCell>.Empty, token);

cells[0] = new StyledCell(_valueConverter0.ConvertToDataCell(obj.Value), null);
cells[1] = new StyledCell(_valueConverter0.ConvertToDataCell(obj.Value1), styleIds[0]);
cells[2] = new StyledCell(new DataCell(obj.StringProp), null);
cells[3] = new StyledCell(_valueConverter1.ConvertToDataCell(obj.StringProp1), null);
return spreadsheet.AddRowAsync(cells.AsMemory(0, 4), 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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -256,4 +256,45 @@ public partial class MyGenRowContext : WorksheetRowContext;
// Act & Assert
return TestHelper.CompileAndVerify<WorksheetRowGenerator>(source, onlyDiagnostics: true);
}

[Fact]
public Task CellValueConverter_ClassWithConverterOnComplexProperty()
{
// Arrange
const string source = """
using SpreadCheetah.SourceGeneration;

namespace MyNamespace;
public class ClassWithoutParameterlessConstructor
{
[CellValueConverter(typeof(ObjectConverter))]
public object? Value { get; set; }

[CellValueConverter(typeof(ObjectConverter))]
[CellStyle("object style")]
public object? Value1 { get; set; }

public string StringProp { get; set; }

[CellValueConverter(typeof(StringConverter))]
public string StringProp1 { get; set; }
}

internal class ObjectConverter : CellValueConverter<object>
{
public override DataCell ConvertToCell(object value) => new(value.ToString());
}

internal class StringConverter : CellValueConverter<string>
{
public override DataCell ConvertToCell(string value) => new(value);
}

[WorksheetRow(typeof(ClassWithoutParameterlessConstructor))]
public partial class MyGenRowContext : WorksheetRowContext;
""";

// Act & Assert
return TestHelper.CompileAndVerify<WorksheetRowGenerator>(source);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ namespace SpreadCheetah.SourceGenerator.Test.Models.CellValueConverter;
[WorksheetRow(typeof(ClassWithCellValueConverterAndCellStyle))]
[WorksheetRow(typeof(ClassWithGenericConverter))]
[WorksheetRow(typeof(ClassWithReusedConverter))]
[WorksheetRow(typeof(ClassWithCellValueConverterOnCustomType))]
internal partial class CellValueConverterContext : WorksheetRowContext;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using SpreadCheetah.SourceGeneration;

namespace SpreadCheetah.SourceGenerator.Test.Models.CellValueConverter;

public class ClassWithCellValueConverterOnCustomType
{
public string Property { get; init; } = null!;

[CellValueConverter(typeof(NullToDashValueConverter<object?>))]
public object? ComplexProperty { get; init; }

[CellStyle("PercentType")]
[CellValueConverter(typeof(PercentToNumberConverter))]
public Percent? PercentType { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace SpreadCheetah.SourceGenerator.Test.Models.CellValueConverter;

public readonly record struct Percent(decimal Value);
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using SpreadCheetah.SourceGeneration;

namespace SpreadCheetah.SourceGenerator.Test.Models.CellValueConverter;

public class PercentToNumberConverter : CellValueConverter<Percent?>
{
public override DataCell ConvertToDataCell(Percent? value)
{
return value is null ? new("-") : new(value.GetValueOrDefault().Value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,32 @@ public async Task CellValueConverter_ClassWithConverterAndCellStyle()
Assert.Equal("ABC123", sheet["A1"].StringValue);
Assert.True(sheet["A1"].Style.Font.Bold);
}

[Fact]
public async Task CellValueConverter_ClassWithConverterOnCustomType()
{
// 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, "PercentType");
var obj = new ClassWithCellValueConverterOnCustomType
{
Property = "Abc123", ComplexProperty = null, PercentType = new Percent(123)
};

// Act
await spreadsheet.AddAsRowAsync(obj, CellValueConverterContext.Default.ClassWithCellValueConverterOnCustomType);
await spreadsheet.FinishAsync();

// Assert
using var sheet = SpreadsheetAssert.SingleSheet(stream);
Assert.Equal("Abc123", sheet["A1"].StringValue);

Assert.Equal("-", sheet["B1"].StringValue);

Assert.Equal(123, sheet["C1"].DecimalValue);
Assert.True(sheet["C1"].Style.Font.Bold);
}
}
9 changes: 8 additions & 1 deletion SpreadCheetah.SourceGenerator/WorksheetRowGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ private static RowType AnalyzeTypeProperties(ITypeSymbol classType, LocationInfo
if (property.IsWriteOnly || property.IsStatic || property.DeclaredAccessibility != Accessibility.Public)
continue;

if (!property.Type.IsSupportedType())
if (!property.Type.IsSupportedType() && !HasCellValueConverter(property))
{
unsupportedPropertyTypeNames.Add(property.Type.Name);
continue;
Expand Down Expand Up @@ -169,6 +169,13 @@ private static IEnumerable<IPropertySymbol> GetClassAndBaseClassProperties(IType
_ => throw new ArgumentOutOfRangeException(nameof(classType), "Unsupported inheritance strategy type")
};
}


private static bool HasCellValueConverter(IPropertySymbol property)
{
return property.GetAttributes().Any(data => string.Equals(data.AttributeClass?.ToDisplayString(),
Attributes.CellValueConverter, StringComparison.Ordinal));
}

private static void Execute(ContextClass? contextClass, SourceProductionContext context)
{
Expand Down
Loading