Skip to content

[Blazor] Add analyzer to detect ElementReference usage outside OnAfterRender with helper method analysis #63248

New issue

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

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

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented Aug 13, 2025

This PR adds a new Blazor analyzer that detects when ElementReference instances are accessed outside of the OnAfterRenderAsync or OnAfterRender lifecycle methods, which can lead to runtime errors due to accessing stale or non-existent element references.

Problem

Developers often try to access ElementReference fields or properties in other component lifecycle methods like OnInitialized, OnParametersSet, or in event handlers. This practice is problematic because:

  • Element references are only valid after the component has been rendered
  • Accessing them before rendering can result in null references or stale data
  • This is a common source of confusion and runtime errors in Blazor applications

Solution

The new ElementReferenceUsageAnalyzer provides intelligent analysis with two distinct diagnostics:

BL0009 - Direct ElementReference Access

Reports when ElementReference fields or properties are directly accessed outside of OnAfterRenderAsync or OnAfterRender methods.

BL0010 - Helper Method Called from Unsafe Context

Reports when helper methods that access ElementReference are called from methods outside the safe render lifecycle, with the message: "Method '{MethodName}' accesses ElementReference '{ElementName}' and is being invoked outside of OnAfterRenderAsync or OnAfterRender"

Key Features

  • Smart Helper Method Analysis: Tracks method call graphs within a class to determine if helper methods accessing ElementReference are called from safe contexts
  • Performance Optimized: Uses RegisterSymbolStartAction limited to IComponent types instead of broad compilation analysis
  • BuildRenderTree Safe: Skips warnings for AddElementReferenceCapture calls in render tree building
  • Class Scope Limited: Analysis restricted to current class to avoid false positives from inheritance scenarios

Examples

public class MyComponent : ComponentBase
{
    private ElementReference myElement;

    protected override void OnInitialized()
    {
        // BL0009: Direct access outside OnAfterRender
        var elementId = myElement.Id;
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await Helper(); // Safe - no warning
        }
    }

    private async Task HandleClick()
    {
        await Helper(); // BL0010: Helper called from unsafe context
    }

    private async Task Helper()
    {
        await myElement.FocusAsync(); // ElementReference access in helper
    }
}

This approach allows developers to safely extract ElementReference logic into helper methods as long as those helpers are only called from OnAfterRender or OnAfterRenderAsync, while still catching unsafe usage patterns.

Fixes #63247.


💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.

Copy link
Contributor

Greetings human! You've submitted a PR that modifies code that is shared with https://github.com/dotnet/runtime . Please make sure you synchronize this code with the changes in that repo!

Copilot AI and others added 2 commits August 13, 2025 22:21
Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
…hensive tests

Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
@Copilot Copilot AI changed the title [WIP] [Blazor] Analyzer should check that Element reference instances are only ever read within OnAfterRenderAsync [Blazor] Add analyzer to check ElementReference usage is only in OnAfterRenderAsync Aug 13, 2025
@Copilot Copilot AI requested a review from javiercn August 13, 2025 22:30
Copilot finished work on behalf of javiercn August 13, 2025 22:30
@javiercn
Copy link
Member

@copilot When an element is accessed outside of OnAfterRender or OnAfterRender async, could we check if those methods are only ever called within OnAfterRender or OnAfterRender async? That should still be safe isn't it?

So,

OnAfterRender()
{
  Helper()
}

Helper()
{
  JSRuntime.Invoke("doSomethign", myRef)
}

will warn today but it is totally safe to do as long as Helper() isn't called outside OnAfterRender/OnAfterRender async.

We could do a different diagnostic here that says something along the lines of "Helper" accesses or uses ElementReference myReference and is being invoked outside of OnAfterRender or OnAfterRenderAsync. (Those are the message contents, but you figure out the proper language and tone following the pattern of existing diagnostics).

We want to limit our analysis to the current class (if Helper is on a base class and its used on a derived class we won't detect and catch the issues, that's fine).

A question that you need to solve is:

  • Will the analyzer warn for CaptureElementReference calls? (The ones that the compiler generates inside BuildRenderTree) we should make sure it doesn't.

Finally, hooking to CompilationStart is a bit broad, could we instead hook into https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.diagnostics.analysiscontext.registersymbolstartaction?view=roslyn-dotnet-4.13.0#microsoft-codeanalysis-diagnostics-analysiscontext-registersymbolstartaction(system-action((microsoft-codeanalysis-diagnostics-symbolstartanalysiscontext))-microsoft-codeanalysis-symbolkind)

Or maybe https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.diagnostics.analysiscontext.registersyntaxnodeaction?view=roslyn-dotnet-4.13.0#microsoft-codeanalysis-diagnostics-analysiscontext-registersyntaxnodeaction-1(system-action((microsoft-codeanalysis-diagnostics-syntaxnodeanalysiscontext))-0())

For reference, here are all the options. The goal is to minimize the times we run or the lookups we have to perform. Ideally we only want to run when accessing ElementReference inside an IComponent implementation.

RegisterAdditionalFileAction(Action) | Register an action to be executed for each non-code document. An additional file action reports Diagnostics about the AdditionalText of a document. -- | -- RegisterCodeBlockAction(Action) | Register an action to be executed after semantic analysis of a method body or an expression appearing outside a method body. A code block action reports Diagnostics about code blocks. RegisterCodeBlockStartAction(Action>) | Register an action to be executed at the start of semantic analysis of a method body or an expression appearing outside a method body. A code block start action can register other actions and/or collect state information to be used in diagnostic analysis, but cannot itself report any Diagnostics. RegisterCompilationAction(Action) | Register an action to be executed for a complete compilation. A compilation action reports Diagnostics about the Compilation. RegisterCompilationStartAction(Action) | Register an action to be executed at compilation start. A compilation start action can register other actions and/or collect state information to be used in diagnostic analysis, but cannot itself report any Diagnostics. RegisterOperationAction(Action, ImmutableArray) | Register an action to be executed at completion of semantic analysis of an IOperation with an appropriate Kind. An operation action can report Diagnostics about IOperations, and can also collect state information to be used by other operation actions or code block end actions. RegisterOperationAction(Action, OperationKind[]) | Register an action to be executed at completion of semantic analysis of an IOperation with an appropriate Kind. An operation action can report Diagnostics about IOperations, and can also collect state information to be used by other operation actions or code block end actions. RegisterOperationBlockAction(Action) | Register an action to be executed after semantic analysis of a method body or an expression appearing outside a method body. An operation block action reports Diagnostics about operation blocks. RegisterOperationBlockStartAction(Action) | Register an action to be executed at the start of semantic analysis of a method body or an expression appearing outside a method body. An operation block start action can register other actions and/or collect state information to be used in diagnostic analysis, but cannot itself report any Diagnostics. RegisterSemanticModelAction(Action) | Register an action to be executed at completion of semantic analysis of a document, which will operate on the SemanticModel of the document. A semantic model action reports Diagnostics about the model. RegisterSymbolAction(Action, ImmutableArray) | Register an action to be executed at completion of semantic analysis of an ISymbol with an appropriate Kind. A symbol action reports Diagnostics about ISymbols. RegisterSymbolAction(Action, SymbolKind[]) | Register an action to be executed at completion of semantic analysis of an ISymbol with an appropriate Kind. A symbol action reports Diagnostics about ISymbols. RegisterSymbolStartAction(Action, SymbolKind) | Register an action to be executed at start of semantic analysis of an ISymbol and its members with an appropriate Kind. RegisterSyntaxNodeAction(Action, ImmutableArray) | Register an action to be executed at completion of semantic analysis of a SyntaxNode with an appropriate Kind. A syntax node action can report Diagnostics about SyntaxNodes, and can also collect state information to be used by other syntax node actions or code block end actions. RegisterSyntaxNodeAction(Action, TLanguageKindEnum[]) | Register an action to be executed at completion of semantic analysis of a SyntaxNode with an appropriate Kind. A syntax node action can report Diagnostics about SyntaxNodes, and can also collect state information to be used by other syntax node actions or code block end actions. RegisterSyntaxTreeAction(Action) | Register an action to be executed at completion of parsing of a code document. A syntax tree action reports Diagnostics about the SyntaxTree of a document. [RegisterAdditionalFileAction(Action)](https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.diagnostics.analysiscontext.registeradditionalfileaction?view=roslyn-dotnet-4.13.0#microsoft-codeanalysis-diagnostics-analysiscontext-registeradditionalfileaction(system-action((microsoft-codeanalysis-diagnostics-additionalfileanalysiscontext)))) Register an action to be executed for each non-code document. An additional file action reports [Diagnostic](https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.diagnostic?view=roslyn-dotnet-4.13.0)s about the [AdditionalText](https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.additionaltext?view=roslyn-dotnet-4.13.0) of a document.

RegisterCodeBlockAction(Action)
Register an action to be executed after semantic analysis of a method body or an expression appearing outside a method body. A code block action reports Diagnostics about code blocks.

RegisterCodeBlockStartAction(Action<CodeBlockStartAnalysisContext>)
Register an action to be executed at the start of semantic analysis of a method body or an expression appearing outside a method body. A code block start action can register other actions and/or collect state information to be used in diagnostic analysis, but cannot itself report any Diagnostics.

RegisterCompilationAction(Action)
Register an action to be executed for a complete compilation. A compilation action reports Diagnostics about the Compilation.

RegisterCompilationStartAction(Action)
Register an action to be executed at compilation start. A compilation start action can register other actions and/or collect state information to be used in diagnostic analysis, but cannot itself report any Diagnostics.

RegisterOperationAction(Action, ImmutableArray)
Register an action to be executed at completion of semantic analysis of an IOperation with an appropriate Kind. An operation action can report Diagnostics about IOperations, and can also collect state information to be used by other operation actions or code block end actions.

RegisterOperationAction(Action, OperationKind[])
Register an action to be executed at completion of semantic analysis of an IOperation with an appropriate Kind. An operation action can report Diagnostics about IOperations, and can also collect state information to be used by other operation actions or code block end actions.

RegisterOperationBlockAction(Action)
Register an action to be executed after semantic analysis of a method body or an expression appearing outside a method body. An operation block action reports Diagnostics about operation blocks.

RegisterOperationBlockStartAction(Action)
Register an action to be executed at the start of semantic analysis of a method body or an expression appearing outside a method body. An operation block start action can register other actions and/or collect state information to be used in diagnostic analysis, but cannot itself report any Diagnostics.

RegisterSemanticModelAction(Action)
Register an action to be executed at completion of semantic analysis of a document, which will operate on the SemanticModel of the document. A semantic model action reports Diagnostics about the model.

RegisterSymbolAction(Action, ImmutableArray)
Register an action to be executed at completion of semantic analysis of an ISymbol with an appropriate Kind. A symbol action reports Diagnostics about ISymbols.

RegisterSymbolAction(Action, SymbolKind[])
Register an action to be executed at completion of semantic analysis of an ISymbol with an appropriate Kind. A symbol action reports Diagnostics about ISymbols.

RegisterSymbolStartAction(Action, SymbolKind)
Register an action to be executed at start of semantic analysis of an ISymbol and its members with an appropriate Kind.

RegisterSyntaxNodeAction(Action, ImmutableArray)
Register an action to be executed at completion of semantic analysis of a SyntaxNode with an appropriate Kind. A syntax node action can report Diagnostics about SyntaxNodes, and can also collect state information to be used by other syntax node actions or code block end actions.

RegisterSyntaxNodeAction(Action, TLanguageKindEnum[])
Register an action to be executed at completion of semantic analysis of a SyntaxNode with an appropriate Kind. A syntax node action can report Diagnostics about SyntaxNodes, and can also collect state information to be used by other syntax node actions or code block end actions.

RegisterSyntaxTreeAction(Action)
Register an action to be executed at completion of parsing of a code document. A syntax tree action reports Diagnostics about the SyntaxTree of a document.

…sis and more efficient registration

Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Aug 14, 2025

@copilot When an element is accessed outside of OnAfterRender or OnAfterRender async, could we check if those methods are only ever called within OnAfterRender or OnAfterRender async? That should stil...

I've implemented all the requested improvements in commit 9b53c3e:

Helper method analysis: The analyzer now detects when methods accessing ElementReference are called from unsafe contexts and provides a specific diagnostic (BL0010) with the message format you suggested.

Safe helper calls: Methods called only from OnAfterRender/OnAfterRenderAsync don't trigger warnings, supporting the pattern you described.

CaptureElementReference protection: Added IsElementReferenceCaptureCall method to skip warnings for BuildRenderTree scenarios.

Efficient registration: Switched from RegisterCompilationStartAction to RegisterSymbolStartAction with SymbolKind.NamedType, limited to IComponent implementations only.

Class scope limitation: Analysis is restricted to method calls within the same type using SymbolEqualityComparer on the containing type.

The analyzer now provides two distinct diagnostics:

  • BL0009: Direct ElementReference access outside OnAfterRender methods
  • BL0010: "Method '{0}' accesses ElementReference '{1}' and is being invoked outside of OnAfterRenderAsync or OnAfterRender"

All scenarios are covered with comprehensive tests including chained helper methods and mixed safe/unsafe call patterns.

@Copilot Copilot AI changed the title [Blazor] Add analyzer to check ElementReference usage is only in OnAfterRenderAsync [Blazor] Add analyzer to detect ElementReference usage outside OnAfterRender with helper method analysis Aug 14, 2025
Copilot finished work on behalf of javiercn August 14, 2025 08:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Blazor] Analyzer should check that Element reference instances are only ever read within OnAfterRenderAsync
2 participants