Skip to content

Conversation

@dvoituron
Copy link
Collaborator

@dvoituron dvoituron commented Sep 22, 2025

[dev-v5] Convert the Calendar and DatePicker components to a generic type

The FluentCalendar and FluentDatePicker components are now generic components, so you can use it with other date types such as DateTime?, DateTime, DateOnly? or DateOnly.
Blazor will automatically infer the type based on the value you provide to the Value or SelectedDates parameters.
You can also explicitly set the type using the generic type parameter: TValue=“DateOnly?”.

Example

<FluentDatePicker Label="DateTime?" @bind-Value="@NullableDateTime" />
<FluentDatePicker Label="DateTime" @bind-Value="@DateTime" />
<FluentDatePicker Label="DateOnly?" @bind-Value="@NullableDateOnly" />
<FluentDatePicker Label="DateOnly" @bind-Value="@DateOnly" />

@code
{
    private DateTime? NullableDateTime;
    private DateTime DateTime = DateTime.Today;
    private DateOnly? NullableDateOnly;
    private DateOnly DateOnly = DateOnly.FromDateTime(DateTime.Today);
}
image

Unit Tests

Added and updated

image

… selected dates, improving type safety and flexibility.
Updated calendar components to support nullable DateTime values and generic date representations. Key changes include:
- Modified `DisabledDate` methods to accept `DateTime?`.
- Changed `SelectedDay` in `CalendarSelection.razor` to a non-nullable `DateTime`.
- Updated `DisabledDateFunc` in `FluentCalendarBase.cs` to use a generic type `TValue`.
- Refactored `OnSelectedDateHandlerAsync` to accept `TValue`.
- Implemented conversions to `TValue` in various methods for consistency.
- Enhanced `PickerMonthChanged` to handle nullable month values in `FluentDatePicker.razor`.
- Streamlined `OnSelectedDateAsync` to directly use `TValue`.

These changes improve the flexibility and robustness of the calendar components.
@github-actions
Copy link

github-actions bot commented Sep 22, 2025

✅ All tests passed successfully

Details on your Workflow / Core Tests page.

@dvoituron dvoituron changed the title [dev-v5] POC to convert the Calendar and DatePicker to a generic type [dev-v5] Convert the Calendar and DatePicker components to a generic type Sep 30, 2025
@github-actions
Copy link

github-actions bot commented Sep 30, 2025

Summary - Unit Tests Code Coverage

Summary
Generated on: 10/02/2025 - 18:29:19
Coverage date: 10/02/2025 - 18:29:07
Parser: Cobertura
Assemblies: 1
Classes: 185
Files: 253
Line coverage: 98.9% (6028 of 6095)
Covered lines: 6028
Uncovered lines: 67
Coverable lines: 6095
Total lines: 24031
Branch coverage: 92.6% (3471 of 3748)
Covered branches: 3471
Total branches: 3748
Method coverage: Feature is only available for sponsors
Tag: 4372_18202052189

Coverage

Microsoft.FluentUI.AspNetCore.Components - 98.9%
Name Line Branch
Microsoft.FluentUI.AspNetCore.Components 98.9% 92.6%
Microsoft.FluentUI.AspNetCore.Components.AccordionItemEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.AdditionalAttributesExtensions 100% 100%
Microsoft.FluentUI.AspNetCore.Components.CachedServices 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Calendar.CalendarExtended 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Calendar.CalendarTitles`1 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Calendar.CalendarTValue 100% 100%
Microsoft.FluentUI.AspNetCore.Components.ColumnBase`1 97.2% 90.2%
Microsoft.FluentUI.AspNetCore.Components.ColumnKeyGridSort`1 94.4% 75%
Microsoft.FluentUI.AspNetCore.Components.ColumnOptionsUISettings 100%
Microsoft.FluentUI.AspNetCore.Components.ColumnResizeOptions`1 100% 100%
Microsoft.FluentUI.AspNetCore.Components.ColumnResizeUISettings 100%
Microsoft.FluentUI.AspNetCore.Components.ColumnSortUISettings 100%
Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure.Defer 100%
Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure.InternalGr
idContext`1
100% 100%
Microsoft.FluentUI.AspNetCore.Components.DateTimeProvider 100% 100%
Microsoft.FluentUI.AspNetCore.Components.DateTimeProviderContext 95.6% 92.8%
Microsoft.FluentUI.AspNetCore.Components.DefaultStyles 100%
Microsoft.FluentUI.AspNetCore.Components.Dialog.MessageBox.FluentMessageBox 100% 100%
Microsoft.FluentUI.AspNetCore.Components.DialogEventArgs 100% 92.8%
Microsoft.FluentUI.AspNetCore.Components.DialogInstance 100% 100%
Microsoft.FluentUI.AspNetCore.Components.DialogOptions 100%
Microsoft.FluentUI.AspNetCore.Components.DialogOptionsFooter 100% 100%
Microsoft.FluentUI.AspNetCore.Components.DialogOptionsFooterAction 100% 100%
Microsoft.FluentUI.AspNetCore.Components.DialogOptionsHeader 100%
Microsoft.FluentUI.AspNetCore.Components.DialogResult 100% 100%
Microsoft.FluentUI.AspNetCore.Components.DialogResult`1 100%
Microsoft.FluentUI.AspNetCore.Components.DialogService 100% 82.3%
Microsoft.FluentUI.AspNetCore.Components.DialogToggleEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.DropdownEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.Extensions.DateTimeExtensions 100% 92%
Microsoft.FluentUI.AspNetCore.Components.Extensions.DisplayAttributeExtensi
ons
100% 100%
Microsoft.FluentUI.AspNetCore.Components.Extensions.EnumExtensions 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Extensions.FieldSizeExtensions 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Extensions.FluentInputExtensions 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FileSizeConverter 100%
Microsoft.FluentUI.AspNetCore.Components.FluentAccordion 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentAccordionItem 100% 95%
Microsoft.FluentUI.AspNetCore.Components.FluentAnchorButton 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentAvatar 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentBadge 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentButton 98.4% 90.6%
Microsoft.FluentUI.AspNetCore.Components.FluentCalendar`1 98.1% 88.3%
Microsoft.FluentUI.AspNetCore.Components.FluentCalendarBase`1 100% 94.4%
Microsoft.FluentUI.AspNetCore.Components.FluentCalendarDay`1 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentCalendarMonth`1 100% 92.8%
Microsoft.FluentUI.AspNetCore.Components.FluentCalendarYear`1 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentCard 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentCheckbox 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentCombobox`1 100%
Microsoft.FluentUI.AspNetCore.Components.FluentComponentBase 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentCompoundButton 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentCounterBadge 100% 96.1%
Microsoft.FluentUI.AspNetCore.Components.FluentDataGrid`1 91.6% 87.5%
Microsoft.FluentUI.AspNetCore.Components.FluentDataGridCell`1 100% 86.8%
Microsoft.FluentUI.AspNetCore.Components.FluentDataGridRow`1 97.1% 97.2%
Microsoft.FluentUI.AspNetCore.Components.FluentDatePicker`1 97.4% 85.2%
Microsoft.FluentUI.AspNetCore.Components.FluentDialog 97.5% 88%
Microsoft.FluentUI.AspNetCore.Components.FluentDialogBody 100% 91.6%
Microsoft.FluentUI.AspNetCore.Components.FluentDialogInstance 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentDialogProvider 100% 71.7%
Microsoft.FluentUI.AspNetCore.Components.FluentDivider 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentErrorBoundary 97.6% 93.7%
Microsoft.FluentUI.AspNetCore.Components.FluentField 100% 96.5%
Microsoft.FluentUI.AspNetCore.Components.FluentFieldCondition 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentFieldConditionItem 100%
Microsoft.FluentUI.AspNetCore.Components.FluentFieldConditionOptions 100%
Microsoft.FluentUI.AspNetCore.Components.FluentFieldExtensions 100%
Microsoft.FluentUI.AspNetCore.Components.FluentFieldParameterSelector 100% 98.7%
Microsoft.FluentUI.AspNetCore.Components.FluentGrid 100% 90%
Microsoft.FluentUI.AspNetCore.Components.FluentGridItem 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentHighlighter 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentIcon`1 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentImage 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1 93.8% 50%
Microsoft.FluentUI.AspNetCore.Components.FluentInputFile 100% 93.3%
Microsoft.FluentUI.AspNetCore.Components.FluentInputFileBuffer 100%
Microsoft.FluentUI.AspNetCore.Components.FluentInputFileErrorEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.FluentInputFileEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.FluentInputImmediateBase`1 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentJSModule 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentKeyCode 100% 92.8%
Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs 100% 75%
Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeProvider 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentKeyPressEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.FluentLabel 100%
Microsoft.FluentUI.AspNetCore.Components.FluentLayout 100% 88.8%
Microsoft.FluentUI.AspNetCore.Components.FluentLayoutHamburger 100% 96.4%
Microsoft.FluentUI.AspNetCore.Components.FluentLayoutItem 100% 91%
Microsoft.FluentUI.AspNetCore.Components.FluentLink 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentListBase`1 97.1% 92.8%
Microsoft.FluentUI.AspNetCore.Components.FluentLocalizerExtensions 100%
Microsoft.FluentUI.AspNetCore.Components.FluentLocalizerInternal 100%
Microsoft.FluentUI.AspNetCore.Components.FluentMenu 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentMenuButton 100% 71.4%
Microsoft.FluentUI.AspNetCore.Components.FluentMenuItem 100% 90.4%
Microsoft.FluentUI.AspNetCore.Components.FluentMenuList 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentMessageBar 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentMultiSplitter 100% 91.8%
Microsoft.FluentUI.AspNetCore.Components.FluentMultiSplitterEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.FluentMultiSplitterPane 100% 89.4%
Microsoft.FluentUI.AspNetCore.Components.FluentMultiSplitterResizeEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.FluentOption 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentPaginator 100% 94.4%
Microsoft.FluentUI.AspNetCore.Components.FluentPopover 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentProgress 100%
Microsoft.FluentUI.AspNetCore.Components.FluentProgressBar 100% 95.4%
Microsoft.FluentUI.AspNetCore.Components.FluentProgressRing 100%
Microsoft.FluentUI.AspNetCore.Components.FluentProviders 100%
Microsoft.FluentUI.AspNetCore.Components.FluentRadio`1 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentRadioGroup`1 100% 88.8%
Microsoft.FluentUI.AspNetCore.Components.FluentRatingDisplay 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentSelect`1 100%
Microsoft.FluentUI.AspNetCore.Components.FluentServiceBase`1 100%
Microsoft.FluentUI.AspNetCore.Components.FluentServiceProviderException`1 100%
Microsoft.FluentUI.AspNetCore.Components.FluentSkeleton 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentSlider`1 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentSpacer 100%
Microsoft.FluentUI.AspNetCore.Components.FluentSpinner 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentSplitButton 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentStack 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentStatus 100%
Microsoft.FluentUI.AspNetCore.Components.FluentSwitch 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentTab 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentTabs 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentText 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentTextArea 100% 70%
Microsoft.FluentUI.AspNetCore.Components.FluentTextInput 100% 81.2%
Microsoft.FluentUI.AspNetCore.Components.FluentToggleButton 100% 91.6%
Microsoft.FluentUI.AspNetCore.Components.FluentTooltip 100% 95%
Microsoft.FluentUI.AspNetCore.Components.FluentTooltipProvider 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentTreeItem 100% 95.3%
Microsoft.FluentUI.AspNetCore.Components.FluentTreeView 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FreeOptionOutput 100%
Microsoft.FluentUI.AspNetCore.Components.GridItemsProviderRequest`1 100% 100%
Microsoft.FluentUI.AspNetCore.Components.GridItemsProviderResult 100%
Microsoft.FluentUI.AspNetCore.Components.GridItemsProviderResult`1 100%
Microsoft.FluentUI.AspNetCore.Components.GridSort`1 100% 100%
Microsoft.FluentUI.AspNetCore.Components.HighlighterSplitter 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Icon 100% 92.1%
Microsoft.FluentUI.AspNetCore.Components.IconFromImage 100%
Microsoft.FluentUI.AspNetCore.Components.IconInfo 100%
Microsoft.FluentUI.AspNetCore.Components.IFluentComponentChangeAfterKeyPres
s
100% 100%
Microsoft.FluentUI.AspNetCore.Components.IFluentLocalizer 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Infrastructure.EventCallbackSubscr
ibable`1
100% 100%
Microsoft.FluentUI.AspNetCore.Components.Infrastructure.EventCallbackSubscr
iber`1
100% 87.5%
Microsoft.FluentUI.AspNetCore.Components.InputFileInstance 100% 100%
Microsoft.FluentUI.AspNetCore.Components.InputFileOptions 100%
Microsoft.FluentUI.AspNetCore.Components.InternalListContext`1 100%
Microsoft.FluentUI.AspNetCore.Components.KeyCodeService 100% 85.7%
Microsoft.FluentUI.AspNetCore.Components.KeyPress 100%
Microsoft.FluentUI.AspNetCore.Components.LayoutHamburgerEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.LibraryTooltipOptions 100%
Microsoft.FluentUI.AspNetCore.Components.Localization.LanguageResource 100% 100%
Microsoft.FluentUI.AspNetCore.Components.MenuItemEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.MessageBoxOptions 100%
Microsoft.FluentUI.AspNetCore.Components.Migration.AppearanceExtensions 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Migration.FluentInputAppearanceExt
ensions
100% 100%
Microsoft.FluentUI.AspNetCore.Components.Migration.TooltipPositionExtension 100% 100%
Microsoft.FluentUI.AspNetCore.Components.PaginationState 100% 81.2%
Microsoft.FluentUI.AspNetCore.Components.ProgressFileDetails 100%
Microsoft.FluentUI.AspNetCore.Components.PropertyColumn`2 100% 81.5%
Microsoft.FluentUI.AspNetCore.Components.RadioEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.RangeOfDates 100% 100%
Microsoft.FluentUI.AspNetCore.Components.SelectAllTemplateArgs 100%
Microsoft.FluentUI.AspNetCore.Components.SelectColumn`1 97.2% 94%
Microsoft.FluentUI.AspNetCore.Components.ServiceProviderExtensions 100%
Microsoft.FluentUI.AspNetCore.Components.SortedProperty 100%
Microsoft.FluentUI.AspNetCore.Components.SpacingExtensions 100% 97.2%
Microsoft.FluentUI.AspNetCore.Components.TabChangeEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.TemplateColumn`1 100% 25%
Microsoft.FluentUI.AspNetCore.Components.TooltipEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.TotalItemCountChangedEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.TreeItemChangedEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.TreeViewItem 100% 100%
Microsoft.FluentUI.AspNetCore.Components.TreeViewItemExpandedEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.UploadedFileDetails 100%
Microsoft.FluentUI.AspNetCore.Components.Utilities.AddTag 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Utilities.CssBuilder 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Utilities.Debounce 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Utilities.Identifier 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Utilities.IdentifierContext 100% 75%
Microsoft.FluentUI.AspNetCore.Components.Utilities.InlineStyleBuilder 100% 92.8%
Microsoft.FluentUI.AspNetCore.Components.Utilities.RangeOf`1 96.7% 97.2%
Microsoft.FluentUI.AspNetCore.Components.Utilities.StyleBuilder 100% 100%
Microsoft.FluentUI.AspNetCore.Components.ZIndex 100%

@dvoituron dvoituron marked this pull request as ready for review September 30, 2025 16:22
@dvoituron dvoituron requested a review from vnbaaij as a code owner September 30, 2025 16:22
@MarvinKlein1508
Copy link
Collaborator

@dvoituron just curious. Does this also support TimeOnly and TimeOnly? for the time picker?

@dvoituron
Copy link
Collaborator Author

Yes. Check the sample in the description 🙂

@dvoituron
Copy link
Collaborator Author

Oh sorry. Not yet. The TimePicker is not yet migrated. But I will do that

@MarvinKlein1508 MarvinKlein1508 mentioned this pull request Sep 30, 2025
33 tasks
@Tyme-Bleyaert
Copy link
Collaborator

Tyme-Bleyaert commented Oct 1, 2025

Nice!

Also i have more of a general question.
When we have something like :

protected FluentCalendarBase(LibraryConfiguration configuration) : base(configuration)
{
    if (typeof(TValue).IsNotDateType())
    {
        throw new InvalidOperationException($"The type parameter {typeof(TValue)} is not supported. Supported types are DateTime, DateTime?, DateOnly, and DateOnly?.");
    }
}

Can we provide our own roslyn analyzer(s) for things like this?
[CodePilot example]:

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class FluentCalendarTValueUsageAnalyzer : DiagnosticAnalyzer
{
   public const string DiagnosticId = "FUB002";
   private const string Title = "Unsupported TValue on FluentCalendarBase";
   private const string MessageFormat = "Class '{0}' inherits 'FluentCalendarBase<{1}>' but '{1}' is not a supported date type. Supported types: DateTime, DateTime?, DateOnly, DateOnly?, DateTimeOffset, DateTimeOffset?.";
   private const string Description = "FluentCalendarBase<TValue> only supports specific date types. Use one of the supported types or keep the type parameter generic.";
   private const string Category = "Usage";

   private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
       DiagnosticId,
       Title,
       MessageFormat,
       Category,
       DiagnosticSeverity.Error,
       isEnabledByDefault: true,
       description: Description);

   public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

   public override void Initialize(AnalysisContext context)
   {
       context.EnableConcurrentExecution();
       context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);

       context.RegisterSymbolAction(AnalyzeNamedType, SymbolKind.NamedType);
   }

   private static void AnalyzeNamedType(SymbolAnalysisContext context)
   {
       var namedType = (INamedTypeSymbol)context.Symbol;

       // Only examine classes (includes partials generated for .razor)
       if (namedType.TypeKind != TypeKind.Class)
           return;

       // Find a base type that is FluentCalendarBase<T>
       var compilation = context.Compilation;
       var fluentCalendarBaseSymbol = compilation.GetTypeByMetadataName("Microsoft.FluentUI.AspNetCore.Components.FluentCalendarBase`1");

       INamedTypeSymbol? foundConstructedBase = null;
       for (var bt = namedType.BaseType; bt != null; bt = bt.BaseType)
       {
           if (bt is INamedTypeSymbol namedBase)
           {
               if (fluentCalendarBaseSymbol is not null)
               {
                   if (SymbolEqualityComparer.Default.Equals(namedBase.OriginalDefinition, fluentCalendarBaseSymbol))
                   {
                       foundConstructedBase = namedBase;
                       break;
                   }
               }
               else
               {
                   // Fallback: match by name if metadata lookup failed
                   if (namedBase.OriginalDefinition?.Name == "FluentCalendarBase`1")
                   {
                       foundConstructedBase = namedBase;
                       break;
                   }
               }
           }
       }

       if (foundConstructedBase is null)
           return; // not inheriting FluentCalendarBase<T>

       // Get the TValue type argument
       if (foundConstructedBase.TypeArguments.Length != 1)
           return;

       var tArg = foundConstructedBase.TypeArguments[0];

       // If TValue is a type parameter (e.g. class Foo<TValue> : FluentCalendarBase<TValue>), skip - cannot decide at compile time
       if (tArg.TypeKind == TypeKind.TypeParameter)
           return;

       // Build symbols for supported types
       var dateTimeSym = compilation.GetSpecialType(SpecialType.System_DateTime);
       var dateTimeOffsetSym = compilation.GetSpecialType(SpecialType.System_DateTimeOffset);
       var dateOnlySym = compilation.GetTypeByMetadataName("System.DateOnly");
       var nullableOriginal = compilation.GetSpecialType(SpecialType.System_Nullable_T);

       bool IsSupported(ITypeSymbol type)
       {
           // Nullable<T>
           if (type is INamedTypeSymbol named && SymbolEqualityComparer.Default.Equals(named.OriginalDefinition, nullableOriginal))
           {
               var inner = named.TypeArguments.Length > 0 ? named.TypeArguments[0] : null;
               if (inner is null) return false;
               return IsSupported(inner);
           }

           if (SymbolEqualityComparer.Default.Equals(type, dateTimeSym))
               return true;

           if (SymbolEqualityComparer.Default.Equals(type, dateTimeOffsetSym))
               return true;

           if (dateOnlySym is not null && SymbolEqualityComparer.Default.Equals(type, dateOnlySym))
               return true;

           return false;
       }

       if (IsSupported(tArg))
           return;

       // Report diagnostic. Try to get a good location: use base list syntax if available, otherwise class identifier.
       Location? diagLocation = null;
       foreach (var syntaxRef in namedType.DeclaringSyntaxReferences)
       {
           var node = syntaxRef.GetSyntax(context.CancellationToken);
           var classDecl = node as Microsoft.CodeAnalysis.CSharp.Syntax.ClassDeclarationSyntax;
           if (classDecl == null)
               continue;

           // Try to find the base type syntax that corresponds to FluentCalendarBase<...>
           var baseList = classDecl.BaseList;
           if (baseList != null)
           {
               foreach (var baseTypeSyntax in baseList.Types)
               {
                   // locate the syntax for FluentCalendarBase
                   var text = baseTypeSyntax.Type.ToString();
                   if (text.StartsWith("FluentCalendarBase", StringComparison.Ordinal) || text.Contains("FluentCalendarBase<"))
                   {
                       diagLocation = baseTypeSyntax.GetLocation();
                       break;
                   }
               }
           }

           if (diagLocation == null)
           {
               diagLocation = classDecl.Identifier.GetLocation();
           }

           if (diagLocation != null)
               break;
       }

       var typeDisplay = tArg.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
       var diag = Diagnostic.Create(Rule, diagLocation ?? namedType.Locations[0], namedType.Name, typeDisplay);
       context.ReportDiagnostic(diag);
   }
}

It won't be a big issue but if we can spot the issue at compile time instead of runtime, isn't that better?
What do you guys think?

@dvoituron
Copy link
Collaborator Author

Can we provide our own roslyn analyzer(s) for things like this?

This could help some developers. Although this problem already exists in several other components, such as FluentSlider, for example.

However, this is not the purpose of this library. Adding an analyzer could be done in another NuGet Package that requires a reference to Microsoft.CodeAnalysis.Common (and we cannot have this reference in this Lib).

@dvoituron dvoituron enabled auto-merge (squash) October 2, 2025 13:06
@dvoituron dvoituron merged commit 16469d7 into dev-v5 Oct 2, 2025
4 checks passed
@dvoituron dvoituron deleted the users/dvoituron/dev-v5/poc-calendar-generic branch October 2, 2025 18:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants