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

View File

@ -13,13 +13,13 @@ using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Microsoft.AspNetCore.Components.Analyzers
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(ComponentParametersShouldNotBePublicCodeFixProvider)), Shared]
public class ComponentParametersShouldNotBePublicCodeFixProvider : CodeFixProvider
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(ComponentParametersShouldBePublicCodeFixProvider)), Shared]
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
=> ImmutableArray.Create(DiagnosticDescriptors.ComponentParametersShouldNotBePublic.Id);
=> ImmutableArray.Create(DiagnosticDescriptors.ComponentParametersShouldBePublic.Id);
public sealed override FixAllProvider GetFixAllProvider()
{
@ -64,9 +64,24 @@ namespace Microsoft.AspNetCore.Components.Analyzers
return null;
}
var publicModifier = node.Modifiers.FirstOrDefault(m => m.IsKind(SyntaxKind.PublicKeyword));
node = node.WithModifiers(
node.Modifiers.Remove(publicModifier));
var newModifiers = node.Modifiers;
for (var i = 0; i < node.Modifiers.Count; i++)
{
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;
}
}

View File

@ -11,14 +11,14 @@ namespace Microsoft.AspNetCore.Components.Analyzers
// no change of clashing between that and the BL prefix used here.
//
// 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",
new LocalizableResourceString(nameof(Resources.ComponentParametersShouldNotBePublic_Title), Resources.ResourceManager, typeof(Resources)),
new LocalizableResourceString(nameof(Resources.ComponentParametersShouldNotBePublic_Format), Resources.ResourceManager, typeof(Resources)),
new LocalizableResourceString(nameof(Resources.ComponentParameterSettersShouldBePublic_Title), Resources.ResourceManager, typeof(Resources)),
new LocalizableResourceString(nameof(Resources.ComponentParameterSettersShouldBePublic_Format), Resources.ResourceManager, typeof(Resources)),
"Encapsulation",
DiagnosticSeverity.Warning,
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(
"BL0002",
@ -37,5 +37,14 @@ namespace Microsoft.AspNetCore.Components.Analyzers
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
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>
/// 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>
internal static string ComponentParameterCaptureUnmatchedValuesHasWrongType_Description {
get {
@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.Components.Analyzers {
}
/// <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>
internal static string ComponentParameterCaptureUnmatchedValuesHasWrongType_Format {
get {
@ -115,38 +115,65 @@ namespace Microsoft.AspNetCore.Components.Analyzers {
}
/// <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>
internal static string ComponentParametersShouldNotBePublic_Description {
internal static string ComponentParameterSettersShouldBePublic_Description {
get {
return ResourceManager.GetString("ComponentParametersShouldNotBePublic_Description", resourceCulture);
return ResourceManager.GetString("ComponentParameterSettersShouldBePublic_Description", resourceCulture);
}
}
/// <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>
internal static string ComponentParametersShouldNotBePublic_FixTitle {
internal static string ComponentParameterSettersShouldBePublic_Format {
get {
return ResourceManager.GetString("ComponentParametersShouldNotBePublic_FixTitle", resourceCulture);
return ResourceManager.GetString("ComponentParameterSettersShouldBePublic_Format", resourceCulture);
}
}
/// <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>
internal static string ComponentParametersShouldNotBePublic_Format {
internal static string ComponentParameterSettersShouldBePublic_Title {
get {
return ResourceManager.GetString("ComponentParametersShouldNotBePublic_Format", resourceCulture);
return ResourceManager.GetString("ComponentParameterSettersShouldBePublic_Title", resourceCulture);
}
}
/// <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>
internal static string ComponentParametersShouldNotBePublic_Title {
internal static string ComponentParameterShouldBePublic_Format {
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

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@ -117,17 +117,14 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ComponentParametersShouldNotBePublic_Description" xml:space="preserve">
<value>Component parameters should not have public setters.</value>
<data name="ComponentParameterSettersShouldBePublic_Description" xml:space="preserve">
<value>Component parameters should have public setters.</value>
</data>
<data name="ComponentParametersShouldNotBePublic_FixTitle" xml:space="preserve">
<value>Make component parameter private</value>
<data name="ComponentParameterSettersShouldBePublic_Format" xml:space="preserve">
<value>Component parameter '{0}' should have a public setter.</value>
</data>
<data name="ComponentParametersShouldNotBePublic_Format" xml:space="preserve">
<value>Component parameter '{0}' has a public setter, but component parameters should not be publicly settable.</value>
</data>
<data name="ComponentParametersShouldNotBePublic_Title" xml:space="preserve">
<value>Component parameter has public setter</value>
<data name="ComponentParameterSettersShouldBePublic_Title" xml:space="preserve">
<value>Component parameter should have public setters.</value>
</data>
<data name="ComponentParameterCaptureUnmatchedValuesMustBeUnique_Description" xml:space="preserve">
<value>Components may only define a single parameter with CaptureUnmatchedValues.</value>
@ -147,4 +144,16 @@
<data name="ComponentParameterCaptureUnmatchedValuesHasWrongType_Title" xml:space="preserve">
<value>Component parameter with CaptureUnmatchedValues has the wrong type</value>
</data>
</root>
<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>

View File

@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Components.Analyzers.Test
using {typeof(ParameterAttribute).Namespace};
class TypeName
{{
[Parameter(CaptureUnmatchedValues = true)] {propertyType} MyProperty {{ get; set; }}
[Parameter(CaptureUnmatchedValues = true)] public {propertyType} MyProperty {{ get; set; }}
}}
}}" + ComponentsTestDeclarations.Source;
@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Components.Analyzers.Test
using {typeof(ParameterAttribute).Namespace};
class TypeName
{{
[Parameter(CaptureUnmatchedValues = false)] string MyProperty {{ get; set; }}
[Parameter(CaptureUnmatchedValues = false)] public string MyProperty {{ get; set; }}
}}
}}" + ComponentsTestDeclarations.Source;
@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Components.Analyzers.Test
using {typeof(ParameterAttribute).Namespace};
class TypeName
{{
[Parameter(CaptureUnmatchedValues = true)] string MyProperty {{ get; set; }}
[Parameter(CaptureUnmatchedValues = true)] public string MyProperty {{ get; set; }}
}}
}}" + ComponentsTestDeclarations.Source;
@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Components.Analyzers.Test
Severity = DiagnosticSeverity.Warning,
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};
class TypeName
{{
[Parameter(CaptureUnmatchedValues = false)] string MyProperty {{ get; set; }}
[Parameter(CaptureUnmatchedValues = true)] Dictionary<string, object> MyOtherProperty {{ get; set; }}
[Parameter(CaptureUnmatchedValues = false)] public string MyProperty {{ get; set; }}
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> MyOtherProperty {{ get; set; }}
}}
}}" + ComponentsTestDeclarations.Source;
@ -39,8 +39,8 @@ namespace Microsoft.AspNetCore.Components.Analyzers.Test
using {typeof(ParameterAttribute).Namespace};
class TypeName
{{
[Parameter(CaptureUnmatchedValues = true)] Dictionary<string, object> MyProperty {{ get; set; }}
[Parameter(CaptureUnmatchedValues = true)] Dictionary<string, object> MyOtherProperty {{ get; set; }}
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> MyProperty {{ get; set; }}
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> MyOtherProperty {{ get; set; }}
}}
}}" + 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
{
public class ComponentParametersShouldNotBePublic : CodeFixVerifier
public class ComponentParametersShouldBePublicCodeFixProviderTest : CodeFixVerifier
{
[Fact]
public void IgnoresPublicPropertiesWithoutParameterAttribute()
public void IgnoresPrivatePropertiesWithoutParameterAttribute()
{
var test = @"
namespace ConsoleApplication1
{
class TypeName
{
public string MyProperty { get; set; }
private string MyProperty { get; set; }
}
}" + ComponentsTestDeclarations.Source;
@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Components.Analyzers.Test
}
[Fact]
public void IgnoresNonpublicPropertiesWithParameterAttribute()
public void AddsDiagnosticAndFixForPrivatePropertiesWithParameterAttribute()
{
var test = @"
namespace ConsoleApplication1
@ -36,50 +36,30 @@ namespace Microsoft.AspNetCore.Components.Analyzers.Test
class TypeName
{
[Parameter] string MyPropertyNoModifer { get; set; }
[Parameter] private string MyPropertyPrivate { 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; }
[Parameter] private string BadProperty1 { get; set; }
[CascadingParameter] private object BadProperty2 { get; set; }
}
}" + ComponentsTestDeclarations.Source;
VerifyCSharpDiagnostic(test,
new DiagnosticResult
{
Id = DiagnosticDescriptors.ComponentParametersShouldNotBePublic.Id,
Message = "Component parameter 'ConsoleApplication1.TypeName.BadProperty1' has a public setter, but component parameters should not be publicly settable.",
Id = DiagnosticDescriptors.ComponentParametersShouldBePublic.Id,
Message = "Component parameter 'ConsoleApplication1.TypeName.BadProperty1' should be public.",
Severity = DiagnosticSeverity.Warning,
Locations = new[]
{
new DiagnosticResultLocation("Test0.cs", 8, 39)
new DiagnosticResultLocation("Test0.cs", 8, 40)
}
},
new DiagnosticResult
{
Id = DiagnosticDescriptors.ComponentParametersShouldNotBePublic.Id,
Message = "Component parameter 'ConsoleApplication1.TypeName.BadProperty2' has a public setter, but component parameters should not be publicly settable.",
Id = DiagnosticDescriptors.ComponentParametersShouldBePublic.Id,
Message = "Component parameter 'ConsoleApplication1.TypeName.BadProperty2' should be public.",
Severity = DiagnosticSeverity.Warning,
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
{
[Parameter] string BadProperty1 { get; set; }
[CascadingParameter] object BadProperty2 { get; set; }
[Parameter] public string BadProperty1 { get; set; }
[CascadingParameter] public object BadProperty2 { get; set; }
}
}" + ComponentsTestDeclarations.Source);
}
@ -108,16 +88,46 @@ namespace Microsoft.AspNetCore.Components.Analyzers.Test
{
[Parameter] public string MyProperty1 { get; private set; }
[Parameter] public object MyProperty2 { get; protected set; }
[Parameter] public object MyProperty2 { get; internal set; }
[Parameter] public object MyProperty3 { get; internal set; }
}
}" + 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()
{
return new ComponentParametersShouldNotBePublicCodeFixProvider();
return new ComponentParametersShouldBePublicCodeFixProvider();
}
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();
}
}