diff --git a/Directory.Build.props b/Directory.Build.props
index d84ef556..57bb1b55 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -38,7 +38,7 @@
all
runtime; build; native; contentfiles; analyzers
-
+
all
runtime; build; native; contentfiles; analyzers
diff --git a/docs/Rules/MA0115.md b/docs/Rules/MA0115.md
index 069e307f..ebc2b0e7 100644
--- a/docs/Rules/MA0115.md
+++ b/docs/Rules/MA0115.md
@@ -15,5 +15,29 @@ Detect usage of invalid parameter in Razor components.
```razor
// Report diagnostic as InvalidParameter does not exist in SampleComponent
+ InvalidParameter="Dummy" /> // Report diagnostic as `InvalidParameter` does not exist in SampleComponent
```
+
+In the case where the component allows for unmatched parameters, you can still detect parameters that are in PascalCase.
+
+```.editorconfig
+MA0115.ReportPascalCaseUnmatchedParameter
+```
+
+In the following example, `Param` is reported as an unmatched parameter.
+
+````c#
+class MyComponent : ComponentBase
+{
+ [Parameter(CaptureUnmatchedValues = true)]
+ public Dictionary AdditionalAttributes { get; set; }
+}
+````
+
+````razor
+@*
+attribute1 is valid as it starts with a lowercase character
+InvalidParameter is not valid as it starts with an uppercase character
+*@
+
+````
diff --git a/src/Meziantou.Analyzer.Annotations/Meziantou.Analyzer.Annotations.csproj b/src/Meziantou.Analyzer.Annotations/Meziantou.Analyzer.Annotations.csproj
index 4be4466c..443c4c62 100644
--- a/src/Meziantou.Analyzer.Annotations/Meziantou.Analyzer.Annotations.csproj
+++ b/src/Meziantou.Analyzer.Annotations/Meziantou.Analyzer.Annotations.csproj
@@ -1,7 +1,7 @@
- netstandard1.0;netstandard2.0
+ netstandard2.0
1.0.0
1.0.0
Annotations to configure Meziantou.Analyzer
diff --git a/src/Meziantou.Analyzer/Internals/SyntaxNodeExtensions.cs b/src/Meziantou.Analyzer/Internals/SyntaxNodeExtensions.cs
index 4607ef08..f4c37cc7 100644
--- a/src/Meziantou.Analyzer/Internals/SyntaxNodeExtensions.cs
+++ b/src/Meziantou.Analyzer/Internals/SyntaxNodeExtensions.cs
@@ -10,8 +10,7 @@ public static T WithoutTrailingSpacesTrivia(this T syntaxNode) where T : Synt
if (!syntaxNode.HasTrailingTrivia)
return syntaxNode;
- var trivia = syntaxNode.GetTrailingTrivia().Reverse();
- var newTrivia = trivia.SkipWhile(t => t.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.WhitespaceTrivia));
- return syntaxNode.WithTrailingTrivia(newTrivia.Reverse());
+ return syntaxNode.WithTrailingTrivia(
+ syntaxNode.GetTrailingTrivia().Reverse().SkipWhile(t => t.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.WhitespaceTrivia)).Reverse());
}
}
diff --git a/src/Meziantou.Analyzer/Rules/DoNotUseUnknownParameterForRazorComponentAnalyzer.cs b/src/Meziantou.Analyzer/Rules/DoNotUseUnknownParameterForRazorComponentAnalyzer.cs
index f1b2b664..2c104850 100644
--- a/src/Meziantou.Analyzer/Rules/DoNotUseUnknownParameterForRazorComponentAnalyzer.cs
+++ b/src/Meziantou.Analyzer/Rules/DoNotUseUnknownParameterForRazorComponentAnalyzer.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
+using Meziantou.Analyzer.Configurations;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
@@ -81,7 +82,8 @@ public void AnalyzeBlockOptions(OperationAnalysisContext context)
var value = invocation.Arguments[1].Value.ConstantValue;
if (value.HasValue && value.Value is string parameterName)
{
- if (!IsValidAttribute(currentComponent, parameterName))
+ var reportPascalCaseUnmatchedParameter = context.Options.GetConfigurationValue(operation, "MA0115.ReportPascalCaseUnmatchedParameter", defaultValue: true);
+ if (!IsValidAttribute(currentComponent, parameterName, reportPascalCaseUnmatchedParameter))
{
context.ReportDiagnostic(Rule, invocation.Syntax, parameterName, currentComponent.ToDisplayString(NullableFlowState.None));
}
@@ -94,11 +96,16 @@ public void AnalyzeBlockOptions(OperationAnalysisContext context)
}
}
- private bool IsValidAttribute(ITypeSymbol componentType, string parameterName)
+ private bool IsValidAttribute(ITypeSymbol componentType, string parameterName, bool reportPascalCaseUnmatchedParameter)
{
var descriptor = GetComponentDescriptor(componentType);
if (descriptor.HasMatchUnmatchedParameters)
+ {
+ if (reportPascalCaseUnmatchedParameter && parameterName.Length > 0 && char.IsUpper(parameterName[0]) && !descriptor.Parameters.Contains(parameterName))
+ return false;
+
return true;
+ }
if (descriptor.Parameters.Contains(parameterName))
return true;
diff --git a/tests/Meziantou.Analyzer.Test/Meziantou.Analyzer.Test.csproj b/tests/Meziantou.Analyzer.Test/Meziantou.Analyzer.Test.csproj
index 4f07f2c6..75638da6 100644
--- a/tests/Meziantou.Analyzer.Test/Meziantou.Analyzer.Test.csproj
+++ b/tests/Meziantou.Analyzer.Test/Meziantou.Analyzer.Test.csproj
@@ -8,19 +8,22 @@
-
+
all
runtime; build; native; contentfiles; analyzers
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
-
-
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/tests/Meziantou.Analyzer.Test/Rules/DoNotUseUnknownParameterForRazorComponentAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/DoNotUseUnknownParameterForRazorComponentAnalyzerTests.cs
index d96fad80..c69a1ad4 100755
--- a/tests/Meziantou.Analyzer.Test/Rules/DoNotUseUnknownParameterForRazorComponentAnalyzerTests.cs
+++ b/tests/Meziantou.Analyzer.Test/Rules/DoNotUseUnknownParameterForRazorComponentAnalyzerTests.cs
@@ -116,12 +116,55 @@ await CreateProjectBuilder()
.ValidateAsync();
}
+ [Theory]
+ [InlineData("Param1")]
+ [InlineData("Param2")]
+ [InlineData("unknownParams")]
+ public async Task ComponentWithCaptureUnmatchedValues_AnyLowercaseParameterIsValid(string parameterName)
+ {
+ var sourceCode = $$"""
+class TypeName : ComponentBase
+{
+ protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
+ {
+ __builder.OpenComponent(0);
+ __builder.AddAttribute(1, "{{parameterName}}", "test");
+ __builder.CloseComponent();
+ }
+}
+""";
+ await CreateProjectBuilder()
+ .WithSourceCode(Usings + sourceCode + ComponentWithCaptureUnmatchedValues)
+ .ValidateAsync();
+ }
+
+ [Theory]
+ [InlineData("UnknownParams")]
+ public async Task ComponentWithCaptureUnmatchedValues_PascalCaseParameterIsInvalid(string parameterName)
+ {
+ var sourceCode = $$"""
+class TypeName : ComponentBase
+{
+ protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
+ {
+ __builder.OpenComponent(0);
+ [||]__builder.AddAttribute(1, "{{parameterName}}", "test");
+ __builder.CloseComponent();
+ }
+}
+""";
+ await CreateProjectBuilder()
+ .WithSourceCode(Usings + sourceCode + ComponentWithCaptureUnmatchedValues)
+ .AddAnalyzerConfiguration("MA0115.ReportPascalCaseUnmatchedParameter", "true")
+ .ValidateAsync();
+ }
+
[Theory]
[InlineData("Param1")]
[InlineData("Param2")]
[InlineData("Param3")]
[InlineData("UnknownParams")]
- public async Task ComponentWithCaptureUnmatchedValues_AnyValueIsValid(string parameterName)
+ public async Task ComponentWithCaptureUnmatchedValues_PascalCaseParameterIsValid(string parameterName)
{
var sourceCode = $$"""
class TypeName : ComponentBase
@@ -136,6 +179,7 @@ protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Renderin
""";
await CreateProjectBuilder()
.WithSourceCode(Usings + sourceCode + ComponentWithCaptureUnmatchedValues)
+ .AddAnalyzerConfiguration("MA0115.ReportPascalCaseUnmatchedParameter", "false")
.ValidateAsync();
}