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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,35 @@ private static bool IsAttributeApplicable(AttributeInfo attributeInfo, ISymbol s
if (attributeInfo is UnixTimestampAttributeInfo && symbol is IPropertySymbol property)
{
var type = property.Type;
if (type is INamedTypeSymbol { IsGenericType: true } nullable)
type = nullable.TypeArguments[0];

return type.Name is nameof(DateTime) or nameof(DateTimeOffset);
// Only unwrap genuine Nullable<T>, not arbitrary generics such as List<DateTime>.
if (type is INamedTypeSymbol named &&
named.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T &&
named.TypeArguments.Length == 1)
{
type = named.TypeArguments[0];
}

return IsDateTimeType(type);
}

return true;
}

/// <summary>
/// Determines whether the given type is System.DateTime or System.DateTimeOffset
/// using robust symbol identity rather than brittle name comparisons.
/// </summary>
private static bool IsDateTimeType(ITypeSymbol type)
{
if (type.SpecialType == SpecialType.System_DateTime)
return true;

// DateTimeOffset has no SpecialType, so identify it by its fully-qualified name.
return type.Name == nameof(DateTimeOffset)
&& type.ContainingNamespace?.ToDisplayString() == "System";
}

/// <summary>
/// Validates all attributes on a symbol and reports diagnostics.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,74 @@ public async Task ProcessAttributes_UnixTimestampOnDateTimeOffsetProperty_Should
await Assert.That(attributes[0]).IsTypeOf<UnixTimestampAttributeInfo>();
}

[Test]
public async Task ProcessAttributes_UnixTimestampOnNullableDateTimeProperty_ShouldBeIncluded()
{
// Arrange: [UnixTimestamp] on DateTime? must be unwrapped before the compatibility check
// and kept, just like the non-nullable variant.
var registry = new AttributeHandlerRegistry();
var mockHandler = new Mock<IAttributeHandler>();
var mockAttributeData = MockSymbolFactory.CreateAttributeData("Goa.Clients.Dynamo.UnixTimestampAttribute");
var unixTimestampInfo = new UnixTimestampAttributeInfo
{
AttributeData = mockAttributeData,
AttributeTypeName = "Goa.Clients.Dynamo.UnixTimestampAttribute",
Format = Generator.Models.UnixTimestampFormat.Seconds
};

mockHandler.Setup(h => h.CanHandle(It.IsAny<AttributeData>())).Returns(true);
mockHandler.Setup(h => h.ParseAttribute(It.IsAny<AttributeData>())).Returns(unixTimestampInfo);

registry.RegisterHandler(mockHandler.Object);

// Create a property symbol with DateTime? (Nullable<DateTime>) type
var nullableDateTime = MockSymbolFactory.CreateNullableType(MockSymbolFactory.PrimitiveTypes.DateTime).Object;
var propertySymbol = MockSymbolFactory.CreatePropertySymbol("ExpiresAt", nullableDateTime);
propertySymbol.Setup(p => p.GetAttributes())
.Returns(System.Collections.Immutable.ImmutableArray.Create(mockAttributeData));

// Act
var attributes = registry.ProcessAttributes(propertySymbol.Object);

// Assert: UnixTimestamp on DateTime? should be kept
await Assert.That(attributes).Count().IsEqualTo(1);
await Assert.That(attributes[0]).IsTypeOf<UnixTimestampAttributeInfo>();
}

[Test]
public async Task ProcessAttributes_UnixTimestampOnNullableDateTimeOffsetProperty_ShouldBeIncluded()
{
// Arrange: [UnixTimestamp] on DateTimeOffset? must be unwrapped before the compatibility check
// and kept, just like the non-nullable variant.
var registry = new AttributeHandlerRegistry();
var mockHandler = new Mock<IAttributeHandler>();
var mockAttributeData = MockSymbolFactory.CreateAttributeData("Goa.Clients.Dynamo.UnixTimestampAttribute");
var unixTimestampInfo = new UnixTimestampAttributeInfo
{
AttributeData = mockAttributeData,
AttributeTypeName = "Goa.Clients.Dynamo.UnixTimestampAttribute",
Format = Generator.Models.UnixTimestampFormat.Seconds
};

mockHandler.Setup(h => h.CanHandle(It.IsAny<AttributeData>())).Returns(true);
mockHandler.Setup(h => h.ParseAttribute(It.IsAny<AttributeData>())).Returns(unixTimestampInfo);

registry.RegisterHandler(mockHandler.Object);

// Create a property symbol with DateTimeOffset? (Nullable<DateTimeOffset>) type
var nullableDateTimeOffset = MockSymbolFactory.CreateNullableType(MockSymbolFactory.PrimitiveTypes.DateTimeOffset).Object;
var propertySymbol = MockSymbolFactory.CreatePropertySymbol("UpdatedAt", nullableDateTimeOffset);
propertySymbol.Setup(p => p.GetAttributes())
.Returns(System.Collections.Immutable.ImmutableArray.Create(mockAttributeData));

// Act
var attributes = registry.ProcessAttributes(propertySymbol.Object);

// Assert: UnixTimestamp on DateTimeOffset? should be kept
await Assert.That(attributes).Count().IsEqualTo(1);
await Assert.That(attributes[0]).IsTypeOf<UnixTimestampAttributeInfo>();
}

[Test]
public async Task ProcessAttributes_HandlerReturnsNull_ShouldNotAddToResults()
{
Expand Down
Loading