From 7250e604d23007d109bd71713a05d01336872c32 Mon Sep 17 00:00:00 2001 From: Philippe Matray Date: Thu, 13 Aug 2026 14:03:44 +0200 Subject: [PATCH 1/8] chore(#301): scaffold draft PR for the editorconfig enforcement gate From 2b696d6222f5dea0c3f03bde73f5de62c18d2383 Mon Sep 17 00:00:00 2001 From: Philippe Matray Date: Thu, 13 Aug 2026 14:06:51 +0200 Subject: [PATCH 2/8] ci: add a Format target that verifies dotnet format is clean --- build/Build.cs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/build/Build.cs b/build/Build.cs index 0d4f59d9..75fdaea6 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -119,6 +119,28 @@ class Build : NukeBuild .EnableNoRestore()); }); + // Verifies the tree matches .editorconfig. Fails on any diff; changes nothing (#301). + // + // ⚠️ Deliberately NOT a DependsOn of Test or Pack, and deliberately not EnforceCodeStyleInBuild. + // Those IDE* analyzers are opt-in at build time, and switching them on next to this repo's + // TreatWarningsAsErrors=true would make one missing brace break `dotnet build` mid-edit and add + // an analyzer pass to every incremental build. The rules are enforced where regression actually + // has to be caught — CI — and `./build.sh Test` stays a correctness-only, fast path. + // + // Before #301 nothing read .editorconfig at all: its severities say `warning`, the build turns + // warnings into errors, and the two never met because EnforceCodeStyleInBuild was unset and no + // workflow ran `dotnet format`. 574 violations across 201 files had accumulated behind that gap. + // + // Raw DotNet(...) rather than a typed task: Nuke exposes no DotNetFormat wrapper covering + // --verify-no-changes, and the raw call is exactly the command a developer runs to fix a + // failure here (drop --verify-no-changes to apply). + Target Format => _ => _ + .DependsOn(Restore) + .Executes(() => + { + DotNet($"format {Solution.Path} --verify-no-changes --no-restore"); + }); + Target Test => _ => _ .DependsOn(Compile) .Produces(TestResultsDirectory / "**/*.trx") From e5f7832ede0c4e5d8e2949693bb5e81ad90aa8d5 Mon Sep 17 00:00:00 2001 From: Philippe Matray Date: Thu, 13 Aug 2026 14:09:28 +0200 Subject: [PATCH 3/8] style: apply dotnet format whitespace across the solution --- .../Dialogs/EditFormDialog.razor.cs | 2 +- .../Dialogs/SimpleFormDialog.razor.cs | 2 +- .../Components/Layout/FormCraftTheme.cs | 82 ++++++++++++++----- .../Components/Layout/FormPageLayout.razor.cs | 2 +- .../Pages/AttributeBasedForms.razor.cs | 2 +- .../Components/Pages/CustomRenderers.razor.cs | 2 +- .../Components/Pages/DialogDemo.razor.cs | 2 +- .../Components/Pages/FieldGroups.razor.cs | 2 +- .../Components/Pages/FileUploadDemo.razor.cs | 2 +- .../Components/Pages/FluentForm.razor.cs | 2 +- .../Pages/FluentValidationDemo.razor.cs | 2 +- .../Components/Pages/FormSlots.razor.cs | 2 +- .../Components/Pages/ImprovedForm.razor.cs | 2 +- .../Pages/PasswordFieldDemo.razor.cs | 26 ++++-- .../Components/Pages/SecurityDemo.razor.cs | 5 +- .../Components/Pages/SimplifiedForm.razor.cs | 2 +- .../Components/Pages/StepperForm.razor.cs | 2 +- .../Components/Pages/TabbedForm.razor.cs | 2 +- .../Shared/ApiGuidelinesTable.razor.cs | 2 +- .../Components/Shared/DemoPageHeader.razor.cs | 2 +- .../Shared/DocumentationPage.razor.cs | 2 +- .../Shared/FormDemoSection.razor.cs | 2 +- .../Components/Shared/FormGuidelines.razor.cs | 2 +- .../Shared/FormSuccessDisplay.razor.cs | 2 +- .../Shared/GuidelinesTable.razor.cs | 2 +- .../Helpers/GuidelineHelpers.cs | 2 +- .../Models/ContactModel.cs | 2 +- .../Models/EmployeeModel.cs | 2 +- .../Models/GuidelineItem.cs | 2 +- .../Models/JobApplicationModel.cs | 2 +- .../Models/LoginFormModel.cs | 2 +- .../Models/ProductModel.cs | 2 +- .../Models/UserRegistrationModel.cs | 2 +- FormCraft.DemoBlazorApp/Program.cs | 2 +- .../Services/MarkdownService.cs | 2 +- .../Services/VersionService.cs | 2 +- .../Extensions/FieldBuilderExtensions.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 2 +- .../CollectionFieldComponent.razor.cs | 21 +++-- .../MudBlazorColorPickerComponent.razor.cs | 2 +- .../MudBlazorColorPickerRenderer.cs | 2 +- .../MudBlazorRatingComponent.razor.cs | 2 +- .../CustomFields/MudBlazorRatingRenderer.cs | 2 +- .../MudBlazorSliderComponent.razor.cs | 2 +- .../CustomFields/MudBlazorSliderRenderer.cs | 2 +- .../FormContainer/FormCraftComponent.razor.cs | 5 +- .../FieldValidationMessage.razor.cs | 2 +- ...dBlazorAutocompleteFieldComponent.razor.cs | 3 +- .../MudBlazorBooleanFieldComponent.razor.cs | 2 +- .../MudBlazorBooleanFieldRenderer.cs | 2 +- .../MudBlazorDateTimeFieldComponent.razor.cs | 2 +- .../MudBlazorDateTimeFieldRenderer.cs | 2 +- ...MudBlazorFileUploadFieldComponent.razor.cs | 2 +- .../MudBlazorFileUploadFieldRenderer.cs | 2 +- ...BlazorMultipleFileUploadComponent.razor.cs | 5 +- .../MudBlazorMultipleFileUploadRenderer.cs | 2 +- .../MudBlazorLookupDialog.razor.cs | 15 ++-- .../LovField/LovSelectionDialog.razor.cs | 8 +- .../MudBlazorLovFieldComponent.razor.cs | 21 +++-- .../MudBlazorNumericFieldComponent.razor.cs | 2 +- .../MudBlazorNumericFieldRenderer.cs | 2 +- .../MudBlazorSelectFieldComponent.razor.cs | 2 +- .../MudBlazorSelectFieldRenderer.cs | 2 +- .../TextField/MudBlazorTextFieldRenderer.cs | 2 +- .../Builders/FieldBuilderTests.cs | 2 +- .../FieldConfigurationWrapperTests.cs | 2 +- .../Builders/FieldGroupBuilderTests.cs | 2 +- .../Builders/FormBuilderTests.cs | 2 +- .../Builders/ValidatorWrapperTests.cs | 2 +- .../Components/DynamicFormValidatorTests.cs | 2 +- .../Components/FieldValidationMessageTests.cs | 2 +- .../Components/FormCraftComponentTests.cs | 2 +- .../Core/FieldConfigurationTests.cs | 2 +- .../Core/FieldDependencyTests.cs | 2 +- .../Core/FileUploadConfigurationTests.cs | 2 +- FormCraft.UnitTests/Core/SelectOptionTests.cs | 2 +- .../Core/ValidationResultTests.cs | 2 +- .../AttributeFormBuilderExtensionsTests.cs | 2 +- .../Extensions/FieldBuilderExtensionsTests.cs | 2 +- .../Extensions/FileUploadExtensionsTests.cs | 2 +- .../FluentFormBuilderExtensionsTests.cs | 2 +- .../FormConfigurationExtensionsTests.cs | 2 +- .../ServiceCollectionExtensionsTests.cs | 2 +- .../ForMudBlazor/PasswordFieldTests.cs | 2 +- FormCraft.UnitTests/GlobalUsings.cs | 2 +- .../Integration/CompleteFormWorkflowTests.cs | 2 +- .../DependencyInjectionIntegrationTests.cs | 2 +- .../Integration/FileUploadIntegrationTests.cs | 2 +- .../Integration/RealModelTemplateTests.cs | 2 +- .../ValidationPipelineIntegrationTests.cs | 2 +- .../Rendering/BoolFieldRendererTests.cs | 2 +- .../Rendering/CustomFieldRendererTests.cs | 2 +- .../ColorPickerRendererTests.cs | 2 +- .../CustomRenderers/RatingRendererTests.cs | 2 +- .../Rendering/DateTimeFieldRendererTests.cs | 2 +- .../Rendering/DecimalFieldRendererTests.cs | 2 +- .../Rendering/DoubleFieldRendererTests.cs | 2 +- .../Rendering/FieldRenderContextTests.cs | 2 +- .../Rendering/FieldRendererServiceTests.cs | 2 +- .../Rendering/FileUploadFieldRendererTests.cs | 2 +- .../Rendering/IntFieldRendererTests.cs | 2 +- .../Rendering/StringFieldRendererTests.cs | 2 +- .../Security/AuditLogServiceTests.cs | 2 +- .../Security/RateLimitServiceTests.cs | 2 +- .../Security/SecurityBuilderTests.cs | 2 +- .../Templates/ContactFormTemplateTests.cs | 2 +- .../Validators/AsyncValidatorTests.cs | 8 +- .../Validators/CustomValidatorTests.cs | 8 +- .../FluentValidationAdapterTests.cs | 2 +- .../FluentValidationExtensionsTests.cs | 2 +- .../Validators/RequiredValidatorTests.cs | 2 +- FormCraft/Components/FieldComponentBase.cs | 2 +- .../Abstractions/ICustomFieldRenderer.cs | 2 +- .../Forms/Abstractions/IFieldDependency.cs | 2 +- .../Forms/Abstractions/IFieldRenderContext.cs | 2 +- .../Forms/Abstractions/IFieldRenderer.cs | 2 +- .../Abstractions/IFieldRendererService.cs | 2 +- .../Forms/Abstractions/IFieldValidator.cs | 2 +- .../Rendering/IBooleanFieldComponent.cs | 2 +- .../Rendering/IDateTimeFieldComponent.cs | 2 +- .../Abstractions/Rendering/IFieldComponent.cs | 2 +- .../Rendering/IFileUploadFieldComponent.cs | 2 +- .../Rendering/INumericFieldComponent.cs | 2 +- .../Rendering/ISelectFieldComponent.cs | 2 +- .../Rendering/ITextFieldComponent.cs | 2 +- .../Attributes/CheckboxFieldAttribute.cs | 2 +- .../Forms/Attributes/DateFieldAttribute.cs | 2 +- .../Forms/Attributes/EmailFieldAttribute.cs | 2 +- .../Forms/Attributes/NumberFieldAttribute.cs | 2 +- .../Forms/Attributes/SelectFieldAttribute.cs | 2 +- .../Forms/Attributes/TextAreaAttribute.cs | 2 +- FormCraft/Forms/Builders/FieldBuilder.cs | 2 +- .../Builders/FieldConfigurationWrapper.cs | 2 +- FormCraft/Forms/Builders/FieldGroupBuilder.cs | 2 +- FormCraft/Forms/Builders/SecurityBuilder.cs | 2 +- FormCraft/Forms/Builders/ValidatorWrapper.cs | 2 +- FormCraft/Forms/Core/FieldConfiguration.cs | 2 +- .../Forms/Core/FileUploadConfiguration.cs | 2 +- FormCraft/Forms/Core/IFieldConfiguration.cs | 2 +- FormCraft/Forms/Core/IFieldContext.cs | 2 +- FormCraft/Forms/Core/IFormConfiguration.cs | 2 +- .../Forms/Core/MinimalFieldConfiguration.cs | 2 +- FormCraft/Forms/Core/SelectOption.cs | 2 +- FormCraft/Forms/Core/ValidationResult.cs | 2 +- .../AttributeFormBuilderExtensions.cs | 5 +- .../Extensions/FieldBuilderExtensions.cs | 2 +- .../Extensions/FluentFormBuilderExtensions.cs | 4 +- .../Extensions/FluentValidationExtensions.cs | 2 +- .../Extensions/FormConfigurationExtensions.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 2 +- .../Forms/Rendering/BoolFieldRenderer.cs | 5 +- .../CustomRenderers/ColorPickerRenderer.cs | 2 +- .../CustomRenderers/RatingRenderer.cs | 2 +- .../Forms/Rendering/DateTimeFieldRenderer.cs | 5 +- .../Forms/Rendering/DecimalFieldRenderer.cs | 5 +- .../Forms/Rendering/DoubleFieldRenderer.cs | 5 +- .../Forms/Rendering/FieldRenderContext.cs | 2 +- .../Forms/Rendering/FieldRendererBase.cs | 2 +- .../Rendering/FileUploadFieldRenderer.cs | 5 +- FormCraft/Forms/Rendering/IntFieldRenderer.cs | 5 +- .../Forms/Rendering/StringFieldRenderer.cs | 5 +- .../Forms/Security/EncryptedFieldHelper.cs | 2 +- FormCraft/Forms/Security/FormSecurity.cs | 2 +- FormCraft/Forms/Security/IAuditLogService.cs | 2 +- FormCraft/Forms/Security/ICsrfTokenService.cs | 2 +- .../Forms/Security/IEncryptionService.cs | 2 +- FormCraft/Forms/Security/IFormSecurity.cs | 2 +- FormCraft/Forms/Security/IRateLimitService.cs | 2 +- .../Forms/Templates/ContactFormTemplate.cs | 2 +- .../Forms/Validation/DynamicFormValidator.cs | 17 ++-- FormCraft/Forms/Validators/AsyncValidator.cs | 2 +- FormCraft/Forms/Validators/CustomValidator.cs | 2 +- .../Validators/FluentValidationAdapter.cs | 2 +- .../Forms/Validators/RequiredValidator.cs | 2 +- build/Build.cs | 10 +-- 175 files changed, 336 insertions(+), 246 deletions(-) diff --git a/FormCraft.DemoBlazorApp/Components/Dialogs/EditFormDialog.razor.cs b/FormCraft.DemoBlazorApp/Components/Dialogs/EditFormDialog.razor.cs index 37388073..fa0bfa4c 100644 --- a/FormCraft.DemoBlazorApp/Components/Dialogs/EditFormDialog.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Dialogs/EditFormDialog.razor.cs @@ -47,4 +47,4 @@ private void Cancel() { MudDialog.Cancel(); } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Dialogs/SimpleFormDialog.razor.cs b/FormCraft.DemoBlazorApp/Components/Dialogs/SimpleFormDialog.razor.cs index 34e8a62f..a3dad29b 100644 --- a/FormCraft.DemoBlazorApp/Components/Dialogs/SimpleFormDialog.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Dialogs/SimpleFormDialog.razor.cs @@ -57,4 +57,4 @@ private void Cancel() { MudDialog.Cancel(); } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Layout/FormCraftTheme.cs b/FormCraft.DemoBlazorApp/Components/Layout/FormCraftTheme.cs index c5f158af..2ff4e1db 100644 --- a/FormCraft.DemoBlazorApp/Components/Layout/FormCraftTheme.cs +++ b/FormCraft.DemoBlazorApp/Components/Layout/FormCraftTheme.cs @@ -115,67 +115,107 @@ public static class FormCraftTheme // declared parts — so it earns the largest type on the page. H1 = new H1Typography { - FontFamily = DisplayFont, FontSize = "3.4rem", FontWeight = "800", - LineHeight = "1.02", LetterSpacing = "-0.03em" + FontFamily = DisplayFont, + FontSize = "3.4rem", + FontWeight = "800", + LineHeight = "1.02", + LetterSpacing = "-0.03em" }, H2 = new H2Typography { - FontFamily = DisplayFont, FontSize = "2.6rem", FontWeight = "800", - LineHeight = "1.06", LetterSpacing = "-0.028em" + FontFamily = DisplayFont, + FontSize = "2.6rem", + FontWeight = "800", + LineHeight = "1.06", + LetterSpacing = "-0.028em" }, H3 = new H3Typography { - FontFamily = DisplayFont, FontSize = "2.1rem", FontWeight = "700", - LineHeight = "1.1", LetterSpacing = "-0.024em" + FontFamily = DisplayFont, + FontSize = "2.1rem", + FontWeight = "700", + LineHeight = "1.1", + LetterSpacing = "-0.024em" }, H4 = new H4Typography { - FontFamily = DisplayFont, FontSize = "1.65rem", FontWeight = "700", - LineHeight = "1.15", LetterSpacing = "-0.02em" + FontFamily = DisplayFont, + FontSize = "1.65rem", + FontWeight = "700", + LineHeight = "1.15", + LetterSpacing = "-0.02em" }, H5 = new H5Typography { - FontFamily = DisplayFont, FontSize = "1.3rem", FontWeight = "700", - LineHeight = "1.2", LetterSpacing = "-0.015em" + FontFamily = DisplayFont, + FontSize = "1.3rem", + FontWeight = "700", + LineHeight = "1.2", + LetterSpacing = "-0.015em" }, H6 = new H6Typography { - FontFamily = DisplayFont, FontSize = "1.075rem", FontWeight = "600", - LineHeight = "1.3", LetterSpacing = "-0.01em" + FontFamily = DisplayFont, + FontSize = "1.075rem", + FontWeight = "600", + LineHeight = "1.3", + LetterSpacing = "-0.01em" }, Subtitle1 = new Subtitle1Typography { - FontFamily = BodyFont, FontSize = "1rem", FontWeight = "500", LineHeight = "1.5" + FontFamily = BodyFont, + FontSize = "1rem", + FontWeight = "500", + LineHeight = "1.5" }, Subtitle2 = new Subtitle2Typography { - FontFamily = BodyFont, FontSize = "0.875rem", FontWeight = "600", LineHeight = "1.45" + FontFamily = BodyFont, + FontSize = "0.875rem", + FontWeight = "600", + LineHeight = "1.45" }, Body1 = new Body1Typography { - FontFamily = BodyFont, FontSize = "0.9375rem", FontWeight = "400", LineHeight = "1.6" + FontFamily = BodyFont, + FontSize = "0.9375rem", + FontWeight = "400", + LineHeight = "1.6" }, Body2 = new Body2Typography { - FontFamily = BodyFont, FontSize = "0.875rem", FontWeight = "400", LineHeight = "1.55" + FontFamily = BodyFont, + FontSize = "0.875rem", + FontWeight = "400", + LineHeight = "1.55" }, // Buttons keep sentence case: a control is named for what it does, and // SHOUTING CAPS makes long labels ("Read the documentation") hard to scan. Button = new ButtonTypography { - FontFamily = BodyFont, FontSize = "0.875rem", FontWeight = "600", - LineHeight = "1.75", LetterSpacing = "0.01em", TextTransform = "none" + FontFamily = BodyFont, + FontSize = "0.875rem", + FontWeight = "600", + LineHeight = "1.75", + LetterSpacing = "0.01em", + TextTransform = "none" }, Caption = new CaptionTypography { - FontFamily = BodyFont, FontSize = "0.78rem", FontWeight = "400", LineHeight = "1.4" + FontFamily = BodyFont, + FontSize = "0.78rem", + FontWeight = "400", + LineHeight = "1.4" }, // Overline is the eyebrow role, and every eyebrow on this site is mono. Overline = new OverlineTypography { FontFamily = ["IBM Plex Mono", "ui-monospace", "monospace"], - FontSize = "0.72rem", FontWeight = "500", LineHeight = "1.6", - LetterSpacing = "0.16em", TextTransform = "uppercase" + FontSize = "0.72rem", + FontWeight = "500", + LineHeight = "1.6", + LetterSpacing = "0.16em", + TextTransform = "uppercase" } } }; diff --git a/FormCraft.DemoBlazorApp/Components/Layout/FormPageLayout.razor.cs b/FormCraft.DemoBlazorApp/Components/Layout/FormPageLayout.razor.cs index 7cfaf6f4..5483f275 100644 --- a/FormCraft.DemoBlazorApp/Components/Layout/FormPageLayout.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Layout/FormPageLayout.razor.cs @@ -13,4 +13,4 @@ public partial class FormPageLayout [Parameter] public RenderFragment FormContent { get; set; } = null!; [Parameter] public RenderFragment SidebarContent { get; set; } = null!; [Parameter] public RenderFragment? SuccessContent { get; set; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Pages/AttributeBasedForms.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/AttributeBasedForms.razor.cs index a8164851..f9db74d0 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/AttributeBasedForms.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/AttributeBasedForms.razor.cs @@ -208,4 +208,4 @@ private static string GetFormGenerationCodeStatic() Configuration=""@_formConfiguration"" OnValidSubmit=""HandleValidSubmit"" />"; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Pages/CustomRenderers.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/CustomRenderers.razor.cs index b3e6b37c..00c9d4b0 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/CustomRenderers.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/CustomRenderers.razor.cs @@ -267,4 +267,4 @@ public static FieldBuilder AsColorPicker( } """; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Pages/DialogDemo.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/DialogDemo.razor.cs index ee3b932c..c4679966 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/DialogDemo.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/DialogDemo.razor.cs @@ -207,4 +207,4 @@ private async Task OpenDialog() } """; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Pages/FieldGroups.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/FieldGroups.razor.cs index 1e4e21ef..dc177bde 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/FieldGroups.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/FieldGroups.razor.cs @@ -229,4 +229,4 @@ private static string GetGeneratedCodeStatic() return code; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Pages/FileUploadDemo.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/FileUploadDemo.razor.cs index f3f8049e..d6fead70 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/FileUploadDemo.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/FileUploadDemo.razor.cs @@ -197,4 +197,4 @@ private static string GetGeneratedCodeStatic() Configuration=""@_formConfiguration"" OnValidSubmit=""@HandleSubmit"" />"; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Pages/FluentForm.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/FluentForm.razor.cs index 13b57991..f4987a04 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/FluentForm.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/FluentForm.razor.cs @@ -222,4 +222,4 @@ private static string GetGeneratedCodeStatic() return code; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Pages/FluentValidationDemo.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/FluentValidationDemo.razor.cs index cada1cf6..c5d97baf 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/FluentValidationDemo.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/FluentValidationDemo.razor.cs @@ -197,4 +197,4 @@ public CustomerValidator() .GreaterThanOrEqualTo(18).WithMessage("You must be 18 or older"); } } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Pages/FormSlots.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/FormSlots.razor.cs index 4baf8c63..cc962e62 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/FormSlots.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/FormSlots.razor.cs @@ -270,4 +270,4 @@ public void Dispose() _countdownTimer?.Stop(); _countdownTimer?.Dispose(); } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Pages/ImprovedForm.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/ImprovedForm.razor.cs index a357657d..7895b1f3 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/ImprovedForm.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/ImprovedForm.razor.cs @@ -223,4 +223,4 @@ private static string GetGeneratedCodeStatic() return code; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Pages/PasswordFieldDemo.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/PasswordFieldDemo.razor.cs index f63fb563..ba7b1db2 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/PasswordFieldDemo.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/PasswordFieldDemo.razor.cs @@ -268,8 +268,10 @@ private void ResetSecurityForm() private static string MaskCredential(string value) { - if (string.IsNullOrEmpty(value)) return ""; - if (value.Length <= 8) return new string('•', value.Length); + if (string.IsNullOrEmpty(value)) + return ""; + if (value.Length <= 8) + return new string('•', value.Length); return $"{value[..4]}...{value[^4..]}"; } @@ -283,12 +285,18 @@ private void CalculatePasswordStrength(string password) } int score = 0; - if (password.Length >= 8) score += 20; - if (password.Length >= 12) score += 10; - if (password.Any(char.IsUpper)) score += 20; - if (password.Any(char.IsLower)) score += 20; - if (password.Any(char.IsDigit)) score += 20; - if (password.Any(c => !char.IsLetterOrDigit(c))) score += 10; + if (password.Length >= 8) + score += 20; + if (password.Length >= 12) + score += 10; + if (password.Any(char.IsUpper)) + score += 20; + if (password.Any(char.IsLower)) + score += 20; + if (password.Any(char.IsDigit)) + score += 20; + if (password.Any(c => !char.IsLetterOrDigit(c))) + score += 10; _passwordStrengthScore = Math.Min(score, 100); } @@ -401,4 +409,4 @@ private static string GetSecurityCodeStatic() .Required(""PIN is required"")) .Build();"; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Pages/SecurityDemo.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/SecurityDemo.razor.cs index 6afb7363..7b1275bd 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/SecurityDemo.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/SecurityDemo.razor.cs @@ -230,7 +230,8 @@ private void ResetForm() private List GetDataDisplayItems() { - if (_lastSubmission == null) return new(); + if (_lastSubmission == null) + return new(); return new List { @@ -280,4 +281,4 @@ private static string GetSecurityCodeStatic() })) .Build();"; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Pages/SimplifiedForm.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/SimplifiedForm.razor.cs index 16c00ec8..85070c63 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/SimplifiedForm.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/SimplifiedForm.razor.cs @@ -198,4 +198,4 @@ private static string GetGeneratedCodeStatic() return actualCode; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Pages/StepperForm.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/StepperForm.razor.cs index 4d3d181e..8394cf82 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/StepperForm.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/StepperForm.razor.cs @@ -334,4 +334,4 @@ private async Task NextStep() } """; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Pages/TabbedForm.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/TabbedForm.razor.cs index ab503756..346d29e5 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/TabbedForm.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/TabbedForm.razor.cs @@ -496,4 +496,4 @@ private Task SubmitForm() } """; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Shared/ApiGuidelinesTable.razor.cs b/FormCraft.DemoBlazorApp/Components/Shared/ApiGuidelinesTable.razor.cs index f4827b36..3f4f3eca 100644 --- a/FormCraft.DemoBlazorApp/Components/Shared/ApiGuidelinesTable.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Shared/ApiGuidelinesTable.razor.cs @@ -15,4 +15,4 @@ public partial class ApiGuidelinesTable [Parameter] public RenderFragment? AdditionalContent { get; set; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Shared/DemoPageHeader.razor.cs b/FormCraft.DemoBlazorApp/Components/Shared/DemoPageHeader.razor.cs index d14c6458..7323aee3 100644 --- a/FormCraft.DemoBlazorApp/Components/Shared/DemoPageHeader.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Shared/DemoPageHeader.razor.cs @@ -12,4 +12,4 @@ public partial class DemoPageHeader [Parameter, EditorRequired] public string Description { get; set; } = ""; -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Shared/DocumentationPage.razor.cs b/FormCraft.DemoBlazorApp/Components/Shared/DocumentationPage.razor.cs index f49ebd4b..1187e8d9 100644 --- a/FormCraft.DemoBlazorApp/Components/Shared/DocumentationPage.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Shared/DocumentationPage.razor.cs @@ -90,4 +90,4 @@ private class TocSection public string Title { get; set; } = ""; public string Id { get; set; } = ""; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Shared/FormDemoSection.razor.cs b/FormCraft.DemoBlazorApp/Components/Shared/FormDemoSection.razor.cs index c233a147..61e212a8 100644 --- a/FormCraft.DemoBlazorApp/Components/Shared/FormDemoSection.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Shared/FormDemoSection.razor.cs @@ -61,4 +61,4 @@ public partial class FormDemoSection [Parameter] public RenderFragment? AfterFormContent { get; set; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Shared/FormGuidelines.razor.cs b/FormCraft.DemoBlazorApp/Components/Shared/FormGuidelines.razor.cs index a6375af7..963b382b 100644 --- a/FormCraft.DemoBlazorApp/Components/Shared/FormGuidelines.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Shared/FormGuidelines.razor.cs @@ -20,4 +20,4 @@ public class GuidelineItem public Color Color { get; set; } = Color.Primary; public string Text { get; set; } = ""; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Shared/FormSuccessDisplay.razor.cs b/FormCraft.DemoBlazorApp/Components/Shared/FormSuccessDisplay.razor.cs index 4bbdbe9a..881c2896 100644 --- a/FormCraft.DemoBlazorApp/Components/Shared/FormSuccessDisplay.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Shared/FormSuccessDisplay.razor.cs @@ -25,4 +25,4 @@ public class DataDisplayItem public string Label { get; set; } = ""; public string Value { get; set; } = ""; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Components/Shared/GuidelinesTable.razor.cs b/FormCraft.DemoBlazorApp/Components/Shared/GuidelinesTable.razor.cs index b338f56a..773112f2 100644 --- a/FormCraft.DemoBlazorApp/Components/Shared/GuidelinesTable.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Shared/GuidelinesTable.razor.cs @@ -18,4 +18,4 @@ public partial class GuidelinesTable [Parameter] public RenderFragment? AdditionalContent { get; set; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Helpers/GuidelineHelpers.cs b/FormCraft.DemoBlazorApp/Helpers/GuidelineHelpers.cs index 7a7cacfe..08025a0a 100644 --- a/FormCraft.DemoBlazorApp/Helpers/GuidelineHelpers.cs +++ b/FormCraft.DemoBlazorApp/Helpers/GuidelineHelpers.cs @@ -24,4 +24,4 @@ public static GuidelineItem CreateTextGuideline(string feature, string usage, st { return CreateGuideline(feature, usage, example, false); } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Models/ContactModel.cs b/FormCraft.DemoBlazorApp/Models/ContactModel.cs index e1f746b8..12a8f840 100644 --- a/FormCraft.DemoBlazorApp/Models/ContactModel.cs +++ b/FormCraft.DemoBlazorApp/Models/ContactModel.cs @@ -20,4 +20,4 @@ public class ContactModel public decimal? ExpectedSalary { get; set; } public decimal? HourlyRate { get; set; } public double? Rating { get; set; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Models/EmployeeModel.cs b/FormCraft.DemoBlazorApp/Models/EmployeeModel.cs index 280f67b5..4bfaab0d 100644 --- a/FormCraft.DemoBlazorApp/Models/EmployeeModel.cs +++ b/FormCraft.DemoBlazorApp/Models/EmployeeModel.cs @@ -21,4 +21,4 @@ public class EmployeeModel // Additional public string? Biography { get; set; } public string? Notes { get; set; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Models/GuidelineItem.cs b/FormCraft.DemoBlazorApp/Models/GuidelineItem.cs index 1484a028..5f58759c 100644 --- a/FormCraft.DemoBlazorApp/Models/GuidelineItem.cs +++ b/FormCraft.DemoBlazorApp/Models/GuidelineItem.cs @@ -12,4 +12,4 @@ public class ExtendedGuidelineItem : GuidelineItem { public string? AdditionalInfo { get; set; } public string? Icon { get; set; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Models/JobApplicationModel.cs b/FormCraft.DemoBlazorApp/Models/JobApplicationModel.cs index e15e0099..02a523f7 100644 --- a/FormCraft.DemoBlazorApp/Models/JobApplicationModel.cs +++ b/FormCraft.DemoBlazorApp/Models/JobApplicationModel.cs @@ -46,4 +46,4 @@ public class JobApplicationModel /// Gets or sets whether the applicant agrees to terms and conditions. /// public bool AgreeToTerms { get; set; } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Models/LoginFormModel.cs b/FormCraft.DemoBlazorApp/Models/LoginFormModel.cs index 6e8e75bd..4f7f22a2 100644 --- a/FormCraft.DemoBlazorApp/Models/LoginFormModel.cs +++ b/FormCraft.DemoBlazorApp/Models/LoginFormModel.cs @@ -30,4 +30,4 @@ public class SecurityFormModel public string ApiKey { get; set; } = string.Empty; public string SecretToken { get; set; } = string.Empty; public string Pin { get; set; } = string.Empty; -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Models/ProductModel.cs b/FormCraft.DemoBlazorApp/Models/ProductModel.cs index 41eaf929..8295cb66 100644 --- a/FormCraft.DemoBlazorApp/Models/ProductModel.cs +++ b/FormCraft.DemoBlazorApp/Models/ProductModel.cs @@ -15,4 +15,4 @@ public class ProductModel public bool IsAvailable { get; set; } = true; public string Category { get; set; } = ""; public DateTime ReleaseDate { get; set; } = DateTime.Today; -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Models/UserRegistrationModel.cs b/FormCraft.DemoBlazorApp/Models/UserRegistrationModel.cs index 5f6758bd..fa2c3480 100644 --- a/FormCraft.DemoBlazorApp/Models/UserRegistrationModel.cs +++ b/FormCraft.DemoBlazorApp/Models/UserRegistrationModel.cs @@ -63,4 +63,4 @@ public class UserRegistrationModel [TextArea("Additional Comments", "Any additional information...")] public string Comments { get; set; } = string.Empty; -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Program.cs b/FormCraft.DemoBlazorApp/Program.cs index 0c7998dc..b7a23054 100644 --- a/FormCraft.DemoBlazorApp/Program.cs +++ b/FormCraft.DemoBlazorApp/Program.cs @@ -31,4 +31,4 @@ // Custom field renderers are now registered by AddFormCraftMudBlazor -await builder.Build().RunAsync(); \ No newline at end of file +await builder.Build().RunAsync(); diff --git a/FormCraft.DemoBlazorApp/Services/MarkdownService.cs b/FormCraft.DemoBlazorApp/Services/MarkdownService.cs index 69fdad88..755ff608 100644 --- a/FormCraft.DemoBlazorApp/Services/MarkdownService.cs +++ b/FormCraft.DemoBlazorApp/Services/MarkdownService.cs @@ -93,4 +93,4 @@ public async Task LoadDocumentAsync(string fileName) return $"# Error loading document\n\nThe document `{fileName}.md` could not be loaded.\n\nError: {ex.Message}"; } } -} \ No newline at end of file +} diff --git a/FormCraft.DemoBlazorApp/Services/VersionService.cs b/FormCraft.DemoBlazorApp/Services/VersionService.cs index 48691228..08449fb1 100644 --- a/FormCraft.DemoBlazorApp/Services/VersionService.cs +++ b/FormCraft.DemoBlazorApp/Services/VersionService.cs @@ -92,4 +92,4 @@ public async Task GetFormCraftVersionAsync() // Return a default if all else fails return "latest"; } -} \ No newline at end of file +} diff --git a/FormCraft.ForMudBlazor/Extensions/FieldBuilderExtensions.cs b/FormCraft.ForMudBlazor/Extensions/FieldBuilderExtensions.cs index 6e94a7ec..170b7459 100644 --- a/FormCraft.ForMudBlazor/Extensions/FieldBuilderExtensions.cs +++ b/FormCraft.ForMudBlazor/Extensions/FieldBuilderExtensions.cs @@ -538,4 +538,4 @@ public static FieldBuilder AsLookup( return builder; } -} \ No newline at end of file +} diff --git a/FormCraft.ForMudBlazor/Extensions/ServiceCollectionExtensions.cs b/FormCraft.ForMudBlazor/Extensions/ServiceCollectionExtensions.cs index ab86dc13..6f5587f6 100644 --- a/FormCraft.ForMudBlazor/Extensions/ServiceCollectionExtensions.cs +++ b/FormCraft.ForMudBlazor/Extensions/ServiceCollectionExtensions.cs @@ -81,4 +81,4 @@ public IServiceCollection AddFormCraftMudBlazor() return services; } } -} \ No newline at end of file +} diff --git a/FormCraft.ForMudBlazor/Features/CollectionField/CollectionFieldComponent.razor.cs b/FormCraft.ForMudBlazor/Features/CollectionField/CollectionFieldComponent.razor.cs index c1f07d85..9c5d4204 100644 --- a/FormCraft.ForMudBlazor/Features/CollectionField/CollectionFieldComponent.razor.cs +++ b/FormCraft.ForMudBlazor/Features/CollectionField/CollectionFieldComponent.razor.cs @@ -81,7 +81,8 @@ protected override void OnParametersSet() private async Task AddItem() { - if (HasReachedMax) return; + if (HasReachedMax) + return; Items.Add(new TItem()); await NotifyCollectionChanged(); @@ -89,8 +90,10 @@ private async Task AddItem() private async Task RemoveItem(int index) { - if (HasReachedMin) return; - if (index < 0 || index >= Items.Count) return; + if (HasReachedMin) + return; + if (index < 0 || index >= Items.Count) + return; Items.RemoveAt(index); await NotifyCollectionChanged(); @@ -98,7 +101,8 @@ private async Task RemoveItem(int index) private async Task MoveItemUp(int index) { - if (index <= 0 || index >= Items.Count) return; + if (index <= 0 || index >= Items.Count) + return; (Items[index], Items[index - 1]) = (Items[index - 1], Items[index]); await NotifyCollectionChanged(); @@ -106,7 +110,8 @@ private async Task MoveItemUp(int index) private async Task MoveItemDown(int index) { - if (index < 0 || index >= Items.Count - 1) return; + if (index < 0 || index >= Items.Count - 1) + return; (Items[index], Items[index + 1]) = (Items[index + 1], Items[index]); await NotifyCollectionChanged(); @@ -124,7 +129,8 @@ private async Task NotifyCollectionChanged() private async Task UpdateItemFieldValue(int itemIndex, string fieldName, object? value) { - if (itemIndex < 0 || itemIndex >= Items.Count) return; + if (itemIndex < 0 || itemIndex >= Items.Count) + return; var item = Items[itemIndex]; var property = typeof(TItem).GetProperty(fieldName); @@ -167,7 +173,8 @@ private RenderFragment RenderItemFields(int itemIndex) { return builder => { - if (Configuration.ItemFormConfiguration == null) return; + if (Configuration.ItemFormConfiguration == null) + return; var item = Items[itemIndex]; diff --git a/FormCraft.ForMudBlazor/Features/CustomFields/MudBlazorColorPickerComponent.razor.cs b/FormCraft.ForMudBlazor/Features/CustomFields/MudBlazorColorPickerComponent.razor.cs index cc37e361..3f934892 100644 --- a/FormCraft.ForMudBlazor/Features/CustomFields/MudBlazorColorPickerComponent.razor.cs +++ b/FormCraft.ForMudBlazor/Features/CustomFields/MudBlazorColorPickerComponent.razor.cs @@ -12,4 +12,4 @@ protected override void OnInitialized() CurrentValue = "#000000"; } } -} \ No newline at end of file +} diff --git a/FormCraft.ForMudBlazor/Features/CustomFields/MudBlazorColorPickerRenderer.cs b/FormCraft.ForMudBlazor/Features/CustomFields/MudBlazorColorPickerRenderer.cs index 67fddb13..7755c224 100644 --- a/FormCraft.ForMudBlazor/Features/CustomFields/MudBlazorColorPickerRenderer.cs +++ b/FormCraft.ForMudBlazor/Features/CustomFields/MudBlazorColorPickerRenderer.cs @@ -34,4 +34,4 @@ public override bool CanRender(Type fieldType, IFieldConfiguration /// The model type that the form will bind to. -public partial class MudBlazorSliderComponent where TModel : new(); \ No newline at end of file +public partial class MudBlazorSliderComponent where TModel : new(); diff --git a/FormCraft.ForMudBlazor/Features/CustomFields/MudBlazorSliderRenderer.cs b/FormCraft.ForMudBlazor/Features/CustomFields/MudBlazorSliderRenderer.cs index 3c8671dc..6a8dc4c5 100644 --- a/FormCraft.ForMudBlazor/Features/CustomFields/MudBlazorSliderRenderer.cs +++ b/FormCraft.ForMudBlazor/Features/CustomFields/MudBlazorSliderRenderer.cs @@ -22,4 +22,4 @@ public override RenderFragment Render(IFieldRenderContext context) builder.CloseComponent(); }; } -} \ No newline at end of file +} diff --git a/FormCraft.ForMudBlazor/Features/FormContainer/FormCraftComponent.razor.cs b/FormCraft.ForMudBlazor/Features/FormContainer/FormCraftComponent.razor.cs index 08b67ab4..01e07f36 100644 --- a/FormCraft.ForMudBlazor/Features/FormContainer/FormCraftComponent.razor.cs +++ b/FormCraft.ForMudBlazor/Features/FormContainer/FormCraftComponent.razor.cs @@ -320,7 +320,8 @@ private RenderFragment RenderField(IFieldConfiguration field) if (field.CustomTemplate != null && _editContext != null) { var property = typeof(TModel).GetProperty(field.FieldName); - if (property == null) return; + if (property == null) + return; var templateContext = new FieldContext( Model, @@ -439,4 +440,4 @@ private async Task HandleSubmit() await OnValidSubmit.InvokeAsync(Model); } } -} \ No newline at end of file +} diff --git a/FormCraft.ForMudBlazor/Features/Validation/FieldValidationMessage.razor.cs b/FormCraft.ForMudBlazor/Features/Validation/FieldValidationMessage.razor.cs index d9f1c3fe..b04ff5ac 100644 --- a/FormCraft.ForMudBlazor/Features/Validation/FieldValidationMessage.razor.cs +++ b/FormCraft.ForMudBlazor/Features/Validation/FieldValidationMessage.razor.cs @@ -33,4 +33,4 @@ public void Dispose() EditContext.OnValidationStateChanged -= HandleValidationStateChanged; } } -} \ No newline at end of file +} diff --git a/FormCraft.ForMudBlazor/Fields/AutocompleteField/MudBlazorAutocompleteFieldComponent.razor.cs b/FormCraft.ForMudBlazor/Fields/AutocompleteField/MudBlazorAutocompleteFieldComponent.razor.cs index 7882d5dc..5a814691 100644 --- a/FormCraft.ForMudBlazor/Fields/AutocompleteField/MudBlazorAutocompleteFieldComponent.razor.cs +++ b/FormCraft.ForMudBlazor/Fields/AutocompleteField/MudBlazorAutocompleteFieldComponent.razor.cs @@ -77,7 +77,8 @@ private async Task> SearchAsync(string searchText, Cancellat var labelList = optionsList.Select(o => (Value: o.Value, Label: o.Label)).ToList(); _toStringFunc = v => { - if (v == null) return string.Empty; + if (v == null) + return string.Empty; var match = labelList.FirstOrDefault(item => EqualityComparer.Default.Equals(item.Value, v)); return match.Label ?? v.ToString() ?? string.Empty; }; diff --git a/FormCraft.ForMudBlazor/Fields/BooleanField/MudBlazorBooleanFieldComponent.razor.cs b/FormCraft.ForMudBlazor/Fields/BooleanField/MudBlazorBooleanFieldComponent.razor.cs index 6d743659..103b016f 100644 --- a/FormCraft.ForMudBlazor/Fields/BooleanField/MudBlazorBooleanFieldComponent.razor.cs +++ b/FormCraft.ForMudBlazor/Fields/BooleanField/MudBlazorBooleanFieldComponent.razor.cs @@ -66,4 +66,4 @@ private async Task OnLocalValueChanged() SetValueWithoutNotification(_localValue); await Context.OnValueChanged.InvokeAsync(_localValue); } -} \ No newline at end of file +} diff --git a/FormCraft.ForMudBlazor/Fields/BooleanField/MudBlazorBooleanFieldRenderer.cs b/FormCraft.ForMudBlazor/Fields/BooleanField/MudBlazorBooleanFieldRenderer.cs index e241a985..c6be4302 100644 --- a/FormCraft.ForMudBlazor/Fields/BooleanField/MudBlazorBooleanFieldRenderer.cs +++ b/FormCraft.ForMudBlazor/Fields/BooleanField/MudBlazorBooleanFieldRenderer.cs @@ -13,4 +13,4 @@ public override bool CanRender(Type fieldType, IFieldConfiguration) && fieldType.GetGenericArguments()[0].Name.Contains("IBrowserFile")); } -} \ No newline at end of file +} diff --git a/FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorMultipleFileUploadComponent.razor.cs b/FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorMultipleFileUploadComponent.razor.cs index dc4fc2c2..2788aa19 100644 --- a/FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorMultipleFileUploadComponent.razor.cs +++ b/FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorMultipleFileUploadComponent.razor.cs @@ -85,7 +85,8 @@ private string GetHeight() private static string FormatFileSize(long bytes) { - if (bytes == 0) return "0 Bytes"; + if (bytes == 0) + return "0 Bytes"; const int scale = 1024; string[] orders = { "GB", "MB", "KB", "Bytes" }; @@ -100,4 +101,4 @@ private static string FormatFileSize(long bytes) } return "0 Bytes"; } -} \ No newline at end of file +} diff --git a/FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorMultipleFileUploadRenderer.cs b/FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorMultipleFileUploadRenderer.cs index dbecae45..f5f4cea6 100644 --- a/FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorMultipleFileUploadRenderer.cs +++ b/FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorMultipleFileUploadRenderer.cs @@ -15,4 +15,4 @@ public override bool CanRender(Type fieldType, IFieldConfiguration); } -} \ No newline at end of file +} diff --git a/FormCraft.ForMudBlazor/Fields/LookupField/MudBlazorLookupDialog.razor.cs b/FormCraft.ForMudBlazor/Fields/LookupField/MudBlazorLookupDialog.razor.cs index 6eba8d56..e1f010d9 100644 --- a/FormCraft.ForMudBlazor/Fields/LookupField/MudBlazorLookupDialog.razor.cs +++ b/FormCraft.ForMudBlazor/Fields/LookupField/MudBlazorLookupDialog.razor.cs @@ -55,16 +55,19 @@ protected override void OnInitialized() private void ExtractColumnDefinitions() { - if (Columns == null) return; + if (Columns == null) + return; // The columns parameter is a List> stored as object. // We use reflection to extract column info. var columnsType = Columns.GetType(); - if (!columnsType.IsGenericType) return; + if (!columnsType.IsGenericType) + return; var enumerableInterface = columnsType.GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)); - if (enumerableInterface == null) return; + if (enumerableInterface == null) + return; foreach (var col in (System.Collections.IEnumerable)Columns) { @@ -103,14 +106,16 @@ private async Task> LoadServerData(TableState state, Cancellat // Invoke the data provider delegate var task = ((Delegate)DataProvider).DynamicInvoke(query) as Task; - if (task == null) return new TableData { Items = Array.Empty(), TotalItems = 0 }; + if (task == null) + return new TableData { Items = Array.Empty(), TotalItems = 0 }; await task; // Get the result from the completed task var resultProp = task.GetType().GetProperty("Result"); var result = resultProp?.GetValue(task); - if (result == null) return new TableData { Items = Array.Empty(), TotalItems = 0 }; + if (result == null) + return new TableData { Items = Array.Empty(), TotalItems = 0 }; // Extract Items and TotalCount from LookupResult var itemsProp = result.GetType().GetProperty("Items"); diff --git a/FormCraft.ForMudBlazor/Fields/LovField/LovSelectionDialog.razor.cs b/FormCraft.ForMudBlazor/Fields/LovField/LovSelectionDialog.razor.cs index bd21d6d0..5acd8b07 100644 --- a/FormCraft.ForMudBlazor/Fields/LovField/LovSelectionDialog.razor.cs +++ b/FormCraft.ForMudBlazor/Fields/LovField/LovSelectionDialog.razor.cs @@ -60,7 +60,7 @@ protected override void OnInitialized() if (SelectedItems.Count > 0) { - _selectedItemsSet = [..SelectedItems]; + _selectedItemsSet = [.. SelectedItems]; } } @@ -192,8 +192,10 @@ private void ClearAllSelections() /// private string GetConfirmButtonText() { - if (!_selectedItemsSet.Any()) return "Select"; - if (IsMultiSelect) return $"Select ({_selectedItemsSet.Count})"; + if (!_selectedItemsSet.Any()) + return "Select"; + if (IsMultiSelect) + return $"Select ({_selectedItemsSet.Count})"; return "Select"; } diff --git a/FormCraft.ForMudBlazor/Fields/LovField/MudBlazorLovFieldComponent.razor.cs b/FormCraft.ForMudBlazor/Fields/LovField/MudBlazorLovFieldComponent.razor.cs index 6bf74abc..aae725cf 100644 --- a/FormCraft.ForMudBlazor/Fields/LovField/MudBlazorLovFieldComponent.razor.cs +++ b/FormCraft.ForMudBlazor/Fields/LovField/MudBlazorLovFieldComponent.razor.cs @@ -61,8 +61,10 @@ protected string AdornmentIcon { get { - if (_isLoading) return Icons.Material.Filled.HourglassEmpty; - if (CurrentValue != null && !IsMultiSelect) return Icons.Material.Filled.Clear; + if (_isLoading) + return Icons.Material.Filled.HourglassEmpty; + if (CurrentValue != null && !IsMultiSelect) + return Icons.Material.Filled.Clear; return Icons.Material.Filled.Search; } } @@ -89,7 +91,8 @@ protected override void OnInitialized() private void InitializeDataProvider() { - if (_lovConfig == null) return; + if (_lovConfig == null) + return; var factory = ServiceProvider.GetService(typeof(ILovDataProviderFactory)) as ILovDataProviderFactory; if (factory != null) @@ -140,7 +143,8 @@ protected string GetItemDisplayText(TItem item) /// private async Task HandleAdornmentClick() { - if (IsDisabled || IsReadOnly) return; + if (IsDisabled || IsReadOnly) + return; // If there's a value and it's single select, clear it if (CurrentValue != null && !IsMultiSelect) @@ -157,7 +161,8 @@ private async Task HandleAdornmentClick() /// private async Task OpenLovDialog() { - if (_lovConfig == null || _dataProvider == null) return; + if (_lovConfig == null || _dataProvider == null) + return; _isLoading = true; StateHasChanged(); @@ -208,7 +213,8 @@ private async Task ApplySelection(List items) _selectedItems.Clear(); _selectedItems.AddRange(items); - if (_lovConfig == null) return; + if (_lovConfig == null) + return; if (IsMultiSelect) { @@ -249,7 +255,8 @@ private async Task ApplySelection(List items) /// private async Task ApplyFieldMappings(TItem item) { - if (_lovConfig == null || Context.Model == null) return; + if (_lovConfig == null || Context.Model == null) + return; // Apply each mapping directly foreach (var mapping in _lovConfig.FieldMappings) diff --git a/FormCraft.ForMudBlazor/Fields/NumericField/MudBlazorNumericFieldComponent.razor.cs b/FormCraft.ForMudBlazor/Fields/NumericField/MudBlazorNumericFieldComponent.razor.cs index e3c5c0ca..aea03741 100644 --- a/FormCraft.ForMudBlazor/Fields/NumericField/MudBlazorNumericFieldComponent.razor.cs +++ b/FormCraft.ForMudBlazor/Fields/NumericField/MudBlazorNumericFieldComponent.razor.cs @@ -109,4 +109,4 @@ private async Task OnLocalValueChanged() SetValueWithoutNotification(_localValue); await Context.OnValueChanged.InvokeAsync(_localValue); } -} \ No newline at end of file +} diff --git a/FormCraft.ForMudBlazor/Fields/NumericField/MudBlazorNumericFieldRenderer.cs b/FormCraft.ForMudBlazor/Fields/NumericField/MudBlazorNumericFieldRenderer.cs index 3ea95ab2..185df0d1 100644 --- a/FormCraft.ForMudBlazor/Fields/NumericField/MudBlazorNumericFieldRenderer.cs +++ b/FormCraft.ForMudBlazor/Fields/NumericField/MudBlazorNumericFieldRenderer.cs @@ -33,4 +33,4 @@ public override bool CanRender(Type fieldType, IFieldConfiguration public override bool CanRender(Type fieldType, IFieldConfiguration field) => fieldType == typeof(string); -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Builders/FieldBuilderTests.cs b/FormCraft.UnitTests/Builders/FieldBuilderTests.cs index ea9eb281..25c26371 100644 --- a/FormCraft.UnitTests/Builders/FieldBuilderTests.cs +++ b/FormCraft.UnitTests/Builders/FieldBuilderTests.cs @@ -443,4 +443,4 @@ public class TestModel public string City { get; set; } = string.Empty; public bool IsActive { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Builders/FieldConfigurationWrapperTests.cs b/FormCraft.UnitTests/Builders/FieldConfigurationWrapperTests.cs index 22ea5799..b6265965 100644 --- a/FormCraft.UnitTests/Builders/FieldConfigurationWrapperTests.cs +++ b/FormCraft.UnitTests/Builders/FieldConfigurationWrapperTests.cs @@ -619,4 +619,4 @@ public class TestModel public string Email { get; set; } = string.Empty; public int Age { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Builders/FieldGroupBuilderTests.cs b/FormCraft.UnitTests/Builders/FieldGroupBuilderTests.cs index 977342b2..30d240f1 100644 --- a/FormCraft.UnitTests/Builders/FieldGroupBuilderTests.cs +++ b/FormCraft.UnitTests/Builders/FieldGroupBuilderTests.cs @@ -311,4 +311,4 @@ public void WithHeaderRightContent_Generic_With_Parameters_Should_Work() fieldGroup.ShouldNotBeNull(); fieldGroup.HeaderRightContent.ShouldNotBeNull(); } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Builders/FormBuilderTests.cs b/FormCraft.UnitTests/Builders/FormBuilderTests.cs index b9f08aa5..9361eb23 100644 --- a/FormCraft.UnitTests/Builders/FormBuilderTests.cs +++ b/FormCraft.UnitTests/Builders/FormBuilderTests.cs @@ -338,4 +338,4 @@ public class TestModel public string Country { get; set; } = string.Empty; public string City { get; set; } = string.Empty; } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Builders/ValidatorWrapperTests.cs b/FormCraft.UnitTests/Builders/ValidatorWrapperTests.cs index 981c3462..dbaeeed9 100644 --- a/FormCraft.UnitTests/Builders/ValidatorWrapperTests.cs +++ b/FormCraft.UnitTests/Builders/ValidatorWrapperTests.cs @@ -283,4 +283,4 @@ public class TestModel public string Email { get; set; } = string.Empty; public int Age { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Components/DynamicFormValidatorTests.cs b/FormCraft.UnitTests/Components/DynamicFormValidatorTests.cs index c1e1fc60..4d94bc5e 100644 --- a/FormCraft.UnitTests/Components/DynamicFormValidatorTests.cs +++ b/FormCraft.UnitTests/Components/DynamicFormValidatorTests.cs @@ -174,4 +174,4 @@ public class AnotherTestModel public string Title { get; set; } = string.Empty; public bool IsActive { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Components/FieldValidationMessageTests.cs b/FormCraft.UnitTests/Components/FieldValidationMessageTests.cs index a3624c2a..b0549de8 100644 --- a/FormCraft.UnitTests/Components/FieldValidationMessageTests.cs +++ b/FormCraft.UnitTests/Components/FieldValidationMessageTests.cs @@ -259,4 +259,4 @@ public class TestModel public string Email { get; set; } = string.Empty; public int Age { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Components/FormCraftComponentTests.cs b/FormCraft.UnitTests/Components/FormCraftComponentTests.cs index 6692abc6..da8c06c8 100644 --- a/FormCraft.UnitTests/Components/FormCraftComponentTests.cs +++ b/FormCraft.UnitTests/Components/FormCraftComponentTests.cs @@ -244,4 +244,4 @@ public class TestModel public int Age { get; set; } public bool ShowOptionalField { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Core/FieldConfigurationTests.cs b/FormCraft.UnitTests/Core/FieldConfigurationTests.cs index c6fba5a7..18b5221e 100644 --- a/FormCraft.UnitTests/Core/FieldConfigurationTests.cs +++ b/FormCraft.UnitTests/Core/FieldConfigurationTests.cs @@ -207,4 +207,4 @@ public class AddressModel { public string City { get; set; } = string.Empty; } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Core/FieldDependencyTests.cs b/FormCraft.UnitTests/Core/FieldDependencyTests.cs index 55ec75ae..f4b3fbd1 100644 --- a/FormCraft.UnitTests/Core/FieldDependencyTests.cs +++ b/FormCraft.UnitTests/Core/FieldDependencyTests.cs @@ -268,4 +268,4 @@ public class AddressModel { public string PostalCode { get; set; } = string.Empty; } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Core/FileUploadConfigurationTests.cs b/FormCraft.UnitTests/Core/FileUploadConfigurationTests.cs index eba68360..4e9ecbfa 100644 --- a/FormCraft.UnitTests/Core/FileUploadConfigurationTests.cs +++ b/FormCraft.UnitTests/Core/FileUploadConfigurationTests.cs @@ -261,4 +261,4 @@ public void GetConstraintsDescription_CombinesAllConstraints() result.ShouldContain("Max files: 5"); result.ShouldContain(" • "); // Separator } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Core/SelectOptionTests.cs b/FormCraft.UnitTests/Core/SelectOptionTests.cs index 58bc2daa..f34a0f3e 100644 --- a/FormCraft.UnitTests/Core/SelectOptionTests.cs +++ b/FormCraft.UnitTests/Core/SelectOptionTests.cs @@ -66,4 +66,4 @@ private class TestClass { public int Id { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Core/ValidationResultTests.cs b/FormCraft.UnitTests/Core/ValidationResultTests.cs index 994cb366..3729f124 100644 --- a/FormCraft.UnitTests/Core/ValidationResultTests.cs +++ b/FormCraft.UnitTests/Core/ValidationResultTests.cs @@ -51,4 +51,4 @@ public void Failure_With_Null_Message_Should_Create_Invalid_Result() result.IsValid.ShouldBeFalse(); result.ErrorMessage.ShouldBeNull(); } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Extensions/AttributeFormBuilderExtensionsTests.cs b/FormCraft.UnitTests/Extensions/AttributeFormBuilderExtensionsTests.cs index 1cefbf15..b76029fe 100644 --- a/FormCraft.UnitTests/Extensions/AttributeFormBuilderExtensionsTests.cs +++ b/FormCraft.UnitTests/Extensions/AttributeFormBuilderExtensionsTests.cs @@ -393,4 +393,4 @@ public void Multiple_Attributes_Should_All_Be_Processed() config.Fields.ShouldContain(f => f.FieldName == "Price"); config.Fields.ShouldContain(f => f.FieldName == "AppointmentDate"); } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Extensions/FieldBuilderExtensionsTests.cs b/FormCraft.UnitTests/Extensions/FieldBuilderExtensionsTests.cs index e317ce0d..42275780 100644 --- a/FormCraft.UnitTests/Extensions/FieldBuilderExtensionsTests.cs +++ b/FormCraft.UnitTests/Extensions/FieldBuilderExtensionsTests.cs @@ -408,4 +408,4 @@ public class TestModel public string Bio { get; set; } = string.Empty; public int Age { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Extensions/FileUploadExtensionsTests.cs b/FormCraft.UnitTests/Extensions/FileUploadExtensionsTests.cs index baaf286e..2db7a11c 100644 --- a/FormCraft.UnitTests/Extensions/FileUploadExtensionsTests.cs +++ b/FormCraft.UnitTests/Extensions/FileUploadExtensionsTests.cs @@ -214,4 +214,4 @@ public void FileUploadExtensions_DoNotForceARendererOverride() field.AdditionalAttributes.ShouldNotContainKey("CustomRendererInstance"); field.CustomRendererType.ShouldBeNull(); } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Extensions/FluentFormBuilderExtensionsTests.cs b/FormCraft.UnitTests/Extensions/FluentFormBuilderExtensionsTests.cs index 38eee296..0fce115c 100644 --- a/FormCraft.UnitTests/Extensions/FluentFormBuilderExtensionsTests.cs +++ b/FormCraft.UnitTests/Extensions/FluentFormBuilderExtensionsTests.cs @@ -457,4 +457,4 @@ public class TestModel public string Password { get; set; } = string.Empty; public bool AcceptTerms { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Extensions/FormConfigurationExtensionsTests.cs b/FormCraft.UnitTests/Extensions/FormConfigurationExtensionsTests.cs index 812d42b6..32d01aa3 100644 --- a/FormCraft.UnitTests/Extensions/FormConfigurationExtensionsTests.cs +++ b/FormCraft.UnitTests/Extensions/FormConfigurationExtensionsTests.cs @@ -231,4 +231,4 @@ public class TestModel public string Phone { get; set; } = string.Empty; public bool ShowOptionalFields { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Extensions/ServiceCollectionExtensionsTests.cs b/FormCraft.UnitTests/Extensions/ServiceCollectionExtensionsTests.cs index fb629bfe..d78476c1 100644 --- a/FormCraft.UnitTests/Extensions/ServiceCollectionExtensionsTests.cs +++ b/FormCraft.UnitTests/Extensions/ServiceCollectionExtensionsTests.cs @@ -195,4 +195,4 @@ public class TestService : ITestService { public string GetMessage() => "Test message"; } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/ForMudBlazor/PasswordFieldTests.cs b/FormCraft.UnitTests/ForMudBlazor/PasswordFieldTests.cs index 5205f67a..8c50782e 100644 --- a/FormCraft.UnitTests/ForMudBlazor/PasswordFieldTests.cs +++ b/FormCraft.UnitTests/ForMudBlazor/PasswordFieldTests.cs @@ -250,4 +250,4 @@ public async Task Username_Field_Should_Update_Model_When_User_Types() // Assert - Model should be updated model.Username.ShouldBe("john_doe"); } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/GlobalUsings.cs b/FormCraft.UnitTests/GlobalUsings.cs index 01dce6ed..42eb7563 100644 --- a/FormCraft.UnitTests/GlobalUsings.cs +++ b/FormCraft.UnitTests/GlobalUsings.cs @@ -7,4 +7,4 @@ global using Microsoft.Extensions.DependencyInjection; global using System.Linq.Expressions; global using FormCraft; -global using FormCraft.ForMudBlazor; \ No newline at end of file +global using FormCraft.ForMudBlazor; diff --git a/FormCraft.UnitTests/Integration/CompleteFormWorkflowTests.cs b/FormCraft.UnitTests/Integration/CompleteFormWorkflowTests.cs index 96a1e342..674f5445 100644 --- a/FormCraft.UnitTests/Integration/CompleteFormWorkflowTests.cs +++ b/FormCraft.UnitTests/Integration/CompleteFormWorkflowTests.cs @@ -301,4 +301,4 @@ public class CompleteTestModel public bool IsActive { get; set; } public DateTime BirthDate { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Integration/DependencyInjectionIntegrationTests.cs b/FormCraft.UnitTests/Integration/DependencyInjectionIntegrationTests.cs index 71e153d4..694e0aeb 100644 --- a/FormCraft.UnitTests/Integration/DependencyInjectionIntegrationTests.cs +++ b/FormCraft.UnitTests/Integration/DependencyInjectionIntegrationTests.cs @@ -357,4 +357,4 @@ public class TestModel { public string Name { get; set; } = string.Empty; } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Integration/FileUploadIntegrationTests.cs b/FormCraft.UnitTests/Integration/FileUploadIntegrationTests.cs index e14ffb62..01d01651 100644 --- a/FormCraft.UnitTests/Integration/FileUploadIntegrationTests.cs +++ b/FormCraft.UnitTests/Integration/FileUploadIntegrationTests.cs @@ -242,4 +242,4 @@ public void ComplexFormWithFileUploads_ConfiguresAllFieldsCorrectly() fileFields.ShouldAllBe(f => f.CustomRendererType == null); fileFields.ShouldAllBe(f => f.AdditionalAttributes.ContainsKey("FileUploadConfiguration")); } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Integration/RealModelTemplateTests.cs b/FormCraft.UnitTests/Integration/RealModelTemplateTests.cs index dcd28bb8..0428db86 100644 --- a/FormCraft.UnitTests/Integration/RealModelTemplateTests.cs +++ b/FormCraft.UnitTests/Integration/RealModelTemplateTests.cs @@ -301,4 +301,4 @@ public class ExtendedContactModel public DateTime DateOfBirth { get; set; } public bool IsSubscribed { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Integration/ValidationPipelineIntegrationTests.cs b/FormCraft.UnitTests/Integration/ValidationPipelineIntegrationTests.cs index 885a2bbb..8a789a80 100644 --- a/FormCraft.UnitTests/Integration/ValidationPipelineIntegrationTests.cs +++ b/FormCraft.UnitTests/Integration/ValidationPipelineIntegrationTests.cs @@ -296,4 +296,4 @@ public class ComplexValidationModel public int Age { get; set; } public bool AcceptTerms { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Rendering/BoolFieldRendererTests.cs b/FormCraft.UnitTests/Rendering/BoolFieldRendererTests.cs index 8c82aacf..b312c85d 100644 --- a/FormCraft.UnitTests/Rendering/BoolFieldRendererTests.cs +++ b/FormCraft.UnitTests/Rendering/BoolFieldRendererTests.cs @@ -633,4 +633,4 @@ public class NullableTestModel { public bool? IsEnabled { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Rendering/CustomFieldRendererTests.cs b/FormCraft.UnitTests/Rendering/CustomFieldRendererTests.cs index 14bbaa84..37b427fe 100644 --- a/FormCraft.UnitTests/Rendering/CustomFieldRendererTests.cs +++ b/FormCraft.UnitTests/Rendering/CustomFieldRendererTests.cs @@ -127,4 +127,4 @@ public override RenderFragment Render(IFieldRenderContext context) public string? TestGetValue(IFieldRenderContext context) => GetValue(context); public Task TestSetValue(IFieldRenderContext context, string? value) => SetValue(context, value); } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Rendering/CustomRenderers/ColorPickerRendererTests.cs b/FormCraft.UnitTests/Rendering/CustomRenderers/ColorPickerRendererTests.cs index fd46a743..37c1ad0b 100644 --- a/FormCraft.UnitTests/Rendering/CustomRenderers/ColorPickerRendererTests.cs +++ b/FormCraft.UnitTests/Rendering/CustomRenderers/ColorPickerRendererTests.cs @@ -62,4 +62,4 @@ private class TestFieldConfiguration public bool IsDisabled { get; set; } public bool IsReadOnly { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Rendering/CustomRenderers/RatingRendererTests.cs b/FormCraft.UnitTests/Rendering/CustomRenderers/RatingRendererTests.cs index 3a879807..4eac2b9b 100644 --- a/FormCraft.UnitTests/Rendering/CustomRenderers/RatingRendererTests.cs +++ b/FormCraft.UnitTests/Rendering/CustomRenderers/RatingRendererTests.cs @@ -88,4 +88,4 @@ private class TestFieldConfiguration public bool IsReadOnly { get; set; } public Dictionary AdditionalAttributes { get; set; } = new(); } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Rendering/DateTimeFieldRendererTests.cs b/FormCraft.UnitTests/Rendering/DateTimeFieldRendererTests.cs index 64ffe7b6..e105923c 100644 --- a/FormCraft.UnitTests/Rendering/DateTimeFieldRendererTests.cs +++ b/FormCraft.UnitTests/Rendering/DateTimeFieldRendererTests.cs @@ -804,4 +804,4 @@ public class TestModel { public DateTime? BirthDate { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Rendering/DecimalFieldRendererTests.cs b/FormCraft.UnitTests/Rendering/DecimalFieldRendererTests.cs index d70350da..b81f7e2e 100644 --- a/FormCraft.UnitTests/Rendering/DecimalFieldRendererTests.cs +++ b/FormCraft.UnitTests/Rendering/DecimalFieldRendererTests.cs @@ -149,4 +149,4 @@ public class TestModel public decimal Price { get; set; } public decimal? OptionalAmount { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Rendering/DoubleFieldRendererTests.cs b/FormCraft.UnitTests/Rendering/DoubleFieldRendererTests.cs index 47f99347..9113cf20 100644 --- a/FormCraft.UnitTests/Rendering/DoubleFieldRendererTests.cs +++ b/FormCraft.UnitTests/Rendering/DoubleFieldRendererTests.cs @@ -150,4 +150,4 @@ public class TestModel public double Temperature { get; set; } public double? OptionalMeasurement { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Rendering/FieldRenderContextTests.cs b/FormCraft.UnitTests/Rendering/FieldRenderContextTests.cs index f41bc28d..430fd693 100644 --- a/FormCraft.UnitTests/Rendering/FieldRenderContextTests.cs +++ b/FormCraft.UnitTests/Rendering/FieldRenderContextTests.cs @@ -176,4 +176,4 @@ public class TestModel public DateTime DateCreated { get; set; } public int? NullableValue { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Rendering/FieldRendererServiceTests.cs b/FormCraft.UnitTests/Rendering/FieldRendererServiceTests.cs index 63e37279..45b32bb3 100644 --- a/FormCraft.UnitTests/Rendering/FieldRendererServiceTests.cs +++ b/FormCraft.UnitTests/Rendering/FieldRendererServiceTests.cs @@ -439,4 +439,4 @@ public RenderFragment Render(IFieldRenderContext context) return builder => builder.AddContent(0, "Custom"); } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Rendering/FileUploadFieldRendererTests.cs b/FormCraft.UnitTests/Rendering/FileUploadFieldRendererTests.cs index 9e9f2f84..705e2bd5 100644 --- a/FormCraft.UnitTests/Rendering/FileUploadFieldRendererTests.cs +++ b/FormCraft.UnitTests/Rendering/FileUploadFieldRendererTests.cs @@ -269,4 +269,4 @@ public class TestModel public IBrowserFile? Resume { get; set; } public IReadOnlyList? Documents { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Rendering/IntFieldRendererTests.cs b/FormCraft.UnitTests/Rendering/IntFieldRendererTests.cs index e301a0c5..654f5905 100644 --- a/FormCraft.UnitTests/Rendering/IntFieldRendererTests.cs +++ b/FormCraft.UnitTests/Rendering/IntFieldRendererTests.cs @@ -663,4 +663,4 @@ public class LongTestModel { public long Value { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Rendering/StringFieldRendererTests.cs b/FormCraft.UnitTests/Rendering/StringFieldRendererTests.cs index 4a2bcdf8..dafe915a 100644 --- a/FormCraft.UnitTests/Rendering/StringFieldRendererTests.cs +++ b/FormCraft.UnitTests/Rendering/StringFieldRendererTests.cs @@ -581,4 +581,4 @@ public class TestModel { public string? Name { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Security/AuditLogServiceTests.cs b/FormCraft.UnitTests/Security/AuditLogServiceTests.cs index 95050f34..332f6703 100644 --- a/FormCraft.UnitTests/Security/AuditLogServiceTests.cs +++ b/FormCraft.UnitTests/Security/AuditLogServiceTests.cs @@ -279,4 +279,4 @@ public async Task Should_Include_Additional_Data_In_Log() call.GetArgument(0) == LogLevel.Information) .MustHaveHappenedOnceExactly(); } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Security/RateLimitServiceTests.cs b/FormCraft.UnitTests/Security/RateLimitServiceTests.cs index 4e664fca..9e24a79a 100644 --- a/FormCraft.UnitTests/Security/RateLimitServiceTests.cs +++ b/FormCraft.UnitTests/Security/RateLimitServiceTests.cs @@ -181,4 +181,4 @@ public void Should_Allow_Multiple_Dispose_Calls() service.Dispose(); }); } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Security/SecurityBuilderTests.cs b/FormCraft.UnitTests/Security/SecurityBuilderTests.cs index b4812432..9f193be6 100644 --- a/FormCraft.UnitTests/Security/SecurityBuilderTests.cs +++ b/FormCraft.UnitTests/Security/SecurityBuilderTests.cs @@ -195,4 +195,4 @@ public void Should_Work_With_Regular_Form_Configuration() config.Security.ShouldNotBeNull(); config.Security.EncryptedFields.ShouldContain("SSN"); } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Templates/ContactFormTemplateTests.cs b/FormCraft.UnitTests/Templates/ContactFormTemplateTests.cs index ff963750..72b07de0 100644 --- a/FormCraft.UnitTests/Templates/ContactFormTemplateTests.cs +++ b/FormCraft.UnitTests/Templates/ContactFormTemplateTests.cs @@ -283,4 +283,4 @@ public class IncompleteModel public string Name { get; set; } = string.Empty; // Missing required properties for contact/registration forms } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Validators/AsyncValidatorTests.cs b/FormCraft.UnitTests/Validators/AsyncValidatorTests.cs index 683d5f15..b26d92ab 100644 --- a/FormCraft.UnitTests/Validators/AsyncValidatorTests.cs +++ b/FormCraft.UnitTests/Validators/AsyncValidatorTests.cs @@ -154,13 +154,15 @@ async Task ComplexValidation(string phoneNumber) // Simulate multiple async operations await Task.Delay(25); // First service call - if (string.IsNullOrWhiteSpace(phoneNumber)) return false; + if (string.IsNullOrWhiteSpace(phoneNumber)) + return false; await Task.Delay(25); // Second service call // Check format var cleanNumber = phoneNumber.Replace("-", "").Replace(" ", "").Replace("(", "").Replace(")", ""); - if (cleanNumber.Length != 10) return false; + if (cleanNumber.Length != 10) + return false; await Task.Delay(25); // Third service call @@ -263,4 +265,4 @@ private class TestModel public string? Email { get; set; } public string? PhoneNumber { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Validators/CustomValidatorTests.cs b/FormCraft.UnitTests/Validators/CustomValidatorTests.cs index e21105ac..216e8dbe 100644 --- a/FormCraft.UnitTests/Validators/CustomValidatorTests.cs +++ b/FormCraft.UnitTests/Validators/CustomValidatorTests.cs @@ -63,7 +63,8 @@ public async Task ValidateAsync_Should_Support_Complex_Validation_Logic() // Arrange Func validationFunction = value => { - if (string.IsNullOrEmpty(value)) return false; + if (string.IsNullOrEmpty(value)) + return false; return value.Length >= 3 && value.Length <= 20 && value.All(char.IsLetterOrDigit); }; @@ -174,7 +175,8 @@ public async Task ValidateAsync_Should_Support_Email_Validation() // Arrange Func emailValidation = value => { - if (string.IsNullOrWhiteSpace(value)) return false; + if (string.IsNullOrWhiteSpace(value)) + return false; try { @@ -209,4 +211,4 @@ public class TestModel public DateTime BirthDate { get; set; } public string? Email { get; set; } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Validators/FluentValidationAdapterTests.cs b/FormCraft.UnitTests/Validators/FluentValidationAdapterTests.cs index 59fab8a7..6686948b 100644 --- a/FormCraft.UnitTests/Validators/FluentValidationAdapterTests.cs +++ b/FormCraft.UnitTests/Validators/FluentValidationAdapterTests.cs @@ -169,4 +169,4 @@ public class Address public string Street { get; set; } = ""; public string City { get; set; } = ""; } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Validators/FluentValidationExtensionsTests.cs b/FormCraft.UnitTests/Validators/FluentValidationExtensionsTests.cs index 00c527d0..80770188 100644 --- a/FormCraft.UnitTests/Validators/FluentValidationExtensionsTests.cs +++ b/FormCraft.UnitTests/Validators/FluentValidationExtensionsTests.cs @@ -130,4 +130,4 @@ public TestModelValidator() .EmailAddress().WithMessage("Invalid email format"); } } -} \ No newline at end of file +} diff --git a/FormCraft.UnitTests/Validators/RequiredValidatorTests.cs b/FormCraft.UnitTests/Validators/RequiredValidatorTests.cs index 175c6758..6d9ac90c 100644 --- a/FormCraft.UnitTests/Validators/RequiredValidatorTests.cs +++ b/FormCraft.UnitTests/Validators/RequiredValidatorTests.cs @@ -262,4 +262,4 @@ private class CustomType { public string Value { get; set; } = string.Empty; } -} \ No newline at end of file +} diff --git a/FormCraft/Components/FieldComponentBase.cs b/FormCraft/Components/FieldComponentBase.cs index ae589f77..2785266e 100644 --- a/FormCraft/Components/FieldComponentBase.cs +++ b/FormCraft/Components/FieldComponentBase.cs @@ -168,4 +168,4 @@ private bool ShouldReloadValue() } return defaultValue; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Abstractions/ICustomFieldRenderer.cs b/FormCraft/Forms/Abstractions/ICustomFieldRenderer.cs index cf398e7f..61c541fa 100644 --- a/FormCraft/Forms/Abstractions/ICustomFieldRenderer.cs +++ b/FormCraft/Forms/Abstractions/ICustomFieldRenderer.cs @@ -61,4 +61,4 @@ protected async Task SetValue(IFieldRenderContext context, TValue? value) { await context.OnValueChanged.InvokeAsync(value); } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Abstractions/IFieldDependency.cs b/FormCraft/Forms/Abstractions/IFieldDependency.cs index 3ef1d003..ddffe4a0 100644 --- a/FormCraft/Forms/Abstractions/IFieldDependency.cs +++ b/FormCraft/Forms/Abstractions/IFieldDependency.cs @@ -45,4 +45,4 @@ Task OnDependencyChangedAsync(TModel model) OnDependencyChanged(model); return Task.CompletedTask; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Abstractions/IFieldRenderContext.cs b/FormCraft/Forms/Abstractions/IFieldRenderContext.cs index b060e79f..e1dbddd2 100644 --- a/FormCraft/Forms/Abstractions/IFieldRenderContext.cs +++ b/FormCraft/Forms/Abstractions/IFieldRenderContext.cs @@ -73,4 +73,4 @@ public interface IFieldRenderContext : IFieldRenderContext /// Gets the callback to invoke when field dependencies change (for conditional visibility). /// EventCallback OnDependencyChanged { get; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Abstractions/IFieldRenderer.cs b/FormCraft/Forms/Abstractions/IFieldRenderer.cs index 1a0fb936..0691d666 100644 --- a/FormCraft/Forms/Abstractions/IFieldRenderer.cs +++ b/FormCraft/Forms/Abstractions/IFieldRenderer.cs @@ -43,4 +43,4 @@ public interface IFieldRenderer /// The render context containing the field configuration, current value, and callbacks. /// A RenderFragment that generates the appropriate UI component for the field. RenderFragment Render(IFieldRenderContext context); -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Abstractions/IFieldRendererService.cs b/FormCraft/Forms/Abstractions/IFieldRendererService.cs index c3943476..48610f8d 100644 --- a/FormCraft/Forms/Abstractions/IFieldRendererService.cs +++ b/FormCraft/Forms/Abstractions/IFieldRendererService.cs @@ -18,4 +18,4 @@ public interface IFieldRendererService /// A RenderFragment that generates the appropriate UI component for the field. RenderFragment RenderField(TModel model, IFieldConfiguration field, EventCallback onValueChanged, EventCallback onDependencyChanged); -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Abstractions/IFieldValidator.cs b/FormCraft/Forms/Abstractions/IFieldValidator.cs index 0d8526b0..9214859c 100644 --- a/FormCraft/Forms/Abstractions/IFieldValidator.cs +++ b/FormCraft/Forms/Abstractions/IFieldValidator.cs @@ -36,4 +36,4 @@ public interface IFieldValidator /// The service provider for dependency injection if needed. /// A ValidationResult indicating whether validation passed or failed with an error message. Task ValidateAsync(TModel model, TValue value, IServiceProvider services); -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Abstractions/Rendering/IBooleanFieldComponent.cs b/FormCraft/Forms/Abstractions/Rendering/IBooleanFieldComponent.cs index 38db1ac5..e76dec2e 100644 --- a/FormCraft/Forms/Abstractions/Rendering/IBooleanFieldComponent.cs +++ b/FormCraft/Forms/Abstractions/Rendering/IBooleanFieldComponent.cs @@ -46,4 +46,4 @@ public enum BooleanDisplayStyle /// Display as radio buttons. /// RadioButtons -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Abstractions/Rendering/IDateTimeFieldComponent.cs b/FormCraft/Forms/Abstractions/Rendering/IDateTimeFieldComponent.cs index de49dc15..ec13a0a9 100644 --- a/FormCraft/Forms/Abstractions/Rendering/IDateTimeFieldComponent.cs +++ b/FormCraft/Forms/Abstractions/Rendering/IDateTimeFieldComponent.cs @@ -66,4 +66,4 @@ public enum DateTimeInputMode /// Year picker only. /// Year -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Abstractions/Rendering/IFieldComponent.cs b/FormCraft/Forms/Abstractions/Rendering/IFieldComponent.cs index 899b960d..de94c221 100644 --- a/FormCraft/Forms/Abstractions/Rendering/IFieldComponent.cs +++ b/FormCraft/Forms/Abstractions/Rendering/IFieldComponent.cs @@ -10,4 +10,4 @@ public interface IFieldComponent /// Gets or sets the field render context containing all necessary information for rendering. /// IFieldRenderContext Context { get; set; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Abstractions/Rendering/IFileUploadFieldComponent.cs b/FormCraft/Forms/Abstractions/Rendering/IFileUploadFieldComponent.cs index e075d2fa..4eed1cbf 100644 --- a/FormCraft/Forms/Abstractions/Rendering/IFileUploadFieldComponent.cs +++ b/FormCraft/Forms/Abstractions/Rendering/IFileUploadFieldComponent.cs @@ -61,4 +61,4 @@ public enum FileUploadMode /// Manual upload triggered by user action. /// Manual -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Abstractions/Rendering/INumericFieldComponent.cs b/FormCraft/Forms/Abstractions/Rendering/INumericFieldComponent.cs index 523ba485..a08534c4 100644 --- a/FormCraft/Forms/Abstractions/Rendering/INumericFieldComponent.cs +++ b/FormCraft/Forms/Abstractions/Rendering/INumericFieldComponent.cs @@ -32,4 +32,4 @@ public interface INumericFieldComponent : IFieldComponent bool ShowSpinButtons { get; set; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Abstractions/Rendering/ISelectFieldComponent.cs b/FormCraft/Forms/Abstractions/Rendering/ISelectFieldComponent.cs index e4abbba6..5e14de06 100644 --- a/FormCraft/Forms/Abstractions/Rendering/ISelectFieldComponent.cs +++ b/FormCraft/Forms/Abstractions/Rendering/ISelectFieldComponent.cs @@ -41,4 +41,4 @@ public interface ISelectFieldComponent : IFieldComponent /// Gets or sets whether to group options. /// bool GroupOptions { get; set; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Abstractions/Rendering/ITextFieldComponent.cs b/FormCraft/Forms/Abstractions/Rendering/ITextFieldComponent.cs index 15b7fb06..d1d43bff 100644 --- a/FormCraft/Forms/Abstractions/Rendering/ITextFieldComponent.cs +++ b/FormCraft/Forms/Abstractions/Rendering/ITextFieldComponent.cs @@ -35,4 +35,4 @@ public interface ITextFieldComponent : IFieldComponent /// /// string? Mask { get; set; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Attributes/CheckboxFieldAttribute.cs b/FormCraft/Forms/Attributes/CheckboxFieldAttribute.cs index f0de89e3..5327bc95 100644 --- a/FormCraft/Forms/Attributes/CheckboxFieldAttribute.cs +++ b/FormCraft/Forms/Attributes/CheckboxFieldAttribute.cs @@ -32,4 +32,4 @@ public CheckboxFieldAttribute(string label, string? text = null) Label = label; Text = text ?? label; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Attributes/DateFieldAttribute.cs b/FormCraft/Forms/Attributes/DateFieldAttribute.cs index 6af39114..3e50d25b 100644 --- a/FormCraft/Forms/Attributes/DateFieldAttribute.cs +++ b/FormCraft/Forms/Attributes/DateFieldAttribute.cs @@ -42,4 +42,4 @@ public DateFieldAttribute(string label, string? placeholder = null) Label = label; Placeholder = placeholder; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Attributes/EmailFieldAttribute.cs b/FormCraft/Forms/Attributes/EmailFieldAttribute.cs index a49ec82b..2dafc3b1 100644 --- a/FormCraft/Forms/Attributes/EmailFieldAttribute.cs +++ b/FormCraft/Forms/Attributes/EmailFieldAttribute.cs @@ -32,4 +32,4 @@ public EmailFieldAttribute(string label, string? placeholder = null) Label = label; Placeholder = placeholder ?? "user@example.com"; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Attributes/NumberFieldAttribute.cs b/FormCraft/Forms/Attributes/NumberFieldAttribute.cs index 310d2b7f..0273c432 100644 --- a/FormCraft/Forms/Attributes/NumberFieldAttribute.cs +++ b/FormCraft/Forms/Attributes/NumberFieldAttribute.cs @@ -42,4 +42,4 @@ public NumberFieldAttribute(string label, string? placeholder = null) Label = label; Placeholder = placeholder; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Attributes/SelectFieldAttribute.cs b/FormCraft/Forms/Attributes/SelectFieldAttribute.cs index f1fba2a3..f1e19031 100644 --- a/FormCraft/Forms/Attributes/SelectFieldAttribute.cs +++ b/FormCraft/Forms/Attributes/SelectFieldAttribute.cs @@ -56,4 +56,4 @@ public SelectFieldAttribute(string label, params string[] options) Placeholder = "Select an option"; Options = options; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Attributes/TextAreaAttribute.cs b/FormCraft/Forms/Attributes/TextAreaAttribute.cs index da550d0e..fc916a1b 100644 --- a/FormCraft/Forms/Attributes/TextAreaAttribute.cs +++ b/FormCraft/Forms/Attributes/TextAreaAttribute.cs @@ -42,4 +42,4 @@ public TextAreaAttribute(string label, string? placeholder = null) Label = label; Placeholder = placeholder; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Builders/FieldBuilder.cs b/FormCraft/Forms/Builders/FieldBuilder.cs index 5a23064a..c4f4e4dd 100644 --- a/FormCraft/Forms/Builders/FieldBuilder.cs +++ b/FormCraft/Forms/Builders/FieldBuilder.cs @@ -395,4 +395,4 @@ public FieldBuilder WithCustomRenderer(IFieldRenderer renderer) return this; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Builders/FieldConfigurationWrapper.cs b/FormCraft/Forms/Builders/FieldConfigurationWrapper.cs index 4d5e42ad..94d82618 100644 --- a/FormCraft/Forms/Builders/FieldConfigurationWrapper.cs +++ b/FormCraft/Forms/Builders/FieldConfigurationWrapper.cs @@ -260,4 +260,4 @@ public Type? CustomRendererType /// /// The Type of TValue, representing the actual field type. public Type GetActualFieldType() => typeof(TValue); -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Builders/FieldGroupBuilder.cs b/FormCraft/Forms/Builders/FieldGroupBuilder.cs index b82f9f4c..fefc1494 100644 --- a/FormCraft/Forms/Builders/FieldGroupBuilder.cs +++ b/FormCraft/Forms/Builders/FieldGroupBuilder.cs @@ -162,4 +162,4 @@ public FieldGroupBuilder WithHeaderRightContent(Action(Expression> expr } throw new ArgumentException("Expression must be a member expression"); } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Builders/ValidatorWrapper.cs b/FormCraft/Forms/Builders/ValidatorWrapper.cs index e387ee13..25006830 100644 --- a/FormCraft/Forms/Builders/ValidatorWrapper.cs +++ b/FormCraft/Forms/Builders/ValidatorWrapper.cs @@ -47,4 +47,4 @@ public async Task ValidateAsync(TModel model, object? value, I return await _inner.ValidateAsync(model, typedValue, services); } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Core/FieldConfiguration.cs b/FormCraft/Forms/Core/FieldConfiguration.cs index 0f403623..55dcea2a 100644 --- a/FormCraft/Forms/Core/FieldConfiguration.cs +++ b/FormCraft/Forms/Core/FieldConfiguration.cs @@ -90,4 +90,4 @@ public FieldConfiguration(Expression> valueExpression) FieldName = memberExpression?.Member.Name ?? throw new ArgumentException("Invalid expression"); Label = FieldName; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Core/FileUploadConfiguration.cs b/FormCraft/Forms/Core/FileUploadConfiguration.cs index a3333ece..e786a787 100644 --- a/FormCraft/Forms/Core/FileUploadConfiguration.cs +++ b/FormCraft/Forms/Core/FileUploadConfiguration.cs @@ -100,4 +100,4 @@ public string GetConstraintsDescription() return parts.Count > 0 ? string.Join(" • ", parts) : string.Empty; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Core/IFieldConfiguration.cs b/FormCraft/Forms/Core/IFieldConfiguration.cs index 370f3870..cba41b9a 100644 --- a/FormCraft/Forms/Core/IFieldConfiguration.cs +++ b/FormCraft/Forms/Core/IFieldConfiguration.cs @@ -203,4 +203,4 @@ public interface IFieldConfiguration /// /// Type? CustomRendererType { get; set; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Core/IFieldContext.cs b/FormCraft/Forms/Core/IFieldContext.cs index 15374e7b..60667e7c 100644 --- a/FormCraft/Forms/Core/IFieldContext.cs +++ b/FormCraft/Forms/Core/IFieldContext.cs @@ -90,4 +90,4 @@ public interface IFieldContext /// Gets the CSS class string to apply to the field, including validation state classes. /// string FieldCssClass { get; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Core/IFormConfiguration.cs b/FormCraft/Forms/Core/IFormConfiguration.cs index 2a72524d..190bc5d8 100644 --- a/FormCraft/Forms/Core/IFormConfiguration.cs +++ b/FormCraft/Forms/Core/IFormConfiguration.cs @@ -140,4 +140,4 @@ public enum FormLayout /// public List CollectionFields { get; } = new(); -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Core/MinimalFieldConfiguration.cs b/FormCraft/Forms/Core/MinimalFieldConfiguration.cs index b73b36d1..af298fdb 100644 --- a/FormCraft/Forms/Core/MinimalFieldConfiguration.cs +++ b/FormCraft/Forms/Core/MinimalFieldConfiguration.cs @@ -37,4 +37,4 @@ internal class MinimalFieldConfiguration : IFieldConfiguration public Func? VisibilityCondition { get; set; } public Func? DisabledCondition { get; set; } public RenderFragment>? CustomTemplate { get; set; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Core/SelectOption.cs b/FormCraft/Forms/Core/SelectOption.cs index 4829fef4..d6c64aff 100644 --- a/FormCraft/Forms/Core/SelectOption.cs +++ b/FormCraft/Forms/Core/SelectOption.cs @@ -31,4 +31,4 @@ public SelectOption(T value, string label) Value = value; Label = label; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Core/ValidationResult.cs b/FormCraft/Forms/Core/ValidationResult.cs index cbbd922d..e1b6ef6b 100644 --- a/FormCraft/Forms/Core/ValidationResult.cs +++ b/FormCraft/Forms/Core/ValidationResult.cs @@ -33,4 +33,4 @@ private ValidationResult(bool isValid, string? errorMessage = null) /// The error message describing why validation failed. /// A ValidationResult indicating failed validation with the specified error message. public static ValidationResult Failure(string errorMessage) => new(false, errorMessage); -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Extensions/AttributeFormBuilderExtensions.cs b/FormCraft/Forms/Extensions/AttributeFormBuilderExtensions.cs index c96d0f6d..20f98340 100644 --- a/FormCraft/Forms/Extensions/AttributeFormBuilderExtensions.cs +++ b/FormCraft/Forms/Extensions/AttributeFormBuilderExtensions.cs @@ -333,7 +333,8 @@ private static void ApplyValidationAttributes(FieldBuilder { - if (value == null) return true; + if (value == null) + return true; return System.Text.RegularExpressions.Regex.IsMatch(value.ToString()!, pattern.Pattern); }, pattern.ErrorMessage ?? "Invalid format"); } @@ -347,4 +348,4 @@ private static bool IsNumericType(Type type) type == typeof(byte) || type == typeof(uint) || type == typeof(ulong) || type == typeof(ushort) || type == typeof(sbyte); } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Extensions/FieldBuilderExtensions.cs b/FormCraft/Forms/Extensions/FieldBuilderExtensions.cs index 4da8d9b1..7ebd60b4 100644 --- a/FormCraft/Forms/Extensions/FieldBuilderExtensions.cs +++ b/FormCraft/Forms/Extensions/FieldBuilderExtensions.cs @@ -143,7 +143,7 @@ public FieldBuilder> AsMultiSelect(params (TValue va return builder.WithAttribute("MultiSelectOptions", selectOptions); } } - + /// The FieldBuilder instance for a numeric field. /// The model type that the form binds to. /// The numeric type of the field value. diff --git a/FormCraft/Forms/Extensions/FluentFormBuilderExtensions.cs b/FormCraft/Forms/Extensions/FluentFormBuilderExtensions.cs index 0304fa60..2006c290 100644 --- a/FormCraft/Forms/Extensions/FluentFormBuilderExtensions.cs +++ b/FormCraft/Forms/Extensions/FluentFormBuilderExtensions.cs @@ -325,7 +325,7 @@ public FormBuilder AddCheckboxField(Expression> expre }); } } - + #region Helper Methods private static string BuildRangeMessage(bool hasMin, bool hasMax, TValue min, TValue max) @@ -548,4 +548,4 @@ public static FormBuilder AddOptionalField( } #endregion -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Extensions/FluentValidationExtensions.cs b/FormCraft/Forms/Extensions/FluentValidationExtensions.cs index 38dbe4c9..b438cef9 100644 --- a/FormCraft/Forms/Extensions/FluentValidationExtensions.cs +++ b/FormCraft/Forms/Extensions/FluentValidationExtensions.cs @@ -57,4 +57,4 @@ public FieldBuilder WithFluentValidator(IValidator va return builder.WithValidator(new SpecificFluentValidationAdapter(validator, propertyExpression)); } } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Extensions/FormConfigurationExtensions.cs b/FormCraft/Forms/Extensions/FormConfigurationExtensions.cs index d076ff11..bdaf81d3 100644 --- a/FormCraft/Forms/Extensions/FormConfigurationExtensions.cs +++ b/FormCraft/Forms/Extensions/FormConfigurationExtensions.cs @@ -51,4 +51,4 @@ public IEnumerable GetVisibleFields(TModel model) .Select(f => f.FieldName); } } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Extensions/ServiceCollectionExtensions.cs b/FormCraft/Forms/Extensions/ServiceCollectionExtensions.cs index 94537a2e..5443fc0d 100644 --- a/FormCraft/Forms/Extensions/ServiceCollectionExtensions.cs +++ b/FormCraft/Forms/Extensions/ServiceCollectionExtensions.cs @@ -71,4 +71,4 @@ public IServiceCollection AddFormCraft() return services; } } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Rendering/BoolFieldRenderer.cs b/FormCraft/Forms/Rendering/BoolFieldRenderer.cs index 720aaad8..ceb48121 100644 --- a/FormCraft/Forms/Rendering/BoolFieldRenderer.cs +++ b/FormCraft/Forms/Rendering/BoolFieldRenderer.cs @@ -30,7 +30,8 @@ private class TestStubComponent : ComponentBase protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { - if (Context == null) return; + if (Context == null) + return; var sequence = 0; builder.OpenElement(sequence++, "div"); @@ -49,4 +50,4 @@ protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Renderin builder.CloseElement(); } } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Rendering/CustomRenderers/ColorPickerRenderer.cs b/FormCraft/Forms/Rendering/CustomRenderers/ColorPickerRenderer.cs index 1b264171..3dd8144f 100644 --- a/FormCraft/Forms/Rendering/CustomRenderers/ColorPickerRenderer.cs +++ b/FormCraft/Forms/Rendering/CustomRenderers/ColorPickerRenderer.cs @@ -33,4 +33,4 @@ public override RenderFragment Render(IFieldRenderContext context) builder.CloseElement(); }; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Rendering/CustomRenderers/RatingRenderer.cs b/FormCraft/Forms/Rendering/CustomRenderers/RatingRenderer.cs index ccab437e..b50a1697 100644 --- a/FormCraft/Forms/Rendering/CustomRenderers/RatingRenderer.cs +++ b/FormCraft/Forms/Rendering/CustomRenderers/RatingRenderer.cs @@ -35,4 +35,4 @@ public override RenderFragment Render(IFieldRenderContext context) builder.CloseElement(); }; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Rendering/DateTimeFieldRenderer.cs b/FormCraft/Forms/Rendering/DateTimeFieldRenderer.cs index 9792849f..1707fa0c 100644 --- a/FormCraft/Forms/Rendering/DateTimeFieldRenderer.cs +++ b/FormCraft/Forms/Rendering/DateTimeFieldRenderer.cs @@ -29,7 +29,8 @@ private class TestStubComponent : ComponentBase protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { - if (Context == null) return; + if (Context == null) + return; var sequence = 0; builder.OpenElement(sequence++, "div"); @@ -57,4 +58,4 @@ protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Renderin builder.CloseElement(); } } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Rendering/DecimalFieldRenderer.cs b/FormCraft/Forms/Rendering/DecimalFieldRenderer.cs index 89e2983e..f3c296f1 100644 --- a/FormCraft/Forms/Rendering/DecimalFieldRenderer.cs +++ b/FormCraft/Forms/Rendering/DecimalFieldRenderer.cs @@ -29,7 +29,8 @@ private class TestStubComponent : ComponentBase protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { - if (Context == null) return; + if (Context == null) + return; var sequence = 0; builder.OpenElement(sequence++, "div"); @@ -58,4 +59,4 @@ protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Renderin builder.CloseElement(); } } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Rendering/DoubleFieldRenderer.cs b/FormCraft/Forms/Rendering/DoubleFieldRenderer.cs index 38114d61..d0ced3ef 100644 --- a/FormCraft/Forms/Rendering/DoubleFieldRenderer.cs +++ b/FormCraft/Forms/Rendering/DoubleFieldRenderer.cs @@ -29,7 +29,8 @@ private class TestStubComponent : ComponentBase protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { - if (Context == null) return; + if (Context == null) + return; var sequence = 0; builder.OpenElement(sequence++, "div"); @@ -58,4 +59,4 @@ protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Renderin builder.CloseElement(); } } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Rendering/FieldRenderContext.cs b/FormCraft/Forms/Rendering/FieldRenderContext.cs index 742b129f..5be8e616 100644 --- a/FormCraft/Forms/Rendering/FieldRenderContext.cs +++ b/FormCraft/Forms/Rendering/FieldRenderContext.cs @@ -30,4 +30,4 @@ public class FieldRenderContext : IFieldRenderContext object IFieldRenderContext.Model => Model!; object IFieldRenderContext.FieldConfiguration => Field; EventCallback IFieldRenderContext.OnFieldChanged => OnDependencyChanged; -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Rendering/FieldRendererBase.cs b/FormCraft/Forms/Rendering/FieldRendererBase.cs index e6cfe537..7c1c0a19 100644 --- a/FormCraft/Forms/Rendering/FieldRendererBase.cs +++ b/FormCraft/Forms/Rendering/FieldRendererBase.cs @@ -118,4 +118,4 @@ public override bool CanRender(Type fieldType, IFieldConfiguration protected virtual bool CanRenderDerivedType(Type fieldType) => false; -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Rendering/FileUploadFieldRenderer.cs b/FormCraft/Forms/Rendering/FileUploadFieldRenderer.cs index c87ee486..1ce022fc 100644 --- a/FormCraft/Forms/Rendering/FileUploadFieldRenderer.cs +++ b/FormCraft/Forms/Rendering/FileUploadFieldRenderer.cs @@ -42,7 +42,8 @@ private class TestStubComponent : ComponentBase protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { - if (Context == null) return; + if (Context == null) + return; var sequence = 0; builder.OpenElement(sequence++, "div"); @@ -85,4 +86,4 @@ protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Renderin builder.CloseElement(); } } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Rendering/IntFieldRenderer.cs b/FormCraft/Forms/Rendering/IntFieldRenderer.cs index 0764ec53..c6fc13c1 100644 --- a/FormCraft/Forms/Rendering/IntFieldRenderer.cs +++ b/FormCraft/Forms/Rendering/IntFieldRenderer.cs @@ -30,7 +30,8 @@ private class TestStubComponent : ComponentBase protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { - if (Context == null) return; + if (Context == null) + return; var sequence = 0; builder.OpenElement(sequence++, "div"); @@ -58,4 +59,4 @@ protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Renderin builder.CloseElement(); } } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Rendering/StringFieldRenderer.cs b/FormCraft/Forms/Rendering/StringFieldRenderer.cs index 954d774c..72d9dc81 100644 --- a/FormCraft/Forms/Rendering/StringFieldRenderer.cs +++ b/FormCraft/Forms/Rendering/StringFieldRenderer.cs @@ -26,7 +26,8 @@ private class TestStubComponent : ComponentBase protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { - if (Context == null) return; + if (Context == null) + return; var sequence = 0; builder.OpenElement(sequence++, "div"); @@ -54,4 +55,4 @@ protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Renderin builder.CloseElement(); } } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Security/EncryptedFieldHelper.cs b/FormCraft/Forms/Security/EncryptedFieldHelper.cs index 6baa3c0e..ecf0c6df 100644 --- a/FormCraft/Forms/Security/EncryptedFieldHelper.cs +++ b/FormCraft/Forms/Security/EncryptedFieldHelper.cs @@ -81,4 +81,4 @@ public static TModel CreateDecryptedCopy(TModel model, IFormSecurity sec return copy; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Security/FormSecurity.cs b/FormCraft/Forms/Security/FormSecurity.cs index c03f63a1..c04a254b 100644 --- a/FormCraft/Forms/Security/FormSecurity.cs +++ b/FormCraft/Forms/Security/FormSecurity.cs @@ -11,4 +11,4 @@ public class FormSecurity : IFormSecurity public RateLimitConfiguration? RateLimit { get; set; } public bool IsAuditLoggingEnabled { get; set; } public AuditLogConfiguration? AuditLog { get; set; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Security/IAuditLogService.cs b/FormCraft/Forms/Security/IAuditLogService.cs index e2fc67ec..0b5f6131 100644 --- a/FormCraft/Forms/Security/IAuditLogService.cs +++ b/FormCraft/Forms/Security/IAuditLogService.cs @@ -86,4 +86,4 @@ public static class AuditEventTypes public const string FormLoaded = "FormLoaded"; public const string RateLimitExceeded = "RateLimitExceeded"; public const string CsrfValidationFailed = "CsrfValidationFailed"; -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Security/ICsrfTokenService.cs b/FormCraft/Forms/Security/ICsrfTokenService.cs index 716e9352..e18d8971 100644 --- a/FormCraft/Forms/Security/ICsrfTokenService.cs +++ b/FormCraft/Forms/Security/ICsrfTokenService.cs @@ -17,4 +17,4 @@ public interface ICsrfTokenService /// The token to validate. /// True if the token is valid, false otherwise. Task ValidateTokenAsync(string token); -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Security/IEncryptionService.cs b/FormCraft/Forms/Security/IEncryptionService.cs index 26d10d6b..a8d4b25e 100644 --- a/FormCraft/Forms/Security/IEncryptionService.cs +++ b/FormCraft/Forms/Security/IEncryptionService.cs @@ -18,4 +18,4 @@ public interface IEncryptionService /// The encrypted value as a base64 string. /// The decrypted value. string? Decrypt(string? encryptedValue); -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Security/IFormSecurity.cs b/FormCraft/Forms/Security/IFormSecurity.cs index 986e7d15..86002bd3 100644 --- a/FormCraft/Forms/Security/IFormSecurity.cs +++ b/FormCraft/Forms/Security/IFormSecurity.cs @@ -81,4 +81,4 @@ public class AuditLogConfiguration /// Fields to exclude from logging (e.g., passwords). /// public HashSet ExcludedFields { get; set; } = new(); -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Security/IRateLimitService.cs b/FormCraft/Forms/Security/IRateLimitService.cs index 664da296..56eecf55 100644 --- a/FormCraft/Forms/Security/IRateLimitService.cs +++ b/FormCraft/Forms/Security/IRateLimitService.cs @@ -40,4 +40,4 @@ public class RateLimitResult /// Time until the rate limit resets. /// public TimeSpan? RetryAfter { get; set; } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Templates/ContactFormTemplate.cs b/FormCraft/Forms/Templates/ContactFormTemplate.cs index dcc42fd9..29b6790e 100644 --- a/FormCraft/Forms/Templates/ContactFormTemplate.cs +++ b/FormCraft/Forms/Templates/ContactFormTemplate.cs @@ -91,4 +91,4 @@ private static Expression> GetBoolPropertyExpression( var property = Expression.Property(parameter, propertyName); return Expression.Lambda>(property, parameter); } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Validation/DynamicFormValidator.cs b/FormCraft/Forms/Validation/DynamicFormValidator.cs index a9115854..0374967e 100644 --- a/FormCraft/Forms/Validation/DynamicFormValidator.cs +++ b/FormCraft/Forms/Validation/DynamicFormValidator.cs @@ -162,7 +162,8 @@ private async Task> ValidateCollectionFieldAsync(TModel model, ICol var validator = Activator.CreateInstance(validatorType, collectionField); var validateMethod = validatorType.GetMethod("ValidateAsync"); - if (validateMethod == null) return new List(); + if (validateMethod == null) + return new List(); var task = (Task>)validateMethod.Invoke(validator, new object[] { model!, ServiceProvider })!; return await task; @@ -175,7 +176,8 @@ private async Task> ValidateCollectionItemsAsync(TMode var validator = Activator.CreateInstance(validatorType, collectionField); var validateMethod = validatorType.GetMethod("ValidateItemsAsync"); - if (validateMethod == null) return new List(); + if (validateMethod == null) + return new List(); var task = (Task>)validateMethod.Invoke(validator, new object[] { model!, ServiceProvider })!; return await task; @@ -204,7 +206,8 @@ private async void HandleFieldChanged(object? sender, FieldChangedEventArgs e) // Find the field configuration for the changed field var fieldConfig = Configuration.Fields.FirstOrDefault(f => f.FieldName == e.FieldIdentifier.FieldName); - if (fieldConfig == null) return; + if (fieldConfig == null) + return; var model = (TModel)_editContext!.Model; var getter = fieldConfig.ValueExpression.Compile(); @@ -233,7 +236,8 @@ private async void HandleFieldChanged(object? sender, FieldChangedEventArgs e) private async Task ValidateCollectionItemFieldAsync(FieldIdentifier fieldIdentifier, System.Text.RegularExpressions.Match nestedMatch) { - if (Configuration is not ICollectionFormConfiguration collectionConfig) return; + if (Configuration is not ICollectionFormConfiguration collectionConfig) + return; var collectionFieldName = nestedMatch.Groups["collection"].Value; var itemIndex = int.Parse(nestedMatch.Groups["index"].Value); @@ -241,7 +245,8 @@ private async Task ValidateCollectionItemFieldAsync(FieldIdentifier fieldIdentif var collectionField = collectionConfig.CollectionFields .FirstOrDefault(f => f.FieldName == collectionFieldName); - if (collectionField == null) return; + if (collectionField == null) + return; var model = (TModel)_editContext!.Model; @@ -269,4 +274,4 @@ public void Dispose() _editContext.OnFieldChanged -= HandleFieldChanged; } } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Validators/AsyncValidator.cs b/FormCraft/Forms/Validators/AsyncValidator.cs index dca51714..46670926 100644 --- a/FormCraft/Forms/Validators/AsyncValidator.cs +++ b/FormCraft/Forms/Validators/AsyncValidator.cs @@ -63,4 +63,4 @@ public async Task ValidateAsync(TModel model, TValue value, IS return ValidationResult.Failure($"Validation could not be completed: {ex.Message}"); } } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Validators/CustomValidator.cs b/FormCraft/Forms/Validators/CustomValidator.cs index 6392c9fe..01d232b9 100644 --- a/FormCraft/Forms/Validators/CustomValidator.cs +++ b/FormCraft/Forms/Validators/CustomValidator.cs @@ -62,4 +62,4 @@ public Task ValidateAsync(TModel model, TValue value, IService return Task.FromResult(ValidationResult.Failure($"Validation could not be completed: {ex.Message}")); } } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Validators/FluentValidationAdapter.cs b/FormCraft/Forms/Validators/FluentValidationAdapter.cs index c014302b..14be415c 100644 --- a/FormCraft/Forms/Validators/FluentValidationAdapter.cs +++ b/FormCraft/Forms/Validators/FluentValidationAdapter.cs @@ -180,4 +180,4 @@ private string GetPropertyName() return string.Join(".", propertyPath); } -} \ No newline at end of file +} diff --git a/FormCraft/Forms/Validators/RequiredValidator.cs b/FormCraft/Forms/Validators/RequiredValidator.cs index fe3e1b31..7d62cdef 100644 --- a/FormCraft/Forms/Validators/RequiredValidator.cs +++ b/FormCraft/Forms/Validators/RequiredValidator.cs @@ -53,4 +53,4 @@ public Task ValidateAsync(TModel model, TValue value, IService ? ValidationResult.Success() : ValidationResult.Failure(ErrorMessage!)); } -} \ No newline at end of file +} diff --git a/build/Build.cs b/build/Build.cs index 75fdaea6..ed5831aa 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -441,10 +441,10 @@ class Build : NukeBuild Serilog.Log.Information(" - Current version: {Version}", CurrentVersion); }) .DependsOn(Publish); - // Note: the GitHub Release is created exclusively by release-please, in - // .github/workflows/release-please.yml, which then runs this publish in the same job. - // Do NOT add a release-creating target back into this chain — two producers would race - // to create a release for the same tag (already_exists errors). + // Note: the GitHub Release is created exclusively by release-please, in + // .github/workflows/release-please.yml, which then runs this publish in the same job. + // Do NOT add a release-creating target back into this chain — two producers would race + // to create a release for the same tag (already_exists errors). Target Release => _ => _ .Description("Creates a new release (NuGet + GitHub)") @@ -514,4 +514,4 @@ string GetCurrentTag() /// legal-but-nonsense prerelease label is not excluded (#227). /// bool IsOnVersionTag() => BuildVersioning.IsVersionTag(GetCurrentTag()); -} \ No newline at end of file +} From b297ab33686df0935ec73dbd15a30a742a8e39ad Mon Sep 17 00:00:00 2001 From: Philippe Matray Date: Thu, 13 Aug 2026 14:22:09 +0200 Subject: [PATCH 4/8] style: apply dotnet format code-style fixes across the solution --- .../Pages/AsyncValueProviderDemo.razor.cs | 2 + .../Pages/AttributeBasedForms.razor.cs | 2 +- .../Components/Pages/AutoFormDemo.razor.cs | 2 +- .../Pages/CrossFieldValidationDemo.razor.cs | 2 +- .../Components/Pages/FluentForm.razor.cs | 8 ++ .../Components/Pages/ImprovedForm.razor.cs | 2 + .../Pages/PasswordFieldDemo.razor.cs | 23 ++++++ .../Components/Pages/SecurityDemo.razor.cs | 2 + .../Components/Pages/SimplifiedForm.razor.cs | 4 + .../Components/Pages/TabbedForm.razor.cs | 20 +++++ .../Components/Shared/CodeExample.razor.cs | 2 + .../Shared/Navigation/DemoBreadcrumb.razor.cs | 6 +- .../DemoPrevNextNavigation.razor.cs | 4 + FormCraft.DemoBlazorApp/Program.cs | 10 +-- .../Services/DemoDocumentationValidator.cs | 43 +++++++++++ .../Services/DemoRegistry.cs | 6 ++ .../GlobalUsings.cs | 12 +-- .../GlobalUsings.cs | 12 +-- .../Extensions/FieldBuilderExtensions.cs | 2 + .../CollectionFieldComponent.razor.cs | 15 ++++ .../FormContainer/FormCraftComponent.razor.cs | 2 + ...dBlazorAutocompleteFieldComponent.razor.cs | 3 + .../MudBlazorFileUploadComponentBase.cs | 39 ++++++---- ...MudBlazorFileUploadFieldComponent.razor.cs | 2 + ...BlazorMultipleFileUploadComponent.razor.cs | 4 + .../MudBlazorLookupDialog.razor.cs | 15 +++- .../MudBlazorLookupFieldComponent.razor.cs | 4 + .../LovField/LovSelectionDialog.razor.cs | 6 ++ .../MudBlazorLovFieldComponent.razor.cs | 77 +++++++++++-------- ...azorNullableNumericFieldComponent.razor.cs | 9 +++ .../MudBlazorNumericFieldComponent.razor.cs | 9 +++ .../Builders/FormBuilderTests.cs | 2 + FormCraft.UnitTests/GlobalUsings.cs | 12 +-- .../Validators/AsyncValidatorTests.cs | 4 + .../Validators/CustomValidatorTests.cs | 5 ++ FormCraft/Forms/Builders/FieldBuilder.cs | 49 ++++++------ .../Builders/FieldConfigurationWrapper.cs | 77 +++++++++---------- FormCraft/Forms/Builders/ValidatorWrapper.cs | 11 ++- .../Forms/Core/FileUploadConfiguration.cs | 6 ++ .../AttributeFormBuilderExtensions.cs | 48 +++++++++++- .../Extensions/FieldBuilderExtensions.cs | 8 ++ .../Extensions/FluentFormBuilderExtensions.cs | 28 +++++++ .../Forms/Rendering/BoolFieldRenderer.cs | 2 + .../Forms/Rendering/DateTimeFieldRenderer.cs | 2 + .../Forms/Rendering/DecimalFieldRenderer.cs | 2 + .../Forms/Rendering/DoubleFieldRenderer.cs | 2 + .../Forms/Rendering/FieldRendererBase.cs | 4 +- .../Forms/Rendering/FieldRendererService.cs | 2 +- .../Rendering/FileUploadFieldRenderer.cs | 2 + FormCraft/Forms/Rendering/IntFieldRenderer.cs | 2 + .../Forms/Rendering/StringFieldRenderer.cs | 2 + .../Forms/Security/BlazorCsrfTokenService.cs | 7 ++ .../Forms/Security/BlazorEncryptionService.cs | 8 ++ .../Forms/Security/ConsoleAuditLogService.cs | 4 + .../Security/DefaultEncryptionService.cs | 8 ++ .../Forms/Security/EncryptedFieldHelper.cs | 6 ++ .../Security/InMemoryRateLimitService.cs | 4 + .../Forms/Validation/DynamicFormValidator.cs | 10 +++ .../Validators/FluentValidationAdapter.cs | 2 +- 59 files changed, 515 insertions(+), 153 deletions(-) diff --git a/FormCraft.DemoBlazorApp/Components/Pages/AsyncValueProviderDemo.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/AsyncValueProviderDemo.razor.cs index 22931c5f..affd2804 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/AsyncValueProviderDemo.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/AsyncValueProviderDemo.razor.cs @@ -220,7 +220,9 @@ private async Task ValidatePostalCodeAsync(string postalCode) await Task.Delay(300); if (string.IsNullOrEmpty(postalCode)) + { return true; + } // Simple validation based on country return _model.Country switch diff --git a/FormCraft.DemoBlazorApp/Components/Pages/AttributeBasedForms.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/AttributeBasedForms.razor.cs index f9db74d0..9cb42b8b 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/AttributeBasedForms.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/AttributeBasedForms.razor.cs @@ -1,8 +1,8 @@ using FormCraft.DemoBlazorApp.Components.Shared; using FormCraft.DemoBlazorApp.Models; using FormCraft.DemoBlazorApp.Services; -using MudBlazor; using Microsoft.AspNetCore.Components; +using MudBlazor; namespace FormCraft.DemoBlazorApp.Components.Pages; diff --git a/FormCraft.DemoBlazorApp/Components/Pages/AutoFormDemo.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/AutoFormDemo.razor.cs index 04b0d081..abb4fe7a 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/AutoFormDemo.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/AutoFormDemo.razor.cs @@ -12,7 +12,7 @@ public partial class AutoFormDemo private bool _isPlainSubmitting; private IFormConfiguration _plainConfiguration = null!; - private SpeakerProfileModel _annotatedModel = new(); + private readonly SpeakerProfileModel _annotatedModel = new(); private bool _isAnnotatedSubmitted; private IFormConfiguration _annotatedConfiguration = null!; diff --git a/FormCraft.DemoBlazorApp/Components/Pages/CrossFieldValidationDemo.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/CrossFieldValidationDemo.razor.cs index 5bc50c86..f189fdb9 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/CrossFieldValidationDemo.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/CrossFieldValidationDemo.razor.cs @@ -13,7 +13,7 @@ public partial class CrossFieldValidationDemo : ComponentBase private IFormConfiguration? _formConfig; private bool _submitted; private bool _isSubmitting; - private List _validationErrors = []; + private readonly List _validationErrors = []; /// /// Structured documentation for this demo page. diff --git a/FormCraft.DemoBlazorApp/Components/Pages/FluentForm.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/FluentForm.razor.cs index f4987a04..4900191c 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/FluentForm.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/FluentForm.razor.cs @@ -149,18 +149,26 @@ private Task HandleFieldChanged(string fieldName, object? value) }; if (!string.IsNullOrEmpty(_model.City)) + { items.Add(new() { Label = "City", Value = _model.City }); + } if (_model.ExpectedSalary.HasValue) + { items.Add(new() { Label = "Expected Salary", Value = $"${_model.ExpectedSalary.Value:N2}" }); + } if (_model.HourlyRate.HasValue) + { items.Add(new() { Label = "Hourly Rate", Value = $"${_model.HourlyRate.Value:N2}/hr" }); + } items.Add(new() { Label = "Newsletter", Value = _model.SubscribeToNewsletter ? "Subscribed" : "Not Subscribed" }); if (_fieldChanges.Any()) + { items.Add(new() { Label = "Field Changes", Value = $"{_fieldChanges.Count} changes tracked" }); + } return items; } diff --git a/FormCraft.DemoBlazorApp/Components/Pages/ImprovedForm.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/ImprovedForm.razor.cs index 7895b1f3..a19f9326 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/ImprovedForm.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/ImprovedForm.razor.cs @@ -152,7 +152,9 @@ private void ResetForm() }; if (!string.IsNullOrEmpty(_model.City)) + { items.Add(new() { Label = "City", Value = _model.City }); + } items.Add(new() { Label = "Newsletter", Value = _model.SubscribeToNewsletter ? "Yes" : "No" }); diff --git a/FormCraft.DemoBlazorApp/Components/Pages/PasswordFieldDemo.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/PasswordFieldDemo.razor.cs index ba7b1db2..45d54e39 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/PasswordFieldDemo.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/PasswordFieldDemo.razor.cs @@ -269,9 +269,15 @@ private void ResetSecurityForm() private static string MaskCredential(string value) { if (string.IsNullOrEmpty(value)) + { return ""; + } + if (value.Length <= 8) + { return new string('•', value.Length); + } + return $"{value[..4]}...{value[^4..]}"; } @@ -286,17 +292,34 @@ private void CalculatePasswordStrength(string password) int score = 0; if (password.Length >= 8) + { score += 20; + } + if (password.Length >= 12) + { score += 10; + } + if (password.Any(char.IsUpper)) + { score += 20; + } + if (password.Any(char.IsLower)) + { score += 20; + } + if (password.Any(char.IsDigit)) + { score += 20; + } + if (password.Any(c => !char.IsLetterOrDigit(c))) + { score += 10; + } _passwordStrengthScore = Math.Min(score, 100); } diff --git a/FormCraft.DemoBlazorApp/Components/Pages/SecurityDemo.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/SecurityDemo.razor.cs index 7b1275bd..3a4cdf23 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/SecurityDemo.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/SecurityDemo.razor.cs @@ -231,7 +231,9 @@ private void ResetForm() private List GetDataDisplayItems() { if (_lastSubmission == null) + { return new(); + } return new List { diff --git a/FormCraft.DemoBlazorApp/Components/Pages/SimplifiedForm.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/SimplifiedForm.razor.cs index 85070c63..44aa80ce 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/SimplifiedForm.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/SimplifiedForm.razor.cs @@ -145,12 +145,16 @@ private void ResetForm() }; if (!string.IsNullOrEmpty(_model.City)) + { items.Add(new() { Label = "City", Value = _model.City }); + } items.Add(new() { Label = "Newsletter", Value = _model.SubscribeToNewsletter ? "Yes" : "No" }); if (_fieldChanges.Any()) + { items.Add(new() { Label = "Field Changes", Value = $"{_fieldChanges.Count} changes tracked" }); + } return items; } diff --git a/FormCraft.DemoBlazorApp/Components/Pages/TabbedForm.razor.cs b/FormCraft.DemoBlazorApp/Components/Pages/TabbedForm.razor.cs index 346d29e5..6469e42f 100644 --- a/FormCraft.DemoBlazorApp/Components/Pages/TabbedForm.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Pages/TabbedForm.razor.cs @@ -217,9 +217,14 @@ private Color GetBadgeColor() private object? GetPersonalInfoBadge() { if (IsPersonalInfoComplete()) + { return Icons.Material.Filled.CheckCircle; + } + if (HasPersonalInfoData()) + { return Icons.Material.Filled.Warning; + } return null; } @@ -227,9 +232,14 @@ private Color GetBadgeColor() private object? GetContactInfoBadge() { if (IsContactInfoComplete()) + { return Icons.Material.Filled.CheckCircle; + } + if (HasContactInfoData()) + { return Icons.Material.Filled.Warning; + } return null; } @@ -237,9 +247,14 @@ private Color GetBadgeColor() private object? GetProfessionalInfoBadge() { if (IsProfessionalInfoComplete()) + { return Icons.Material.Filled.CheckCircle; + } + if (HasProfessionalInfoData()) + { return Icons.Material.Filled.Warning; + } return null; } @@ -247,9 +262,14 @@ private Color GetBadgeColor() private Color GetTabBadgeColor(bool isComplete, bool hasData) { if (isComplete) + { return Color.Success; + } + if (hasData) + { return Color.Warning; + } return Color.Default; } diff --git a/FormCraft.DemoBlazorApp/Components/Shared/CodeExample.razor.cs b/FormCraft.DemoBlazorApp/Components/Shared/CodeExample.razor.cs index 72ca6c55..0f7e0b67 100644 --- a/FormCraft.DemoBlazorApp/Components/Shared/CodeExample.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Shared/CodeExample.razor.cs @@ -80,7 +80,9 @@ private string GetPreClasses() private string GetLineNumbersHtml() { if (string.IsNullOrEmpty(Code)) + { return ""; + } var lineCount = Code.Split('\n').Length; return string.Concat(Enumerable.Repeat("", lineCount)); diff --git a/FormCraft.DemoBlazorApp/Components/Shared/Navigation/DemoBreadcrumb.razor.cs b/FormCraft.DemoBlazorApp/Components/Shared/Navigation/DemoBreadcrumb.razor.cs index 098425f7..34421154 100644 --- a/FormCraft.DemoBlazorApp/Components/Shared/Navigation/DemoBreadcrumb.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Shared/Navigation/DemoBreadcrumb.razor.cs @@ -10,7 +10,7 @@ public partial class DemoBreadcrumb [Parameter] public string? DemoId { get; set; } - private List _items = new(); + private readonly List _items = new(); private DemoMetadata? _currentDemo; protected override void OnParametersSet() @@ -27,11 +27,15 @@ private void BuildBreadcrumbs() _items.Add(new BreadcrumbItem("Home", "home", icon: Icons.Material.Filled.Home)); if (string.IsNullOrEmpty(DemoId)) + { return; + } var demo = DemoRegistry.GetDemo(DemoId); if (demo == null) + { return; + } _currentDemo = demo; diff --git a/FormCraft.DemoBlazorApp/Components/Shared/Navigation/DemoPrevNextNavigation.razor.cs b/FormCraft.DemoBlazorApp/Components/Shared/Navigation/DemoPrevNextNavigation.razor.cs index 45281dd5..89dc4756 100644 --- a/FormCraft.DemoBlazorApp/Components/Shared/Navigation/DemoPrevNextNavigation.razor.cs +++ b/FormCraft.DemoBlazorApp/Components/Shared/Navigation/DemoPrevNextNavigation.razor.cs @@ -25,11 +25,15 @@ protected override void OnParametersSet() _nextLevelInfo = null; if (string.IsNullOrEmpty(DemoId)) + { return; + } _current = DemoRegistry.GetDemo(DemoId); if (_current == null) + { return; + } // Use learning path navigation for form examples if (_current.Category == "form-examples") diff --git a/FormCraft.DemoBlazorApp/Program.cs b/FormCraft.DemoBlazorApp/Program.cs index b7a23054..3eed1fdb 100644 --- a/FormCraft.DemoBlazorApp/Program.cs +++ b/FormCraft.DemoBlazorApp/Program.cs @@ -1,12 +1,12 @@ -using Microsoft.AspNetCore.Components.Web; -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using FluentValidation; +using FormCraft; using FormCraft.DemoBlazorApp.Components; +using FormCraft.DemoBlazorApp.Components.Pages; using FormCraft.DemoBlazorApp.Services; -using FormCraft; using FormCraft.ForMudBlazor.Extensions; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using MudBlazor.Services; -using FluentValidation; -using FormCraft.DemoBlazorApp.Components.Pages; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); diff --git a/FormCraft.DemoBlazorApp/Services/DemoDocumentationValidator.cs b/FormCraft.DemoBlazorApp/Services/DemoDocumentationValidator.cs index c255dee3..25158c4b 100644 --- a/FormCraft.DemoBlazorApp/Services/DemoDocumentationValidator.cs +++ b/FormCraft.DemoBlazorApp/Services/DemoDocumentationValidator.cs @@ -45,68 +45,111 @@ public DocumentationValidationResult Validate(DemoDocumentation doc) // Required validations (errors) if (string.IsNullOrWhiteSpace(doc.DemoId)) + { errors.Add("DemoId is required"); + } if (string.IsNullOrWhiteSpace(doc.Title)) + { errors.Add("Title is required"); + } if (string.IsNullOrWhiteSpace(doc.Description)) + { errors.Add("Description is required"); + } else if (doc.Description.Length < MinDescriptionLength) + { errors.Add($"Description must be at least {MinDescriptionLength} characters (current: {doc.Description.Length})"); + } if (string.IsNullOrWhiteSpace(doc.Icon)) + { errors.Add("Icon is required"); + } if (doc.FeatureHighlights.Count < MinFeatureHighlights) + { errors.Add($"At least {MinFeatureHighlights} feature highlights required (current: {doc.FeatureHighlights.Count})"); + } if (doc.ApiGuidelines.Count < MinApiGuidelines) + { errors.Add($"At least {MinApiGuidelines} API guidelines required (current: {doc.ApiGuidelines.Count})"); + } if (doc.CodeExamples.Count < MinCodeExamples) + { errors.Add($"At least {MinCodeExamples} code example required (current: {doc.CodeExamples.Count})"); + } // Validate feature highlights foreach (var (highlight, index) in doc.FeatureHighlights.Select((h, i) => (h, i))) { if (string.IsNullOrWhiteSpace(highlight.Icon)) + { errors.Add($"Feature highlight {index + 1}: Icon is required"); + } + if (string.IsNullOrWhiteSpace(highlight.Text)) + { errors.Add($"Feature highlight {index + 1}: Text is required"); + } } // Validate API guidelines foreach (var (guideline, index) in doc.ApiGuidelines.Select((g, i) => (g, i))) { if (string.IsNullOrWhiteSpace(guideline.Feature)) + { errors.Add($"API guideline {index + 1}: Feature is required"); + } + if (string.IsNullOrWhiteSpace(guideline.Usage)) + { errors.Add($"API guideline {index + 1}: Usage is required"); + } + if (string.IsNullOrWhiteSpace(guideline.Example)) + { errors.Add($"API guideline {index + 1}: Example is required"); + } } // Validate code examples foreach (var (example, index) in doc.CodeExamples.Select((e, i) => (e, i))) { if (string.IsNullOrWhiteSpace(example.Title)) + { errors.Add($"Code example {index + 1}: Title is required"); + } + if (string.IsNullOrWhiteSpace(example.Language)) + { errors.Add($"Code example {index + 1}: Language is required"); + } + if (example.CodeProvider == null) + { errors.Add($"Code example {index + 1}: CodeProvider is required"); + } } // Recommended validations (warnings) if (string.IsNullOrWhiteSpace(doc.WhenToUse)) + { warnings.Add("Consider adding 'WhenToUse' to help users understand when to use this approach"); + } if (doc.RelatedDemoIds == null || doc.RelatedDemoIds.Count == 0) + { warnings.Add("Consider adding 'RelatedDemoIds' for cross-referencing"); + } if (doc.CommonPitfalls == null || doc.CommonPitfalls.Count == 0) + { warnings.Add("Consider adding 'CommonPitfalls' to help users avoid common mistakes"); + } return new DocumentationValidationResult( doc.DemoId, diff --git a/FormCraft.DemoBlazorApp/Services/DemoRegistry.cs b/FormCraft.DemoBlazorApp/Services/DemoRegistry.cs index f751307c..39e08157 100644 --- a/FormCraft.DemoBlazorApp/Services/DemoRegistry.cs +++ b/FormCraft.DemoBlazorApp/Services/DemoRegistry.cs @@ -413,7 +413,9 @@ public IReadOnlyList GetDemosByCategory(string category) => { var current = GetDemo(currentId); if (current == null) + { return (null, null); + } var categoryDemos = GetDemosByCategory(current.Category).ToList(); var index = categoryDemos.FindIndex(d => d.Id.Equals(currentId, StringComparison.OrdinalIgnoreCase)); @@ -437,7 +439,9 @@ public IReadOnlyList GetDemosByLevel(string level) => { var current = GetDemo(currentId); if (current == null || current.Category != "form-examples") + { return (null, null); + } // Get all form examples ordered by level then by level order var learningPath = AllDemos @@ -448,7 +452,9 @@ public IReadOnlyList GetDemosByLevel(string level) => var index = learningPath.FindIndex(d => d.Id.Equals(currentId, StringComparison.OrdinalIgnoreCase)); if (index < 0) + { return (null, null); + } var previous = index > 0 ? learningPath[index - 1] : null; var next = index < learningPath.Count - 1 ? learningPath[index + 1] : null; diff --git a/FormCraft.ForFluentUI.UnitTests/GlobalUsings.cs b/FormCraft.ForFluentUI.UnitTests/GlobalUsings.cs index ecca99e3..24aa93f7 100644 --- a/FormCraft.ForFluentUI.UnitTests/GlobalUsings.cs +++ b/FormCraft.ForFluentUI.UnitTests/GlobalUsings.cs @@ -1,11 +1,11 @@ -global using Xunit; -global using Shouldly; -global using FakeItEasy; +global using System.Linq.Expressions; global using Bunit; +global using FakeItEasy; +global using FormCraft; +global using FormCraft.ForFluentUI; global using Microsoft.AspNetCore.Components; global using Microsoft.AspNetCore.Components.Forms; global using Microsoft.Extensions.DependencyInjection; -global using System.Linq.Expressions; -global using FormCraft; -global using FormCraft.ForFluentUI; global using Microsoft.FluentUI.AspNetCore.Components; +global using Shouldly; +global using Xunit; diff --git a/FormCraft.ForMudBlazor.UnitTests/GlobalUsings.cs b/FormCraft.ForMudBlazor.UnitTests/GlobalUsings.cs index 0df42c85..f446fa43 100644 --- a/FormCraft.ForMudBlazor.UnitTests/GlobalUsings.cs +++ b/FormCraft.ForMudBlazor.UnitTests/GlobalUsings.cs @@ -1,12 +1,12 @@ -global using Xunit; -global using Shouldly; -global using FakeItEasy; +global using System.Linq.Expressions; global using Bunit; +global using FakeItEasy; +global using FormCraft; +global using FormCraft.ForMudBlazor; global using Microsoft.AspNetCore.Components; global using Microsoft.AspNetCore.Components.Forms; global using Microsoft.Extensions.DependencyInjection; -global using System.Linq.Expressions; -global using FormCraft; -global using FormCraft.ForMudBlazor; global using MudBlazor; global using MudBlazor.Services; +global using Shouldly; +global using Xunit; diff --git a/FormCraft.ForMudBlazor/Extensions/FieldBuilderExtensions.cs b/FormCraft.ForMudBlazor/Extensions/FieldBuilderExtensions.cs index 170b7459..11c9a0d8 100644 --- a/FormCraft.ForMudBlazor/Extensions/FieldBuilderExtensions.cs +++ b/FormCraft.ForMudBlazor/Extensions/FieldBuilderExtensions.cs @@ -534,7 +534,9 @@ public static FieldBuilder AsLookup( } if (onItemSelected != null) + { builder.WithAttribute("LookupOnItemSelected", onItemSelected); + } return builder; } diff --git a/FormCraft.ForMudBlazor/Features/CollectionField/CollectionFieldComponent.razor.cs b/FormCraft.ForMudBlazor/Features/CollectionField/CollectionFieldComponent.razor.cs index 9c5d4204..f0123e66 100644 --- a/FormCraft.ForMudBlazor/Features/CollectionField/CollectionFieldComponent.razor.cs +++ b/FormCraft.ForMudBlazor/Features/CollectionField/CollectionFieldComponent.razor.cs @@ -82,7 +82,9 @@ protected override void OnParametersSet() private async Task AddItem() { if (HasReachedMax) + { return; + } Items.Add(new TItem()); await NotifyCollectionChanged(); @@ -91,9 +93,14 @@ private async Task AddItem() private async Task RemoveItem(int index) { if (HasReachedMin) + { return; + } + if (index < 0 || index >= Items.Count) + { return; + } Items.RemoveAt(index); await NotifyCollectionChanged(); @@ -102,7 +109,9 @@ private async Task RemoveItem(int index) private async Task MoveItemUp(int index) { if (index <= 0 || index >= Items.Count) + { return; + } (Items[index], Items[index - 1]) = (Items[index - 1], Items[index]); await NotifyCollectionChanged(); @@ -111,7 +120,9 @@ private async Task MoveItemUp(int index) private async Task MoveItemDown(int index) { if (index < 0 || index >= Items.Count - 1) + { return; + } (Items[index], Items[index + 1]) = (Items[index + 1], Items[index]); await NotifyCollectionChanged(); @@ -130,7 +141,9 @@ private async Task NotifyCollectionChanged() private async Task UpdateItemFieldValue(int itemIndex, string fieldName, object? value) { if (itemIndex < 0 || itemIndex >= Items.Count) + { return; + } var item = Items[itemIndex]; var property = typeof(TItem).GetProperty(fieldName); @@ -174,7 +187,9 @@ private RenderFragment RenderItemFields(int itemIndex) return builder => { if (Configuration.ItemFormConfiguration == null) + { return; + } var item = Items[itemIndex]; diff --git a/FormCraft.ForMudBlazor/Features/FormContainer/FormCraftComponent.razor.cs b/FormCraft.ForMudBlazor/Features/FormContainer/FormCraftComponent.razor.cs index 01e07f36..d34d5371 100644 --- a/FormCraft.ForMudBlazor/Features/FormContainer/FormCraftComponent.razor.cs +++ b/FormCraft.ForMudBlazor/Features/FormContainer/FormCraftComponent.razor.cs @@ -321,7 +321,9 @@ private RenderFragment RenderField(IFieldConfiguration field) { var property = typeof(TModel).GetProperty(field.FieldName); if (property == null) + { return; + } var templateContext = new FieldContext( Model, diff --git a/FormCraft.ForMudBlazor/Fields/AutocompleteField/MudBlazorAutocompleteFieldComponent.razor.cs b/FormCraft.ForMudBlazor/Fields/AutocompleteField/MudBlazorAutocompleteFieldComponent.razor.cs index 5a814691..2d4bb65c 100644 --- a/FormCraft.ForMudBlazor/Fields/AutocompleteField/MudBlazorAutocompleteFieldComponent.razor.cs +++ b/FormCraft.ForMudBlazor/Fields/AutocompleteField/MudBlazorAutocompleteFieldComponent.razor.cs @@ -78,7 +78,10 @@ private async Task> SearchAsync(string searchText, Cancellat _toStringFunc = v => { if (v == null) + { return string.Empty; + } + var match = labelList.FirstOrDefault(item => EqualityComparer.Default.Equals(item.Value, v)); return match.Label ?? v.ToString() ?? string.Empty; }; diff --git a/FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorFileUploadComponentBase.cs b/FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorFileUploadComponentBase.cs index 2c28ee0b..0163cf5d 100644 --- a/FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorFileUploadComponentBase.cs +++ b/FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorFileUploadComponentBase.cs @@ -27,18 +27,6 @@ namespace FormCraft.ForMudBlazor; /// The field's value type — one file, or a list of them. public abstract class MudBlazorFileUploadComponentBase : FieldComponentBase { - /// - /// Per-render-instance discriminator for . - /// - /// - /// The field name alone is NOT unique in a document. Item fields render through these very - /// components since #203, so a required upload inside .WithItemForm(...) emits one hint - /// per row; two forms over the same model on one page collide the same way; and two nested - /// fields can share a member name (x => x.Passport.Scan and x => x.Visa.Scan). - /// Duplicate ids are invalid HTML and, worse, point every later button at the first row's - /// description. MudBlazor solves this the same way, with a per-component identifier. - /// - private readonly string _instanceDiscriminator = Guid.NewGuid().ToString("N")[..8]; /// /// Whether this field is marked as required, resolved by the same rule as every other field @@ -69,8 +57,31 @@ public abstract class MudBlazorFileUploadComponentBase : FieldCo /// /// The id of the requirement hint, unique per rendered field instance. /// - protected string RequiredDescriptionId => - $"formcraft-{Context.Field.FieldName}-required-{_instanceDiscriminator}"; + /// + /// + /// field is this property's own backing store (C# 14), initialised by the trailing + /// initializer to a short GUID once per component instance — that is the whole mechanism, + /// so do not "simplify" it to a constant or a shared static. + /// + /// + /// The field name alone is NOT unique in a document. Item fields render through these very + /// components since #203, so a required upload inside .WithItemForm(...) emits one hint + /// per row; two forms over the same model on one page collide the same way; and two nested + /// fields can share a member name (x => x.Passport.Scan and x => x.Visa.Scan). + /// Duplicate ids are invalid HTML and, worse, point every later button at the first row's + /// description. MudBlazor solves this the same way, with a per-component identifier. + /// + /// + /// This was an explicit _instanceDiscriminator field until #301, when IDE0032 — + /// newly reachable now that the format gate is enforced — folded it into the property. The + /// rewrite is value-identical; it deleted this explanation, which is why the explanation is + /// back. + /// + /// + protected string RequiredDescriptionId + { + get => $"formcraft-{Context.Field.FieldName}-required-{field}"; + } = Guid.NewGuid().ToString("N")[..8]; /// /// The value for the focusable button's aria-describedby: the hint's id when the field is diff --git a/FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorFileUploadFieldComponent.razor.cs b/FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorFileUploadFieldComponent.razor.cs index 722bc9c8..75d41464 100644 --- a/FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorFileUploadFieldComponent.razor.cs +++ b/FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorFileUploadFieldComponent.razor.cs @@ -79,7 +79,9 @@ private static string FormatFileSize(long bytes) foreach (string order in orders) { if (bytes > max) + { return $"{decimal.Divide(bytes, max):##.##} {order}"; + } max /= scale; } diff --git a/FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorMultipleFileUploadComponent.razor.cs b/FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorMultipleFileUploadComponent.razor.cs index 2788aa19..3efada7f 100644 --- a/FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorMultipleFileUploadComponent.razor.cs +++ b/FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorMultipleFileUploadComponent.razor.cs @@ -86,7 +86,9 @@ private string GetHeight() private static string FormatFileSize(long bytes) { if (bytes == 0) + { return "0 Bytes"; + } const int scale = 1024; string[] orders = { "GB", "MB", "KB", "Bytes" }; @@ -95,7 +97,9 @@ private static string FormatFileSize(long bytes) foreach (string order in orders) { if (bytes > max) + { return $"{decimal.Divide(bytes, max):##.##} {order}"; + } max /= scale; } diff --git a/FormCraft.ForMudBlazor/Fields/LookupField/MudBlazorLookupDialog.razor.cs b/FormCraft.ForMudBlazor/Fields/LookupField/MudBlazorLookupDialog.razor.cs index e1f010d9..da176f3e 100644 --- a/FormCraft.ForMudBlazor/Fields/LookupField/MudBlazorLookupDialog.razor.cs +++ b/FormCraft.ForMudBlazor/Fields/LookupField/MudBlazorLookupDialog.razor.cs @@ -45,7 +45,7 @@ public partial class MudBlazorLookupDialog : ComponentBase private string _searchText = string.Empty; private object? _selectedItem; private bool _loading; - private List _columnDefinitions = new(); + private readonly List _columnDefinitions = new(); protected override void OnInitialized() { @@ -56,18 +56,24 @@ protected override void OnInitialized() private void ExtractColumnDefinitions() { if (Columns == null) + { return; + } // The columns parameter is a List> stored as object. // We use reflection to extract column info. var columnsType = Columns.GetType(); if (!columnsType.IsGenericType) + { return; + } var enumerableInterface = columnsType.GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)); if (enumerableInterface == null) + { return; + } foreach (var col in (System.Collections.IEnumerable)Columns) { @@ -105,9 +111,10 @@ private async Task> LoadServerData(TableState state, Cancellat }; // Invoke the data provider delegate - var task = ((Delegate)DataProvider).DynamicInvoke(query) as Task; - if (task == null) + if (((Delegate)DataProvider).DynamicInvoke(query) is not Task task) + { return new TableData { Items = Array.Empty(), TotalItems = 0 }; + } await task; @@ -115,7 +122,9 @@ private async Task> LoadServerData(TableState state, Cancellat var resultProp = task.GetType().GetProperty("Result"); var result = resultProp?.GetValue(task); if (result == null) + { return new TableData { Items = Array.Empty(), TotalItems = 0 }; + } // Extract Items and TotalCount from LookupResult var itemsProp = result.GetType().GetProperty("Items"); diff --git a/FormCraft.ForMudBlazor/Fields/LookupField/MudBlazorLookupFieldComponent.razor.cs b/FormCraft.ForMudBlazor/Fields/LookupField/MudBlazorLookupFieldComponent.razor.cs index 3b60520d..d2169331 100644 --- a/FormCraft.ForMudBlazor/Fields/LookupField/MudBlazorLookupFieldComponent.razor.cs +++ b/FormCraft.ForMudBlazor/Fields/LookupField/MudBlazorLookupFieldComponent.razor.cs @@ -41,7 +41,9 @@ private void UpdateDisplayText() private async Task OpenLookupDialog() { if (IsReadOnly || IsDisabled) + { return; + } var dataProvider = GetAttribute("LookupDataProvider"); var valueSelector = GetAttribute("LookupValueSelector"); @@ -50,7 +52,9 @@ private async Task OpenLookupDialog() var onItemSelected = GetAttribute("LookupOnItemSelected"); if (dataProvider == null || valueSelector == null || displaySelector == null) + { return; + } var parameters = new DialogParameters { diff --git a/FormCraft.ForMudBlazor/Fields/LovField/LovSelectionDialog.razor.cs b/FormCraft.ForMudBlazor/Fields/LovField/LovSelectionDialog.razor.cs index 5acd8b07..087dda9a 100644 --- a/FormCraft.ForMudBlazor/Fields/LovField/LovSelectionDialog.razor.cs +++ b/FormCraft.ForMudBlazor/Fields/LovField/LovSelectionDialog.razor.cs @@ -193,9 +193,15 @@ private void ClearAllSelections() private string GetConfirmButtonText() { if (!_selectedItemsSet.Any()) + { return "Select"; + } + if (IsMultiSelect) + { return $"Select ({_selectedItemsSet.Count})"; + } + return "Select"; } diff --git a/FormCraft.ForMudBlazor/Fields/LovField/MudBlazorLovFieldComponent.razor.cs b/FormCraft.ForMudBlazor/Fields/LovField/MudBlazorLovFieldComponent.razor.cs index aae725cf..977b4846 100644 --- a/FormCraft.ForMudBlazor/Fields/LovField/MudBlazorLovFieldComponent.razor.cs +++ b/FormCraft.ForMudBlazor/Fields/LovField/MudBlazorLovFieldComponent.razor.cs @@ -11,16 +11,14 @@ namespace FormCraft.ForMudBlazor; /// The type of items in the LOV. public partial class MudBlazorLovFieldComponent { - private ILovConfiguration? _lovConfig; private ILovDataProvider? _dataProvider; private readonly List _selectedItems = []; - private string? _displayText; private bool _isLoading; /// /// Gets the LOV configuration. /// - protected ILovConfiguration? LovConfig => _lovConfig; + protected ILovConfiguration? LovConfig { get; private set; } /// /// Suppresses the ShrinkLabel diagnostic (#181) for LOV fields. @@ -37,7 +35,7 @@ public partial class MudBlazorLovFieldComponent /// /// Gets whether multiple selection is enabled. /// - protected bool IsMultiSelect => _lovConfig?.SelectionMode == LovSelectionMode.Multiple; + protected bool IsMultiSelect => LovConfig?.SelectionMode == LovSelectionMode.Multiple; /// /// Gets the list of selected items. @@ -47,7 +45,7 @@ public partial class MudBlazorLovFieldComponent /// /// Gets the display text for the field. /// - protected string? DisplayText => _displayText; + protected string? DisplayText { get; private set; } /// /// Gets the CSS class for the field. @@ -62,9 +60,15 @@ protected string AdornmentIcon get { if (_isLoading) + { return Icons.Material.Filled.HourglassEmpty; + } + if (CurrentValue != null && !IsMultiSelect) + { return Icons.Material.Filled.Clear; + } + return Icons.Material.Filled.Search; } } @@ -74,9 +78,9 @@ protected override void OnInitialized() { base.OnInitialized(); - _lovConfig = GetAttribute>("LovConfiguration"); + LovConfig = GetAttribute>("LovConfiguration"); - if (_lovConfig == null) + if (LovConfig == null) { throw new InvalidOperationException( "LovConfiguration is required. Use .AsLov() extension method to configure the field."); @@ -91,17 +95,18 @@ protected override void OnInitialized() private void InitializeDataProvider() { - if (_lovConfig == null) + if (LovConfig == null) + { return; + } - var factory = ServiceProvider.GetService(typeof(ILovDataProviderFactory)) as ILovDataProviderFactory; - if (factory != null) + if (ServiceProvider.GetService(typeof(ILovDataProviderFactory)) is ILovDataProviderFactory factory) { - _dataProvider = factory.Create(_lovConfig); + _dataProvider = factory.Create(LovConfig); } - else if (_lovConfig.DataProvider != null) + else if (LovConfig.DataProvider != null) { - _dataProvider = new LambdaLovDataProvider(_lovConfig.DataProvider, null); + _dataProvider = new LambdaLovDataProvider(LovConfig.DataProvider, null); } } @@ -109,24 +114,24 @@ private void UpdateDisplayText() { if (CurrentValue == null || EqualityComparer.Default.Equals(CurrentValue, default)) { - _displayText = null; + DisplayText = null; return; } if (IsMultiSelect) { - _displayText = _selectedItems.Count > 0 + DisplayText = _selectedItems.Count > 0 ? $"{_selectedItems.Count} item(s) selected" : null; } else if (_selectedItems.Count > 0) { - _displayText = GetItemDisplayText(_selectedItems[0]); + DisplayText = GetItemDisplayText(_selectedItems[0]); } else { // Try to get display text from cached value or show the value itself - _displayText = CurrentValue?.ToString(); + DisplayText = CurrentValue?.ToString(); } } @@ -135,7 +140,7 @@ private void UpdateDisplayText() /// protected string GetItemDisplayText(TItem item) { - return _lovConfig?.DisplaySelector(item) ?? item?.ToString() ?? string.Empty; + return LovConfig?.DisplaySelector(item) ?? item?.ToString() ?? string.Empty; } /// @@ -144,7 +149,9 @@ protected string GetItemDisplayText(TItem item) private async Task HandleAdornmentClick() { if (IsDisabled || IsReadOnly) + { return; + } // If there's a value and it's single select, clear it if (CurrentValue != null && !IsMultiSelect) @@ -161,8 +168,10 @@ private async Task HandleAdornmentClick() /// private async Task OpenLovDialog() { - if (_lovConfig == null || _dataProvider == null) + if (LovConfig == null || _dataProvider == null) + { return; + } _isLoading = true; StateHasChanged(); @@ -171,7 +180,7 @@ private async Task OpenLovDialog() { var parameters = new DialogParameters> { - { x => x.LovConfig, _lovConfig }, + { x => x.LovConfig, LovConfig }, { x => x.DataProvider, _dataProvider }, { x => x.SelectedItems, _selectedItems.ToList() }, { x => x.ServiceProvider, ServiceProvider } @@ -179,15 +188,15 @@ private async Task OpenLovDialog() var options = new DialogOptions { - MaxWidth = ConvertModalSize(_lovConfig.ModalOptions.Size), + MaxWidth = ConvertModalSize(LovConfig.ModalOptions.Size), FullWidth = true, CloseButton = true, - CloseOnEscapeKey = _lovConfig.ModalOptions.CloseOnEscapeKey, - BackdropClick = _lovConfig.ModalOptions.CloseOnBackdropClick + CloseOnEscapeKey = LovConfig.ModalOptions.CloseOnEscapeKey, + BackdropClick = LovConfig.ModalOptions.CloseOnBackdropClick }; var dialog = await DialogService.ShowAsync>( - _lovConfig.ModalOptions.Title, + LovConfig.ModalOptions.Title, parameters, options); @@ -213,14 +222,16 @@ private async Task ApplySelection(List items) _selectedItems.Clear(); _selectedItems.AddRange(items); - if (_lovConfig == null) + if (LovConfig == null) + { return; + } if (IsMultiSelect) { // For multi-select, we'd need to handle IEnumerable // This is a simplified implementation - var values = items.Select(_lovConfig.ValueSelector).ToList(); + var values = items.Select(LovConfig.ValueSelector).ToList(); // Note: This cast may need adjustment based on actual TValue type var typedValues = (TValue)(object)values; SetValueWithoutNotification(typedValues); @@ -231,7 +242,7 @@ private async Task ApplySelection(List items) var item = items.FirstOrDefault(); if (item != null) { - var value = _lovConfig.ValueSelector(item); + var value = LovConfig.ValueSelector(item); // Update our own state first so the display text and the // adornment reflect the selection immediately SetValueWithoutNotification(value); @@ -255,11 +266,13 @@ private async Task ApplySelection(List items) /// private async Task ApplyFieldMappings(TItem item) { - if (_lovConfig == null || Context.Model == null) + if (LovConfig == null || Context.Model == null) + { return; + } // Apply each mapping directly - foreach (var mapping in _lovConfig.FieldMappings) + foreach (var mapping in LovConfig.FieldMappings) { if (mapping is IAsyncLovFieldMapping asyncMapping) { @@ -281,15 +294,15 @@ private async Task ApplyFieldMappings(TItem item) private async Task ClearSelection() { _selectedItems.Clear(); - _displayText = null; + DisplayText = null; SetValueWithoutNotification(default); await NotifyValueChangedAsync(default); // Clear mapped fields by setting them to default values - if (_lovConfig != null && Context.Model != null) + if (LovConfig != null && Context.Model != null) { var modelType = Context.Model.GetType(); - foreach (var mapping in _lovConfig.FieldMappings) + foreach (var mapping in LovConfig.FieldMappings) { var property = modelType.GetProperty(mapping.TargetProperty); if (property?.CanWrite == true) diff --git a/FormCraft.ForMudBlazor/Fields/NumericField/MudBlazorNullableNumericFieldComponent.razor.cs b/FormCraft.ForMudBlazor/Fields/NumericField/MudBlazorNullableNumericFieldComponent.razor.cs index 23ffc7ff..b6014ba3 100644 --- a/FormCraft.ForMudBlazor/Fields/NumericField/MudBlazorNullableNumericFieldComponent.razor.cs +++ b/FormCraft.ForMudBlazor/Fields/NumericField/MudBlazorNullableNumericFieldComponent.razor.cs @@ -92,11 +92,20 @@ private static TValue GetDefaultStep() // so each floating type needs its own literal (0.1 boxed as double cannot be // unboxed as float, and 1 boxed as int cannot be unboxed as long/short/byte). if (typeof(TValue) == typeof(decimal)) + { return (TValue)(object)0.01m; + } + if (typeof(TValue) == typeof(double)) + { return (TValue)(object)0.1d; + } + if (typeof(TValue) == typeof(float)) + { return (TValue)(object)0.1f; + } + return (TValue)Convert.ChangeType(1, typeof(TValue)); } diff --git a/FormCraft.ForMudBlazor/Fields/NumericField/MudBlazorNumericFieldComponent.razor.cs b/FormCraft.ForMudBlazor/Fields/NumericField/MudBlazorNumericFieldComponent.razor.cs index aea03741..7022067d 100644 --- a/FormCraft.ForMudBlazor/Fields/NumericField/MudBlazorNumericFieldComponent.razor.cs +++ b/FormCraft.ForMudBlazor/Fields/NumericField/MudBlazorNumericFieldComponent.razor.cs @@ -84,11 +84,20 @@ private static TValue GetDefaultStep() // so each floating type needs its own literal (0.1 boxed as double cannot be // unboxed as float, and 1 boxed as int cannot be unboxed as long/short/byte). if (typeof(TValue) == typeof(decimal)) + { return (TValue)(object)0.01m; + } + if (typeof(TValue) == typeof(double)) + { return (TValue)(object)0.1d; + } + if (typeof(TValue) == typeof(float)) + { return (TValue)(object)0.1f; + } + return (TValue)Convert.ChangeType(1, typeof(TValue)); } diff --git a/FormCraft.UnitTests/Builders/FormBuilderTests.cs b/FormCraft.UnitTests/Builders/FormBuilderTests.cs index 9361eb23..5ca6ef2d 100644 --- a/FormCraft.UnitTests/Builders/FormBuilderTests.cs +++ b/FormCraft.UnitTests/Builders/FormBuilderTests.cs @@ -251,7 +251,9 @@ public void Complex_Form_Building_Scenario() .DependsOn(x => x.Country, (m, country) => { if (string.IsNullOrEmpty(country)) + { m.City = string.Empty; + } }) .WithOrder(5)) .Build(); diff --git a/FormCraft.UnitTests/GlobalUsings.cs b/FormCraft.UnitTests/GlobalUsings.cs index 42eb7563..e4edfe8b 100644 --- a/FormCraft.UnitTests/GlobalUsings.cs +++ b/FormCraft.UnitTests/GlobalUsings.cs @@ -1,10 +1,10 @@ -global using Xunit; -global using Shouldly; -global using FakeItEasy; +global using System.Linq.Expressions; global using Bunit; +global using FakeItEasy; +global using FormCraft; +global using FormCraft.ForMudBlazor; global using Microsoft.AspNetCore.Components; global using Microsoft.AspNetCore.Components.Forms; global using Microsoft.Extensions.DependencyInjection; -global using System.Linq.Expressions; -global using FormCraft; -global using FormCraft.ForMudBlazor; +global using Shouldly; +global using Xunit; diff --git a/FormCraft.UnitTests/Validators/AsyncValidatorTests.cs b/FormCraft.UnitTests/Validators/AsyncValidatorTests.cs index b26d92ab..f3e4f0f1 100644 --- a/FormCraft.UnitTests/Validators/AsyncValidatorTests.cs +++ b/FormCraft.UnitTests/Validators/AsyncValidatorTests.cs @@ -155,14 +155,18 @@ async Task ComplexValidation(string phoneNumber) await Task.Delay(25); // First service call if (string.IsNullOrWhiteSpace(phoneNumber)) + { return false; + } await Task.Delay(25); // Second service call // Check format var cleanNumber = phoneNumber.Replace("-", "").Replace(" ", "").Replace("(", "").Replace(")", ""); if (cleanNumber.Length != 10) + { return false; + } await Task.Delay(25); // Third service call diff --git a/FormCraft.UnitTests/Validators/CustomValidatorTests.cs b/FormCraft.UnitTests/Validators/CustomValidatorTests.cs index 216e8dbe..2fcbd56f 100644 --- a/FormCraft.UnitTests/Validators/CustomValidatorTests.cs +++ b/FormCraft.UnitTests/Validators/CustomValidatorTests.cs @@ -64,7 +64,10 @@ public async Task ValidateAsync_Should_Support_Complex_Validation_Logic() Func validationFunction = value => { if (string.IsNullOrEmpty(value)) + { return false; + } + return value.Length >= 3 && value.Length <= 20 && value.All(char.IsLetterOrDigit); }; @@ -176,7 +179,9 @@ public async Task ValidateAsync_Should_Support_Email_Validation() Func emailValidation = value => { if (string.IsNullOrWhiteSpace(value)) + { return false; + } try { diff --git a/FormCraft/Forms/Builders/FieldBuilder.cs b/FormCraft/Forms/Builders/FieldBuilder.cs index c4f4e4dd..2853daa3 100644 --- a/FormCraft/Forms/Builders/FieldBuilder.cs +++ b/FormCraft/Forms/Builders/FieldBuilder.cs @@ -20,18 +20,17 @@ namespace FormCraft; public class FieldBuilder where TModel : new() { private readonly FormBuilder _formBuilder; - private readonly FieldConfiguration _fieldConfiguration; internal FieldBuilder(FormBuilder formBuilder, FieldConfiguration fieldConfiguration) { _formBuilder = formBuilder; - _fieldConfiguration = fieldConfiguration; + Configuration = fieldConfiguration; } /// /// Gets the underlying field configuration for advanced scenarios. /// - internal FieldConfiguration Configuration => _fieldConfiguration; + internal FieldConfiguration Configuration { get; } /// /// Sets the display label for the field. @@ -45,7 +44,7 @@ internal FieldBuilder(FormBuilder formBuilder, FieldConfiguration public FieldBuilder WithLabel(string label) { - _fieldConfiguration.Label = label; + Configuration.Label = label; return this; } @@ -61,7 +60,7 @@ public FieldBuilder WithLabel(string label) /// public FieldBuilder WithPlaceholder(string placeholder) { - _fieldConfiguration.Placeholder = placeholder; + Configuration.Placeholder = placeholder; return this; } @@ -77,7 +76,7 @@ public FieldBuilder WithPlaceholder(string placeholder) /// public FieldBuilder WithHelpText(string helpText) { - _fieldConfiguration.HelpText = helpText; + Configuration.HelpText = helpText; return this; } @@ -93,7 +92,7 @@ public FieldBuilder WithHelpText(string helpText) /// public FieldBuilder WithCssClass(string cssClass) { - _fieldConfiguration.CssClass = cssClass; + Configuration.CssClass = cssClass; return this; } @@ -110,7 +109,7 @@ public FieldBuilder WithCssClass(string cssClass) /// public FieldBuilder WithInputType(string inputType) { - _fieldConfiguration.InputType = inputType; + Configuration.InputType = inputType; return this; } @@ -126,8 +125,8 @@ public FieldBuilder WithInputType(string inputType) /// public FieldBuilder Required(string? errorMessage = null) { - _fieldConfiguration.IsRequired = true; - _fieldConfiguration.AddValidator(new RequiredValidator(errorMessage)); + Configuration.IsRequired = true; + Configuration.AddValidator(new RequiredValidator(errorMessage)); return this; } @@ -143,7 +142,7 @@ public FieldBuilder Required(string? errorMessage = null) /// public FieldBuilder Disabled(bool disabled = true) { - _fieldConfiguration.IsDisabled = disabled; + Configuration.IsDisabled = disabled; return this; } @@ -159,7 +158,7 @@ public FieldBuilder Disabled(bool disabled = true) /// public FieldBuilder ReadOnly(bool readOnly = true) { - _fieldConfiguration.IsReadOnly = readOnly; + Configuration.IsReadOnly = readOnly; return this; } @@ -175,7 +174,7 @@ public FieldBuilder ReadOnly(bool readOnly = true) /// public FieldBuilder VisibleWhen(Func condition) { - _fieldConfiguration.VisibilityCondition = condition; + Configuration.VisibilityCondition = condition; return this; } @@ -191,7 +190,7 @@ public FieldBuilder VisibleWhen(Func condition) /// public FieldBuilder DisabledWhen(Func condition) { - _fieldConfiguration.DisabledCondition = condition; + Configuration.DisabledCondition = condition; return this; } @@ -208,7 +207,7 @@ public FieldBuilder DisabledWhen(Func condition) /// public FieldBuilder WithAttribute(string name, object value) { - _fieldConfiguration.AdditionalAttributes[name] = value; + Configuration.AdditionalAttributes[name] = value; return this; } @@ -230,7 +229,7 @@ public FieldBuilder WithAttributes(Dictionary at { foreach (var attr in attributes) { - _fieldConfiguration.AdditionalAttributes[attr.Key] = attr.Value; + Configuration.AdditionalAttributes[attr.Key] = attr.Value; } return this; } @@ -247,7 +246,7 @@ public FieldBuilder WithAttributes(Dictionary at /// public FieldBuilder WithValidator(IFieldValidator validator) { - _fieldConfiguration.AddValidator(validator); + Configuration.AddValidator(validator); return this; } @@ -264,7 +263,7 @@ public FieldBuilder WithValidator(IFieldValidator public FieldBuilder WithValidator(Func validation, string errorMessage) { - _fieldConfiguration.AddValidator(new CustomValidator(validation, errorMessage)); + Configuration.AddValidator(new CustomValidator(validation, errorMessage)); return this; } @@ -281,7 +280,7 @@ public FieldBuilder WithValidator(Func validation, /// public FieldBuilder WithAsyncValidator(Func> validation, string errorMessage) { - _fieldConfiguration.AddValidator(new AsyncValidator(validation, errorMessage)); + Configuration.AddValidator(new AsyncValidator(validation, errorMessage)); return this; } @@ -306,7 +305,7 @@ public FieldBuilder DependsOn( Action onChanged) { var dependency = new FieldDependency(dependsOnExpression, onChanged); - _fieldConfiguration.Dependencies.Add(dependency); + Configuration.Dependencies.Add(dependency); // Key by the WATCHED field's name: the runtime looks the dictionary up with // the name of the field that just changed to find the callbacks to fire. _formBuilder.AddFieldDependency(dependency.DependentFieldName, dependency); @@ -335,7 +334,7 @@ public FieldBuilder DependsOn( Func onChangedAsync) { var dependency = new FieldDependency(dependsOnExpression, onChangedAsync); - _fieldConfiguration.Dependencies.Add(dependency); + Configuration.Dependencies.Add(dependency); // Key by the WATCHED field's name: the runtime looks the dictionary up with // the name of the field that just changed to find the callbacks to fire. _formBuilder.AddFieldDependency(dependency.DependentFieldName, dependency); @@ -358,7 +357,7 @@ public FieldBuilder DependsOn( /// public FieldBuilder WithCustomTemplate(RenderFragment> template) { - _fieldConfiguration.CustomTemplate = template; + Configuration.CustomTemplate = template; return this; } @@ -374,7 +373,7 @@ public FieldBuilder WithCustomTemplate(RenderFragment public FieldBuilder WithOrder(int order) { - _fieldConfiguration.Order = order; + Configuration.Order = order; return this; } @@ -390,8 +389,8 @@ public FieldBuilder WithOrder(int order) /// public FieldBuilder WithCustomRenderer(IFieldRenderer renderer) { - _fieldConfiguration.CustomRendererType = renderer.GetType(); - _fieldConfiguration.AdditionalAttributes["CustomRendererInstance"] = renderer; + Configuration.CustomRendererType = renderer.GetType(); + Configuration.AdditionalAttributes["CustomRendererInstance"] = renderer; return this; } diff --git a/FormCraft/Forms/Builders/FieldConfigurationWrapper.cs b/FormCraft/Forms/Builders/FieldConfigurationWrapper.cs index 94d82618..0ebf0cc3 100644 --- a/FormCraft/Forms/Builders/FieldConfigurationWrapper.cs +++ b/FormCraft/Forms/Builders/FieldConfigurationWrapper.cs @@ -11,7 +11,6 @@ namespace FormCraft; /// The actual type of the field value. public class FieldConfigurationWrapper : IFieldConfiguration { - private readonly IFieldConfiguration _inner; /// /// Initializes a new instance of the FieldConfigurationWrapper class. @@ -19,13 +18,11 @@ public class FieldConfigurationWrapper : IFieldConfigurationThe strongly-typed field configuration to wrap. public FieldConfigurationWrapper(IFieldConfiguration inner) { - _inner = inner; + TypedConfiguration = inner; } /// - public string FieldName => _inner.FieldName; - - private Expression>? _valueExpression; + public string FieldName => TypedConfiguration.FieldName; /// /// @@ -48,42 +45,42 @@ public FieldConfigurationWrapper(IFieldConfiguration inner) /// /// public Expression> ValueExpression => - _valueExpression ??= Expression.Lambda>( - Expression.Convert(_inner.ValueExpression.Body, typeof(object)), - _inner.ValueExpression.Parameters); + field ??= Expression.Lambda>( + Expression.Convert(TypedConfiguration.ValueExpression.Body, typeof(object)), + TypedConfiguration.ValueExpression.Parameters); /// - public string? Label { get => _inner.Label; set => _inner.Label = value; } + public string? Label { get => TypedConfiguration.Label; set => TypedConfiguration.Label = value; } /// - public string? Placeholder { get => _inner.Placeholder; set => _inner.Placeholder = value; } + public string? Placeholder { get => TypedConfiguration.Placeholder; set => TypedConfiguration.Placeholder = value; } /// - public string? HelpText { get => _inner.HelpText; set => _inner.HelpText = value; } + public string? HelpText { get => TypedConfiguration.HelpText; set => TypedConfiguration.HelpText = value; } /// - public string? CssClass { get => _inner.CssClass; set => _inner.CssClass = value; } + public string? CssClass { get => TypedConfiguration.CssClass; set => TypedConfiguration.CssClass = value; } /// - public bool IsRequired { get => _inner.IsRequired; set => _inner.IsRequired = value; } + public bool IsRequired { get => TypedConfiguration.IsRequired; set => TypedConfiguration.IsRequired = value; } /// - public bool IsVisible { get => _inner.IsVisible; set => _inner.IsVisible = value; } + public bool IsVisible { get => TypedConfiguration.IsVisible; set => TypedConfiguration.IsVisible = value; } /// - public bool IsDisabled { get => _inner.IsDisabled; set => _inner.IsDisabled = value; } + public bool IsDisabled { get => TypedConfiguration.IsDisabled; set => TypedConfiguration.IsDisabled = value; } /// - public bool IsReadOnly { get => _inner.IsReadOnly; set => _inner.IsReadOnly = value; } + public bool IsReadOnly { get => TypedConfiguration.IsReadOnly; set => TypedConfiguration.IsReadOnly = value; } /// - public int Order { get => _inner.Order; set => _inner.Order = value; } + public int Order { get => TypedConfiguration.Order; set => TypedConfiguration.Order = value; } /// - public Dictionary AdditionalAttributes => _inner.AdditionalAttributes; + public Dictionary AdditionalAttributes => TypedConfiguration.AdditionalAttributes; /// - public string? InputType { get => _inner.InputType; set => _inner.InputType = value; } + public string? InputType { get => TypedConfiguration.InputType; set => TypedConfiguration.InputType = value; } private List>? _validators; private int _wrappedInnerValidatorCount; @@ -102,10 +99,10 @@ public IReadOnlyList> Validators { if (_validators == null) { - _validators = _inner.Validators + _validators = TypedConfiguration.Validators .Select, IFieldValidator>(v => new ValidatorWrapper(v)) .ToList(); - _wrappedInnerValidatorCount = _inner.Validators.Count; + _wrappedInnerValidatorCount = TypedConfiguration.Validators.Count; } else { @@ -133,24 +130,24 @@ public void AddValidator(IFieldValidator validator) var typedValidator = validator is ValidatorWrapper wrapper ? wrapper.Inner : new ObjectValidatorAdapter(validator); - _inner.AddValidator(typedValidator); + TypedConfiguration.AddValidator(typedValidator); // Appended to the backing list directly: the public view is IReadOnlyList since #155, and // the whole point of that change is that callers cannot do this. Adding the caller's own // instance (rather than re-wrapping the one just handed to _inner) keeps reference identity // through the object-typed view. _validators!.Add(validator); - _wrappedInnerValidatorCount = _inner.Validators.Count; + _wrappedInnerValidatorCount = TypedConfiguration.Validators.Count; } private void SyncNewInnerValidators() { - for (var i = _wrappedInnerValidatorCount; i < _inner.Validators.Count; i++) + for (var i = _wrappedInnerValidatorCount; i < TypedConfiguration.Validators.Count; i++) { - _validators!.Add(new ValidatorWrapper(_inner.Validators[i])); + _validators!.Add(new ValidatorWrapper(TypedConfiguration.Validators[i])); } - _wrappedInnerValidatorCount = _inner.Validators.Count; + _wrappedInnerValidatorCount = TypedConfiguration.Validators.Count; } private sealed class ObjectValidatorAdapter : IFieldValidator @@ -173,45 +170,43 @@ public Task ValidateAsync(TModel model, TValue value, IService } /// - public List> Dependencies => _inner.Dependencies; + public List> Dependencies => TypedConfiguration.Dependencies; /// public Func? VisibilityCondition { - get => _inner.VisibilityCondition; - set => _inner.VisibilityCondition = value; + get => TypedConfiguration.VisibilityCondition; + set => TypedConfiguration.VisibilityCondition = value; } /// public Func? DisabledCondition { - get => _inner.DisabledCondition; - set => _inner.DisabledCondition = value; + get => TypedConfiguration.DisabledCondition; + set => TypedConfiguration.DisabledCondition = value; } - private RenderFragment>? _customTemplate; - /// public RenderFragment>? CustomTemplate { get { - if (_customTemplate != null) + if (field != null) { - return _customTemplate; + return field; } // Surface templates configured through the typed builder API by adapting // the object-typed render context to the typed one the template expects. - var typedTemplate = _inner.CustomTemplate; + var typedTemplate = TypedConfiguration.CustomTemplate; if (typedTemplate == null) { return null; } - return objectContext => typedTemplate(new TypedFieldContextAdapter(objectContext, _inner)); + return objectContext => typedTemplate(new TypedFieldContextAdapter(objectContext, TypedConfiguration)); } - set => _customTemplate = value; + set; } private sealed class TypedFieldContextAdapter : IFieldContext @@ -246,14 +241,14 @@ public TValue Value /// public Type? CustomRendererType { - get => _inner.CustomRendererType; - set => _inner.CustomRendererType = value; + get => TypedConfiguration.CustomRendererType; + set => TypedConfiguration.CustomRendererType = value; } /// /// Gets access to the original typed configuration. /// - public IFieldConfiguration TypedConfiguration => _inner; + public IFieldConfiguration TypedConfiguration { get; } /// /// Gets the actual runtime type of the field value. diff --git a/FormCraft/Forms/Builders/ValidatorWrapper.cs b/FormCraft/Forms/Builders/ValidatorWrapper.cs index 25006830..2717fe73 100644 --- a/FormCraft/Forms/Builders/ValidatorWrapper.cs +++ b/FormCraft/Forms/Builders/ValidatorWrapper.cs @@ -8,7 +8,6 @@ namespace FormCraft; /// The actual type of the field value being validated. public class ValidatorWrapper : IFieldValidator { - private readonly IFieldValidator _inner; /// /// Initializes a new instance of the ValidatorWrapper class. @@ -16,19 +15,19 @@ public class ValidatorWrapper : IFieldValidator /// The strongly-typed validator to wrap. public ValidatorWrapper(IFieldValidator inner) { - _inner = inner; + Inner = inner; } /// /// Gets the strongly-typed validator wrapped by this instance. /// - internal IFieldValidator Inner => _inner; + internal IFieldValidator Inner { get; } /// public string? ErrorMessage { - get => _inner.ErrorMessage; - set => _inner.ErrorMessage = value; + get => Inner.ErrorMessage; + set => Inner.ErrorMessage = value; } /// @@ -45,6 +44,6 @@ public async Task ValidateAsync(TModel model, object? value, I typedValue = default!; } - return await _inner.ValidateAsync(model, typedValue, services); + return await Inner.ValidateAsync(model, typedValue, services); } } diff --git a/FormCraft/Forms/Core/FileUploadConfiguration.cs b/FormCraft/Forms/Core/FileUploadConfiguration.cs index e786a787..deb4a880 100644 --- a/FormCraft/Forms/Core/FileUploadConfiguration.cs +++ b/FormCraft/Forms/Core/FileUploadConfiguration.cs @@ -55,7 +55,9 @@ public class FileUploadConfiguration public ValidationResult ValidateFile(IBrowserFile? file) { if (file == null) + { return ValidationResult.Success(); + } // Check file size if (MaxFileSize.HasValue && file.Size > MaxFileSize.Value) @@ -87,7 +89,9 @@ public string GetConstraintsDescription() var parts = new List(); if (AcceptedFileTypes?.Length > 0) + { parts.Add($"Accepted formats: {string.Join(", ", AcceptedFileTypes)}"); + } if (MaxFileSize.HasValue) { @@ -96,7 +100,9 @@ public string GetConstraintsDescription() } if (MaxFiles > 1) + { parts.Add($"Max files: {MaxFiles}"); + } return parts.Count > 0 ? string.Join(" • ", parts) : string.Empty; } diff --git a/FormCraft/Forms/Extensions/AttributeFormBuilderExtensions.cs b/FormCraft/Forms/Extensions/AttributeFormBuilderExtensions.cs index 20f98340..bbe88c4b 100644 --- a/FormCraft/Forms/Extensions/AttributeFormBuilderExtensions.cs +++ b/FormCraft/Forms/Extensions/AttributeFormBuilderExtensions.cs @@ -47,7 +47,9 @@ public FormBuilder AddFieldsFromAttributes() .WithInputType("email"); if (!string.IsNullOrEmpty(emailAttr.Placeholder)) + { field.WithPlaceholder(emailAttr.Placeholder); + } if (emailAttr.ValidateFormat) { @@ -75,7 +77,9 @@ public FormBuilder AddFieldsFromAttributes() field.WithLabel(textAreaAttr.Label); if (!string.IsNullOrEmpty(textAreaAttr.Placeholder)) + { field.WithPlaceholder(textAreaAttr.Placeholder); + } field.WithAttribute("rows", textAreaAttr.Rows); @@ -86,7 +90,9 @@ public FormBuilder AddFieldsFromAttributes() } if (textAreaAttr.AutoResize) + { field.WithAttribute("auto-resize", true); + } ApplyValidationAttributes(field, prop, textAreaAttr.Label); }); @@ -122,10 +128,14 @@ public FormBuilder AddFieldsFromAttributes() field.WithLabel(checkboxAttr.Label); if (!string.IsNullOrEmpty(checkboxAttr.Text)) + { field.WithAttribute("text", checkboxAttr.Text); + } if (checkboxAttr.DefaultChecked) + { field.WithAttribute("default-checked", true); + } }); continue; } @@ -155,7 +165,9 @@ private static void AddStringField(FormBuilder builder, Property .WithInputType(inputType); if (!string.IsNullOrEmpty(placeholder)) + { field.WithPlaceholder(placeholder); + } ApplyValidationAttributes(field, prop, label); }); @@ -202,16 +214,24 @@ private static void ConfigureNumericField(FieldBuilder(FieldBuilder( field.WithLabel(selectAttr.Label); if (!string.IsNullOrEmpty(selectAttr.Placeholder)) + { field.WithPlaceholder(selectAttr.Placeholder); + } if (selectAttr.Options != null && selectAttr.Options.Length > 0) + { field.WithAttribute("options", selectAttr.Options); + } if (selectAttr.AllowMultiple) + { field.WithAttribute("multiple", true); + } if (!string.IsNullOrEmpty(selectAttr.OptionsProviderName)) + { field.WithAttribute("options-provider", selectAttr.OptionsProviderName); + } ApplyValidationAttributes(field, prop, selectAttr.Label); }); @@ -300,21 +336,26 @@ private static void ApplyValidationAttributes(FieldBuilder(); if (required != null) + { field.Required(required.ErrorMessage ?? $"{label} is required"); + } // Only apply string-specific validations for string fields if (typeof(TValue) == typeof(string)) { - var stringField = field as FieldBuilder; - if (stringField != null) + if (field is FieldBuilder stringField) { var minLength = prop.GetCustomAttribute(); if (minLength != null) + { stringField.WithMinLength(minLength.Length, minLength.ErrorMessage ?? $"Must be at least {minLength.Length} characters"); + } var maxLength = prop.GetCustomAttribute(); if (maxLength != null) + { stringField.WithMaxLength(maxLength.Length, maxLength.ErrorMessage ?? $"Must be no more than {maxLength.Length} characters"); + } } } @@ -334,7 +375,10 @@ private static void ApplyValidationAttributes(FieldBuilder { if (value == null) + { return true; + } + return System.Text.RegularExpressions.Regex.IsMatch(value.ToString()!, pattern.Pattern); }, pattern.ErrorMessage ?? "Invalid format"); } diff --git a/FormCraft/Forms/Extensions/FieldBuilderExtensions.cs b/FormCraft/Forms/Extensions/FieldBuilderExtensions.cs index 7ebd60b4..d7e2179b 100644 --- a/FormCraft/Forms/Extensions/FieldBuilderExtensions.cs +++ b/FormCraft/Forms/Extensions/FieldBuilderExtensions.cs @@ -457,7 +457,10 @@ public static FieldBuilder AsAutocomplete( builder.WithAttribute("AutocompleteDebounceMs", debounceMs); builder.WithAttribute("AutocompleteMinCharacters", minCharacters); if (toStringFunc != null) + { builder.WithAttribute("AutocompleteToStringFunc", toStringFunc); + } + return builder; } @@ -493,14 +496,19 @@ public static FieldBuilder AsAutocomplete( builder.WithAttribute("AutocompleteDebounceMs", debounceMs); builder.WithAttribute("AutocompleteMinCharacters", minCharacters); if (toStringFunc != null) + { builder.WithAttribute("AutocompleteToStringFunc", toStringFunc); + } + return builder; } private static bool IsValidEmail(string email) { if (string.IsNullOrWhiteSpace(email)) + { return false; + } try { diff --git a/FormCraft/Forms/Extensions/FluentFormBuilderExtensions.cs b/FormCraft/Forms/Extensions/FluentFormBuilderExtensions.cs index 2006c290..c022917a 100644 --- a/FormCraft/Forms/Extensions/FluentFormBuilderExtensions.cs +++ b/FormCraft/Forms/Extensions/FluentFormBuilderExtensions.cs @@ -39,12 +39,16 @@ public FormBuilder AddRequiredTextField(Expression> .Required($"{label} is required"); if (minLength > 1) + { field.WithMinLength(minLength, $"Must be at least {minLength} characters"); + } field.WithMaxLength(maxLength, $"Must be no more than {maxLength} characters"); if (!string.IsNullOrEmpty(placeholder)) + { field.WithPlaceholder(placeholder); + } }); } @@ -102,12 +106,16 @@ public FormBuilder AddNumericField(Expression> express field.WithLabel(label); if (required) + { field.Required($"{label} is required"); + } var hasMin = min != int.MinValue; var hasMax = max != int.MaxValue; if (hasMin || hasMax) + { field.WithRange(min, max, BuildRangeMessage(hasMin, hasMax, min, max)); + } }); } @@ -138,15 +146,21 @@ public FormBuilder AddDecimalField(Expression> exp field.WithLabel(label); if (required) + { field.Required($"{label} is required"); + } var hasMin = min != decimal.MinValue; var hasMax = max != decimal.MaxValue; if (hasMin || hasMax) + { field.WithRange(min, max, BuildRangeMessage(hasMin, hasMax, min, max)); + } if (!string.IsNullOrEmpty(placeholder)) + { field.WithPlaceholder(placeholder); + } }); } @@ -175,7 +189,9 @@ public FormBuilder AddCurrencyField(Expression> ex .WithHelpText($"Enter amount in {currencySymbol}"); if (required) + { field.Required($"{label} is required"); + } // Ensure non-negative values for currency field.WithRange(0, decimal.MaxValue, "Amount must be positive"); @@ -206,7 +222,9 @@ public FormBuilder AddPercentageField(Expression> .WithRange(0, 100, "Percentage must be between 0 and 100"); if (required) + { field.Required($"{label} is required"); + } }); } @@ -261,7 +279,9 @@ public FormBuilder AddPhoneField(Expression> expres "Please enter a valid phone number"); if (required) + { field.Required($"{label} is required"); + } }); } @@ -321,7 +341,9 @@ public FormBuilder AddCheckboxField(Expression> expre field.WithLabel(label); if (!string.IsNullOrEmpty(helpText)) + { field.WithHelpText(helpText); + } }); } } @@ -340,7 +362,9 @@ private static string BuildRangeMessage(bool hasMin, bool hasMax, TValue private static bool IsValidPhone(string phone) { if (string.IsNullOrWhiteSpace(phone)) + { return false; + } // Remove common phone formatting var cleanPhone = phone.Replace("(", "").Replace(")", "").Replace("-", "").Replace(" ", "").Replace(".", ""); @@ -386,7 +410,9 @@ public static FormBuilder AddFileUploadField( .AsFileUpload(acceptedFileTypes, maxFileSize); if (required) + { field.Required($"{label} is required"); + } }); } @@ -425,7 +451,9 @@ public static FormBuilder AddMultipleFileUploadField( .AsMultipleFileUpload(maxFiles, acceptedFileTypes, maxFileSize); if (required) + { field.Required($"At least one {label.ToLower()} is required"); + } }); } diff --git a/FormCraft/Forms/Rendering/BoolFieldRenderer.cs b/FormCraft/Forms/Rendering/BoolFieldRenderer.cs index ceb48121..02aabca7 100644 --- a/FormCraft/Forms/Rendering/BoolFieldRenderer.cs +++ b/FormCraft/Forms/Rendering/BoolFieldRenderer.cs @@ -31,7 +31,9 @@ private class TestStubComponent : ComponentBase protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { if (Context == null) + { return; + } var sequence = 0; builder.OpenElement(sequence++, "div"); diff --git a/FormCraft/Forms/Rendering/DateTimeFieldRenderer.cs b/FormCraft/Forms/Rendering/DateTimeFieldRenderer.cs index 1707fa0c..1123f32f 100644 --- a/FormCraft/Forms/Rendering/DateTimeFieldRenderer.cs +++ b/FormCraft/Forms/Rendering/DateTimeFieldRenderer.cs @@ -30,7 +30,9 @@ private class TestStubComponent : ComponentBase protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { if (Context == null) + { return; + } var sequence = 0; builder.OpenElement(sequence++, "div"); diff --git a/FormCraft/Forms/Rendering/DecimalFieldRenderer.cs b/FormCraft/Forms/Rendering/DecimalFieldRenderer.cs index f3c296f1..214c1187 100644 --- a/FormCraft/Forms/Rendering/DecimalFieldRenderer.cs +++ b/FormCraft/Forms/Rendering/DecimalFieldRenderer.cs @@ -30,7 +30,9 @@ private class TestStubComponent : ComponentBase protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { if (Context == null) + { return; + } var sequence = 0; builder.OpenElement(sequence++, "div"); diff --git a/FormCraft/Forms/Rendering/DoubleFieldRenderer.cs b/FormCraft/Forms/Rendering/DoubleFieldRenderer.cs index d0ced3ef..f7e0e844 100644 --- a/FormCraft/Forms/Rendering/DoubleFieldRenderer.cs +++ b/FormCraft/Forms/Rendering/DoubleFieldRenderer.cs @@ -30,7 +30,9 @@ private class TestStubComponent : ComponentBase protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { if (Context == null) + { return; + } var sequence = 0; builder.OpenElement(sequence++, "div"); diff --git a/FormCraft/Forms/Rendering/FieldRendererBase.cs b/FormCraft/Forms/Rendering/FieldRendererBase.cs index 7c1c0a19..b149eabc 100644 --- a/FormCraft/Forms/Rendering/FieldRendererBase.cs +++ b/FormCraft/Forms/Rendering/FieldRendererBase.cs @@ -1,6 +1,6 @@ +using System.Reflection; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; -using System.Reflection; namespace FormCraft; @@ -92,7 +92,9 @@ private static bool HasStructConstraint(Type genericType, int parameterIndex) { var genericArgs = genericType.GetGenericArguments(); if (parameterIndex >= genericArgs.Length) + { return false; + } var attributes = genericArgs[parameterIndex].GenericParameterAttributes; diff --git a/FormCraft/Forms/Rendering/FieldRendererService.cs b/FormCraft/Forms/Rendering/FieldRendererService.cs index d4ad38b1..bf28fa45 100644 --- a/FormCraft/Forms/Rendering/FieldRendererService.cs +++ b/FormCraft/Forms/Rendering/FieldRendererService.cs @@ -1,7 +1,7 @@ -using Microsoft.AspNetCore.Components; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Components; namespace FormCraft; diff --git a/FormCraft/Forms/Rendering/FileUploadFieldRenderer.cs b/FormCraft/Forms/Rendering/FileUploadFieldRenderer.cs index 1ce022fc..902cd71e 100644 --- a/FormCraft/Forms/Rendering/FileUploadFieldRenderer.cs +++ b/FormCraft/Forms/Rendering/FileUploadFieldRenderer.cs @@ -43,7 +43,9 @@ private class TestStubComponent : ComponentBase protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { if (Context == null) + { return; + } var sequence = 0; builder.OpenElement(sequence++, "div"); diff --git a/FormCraft/Forms/Rendering/IntFieldRenderer.cs b/FormCraft/Forms/Rendering/IntFieldRenderer.cs index c6fc13c1..1a13c426 100644 --- a/FormCraft/Forms/Rendering/IntFieldRenderer.cs +++ b/FormCraft/Forms/Rendering/IntFieldRenderer.cs @@ -31,7 +31,9 @@ private class TestStubComponent : ComponentBase protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { if (Context == null) + { return; + } var sequence = 0; builder.OpenElement(sequence++, "div"); diff --git a/FormCraft/Forms/Rendering/StringFieldRenderer.cs b/FormCraft/Forms/Rendering/StringFieldRenderer.cs index 72d9dc81..594ca39c 100644 --- a/FormCraft/Forms/Rendering/StringFieldRenderer.cs +++ b/FormCraft/Forms/Rendering/StringFieldRenderer.cs @@ -27,7 +27,9 @@ private class TestStubComponent : ComponentBase protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { if (Context == null) + { return; + } var sequence = 0; builder.OpenElement(sequence++, "div"); diff --git a/FormCraft/Forms/Security/BlazorCsrfTokenService.cs b/FormCraft/Forms/Security/BlazorCsrfTokenService.cs index 542424d0..3f7be1a2 100644 --- a/FormCraft/Forms/Security/BlazorCsrfTokenService.cs +++ b/FormCraft/Forms/Security/BlazorCsrfTokenService.cs @@ -56,7 +56,9 @@ public async Task GenerateTokenAsync() public async Task ValidateTokenAsync(string token) { if (string.IsNullOrEmpty(token)) + { return false; + } string? storedToken = null; try @@ -69,14 +71,19 @@ public async Task ValidateTokenAsync(string token) } if (!string.IsNullOrEmpty(storedToken)) + { return FixedTimeEquals(storedToken, token); + } // No stored token: accept the token this service instance issued (it may not have been // persisted yet because generation happened during prerendering) and try to persist it now. if (_currentToken != null && FixedTimeEquals(_currentToken, token)) { if (!_isPersisted) + { _isPersisted = await TryPersistTokenAsync(_currentToken); + } + return true; } diff --git a/FormCraft/Forms/Security/BlazorEncryptionService.cs b/FormCraft/Forms/Security/BlazorEncryptionService.cs index f147bac3..529d8bab 100644 --- a/FormCraft/Forms/Security/BlazorEncryptionService.cs +++ b/FormCraft/Forms/Security/BlazorEncryptionService.cs @@ -55,7 +55,9 @@ public BlazorEncryptionService(IConfiguration? configuration = null) public string? Encrypt(string? value) { if (string.IsNullOrEmpty(value)) + { return value; + } var data = Encoding.UTF8.GetBytes(value); var encrypted = new byte[data.Length]; @@ -76,7 +78,9 @@ public BlazorEncryptionService(IConfiguration? configuration = null) public string? Decrypt(string? encryptedValue) { if (string.IsNullOrEmpty(encryptedValue)) + { return encryptedValue; + } byte[] encrypted; try @@ -107,12 +111,16 @@ private static byte[] ParseKey(string keyString) // Prefer a Base64-encoded 32-byte key. var base64Buffer = new byte[keyString.Length]; if (Convert.TryFromBase64String(keyString, base64Buffer, out var bytesWritten) && bytesWritten == KeySizeInBytes) + { return base64Buffer[..bytesWritten]; + } // Fall back to a raw string whose UTF-8 representation is exactly 32 bytes. var utf8Key = Encoding.UTF8.GetBytes(keyString); if (utf8Key.Length == KeySizeInBytes) + { return utf8Key; + } throw new InvalidOperationException( $"The configured '{KeyConfigurationPath}' is invalid. Provide a Base64-encoded {KeySizeInBytes}-byte key " + diff --git a/FormCraft/Forms/Security/ConsoleAuditLogService.cs b/FormCraft/Forms/Security/ConsoleAuditLogService.cs index a1d7b63c..76695404 100644 --- a/FormCraft/Forms/Security/ConsoleAuditLogService.cs +++ b/FormCraft/Forms/Security/ConsoleAuditLogService.cs @@ -61,12 +61,16 @@ private AuditLogEntry Redact(AuditLogEntry entry) { var excludedFields = _configuration?.ExcludedFields; if (excludedFields == null || excludedFields.Count == 0) + { return entry; + } var isFieldExcluded = entry.FieldName != null && excludedFields.Contains(entry.FieldName); var hasExcludedAdditionalData = entry.AdditionalData.Keys.Any(excludedFields.Contains); if (!isFieldExcluded && !hasExcludedAdditionalData) + { return entry; + } // Never mutate the caller's entry — log a redacted copy instead. return new AuditLogEntry diff --git a/FormCraft/Forms/Security/DefaultEncryptionService.cs b/FormCraft/Forms/Security/DefaultEncryptionService.cs index 313c1244..159255da 100644 --- a/FormCraft/Forms/Security/DefaultEncryptionService.cs +++ b/FormCraft/Forms/Security/DefaultEncryptionService.cs @@ -60,7 +60,9 @@ public DefaultEncryptionService(IConfiguration? configuration = null) public string? Encrypt(string? value) { if (string.IsNullOrEmpty(value)) + { return value; + } using var aes = Aes.Create(); aes.Key = _key; @@ -87,7 +89,9 @@ public DefaultEncryptionService(IConfiguration? configuration = null) public string? Decrypt(string? encryptedValue) { if (string.IsNullOrEmpty(encryptedValue)) + { return encryptedValue; + } try { @@ -121,12 +125,16 @@ private static byte[] ParseKey(string keyString) // Prefer a Base64-encoded 32-byte key. var base64Buffer = new byte[keyString.Length]; if (Convert.TryFromBase64String(keyString, base64Buffer, out var bytesWritten) && bytesWritten == KeySizeInBytes) + { return base64Buffer[..bytesWritten]; + } // Fall back to a raw string whose UTF-8 representation is exactly 32 bytes. var utf8Key = Encoding.UTF8.GetBytes(keyString); if (utf8Key.Length == KeySizeInBytes) + { return utf8Key; + } throw new InvalidOperationException( $"The configured '{KeyConfigurationPath}' is invalid. Provide a Base64-encoded {KeySizeInBytes}-byte key " + diff --git a/FormCraft/Forms/Security/EncryptedFieldHelper.cs b/FormCraft/Forms/Security/EncryptedFieldHelper.cs index ecf0c6df..0c449d1b 100644 --- a/FormCraft/Forms/Security/EncryptedFieldHelper.cs +++ b/FormCraft/Forms/Security/EncryptedFieldHelper.cs @@ -14,7 +14,9 @@ public static void EncryptFields(TModel model, IFormSecurity security, I where TModel : new() { if (model == null || security?.EncryptedFields == null || !security.EncryptedFields.Any()) + { return; + } foreach (var fieldName in security.EncryptedFields) { @@ -38,7 +40,9 @@ public static void DecryptFields(TModel model, IFormSecurity security, I where TModel : new() { if (model == null || security?.EncryptedFields == null || !security.EncryptedFields.Any()) + { return; + } foreach (var fieldName in security.EncryptedFields) { @@ -62,7 +66,9 @@ public static TModel CreateDecryptedCopy(TModel model, IFormSecurity sec where TModel : new() { if (model == null) + { return new TModel(); + } // Create a shallow copy var copy = (TModel)Activator.CreateInstance(typeof(TModel))!; diff --git a/FormCraft/Forms/Security/InMemoryRateLimitService.cs b/FormCraft/Forms/Security/InMemoryRateLimitService.cs index 9a24e988..b0b55af3 100644 --- a/FormCraft/Forms/Security/InMemoryRateLimitService.cs +++ b/FormCraft/Forms/Security/InMemoryRateLimitService.cs @@ -48,7 +48,9 @@ public Task CheckRateLimitAsync(string identifier, int maxAttem var oldestAttempt = attempts.Min(); var computed = oldestAttempt.Add(timeWindow).Subtract(now); if (computed > TimeSpan.Zero) + { retryAfter = computed; + } } return Task.FromResult(new RateLimitResult @@ -123,7 +125,9 @@ private void Cleanup(object? state) public void Dispose() { if (_disposed) + { return; + } _disposed = true; _cleanupTimer.Dispose(); diff --git a/FormCraft/Forms/Validation/DynamicFormValidator.cs b/FormCraft/Forms/Validation/DynamicFormValidator.cs index 0374967e..60443d5e 100644 --- a/FormCraft/Forms/Validation/DynamicFormValidator.cs +++ b/FormCraft/Forms/Validation/DynamicFormValidator.cs @@ -163,7 +163,9 @@ private async Task> ValidateCollectionFieldAsync(TModel model, ICol var validateMethod = validatorType.GetMethod("ValidateAsync"); if (validateMethod == null) + { return new List(); + } var task = (Task>)validateMethod.Invoke(validator, new object[] { model!, ServiceProvider })!; return await task; @@ -177,7 +179,9 @@ private async Task> ValidateCollectionItemsAsync(TMode var validateMethod = validatorType.GetMethod("ValidateItemsAsync"); if (validateMethod == null) + { return new List(); + } var task = (Task>)validateMethod.Invoke(validator, new object[] { model!, ServiceProvider })!; return await task; @@ -207,7 +211,9 @@ private async void HandleFieldChanged(object? sender, FieldChangedEventArgs e) // Find the field configuration for the changed field var fieldConfig = Configuration.Fields.FirstOrDefault(f => f.FieldName == e.FieldIdentifier.FieldName); if (fieldConfig == null) + { return; + } var model = (TModel)_editContext!.Model; var getter = fieldConfig.ValueExpression.Compile(); @@ -237,7 +243,9 @@ private async void HandleFieldChanged(object? sender, FieldChangedEventArgs e) private async Task ValidateCollectionItemFieldAsync(FieldIdentifier fieldIdentifier, System.Text.RegularExpressions.Match nestedMatch) { if (Configuration is not ICollectionFormConfiguration collectionConfig) + { return; + } var collectionFieldName = nestedMatch.Groups["collection"].Value; var itemIndex = int.Parse(nestedMatch.Groups["index"].Value); @@ -246,7 +254,9 @@ private async Task ValidateCollectionItemFieldAsync(FieldIdentifier fieldIdentif var collectionField = collectionConfig.CollectionFields .FirstOrDefault(f => f.FieldName == collectionFieldName); if (collectionField == null) + { return; + } var model = (TModel)_editContext!.Model; diff --git a/FormCraft/Forms/Validators/FluentValidationAdapter.cs b/FormCraft/Forms/Validators/FluentValidationAdapter.cs index 14be415c..243f69ef 100644 --- a/FormCraft/Forms/Validators/FluentValidationAdapter.cs +++ b/FormCraft/Forms/Validators/FluentValidationAdapter.cs @@ -1,6 +1,6 @@ +using System.Linq.Expressions; using FluentValidation; using Microsoft.Extensions.DependencyInjection; -using System.Linq.Expressions; namespace FormCraft; From bb357d746595467ff4494f0dc3842148987f0724 Mon Sep 17 00:00:00 2001 From: Philippe Matray Date: Thu, 13 Aug 2026 14:24:35 +0200 Subject: [PATCH 5/8] ci: enforce dotnet format in CI and correct the docs that denied it --- .claude/skills/repo-profile.md | 29 +++++++++++++++++++++-------- .github/workflows/ci.yml | 11 +++++++++++ CLAUDE.md | 28 ++++++++++++++++++++++++++++ README.md | 4 ++++ 4 files changed, 64 insertions(+), 8 deletions(-) diff --git a/.claude/skills/repo-profile.md b/.claude/skills/repo-profile.md index a49711ed..4bca6c79 100644 --- a/.claude/skills/repo-profile.md +++ b/.claude/skills/repo-profile.md @@ -96,11 +96,22 @@ in nondeterministic order, so a check that reads the first one accepts `Total: 68` as the whole suite. Confirm all three assemblies reported, or trust the process exit code for the full run (unpiped). Filter to iterate; run one of these before you claim done. -- **Format/lint apply:** `dotnet format FormCraft.sln` -- **Format/lint verify (the gate):** *CI runs no `dotnet format` check.* The enforcing gate is the - **build itself**: `Directory.Build.props` sets `true` - (with `NoWarn=CS1591;CS8620`), so any analyzer/compiler warning fails `dotnet build -c Release`. - `.editorconfig` severities are capped at `warning` and are IDE/`dotnet format` guidance only. +- **Format/lint apply:** `dotnet format FormCraft.sln` (or `whitespace` / `style` to scope it) +- **Format/lint verify (the gate):** `./build.sh Format` — a Nuke target wrapping + `dotnet format FormCraft.sln --verify-no-changes`, run by `ci.yml` **before** `Test` (#301). + There are now **two** independent gates: + 1. **The build** — `Directory.Build.props` sets `true` + (with `NoWarn=CS1591;CS8620`), so any compiler/package-analyzer warning fails + `dotnet build -c Release`. This does **not** cover `.editorconfig`. + 2. **The `Format` target** — the only thing that reads `.editorconfig`. `EnforceCodeStyleInBuild` + is deliberately unset, so `IDE*` analyzers never run during a build; before #301 that meant + nothing anywhere read those severities and 574 violations had accumulated across 201 files. +- ⚠️ **`dotnet format` corrupts multi-targeted files when *applying*.** `FormCraft` is + `net8.0;net10.0`, so a fix can be applied once per TFM and land as a literal + `<<<<<<< TODO: Unmerged change from project 'FormCraft(net10.0)'` conflict block **written into the + `.cs` file** (measured in #301 on `FieldRendererBase.cs`; the result does not compile). Verify mode + never writes, so CI is unaffected. After any *apply* run: + `grep -rl '<<<<<<< TODO' --include='*.cs' .` and hand-resolve before committing. - **Prerequisites / caveats:** - `global.json` pins SDK `10.0.302` with `rollForward: latestFeature`. - Multi-target `net8.0;net10.0` — a build error can be TFM-specific; read which TFM the error names. @@ -110,6 +121,8 @@ `Continuous`). `Test` is `DependsOn(Compile)` with `--no-build --no-restore`. ## CI gates (the exact commands CI fails on — satisfy these locally before ready/merge) +- `./build.cmd Format` — `.github/workflows/ci.yml`, run **before** `Test` since #301. Verifies + `dotnet format --verify-no-changes` is clean; fails on any diff. - `./build.cmd Test` — `.github/workflows/ci.yml` (Compile then Test, Release config) - `./build.cmd Pack` — `.github/workflows/continuous.yml` (`Pack` is `DependsOn(Test)`). Since #197 this workflow builds/tests/packs only: no `tags:` trigger, no key, and it invokes `Pack` rather @@ -118,9 +131,9 @@ *is* the `v3.1.0` commit) and would then hard-fail on `Publish`'s `Requires(NuGetApiKey)`. - **`pr-title-lint.yml`** — `amannn/action-semantic-pull-request` on `pull_request_target`. The PR title **must** be a Conventional Commit. Skipped on drafts, and re-evaluated on `ready_for_review`. -- Locally equivalent: `dotnet build -c Release` && `dotnet test -c Release`. -- No format/lint check, and **no branch protection** (`required_status_checks` is - `enforcement_level: off`) — CI is advisory at the API level, so read the run result yourself. +- Locally equivalent: `./build.sh Format` && `dotnet build -c Release` && `dotnet test -c Release`. +- **No branch protection** (`required_status_checks` is `enforcement_level: off`) — CI is advisory at + the API level, so read the run result yourself. - `release-please.yml` is not a PR gate: it runs on push to `dev` and on `workflow_dispatch`. ## Integration style diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77aa0e16..f6918b5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,17 @@ jobs: - uses: actions/setup-dotnet@v6 with: dotnet-version: '10.0.x' + # Runs BEFORE Test so a style slip reports in ~30s instead of behind a full build+test. + # + # This is the only thing that reads .editorconfig (#301). Its severities say `warning` and + # Directory.Build.props sets TreatWarningsAsErrors=true, which together look like enforcement + # and are not: IDE* analyzers need EnforceCodeStyleInBuild, which is deliberately NOT set — + # turning it on would make one missing brace break `dotnet build` mid-edit. Delete this step + # and the rules go back to being decorative, which is how 574 violations accumulated. + - run: ./build.cmd Format + env: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + - run: ./build.cmd Test env: DOTNET_CLI_TELEMETRY_OPTOUT: 1 diff --git a/CLAUDE.md b/CLAUDE.md index 766deb77..f8c51eec 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,6 +21,34 @@ dotnet build /p:TreatWarningsAsErrors=true ./pack-local.ps1 # Windows ``` +### Code style (`.editorconfig`) — enforced in CI, not in the build + +```bash +./build.sh Format # the CI gate: verify only, fails on any diff +dotnet format FormCraft.sln # apply the fixes +dotnet format FormCraft.sln whitespace # or scope it: whitespace only +dotnet format FormCraft.sln style # ...or the IDE* code-style rules only +``` + +- **`EnforceCodeStyleInBuild` is deliberately NOT set**, so `IDE*` analyzers do not run during + `dotnet build`. That is why `.editorconfig`'s `warning` severities never met + `TreatWarningsAsErrors` and 574 violations accumulated across 201 files before #301 — the two + settings *look* like enforcement together and are not. ⛔ Don't "fix" that by switching the + property on: with warnings-as-errors, one missing brace would break `dotnet build` mid-edit and + every incremental build would pay for the analyzer pass. The `Format` CI step is where regression + is caught. +- ⚠️ **`dotnet format` can corrupt multi-targeted files when it *applies* fixes.** `FormCraft` is + `net8.0;net10.0`, and a fix applied once per TFM can land as a literal + `<<<<<<< TODO: Unmerged change from project 'FormCraft(net10.0)'` conflict block **written into + the `.cs` file** — code that does not compile (measured in #301 on `FieldRendererBase.cs`). + Verify mode never writes, so CI is safe. After any apply run: + `grep -rl '<<<<<<< TODO' --include='*.cs' .` and hand-resolve before committing. +- **`IDE0032` now rewrites a private backing field into C# 14's `field` keyword.** Value-identical, + but it silently deletes the XML docs that lived on the field — which on + `MudBlazorFileUploadComponentBase.RequiredDescriptionId` were documenting a real correctness + invariant (#262: the hint id must be unique per rendered instance). Read those hunks; move the + explanation onto the property rather than losing it. + ### Running Tests ```bash # Run all tests (600+ unit tests across 2 test projects) diff --git a/README.md b/README.md index 31c469c6..c2bdba23 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,10 @@ Experience FormCraft in action! Visit our [interactive demo](https://phmatray.gi ## 🎉 Unreleased +- **`.editorconfig` is now actually enforced, and the tree matches it (#301).** The file declared its code-style rules at `warning` severity and `Directory.Build.props` sets `TreatWarningsAsErrors=true` — which together *look* like enforcement and were not: `IDE*` analyzers only run at build time when `EnforceCodeStyleInBuild` is set, and it wasn't, while no CI job ran `dotnet format` either. Nothing anywhere read those severities, so 574 violations had accumulated across 201 files. There is now a Nuke `Format` target (`./build.sh Format`) wrapping `dotnet format --verify-no-changes`, wired into `ci.yml` ahead of the test run, and a one-off pass has cleared the backlog — split into a whitespace commit and a code-style commit so the shape-changing fixes stayed reviewable. Enforcement lives in CI rather than the build on purpose: with warnings-as-errors, build-time style analysis would break `dotnet build` mid-edit over a missing brace. + + ⚠️ **`dotnet format` can corrupt multi-targeted files when it applies fixes** — `FormCraft` is `net8.0;net10.0`, and a fix applied once per target framework can land as a literal `<<<<<<< TODO: Unmerged change` conflict block written into the `.cs` file, which then doesn't compile. Verify mode never writes, so the CI gate is unaffected. After running the formatter to apply fixes, `grep -rl '<<<<<<< TODO' --include='*.cs' .` before committing. + - **The UI-agnostic adapter machinery moved into `FormCraft` core, so both adapters share one implementation (#279).** Building a second adapter revealed the core/adapter boundary was drawn in the wrong place: `DynamicFormValidator` — 242 lines referencing nothing outside `Microsoft.AspNetCore.Components` — the native-required rule, and `.WithNativeRequired(...)` all lived in `FormCraft.ForMudBlazor`, so `FormCraft.ForFluentUI` had to copy them. That is the failure mode #146, #177, #184, #190 and #203 each arrived as: one behaviour implemented twice, drifting apart, reported one bug at a time. All three now live in core, and the Fluent copy is gone. **⚠️ Breaking for direct references by namespace.** `DynamicFormValidator` is now `FormCraft.DynamicFormValidator` rather than `FormCraft.ForMudBlazor.DynamicFormValidator`. **No `[Obsolete]` shim was left behind**, deliberately: both packages surface these names in namespace `FormCraft`, so a forwarder visible to the same `using FormCraft;` that reaches the new member would make every existing call site *ambiguous* (`CS0121`/`CS0104`) — breaking the very callers a shim exists to protect — while hiding it in a namespace nobody imports would protect nobody. `.WithNativeRequired(...)` keeps working untouched for that same reason: the namespace never changed. Only code naming `FormCraft.ForMudBlazor.DynamicFormValidator` explicitly, or a pre-compiled assembly bound to the old location, needs a change. From 4a903d1342df11f1f0bf54adf9742fd69b9977c3 Mon Sep 17 00:00:00 2001 From: Philippe Matray Date: Thu, 13 Aug 2026 14:30:25 +0200 Subject: [PATCH 6/8] style: format the code merged from dev so the new gate passes --- CLAUDE.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 057251b7..22c909b5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -43,6 +43,10 @@ dotnet format FormCraft.sln style # ...or the IDE* code-style rules only the `.cs` file** — code that does not compile (measured in #301 on `FieldRendererBase.cs`). Verify mode never writes, so CI is safe. After any apply run: `grep -rl '<<<<<<< TODO' --include='*.cs' .` and hand-resolve before committing. +- ⚠️ **One `dotnet format` run does not always reach a fixpoint.** Measured in #301: after formatting + a `field`-keyword property, verify still reported three `WHITESPACE` diagnostics on it, and a + second run cleared them. If `./build.sh Format` fails right after you formatted, run the formatter + again before assuming the gate is wrong. - **`IDE0032` now rewrites a private backing field into C# 14's `field` keyword.** Value-identical, but it silently deletes the XML docs that lived on the field — which on `MudBlazorFileUploadComponentBase.RequiredDescriptionId` were documenting a real correctness From e2781da5ba37bdafcb033abe1fcadf2028bfeac7 Mon Sep 17 00:00:00 2001 From: Philippe Matray Date: Thu, 13 Aug 2026 14:43:14 +0200 Subject: [PATCH 7/8] style: format the Fluent UI code merged from dev so the gate passes --- .../FormContainer/FormCraftComponent.razor.cs | 2 +- .../FluentUIFileUploadComponentBase.cs | 17 +++---- .../FluentUIFileUploadFieldComponent.razor.cs | 4 +- ...uentUIMultipleFileUploadComponent.razor.cs | 11 ++--- .../FluentUILookupFieldComponent.razor.cs | 13 +++--- .../FluentUILovFieldComponent.razor.cs | 44 +++++++++---------- 6 files changed, 37 insertions(+), 54 deletions(-) diff --git a/FormCraft.ForFluentUI/Features/FormContainer/FormCraftComponent.razor.cs b/FormCraft.ForFluentUI/Features/FormContainer/FormCraftComponent.razor.cs index 93f15ee1..59e80014 100644 --- a/FormCraft.ForFluentUI/Features/FormContainer/FormCraftComponent.razor.cs +++ b/FormCraft.ForFluentUI/Features/FormContainer/FormCraftComponent.razor.cs @@ -1,8 +1,8 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Forms; -using Microsoft.FluentUI.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.FluentUI.AspNetCore.Components; namespace FormCraft.ForFluentUI; diff --git a/FormCraft.ForFluentUI/Fields/FileUploadField/FluentUIFileUploadComponentBase.cs b/FormCraft.ForFluentUI/Fields/FileUploadField/FluentUIFileUploadComponentBase.cs index bfa28363..10f7ecd5 100644 --- a/FormCraft.ForFluentUI/Fields/FileUploadField/FluentUIFileUploadComponentBase.cs +++ b/FormCraft.ForFluentUI/Fields/FileUploadField/FluentUIFileUploadComponentBase.cs @@ -23,16 +23,6 @@ namespace FormCraft.ForFluentUI; /// public abstract class FluentUIFileUploadComponentBase : FluentUIFieldComponentBase { - /// - /// Per-render-instance discriminator for . - /// - /// - /// The field name alone is not unique in a document: item fields render through these very - /// components, so a collection emits one hint per row; two forms over one model on a page - /// collide the same way; and two nested fields can share a member name. Duplicate ids are - /// invalid HTML and, worse, point every later button at the first one's description. - /// - private readonly string _instanceDiscriminator = Guid.NewGuid().ToString("N")[..8]; /// /// Whether this field is marked required, by the same rule as every other field type: an @@ -51,8 +41,11 @@ public abstract class FluentUIFileUploadComponentBase : FluentUI protected string FileInputAccessibleName => HasLabel ? Label! : "File upload"; /// The id of the requirement hint, unique per rendered field instance. - protected string RequiredDescriptionId => - $"formcraft-{Context.Field.FieldName}-required-{_instanceDiscriminator}"; + protected string RequiredDescriptionId + { + get => + $"formcraft-{Context.Field.FieldName}-required-{field}"; + } = Guid.NewGuid().ToString("N")[..8]; /// /// The value for the browse control's aria-describedby: the hint's id when the field is diff --git a/FormCraft.ForFluentUI/Fields/FileUploadField/FluentUIFileUploadFieldComponent.razor.cs b/FormCraft.ForFluentUI/Fields/FileUploadField/FluentUIFileUploadFieldComponent.razor.cs index ef9cb750..f59abe44 100644 --- a/FormCraft.ForFluentUI/Fields/FileUploadField/FluentUIFileUploadFieldComponent.razor.cs +++ b/FormCraft.ForFluentUI/Fields/FileUploadField/FluentUIFileUploadFieldComponent.razor.cs @@ -8,13 +8,11 @@ namespace FormCraft.ForFluentUI; /// The form's model type. public partial class FluentUIFileUploadFieldComponent { - private readonly string _browseButtonId = $"formcraft-upload-{Guid.NewGuid():N}"; - /// /// The id the hidden file input anchors to, so clicking the visible button opens the picker. /// Unique per instance for the same reason the hint id is. /// - private string BrowseButtonId => _browseButtonId; + private string BrowseButtonId { get; } = $"formcraft-upload-{Guid.NewGuid():N}"; /// The chosen file's name, shown back to the user once one is picked. private string? SelectedFileName => CurrentValue?.Name; diff --git a/FormCraft.ForFluentUI/Fields/FileUploadField/FluentUIMultipleFileUploadComponent.razor.cs b/FormCraft.ForFluentUI/Fields/FileUploadField/FluentUIMultipleFileUploadComponent.razor.cs index da35fa7e..2a3065e0 100644 --- a/FormCraft.ForFluentUI/Fields/FileUploadField/FluentUIMultipleFileUploadComponent.razor.cs +++ b/FormCraft.ForFluentUI/Fields/FileUploadField/FluentUIMultipleFileUploadComponent.razor.cs @@ -14,13 +14,10 @@ namespace FormCraft.ForFluentUI; /// public partial class FluentUIMultipleFileUploadComponent { - private readonly string _browseButtonId = $"formcraft-upload-{Guid.NewGuid():N}"; - private string? _tooManyFilesError; - /// /// The id the hidden file input anchors to, so clicking the visible button opens the picker. /// - private string BrowseButtonId => _browseButtonId; + private string BrowseButtonId { get; } = $"formcraft-upload-{Guid.NewGuid():N}"; /// The chosen files, shown back to the user. private IReadOnlyList SelectedFiles => CurrentValue ?? []; @@ -28,7 +25,7 @@ public partial class FluentUIMultipleFileUploadComponent /// /// The message shown when the user picked more files than the field allows, or null. /// - private string? TooManyFilesError => _tooManyFilesError; + private string? TooManyFilesError { get; set; } private async Task HandleFilesChangedAsync(InputFileChangeEventArgs args) { @@ -38,11 +35,11 @@ private async Task HandleFilesChangedAsync(InputFileChangeEventArgs args) // Checked up front so the limit reports as a message instead. if (args.FileCount > MaximumFileCount) { - _tooManyFilesError = $"Select at most {MaximumFileCount} file{(MaximumFileCount == 1 ? "" : "s")}."; + TooManyFilesError = $"Select at most {MaximumFileCount} file{(MaximumFileCount == 1 ? "" : "s")}."; return; } - _tooManyFilesError = null; + TooManyFilesError = null; IReadOnlyList files = args.GetMultipleFiles(MaximumFileCount); SetValueWithoutNotification(files); diff --git a/FormCraft.ForFluentUI/Fields/LookupField/FluentUILookupFieldComponent.razor.cs b/FormCraft.ForFluentUI/Fields/LookupField/FluentUILookupFieldComponent.razor.cs index 0f29884f..584e611c 100644 --- a/FormCraft.ForFluentUI/Fields/LookupField/FluentUILookupFieldComponent.razor.cs +++ b/FormCraft.ForFluentUI/Fields/LookupField/FluentUILookupFieldComponent.razor.cs @@ -1,5 +1,5 @@ -using Microsoft.AspNetCore.Components.Web; using System.Collections; +using Microsoft.AspNetCore.Components.Web; namespace FormCraft.ForFluentUI; @@ -34,10 +34,9 @@ public partial class FluentUILookupFieldComponent private bool _isOpen; private bool _isLoading; private string _searchText = string.Empty; - private string _displayText = string.Empty; /// The text shown in the read-only display. - private string DisplayText => _displayText; + private string DisplayText { get; set; } = string.Empty; /// /// The grid's columns. Falls back to a single display-text column when the field configured @@ -54,7 +53,7 @@ protected override void OnInitialized() if (CurrentValue is not null) { - _displayText = CurrentValue.ToString() ?? string.Empty; + DisplayText = CurrentValue.ToString() ?? string.Empty; } } @@ -142,9 +141,7 @@ private async Task LoadRowsAsync() private async Task SelectRowAsync(object row) { - var valueSelector = GetAttribute("LookupValueSelector") as Delegate; - var displaySelector = GetAttribute("LookupDisplaySelector") as Delegate; - if (valueSelector is null || displaySelector is null) + if (GetAttribute("LookupValueSelector") is not Delegate valueSelector || GetAttribute("LookupDisplaySelector") is not Delegate displaySelector) { return; } @@ -154,7 +151,7 @@ private async Task SelectRowAsync(object row) return; } - _displayText = displaySelector.DynamicInvoke(row)?.ToString() ?? string.Empty; + DisplayText = displaySelector.DynamicInvoke(row)?.ToString() ?? string.Empty; _isOpen = false; // The multi-field mapping hook runs before the value change is announced, so a handler diff --git a/FormCraft.ForFluentUI/Fields/LovField/FluentUILovFieldComponent.razor.cs b/FormCraft.ForFluentUI/Fields/LovField/FluentUILovFieldComponent.razor.cs index f50cc8ad..78449fd9 100644 --- a/FormCraft.ForFluentUI/Fields/LovField/FluentUILovFieldComponent.razor.cs +++ b/FormCraft.ForFluentUI/Fields/LovField/FluentUILovFieldComponent.razor.cs @@ -1,5 +1,5 @@ -using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; using Microsoft.Extensions.DependencyInjection; namespace FormCraft.ForFluentUI; @@ -21,46 +21,44 @@ public partial class FluentUILovFieldComponent { private readonly List _rows = []; private readonly List _selectedItems = []; - private ILovConfiguration? _lovConfig; private int _loadTicket; private bool _isOpen; private bool _isLoading; private string _searchText = string.Empty; - private string _displayText = string.Empty; [Inject] private IServiceProvider ServiceProvider { get; set; } = null!; /// The resolved LOV configuration. - private ILovConfiguration? LovConfig => _lovConfig; + private ILovConfiguration? LovConfig { get; set; } /// The text shown in the read-only display. - private string DisplayText => _displayText; + private string DisplayText { get; set; } = string.Empty; /// Whether the configuration asked for multiple selection. - private bool IsMultiSelect => _lovConfig?.SelectionMode == LovSelectionMode.Multiple; + private bool IsMultiSelect => LovConfig?.SelectionMode == LovSelectionMode.Multiple; /// Whether the picker offers a search box. - private bool SearchEnabled => _lovConfig?.SearchOptions.Enabled ?? true; + private bool SearchEnabled => LovConfig?.SearchOptions.Enabled ?? true; /// The search box's placeholder. - private string SearchPlaceholder => _lovConfig?.SearchOptions.Placeholder ?? "Search..."; + private string SearchPlaceholder => LovConfig?.SearchOptions.Placeholder ?? "Search..."; /// The grid's columns. - private IReadOnlyList> Columns => _lovConfig?.Columns ?? []; + private IReadOnlyList> Columns => LovConfig?.Columns ?? []; /// protected override void OnInitialized() { base.OnInitialized(); - _lovConfig = GetAttribute>("LovConfiguration") + LovConfig = GetAttribute>("LovConfiguration") ?? throw new InvalidOperationException( "LovConfiguration is required. Use the .AsLov() extension method to configure the field."); if (CurrentValue is not null) { - _displayText = CurrentValue.ToString() ?? string.Empty; + DisplayText = CurrentValue.ToString() ?? string.Empty; } } @@ -93,7 +91,7 @@ private async Task HandleRowKeyDownAsync(KeyboardEventArgs args, TItem row) private async Task LoadRowsAsync() { - if (_lovConfig is null) + if (LovConfig is null) { return; } @@ -131,12 +129,12 @@ private async Task LoadRowsAsync() /// private async Task> ResolveDataAsync(LovQuery query) { - if (_lovConfig!.DataProvider is { } provider) + if (LovConfig!.DataProvider is { } provider) { return await provider(query, CancellationToken.None); } - if (_lovConfig.DataProviderServiceType is { } serviceType && + if (LovConfig.DataProviderServiceType is { } serviceType && ServiceProvider.GetService(serviceType) is ILovDataProvider service) { return await service.GetItemsAsync(query, CancellationToken.None); @@ -147,7 +145,7 @@ private async Task> ResolveDataAsync(LovQuery query) private async Task SelectRowAsync(TItem row) { - if (_lovConfig is null) + if (LovConfig is null) { return; } @@ -159,13 +157,13 @@ private async Task SelectRowAsync(TItem row) _selectedItems.Add(row); } - _displayText = string.Join(", ", _selectedItems.Select(_lovConfig.DisplaySelector)); + DisplayText = string.Join(", ", _selectedItems.Select(LovConfig.DisplaySelector)); } else { _selectedItems.Clear(); _selectedItems.Add(row); - _displayText = _lovConfig.DisplaySelector(row); + DisplayText = LovConfig.DisplaySelector(row); _isOpen = false; } @@ -175,13 +173,13 @@ private async Task SelectRowAsync(TItem row) private async Task RemoveSelectedAsync(TItem row) { - if (_lovConfig is null) + if (LovConfig is null) { return; } _selectedItems.Remove(row); - _displayText = string.Join(", ", _selectedItems.Select(_lovConfig.DisplaySelector)); + DisplayText = string.Join(", ", _selectedItems.Select(LovConfig.DisplaySelector)); await PublishSelectionAsync(); } @@ -211,12 +209,12 @@ private async Task PublishSelectionAsync() private TValue? ResolveSelectionValue() { - if (_lovConfig is null || _selectedItems.Count == 0) + if (LovConfig is null || _selectedItems.Count == 0) { return default; } - var values = _selectedItems.Select(_lovConfig.ValueSelector).ToList(); + var values = _selectedItems.Select(LovConfig.ValueSelector).ToList(); if (!IsMultiSelect) { @@ -237,12 +235,12 @@ private async Task PublishSelectionAsync() /// private async Task ApplyFieldMappingsAsync(TItem row) { - if (_lovConfig is null || Context.Model is null) + if (LovConfig is null || Context.Model is null) { return; } - foreach (var mapping in _lovConfig.FieldMappings) + foreach (var mapping in LovConfig.FieldMappings) { if (mapping is IAsyncLovFieldMapping asyncMapping) { From 94fe23e9050a8658d8ea1cc2641ea8cd0da0bda3 Mon Sep 17 00:00:00 2001 From: Philippe Matray Date: Thu, 13 Aug 2026 14:59:14 +0200 Subject: [PATCH 8/8] style: satisfy the format gate on code merged from dev --- .../Components/Shared/DemoComponentBase.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/FormCraft.DemoBlazorApp/Components/Shared/DemoComponentBase.cs b/FormCraft.DemoBlazorApp/Components/Shared/DemoComponentBase.cs index f0bb090b..e6604e77 100644 --- a/FormCraft.DemoBlazorApp/Components/Shared/DemoComponentBase.cs +++ b/FormCraft.DemoBlazorApp/Components/Shared/DemoComponentBase.cs @@ -28,14 +28,13 @@ namespace FormCraft.DemoBlazorApp.Components.Shared; public abstract class DemoComponentBase : ComponentBase, IDisposable { private readonly CancellationTokenSource _lifetimeCts = new(); - private bool _disposed; /// /// Whether the component has been torn down. Check this before calling /// from anything that can outlive the component — a /// continuation after any await, or a timer callback. /// - protected bool IsDisposed => _disposed; + protected bool IsDisposed { get; private set; } /// /// Waits and reports whether the component is still around. @@ -47,7 +46,7 @@ public abstract class DemoComponentBase : ComponentBase, IDisposable /// /// /// Two hazards, both live findings on #285: the token covers the wait itself, and the - /// re-check afterwards covers the gap where the timer has already fired and + /// re-check afterwards covers the gap where the timer has already fired and /// the continuation is queued on the dispatcher — cancellation cannot help once that has happened. /// /// @@ -64,7 +63,7 @@ public abstract class DemoComponentBase : ComponentBase, IDisposable /// protected async Task DelayAsync(int milliseconds) { - if (_disposed) + if (IsDisposed) { return false; } @@ -78,7 +77,7 @@ protected async Task DelayAsync(int milliseconds) return false; } - return !_disposed; + return !IsDisposed; } /// @@ -90,12 +89,12 @@ protected async Task DelayAsync(int milliseconds) /// public virtual void Dispose() { - if (_disposed) + if (IsDisposed) { return; } - _disposed = true; + IsDisposed = true; _lifetimeCts.Cancel(); _lifetimeCts.Dispose(); GC.SuppressFinalize(this);