Skip to content

v0.18 Release#500

Merged
csharpfritz merged 16 commits intomainfrom
dev
Mar 23, 2026
Merged

v0.18 Release#500
csharpfritz merged 16 commits intomainfrom
dev

Conversation

@csharpfritz
Copy link
Copy Markdown
Collaborator

v0.18 Release

Includes all work merged to dev since v0.14:

  • BWFC013/014 analyzers, architecture guide, CI docs
  • Roslyn Analyzer documentation and samples
  • Medium effort fidelity divergences docs, ID rendering tests
  • Quick wins - sample pages, docs, and integration tests
  • Docs and sample pages for 6 sub-100% components
  • Tier 2: Property/event gaps + health detection fixes
  • Health scoring accuracy improvements + CI snapshot generation
  • Audit gap fixes - ID rendering, RouteData bug, style docs
  • 5 remaining AJAX Control Toolkit extenders
  • ASHX/AXD middleware handling + deprecation guidance docs
  • Cross-platform line ending fix for code fix providers

csharpfritz and others added 16 commits March 18, 2026 22:47
* feat: Add ASHX and AXD URL handling to middleware (#423)

- Add EnableAshxHandling and EnableAxdHandling options (default: true)
- Add AshxRedirectMappings dictionary for custom .ashx redirects
- AshxHandlerMiddleware: 410 Gone default, 301 redirect with mapping
- AxdHandlerMiddleware: 404 for most .axd, 410 Gone for ChartImg.axd
- Existing .aspx rewriting behavior unchanged

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: Add deprecation guidance for Web Forms patterns (#438)

Document migration guidance for runat=server, ViewState, UpdatePanel,
Page_Load/IsPostBack, ScriptManager, and 3 additional patterns.
887-line guide with before/after code examples.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: Add 46 middleware tests for ASHX/AXD handling (#423)

TestServer-based tests covering .ashx→410, .axd→404, ChartImg→410,
custom redirect mappings, options toggling, .aspx regression guards,
and edge cases (mixed case, query strings, substrings).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: Add TextBoxWatermarkExtender

Implements placeholder text for TextBox controls, matching the original
Ajax Control Toolkit TextBoxWatermarkExtender behavior.

- WatermarkText property for placeholder text
- WatermarkCssClass for styling the watermark state
- JS behavior handles focus/blur/input events

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: Add DragPanelExtender

Makes panels draggable by a handle element. Matches the original
Ajax Control Toolkit DragPanelExtender behavior.

- DragHandleID property to specify the drag handle element
- Falls back to entire target if no handle specified
- Handles edge cases (text selection, left-click only)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: Add ResizableControlExtender

Allows users to resize elements by dragging a handle. Matches the original
Ajax Control Toolkit ResizableControlExtender behavior.

- HandleCssClass for styling the resize handle
- ResizableCssClass applied while actively resizing
- Min/Max width and height constraints
- Creates resize handle if not found

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add DropShadowExtender and AlwaysVisibleControlExtender

- DropShadowExtender: CSS box-shadow with opacity, width, rounded corners, position tracking
- AlwaysVisibleControlExtender: Fixed positioning with 9 anchor points, offsets, animation support
- New enums: HorizontalSide, VerticalSide for positioning

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add RoundedCornersExtender and UpdatePanelAnimationExtender

- RoundedCornersExtender: Selective border-radius with BoxCorners enum, optional background color
- UpdatePanelAnimationExtender: MutationObserver-based update detection with CSS class and fade animations
- New enum: BoxCorners (flags for corner selection)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add PasswordStrength and MaskedEditExtender

- PasswordStrength: Real-time password quality indicator with text/bar modes, 5 strength levels
- MaskedEditExtender: Input masking for phone/date/SSN with configurable mask patterns
- New enums: DisplayPosition, StrengthIndicatorType, MaskType, InputDirection, AcceptNegative, DisplayMoney

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add ValidatorCalloutExtender and HoverMenuExtender

- ValidatorCalloutExtender: Validation error callout bubbles with positioning, highlight CSS, icons
- HoverMenuExtender: Hover-triggered popup menus with delays and positioning
- New enum: PopupPosition (TopLeft, TopRight, BottomLeft, BottomRight)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add SlideShowExtender

- SlideShowExtender: Image carousel with auto-play, navigation controls, slide titles/descriptions
- Client-side slide management via Slides parameter or data-slides attribute
- Play/pause, next/previous controls with configurable interval

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add ListSearchExtender and BalloonPopupExtender

- ListSearchExtender: Type-to-filter for ListBox/DropDownList with Contains/StartsWith matching
- BalloonPopupExtender: Styled tooltip balloons with pointer arrows, multiple trigger modes
- New enums: PromptPosition, QueryPattern, BalloonStyle, BalloonSize, BalloonPosition

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add docs, tests, and migration guide for new ACT extenders

Documentation (12 new files in docs/AjaxToolkit/):
- TextBoxWatermark, DragPanel, ResizableControl, DropShadow
- AlwaysVisibleControl, RoundedCorners, UpdatePanelAnimation
- PasswordStrength, ValidatorCallout, SlideShow, ListSearch, BalloonPopup
- Updated index.md with all new components

bUnit Tests (6 new test files, 104 tests):
- TextBoxWatermarkExtender, DropShadowExtender, PasswordStrength
- ValidatorCalloutExtender, ListSearchExtender, BalloonPopupExtender

Migration Toolkit:
- Updated AJAX-TOOLKIT.md with migration patterns for all 12 extenders

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: Set correct ACT defaults for HoverMenuExtender (HoverDelay=300, PopDelay=100)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(.squad): log ajax nav session and dashboard deferral decision

- Log AJAX nav fix session (alphabetize items, collapsed state on desktop)
- Document decision to defer AJAX Control Toolkit from health dashboard
- Route Jubilee agent for sample nav updates
- Merge inbox decision to decisions.md

* fix(samples): alphabetize AJAX nav items and start collapsed

- Sort GetByCategory() results alphabetically by name
- Exclude AJAX category from desktop auto-expansion

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(.squad): Jubilee history + ajax nav decision

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(tests): scope DetailsView pager locator to main content

The page-wide locator a:has-text('2') was matching collapsed nav
sidebar links after AJAX nav alphabetization. Scope to .main-content
to only match pager links within the component demo area.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: strip [RouteData] from method params instead of replacing with [Parameter]

The [RouteData] → [Parameter] conversion placed [Parameter] on method
parameters, but ParameterAttribute targets properties only (CS0592).
This caused build failures in ProductDetails.razor.cs and
ProductList.razor.cs (Run 15 regression).

Fix: strip [RouteData] entirely and leave a /* TODO */ block comment
directing Layer 2 to promote the value to a [Parameter] property.
Block comments avoid absorbing the closing ) of method signatures.

All 15 L1 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add ID rendering to 5 data controls

Add ClientID-based id attribute rendering to GridView, DropDownList,
FormView, DataList, and DataGrid following the established pattern
from Button/TextBox/Label/Panel/CheckBox.

DetailsView and HiddenField already had ID rendering.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: add Styling Components utility documentation

Create comprehensive guide for the 66 style sub-components covering
cascading parameter patterns, Web Forms to Blazor migration examples,
and complete inventory organized by parent control.

Added to mkdocs.yml under Utility Features navigation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(ai-team): Merge audit gap fixes — script stabilization, data control ID rendering, style docs

Session: 2026-03-18T15-48-15Z-audit-gap-fixes
Requested by: Team orchestration

Changes:
- Bishop: Fixed RouteData→Parameter conversion in bwfc-migrate.ps1 (CS0592, 15/15 L1 tests pass)
- Cyclops: Extended ID rendering to data controls (GridView, DropDownList, FormView, DataList, DataGrid)
- Beast: Created StylingComponents.md documentation (all 66 style sub-components)
- Merged 5 decisions from inbox; deleted inbox files
- Appended cross-agent team updates to Cyclops and Beast history

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(ai-team): Merge Forge's ID rendering approval decision

Appended Forge's decision to decisions.md (ID rendering pattern approval for Bishop and Cyclops work).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add 23 quick-win missing properties across 15 components

Batch implementation of properties identified by gap analysis:

- CausesValidation + ValidationGroup on 5 list controls
  (DropDownList, CheckBoxList, RadioButtonList, ListBox, BulletedList)
- Calendar.TodaysDate, HyperLink.ImageUrl
- DataPager: PagedControlID, QueryStringField
- DataList: DataKeyField, EditItemIndex
- Menu: ScrollDownText, ScrollUpText, IncludeStyleBlock
- TreeView.NodeWrap
- Validator stubs: EnableClientScript, ShowMessageBox, ControlToCompare
- ScriptManager.ScriptPath, SiteMapPath.SkipLinkText

Build passes clean (0 errors).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: Chart Phase 1 — Web Forms property compatibility via Pattern B+

Add 10 Web Forms Chart parameters with migration-compatible mappings:
- 5 new enums: AntiAliasingStyles, GradientStyle, ChartHatchStyle,
  ImageStorageMode, TextAntiAliasingQuality
- 8 [Parameter] properties: AntiAliasing, BackGradientStyle,
  BackHatchStyle, BackSecondaryColor, BorderlineDashStyle,
  ImageLocation, ImageStorageMode, TextAntiAliasingQuality
- 2 EventCallback parameters: CustomizeLegend, CustomizeMapAreas
- CSS rendering: BackGradientStyle maps to linear/radial-gradient,
  BorderlineDashStyle maps to border-style on container div
- Server-only properties marked [Obsolete] with migration guidance

Migrated Chart markup now compiles unchanged. Coverage: 25% -> 100%
markup acceptance with 50% real visual fidelity.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add 135 bUnit tests for new properties, Chart Phase 1, and ID rendering

- 40 tests for quick-win properties across 15 components
  (CausesValidation/ValidationGroup, TodaysDate, ImageUrl, etc.)
- 22 tests for Chart Phase 1 params and CSS gradient/border rendering
- 10 tests for ID rendering on GridView, DropDownList, FormView,
  DataList, DataGrid
- Total test suite: 2373 tests, 0 failures

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: update 8 sample pages with new property demonstrations

- Chart/Styling: BackGradientStyle, BorderlineDashStyle, AntiAliasing demos
- HyperLink: ImageUrl example
- Calendar: TodaysDate override demo
- Menu: ScrollDownText/ScrollUpText with Unicode arrows
- TreeView: NodeWrap with long wrapping text
- ValidationSummary: ShowMessageBox migration compatibility
- DataPager: PagedControlID and QueryStringField section
- DataList: DataKeyField and EditItemIndex demo

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add 16 Playwright integration tests for sample pages

- 15 smoke tests covering uncovered Ajax Toolkit sample pages
- 1 Chart Styling render test verifying canvas elements and CSS
- Every sample page now has at least one integration test

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: add Chart property mapping strategy proposal

Comprehensive analysis of Web Forms Chart vs Chart.js architecture
with 4 implementation options per property, plugin analysis, and
recommended Pattern B+ approach. Covers migration-first vs
fidelity-first tradeoffs with full property mapping table.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: implement 10 high-impact gap items across 5 components

Batch A - Validators:
- BaseValidator: EnableClientScript (stub), AssociatedControlID (stub)
  Covers all 5 validators via inheritance

Batch B - FormView:
- AllowPaging (wired to pager visibility)
- CellPadding, CellSpacing, GridLines (table rendering, GridView pattern)

Batch C - Login events:
- ChangePassword: OnSendingMail, OnSendMailError
- CreateUserWizard: OnSendingMail, OnSendMailError
  (copied from PasswordRecovery pattern)

Batch D - GridView + Menu:
- GridView: AutoGenerateDeleteButton (wired to command column)
- Menu: DynamicHorizontalOffset, DynamicVerticalOffset

Build passes clean (0 errors).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add 29 bUnit tests for next batch gap items

- BaseValidator EnableClientScript/AssociatedControlID on RequiredFieldValidator + CompareValidator
- FormView AllowPaging, CellPadding, CellSpacing, GridLines
- GridView AutoGenerateDeleteButton (including rendered delete link verification)
- ChangePassword + CreateUserWizard OnSendingMail/OnSendMailError events
- Menu DynamicHorizontalOffset/DynamicVerticalOffset

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Updated the description to clarify the purpose of the library.
…ide (#481)

The [HandlerRoute] attribute and MapBlazorWebFormsHandlers() were eliminated
during the design pivot to Minimal API registration. The actual API uses
MapHandler<T>("/path") in Program.cs, but the documentation still referenced
the old attribute-based pattern throughout.

Changes:
- Remove all [HandlerRoute(...)] decorations from code examples
- Replace MapBlazorWebFormsHandlers() with explicit MapHandler<T>() calls
- Update Quick Start checklist step 5 to reference Program.cs registration
- Add Program.cs registration snippets to all handler examples
- Update troubleshooting table and summary section

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… 100%) (#483)

* Tier 2: Property/event gaps + health detection fixes

Health detection:
- Add TypeAliases mapping in ComponentHealthService for components
  whose class name differs from the tracked name (AspNetValidationSummary)
- Fix reference baselines: FileUpload read-only props, TreeView DataSourceID
  on stop type, CustomValidator ServerValidate as Func not EventCallback

Component property/event additions:
- TreeView: 9 new properties (image URLs, tooltips, config stubs)
- BulletedList: 4 properties + 3 events (Click alias, SelectedIndexChanged,
  TextChanged, AutoPostBack, SelectedIndex, SelectedValue, Text)
- SiteMapPath: 2 events (ItemCreated, ItemDataBound) + SiteMapNodeItemEventArgs
- CustomValidator: 2 properties (ClientValidationFunction, IsValid)

Fix existing AutoGenerateDataBindings="False"  "false" in sample + test

Health: 6 components raised to 100% (44  50/59)
Tests: 2,470 pass (22 new)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add docs, samples, and integration tests for Tier 2 features

New sample pages:
- TreeView/ImageAndConfig: CollapseImageUrl, ExpandImageUrl, tooltips,
  MaxDataBindDepth, AutoGenerateDataBindings, EnableClientScript
- BulletedList/Selection: Click alias, SelectedIndex, SelectedValue,
  AutoPostBack, Text, SelectedIndexChanged, TextChanged
- SiteMapPath/Events: ItemCreated and ItemDataBound events

Updated sample:
- CustomValidator: ClientValidationFunction and IsValid properties

Documentation updates:
- TreeView.md: Image Customization and Data Binding Config references
- BulletedList.md: Selection properties, Click alias, events
- SiteMapPath.md: Events section, removed ItemDataBound from NOT Supported
- CustomValidator.md: Full documentation (was _TODO_)

Integration tests:
- Added 3 new InlineData entries for new sample pages

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix navigation for new sample sub-pages

Add SubPages entries to ComponentCatalog.cs so the sample app
navigation links to the new pages:
- BulletedList: added "Selection" sub-page
- SiteMapPath: added "Events" sub-page
- TreeView: added "ImageAndConfig" sub-page (alphabetical order)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: BulletedList Selection demo  separate interactive vs migration stubs

- Click alias demo remains interactive with clear CTA
- New OnClick+SelectedIndex tracking demo shows functional selection
- Migration stub section clearly labeled as markup compatibility only
- Removed misleading SelectedIndexChanged/TextChanged interactive demos
- Updated docs to clarify stubs are accepted but not fired

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: wire up SiteMapPath ItemCreated/ItemDataBound events

- Events now fire during OnParametersSetAsync when navigation path changes
- Uses URL change tracking to prevent re-render loops
- ItemCreated fires for each node after path is built
- ItemDataBound fires for each node after ItemCreated
- All 2,470 bUnit tests pass
- All 243 integration tests pass (including SiteMapPath/Events)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: Add docs and sample pages for 6 sub-100% components

Add missing documentation and sample pages to bring 6 components to
100% health score (44 -> 50 components at full health).

Documentation added:
- View.md - MultiView child container component
- Content.md - Master page content region component
- ContentPlaceHolder.md - Master page placeholder component
- MasterPage.md - Master page layout component

Sample pages added:
- RadioButton - standalone radio button with grouping, alignment, events
- NamingContainer - ID scoping with nesting, ctl00 prefix, visibility

Catalog entries added for View, Content, ContentPlaceHolder,
RadioButton, and NamingContainer in ComponentCatalog.cs.
Updated mkdocs.yml navigation.

All 2,448 tests pass. MkDocs strict build passes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: Remove duplicate RadioButton sample page causing route conflict

The PR added Components/Pages/ControlSamples/RadioButton/Index.razor but
Pages/ControlSamples/RadioButton/Index.razor already existed with the same
@page route. The ambiguous route crashed the Blazor circuit, cascading
failures to 32 integration tests.

All 243 integration tests now pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add standalone sample pages for Content, ContentPlaceHolder, and View
  (previously only available via shared MasterPage/MultiView group pages)
- Update ComponentCatalog routes to point to individual pages
- Create BaseValidator and BaseCompareValidator documentation
- Add to mkdocs.yml navigation
- Add 14 integration tests: 3 smoke tests + 11 interaction tests
  - Validator interaction tests for CompareValidator, RangeValidator,
    RegularExpressionValidator, CustomValidator, ValidationSummary
  - Content, ContentPlaceHolder, View smoke and render tests
- Fix validator test assertions to check visibility (not DOM text content)
  since validators keep error text in DOM when hidden

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…samples

feat: Quick wins - sample pages, docs, and integration tests
…Substitution promotion (#485)

- Add Known Fidelity Divergences documentation (ListView DOM, Calendar sub-IDs, Label, FormView, ID coverage)
- Add DataList ID rendering tests (3 tests: table with ID, table without ID, flow layout with ID)
- Add HiddenField ID rendering tests (2 tests: with and without ID)
- Update Substitution status from Deferred to active in tracked-components.json
- Correct audit: 7/8 data controls already have id=ClientID rendering
- Mark audit priority #2 (Extend ID rendering) as DONE
- Add fidelity doc to Migration section in mkdocs.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add 8 Roslyn analyzers with code fixes for Web Forms migration patterns:
- BWFC001: Missing [Parameter] attribute
- BWFC002: ViewState usage
- BWFC003: IsPostBack usage
- BWFC004: Response.Redirect
- BWFC005: Session usage
- BWFC010: Required component attributes
- BWFC011: Web Forms event handler signatures
- BWFC012: runat=server leftovers

Includes 89 tests, MkDocs documentation, and sample app content.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rm CI

Code fix providers for BWFC002, BWFC004, BWFC010, BWFC012 hardcoded \r\n
in EndOfLine trivia, causing test failures on Linux CI runners where
source files have \n line endings.

Added SyntaxExtensions.DetectEndOfLine() helper that reads the first
EndOfLineTrivia from the syntax tree, ensuring code fixes match the
document's existing line ending style.

Fixes failing tests in PR #487.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
fix: cross-platform line endings in analyzer code fix providers
* feat: Add BWFC013/014 analyzers, architecture guide, and CI docs

Analyzer Expansion:
- BWFC013: Detects Response.Write/WriteFile/Clear/Flush/End usage
- BWFC014: Detects Request.Form/Cookies/Headers/Files/QueryString access
- Both produce WARNING diagnostics with guidance-only code fixes (TODO comments)
- 21 new tests (111 total, all passing)

Documentation:
- dev-docs/ANALYZER-ARCHITECTURE.md: Contributor guide for building analyzers
- docs/Migration/Analyzers.md: BWFC013/014 entries, CI/CD integration section,
  .editorconfig severity tuning, prioritization guide

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(ai-team): Analyzer sprint 1 complete  orchestration & decision logs merged

Session: 2026-03-20T14-18-05Z-analyzer-sprint1
Requested by: Scribe

Changes:
- Orchestration logs for Cyclops (BWFC013/014 analyzers, 6 files, 111 tests) and Beast (architecture guide, 579 lines)
- Session log for analyzer sprint 1 execution
- Merged 10 decision inbox files  decisions.md (BWFC013/014 IDs, analyzer docs, deprecation guidance, ASHX/AXD middleware, RouteData fix, component audit, navigation UX, sample pages, middleware testing)
- Deleted all inbox files after merge
- Appended team updates to Cyclops, Beast, Forge, Jubilee, Rogue history.md files
- PR #487 opened on upstream targeting dev branch

* feat: bundle analyzers in main NuGet + add -Prescan switch to L1 script

Task 1: Added ProjectReference from BlazorWebFormsComponents to
BlazorWebFormsComponents.Analyzers with OutputItemType=Analyzer so
consumers get Roslyn analyzers automatically via the main NuGet package.

Task 2: Added -Prescan switch to bwfc-migrate.ps1 that scans source
.cs files for 9 BWFC analyzer patterns (BWFC001-005, 011-014) and
outputs a JSON summary report without performing any migration.
Includes human-readable breakdown via Write-Host.

Build: 0 errors, 111 analyzer tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: add Roslyn analyzer tests to build and squad-ci workflows

- build.yml: restore, build, run, upload, and publish analyzer test results
- squad-ci.yml: replace placeholder with dotnet restore/build/test for both suites
- Add setup-dotnet step to squad-ci.yml for .NET 10.0.x

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: use DetectEndOfLine() for cross-platform CI compatibility

All 4 code fix providers (BWFC001, BWFC004, BWFC013, BWFC014) used
hardcoded \r\n in SyntaxFactory.EndOfLine(), causing test failures
on Linux CI where source strings use \n.

- Add SyntaxExtensions.DetectEndOfLine() helper that reads line
  endings from the existing syntax tree
- Replace hardcoded \r\n with root.DetectEndOfLine() in all 4
  code fix providers
- Replace foreach+if with LINQ .Where() (CodeQL suggestion)

All 111 analyzer tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: use DetectEndOfLine() in MissingParameterAttributeCodeFixProvider

The using directive insertion used SyntaxFactory.CarriageReturnLineFeed
which hardcodes CRLF, failing on Linux CI where source uses LF.

Replace with newRoot.DetectEndOfLine() for cross-platform compatibility.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Initial plan

* Add sitemap meta tag to docs site via MkDocs Material theme override

Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>
Agent-Logs-Url: https://github.com/FritzAndFriends/BlazorWebFormsComponents/sessions/0a9bccbb-1c5c-4660-af93-5084ce1fc875

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>

private async Task<Document> CommentOutIsPostBackAsync(Document document, SyntaxNode diagnosticNode, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

Check warning

Code scanning / CodeQL

Useless assignment to local variable Warning

This assignment to
root
is useless, since its value is never read.

Copilot Autofix

AI 27 days ago

To fix the problem, remove the useless assignment to root. Since the value of root is never read, we can safely delete the entire line that calls document.GetSyntaxRootAsync(...). There is no need to replace it with anything (such as discarding to _), because the call itself is not needed and has no required side effects.

Concretely, in src/BlazorWebFormsComponents.Analyzers/IsPostBackUsageCodeFixProvider.cs, inside the CommentOutIsPostBackAsync method, delete line 41:

var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

No other lines in this file need to change, and no additional imports, methods, or definitions are required. The method will then start directly by locating the StatementSyntax from diagnosticNode, as it already does after the unused root assignment.

Suggested changeset 1
src/BlazorWebFormsComponents.Analyzers/IsPostBackUsageCodeFixProvider.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/BlazorWebFormsComponents.Analyzers/IsPostBackUsageCodeFixProvider.cs b/src/BlazorWebFormsComponents.Analyzers/IsPostBackUsageCodeFixProvider.cs
--- a/src/BlazorWebFormsComponents.Analyzers/IsPostBackUsageCodeFixProvider.cs
+++ b/src/BlazorWebFormsComponents.Analyzers/IsPostBackUsageCodeFixProvider.cs
@@ -38,7 +38,6 @@
 
         private async Task<Document> CommentOutIsPostBackAsync(Document document, SyntaxNode diagnosticNode, CancellationToken cancellationToken)
         {
-            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
             var statement = diagnosticNode.FirstAncestorOrSelf<StatementSyntax>();
             if (statement == null)
                 return document;
EOF
@@ -38,7 +38,6 @@

private async Task<Document> CommentOutIsPostBackAsync(Document document, SyntaxNode diagnosticNode, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var statement = diagnosticNode.FirstAncestorOrSelf<StatementSyntax>();
if (statement == null)
return document;
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated

private async Task<Document> CommentOutViewStateAsync(Document document, SyntaxNode diagnosticNode, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

Check warning

Code scanning / CodeQL

Useless assignment to local variable Warning

This assignment to
root
is useless, since its value is never read.

Copilot Autofix

AI 27 days ago

To fix the problem, remove the useless assignment to root and the associated call to GetSyntaxRootAsync, since its result is not used anywhere in CommentOutViewStateAsync. This keeps the method’s observable behavior the same while avoiding unnecessary work and eliminating the dead local variable.

Concretely, in src/BlazorWebFormsComponents.Analyzers/ViewStateUsageCodeFixProvider.cs, inside the CommentOutViewStateAsync method, delete the line:

var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

No additional imports, methods, or definitions are required. The rest of the method already uses document and diagnosticNode directly and does not depend on root, so functionality remains unchanged.

Suggested changeset 1
src/BlazorWebFormsComponents.Analyzers/ViewStateUsageCodeFixProvider.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/BlazorWebFormsComponents.Analyzers/ViewStateUsageCodeFixProvider.cs b/src/BlazorWebFormsComponents.Analyzers/ViewStateUsageCodeFixProvider.cs
--- a/src/BlazorWebFormsComponents.Analyzers/ViewStateUsageCodeFixProvider.cs
+++ b/src/BlazorWebFormsComponents.Analyzers/ViewStateUsageCodeFixProvider.cs
@@ -38,7 +38,6 @@
 
         private async Task<Document> CommentOutViewStateAsync(Document document, SyntaxNode diagnosticNode, CancellationToken cancellationToken)
         {
-            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
             var statement = diagnosticNode.FirstAncestorOrSelf<StatementSyntax>();
             if (statement == null)
                 return document;
EOF
@@ -38,7 +38,6 @@

private async Task<Document> CommentOutViewStateAsync(Document document, SyntaxNode diagnosticNode, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var statement = diagnosticNode.FirstAncestorOrSelf<StatementSyntax>();
if (statement == null)
return document;
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
// Only report if this node is not already the expression of another
// member access that we'd also flag.
var parent = memberAccess.Parent;
if (parent is MemberAccessExpressionSyntax parentMember)

Check warning

Code scanning / CodeQL

Useless assignment to local variable Warning

This assignment to
parentMember
is useless, since its value is never read.

Copilot Autofix

AI 27 days ago

In general, to fix a “useless assignment to local variable” you either remove the variable (and possibly the whole statement) if it is not needed, or start using it in the logic if it was supposed to be used. Here, the concrete pattern match introduces parentMember but never uses it; only the fact that parent might be a MemberAccessExpressionSyntax appears to have been of interest. The simplest fix that preserves current behavior is to remove the variable from the pattern and keep only a type check, or remove the entire if if that check is also unnecessary.

The single best minimal fix without changing functionality is to change line 72 from a pattern with a bound variable to a plain type check: if (parent is MemberAccessExpressionSyntax). This keeps the structure and comments about “parent is MemberAccessExpressionSyntax” but removes the unused parentMember variable, silencing the CodeQL warning. No new imports, methods, or additional definitions are required. Only the AnalyzeMemberAccess method in src/BlazorWebFormsComponents.Analyzers/SessionUsageAnalyzer.cs needs modification, at the if (parent is MemberAccessExpressionSyntax parentMember) line.

Suggested changeset 1
src/BlazorWebFormsComponents.Analyzers/SessionUsageAnalyzer.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/BlazorWebFormsComponents.Analyzers/SessionUsageAnalyzer.cs b/src/BlazorWebFormsComponents.Analyzers/SessionUsageAnalyzer.cs
--- a/src/BlazorWebFormsComponents.Analyzers/SessionUsageAnalyzer.cs
+++ b/src/BlazorWebFormsComponents.Analyzers/SessionUsageAnalyzer.cs
@@ -69,7 +69,7 @@
                 // Only report if this node is not already the expression of another
                 // member access that we'd also flag.
                 var parent = memberAccess.Parent;
-                if (parent is MemberAccessExpressionSyntax parentMember)
+                if (parent is MemberAccessExpressionSyntax)
                 {
                     // If parent is HttpContext.Current.Session, and that's followed by element access,
                     // we'll catch the Session["key"] separately. But HttpContext.Current itself is the problem.
EOF
@@ -69,7 +69,7 @@
// Only report if this node is not already the expression of another
// member access that we'd also flag.
var parent = memberAccess.Parent;
if (parent is MemberAccessExpressionSyntax parentMember)
if (parent is MemberAccessExpressionSyntax)
{
// If parent is HttpContext.Current.Session, and that's followed by element access,
// we'll catch the Session["key"] separately. But HttpContext.Current itself is the problem.
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
cut.FindComponent<AspNetValidationSummary>().Instance.ShowMessageBox.ShouldBeTrue();
}

private ExampleModel model = new ExampleModel();

Check notice

Code scanning / CodeQL

Missed 'readonly' opportunity Note test

Field 'model' can be 'readonly'.

Copilot Autofix

AI 27 days ago

In general, to fix a “Missed 'readonly' opportunity” for a field, you add the readonly modifier to the field declaration when the field is only assigned at declaration or within constructors of the same class. This enforces that the field cannot be reassigned later, while leaving mutation of the object’s internal state (its properties) still possible.

For this specific case, update the field declaration on line 51 in src/BlazorWebFormsComponents.Test/Validations/ValidationSummary/NewProperties.razor from private ExampleModel model = new ExampleModel(); to private readonly ExampleModel model = new ExampleModel();. No other changes are required: the tests only read from model via Model="@model" in the EditForm, and the ExampleModel class remains mutable through its Name property. No imports or new methods are needed.

Suggested changeset 1
src/BlazorWebFormsComponents.Test/Validations/ValidationSummary/NewProperties.razor

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/BlazorWebFormsComponents.Test/Validations/ValidationSummary/NewProperties.razor b/src/BlazorWebFormsComponents.Test/Validations/ValidationSummary/NewProperties.razor
--- a/src/BlazorWebFormsComponents.Test/Validations/ValidationSummary/NewProperties.razor
+++ b/src/BlazorWebFormsComponents.Test/Validations/ValidationSummary/NewProperties.razor
@@ -48,7 +48,7 @@
 		cut.FindComponent<AspNetValidationSummary>().Instance.ShowMessageBox.ShouldBeTrue();
 	}
 
-	private ExampleModel model = new ExampleModel();
+	private readonly ExampleModel model = new ExampleModel();
 
 	public class ExampleModel
 	{
EOF
@@ -48,7 +48,7 @@
cut.FindComponent<AspNetValidationSummary>().Instance.ShowMessageBox.ShouldBeTrue();
}

private ExampleModel model = new ExampleModel();
private readonly ExampleModel model = new ExampleModel();

public class ExampleModel
{
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
cut.FindComponent<CompareValidator<int>>().Instance.ControlToCompare.ShouldBe("OtherInput");
}

private ExampleModel model = new ExampleModel();

Check notice

Code scanning / CodeQL

Missed 'readonly' opportunity Note test

Field 'model' can be 'readonly'.

Copilot Autofix

AI 27 days ago

In general, to fix this kind of issue you add the readonly modifier to any field that is only assigned at its declaration or within a constructor of the same class. This guarantees the field reference cannot be changed after object initialization, while not affecting mutations to the object it refers to.

For this specific file, the best fix is to update the declaration of the private ExampleModel model field on line 40 to include the readonly modifier: private readonly ExampleModel model = new ExampleModel();. This does not change any behavior, because the field was never reassigned elsewhere in the shown code. No additional methods, imports, or other definitions are required.

Suggested changeset 1
src/BlazorWebFormsComponents.Test/Validations/CompareValidator/ControlToCompare/ControlToCompareTests.razor

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/BlazorWebFormsComponents.Test/Validations/CompareValidator/ControlToCompare/ControlToCompareTests.razor b/src/BlazorWebFormsComponents.Test/Validations/CompareValidator/ControlToCompare/ControlToCompareTests.razor
--- a/src/BlazorWebFormsComponents.Test/Validations/CompareValidator/ControlToCompare/ControlToCompareTests.razor
+++ b/src/BlazorWebFormsComponents.Test/Validations/CompareValidator/ControlToCompare/ControlToCompareTests.razor
@@ -37,7 +37,7 @@
 		cut.FindComponent<CompareValidator<int>>().Instance.ControlToCompare.ShouldBe("OtherInput");
 	}
 
-	private ExampleModel model = new ExampleModel();
+	private readonly ExampleModel model = new ExampleModel();
 
 	public class ExampleModel
 	{
EOF
@@ -37,7 +37,7 @@
cut.FindComponent<CompareValidator<int>>().Instance.ControlToCompare.ShouldBe("OtherInput");
}

private ExampleModel model = new ExampleModel();
private readonly ExampleModel model = new ExampleModel();

public class ExampleModel
{
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
Comment on lines +133 to +140
foreach (var expr in creation.Initializer.Expressions)
{
if (expr is AssignmentExpressionSyntax assignment &&
assignment.Left is IdentifierNameSyntax propName)
{
result.Add(propName.Identifier.Text);
}
}

Check notice

Code scanning / CodeQL

Missed opportunity to use Where Note

This foreach loop
implicitly filters its target sequence
- consider filtering the sequence explicitly using '.Where(...)'.

Copilot Autofix

AI 27 days ago

In general, to fix this class of issues you move the filtering predicate from inside the loop body into a LINQ .Where(...) on the sequence you are iterating, so the foreach only runs over already‑filtered elements. This avoids an initial if (!condition) continue; or wrapping the whole body in if (condition).

Here, we should change foreach (var expr in creation.Initializer.Expressions) to iterate only over those expressions that are AssignmentExpressionSyntax with an IdentifierNameSyntax on the left. We can do this with a Where plus pattern matching in the lambda. The rest of the method (result.Add(...), returning the set) remains unchanged, so behavior is preserved.

Concretely, in src/BlazorWebFormsComponents.Analyzers/RequiredAttributeAnalyzer.cs, within GetInitializerAssignments, replace:

  • the foreach (var expr in creation.Initializer.Expressions) header, and
  • the inner if (expr is AssignmentExpressionSyntax ... ) { ... }

with a foreach that iterates over creation.Initializer.Expressions.Where(expr => expr is AssignmentExpressionSyntax { Left: IdentifierNameSyntax }), and a simplified pattern match inside the loop. No new imports are needed because System.Linq is already included.

Suggested changeset 1
src/BlazorWebFormsComponents.Analyzers/RequiredAttributeAnalyzer.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/BlazorWebFormsComponents.Analyzers/RequiredAttributeAnalyzer.cs b/src/BlazorWebFormsComponents.Analyzers/RequiredAttributeAnalyzer.cs
--- a/src/BlazorWebFormsComponents.Analyzers/RequiredAttributeAnalyzer.cs
+++ b/src/BlazorWebFormsComponents.Analyzers/RequiredAttributeAnalyzer.cs
@@ -130,10 +130,9 @@
             var result = new HashSet<string>();
             if (creation.Initializer != null)
             {
-                foreach (var expr in creation.Initializer.Expressions)
+                foreach (var expr in creation.Initializer.Expressions.Where(expr => expr is AssignmentExpressionSyntax { Left: IdentifierNameSyntax }))
                 {
-                    if (expr is AssignmentExpressionSyntax assignment &&
-                        assignment.Left is IdentifierNameSyntax propName)
+                    if (expr is AssignmentExpressionSyntax { Left: IdentifierNameSyntax propName })
                     {
                         result.Add(propName.Identifier.Text);
                     }
EOF
@@ -130,10 +130,9 @@
var result = new HashSet<string>();
if (creation.Initializer != null)
{
foreach (var expr in creation.Initializer.Expressions)
foreach (var expr in creation.Initializer.Expressions.Where(expr => expr is AssignmentExpressionSyntax { Left: IdentifierNameSyntax }))
{
if (expr is AssignmentExpressionSyntax assignment &&
assignment.Left is IdentifierNameSyntax propName)
if (expr is AssignmentExpressionSyntax { Left: IdentifierNameSyntax propName })
{
result.Add(propName.Identifier.Text);
}
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
Comment on lines +90 to +97
foreach (var required in requiredProps)
{
if (!assignedProperties.Contains(required))
{
var diagnostic = Diagnostic.Create(Rule, creation.GetLocation(), baseTypeName, required);
context.ReportDiagnostic(diagnostic);
}
}

Check notice

Code scanning / CodeQL

Missed opportunity to use Where Note

This foreach loop
implicitly filters its target sequence
- consider filtering the sequence explicitly using '.Where(...)'.

Copilot Autofix

AI 27 days ago

In general, to fix this kind of issue you identify the foreach loop that conditionally processes elements based on an if inside the loop, and refactor it to iterate over a filtered sequence using Where. That is, instead of looping over all items and skipping those that fail the condition, you pre-filter the enumerable and then loop over only the relevant items.

For this specific code, we should change the loop:

foreach (var required in requiredProps)
{
    if (!assignedProperties.Contains(required))
    {
        var diagnostic = Diagnostic.Create(Rule, creation.GetLocation(), baseTypeName, required);
        context.ReportDiagnostic(diagnostic);
    }
}

to instead iterate only over those required properties that are not in assignedProperties. Since requiredProps is an array of strings and System.Linq is already imported (line 7), we can use:

foreach (var required in requiredProps.Where(required => !assignedProperties.Contains(required)))
{
    var diagnostic = Diagnostic.Create(Rule, creation.GetLocation(), baseTypeName, required);
    context.ReportDiagnostic(diagnostic);
}

This preserves the existing behavior exactly: diagnostics are still reported for each missing required property, but the filter is expressed with Where instead of an if inside the loop. No additional imports, methods, or type changes are needed; the change is localized to the foreach loop around line 90 in RequiredAttributeAnalyzer.cs.

Suggested changeset 1
src/BlazorWebFormsComponents.Analyzers/RequiredAttributeAnalyzer.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/BlazorWebFormsComponents.Analyzers/RequiredAttributeAnalyzer.cs b/src/BlazorWebFormsComponents.Analyzers/RequiredAttributeAnalyzer.cs
--- a/src/BlazorWebFormsComponents.Analyzers/RequiredAttributeAnalyzer.cs
+++ b/src/BlazorWebFormsComponents.Analyzers/RequiredAttributeAnalyzer.cs
@@ -87,13 +87,10 @@
                 }
             }
 
-            foreach (var required in requiredProps)
+            foreach (var required in requiredProps.Where(required => !assignedProperties.Contains(required)))
             {
-                if (!assignedProperties.Contains(required))
-                {
-                    var diagnostic = Diagnostic.Create(Rule, creation.GetLocation(), baseTypeName, required);
-                    context.ReportDiagnostic(diagnostic);
-                }
+                var diagnostic = Diagnostic.Create(Rule, creation.GetLocation(), baseTypeName, required);
+                context.ReportDiagnostic(diagnostic);
             }
         }
 
EOF
@@ -87,13 +87,10 @@
}
}

foreach (var required in requiredProps)
foreach (var required in requiredProps.Where(required => !assignedProperties.Contains(required)))
{
if (!assignedProperties.Contains(required))
{
var diagnostic = Diagnostic.Create(Rule, creation.GetLocation(), baseTypeName, required);
context.ReportDiagnostic(diagnostic);
}
var diagnostic = Diagnostic.Create(Rule, creation.GetLocation(), baseTypeName, required);
context.ReportDiagnostic(diagnostic);
}
}

Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
Comment on lines +80 to +83
if (i == 0)
commentedLines[i] = "// " + lineCode;
else
commentedLines[i] = lineIndent + "// " + lineCode;

Check notice

Code scanning / CodeQL

Missed ternary opportunity Note

Both branches of this 'if' statement write to the same variable - consider using '?' to express intent better.

Copilot Autofix

AI 27 days ago

To fix the problem, replace the if statement that assigns to commentedLines[i] in both branches with a single assignment using the conditional (?:) operator. This keeps the logic identical while making the code more concise and clearly expressing that only the value assigned changes based on the condition.

Concretely, in src/BlazorWebFormsComponents.Analyzers/IsPostBackUsageCodeFixProvider.cs, within the loop that builds commentedLines, replace lines 80–83:

  • Remove the multi-line if (i == 0) ... else ... block.
  • Add a single line: commentedLines[i] = i == 0 ? "// " + lineCode : lineIndent + "// " + lineCode;

No new methods, imports, or definitions are needed; this is a pure expression-level refactor.

Suggested changeset 1
src/BlazorWebFormsComponents.Analyzers/IsPostBackUsageCodeFixProvider.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/BlazorWebFormsComponents.Analyzers/IsPostBackUsageCodeFixProvider.cs b/src/BlazorWebFormsComponents.Analyzers/IsPostBackUsageCodeFixProvider.cs
--- a/src/BlazorWebFormsComponents.Analyzers/IsPostBackUsageCodeFixProvider.cs
+++ b/src/BlazorWebFormsComponents.Analyzers/IsPostBackUsageCodeFixProvider.cs
@@ -77,10 +77,7 @@
                 var lineIndent = line.Substring(0, ws);
                 var lineCode = line.Substring(ws);
 
-                if (i == 0)
-                    commentedLines[i] = "// " + lineCode;
-                else
-                    commentedLines[i] = lineIndent + "// " + lineCode;
+                commentedLines[i] = i == 0 ? "// " + lineCode : lineIndent + "// " + lineCode;
             }
             var commentedText = string.Join(newLine, commentedLines);
 
EOF
@@ -77,10 +77,7 @@
var lineIndent = line.Substring(0, ws);
var lineCode = line.Substring(ws);

if (i == 0)
commentedLines[i] = "// " + lineCode;
else
commentedLines[i] = lineIndent + "// " + lineCode;
commentedLines[i] = i == 0 ? "// " + lineCode : lineIndent + "// " + lineCode;
}
var commentedText = string.Join(newLine, commentedLines);

Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
Comment on lines +64 to +71
foreach (var trivia in method.GetLeadingTrivia())
{
if (trivia.IsKind(SyntaxKind.WhitespaceTrivia))
{
newLeadingTrivia = newLeadingTrivia.Add(trivia);
break;
}
}

Check notice

Code scanning / CodeQL

Missed opportunity to use Where Note

This foreach loop
implicitly filters its target sequence
- consider filtering the sequence explicitly using '.Where(...)'.

Copilot Autofix

AI 27 days ago

Generally, to fix this kind of issue, replace a foreach loop that immediately filters inside the loop body with a LINQ query such as .Where(...), potentially combined with .FirstOrDefault()/.First() if only the first matching element is needed. This moves the filter predicate into the sequence definition instead of being embedded in loop control logic.

In this specific case, lines 64–71 iterate over method.GetLeadingTrivia(), look for the first trivia whose kind is SyntaxKind.WhitespaceTrivia, append that trivia to newLeadingTrivia, then break. To keep the behavior identical, we can compute the first whitespace trivia via LINQ, then append it if present:

var indentationTrivia = method.GetLeadingTrivia()
    .FirstOrDefault(t => t.IsKind(SyntaxKind.WhitespaceTrivia));

if (!indentationTrivia.Equals(default(SyntaxTrivia)))
{
    newLeadingTrivia = newLeadingTrivia.Add(indentationTrivia);
}

This removes the implicit filtering foreach entirely, replacing it with explicit filtering using LINQ (FirstOrDefault with a predicate, which is equivalent to Where(...).FirstOrDefault()). No new imports are required because System.Linq is already imported at the top of the file. The rest of the method and file remain unchanged.

Suggested changeset 1
src/BlazorWebFormsComponents.Analyzers/EventHandlerSignatureCodeFixProvider.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/BlazorWebFormsComponents.Analyzers/EventHandlerSignatureCodeFixProvider.cs b/src/BlazorWebFormsComponents.Analyzers/EventHandlerSignatureCodeFixProvider.cs
--- a/src/BlazorWebFormsComponents.Analyzers/EventHandlerSignatureCodeFixProvider.cs
+++ b/src/BlazorWebFormsComponents.Analyzers/EventHandlerSignatureCodeFixProvider.cs
@@ -61,13 +61,12 @@
                 .Add(root.DetectEndOfLine());
 
             // Collect indentation from original method
-            foreach (var trivia in method.GetLeadingTrivia())
+            var indentationTrivia = method.GetLeadingTrivia()
+                .FirstOrDefault(t => t.IsKind(SyntaxKind.WhitespaceTrivia));
+
+            if (!indentationTrivia.Equals(default(SyntaxTrivia)))
             {
-                if (trivia.IsKind(SyntaxKind.WhitespaceTrivia))
-                {
-                    newLeadingTrivia = newLeadingTrivia.Add(trivia);
-                    break;
-                }
+                newLeadingTrivia = newLeadingTrivia.Add(indentationTrivia);
             }
 
             var newMethod = method.WithLeadingTrivia(newLeadingTrivia);
EOF
@@ -61,13 +61,12 @@
.Add(root.DetectEndOfLine());

// Collect indentation from original method
foreach (var trivia in method.GetLeadingTrivia())
var indentationTrivia = method.GetLeadingTrivia()
.FirstOrDefault(t => t.IsKind(SyntaxKind.WhitespaceTrivia));

if (!indentationTrivia.Equals(default(SyntaxTrivia)))
{
if (trivia.IsKind(SyntaxKind.WhitespaceTrivia))
{
newLeadingTrivia = newLeadingTrivia.Add(trivia);
break;
}
newLeadingTrivia = newLeadingTrivia.Add(indentationTrivia);
}

var newMethod = method.WithLeadingTrivia(newLeadingTrivia);
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
Comment on lines +47 to +54
foreach (var trivia in method.GetLeadingTrivia())
{
if (trivia.IsKind(SyntaxKind.SingleLineCommentTrivia) &&
trivia.ToString().Contains("TODO: Convert to EventCallback"))
{
return document;
}
}

Check notice

Code scanning / CodeQL

Missed opportunity to use Where Note

This foreach loop
implicitly filters its target sequence
- consider filtering the sequence explicitly using '.Where(...)'.

Copilot Autofix

AI 27 days ago

In general, to fix a “missed opportunity to use Where” in a foreach, convert the pattern:

foreach (var x in sequence)
{
    if (!predicate(x))
        continue;

    // use x
}

into something that filters first and then operates, e.g.:

foreach (var x in sequence.Where(predicate))
{
    // use x
}

or, if the body only returns or breaks, use Any/FirstOrDefault with a predicate.

In this specific code, the loop only checks whether any leading trivia is a single-line comment containing "TODO: Convert to EventCallback" and, if found, returns the document early. This is more idiomatically written using Any with a predicate on the trivia sequence, eliminating the explicit loop. We already have using System.Linq;, so we can write:

if (method.GetLeadingTrivia().Any(trivia =>
        trivia.IsKind(SyntaxKind.SingleLineCommentTrivia) &&
        trivia.ToString().Contains("TODO: Convert to EventCallback")))
{
    return document;
}

This preserves the existing behavior: if such a TODO comment exists anywhere in the leading trivia, we return the original document; otherwise we proceed to build and insert the TODO comment. The change is localized to the loop in AddTodoCommentAsync (lines 47–54); no additional imports or helper methods are required.

Suggested changeset 1
src/BlazorWebFormsComponents.Analyzers/EventHandlerSignatureCodeFixProvider.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/BlazorWebFormsComponents.Analyzers/EventHandlerSignatureCodeFixProvider.cs b/src/BlazorWebFormsComponents.Analyzers/EventHandlerSignatureCodeFixProvider.cs
--- a/src/BlazorWebFormsComponents.Analyzers/EventHandlerSignatureCodeFixProvider.cs
+++ b/src/BlazorWebFormsComponents.Analyzers/EventHandlerSignatureCodeFixProvider.cs
@@ -44,13 +44,11 @@
             var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
 
             // Don't add if a TODO comment already exists in leading trivia
-            foreach (var trivia in method.GetLeadingTrivia())
+            if (method.GetLeadingTrivia().Any(trivia =>
+                trivia.IsKind(SyntaxKind.SingleLineCommentTrivia) &&
+                trivia.ToString().Contains("TODO: Convert to EventCallback")))
             {
-                if (trivia.IsKind(SyntaxKind.SingleLineCommentTrivia) &&
-                    trivia.ToString().Contains("TODO: Convert to EventCallback"))
-                {
-                    return document;
-                }
+                return document;
             }
 
             var todoComment = SyntaxFactory.Comment(
EOF
@@ -44,13 +44,11 @@
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

// Don't add if a TODO comment already exists in leading trivia
foreach (var trivia in method.GetLeadingTrivia())
if (method.GetLeadingTrivia().Any(trivia =>
trivia.IsKind(SyntaxKind.SingleLineCommentTrivia) &&
trivia.ToString().Contains("TODO: Convert to EventCallback")))
{
if (trivia.IsKind(SyntaxKind.SingleLineCommentTrivia) &&
trivia.ToString().Contains("TODO: Convert to EventCallback"))
{
return document;
}
return document;
}

var todoComment = SyntaxFactory.Comment(
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
@csharpfritz csharpfritz merged commit c7b4b4b into main Mar 23, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants