Add analyzer to expect public properties for component parameters.

- If we find a parameter that is non-public we will create a warning for that situation and notify the user.
- Updated the naming of existing properties and classes that referred to public/private required properties to be specific to those properties setters (that's what they verified before these changes).
- Updated test expectations and names.
- Changed the code fix provider to no longer worry about the property setters and instead inspect the properties declared accessibility. Did not re-add the property setter code fix provider because these code fix providers do nothing in practice (Razor light bulbs aren't enabled in the editor).
- Added new tests.

#8825
This commit is contained in:
N. Taylor Mullen 2019-07-03 16:07:26 -07:00
parent e31813b9cd
commit b647a223f7
10 changed files with 404 additions and 119 deletions

View File

@ -18,7 +18,8 @@ namespace Microsoft.AspNetCore.Components.Analyzers
{ {
SupportedDiagnostics = ImmutableArray.Create(new[] SupportedDiagnostics = ImmutableArray.Create(new[]
{ {
DiagnosticDescriptors.ComponentParametersShouldNotBePublic, DiagnosticDescriptors.ComponentParametersShouldBePublic,
DiagnosticDescriptors.ComponentParameterSettersShouldBePublic,
DiagnosticDescriptors.ComponentParameterCaptureUnmatchedValuesMustBeUnique, DiagnosticDescriptors.ComponentParameterCaptureUnmatchedValuesMustBeUnique,
DiagnosticDescriptors.ComponentParameterCaptureUnmatchedValuesHasWrongType, DiagnosticDescriptors.ComponentParameterCaptureUnmatchedValuesHasWrongType,
}); });
@ -61,14 +62,27 @@ namespace Microsoft.AspNetCore.Components.Analyzers
{ {
var captureUnmatchedValuesParameters = new List<IPropertySymbol>(); var captureUnmatchedValuesParameters = new List<IPropertySymbol>();
// Per-property validations // Per-property validations
foreach (var property in properties) foreach (var property in properties)
{ {
if (property.SetMethod?.DeclaredAccessibility == Accessibility.Public) var propertyLocation = property.Locations.FirstOrDefault();
if (propertyLocation == null)
{
continue;
}
if (property.DeclaredAccessibility != Accessibility.Public)
{ {
context.ReportDiagnostic(Diagnostic.Create( context.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.ComponentParametersShouldNotBePublic, DiagnosticDescriptors.ComponentParametersShouldBePublic,
property.Locations[0], propertyLocation,
property.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
}
else if (property.SetMethod?.DeclaredAccessibility != Accessibility.Public)
{
context.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.ComponentParameterSettersShouldBePublic,
propertyLocation,
property.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat))); property.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
} }
@ -76,13 +90,13 @@ namespace Microsoft.AspNetCore.Components.Analyzers
{ {
captureUnmatchedValuesParameters.Add(property); captureUnmatchedValuesParameters.Add(property);
// Check the type, we need to be able to assign a Dictionary<string, object> // Check the type, we need to be able to assign a Dictionary<string, object>
var conversion = context.Compilation.ClassifyConversion(symbols.ParameterCaptureUnmatchedValuesRuntimeType, property.Type); var conversion = context.Compilation.ClassifyConversion(symbols.ParameterCaptureUnmatchedValuesRuntimeType, property.Type);
if (!conversion.Exists || conversion.IsExplicit) if (!conversion.Exists || conversion.IsExplicit)
{ {
context.ReportDiagnostic(Diagnostic.Create( context.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.ComponentParameterCaptureUnmatchedValuesHasWrongType, DiagnosticDescriptors.ComponentParameterCaptureUnmatchedValuesHasWrongType,
property.Locations[0], propertyLocation,
property.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat), property.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
property.Type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat), property.Type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
symbols.ParameterCaptureUnmatchedValuesRuntimeType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat))); symbols.ParameterCaptureUnmatchedValuesRuntimeType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
@ -90,9 +104,9 @@ namespace Microsoft.AspNetCore.Components.Analyzers
} }
} }
// Check if the type defines multiple CaptureUnmatchedValues parameters. Doing this outside the loop means we place the // Check if the type defines multiple CaptureUnmatchedValues parameters. Doing this outside the loop means we place the
// errors on the type. // errors on the type.
if (captureUnmatchedValuesParameters.Count > 1) if (captureUnmatchedValuesParameters.Count > 1)
{ {
context.ReportDiagnostic(Diagnostic.Create( context.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.ComponentParameterCaptureUnmatchedValuesMustBeUnique, DiagnosticDescriptors.ComponentParameterCaptureUnmatchedValuesMustBeUnique,

View File

@ -13,13 +13,13 @@ using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Microsoft.AspNetCore.Components.Analyzers namespace Microsoft.AspNetCore.Components.Analyzers
{ {
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(ComponentParametersShouldNotBePublicCodeFixProvider)), Shared] [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(ComponentParametersShouldBePublicCodeFixProvider)), Shared]
public class ComponentParametersShouldNotBePublicCodeFixProvider : CodeFixProvider public class ComponentParametersShouldBePublicCodeFixProvider : CodeFixProvider
{ {
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.ComponentParametersShouldNotBePublic_FixTitle), Resources.ResourceManager, typeof(Resources)); private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.ComponentParametersShouldBePublic_FixTitle), Resources.ResourceManager, typeof(Resources));
public override ImmutableArray<string> FixableDiagnosticIds public override ImmutableArray<string> FixableDiagnosticIds
=> ImmutableArray.Create(DiagnosticDescriptors.ComponentParametersShouldNotBePublic.Id); => ImmutableArray.Create(DiagnosticDescriptors.ComponentParametersShouldBePublic.Id);
public sealed override FixAllProvider GetFixAllProvider() public sealed override FixAllProvider GetFixAllProvider()
{ {
@ -64,9 +64,24 @@ namespace Microsoft.AspNetCore.Components.Analyzers
return null; return null;
} }
var publicModifier = node.Modifiers.FirstOrDefault(m => m.IsKind(SyntaxKind.PublicKeyword)); var newModifiers = node.Modifiers;
node = node.WithModifiers( for (var i = 0; i < node.Modifiers.Count; i++)
node.Modifiers.Remove(publicModifier)); {
var modifier = node.Modifiers[i];
if (modifier.IsKind(SyntaxKind.PrivateKeyword) ||
modifier.IsKind(SyntaxKind.ProtectedKeyword) ||
modifier.IsKind(SyntaxKind.InternalKeyword) ||
// We also remove public in case the user has written something totally backwards such as private public protected Foo
modifier.IsKind(SyntaxKind.PublicKeyword))
{
newModifiers = newModifiers.Remove(modifier);
}
}
var publicModifier = SyntaxFactory.Token(SyntaxKind.PublicKeyword);
newModifiers = newModifiers.Insert(0, publicModifier);
node = node.WithModifiers(newModifiers);
return node; return node;
} }
} }

View File

@ -11,14 +11,14 @@ namespace Microsoft.AspNetCore.Components.Analyzers
// no change of clashing between that and the BL prefix used here. // no change of clashing between that and the BL prefix used here.
// //
// Tracking https://github.com/aspnet/AspNetCore/issues/10382 to rationalize this // Tracking https://github.com/aspnet/AspNetCore/issues/10382 to rationalize this
public static readonly DiagnosticDescriptor ComponentParametersShouldNotBePublic = new DiagnosticDescriptor( public static readonly DiagnosticDescriptor ComponentParameterSettersShouldBePublic = new DiagnosticDescriptor(
"BL0001", "BL0001",
new LocalizableResourceString(nameof(Resources.ComponentParametersShouldNotBePublic_Title), Resources.ResourceManager, typeof(Resources)), new LocalizableResourceString(nameof(Resources.ComponentParameterSettersShouldBePublic_Title), Resources.ResourceManager, typeof(Resources)),
new LocalizableResourceString(nameof(Resources.ComponentParametersShouldNotBePublic_Format), Resources.ResourceManager, typeof(Resources)), new LocalizableResourceString(nameof(Resources.ComponentParameterSettersShouldBePublic_Format), Resources.ResourceManager, typeof(Resources)),
"Encapsulation", "Encapsulation",
DiagnosticSeverity.Warning, DiagnosticSeverity.Warning,
isEnabledByDefault: true, isEnabledByDefault: true,
description: new LocalizableResourceString(nameof(Resources.ComponentParametersShouldNotBePublic_Description), Resources.ResourceManager, typeof(Resources))); description: new LocalizableResourceString(nameof(Resources.ComponentParameterSettersShouldBePublic_Description), Resources.ResourceManager, typeof(Resources)));
public static readonly DiagnosticDescriptor ComponentParameterCaptureUnmatchedValuesMustBeUnique = new DiagnosticDescriptor( public static readonly DiagnosticDescriptor ComponentParameterCaptureUnmatchedValuesMustBeUnique = new DiagnosticDescriptor(
"BL0002", "BL0002",
@ -37,5 +37,14 @@ namespace Microsoft.AspNetCore.Components.Analyzers
DiagnosticSeverity.Warning, DiagnosticSeverity.Warning,
isEnabledByDefault: true, isEnabledByDefault: true,
description: new LocalizableResourceString(nameof(Resources.ComponentParameterCaptureUnmatchedValuesHasWrongType_Description), Resources.ResourceManager, typeof(Resources))); description: new LocalizableResourceString(nameof(Resources.ComponentParameterCaptureUnmatchedValuesHasWrongType_Description), Resources.ResourceManager, typeof(Resources)));
public static readonly DiagnosticDescriptor ComponentParametersShouldBePublic = new DiagnosticDescriptor(
"BL0004",
new LocalizableResourceString(nameof(Resources.ComponentParameterShouldBePublic_Title), Resources.ResourceManager, typeof(Resources)),
new LocalizableResourceString(nameof(Resources.ComponentParameterShouldBePublic_Format), Resources.ResourceManager, typeof(Resources)),
"Encapsulation",
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: new LocalizableResourceString(nameof(Resources.ComponentParametersShouldBePublic_Description), Resources.ResourceManager, typeof(Resources)));
} }
} }

View File

@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Components.Analyzers {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Component parameters with CaptureUnmatchedValuess must be a correct type.. /// Looks up a localized string similar to Component parameters with CaptureUnmatchedValues must be a correct type..
/// </summary> /// </summary>
internal static string ComponentParameterCaptureUnmatchedValuesHasWrongType_Description { internal static string ComponentParameterCaptureUnmatchedValuesHasWrongType_Description {
get { get {
@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.Components.Analyzers {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Component parameter &apos;{0}&apos; defines CaptureUnmatchedValuess but has an unsupported type &apos;{1}&apos;. Use a type assignable from &apos;{2}&apos;.. /// Looks up a localized string similar to Component parameter &apos;{0}&apos; defines CaptureUnmatchedValues but has an unsupported type &apos;{1}&apos;. Use a type assignable from &apos;{2}&apos;..
/// </summary> /// </summary>
internal static string ComponentParameterCaptureUnmatchedValuesHasWrongType_Format { internal static string ComponentParameterCaptureUnmatchedValuesHasWrongType_Format {
get { get {
@ -115,38 +115,65 @@ namespace Microsoft.AspNetCore.Components.Analyzers {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Component parameters should not have public setters.. /// Looks up a localized string similar to Component parameters should have public setters..
/// </summary> /// </summary>
internal static string ComponentParametersShouldNotBePublic_Description { internal static string ComponentParameterSettersShouldBePublic_Description {
get { get {
return ResourceManager.GetString("ComponentParametersShouldNotBePublic_Description", resourceCulture); return ResourceManager.GetString("ComponentParameterSettersShouldBePublic_Description", resourceCulture);
} }
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Make component parameter private. /// Looks up a localized string similar to Component parameter &apos;{0}&apos; should have a public setter..
/// </summary> /// </summary>
internal static string ComponentParametersShouldNotBePublic_FixTitle { internal static string ComponentParameterSettersShouldBePublic_Format {
get { get {
return ResourceManager.GetString("ComponentParametersShouldNotBePublic_FixTitle", resourceCulture); return ResourceManager.GetString("ComponentParameterSettersShouldBePublic_Format", resourceCulture);
} }
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Component parameter &apos;{0}&apos; has a public setter, but component parameters should not be publicly settable.. /// Looks up a localized string similar to Component parameter should have public setters..
/// </summary> /// </summary>
internal static string ComponentParametersShouldNotBePublic_Format { internal static string ComponentParameterSettersShouldBePublic_Title {
get { get {
return ResourceManager.GetString("ComponentParametersShouldNotBePublic_Format", resourceCulture); return ResourceManager.GetString("ComponentParameterSettersShouldBePublic_Title", resourceCulture);
} }
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Component parameter has public setter. /// Looks up a localized string similar to Component parameter &apos;{0}&apos; should be public..
/// </summary> /// </summary>
internal static string ComponentParametersShouldNotBePublic_Title { internal static string ComponentParameterShouldBePublic_Format {
get { get {
return ResourceManager.GetString("ComponentParametersShouldNotBePublic_Title", resourceCulture); return ResourceManager.GetString("ComponentParameterShouldBePublic_Format", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Component parameter should be public..
/// </summary>
internal static string ComponentParameterShouldBePublic_Title {
get {
return ResourceManager.GetString("ComponentParameterShouldBePublic_Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Component parameters should be public..
/// </summary>
internal static string ComponentParametersShouldBePublic_Description {
get {
return ResourceManager.GetString("ComponentParametersShouldBePublic_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Make component parameters public..
/// </summary>
internal static string ComponentParametersShouldBePublic_FixTitle {
get {
return ResourceManager.GetString("ComponentParametersShouldBePublic_FixTitle", resourceCulture);
} }
} }
} }

View File

@ -117,17 +117,14 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="ComponentParametersShouldNotBePublic_Description" xml:space="preserve"> <data name="ComponentParameterSettersShouldBePublic_Description" xml:space="preserve">
<value>Component parameters should not have public setters.</value> <value>Component parameters should have public setters.</value>
</data> </data>
<data name="ComponentParametersShouldNotBePublic_FixTitle" xml:space="preserve"> <data name="ComponentParameterSettersShouldBePublic_Format" xml:space="preserve">
<value>Make component parameter private</value> <value>Component parameter '{0}' should have a public setter.</value>
</data> </data>
<data name="ComponentParametersShouldNotBePublic_Format" xml:space="preserve"> <data name="ComponentParameterSettersShouldBePublic_Title" xml:space="preserve">
<value>Component parameter '{0}' has a public setter, but component parameters should not be publicly settable.</value> <value>Component parameter should have public setters.</value>
</data>
<data name="ComponentParametersShouldNotBePublic_Title" xml:space="preserve">
<value>Component parameter has public setter</value>
</data> </data>
<data name="ComponentParameterCaptureUnmatchedValuesMustBeUnique_Description" xml:space="preserve"> <data name="ComponentParameterCaptureUnmatchedValuesMustBeUnique_Description" xml:space="preserve">
<value>Components may only define a single parameter with CaptureUnmatchedValues.</value> <value>Components may only define a single parameter with CaptureUnmatchedValues.</value>
@ -147,4 +144,16 @@
<data name="ComponentParameterCaptureUnmatchedValuesHasWrongType_Title" xml:space="preserve"> <data name="ComponentParameterCaptureUnmatchedValuesHasWrongType_Title" xml:space="preserve">
<value>Component parameter with CaptureUnmatchedValues has the wrong type</value> <value>Component parameter with CaptureUnmatchedValues has the wrong type</value>
</data> </data>
<data name="ComponentParameterShouldBePublic_Format" xml:space="preserve">
<value>Component parameter '{0}' should be public.</value>
</data>
<data name="ComponentParameterShouldBePublic_Title" xml:space="preserve">
<value>Component parameter should be public.</value>
</data>
<data name="ComponentParametersShouldBePublic_Description" xml:space="preserve">
<value>Component parameters should be public.</value>
</data>
<data name="ComponentParametersShouldBePublic_FixTitle" xml:space="preserve">
<value>Make component parameters public.</value>
</data>
</root> </root>

View File

@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Components.Analyzers.Test
using {typeof(ParameterAttribute).Namespace}; using {typeof(ParameterAttribute).Namespace};
class TypeName class TypeName
{{ {{
[Parameter(CaptureUnmatchedValues = true)] {propertyType} MyProperty {{ get; set; }} [Parameter(CaptureUnmatchedValues = true)] public {propertyType} MyProperty {{ get; set; }}
}} }}
}}" + ComponentsTestDeclarations.Source; }}" + ComponentsTestDeclarations.Source;
@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Components.Analyzers.Test
using {typeof(ParameterAttribute).Namespace}; using {typeof(ParameterAttribute).Namespace};
class TypeName class TypeName
{{ {{
[Parameter(CaptureUnmatchedValues = false)] string MyProperty {{ get; set; }} [Parameter(CaptureUnmatchedValues = false)] public string MyProperty {{ get; set; }}
}} }}
}}" + ComponentsTestDeclarations.Source; }}" + ComponentsTestDeclarations.Source;
@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Components.Analyzers.Test
using {typeof(ParameterAttribute).Namespace}; using {typeof(ParameterAttribute).Namespace};
class TypeName class TypeName
{{ {{
[Parameter(CaptureUnmatchedValues = true)] string MyProperty {{ get; set; }} [Parameter(CaptureUnmatchedValues = true)] public string MyProperty {{ get; set; }}
}} }}
}}" + ComponentsTestDeclarations.Source; }}" + ComponentsTestDeclarations.Source;
@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Components.Analyzers.Test
Severity = DiagnosticSeverity.Warning, Severity = DiagnosticSeverity.Warning,
Locations = new[] Locations = new[]
{ {
new DiagnosticResultLocation("Test0.cs", 7, 63) new DiagnosticResultLocation("Test0.cs", 7, 70)
} }
}); });
} }

View File

@ -21,8 +21,8 @@ namespace Microsoft.AspNetCore.Components.Analyzers.Test
using {typeof(ParameterAttribute).Namespace}; using {typeof(ParameterAttribute).Namespace};
class TypeName class TypeName
{{ {{
[Parameter(CaptureUnmatchedValues = false)] string MyProperty {{ get; set; }} [Parameter(CaptureUnmatchedValues = false)] public string MyProperty {{ get; set; }}
[Parameter(CaptureUnmatchedValues = true)] Dictionary<string, object> MyOtherProperty {{ get; set; }} [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> MyOtherProperty {{ get; set; }}
}} }}
}}" + ComponentsTestDeclarations.Source; }}" + ComponentsTestDeclarations.Source;
@ -39,8 +39,8 @@ namespace Microsoft.AspNetCore.Components.Analyzers.Test
using {typeof(ParameterAttribute).Namespace}; using {typeof(ParameterAttribute).Namespace};
class TypeName class TypeName
{{ {{
[Parameter(CaptureUnmatchedValues = true)] Dictionary<string, object> MyProperty {{ get; set; }} [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> MyProperty {{ get; set; }}
[Parameter(CaptureUnmatchedValues = true)] Dictionary<string, object> MyOtherProperty {{ get; set; }} [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> MyOtherProperty {{ get; set; }}
}} }}
}}" + ComponentsTestDeclarations.Source; }}" + ComponentsTestDeclarations.Source;

View File

@ -0,0 +1,95 @@
// 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 ComponentParameterSettersShouldBePublicTest : DiagnosticVerifier
{
[Fact]
public void IgnoresPublicSettersProperties()
{
var test = $@"
namespace ConsoleApplication1
{{
using {typeof(ParameterAttribute).Namespace};
class TypeName
{{
[Parameter] public string MyProperty {{ get; set; }}
}}
}}" + ComponentsTestDeclarations.Source;
VerifyCSharpDiagnostic(test);
}
[Fact]
public void IgnoresPrivateSettersNonParameterProperties()
{
var test = $@"
namespace ConsoleApplication1
{{
using {typeof(ParameterAttribute).Namespace};
class TypeName
{{
private string MyProperty {{ get; private set; }}
}}
}}" + ComponentsTestDeclarations.Source;
VerifyCSharpDiagnostic(test);
}
[Fact]
public void ErrorsForNonPublicSetterParameters()
{
var test = $@"
namespace ConsoleApplication1
{{
using {typeof(ParameterAttribute).Namespace};
class TypeName
{{
[Parameter] public string MyProperty1 {{ get; private set; }}
[Parameter] public string MyProperty2 {{ get; protected set; }}
[Parameter] public string MyProperty3 {{ get; internal set; }}
}}
}}" + ComponentsTestDeclarations.Source;
VerifyCSharpDiagnostic(test,
new DiagnosticResult
{
Id = DiagnosticDescriptors.ComponentParameterSettersShouldBePublic.Id,
Message = "Component parameter 'ConsoleApplication1.TypeName.MyProperty1' should have a public setter.",
Severity = DiagnosticSeverity.Warning,
Locations = new[]
{
new DiagnosticResultLocation("Test0.cs", 7, 39)
}
},
new DiagnosticResult
{
Id = DiagnosticDescriptors.ComponentParameterSettersShouldBePublic.Id,
Message = "Component parameter 'ConsoleApplication1.TypeName.MyProperty2' should have a public setter.",
Severity = DiagnosticSeverity.Warning,
Locations = new[]
{
new DiagnosticResultLocation("Test0.cs", 8, 39)
}
},
new DiagnosticResult
{
Id = DiagnosticDescriptors.ComponentParameterSettersShouldBePublic.Id,
Message = "Component parameter 'ConsoleApplication1.TypeName.MyProperty3' should have a public setter.",
Severity = DiagnosticSeverity.Warning,
Locations = new[]
{
new DiagnosticResultLocation("Test0.cs", 9, 39)
}
});
}
protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() => new ComponentParameterAnalyzer();
}
}

View File

@ -9,17 +9,17 @@ using Xunit;
namespace Microsoft.AspNetCore.Components.Analyzers.Test namespace Microsoft.AspNetCore.Components.Analyzers.Test
{ {
public class ComponentParametersShouldNotBePublic : CodeFixVerifier public class ComponentParametersShouldBePublicCodeFixProviderTest : CodeFixVerifier
{ {
[Fact] [Fact]
public void IgnoresPublicPropertiesWithoutParameterAttribute() public void IgnoresPrivatePropertiesWithoutParameterAttribute()
{ {
var test = @" var test = @"
namespace ConsoleApplication1 namespace ConsoleApplication1
{ {
class TypeName class TypeName
{ {
public string MyProperty { get; set; } private string MyProperty { get; set; }
} }
}" + ComponentsTestDeclarations.Source; }" + ComponentsTestDeclarations.Source;
@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Components.Analyzers.Test
} }
[Fact] [Fact]
public void IgnoresNonpublicPropertiesWithParameterAttribute() public void AddsDiagnosticAndFixForPrivatePropertiesWithParameterAttribute()
{ {
var test = @" var test = @"
namespace ConsoleApplication1 namespace ConsoleApplication1
@ -36,50 +36,30 @@ namespace Microsoft.AspNetCore.Components.Analyzers.Test
class TypeName class TypeName
{ {
[Parameter] string MyPropertyNoModifer { get; set; } [Parameter] private string BadProperty1 { get; set; }
[Parameter] private string MyPropertyPrivate { get; set; } [CascadingParameter] private object BadProperty2 { get; set; }
[CascadingParameter] protected string MyPropertyProtected { get; set; }
[CascadingParameter] internal string MyPropertyInternal { get; set; }
}
}" + ComponentsTestDeclarations.Source;
VerifyCSharpDiagnostic(test);
}
[Fact]
public void AddsDiagnosticAndFixForPublicPropertiesWithParameterAttribute()
{
var test = @"
namespace ConsoleApplication1
{
using " + typeof(ParameterAttribute).Namespace + @";
class TypeName
{
[Parameter] public string BadProperty1 { get; set; }
[CascadingParameter] public object BadProperty2 { get; set; }
} }
}" + ComponentsTestDeclarations.Source; }" + ComponentsTestDeclarations.Source;
VerifyCSharpDiagnostic(test, VerifyCSharpDiagnostic(test,
new DiagnosticResult new DiagnosticResult
{ {
Id = DiagnosticDescriptors.ComponentParametersShouldNotBePublic.Id, Id = DiagnosticDescriptors.ComponentParametersShouldBePublic.Id,
Message = "Component parameter 'ConsoleApplication1.TypeName.BadProperty1' has a public setter, but component parameters should not be publicly settable.", Message = "Component parameter 'ConsoleApplication1.TypeName.BadProperty1' should be public.",
Severity = DiagnosticSeverity.Warning, Severity = DiagnosticSeverity.Warning,
Locations = new[] Locations = new[]
{ {
new DiagnosticResultLocation("Test0.cs", 8, 39) new DiagnosticResultLocation("Test0.cs", 8, 40)
} }
}, },
new DiagnosticResult new DiagnosticResult
{ {
Id = DiagnosticDescriptors.ComponentParametersShouldNotBePublic.Id, Id = DiagnosticDescriptors.ComponentParametersShouldBePublic.Id,
Message = "Component parameter 'ConsoleApplication1.TypeName.BadProperty2' has a public setter, but component parameters should not be publicly settable.", Message = "Component parameter 'ConsoleApplication1.TypeName.BadProperty2' should be public.",
Severity = DiagnosticSeverity.Warning, Severity = DiagnosticSeverity.Warning,
Locations = new[] Locations = new[]
{ {
new DiagnosticResultLocation("Test0.cs", 9, 48) new DiagnosticResultLocation("Test0.cs", 9, 49)
} }
}); });
@ -90,8 +70,8 @@ namespace Microsoft.AspNetCore.Components.Analyzers.Test
class TypeName class TypeName
{ {
[Parameter] string BadProperty1 { get; set; } [Parameter] public string BadProperty1 { get; set; }
[CascadingParameter] object BadProperty2 { get; set; } [CascadingParameter] public object BadProperty2 { get; set; }
} }
}" + ComponentsTestDeclarations.Source); }" + ComponentsTestDeclarations.Source);
} }
@ -108,16 +88,46 @@ namespace Microsoft.AspNetCore.Components.Analyzers.Test
{ {
[Parameter] public string MyProperty1 { get; private set; } [Parameter] public string MyProperty1 { get; private set; }
[Parameter] public object MyProperty2 { get; protected set; } [Parameter] public object MyProperty2 { get; protected set; }
[Parameter] public object MyProperty2 { get; internal set; } [Parameter] public object MyProperty3 { get; internal set; }
} }
}" + ComponentsTestDeclarations.Source; }" + ComponentsTestDeclarations.Source;
VerifyCSharpDiagnostic(test); VerifyCSharpDiagnostic(test,
new DiagnosticResult
{
Id = DiagnosticDescriptors.ComponentParameterSettersShouldBePublic.Id,
Message = "Component parameter 'ConsoleApplication1.TypeName.MyProperty1' should have a public setter.",
Severity = DiagnosticSeverity.Warning,
Locations = new[]
{
new DiagnosticResultLocation("Test0.cs", 8, 39)
}
},
new DiagnosticResult
{
Id = DiagnosticDescriptors.ComponentParameterSettersShouldBePublic.Id,
Message = "Component parameter 'ConsoleApplication1.TypeName.MyProperty2' should have a public setter.",
Severity = DiagnosticSeverity.Warning,
Locations = new[]
{
new DiagnosticResultLocation("Test0.cs", 9, 39)
}
},
new DiagnosticResult
{
Id = DiagnosticDescriptors.ComponentParameterSettersShouldBePublic.Id,
Message = "Component parameter 'ConsoleApplication1.TypeName.MyProperty3' should have a public setter.",
Severity = DiagnosticSeverity.Warning,
Locations = new[]
{
new DiagnosticResultLocation("Test0.cs", 10, 39)
}
});
} }
protected override CodeFixProvider GetCSharpCodeFixProvider() protected override CodeFixProvider GetCSharpCodeFixProvider()
{ {
return new ComponentParametersShouldNotBePublicCodeFixProvider(); return new ComponentParametersShouldBePublicCodeFixProvider();
} }
protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer()

View File

@ -0,0 +1,106 @@
// 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 ComponentParametersShouldBePublicTest : DiagnosticVerifier
{
[Fact]
public void IgnoresPublicProperties()
{
var test = $@"
namespace ConsoleApplication1
{{
using {typeof(ParameterAttribute).Namespace};
class TypeName
{{
[Parameter] public string MyProperty {{ get; set; }}
}}
}}" + ComponentsTestDeclarations.Source;
VerifyCSharpDiagnostic(test);
}
[Fact]
public void IgnoresPrivateNonParameterProperties()
{
var test = $@"
namespace ConsoleApplication1
{{
using {typeof(ParameterAttribute).Namespace};
class TypeName
{{
private string MyProperty {{ get; set; }}
}}
}}" + ComponentsTestDeclarations.Source;
VerifyCSharpDiagnostic(test);
}
[Fact]
public void ErrorsForNonPublicParameters()
{
var test = $@"
namespace ConsoleApplication1
{{
using {typeof(ParameterAttribute).Namespace};
class TypeName
{{
[Parameter] string MyProperty1 {{ get; set; }}
[Parameter] private string MyProperty2 {{ get; set; }}
[Parameter] protected string MyProperty3 {{ get; set; }}
[Parameter] internal string MyProperty4 {{ get; set; }}
}}
}}" + ComponentsTestDeclarations.Source;
VerifyCSharpDiagnostic(test,
new DiagnosticResult
{
Id = DiagnosticDescriptors.ComponentParametersShouldBePublic.Id,
Message = "Component parameter 'ConsoleApplication1.TypeName.MyProperty1' should be public.",
Severity = DiagnosticSeverity.Warning,
Locations = new[]
{
new DiagnosticResultLocation("Test0.cs", 7, 32)
}
},
new DiagnosticResult
{
Id = DiagnosticDescriptors.ComponentParametersShouldBePublic.Id,
Message = "Component parameter 'ConsoleApplication1.TypeName.MyProperty2' should be public.",
Severity = DiagnosticSeverity.Warning,
Locations = new[]
{
new DiagnosticResultLocation("Test0.cs", 8, 40)
}
},
new DiagnosticResult
{
Id = DiagnosticDescriptors.ComponentParametersShouldBePublic.Id,
Message = "Component parameter 'ConsoleApplication1.TypeName.MyProperty3' should be public.",
Severity = DiagnosticSeverity.Warning,
Locations = new[]
{
new DiagnosticResultLocation("Test0.cs", 9, 42)
}
},
new DiagnosticResult
{
Id = DiagnosticDescriptors.ComponentParametersShouldBePublic.Id,
Message = "Component parameter 'ConsoleApplication1.TypeName.MyProperty4' should be public.",
Severity = DiagnosticSeverity.Warning,
Locations = new[]
{
new DiagnosticResultLocation("Test0.cs", 10, 41)
}
});
}
protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() => new ComponentParameterAnalyzer();
}
}