Add analyzer to warn for invalid Component parameter usage.
- The newly added analyzer warns users if they try to assign another components parameter.It does sanity checks to ensure that 1. The property reference is indeed a component parameter 2. The property reference is from a component 3. The assignment is outside of the parameters type hierarchy. Aka, we don't warn users for setting a components parameter if it's in the same class. - Updated existing `ComponentsFacts` to add additional utility methods to properly interact with components. - Added tests to ensure we're analyzing all the goods properly. #8825
This commit is contained in:
parent
47eb53492c
commit
cae31a55f9
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Analyzers
|
namespace Microsoft.AspNetCore.Components.Analyzers
|
||||||
{
|
{
|
||||||
|
|
@ -85,5 +86,26 @@ namespace Microsoft.AspNetCore.Components.Analyzers
|
||||||
|
|
||||||
return property.GetAttributes().Any(a => a.AttributeClass == symbols.CascadingParameterAttribute);
|
return property.GetAttributes().Any(a => a.AttributeClass == symbols.CascadingParameterAttribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsComponent(ComponentSymbols symbols, Compilation compilation, INamedTypeSymbol type)
|
||||||
|
{
|
||||||
|
if (symbols is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(symbols));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
var conversion = compilation.ClassifyConversion(symbols.IComponentType, type);
|
||||||
|
if (!conversion.Exists || !conversion.IsExplicit)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
using Microsoft.CodeAnalysis.Operations;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Analyzers
|
||||||
|
{
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class ComponentParameterUsageAnalyzer : DiagnosticAnalyzer
|
||||||
|
{
|
||||||
|
public ComponentParameterUsageAnalyzer()
|
||||||
|
{
|
||||||
|
SupportedDiagnostics = ImmutableArray.Create(new[]
|
||||||
|
{
|
||||||
|
DiagnosticDescriptors.ComponentParametersShouldNotBeSetOutsideOfTheirDeclaredComponent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
|
||||||
|
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
|
||||||
|
context.RegisterCompilationStartAction(context =>
|
||||||
|
{
|
||||||
|
if (!ComponentSymbols.TryCreate(context.Compilation, out var symbols))
|
||||||
|
{
|
||||||
|
// Types we need are not defined.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.RegisterOperationBlockStartAction(startBlockContext =>
|
||||||
|
{
|
||||||
|
startBlockContext.RegisterOperationAction(context =>
|
||||||
|
{
|
||||||
|
var assignmentOperation = (IAssignmentOperation)context.Operation;
|
||||||
|
var leftHandSide = assignmentOperation.Target;
|
||||||
|
if (leftHandSide == null)
|
||||||
|
{
|
||||||
|
// Malformed assignment, no left hand side.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leftHandSide.Kind != OperationKind.PropertyReference)
|
||||||
|
{
|
||||||
|
// We don't want to capture situations where a user does
|
||||||
|
// MyOtherProperty = aComponent.SomeParameter
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var propertyReference = (IPropertyReferenceOperation)leftHandSide;
|
||||||
|
var componentProperty = (IPropertySymbol)propertyReference.Member;
|
||||||
|
|
||||||
|
if (!ComponentFacts.IsParameter(symbols, componentProperty))
|
||||||
|
{
|
||||||
|
// This is not a property reference that we care about, it is not decorated with [Parameter].
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var propertyContainingType = componentProperty.ContainingType;
|
||||||
|
if (!ComponentFacts.IsComponent(symbols, context.Compilation, propertyContainingType))
|
||||||
|
{
|
||||||
|
// Someone referenced a property as [Parameter] inside something that is not a component.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var assignmentContainingType = startBlockContext.OwningSymbol?.ContainingType;
|
||||||
|
if (assignmentContainingType == null)
|
||||||
|
{
|
||||||
|
// Assignment location has no containing type. Most likely we're operating on malformed code, don't try and validate.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var conversion = context.Compilation.ClassifyConversion(propertyContainingType, assignmentContainingType);
|
||||||
|
if (conversion.Exists && conversion.IsIdentity)
|
||||||
|
{
|
||||||
|
// The assignment is taking place inside of the declaring component.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conversion.Exists && conversion.IsExplicit)
|
||||||
|
{
|
||||||
|
// The assignment is taking place within the components type hierarchy. This means the user is setting this in a supported
|
||||||
|
// scenario.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point the user is referencing a component parameter outside of its declaring class.
|
||||||
|
|
||||||
|
context.ReportDiagnostic(Diagnostic.Create(
|
||||||
|
DiagnosticDescriptors.ComponentParametersShouldNotBeSetOutsideOfTheirDeclaredComponent,
|
||||||
|
propertyReference.Syntax.GetLocation(),
|
||||||
|
propertyReference.Member.Name));
|
||||||
|
}, OperationKind.SimpleAssignment, OperationKind.CompoundAssignment, OperationKind.CoalesceAssignment);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Analyzers
|
namespace Microsoft.AspNetCore.Components.Analyzers
|
||||||
|
|
@ -30,6 +29,13 @@ namespace Microsoft.AspNetCore.Components.Analyzers
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var icomponentType = compilation.GetTypeByMetadataName(ComponentsApi.IComponent.MetadataName);
|
||||||
|
if (icomponentType == null)
|
||||||
|
{
|
||||||
|
symbols = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var dictionary = compilation.GetTypeByMetadataName("System.Collections.Generic.Dictionary`2");
|
var dictionary = compilation.GetTypeByMetadataName("System.Collections.Generic.Dictionary`2");
|
||||||
var @string = compilation.GetSpecialType(SpecialType.System_String);
|
var @string = compilation.GetSpecialType(SpecialType.System_String);
|
||||||
var @object = compilation.GetSpecialType(SpecialType.System_Object);
|
var @object = compilation.GetSpecialType(SpecialType.System_Object);
|
||||||
|
|
@ -41,18 +47,24 @@ namespace Microsoft.AspNetCore.Components.Analyzers
|
||||||
|
|
||||||
var parameterCaptureUnmatchedValuesRuntimeType = dictionary.Construct(@string, @object);
|
var parameterCaptureUnmatchedValuesRuntimeType = dictionary.Construct(@string, @object);
|
||||||
|
|
||||||
symbols = new ComponentSymbols(parameterAttribute, cascadingParameterAttribute, parameterCaptureUnmatchedValuesRuntimeType);
|
symbols = new ComponentSymbols(
|
||||||
|
parameterAttribute,
|
||||||
|
cascadingParameterAttribute,
|
||||||
|
parameterCaptureUnmatchedValuesRuntimeType,
|
||||||
|
icomponentType);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ComponentSymbols(
|
private ComponentSymbols(
|
||||||
INamedTypeSymbol parameterAttribute,
|
INamedTypeSymbol parameterAttribute,
|
||||||
INamedTypeSymbol cascadingParameterAttribute,
|
INamedTypeSymbol cascadingParameterAttribute,
|
||||||
INamedTypeSymbol parameterCaptureUnmatchedValuesRuntimeType)
|
INamedTypeSymbol parameterCaptureUnmatchedValuesRuntimeType,
|
||||||
|
INamedTypeSymbol icomponentType)
|
||||||
{
|
{
|
||||||
ParameterAttribute = parameterAttribute;
|
ParameterAttribute = parameterAttribute;
|
||||||
CascadingParameterAttribute = cascadingParameterAttribute;
|
CascadingParameterAttribute = cascadingParameterAttribute;
|
||||||
ParameterCaptureUnmatchedValuesRuntimeType = parameterCaptureUnmatchedValuesRuntimeType;
|
ParameterCaptureUnmatchedValuesRuntimeType = parameterCaptureUnmatchedValuesRuntimeType;
|
||||||
|
IComponentType = icomponentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public INamedTypeSymbol ParameterAttribute { get; }
|
public INamedTypeSymbol ParameterAttribute { get; }
|
||||||
|
|
@ -61,5 +73,7 @@ namespace Microsoft.AspNetCore.Components.Analyzers
|
||||||
public INamedTypeSymbol ParameterCaptureUnmatchedValuesRuntimeType { get; }
|
public INamedTypeSymbol ParameterCaptureUnmatchedValuesRuntimeType { get; }
|
||||||
|
|
||||||
public INamedTypeSymbol CascadingParameterAttribute { get; }
|
public INamedTypeSymbol CascadingParameterAttribute { get; }
|
||||||
|
|
||||||
|
public INamedTypeSymbol IComponentType { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,5 +22,11 @@ namespace Microsoft.AspNetCore.Components.Analyzers
|
||||||
public static readonly string FullTypeName = "Microsoft.AspNetCore.Components.CascadingParameterAttribute";
|
public static readonly string FullTypeName = "Microsoft.AspNetCore.Components.CascadingParameterAttribute";
|
||||||
public static readonly string MetadataName = FullTypeName;
|
public static readonly string MetadataName = FullTypeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class IComponent
|
||||||
|
{
|
||||||
|
public static readonly string FullTypeName = "Microsoft.AspNetCore.Components.IComponent";
|
||||||
|
public static readonly string MetadataName = FullTypeName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,5 +46,14 @@ namespace Microsoft.AspNetCore.Components.Analyzers
|
||||||
DiagnosticSeverity.Warning,
|
DiagnosticSeverity.Warning,
|
||||||
isEnabledByDefault: true,
|
isEnabledByDefault: true,
|
||||||
description: new LocalizableResourceString(nameof(Resources.ComponentParametersShouldBePublic_Description), Resources.ResourceManager, typeof(Resources)));
|
description: new LocalizableResourceString(nameof(Resources.ComponentParametersShouldBePublic_Description), Resources.ResourceManager, typeof(Resources)));
|
||||||
|
|
||||||
|
public static readonly DiagnosticDescriptor ComponentParametersShouldNotBeSetOutsideOfTheirDeclaredComponent = new DiagnosticDescriptor(
|
||||||
|
"BL0005",
|
||||||
|
new LocalizableResourceString(nameof(Resources.ComponentParameterShouldNotBeSetOutsideOfTheirDeclaredComponent_Title), Resources.ResourceManager, typeof(Resources)),
|
||||||
|
new LocalizableResourceString(nameof(Resources.ComponentParameterShouldNotBeSetOutsideOfTheirDeclaredComponent_Format), Resources.ResourceManager, typeof(Resources)),
|
||||||
|
"Usage",
|
||||||
|
DiagnosticSeverity.Warning,
|
||||||
|
isEnabledByDefault: true,
|
||||||
|
description: new LocalizableResourceString(nameof(Resources.ComponentParameterShouldNotBeSetOutsideOfTheirDeclaredComponent_Description), Resources.ResourceManager, typeof(Resources)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,33 @@ namespace Microsoft.AspNetCore.Components.Analyzers {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Component parameters should not be set outside of their declared component. Doing so may result in unexpected behavior at runtime..
|
||||||
|
/// </summary>
|
||||||
|
internal static string ComponentParameterShouldNotBeSetOutsideOfTheirDeclaredComponent_Description {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ComponentParameterShouldNotBeSetOutsideOfTheirDeclaredComponent_Description", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Component parameter '{0}' should not be set outside of its component..
|
||||||
|
/// </summary>
|
||||||
|
internal static string ComponentParameterShouldNotBeSetOutsideOfTheirDeclaredComponent_Format {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ComponentParameterShouldNotBeSetOutsideOfTheirDeclaredComponent_Format", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Component parameter should not be set outside of its component..
|
||||||
|
/// </summary>
|
||||||
|
internal static string ComponentParameterShouldNotBeSetOutsideOfTheirDeclaredComponent_Title {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ComponentParameterShouldNotBeSetOutsideOfTheirDeclaredComponent_Title", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Component parameters should be public..
|
/// Looks up a localized string similar to Component parameters should be public..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -156,4 +156,13 @@
|
||||||
<data name="ComponentParametersShouldBePublic_FixTitle" xml:space="preserve">
|
<data name="ComponentParametersShouldBePublic_FixTitle" xml:space="preserve">
|
||||||
<value>Make component parameters public.</value>
|
<value>Make component parameters public.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ComponentParameterShouldNotBeSetOutsideOfTheirDeclaredComponent_Description" xml:space="preserve">
|
||||||
|
<value>Component parameters should not be set outside of their declared component. Doing so may result in unexpected behavior at runtime.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ComponentParameterShouldNotBeSetOutsideOfTheirDeclaredComponent_Format" xml:space="preserve">
|
||||||
|
<value>Component parameter '{0}' should not be set outside of its component.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ComponentParameterShouldNotBeSetOutsideOfTheirDeclaredComponent_Title" xml:space="preserve">
|
||||||
|
<value>Component parameter should not be set outside of its component.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -0,0 +1,298 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
using TestHelper;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Analyzers
|
||||||
|
{
|
||||||
|
public class ComponentParameterUsageAnalyzerTest : DiagnosticVerifier
|
||||||
|
{
|
||||||
|
public ComponentParameterUsageAnalyzerTest()
|
||||||
|
{
|
||||||
|
ComponentTestSource = $@"
|
||||||
|
namespace ConsoleApplication1
|
||||||
|
{{
|
||||||
|
using {typeof(ParameterAttribute).Namespace};
|
||||||
|
class TestComponent : IComponent
|
||||||
|
{{
|
||||||
|
[Parameter] public string TestProperty {{ get; set; }}
|
||||||
|
public string NonParameter {{ get; set; }}
|
||||||
|
}}
|
||||||
|
}}" + ComponentsTestDeclarations.Source;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ComponentTestSource { get; }
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ComponentPropertySimpleAssignment_Warns()
|
||||||
|
{
|
||||||
|
var test = $@"
|
||||||
|
namespace ConsoleApplication1
|
||||||
|
{{
|
||||||
|
using {typeof(ParameterAttribute).Namespace};
|
||||||
|
class OtherComponent : IComponent
|
||||||
|
{{
|
||||||
|
private TestComponent _testComponent;
|
||||||
|
void Render()
|
||||||
|
{{
|
||||||
|
_testComponent = new TestComponent();
|
||||||
|
_testComponent.TestProperty = ""Hello World"";
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}" + ComponentTestSource;
|
||||||
|
|
||||||
|
VerifyCSharpDiagnostic(test,
|
||||||
|
new DiagnosticResult
|
||||||
|
{
|
||||||
|
Id = DiagnosticDescriptors.ComponentParametersShouldNotBeSetOutsideOfTheirDeclaredComponent.Id,
|
||||||
|
Message = "Component parameter 'TestProperty' should not be set outside of its component.",
|
||||||
|
Severity = DiagnosticSeverity.Warning,
|
||||||
|
Locations = new[]
|
||||||
|
{
|
||||||
|
new DiagnosticResultLocation("Test0.cs", 11, 17)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ComponentPropertyCoalesceAssignment__Warns()
|
||||||
|
{
|
||||||
|
var test = $@"
|
||||||
|
namespace ConsoleApplication1
|
||||||
|
{{
|
||||||
|
using {typeof(ParameterAttribute).Namespace};
|
||||||
|
class OtherComponent : IComponent
|
||||||
|
{{
|
||||||
|
private TestComponent _testComponent;
|
||||||
|
void Render()
|
||||||
|
{{
|
||||||
|
_testComponent = new TestComponent();
|
||||||
|
_testComponent.TestProperty ??= ""Hello World"";
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}" + ComponentTestSource;
|
||||||
|
|
||||||
|
VerifyCSharpDiagnostic(test,
|
||||||
|
new DiagnosticResult
|
||||||
|
{
|
||||||
|
Id = DiagnosticDescriptors.ComponentParametersShouldNotBeSetOutsideOfTheirDeclaredComponent.Id,
|
||||||
|
Message = "Component parameter 'TestProperty' should not be set outside of its component.",
|
||||||
|
Severity = DiagnosticSeverity.Warning,
|
||||||
|
Locations = new[]
|
||||||
|
{
|
||||||
|
new DiagnosticResultLocation("Test0.cs", 11, 17)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ComponentPropertyCompoundAssignment__Warns()
|
||||||
|
{
|
||||||
|
var test = $@"
|
||||||
|
namespace ConsoleApplication1
|
||||||
|
{{
|
||||||
|
using {typeof(ParameterAttribute).Namespace};
|
||||||
|
class OtherComponent : IComponent
|
||||||
|
{{
|
||||||
|
private TestComponent _testComponent;
|
||||||
|
void Render()
|
||||||
|
{{
|
||||||
|
_testComponent = new TestComponent();
|
||||||
|
_testComponent.TestProperty += ""Hello World"";
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}" + ComponentTestSource;
|
||||||
|
|
||||||
|
VerifyCSharpDiagnostic(test,
|
||||||
|
new DiagnosticResult
|
||||||
|
{
|
||||||
|
Id = DiagnosticDescriptors.ComponentParametersShouldNotBeSetOutsideOfTheirDeclaredComponent.Id,
|
||||||
|
Message = "Component parameter 'TestProperty' should not be set outside of its component.",
|
||||||
|
Severity = DiagnosticSeverity.Warning,
|
||||||
|
Locations = new[]
|
||||||
|
{
|
||||||
|
new DiagnosticResultLocation("Test0.cs", 11, 17)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ComponentPropertyExpression_Ignores()
|
||||||
|
{
|
||||||
|
var test = $@"
|
||||||
|
namespace ConsoleApplication1
|
||||||
|
{{
|
||||||
|
using {typeof(ParameterAttribute).Namespace};
|
||||||
|
class TypeName
|
||||||
|
{{
|
||||||
|
void Method()
|
||||||
|
{{
|
||||||
|
System.IO.Console.WriteLine(new TestComponent().TestProperty);
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}" + ComponentTestSource;
|
||||||
|
|
||||||
|
VerifyCSharpDiagnostic(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ComponentPropertyExpressionInStatement_Ignores()
|
||||||
|
{
|
||||||
|
var test = $@"
|
||||||
|
namespace ConsoleApplication1
|
||||||
|
{{
|
||||||
|
using {typeof(ParameterAttribute).Namespace};
|
||||||
|
class TypeName
|
||||||
|
{{
|
||||||
|
void Method()
|
||||||
|
{{
|
||||||
|
var testComponent = new TestComponent();
|
||||||
|
for (var i = 0; i < testComponent.TestProperty.Length; i++)
|
||||||
|
{{
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}" + ComponentTestSource;
|
||||||
|
|
||||||
|
VerifyCSharpDiagnostic(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RetrievalOfComponentPropertyValueInAssignment_Ignores()
|
||||||
|
{
|
||||||
|
var test = $@"
|
||||||
|
namespace ConsoleApplication1
|
||||||
|
{{
|
||||||
|
using {typeof(ParameterAttribute).Namespace};
|
||||||
|
class TypeName
|
||||||
|
{{
|
||||||
|
void Method()
|
||||||
|
{{
|
||||||
|
var testComponent = new TestComponent();
|
||||||
|
AnotherProperty = testComponent.TestProperty;
|
||||||
|
}}
|
||||||
|
|
||||||
|
public string AnotherProperty {{ get; set; }}
|
||||||
|
}}
|
||||||
|
}}" + ComponentTestSource;
|
||||||
|
|
||||||
|
VerifyCSharpDiagnostic(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ShadowedComponentPropertyAssignment_Ignores()
|
||||||
|
{
|
||||||
|
var test = $@"
|
||||||
|
namespace ConsoleApplication1
|
||||||
|
{{
|
||||||
|
using {typeof(ParameterAttribute).Namespace};
|
||||||
|
class TypeName
|
||||||
|
{{
|
||||||
|
void Method()
|
||||||
|
{{
|
||||||
|
var testComponent = new InheritedComponent();
|
||||||
|
testComponent.TestProperty = ""Hello World"";
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
|
||||||
|
class InheritedComponent : TestComponent
|
||||||
|
{{
|
||||||
|
public new string TestProperty {{ get; set; }}
|
||||||
|
}}
|
||||||
|
}}" + ComponentTestSource;
|
||||||
|
|
||||||
|
VerifyCSharpDiagnostic(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InheritedImplicitComponentPropertyAssignment_Ignores()
|
||||||
|
{
|
||||||
|
var test = $@"
|
||||||
|
namespace ConsoleApplication1
|
||||||
|
{{
|
||||||
|
using {typeof(ParameterAttribute).Namespace};
|
||||||
|
class TypeName : TestComponent
|
||||||
|
{{
|
||||||
|
void Method()
|
||||||
|
{{
|
||||||
|
this.TestProperty = ""Hello World"";
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}" + ComponentTestSource;
|
||||||
|
|
||||||
|
VerifyCSharpDiagnostic(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ImplicitComponentPropertyAssignment_Ignores()
|
||||||
|
{
|
||||||
|
var test = $@"
|
||||||
|
namespace ConsoleApplication1
|
||||||
|
{{
|
||||||
|
using {typeof(ParameterAttribute).Namespace};
|
||||||
|
class TypeName : IComponent
|
||||||
|
{{
|
||||||
|
void Method()
|
||||||
|
{{
|
||||||
|
TestProperty = ""Hello World"";
|
||||||
|
}}
|
||||||
|
|
||||||
|
[Parameter] public string TestProperty {{ get; set; }}
|
||||||
|
}}
|
||||||
|
}}" + ComponentTestSource;
|
||||||
|
|
||||||
|
VerifyCSharpDiagnostic(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ComponentPropertyAssignment_NonParameter_Ignores()
|
||||||
|
{
|
||||||
|
var test = $@"
|
||||||
|
namespace ConsoleApplication1
|
||||||
|
{{
|
||||||
|
using {typeof(ParameterAttribute).Namespace};
|
||||||
|
class OtherComponent : IComponent
|
||||||
|
{{
|
||||||
|
private TestComponent _testComponent;
|
||||||
|
void Render()
|
||||||
|
{{
|
||||||
|
_testComponent = new TestComponent();
|
||||||
|
_testComponent.NonParameter = ""Hello World"";
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}" + ComponentTestSource;
|
||||||
|
|
||||||
|
VerifyCSharpDiagnostic(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NonComponentPropertyAssignment_Ignores()
|
||||||
|
{
|
||||||
|
var test = $@"
|
||||||
|
namespace ConsoleApplication1
|
||||||
|
{{
|
||||||
|
using {typeof(ParameterAttribute).Namespace};
|
||||||
|
class OtherComponent : IComponent
|
||||||
|
{{
|
||||||
|
private SomethingElse _testNonComponent;
|
||||||
|
void Render()
|
||||||
|
{{
|
||||||
|
_testNonComponent = new NotAComponent();
|
||||||
|
_testNonComponent.TestProperty = ""Hello World"";
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
class NotAComponent
|
||||||
|
{{
|
||||||
|
[Parameter] public string TestProperty {{ get; set; }}
|
||||||
|
}}
|
||||||
|
}}" + ComponentTestSource;
|
||||||
|
|
||||||
|
VerifyCSharpDiagnostic(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() => new ComponentParameterUsageAnalyzer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,10 @@ namespace Microsoft.AspNetCore.Components.Analyzers
|
||||||
public class {typeof(CascadingParameterAttribute).Name} : System.Attribute
|
public class {typeof(CascadingParameterAttribute).Name} : System.Attribute
|
||||||
{{
|
{{
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
public interface {typeof(IComponent).Name}
|
||||||
|
{{
|
||||||
|
}}
|
||||||
}}
|
}}
|
||||||
";
|
";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue