Skip to content

Conversation

chsienki
Copy link
Member

Adds a suppressor for CS8618 when a component property has EditorRequired attached to it.

@chsienki chsienki added the area-compiler Umbrella for all compiler issues label Mar 10, 2025
@chsienki chsienki requested a review from a team as a code owner March 10, 2025 21:18
- Use symbols rather than syntax
- Only report if both parameter and editor required are on the property
- Thread through the cancellation token
@chsienki
Copy link
Member Author

Ok, updated to use symbols. Will work on adding some tests if we're happy with this approach.

Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think this is in a good enough place to go for the tests.

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BOM?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it was missing the BOM and this adds it back.

Comment on lines 37 to 38
if (attributes.Any(ad => SymbolEqualityComparer.Default.Equals(ad.AttributeClass, parameterSymbol))
&& attributes.Any(ad => SymbolEqualityComparer.Default.Equals(ad.AttributeClass, editorRequiredSymbol)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: cute syntax, but potentially loops through the list twice. Maybe a foreach would be better 🙂.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. But also lol:

image

using System;
using Microsoft.AspNetCore.Components;
public class MyComponent
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test makes me wonder. Should we be checking that these attributes are being used in something that implements IComponent?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's required, consider this example:

abstract class B
{
    [Parameter, EditorRequired] string Param { get; set; }
}

class C : B, IComponent;

B doesn't implement IComponent but can still be instantiated as a component through C.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, for this type of edge case, I'd prefer to have false positives, rather than false negatives (iow, I think we should check for implementing IComponent). If we get feedback that this case is still being hit and causing pain, then we can take another look at it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, lets keep it tightly scoped for now, and we can expand it later if we get feedback on it

using System;
using Microsoft.AspNetCore.Components;
public class MyComponent
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's required, consider this example:

abstract class B
{
    [Parameter, EditorRequired] string Param { get; set; }
}

class C : B, IComponent;

B doesn't implement IComponent but can still be instantiated as a component through C.


public override void ReportSuppressions(SuppressionAnalysisContext context)
{
var editorRequiredSymbol = context.Compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Components.EditorRequiredAttribute");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple Qs:

  • Should we bail out of the analyzer here if the value is null?
  • This will fail in the face of multiple definitions of this type. Not against that but want to make sure we clearly decided this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added an early return if we can't find the types, and a test to show behavior in the presence of duplicate attributes.

public override void ReportSuppressions(SuppressionAnalysisContext context)
{
var editorRequiredSymbol = context.Compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Components.EditorRequiredAttribute");
var parameterSymbol = context.Compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Components.ParameterAttribute");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we bail out if this is null?

public class MyComponent : ComponentBase
{
[Parameter, EditorRequired]
public string MyParameter { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider also testing string?.

}

[Theory]
[InlineData("init;")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the runtime supports init parameters just fine, so perhaps we should suppress the warning for them as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it turns out this already supported init properties, this test just wasn't explicitly checking IsSuppressed(false) so it was incorrectly passing. I've added an explicit suppress check for all the instances where it should be false and split init out into its own passing test.

}

// containing type implements IComponent
if (!symbol.ContainingType.AllInterfaces.Any(@interface => @interface.Equals(componentSymbol, SymbolEqualityComparer.Default)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: not sure if we have an overload like this available

Suggested change
if (!symbol.ContainingType.AllInterfaces.Any(@interface => @interface.Equals(componentSymbol, SymbolEqualityComparer.Default)))
if (!symbol.ContainingType.AllInterfaces.Any(static (@interface, componentSymbol) => @interface.Equals(componentSymbol, SymbolEqualityComparer.Default), componentSymbol))

chsienki and others added 2 commits March 26, 2025 12:13
…rs/ComponentParameterNullableWarningSuppressor.cs

Co-authored-by: Fred Silberberg <[email protected]>
@chsienki chsienki enabled auto-merge (squash) March 26, 2025 19:19
@chsienki chsienki merged commit c598cfa into dotnet:main Mar 26, 2025
12 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Mar 26, 2025
jjonescz added a commit that referenced this pull request Mar 27, 2025
@jjonescz jjonescz modified the milestones: Next, 17.14 P3 Apr 1, 2025
@omccully
Copy link

@chsienki If the component has a constructor, CS8618 puts the warning on the constructor rather than on the property. It seems this suppressor does not work as it should when components have a constructor defined.

Also, I would still love to see this suppressor for [Inject] as well. Is that still planned?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-compiler Umbrella for all compiler issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants