diff --git a/analyzers/rspec/cs/S6932.html b/analyzers/rspec/cs/S6932.html
new file mode 100644
index 00000000000..7f0cef39296
--- /dev/null
+++ b/analyzers/rspec/cs/S6932.html
@@ -0,0 +1,336 @@
+
The HttpRequest
class provides access to the raw request data through the QueryString
, Headers
, and
+Forms
properties. However, whenever possible it is recommended to use model binding instead of directly accessing the input data.
+Why is this an issue?
+Both ASP.Net MVC implementations - Core and Framework - support model binding in a comparable fashion. Model binding streamlines the
+process by automatically aligning data from HTTP requests with action method parameters, providing numerous benefits compared to manually parsing raw
+incoming request data:
+
+
+ Simplicity
+
+
+ Model binding simplifies the code by automatically mapping data from HTTP requests to action method parameters. You don’t need to write any
+ code to manually extract values from the request.
+
+
+ Type Safety
+
+
+ Model binding provides type safety by automatically converting the incoming data into the appropriate .NET types. If the conversion fails, the
+ model state becomes invalid, which you can easily check using ModelState.IsValid
.
+
+
+ Validation
+
+
+ With model binding, you can easily apply validation rules to your models using data annotations. If the incoming data doesn’t comply with these
+ rules, the model state becomes invalid.
+
+
+ Security
+
+
+ Model binding helps protect against over-posting attacks by only including properties in the model that you explicitly bind using the
+ [Bind]
attribute or by using view models that only contain the properties you want to update.
+
+
+ Maintainability
+
+
+ By using model binding, your code becomes cleaner, easier to read, and maintain. It promotes the use of strongly typed views, which can provide
+ compile-time checking of your views.
+
+
+How to fix it in ASP.NET Core
+Request.Form
, Request.Form.Files
, Request.Headers
, Request.Query
and Request.RouteValues
are keyed
+collections that expose data from the incoming HTTP request:
+
+Model binding can bind these keyed collections to
+
+ action method parameters by matching the key to the parameter name or
+ the property of a complex type by matching the key to the property name.
+
+To replace the keyed collection access, you can:
+
+The Model Binding in ASP.NET Core article describes the
+mechanisms, conventions, and customization options for model binding in more detail. Route-based binding is described in the Routing to controller actions in ASP.NET Core document.
+Code examples
+Noncompliant code example
+
+public IActionResult Post()
+{
+ var name = Request.Form["name"]; // Noncompliant: Request.Form
+ var birthdate = DateTime.Parse(Request.Form["Birthdate"]); // Noncompliant: Request.Form
+
+ var origin = Request.Headers[HeaderNames.Origin]; // Noncompliant: Request.Headers
+ var locale = Request.Query.TryGetValue("locale", out var locales)
+ ? locales.ToString()
+ : "en-US"; // Noncompliant: Request.Query
+ // ..
+}
+
+Compliant solution
+
+public record User
+{
+ [Required, StringLength(100)]
+ public required string Name { get; init; }
+ [DataType(DataType.Date)]
+ public DateTime? Birthdate { get; init; }
+}
+
+public IActionResult Post(User user, [FromHeader] string origin, [FromQuery] string locale = "en-US")
+{
+ if (ModelState.IsValid)
+ {
+ // ...
+ }
+}
+
+How does this work?
+Model binding in ASP.NET Core MVC and ASP.NET MVC 4.x works by automatically mapping data from HTTP requests to action method parameters. Here’s a
+step-by-step breakdown of how it works:
+
+ Request Data When a user submits a form or sends a request to an ASP.NET application, the request data might include form
+ data, query string parameters, request body, and HTTP headers.
+ Model Binder The model binder’s job is to create .NET objects from the request data. It looks at each parameter in the action
+ method and attempts to populate it with the incoming data.
+ Value Providers The model binder uses Value Providers to get data from various parts of the request, such as the query string,
+ form data, or route data. Each value provider tells the model binder where to find values in the request.
+ Binding The model binder tries to match the keys from the incoming data with the properties of the action method’s parameters.
+ If a match is found, it attempts to convert the incoming data into the appropriate .NET type and assigns it to the parameter.
+ Validation If the model binder can’t convert the value or if the converted value doesn’t pass any specified validation rules,
+ it adds an error to the ModelState.Errors
collection. You can check ModelState.IsValid
in your action method to see if any
+ errors occurred during model binding.
+ Action Method Execution The action method is executed with the bound parameters. If ModelState.IsValid
is
+ false
, you can handle the errors in your action method and return an appropriate response.
+
+See the links in the Resources section for more information.
+How to fix it in ASP.NET MVC 4.x
+Request.Form
and Request.QueryString
are keyed collections
+that expose data from the incoming HTTP request:
+
+Model binding can bind these keyed collections to
+
+ action method parameters by matching the key to the parameter name or
+ the property of a complex type by matching the key to the property name.
+
+To replace the keyed collection access, you can:
+
+
+
+
+
+
+
+
+ Replace
+ with parameter binding
+ or complex type binding
+
+
+
+
+ Request.Form ["id"]
+ optional [Bind]
attribute on the
+ parameter or a FormCollection
+ parameter
+ optional [Bind]
attribute on the
+ parameter or type
+
+
+ Request.QueryString ["id"]
+ optional [Bind]
attribute on the
+ parameter
+ property name must match query parameter key
+
+
+
+Code examples
+Noncompliant code example
+
+public ActionResult Post()
+{
+ var name = Request.Form["name"]; // Noncompliant: Request.Form
+ Debug.WriteLine(Request.Form[0]); // Compliant: Binding by index is not supported.
+ var birthdate = DateTime.Parse(Request.Form["Birthdate"]); // Noncompliant: Request.Form
+
+ var cultureName = Request.QueryString["locale"] ?? "en-US"; // Noncompliant: Request.QueryString
+ // ..
+}
+
+Compliant solution
+
+public class User
+{
+ [Required, StringLength(100)]
+ public string Name { get; set; }
+ [DataType(DataType.Date)]
+ public DateTime? Birthdate { get; set; }
+}
+
+public ActionResult Post(User user, [Bind(Prefix = "locale")] string cultureName = "en-US")
+{
+ if (ModelState.IsValid)
+ {
+ // ...
+ }
+}
+
+How does this work?
+Model binding in ASP.NET Core MVC and ASP.NET MVC 4.x works by automatically mapping data from HTTP requests to action method parameters. Here’s a
+step-by-step breakdown of how it works:
+
+ Request Data When a user submits a form or sends a request to an ASP.NET application, the request data might include form
+ data, query string parameters, request body, and HTTP headers.
+ Model Binder The model binder’s job is to create .NET objects from the request data. It looks at each parameter in the action
+ method and attempts to populate it with the incoming data.
+ Value Providers The model binder uses Value Providers to get data from various parts of the request, such as the query string,
+ form data, or route data. Each value provider tells the model binder where to find values in the request.
+ Binding The model binder tries to match the keys from the incoming data with the properties of the action method’s parameters.
+ If a match is found, it attempts to convert the incoming data into the appropriate .NET type and assigns it to the parameter.
+ Validation If the model binder can’t convert the value or if the converted value doesn’t pass any specified validation rules,
+ it adds an error to the ModelState.Errors
collection. You can check ModelState.IsValid
in your action method to see if any
+ errors occurred during model binding.
+ Action Method Execution The action method is executed with the bound parameters. If ModelState.IsValid
is
+ false
, you can handle the errors in your action method and return an appropriate response.
+
+See the links in the Resources section for more information.
+Resources
+Documentation
+
+
diff --git a/analyzers/rspec/cs/S6932.json b/analyzers/rspec/cs/S6932.json
new file mode 100644
index 00000000000..24c4c6e7f45
--- /dev/null
+++ b/analyzers/rspec/cs/S6932.json
@@ -0,0 +1,25 @@
+{
+ "title": "Use model binding instead of reading raw request data",
+ "type": "CODE_SMELL",
+ "status": "ready",
+ "remediation": {
+ "func": "Constant\/Issue",
+ "constantCost": "5min"
+ },
+ "tags": [
+ "asp.net"
+ ],
+ "defaultSeverity": "Major",
+ "ruleSpecification": "RSPEC-6932",
+ "sqKey": "S6932",
+ "scope": "Main",
+ "quickfix": "infeasible",
+ "code": {
+ "impacts": {
+ "MAINTAINABILITY": "HIGH",
+ "RELIABILITY": "MEDIUM",
+ "SECURITY": "MEDIUM"
+ },
+ "attribute": "FOCUSED"
+ }
+}
diff --git a/analyzers/src/SonarAnalyzer.CSharp/Extensions/ExpressionSyntaxExtensions.Roslyn.cs b/analyzers/src/SonarAnalyzer.CSharp/Extensions/ExpressionSyntaxExtensions.Roslyn.cs
new file mode 100644
index 00000000000..871a8663981
--- /dev/null
+++ b/analyzers/src/SonarAnalyzer.CSharp/Extensions/ExpressionSyntaxExtensions.Roslyn.cs
@@ -0,0 +1,288 @@
+/*
+ * SonarAnalyzer for .NET
+ * Copyright (C) 2015-2023 SonarSource SA
+ * mailto: contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using System.CodeDom.Compiler;
+
+namespace SonarAnalyzer.Extensions;
+
+[GeneratedCode("Copied From Roslyn", "575bc42589145ba18b4f1cc2267d02695f861d8f")]
+public partial class ExpressionSyntaxExtensions
+{
+ // Copied from
+ // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L319
+ public static bool IsWrittenTo(
+ this ExpressionSyntax expression,
+ SemanticModel semanticModel,
+ CancellationToken cancellationToken)
+ {
+ if (expression == null)
+ return false;
+
+ expression = GetExpressionToAnalyzeForWrites(expression);
+
+ if (expression.IsOnlyWrittenTo())
+ return true;
+
+ if (expression.IsInRefContext(out var refParent))
+ {
+ // most cases of `ref x` will count as a potential write of `x`. An important exception is:
+ // `ref readonly y = ref x`. In that case, because 'y' can't be written to, this would not
+ // be a write of 'x'.
+ if (refParent.Parent is EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Type: { } variableDeclarationType } } })
+ {
+ if (ScopedTypeSyntaxWrapper.IsInstance(variableDeclarationType) && (ScopedTypeSyntaxWrapper)variableDeclarationType is { } scopedType)
+ {
+ variableDeclarationType = scopedType.Type;
+ }
+
+ if (RefTypeSyntaxWrapper.IsInstance(variableDeclarationType) && ((RefTypeSyntaxWrapper)variableDeclarationType).ReadOnlyKeyword != default)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // Similar to `ref x`, `&x` allows reads and write of the value, meaning `x` may be (but is not definitely)
+ // written to.
+ if (expression.Parent.IsKind(SyntaxKind.AddressOfExpression))
+ return true;
+
+ // We're written if we're used in a ++, or -- expression.
+ if (expression.IsOperandOfIncrementOrDecrementExpression())
+ return true;
+
+ if (expression.IsLeftSideOfAnyAssignExpression())
+ return true;
+
+ // An extension method invocation with a ref-this parameter can write to an expression.
+ if (expression.Parent is MemberAccessExpressionSyntax memberAccess &&
+ expression == memberAccess.Expression)
+ {
+ var symbol = semanticModel.GetSymbolInfo(memberAccess, cancellationToken).Symbol;
+ if (symbol is IMethodSymbol
+ {
+ MethodKind: MethodKind.ReducedExtension,
+ ReducedFrom.Parameters: { Length: > 0 } parameters,
+ } && parameters[0].RefKind == RefKind.Ref)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // Copy of
+ // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L221
+ private static ExpressionSyntax GetExpressionToAnalyzeForWrites(ExpressionSyntax? expression)
+ {
+ if (expression.IsRightSideOfDotOrArrow())
+ {
+ expression = (ExpressionSyntax)expression.Parent;
+ }
+
+ expression = (ExpressionSyntax)expression.WalkUpParentheses();
+
+ return expression;
+ }
+
+ // Copy of
+ // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L63
+ public static bool IsRightSideOfDotOrArrow(this ExpressionSyntax name)
+ => IsAnyMemberAccessExpressionName(name) || IsRightSideOfQualifiedName(name);
+
+ // Copy of
+ // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L41
+ public static bool IsAnyMemberAccessExpressionName(this ExpressionSyntax expression)
+ {
+ if (expression == null)
+ return false;
+
+ return expression == (expression.Parent as MemberAccessExpressionSyntax)?.Name ||
+ expression.IsMemberBindingExpressionName();
+ }
+
+ // Copy of
+ // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L50
+ public static bool IsMemberBindingExpressionName(this ExpressionSyntax expression)
+ => expression?.Parent is MemberBindingExpressionSyntax memberBinding &&
+ memberBinding.Name == expression;
+
+ // Copy of
+ // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L54
+ public static bool IsRightSideOfQualifiedName(this ExpressionSyntax expression)
+ => expression?.Parent is QualifiedNameSyntax qualifiedName && qualifiedName.Right == expression;
+
+ // Copy of
+ // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L233
+ public static bool IsOnlyWrittenTo(this ExpressionSyntax expression)
+ {
+ expression = GetExpressionToAnalyzeForWrites(expression);
+
+ if (expression != null)
+ {
+ if (expression.IsInOutContext())
+ {
+ return true;
+ }
+
+ if (expression.Parent != null)
+ {
+ if (expression.IsLeftSideOfAssignExpression())
+ {
+ return true;
+ }
+
+ if (expression.IsAttributeNamedArgumentIdentifier())
+ {
+ return true;
+ }
+ }
+
+ if (IsExpressionOfArgumentInDeconstruction(expression))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// If this declaration or identifier is part of a deconstruction, find the deconstruction.
+ /// If found, returns either an assignment expression or a foreach variable statement.
+ /// Returns null otherwise.
+ ///
+ /// copied from SyntaxExtensions.GetContainingDeconstruction.
+ ///
+ // Copy of
+ // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L273
+ private static bool IsExpressionOfArgumentInDeconstruction(ExpressionSyntax expr)
+ {
+ if (!expr.IsParentKind(SyntaxKind.Argument))
+ {
+ return false;
+ }
+
+ while (true)
+ {
+ var parent = expr.Parent;
+ if (parent == null)
+ {
+ return false;
+ }
+
+ switch (parent.Kind())
+ {
+ case SyntaxKind.Argument:
+ if (parent.Parent?.Kind() == SyntaxKindEx.TupleExpression)
+ {
+ expr = (ExpressionSyntax)parent.Parent;
+ continue;
+ }
+
+ return false;
+ case SyntaxKind.SimpleAssignmentExpression:
+ if (((AssignmentExpressionSyntax)parent).Left == expr)
+ {
+ return true;
+ }
+
+ return false;
+ case SyntaxKindEx.ForEachVariableStatement:
+ if (((ForEachVariableStatementSyntaxWrapper)parent).Variable == expr)
+ {
+ return true;
+ }
+
+ return false;
+
+ default:
+ return false;
+ }
+ }
+ }
+
+ // Copy of
+ // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L190
+ public static bool IsInOutContext(this ExpressionSyntax expression)
+ => expression?.Parent is ArgumentSyntax { RefOrOutKeyword: SyntaxToken { RawKind: (int)SyntaxKind.OutKeyword } } argument &&
+ argument.Expression == expression;
+
+ // Copy of
+ // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L383
+ public static bool IsAttributeNamedArgumentIdentifier(this ExpressionSyntax expression)
+ {
+ var nameEquals = expression?.Parent as NameEqualsSyntax;
+ return nameEquals.IsParentKind(SyntaxKind.AttributeArgument);
+ }
+
+ // Copy of
+ // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L194
+ public static bool IsInRefContext(this ExpressionSyntax expression)
+ => IsInRefContext(expression, out _);
+
+ ///
+ /// Returns true if this expression is in some ref keyword context. If then
+ /// will be the node containing the keyword.
+ ///
+ // Copy of
+ // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L201
+ public static bool IsInRefContext(this ExpressionSyntax expression, out SyntaxNode refParent)
+ {
+ while (expression?.Parent is ParenthesizedExpressionSyntax or PostfixUnaryExpressionSyntax { RawKind: (int)SyntaxKindEx.SuppressNullableWarningExpression })
+ expression = (ExpressionSyntax)expression.Parent;
+
+ if (expression?.Parent switch
+ {
+ ArgumentSyntax { RefOrOutKeyword.RawKind: (int)SyntaxKind.RefKeyword } => true,
+ var x when RefExpressionSyntaxWrapper.IsInstance(x) => true,
+ _ => false,
+ })
+ {
+ refParent = expression.Parent;
+ return true;
+ }
+
+ refParent = null;
+ return false;
+ }
+
+ // Copy of
+ // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L389
+ public static bool IsOperandOfIncrementOrDecrementExpression(this ExpressionSyntax expression)
+ {
+ if (expression?.Parent is SyntaxNode parent)
+ {
+ switch (parent.Kind())
+ {
+ case SyntaxKind.PostIncrementExpression:
+ case SyntaxKind.PreIncrementExpression:
+ case SyntaxKind.PostDecrementExpression:
+ case SyntaxKind.PreDecrementExpression:
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/analyzers/src/SonarAnalyzer.CSharp/Extensions/ExpressionSyntaxExtensions.cs b/analyzers/src/SonarAnalyzer.CSharp/Extensions/ExpressionSyntaxExtensions.cs
index 2f75c0e0210..d60923bb424 100644
--- a/analyzers/src/SonarAnalyzer.CSharp/Extensions/ExpressionSyntaxExtensions.cs
+++ b/analyzers/src/SonarAnalyzer.CSharp/Extensions/ExpressionSyntaxExtensions.cs
@@ -20,7 +20,7 @@
namespace SonarAnalyzer.Extensions
{
- public static class ExpressionSyntaxExtensions
+ public static partial class ExpressionSyntaxExtensions
{
private static readonly ISet EqualsOrNotEquals = new HashSet
{
diff --git a/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs b/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs
index 4dfe05a601d..cbfa91cec3b 100644
--- a/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs
+++ b/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs
@@ -179,7 +179,6 @@ public static SyntaxNode WalkUpParentheses(this SyntaxNode node)
DestructorDeclarationSyntax { Identifier: var identifier } => identifier,
EnumMemberDeclarationSyntax { Identifier: var identifier } => identifier,
EventDeclarationSyntax { Identifier: var identifier } => identifier,
- IdentifierNameSyntax { Identifier: var identifier } => identifier,
IndexerDeclarationSyntax { ThisKeyword: var thisKeyword } => thisKeyword,
InvocationExpressionSyntax
{
@@ -199,6 +198,7 @@ public static SyntaxNode WalkUpParentheses(this SyntaxNode node)
PointerTypeSyntax { ElementType: { } elementType } => GetIdentifier(elementType),
PredefinedTypeSyntax { Keyword: var keyword } => keyword,
QualifiedNameSyntax { Right.Identifier: var identifier } => identifier,
+ SimpleBaseTypeSyntax { Type: { } type } => GetIdentifier(type),
SimpleNameSyntax { Identifier: var identifier } => identifier,
TypeParameterConstraintClauseSyntax { Name.Identifier: var identifier } => identifier,
TypeParameterSyntax { Identifier: var identifier } => identifier,
@@ -363,23 +363,14 @@ static bool TakesExpressionTree(SymbolInfo info)
}
}
- public static bool IsParentKind(this SyntaxNode node, SyntaxKind kind, out T result) where T : SyntaxNode
- {
- if (node?.Parent?.IsKind(kind) is true && node.Parent is T t)
- {
- result = t;
- return true;
- }
- result = null;
- return false;
- }
-
- // based on Type="ArgumentListSyntax" in https://github.com/dotnet/roslyn/blob/main/src/Compilers/CSharp/Portable/Syntax/Syntax.xml
- public static ArgumentListSyntax ArgumentList(this SyntaxNode node) =>
+ // based on Type="BaseArgumentListSyntax" in https://github.com/dotnet/roslyn/blob/main/src/Compilers/CSharp/Portable/Syntax/Syntax.xml
+ public static BaseArgumentListSyntax ArgumentList(this SyntaxNode node) =>
node switch
{
ObjectCreationExpressionSyntax creation => creation.ArgumentList,
InvocationExpressionSyntax invocation => invocation.ArgumentList,
+ ElementAccessExpressionSyntax x => x.ArgumentList,
+ ElementBindingExpressionSyntax x => x.ArgumentList,
ConstructorInitializerSyntax constructorInitializer => constructorInitializer.ArgumentList,
null => null,
_ when PrimaryConstructorBaseTypeSyntaxWrapper.IsInstance(node) => ((PrimaryConstructorBaseTypeSyntaxWrapper)node).ArgumentList,
@@ -503,6 +494,44 @@ public static ConditionalAccessExpressionSyntax GetRootConditionalAccessExpressi
return current;
}
+ // Copy of
+ // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs#L347
+ public static bool IsLeftSideOfAssignExpression(this SyntaxNode node)
+ => node?.Parent is AssignmentExpressionSyntax { RawKind: (int)SyntaxKind.SimpleAssignmentExpression } assignment &&
+ assignment.Left == node;
+
+ // Copy of
+ // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs#L43C1-L45C1
+ public static bool IsParentKind(this SyntaxNode node, SyntaxKind kind)
+ => Microsoft.CodeAnalysis.CSharpExtensions.IsKind(node?.Parent, kind);
+
+ // Copy of
+ // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs#L46
+ public static bool IsParentKind(this SyntaxNode node, SyntaxKind kind, out T result) where T : SyntaxNode
+ {
+ if (node?.Parent?.IsKind(kind) is true && node.Parent is T t)
+ {
+ result = t;
+ return true;
+ }
+ result = null;
+ return false;
+ }
+
+ // Copy of
+ // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs#L351
+ public static bool IsLeftSideOfAnyAssignExpression(this SyntaxNode node)
+ {
+ return node?.Parent != null &&
+ node.Parent.IsAnyAssignExpression() &&
+ ((AssignmentExpressionSyntax)node.Parent).Left == node;
+ }
+
+ // Copy of
+ // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs#L323
+ public static bool IsAnyAssignExpression(this SyntaxNode node)
+ => SyntaxFacts.IsAssignmentExpression(node.Kind());
+
private static string GetUnknownType(SyntaxKind kind) =>
#if DEBUG
diff --git a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs
index 25023b750ba..eefe6074bd6 100644
--- a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs
+++ b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs
@@ -108,6 +108,9 @@ public override bool IsMemberAccessOnKnownType(SyntaxNode memberAccess, string n
public override bool IsStatic(SyntaxNode node) =>
Cast(node).IsStatic();
+ public override bool IsWrittenTo(SyntaxNode expression, SemanticModel semanticModel, CancellationToken cancellationToken) =>
+ Cast(expression).IsWrittenTo(semanticModel, cancellationToken);
+
public override SyntaxKind Kind(SyntaxNode node) => node.Kind();
public override string LiteralText(SyntaxNode literal) =>
diff --git a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpTrackerFacade.cs b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpTrackerFacade.cs
index 5355c39d22e..87983c5909f 100644
--- a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpTrackerFacade.cs
+++ b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpTrackerFacade.cs
@@ -24,6 +24,7 @@ namespace SonarAnalyzer.Helpers.Facade
{
internal sealed class CSharpTrackerFacade : ITrackerFacade
{
+ public ArgumentTracker Argument { get; } = new CSharpArgumentTracker();
public BaseTypeTracker BaseType { get; } = new CSharpBaseTypeTracker();
public ElementAccessTracker ElementAccess { get; } = new CSharpElementAccessTracker();
public FieldAccessTracker FieldAccess { get; } = new CSharpFieldAccessTracker();
diff --git a/analyzers/src/SonarAnalyzer.CSharp/Helpers/CSharpAttributeParameterLookup.cs b/analyzers/src/SonarAnalyzer.CSharp/Helpers/CSharpAttributeParameterLookup.cs
index f49481b7871..7554163392c 100644
--- a/analyzers/src/SonarAnalyzer.CSharp/Helpers/CSharpAttributeParameterLookup.cs
+++ b/analyzers/src/SonarAnalyzer.CSharp/Helpers/CSharpAttributeParameterLookup.cs
@@ -30,4 +30,7 @@ protected override SyntaxNode Expression(AttributeArgumentSyntax argument) =>
protected override SyntaxToken? GetNameColonArgumentIdentifier(AttributeArgumentSyntax argument) =>
argument.NameColon?.Name.Identifier;
+
+ protected override SyntaxToken? GetNameEqualsArgumentIdentifier(AttributeArgumentSyntax argument) =>
+ argument.NameEquals?.Name.Identifier;
}
diff --git a/analyzers/src/SonarAnalyzer.CSharp/Helpers/CSharpMethodParameterLookup.cs b/analyzers/src/SonarAnalyzer.CSharp/Helpers/CSharpMethodParameterLookup.cs
index 7c85b5a6e13..ffefb350e07 100644
--- a/analyzers/src/SonarAnalyzer.CSharp/Helpers/CSharpMethodParameterLookup.cs
+++ b/analyzers/src/SonarAnalyzer.CSharp/Helpers/CSharpMethodParameterLookup.cs
@@ -28,10 +28,10 @@ public CSharpMethodParameterLookup(InvocationExpressionSyntax invocation, Semant
public CSharpMethodParameterLookup(InvocationExpressionSyntax invocation, IMethodSymbol methodSymbol)
: this(invocation.ArgumentList, methodSymbol) { }
- public CSharpMethodParameterLookup(ArgumentListSyntax argumentList, SemanticModel semanticModel)
+ public CSharpMethodParameterLookup(BaseArgumentListSyntax argumentList, SemanticModel semanticModel)
: base(argumentList.Arguments, semanticModel.GetSymbolInfo(argumentList.Parent)) { }
- public CSharpMethodParameterLookup(ArgumentListSyntax argumentList, IMethodSymbol methodSymbol)
+ public CSharpMethodParameterLookup(BaseArgumentListSyntax argumentList, IMethodSymbol methodSymbol)
: base(argumentList.Arguments, methodSymbol) { }
protected override SyntaxNode Expression(ArgumentSyntax argument) =>
@@ -39,4 +39,7 @@ protected override SyntaxNode Expression(ArgumentSyntax argument) =>
protected override SyntaxToken? GetNameColonArgumentIdentifier(ArgumentSyntax argument) =>
argument.NameColon?.Name.Identifier;
+
+ protected override SyntaxToken? GetNameEqualsArgumentIdentifier(ArgumentSyntax argument) =>
+ null;
}
diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/UseAspNetModelBinding.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/UseAspNetModelBinding.cs
new file mode 100644
index 00000000000..36d58d5889b
--- /dev/null
+++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/UseAspNetModelBinding.cs
@@ -0,0 +1,261 @@
+/*
+ * SonarAnalyzer for .NET
+ * Copyright (C) 2015-2024 SonarSource SA
+ * mailto: contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using System.Collections.Concurrent;
+
+namespace SonarAnalyzer.Rules.CSharp;
+
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class UseAspNetModelBinding : SonarDiagnosticAnalyzer
+{
+ private const string DiagnosticId = "S6932";
+ private const string UseAspNetModelBindingMessage = "Use model binding instead of accessing the raw request data";
+ private const string UseIFormFileBindingMessage = "Use IFormFile or IFormFileCollection binding instead";
+
+ protected override string MessageFormat => "{0}";
+
+ protected override ILanguageFacade Language => CSharpFacade.Instance;
+
+ public UseAspNetModelBinding() : base(DiagnosticId) { }
+
+ protected override void Initialize(SonarAnalysisContext context)
+ {
+ context.RegisterCompilationStartAction(compilationStartContext =>
+ {
+ var (argumentDescriptors, propertyAccessDescriptors) = GetDescriptors(compilationStartContext.Compilation);
+ if (argumentDescriptors.Any() || propertyAccessDescriptors.Any())
+ {
+ compilationStartContext.RegisterSymbolStartAction(symbolStart =>
+ {
+ // If the user overrides any action filters, model binding may not be working as expected.
+ // Then we do not want to raise on expressions that originate from parameters.
+ // See the OverridesController.Undecidable test cases for details.
+ var hasActionFiltersOverrides = false;
+ var candidates = new ConcurrentStack(); // In SymbolEnd, we filter the candidates based on the overriding we learn on the go.
+ if (symbolStart.Symbol is INamedTypeSymbol namedType && namedType.IsControllerType())
+ {
+ symbolStart.RegisterCodeBlockStartAction(codeBlockStart =>
+ {
+ if (IsOverridingFilterMethods(codeBlockStart.OwningSymbol))
+ {
+ // We do not want to raise in ActionFilter overrides and so we do not register.
+ // The SymbolEndAction needs to be made aware, that there are
+ // ActionFilter overrides, so it can filter out some candidates.
+ hasActionFiltersOverrides = true;
+ }
+ else
+ {
+ RegisterCodeBlockActions(codeBlockStart, argumentDescriptors, propertyAccessDescriptors, candidates);
+ }
+ });
+ }
+ symbolStart.RegisterSymbolEndAction(symbolEnd =>
+ {
+ foreach (var candidate in candidates.Where(x => !(hasActionFiltersOverrides && x.OriginatesFromParameter)))
+ {
+ symbolEnd.ReportIssue(Diagnostic.Create(Rule, candidate.Location, candidate.Message));
+ }
+ });
+ }, SymbolKind.NamedType);
+ }
+ });
+ }
+
+ private void RegisterCodeBlockActions(SonarCodeBlockStartAnalysisContext codeBlockStart,
+ ArgumentDescriptor[] argumentDescriptors, MemberDescriptor[] propertyAccessDescriptors,
+ ConcurrentStack controllerCandidates)
+ {
+ // Within a single code block, access via constant and variable keys could be mixed.
+ // We only want to raise, if all access were done via constants.
+ var allConstantAccesses = true;
+ var codeBlockCandidates = new ConcurrentStack();
+ if (argumentDescriptors.Any())
+ {
+ codeBlockStart.RegisterNodeAction(nodeContext =>
+ {
+ var argument = (ArgumentSyntax)nodeContext.Node;
+ var context = new ArgumentContext(argument, nodeContext.SemanticModel);
+ if (allConstantAccesses && Array.Exists(argumentDescriptors, x => Language.Tracker.Argument.MatchArgument(x)(context)))
+ {
+ allConstantAccesses &= nodeContext.SemanticModel.GetConstantValue(argument.Expression) is { HasValue: true, Value: string };
+ codeBlockCandidates.Push(new(UseAspNetModelBindingMessage, GetPrimaryLocation(argument), IsOriginatingFromParameter(nodeContext.SemanticModel, argument)));
+ }
+ }, SyntaxKind.Argument);
+ }
+ if (propertyAccessDescriptors.Any())
+ {
+ codeBlockStart.RegisterNodeAction(nodeContext =>
+ {
+ // The property access of Request.Form.Files can be replaced by an IFormFile binding.
+ // Any access to a "Files" property is therefore noncompliant. This is different from the Argument handling above.
+ var memberAccess = (MemberAccessExpressionSyntax)nodeContext.Node;
+ var context = new PropertyAccessContext(memberAccess, nodeContext.SemanticModel, memberAccess.Name.Identifier.ValueText);
+ if (Language.Tracker.PropertyAccess.MatchProperty(propertyAccessDescriptors)(context))
+ {
+ codeBlockCandidates.Push(new(UseIFormFileBindingMessage, memberAccess.GetLocation(), IsOriginatingFromParameter(nodeContext.SemanticModel, memberAccess)));
+ }
+ }, SyntaxKind.SimpleMemberAccessExpression);
+ }
+ codeBlockStart.RegisterCodeBlockEndAction(codeBlockEnd =>
+ {
+ if (allConstantAccesses)
+ {
+ controllerCandidates.PushRange([.. codeBlockCandidates]);
+ }
+ });
+ }
+
+ private static (ArgumentDescriptor[] ArgumentDescriptors, MemberDescriptor[] PropertyAccessDescriptors) GetDescriptors(Compilation compilation)
+ {
+ var argumentDescriptors = new List();
+ var propertyAccessDescriptors = new List();
+ if (compilation.GetTypeByMetadataName(KnownType.Microsoft_AspNetCore_Mvc_ControllerAttribute) is { })
+ {
+ AddAspNetCoreDescriptors(argumentDescriptors, propertyAccessDescriptors);
+ }
+ // TODO: Add descriptors for Asp.Net MVC 4.x
+ return ([.. argumentDescriptors], [.. propertyAccessDescriptors]);
+ }
+
+ private static void AddAspNetCoreDescriptors(List argumentDescriptors, List propertyAccessDescriptors)
+ {
+ argumentDescriptors.AddRange([
+ ArgumentDescriptor.ElementAccess(// Request.Form["id"]
+ invokedIndexerContainer: KnownType.Microsoft_AspNetCore_Http_IFormCollection,
+ invokedIndexerExpression: "Form",
+ parameterConstraint: _ => true, // There is only a single overload and it is getter only
+ argumentPosition: 0),
+ ArgumentDescriptor.MethodInvocation(// Request.Form.TryGetValue("id", out _)
+ invokedType: KnownType.Microsoft_AspNetCore_Http_IFormCollection,
+ methodName: "TryGetValue",
+ parameterName: "key",
+ argumentPosition: 0),
+ ArgumentDescriptor.MethodInvocation(// Request.Form.ContainsKey("id")
+ invokedType: KnownType.Microsoft_AspNetCore_Http_IFormCollection,
+ methodName: "ContainsKey",
+ parameterName: "key",
+ argumentPosition: 0),
+ ArgumentDescriptor.ElementAccess(// Request.Headers["id"]
+ invokedIndexerContainer: KnownType.Microsoft_AspNetCore_Http_IHeaderDictionary,
+ invokedIndexerExpression: "Headers",
+ parameterConstraint: IsGetterParameter, // Headers are read/write
+ argumentPosition: 0),
+ ArgumentDescriptor.MethodInvocation(// Request.Headers.TryGetValue("id", out _)
+ invokedMethodSymbol: x => IsIDictionaryStringStringValuesInvocation(x, "TryGetValue"), // TryGetValue is from IDictionary here. We check the type arguments.
+ invokedMemberNameConstraint: (name, comparison) => string.Equals(name, "TryGetValue", comparison),
+ invokedMemberNodeConstraint: IsAccessedViaHeaderDictionary,
+ parameterConstraint: x => string.Equals(x.Name, "key", StringComparison.Ordinal),
+ argumentListConstraint: (list, position) => list.Count == 2 && position is 0 or null,
+ refKind: RefKind.None),
+ ArgumentDescriptor.MethodInvocation(// Request.Headers.ContainsKey("id")
+ invokedMethodSymbol: x => IsIDictionaryStringStringValuesInvocation(x, "ContainsKey"),
+ invokedMemberNameConstraint: (name, comparison) => string.Equals(name, "ContainsKey", comparison),
+ invokedMemberNodeConstraint: IsAccessedViaHeaderDictionary,
+ parameterConstraint: x => string.Equals(x.Name, "key", StringComparison.Ordinal),
+ argumentListConstraint: (list, _) => list.Count == 1,
+ refKind: RefKind.None),
+ ArgumentDescriptor.ElementAccess(// Request.Query["id"]
+ invokedIndexerContainer: KnownType.Microsoft_AspNetCore_Http_IQueryCollection,
+ invokedIndexerExpression: "Query",
+ parameterConstraint: _ => true, // There is only a single overload and it is getter only
+ argumentPosition: 0),
+ ArgumentDescriptor.MethodInvocation(// Request.Query.TryGetValue("id", out _)
+ invokedType: KnownType.Microsoft_AspNetCore_Http_IQueryCollection,
+ methodName: "TryGetValue",
+ parameterName: "key",
+ argumentPosition: 0),
+ ArgumentDescriptor.ElementAccess(// Request.RouteValues["id"]
+ invokedIndexerContainer: KnownType.Microsoft_AspNetCore_Routing_RouteValueDictionary,
+ invokedIndexerExpression: "RouteValues",
+ parameterConstraint: IsGetterParameter, // RouteValues are read/write
+ argumentPosition: 0),
+ ArgumentDescriptor.MethodInvocation(// Request.RouteValues.TryGetValue("id", out _)
+ invokedType: KnownType.Microsoft_AspNetCore_Routing_RouteValueDictionary,
+ methodName: "TryGetValue",
+ parameterName: "key",
+ argumentPosition: 0)]);
+
+ propertyAccessDescriptors.Add(new(KnownType.Microsoft_AspNetCore_Http_IFormCollection, "Files")); // Request.Form.Files...
+ }
+
+ // Check that the "Headers" expression in the Headers.TryGetValue("id", out _) invocation is of type IHeaderDictionary
+ private static bool IsAccessedViaHeaderDictionary(SemanticModel model, ILanguageFacade language, SyntaxNode invocation) =>
+ invocation is InvocationExpressionSyntax { Expression: { } expression }
+ && GetLeftOfDot(expression) is { } left
+ && model.GetTypeInfo(left) is { Type: { } typeSymbol } && typeSymbol.Is(KnownType.Microsoft_AspNetCore_Http_IHeaderDictionary);
+
+ private static bool IsOverridingFilterMethods(ISymbol owningSymbol) =>
+ (owningSymbol.GetOverriddenMember() ?? owningSymbol).ExplicitOrImplicitInterfaceImplementations().Any(x => x is IMethodSymbol { ContainingType: { } container }
+ && container.IsAny(
+ KnownType.Microsoft_AspNetCore_Mvc_Filters_IActionFilter,
+ KnownType.Microsoft_AspNetCore_Mvc_Filters_IAsyncActionFilter));
+
+ private static bool IsOriginatingFromParameter(SemanticModel semanticModel, ArgumentSyntax argument) =>
+ GetExpressionOfArgumentParent(argument) is { } parentExpression && IsOriginatingFromParameter(semanticModel, parentExpression);
+
+ private static bool IsOriginatingFromParameter(SemanticModel semanticModel, ExpressionSyntax expression) =>
+ MostLeftOfDottedChain(expression) is { } mostLeft && semanticModel.GetSymbolInfo(mostLeft).Symbol is IParameterSymbol;
+
+ private static ExpressionSyntax GetLeftOfDot(ExpressionSyntax expression) =>
+ expression switch
+ {
+ MemberAccessExpressionSyntax memberAccessExpression => memberAccessExpression.Expression,
+ MemberBindingExpressionSyntax memberBindingExpression => memberBindingExpression.GetParentConditionalAccessExpression()?.Expression,
+ _ => null,
+ };
+
+ private static ExpressionSyntax MostLeftOfDottedChain(ExpressionSyntax root)
+ {
+ var current = root.GetRootConditionalAccessExpression()?.Expression ?? root;
+ while (current.Kind() is SyntaxKind.SimpleMemberAccessExpression or SyntaxKind.ElementAccessExpression)
+ {
+ current = current switch
+ {
+ MemberAccessExpressionSyntax { Expression: { } left } => left,
+ ElementAccessExpressionSyntax { Expression: { } left } => left,
+ _ => throw new InvalidOperationException("Unreachable"),
+ };
+ }
+ return current;
+ }
+
+ private static ExpressionSyntax GetExpressionOfArgumentParent(ArgumentSyntax argument) =>
+ argument switch
+ {
+ { Parent: BracketedArgumentListSyntax { Parent: ElementBindingExpressionSyntax expression } } => expression.GetParentConditionalAccessExpression(),
+ { Parent: BracketedArgumentListSyntax { Parent: ElementAccessExpressionSyntax { Expression: { } expression } } } => expression,
+ { Parent: ArgumentListSyntax { Parent: InvocationExpressionSyntax { Expression: { } expression } } } => expression,
+ _ => null,
+ };
+
+ private static Location GetPrimaryLocation(ArgumentSyntax argument) =>
+ ((SyntaxNode)GetExpressionOfArgumentParent(argument) ?? argument).GetLocation();
+
+ private static bool IsGetterParameter(IParameterSymbol parameter) =>
+ parameter.ContainingSymbol is IMethodSymbol { MethodKind: MethodKind.PropertyGet };
+
+ private static bool IsIDictionaryStringStringValuesInvocation(IMethodSymbol method, string name) =>
+ method.Is(KnownType.System_Collections_Generic_IDictionary_TKey_TValue, name)
+ && method.ContainingType.TypeArguments is { Length: 2 } typeArguments
+ && typeArguments[0].Is(KnownType.System_String)
+ && typeArguments[1].Is(KnownType.Microsoft_Extensions_Primitives_StringValues);
+
+ private readonly record struct ReportCandidate(string Message, Location Location, bool OriginatesFromParameter);
+}
diff --git a/analyzers/src/SonarAnalyzer.CSharp/Trackers/CSharpArgumentTracker.cs b/analyzers/src/SonarAnalyzer.CSharp/Trackers/CSharpArgumentTracker.cs
new file mode 100644
index 00000000000..488ef59ad02
--- /dev/null
+++ b/analyzers/src/SonarAnalyzer.CSharp/Trackers/CSharpArgumentTracker.cs
@@ -0,0 +1,93 @@
+/*
+ * SonarAnalyzer for .NET
+ * Copyright (C) 2015-2024 SonarSource SA
+ * mailto: contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+namespace SonarAnalyzer.Helpers.Trackers;
+
+internal sealed class CSharpArgumentTracker : ArgumentTracker
+{
+ protected override SyntaxKind[] TrackedSyntaxKinds => [SyntaxKind.AttributeArgument, SyntaxKind.Argument,];
+
+ protected override ILanguageFacade Language => CSharpFacade.Instance;
+
+ protected override IReadOnlyCollection ArgumentList(SyntaxNode argumentNode) =>
+ argumentNode switch
+ {
+ AttributeArgumentSyntax { Parent: AttributeArgumentListSyntax { Arguments: { } list } } => list,
+ ArgumentSyntax { Parent: BaseArgumentListSyntax { Arguments: { } list } } => list,
+ _ => null,
+ };
+
+ protected override int? Position(SyntaxNode argumentNode) =>
+ argumentNode is ArgumentSyntax { NameColon: not null } or AttributeArgumentSyntax { NameColon: not null } or AttributeArgumentSyntax { NameEquals: not null }
+ ? null
+ : ArgumentList(argumentNode).IndexOf(x => x == argumentNode);
+
+ protected override RefKind? ArgumentRefKind(SyntaxNode argumentNode) =>
+ argumentNode switch
+ {
+ AttributeArgumentSyntax => null,
+ ArgumentSyntax { RefOrOutKeyword: { } refOrOut } => refOrOut.Kind() switch { SyntaxKind.OutKeyword => RefKind.Out, SyntaxKind.RefKeyword => RefKind.Ref, _ => RefKind.None },
+ _ => null,
+ };
+
+ protected override bool InvocationFitsMemberKind(SyntaxNode invokedExpression, InvokedMemberKind memberKind) =>
+ memberKind switch
+ {
+ InvokedMemberKind.Method => invokedExpression is InvocationExpressionSyntax,
+ InvokedMemberKind.Constructor => invokedExpression is ObjectCreationExpressionSyntax
+ or ConstructorInitializerSyntax
+ || ImplicitObjectCreationExpressionSyntaxWrapper.IsInstance(invokedExpression),
+ InvokedMemberKind.Indexer => invokedExpression is ElementAccessExpressionSyntax or ElementBindingExpressionSyntax,
+ InvokedMemberKind.Attribute => invokedExpression is AttributeSyntax,
+ _ => false,
+ };
+
+ protected override bool InvokedMemberFits(SemanticModel model, SyntaxNode invokedExpression, InvokedMemberKind memberKind, Func invokedMemberNameConstraint) =>
+ memberKind switch
+ {
+ InvokedMemberKind.Method => invokedMemberNameConstraint(invokedExpression.GetName()),
+ InvokedMemberKind.Constructor => invokedExpression switch
+ {
+ ObjectCreationExpressionSyntax { Type: { } typeName } => invokedMemberNameConstraint(typeName.GetName()),
+ ConstructorInitializerSyntax x => FindClassNameFromConstructorInitializerSyntax(x) is not string name || invokedMemberNameConstraint(name),
+ { } x when ImplicitObjectCreationExpressionSyntaxWrapper.IsInstance(x) => invokedMemberNameConstraint(model.GetSymbolInfo(x).Symbol?.ContainingType?.Name),
+ _ => false,
+ },
+ InvokedMemberKind.Indexer => invokedExpression switch
+ {
+ ElementAccessExpressionSyntax { Expression: { } accessedExpression } => invokedMemberNameConstraint(accessedExpression.GetName()),
+ ElementBindingExpressionSyntax { } binding => binding.GetParentConditionalAccessExpression() is
+ { Expression: { } accessedExpression } && invokedMemberNameConstraint(accessedExpression.GetName()),
+ _ => false,
+ },
+ InvokedMemberKind.Attribute => invokedExpression is AttributeSyntax { Name: { } typeName } && invokedMemberNameConstraint(typeName.GetName()),
+ _ => false,
+ };
+
+ private string FindClassNameFromConstructorInitializerSyntax(ConstructorInitializerSyntax initializerSyntax) =>
+ initializerSyntax.ThisOrBaseKeyword.Kind() switch
+ {
+ SyntaxKind.ThisKeyword => initializerSyntax is { Parent: ConstructorDeclarationSyntax { Identifier.ValueText: { } typeName } } ? typeName : null,
+ SyntaxKind.BaseKeyword => initializerSyntax is { Parent: ConstructorDeclarationSyntax { Parent: BaseTypeDeclarationSyntax { BaseList.Types: { Count: > 0 } baseListTypes } } }
+ ? baseListTypes.First().GetName() // Get the class name of the called constructor from the base types list of the type declaration
+ : null,
+ _ => null,
+ };
+}
diff --git a/analyzers/src/SonarAnalyzer.Common/Facade/ITrackerFacade.cs b/analyzers/src/SonarAnalyzer.Common/Facade/ITrackerFacade.cs
index 226b7ae5ebe..869789f9aca 100644
--- a/analyzers/src/SonarAnalyzer.Common/Facade/ITrackerFacade.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Facade/ITrackerFacade.cs
@@ -25,6 +25,7 @@ namespace SonarAnalyzer.Helpers.Facade
public interface ITrackerFacade
where TSyntaxKind : struct
{
+ ArgumentTracker Argument { get; }
BaseTypeTracker BaseType { get; }
ElementAccessTracker ElementAccess { get; }
FieldAccessTracker FieldAccess { get; }
diff --git a/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs b/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs
index 48efed1287d..b95fbbae1d0 100644
--- a/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs
@@ -51,6 +51,7 @@ public abstract class SyntaxFacade
public abstract bool IsMemberAccessOnKnownType(SyntaxNode memberAccess, string name, KnownType knownType, SemanticModel semanticModel);
public abstract bool IsNullLiteral(SyntaxNode node);
public abstract bool IsStatic(SyntaxNode node);
+ public abstract bool IsWrittenTo(SyntaxNode expression, SemanticModel semanticModel, CancellationToken cancellationToken);
public abstract TSyntaxKind Kind(SyntaxNode node);
public abstract string LiteralText(SyntaxNode literal);
public abstract ImmutableArray LocalDeclarationIdentifiers(SyntaxNode node);
diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/ArgumentDescriptor.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/ArgumentDescriptor.cs
new file mode 100644
index 00000000000..7edf1f8bfd0
--- /dev/null
+++ b/analyzers/src/SonarAnalyzer.Common/Helpers/ArgumentDescriptor.cs
@@ -0,0 +1,177 @@
+/*
+ * SonarAnalyzer for .NET
+ * Copyright (C) 2015-2024 SonarSource SA
+ * mailto: contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+namespace SonarAnalyzer.Helpers;
+
+public enum InvokedMemberKind
+{
+ Method,
+ Constructor,
+ Indexer,
+ Attribute
+}
+
+public class ArgumentDescriptor
+{
+ private ArgumentDescriptor(InvokedMemberKind memberKind, Func invokedMemberConstraint, Func invokedMemberNameConstraint,
+ Func invokedMemberNodeConstraint, Func, int?, bool> argumentListConstraint,
+ Func parameterConstraint, RefKind? refKind)
+ {
+ MemberKind = memberKind;
+ ArgumentListConstraint = argumentListConstraint;
+ RefKind = refKind;
+ ParameterConstraint = parameterConstraint;
+ InvokedMemberNameConstraint = invokedMemberNameConstraint;
+ InvokedMemberNodeConstraint = invokedMemberNodeConstraint;
+ InvokedMemberConstraint = invokedMemberConstraint;
+ }
+
+ public static ArgumentDescriptor MethodInvocation(KnownType invokedType, string methodName, string parameterName, int argumentPosition) =>
+ MethodInvocation(invokedType, methodName, parameterName, x => x == argumentPosition);
+
+ public static ArgumentDescriptor MethodInvocation(KnownType invokedType, string methodName, string parameterName, Func argumentPosition) =>
+ MethodInvocation(invokedType, methodName, p => p.Name == parameterName, argumentPosition, null);
+
+ public static ArgumentDescriptor MethodInvocation(KnownType invokedType, string methodName, string parameterName, Func argumentPosition, RefKind refKind) =>
+ MethodInvocation(invokedType, methodName, p => p.Name == parameterName, argumentPosition, refKind);
+
+ public static ArgumentDescriptor MethodInvocation(KnownType invokedType, string methodName, Func parameterConstraint, Func argumentPosition, RefKind? refKind) =>
+ MethodInvocation(invokedType, (n, c) => n.Equals(methodName, c), parameterConstraint, argumentPosition, refKind);
+
+ public static ArgumentDescriptor MethodInvocation(KnownType invokedType, Func invokedMemberNameConstraint, Func parameterConstraint,
+ Func argumentPosition, RefKind? refKind) =>
+ MethodInvocation(s => invokedType.Matches(s.ContainingType), invokedMemberNameConstraint, parameterConstraint, argumentPosition, refKind);
+
+ public static ArgumentDescriptor MethodInvocation(Func invokedMethodSymbol, Func invokedMemberNameConstraint,
+ Func parameterConstraint, Func argumentPosition, RefKind? refKind) =>
+ MethodInvocation(invokedMethodSymbol,
+ invokedMemberNameConstraint,
+ (_, _, _) => true,
+ parameterConstraint,
+ (_, position) => position is null || argumentPosition is null || argumentPosition(position.Value),
+ refKind);
+
+ public static ArgumentDescriptor MethodInvocation(Func invokedMethodSymbol, Func invokedMemberNameConstraint,
+ Func invokedMemberNodeConstraint, Func parameterConstraint,
+ Func, int?, bool> argumentListConstraint, RefKind? refKind) =>
+ new(InvokedMemberKind.Method,
+ invokedMemberConstraint: invokedMethodSymbol,
+ invokedMemberNameConstraint: invokedMemberNameConstraint,
+ invokedMemberNodeConstraint: invokedMemberNodeConstraint,
+ argumentListConstraint: argumentListConstraint,
+ parameterConstraint: parameterConstraint,
+ refKind: refKind);
+
+ public static ArgumentDescriptor ConstructorInvocation(KnownType constructedType, string parameterName, int argumentPosition) =>
+ ConstructorInvocation(
+ x => constructedType.Matches(x.ContainingType),
+ (x, c) => x.Equals(constructedType.TypeName, c),
+ static (_, _, _) => true,
+ x => x.Name == parameterName,
+ (_, x) => x is null || x == argumentPosition,
+ null);
+
+ public static ArgumentDescriptor ConstructorInvocation(Func invokedMethodSymbol, Func invokedMemberNameConstraint,
+ Func invokedMemberNodeConstraint, Func parameterConstraint,
+ Func, int?, bool> argumentListConstraint, RefKind? refKind) =>
+ new(InvokedMemberKind.Constructor,
+ invokedMemberConstraint: invokedMethodSymbol,
+ invokedMemberNameConstraint: invokedMemberNameConstraint,
+ invokedMemberNodeConstraint: invokedMemberNodeConstraint,
+ argumentListConstraint: argumentListConstraint,
+ parameterConstraint: parameterConstraint,
+ refKind: refKind);
+
+ public static ArgumentDescriptor ElementAccess(KnownType invokedIndexerContainer, Func parameterConstraint, int argumentPosition) =>
+ ElementAccess(
+ invokedIndexerContainer,
+ null,
+ parameterConstraint,
+ x => x == argumentPosition);
+
+ public static ArgumentDescriptor ElementAccess(KnownType invokedIndexerContainer, string invokedIndexerExpression, Func parameterConstraint, int argumentPosition) =>
+ ElementAccess(invokedIndexerContainer, invokedIndexerExpression, parameterConstraint, x => x == argumentPosition);
+
+ public static ArgumentDescriptor ElementAccess(KnownType invokedIndexerContainer,
+ Func parameterConstraint, Func argumentPositionConstraint) =>
+ ElementAccess(
+ invokedIndexerContainer,
+ null,
+ parameterConstraint,
+ argumentPositionConstraint);
+
+ public static ArgumentDescriptor ElementAccess(KnownType invokedIndexerContainer, string invokedIndexerExpression,
+ Func parameterConstraint, Func argumentPositionConstraint) =>
+ ElementAccess(
+ x => x is { ContainingSymbol: INamedTypeSymbol { } container } && invokedIndexerContainer.Matches(container),
+ (s, c) => invokedIndexerExpression is null || s.Equals(invokedIndexerExpression, c),
+ (_, _, _) => true,
+ argumentListConstraint: (_, p) => argumentPositionConstraint is null || p is null || argumentPositionConstraint(p.Value),
+ parameterConstraint: parameterConstraint);
+
+ public static ArgumentDescriptor ElementAccess(Func invokedIndexerPropertyMethod, Func invokedIndexerExpression,
+ Func invokedIndexerExpressionNodeConstraint, Func parameterConstraint,
+ Func, int?, bool> argumentListConstraint) =>
+ new(InvokedMemberKind.Indexer,
+ invokedMemberConstraint: invokedIndexerPropertyMethod,
+ invokedMemberNameConstraint: invokedIndexerExpression,
+ invokedMemberNodeConstraint: invokedIndexerExpressionNodeConstraint,
+ argumentListConstraint: argumentListConstraint,
+ parameterConstraint: parameterConstraint,
+ refKind: null);
+
+ public static ArgumentDescriptor AttributeArgument(string attributeName, string parameterName, int argumentPosition) =>
+ AttributeArgument(
+ x => x is { MethodKind: MethodKind.Constructor, ContainingType.Name: { } name } && (name == attributeName || name == $"{attributeName}Attribute"),
+ (x, c) => AttributeClassNameConstraint(attributeName, x, c),
+ (_, _, _) => true,
+ p => p.Name == parameterName,
+ (_, i) => i is null || i.Value == argumentPosition);
+
+ public static ArgumentDescriptor AttributeArgument(Func attributeConstructorConstraint, Func attributeNameConstraint,
+ Func attributeNodeConstraint, Func parameterConstraint,
+ Func, int?, bool> argumentListConstraint) =>
+ new(InvokedMemberKind.Attribute,
+ invokedMemberConstraint: attributeConstructorConstraint,
+ invokedMemberNameConstraint: attributeNameConstraint,
+ invokedMemberNodeConstraint: attributeNodeConstraint,
+ argumentListConstraint: argumentListConstraint,
+ parameterConstraint: parameterConstraint,
+ refKind: null);
+
+ public static ArgumentDescriptor AttributeProperty(string attributeName, string propertyName) =>
+ AttributeArgument(
+ attributeConstructorConstraint: x => x is { MethodKind: MethodKind.PropertySet, AssociatedSymbol.Name: { } name } && name == propertyName,
+ attributeNameConstraint: (s, c) => AttributeClassNameConstraint(attributeName, s, c),
+ (_, _, _) => true,
+ parameterConstraint: p => true,
+ argumentListConstraint: (_, _) => true);
+
+ private static bool AttributeClassNameConstraint(string expectedAttributeName, string nodeClassName, StringComparison c) =>
+ nodeClassName.Equals(expectedAttributeName, c) || nodeClassName.Equals($"{expectedAttributeName}Attribute");
+
+ public InvokedMemberKind MemberKind { get; }
+ public Func, int?, bool> ArgumentListConstraint { get; }
+ public RefKind? RefKind { get; }
+ public Func ParameterConstraint { get; }
+ public Func InvokedMemberNameConstraint { get; }
+ public Func InvokedMemberNodeConstraint { get; }
+ public Func InvokedMemberConstraint { get; }
+}
diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/AspNetMvcHelper.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/AspNetMvcHelper.cs
index 108b5694749..47d9d55a8a3 100644
--- a/analyzers/src/SonarAnalyzer.Common/Helpers/AspNetMvcHelper.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Helpers/AspNetMvcHelper.cs
@@ -52,7 +52,7 @@ public static bool IsControllerMethod(this IMethodSymbol methodSymbol) =>
/// Returns a value indicating whether the provided type symbol is a ASP.NET MVC
/// controller.
///
- private static bool IsControllerType(this INamedTypeSymbol containingType) =>
+ public static bool IsControllerType(this INamedTypeSymbol containingType) =>
containingType != null
&& (containingType.DerivesFromAny(ControllerTypes)
|| containingType.GetAttributes(ControllerAttributeTypes).Any())
diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs
index aee81d18351..ab37fc5b4f1 100644
--- a/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs
@@ -64,13 +64,18 @@ public sealed partial class KnownType
public static readonly KnownType Microsoft_AspNetCore_Hosting_WebHostBuilderExtensions = new("Microsoft.AspNetCore.Hosting.WebHostBuilderExtensions");
public static readonly KnownType Microsoft_AspNetCore_Http_CookieOptions = new("Microsoft.AspNetCore.Http.CookieOptions");
public static readonly KnownType Microsoft_AspNetCore_Http_HeaderDictionaryExtensions = new("Microsoft.AspNetCore.Http.HeaderDictionaryExtensions");
+ public static readonly KnownType Microsoft_AspNetCore_Http_HttpRequest = new("Microsoft.AspNetCore.Http.HttpRequest");
+ public static readonly KnownType Microsoft_AspNetCore_Http_IFormCollection = new("Microsoft.AspNetCore.Http.IFormCollection");
public static readonly KnownType Microsoft_AspNetCore_Http_IHeaderDictionary = new("Microsoft.AspNetCore.Http.IHeaderDictionary");
+ public static readonly KnownType Microsoft_AspNetCore_Http_IQueryCollection = new("Microsoft.AspNetCore.Http.IQueryCollection");
public static readonly KnownType Microsoft_AspNetCore_Http_IRequestCookieCollection = new("Microsoft.AspNetCore.Http.IRequestCookieCollection");
public static readonly KnownType Microsoft_AspNetCore_Http_IResponseCookies = new("Microsoft.AspNetCore.Http.IResponseCookies");
public static readonly KnownType Microsoft_AspNetCore_Mvc_Controller = new("Microsoft.AspNetCore.Mvc.Controller");
public static readonly KnownType Microsoft_AspNetCore_Mvc_ControllerBase = new("Microsoft.AspNetCore.Mvc.ControllerBase");
public static readonly KnownType Microsoft_AspNetCore_Mvc_ControllerAttribute = new("Microsoft.AspNetCore.Mvc.ControllerAttribute");
public static readonly KnownType Microsoft_AspNetCore_Mvc_DisableRequestSizeLimitAttribute = new("Microsoft.AspNetCore.Mvc.DisableRequestSizeLimitAttribute");
+ public static readonly KnownType Microsoft_AspNetCore_Mvc_Filters_IActionFilter = new("Microsoft.AspNetCore.Mvc.Filters.IActionFilter");
+ public static readonly KnownType Microsoft_AspNetCore_Mvc_Filters_IAsyncActionFilter = new("Microsoft.AspNetCore.Mvc.Filters.IAsyncActionFilter");
public static readonly KnownType Microsoft_AspNetCore_Mvc_FromServicesAttribute = new("Microsoft.AspNetCore.Mvc.FromServicesAttribute");
public static readonly KnownType Microsoft_AspNetCore_Mvc_IActionResult = new("Microsoft.AspNetCore.Mvc.IActionResult");
public static readonly KnownType Microsoft_AspNetCore_Mvc_IgnoreAntiforgeryTokenAttribute = new("Microsoft.AspNetCore.Mvc.IgnoreAntiforgeryTokenAttribute");
@@ -82,6 +87,7 @@ public sealed partial class KnownType
public static readonly KnownType Microsoft_AspNetCore_Mvc_RouteAttribute = new("Microsoft.AspNetCore.Mvc.RouteAttribute");
public static readonly KnownType Microsoft_AspNetCore_Mvc_Routing_HttpMethodAttribute = new("Microsoft.AspNetCore.Mvc.Routing.HttpMethodAttribute");
public static readonly KnownType Microsoft_AspNetCore_Razor_Hosting_RazorCompiledItemAttribute = new("Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute");
+ public static readonly KnownType Microsoft_AspNetCore_Routing_RouteValueDictionary = new("Microsoft.AspNetCore.Routing.RouteValueDictionary");
public static readonly KnownType Microsoft_Azure_Cosmos_CosmosClient = new("Microsoft.Azure.Cosmos.CosmosClient");
public static readonly KnownType Microsoft_Azure_Documents_Client_DocumentClient = new("Microsoft.Azure.Documents.Client.DocumentClient");
public static readonly KnownType Microsoft_Azure_ServiceBus_Management_ManagementClient = new("Microsoft.Azure.ServiceBus.Management.ManagementClient");
@@ -214,6 +220,7 @@ public sealed partial class KnownType
public static readonly KnownType System_Collections_DictionaryBase = new("System.Collections.DictionaryBase");
public static readonly KnownType System_Collections_Frozen_FrozenDictionary_TKey_TValue = new("System.Collections.Frozen.FrozenDictionary", "TKey", "TValue");
public static readonly KnownType System_Collections_Frozen_FrozenSet_T = new("System.Collections.Frozen.FrozenSet", "T");
+ public static readonly KnownType System_Collections_Generic_Comparer_T = new("System.Collections.Generic.Comparer", "T");
public static readonly KnownType System_Collections_Generic_Dictionary_TKey_TValue = new("System.Collections.Generic.Dictionary", "TKey", "TValue");
public static readonly KnownType System_Collections_Generic_HashSet_T = new("System.Collections.Generic.HashSet", "T");
public static readonly KnownType System_Collections_Generic_IAsyncEnumerable_T = new("System.Collections.Generic.IAsyncEnumerable", "T");
diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/MethodParameterLookupBase.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/MethodParameterLookupBase.cs
index 43249717989..b2f83dba712 100644
--- a/analyzers/src/SonarAnalyzer.Common/Helpers/MethodParameterLookupBase.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Helpers/MethodParameterLookupBase.cs
@@ -36,6 +36,7 @@ internal abstract class MethodParameterLookupBase : IMethodPara
private readonly SeparatedSyntaxList argumentList;
protected abstract SyntaxToken? GetNameColonArgumentIdentifier(TArgumentSyntax argument);
+ protected abstract SyntaxToken? GetNameEqualsArgumentIdentifier(TArgumentSyntax argument);
protected abstract SyntaxNode Expression(TArgumentSyntax argument);
public IMethodSymbol MethodSymbol { get; }
@@ -75,6 +76,16 @@ private bool TryGetSymbol(SyntaxNode argument, IMethodSymbol methodSymbol, out I
return parameter != null;
}
+ if (GetNameEqualsArgumentIdentifier(arg) is { } nameEqualsArgumentIdentifier
+ && methodSymbol.ContainingType.GetMembers(nameEqualsArgumentIdentifier.ValueText) is { Length: 1 } properties
+ && properties[0] is IPropertySymbol { SetMethod: { } setter } property
+ && property.Name == nameEqualsArgumentIdentifier.ValueText
+ && setter.Parameters is { Length: 1 } parameters)
+ {
+ parameter = parameters[0];
+ return parameter != null;
+ }
+
var index = argumentList.IndexOf(arg);
if (index >= methodSymbol.Parameters.Length)
{
diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/SyntaxNodeExtensions.Roslyn.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/SyntaxNodeExtensions.Roslyn.cs
new file mode 100644
index 00000000000..691fccd214d
--- /dev/null
+++ b/analyzers/src/SonarAnalyzer.Common/Helpers/SyntaxNodeExtensions.Roslyn.cs
@@ -0,0 +1,82 @@
+/*
+ * SonarAnalyzer for .NET
+ * Copyright (C) 2015-2023 SonarSource SA
+ * mailto: contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using System.CodeDom.Compiler;
+
+namespace SonarAnalyzer.Helpers;
+
+[GeneratedCode("Copied from Roslyn", "5a1cc5f83e4baba57f0355a685a5d1f487bfac66")]
+internal static partial class SyntaxNodeExtensions
+{
+ ///
+ /// Returns true if is a given token is a child token of a certain type of parent node.
+ ///
+ /// The type of the parent node.
+ /// The node that we are testing.
+ /// A function that, when given the parent node, returns the child token we are interested in.
+ // Copy of
+ // https://github.com/dotnet/roslyn/blob/5a1cc5f83e4baba57f0355a685a5d1f487bfac66/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxNodeExtensions.cs#L142
+ public static bool IsChildNode(this SyntaxNode node, Func childGetter) where TParent : SyntaxNode
+ {
+ var ancestor = node.GetAncestor();
+ if (ancestor == null)
+ {
+ return false;
+ }
+
+ var ancestorNode = childGetter(ancestor);
+
+ return node == ancestorNode;
+ }
+
+ // Copy of
+ // https://github.com/dotnet/roslyn/blob/5a1cc5f83e4baba57f0355a685a5d1f487bfac66/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxNodeExtensions.cs#L56
+ public static TNode GetAncestor(this SyntaxNode node) where TNode : SyntaxNode
+ {
+ var current = node.Parent;
+ while (current != null)
+ {
+ if (current is TNode tNode)
+ {
+ return tNode;
+ }
+
+ current = current.GetParent(ascendOutOfTrivia: true);
+ }
+
+ return null;
+ }
+
+ // Copy of
+ // https://github.com/dotnet/roslyn/blob/5a1cc5f83e4baba57f0355a685a5d1f487bfac66/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxNodeExtensions.cs#L811
+ public static SyntaxNode GetParent(this SyntaxNode node, bool ascendOutOfTrivia)
+ {
+ var parent = node.Parent;
+ if (parent == null && ascendOutOfTrivia)
+ {
+ if (node is IStructuredTriviaSyntax structuredTrivia)
+ {
+ parent = structuredTrivia.ParentTrivia.Token.Parent;
+ }
+ }
+
+ return parent;
+ }
+}
diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/SyntaxNodeExtensions.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/SyntaxNodeExtensions.cs
index 51209ee6981..057e1e00a5f 100644
--- a/analyzers/src/SonarAnalyzer.Common/Helpers/SyntaxNodeExtensions.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Helpers/SyntaxNodeExtensions.cs
@@ -20,7 +20,7 @@
namespace SonarAnalyzer.Helpers;
-internal static class SyntaxNodeExtensions
+internal static partial class SyntaxNodeExtensions
{
public static SemanticModel EnsureCorrectSemanticModelOrDefault(this SyntaxNode node, SemanticModel semanticModel) =>
node.SyntaxTree.GetSemanticModelOrDefault(semanticModel);
diff --git a/analyzers/src/SonarAnalyzer.Common/Trackers/ArgumentContext.cs b/analyzers/src/SonarAnalyzer.Common/Trackers/ArgumentContext.cs
new file mode 100644
index 00000000000..3befd88f6fd
--- /dev/null
+++ b/analyzers/src/SonarAnalyzer.Common/Trackers/ArgumentContext.cs
@@ -0,0 +1,30 @@
+/*
+ * SonarAnalyzer for .NET
+ * Copyright (C) 2015-2024 SonarSource SA
+ * mailto: contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+namespace SonarAnalyzer.Helpers;
+
+public class ArgumentContext : SyntaxBaseContext
+{
+ public IParameterSymbol Parameter { get; internal set; }
+
+ public ArgumentContext(SonarSyntaxNodeReportingContext context) : base(context) { }
+
+ public ArgumentContext(SyntaxNode node, SemanticModel semanticModel) : base(node, semanticModel) { }
+}
diff --git a/analyzers/src/SonarAnalyzer.Common/Trackers/ArgumentTracker.cs b/analyzers/src/SonarAnalyzer.Common/Trackers/ArgumentTracker.cs
new file mode 100644
index 00000000000..180d4211e22
--- /dev/null
+++ b/analyzers/src/SonarAnalyzer.Common/Trackers/ArgumentTracker.cs
@@ -0,0 +1,86 @@
+/*
+ * SonarAnalyzer for .NET
+ * Copyright (C) 2015-2024 SonarSource SA
+ * mailto: contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+namespace SonarAnalyzer.Helpers.Trackers;
+
+public abstract class ArgumentTracker : SyntaxTrackerBase
+ where TSyntaxKind : struct
+{
+ protected abstract RefKind? ArgumentRefKind(SyntaxNode argumentNode);
+ protected abstract IReadOnlyCollection ArgumentList(SyntaxNode argumentNode);
+ protected abstract int? Position(SyntaxNode argumentNode);
+ protected abstract bool InvocationFitsMemberKind(SyntaxNode invokedExpression, InvokedMemberKind memberKind);
+ protected abstract bool InvokedMemberFits(SemanticModel model, SyntaxNode invokedExpression, InvokedMemberKind memberKind, Func invokedMemberNameConstraint);
+
+ protected override ArgumentContext CreateContext(SonarSyntaxNodeReportingContext context) =>
+ new(context);
+
+ public Condition MatchArgument(ArgumentDescriptor descriptor) =>
+ context =>
+ {
+ if (context.Node is { } argumentNode
+ && argumentNode is { Parent.Parent: { } invoked }
+ && SyntacticChecks(context.SemanticModel, descriptor, argumentNode, invoked)
+ && (descriptor.InvokedMemberNodeConstraint?.Invoke(context.SemanticModel, Language, invoked) ?? true)
+ && MethodSymbol(context.SemanticModel, invoked) is { } methodSymbol
+ && Language.MethodParameterLookup(invoked, methodSymbol).TryGetSymbol(argumentNode, out var parameter)
+ && ParameterFits(parameter, descriptor.ParameterConstraint, descriptor.InvokedMemberConstraint))
+ {
+ context.Parameter = parameter;
+ return true;
+ }
+ return false;
+ };
+
+ private IMethodSymbol MethodSymbol(SemanticModel model, SyntaxNode invoked) =>
+ model.GetSymbolInfo(invoked).Symbol switch
+ {
+ IMethodSymbol x => x,
+ IPropertySymbol propertySymbol => Language.Syntax.IsWrittenTo(invoked, model, CancellationToken.None)
+ ? propertySymbol.SetMethod
+ : propertySymbol.GetMethod,
+ _ => null,
+ };
+
+ private bool SyntacticChecks(SemanticModel model, ArgumentDescriptor descriptor, SyntaxNode argumentNode, SyntaxNode invokedExpression) =>
+ InvocationFitsMemberKind(invokedExpression, descriptor.MemberKind)
+ && (descriptor.RefKind is not { } expectedRefKind || ArgumentRefKind(argumentNode) is not { } actualRefKind || actualRefKind == expectedRefKind)
+ && (descriptor.ArgumentListConstraint == null
+ || (ArgumentList(argumentNode) is { } argList && descriptor.ArgumentListConstraint(argList, Position(argumentNode))))
+ && (descriptor.InvokedMemberNameConstraint == null
+ || InvokedMemberFits(model, invokedExpression, descriptor.MemberKind, x => descriptor.InvokedMemberNameConstraint(x, Language.NameComparison)));
+
+ private static bool ParameterFits(IParameterSymbol parameter, Func parameterConstraint, Func invokedMemberConstraint)
+ {
+ if (parameter.ContainingSymbol is IMethodSymbol method
+ && method.Parameters.IndexOf(parameter) is >= 0 and int position)
+ {
+ do
+ {
+ if (invokedMemberConstraint?.Invoke(method) is null or true && parameterConstraint?.Invoke(method.Parameters[position]) is null or true)
+ {
+ return true;
+ }
+ }
+ while ((method = method.OverriddenMethod) != null);
+ }
+ return false;
+ }
+}
diff --git a/analyzers/src/SonarAnalyzer.Common/Trackers/InvocationTracker.cs b/analyzers/src/SonarAnalyzer.Common/Trackers/InvocationTracker.cs
index d95f82a9f3e..f59f4198967 100644
--- a/analyzers/src/SonarAnalyzer.Common/Trackers/InvocationTracker.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Trackers/InvocationTracker.cs
@@ -51,18 +51,6 @@ public Condition MethodHasParameters(int count) =>
public Condition IsInvalidBuilderInitialization(BuilderPatternCondition condition) where TInvocationSyntax : SyntaxNode =>
condition.IsInvalidBuilderInitialization;
- public Condition ExceptWhen(Condition condition) =>
- value => !condition(value);
-
- public Condition And(Condition condition1, Condition condition2) =>
- value => condition1(value) && condition2(value);
-
- public Condition Or(Condition condition1, Condition condition2) =>
- value => condition1(value) || condition2(value);
-
- public Condition Or(Condition condition1, Condition condition2, Condition condition3) =>
- value => condition1(value) || condition2(value) || condition3(value);
-
internal Condition MethodReturnTypeIs(KnownType returnType) =>
context => context.MethodSymbol.Value != null
&& context.MethodSymbol.Value.ReturnType.DerivesFrom(returnType);
diff --git a/analyzers/src/SonarAnalyzer.Common/Trackers/ObjectCreationTracker.cs b/analyzers/src/SonarAnalyzer.Common/Trackers/ObjectCreationTracker.cs
index acbe8a90b4e..5592eb03377 100644
--- a/analyzers/src/SonarAnalyzer.Common/Trackers/ObjectCreationTracker.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Trackers/ObjectCreationTracker.cs
@@ -28,18 +28,6 @@ public abstract class ObjectCreationTracker : SyntaxTrackerBase
- value => !condition(value);
-
- public Condition And(Condition condition1, Condition condition2) =>
- value => condition1(value) && condition2(value);
-
- public Condition Or(Condition condition1, Condition condition2) =>
- value => condition1(value) || condition2(value);
-
- public Condition Or(Condition condition1, Condition condition2, Condition condition3) =>
- value => condition1(value) || condition2(value) || condition3(value);
-
internal Condition ArgumentIsBoolConstant(string parameterName, bool expectedValue) =>
context => ConstArgumentForParameter(context, parameterName) is bool boolValue && boolValue == expectedValue;
diff --git a/analyzers/src/SonarAnalyzer.Common/Trackers/PropertyAccessContext.cs b/analyzers/src/SonarAnalyzer.Common/Trackers/PropertyAccessContext.cs
index a31699a9d90..c331e3d7f1d 100644
--- a/analyzers/src/SonarAnalyzer.Common/Trackers/PropertyAccessContext.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Trackers/PropertyAccessContext.cs
@@ -30,5 +30,11 @@ public PropertyAccessContext(SonarSyntaxNodeReportingContext context, string pro
PropertyName = propertyName;
PropertySymbol = new Lazy(() => context.SemanticModel.GetSymbolInfo(context.Node).Symbol as IPropertySymbol);
}
+
+ public PropertyAccessContext(SyntaxNode node, SemanticModel semanticModel, string propertyName) : base(node, semanticModel)
+ {
+ PropertyName = propertyName;
+ PropertySymbol = new Lazy(() => semanticModel.GetSymbolInfo(node).Symbol as IPropertySymbol);
+ }
}
}
diff --git a/analyzers/src/SonarAnalyzer.Common/Trackers/PropertyAccessTracker.cs b/analyzers/src/SonarAnalyzer.Common/Trackers/PropertyAccessTracker.cs
index 60c72d2c12a..a04c1af3b5c 100644
--- a/analyzers/src/SonarAnalyzer.Common/Trackers/PropertyAccessTracker.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Trackers/PropertyAccessTracker.cs
@@ -35,18 +35,6 @@ public Condition MatchProperty(params MemberDescriptor[] properties) =>
public Condition MatchProperty(bool checkOverridenProperties, params MemberDescriptor[] properties) =>
context => MemberDescriptor.MatchesAny(context.PropertyName, context.PropertySymbol, checkOverridenProperties, Language.NameComparison, properties);
- public Condition ExceptWhen(Condition condition) =>
- value => !condition(value);
-
- public Condition And(Condition condition1, Condition condition2) =>
- value => condition1(value) && condition2(value);
-
- public Condition Or(Condition condition1, Condition condition2) =>
- value => condition1(value) || condition2(value);
-
- public Condition Or(Condition condition1, Condition condition2, Condition condition3) =>
- value => condition1(value) || condition2(value) || condition3(value);
-
protected override PropertyAccessContext CreateContext(SonarSyntaxNodeReportingContext context)
{
// We register for both MemberAccess and IdentifierName and we want to
diff --git a/analyzers/src/SonarAnalyzer.Common/Trackers/SyntaxTrackerBase.cs b/analyzers/src/SonarAnalyzer.Common/Trackers/SyntaxTrackerBase.cs
index 4d64f815aab..b7f63edf513 100644
--- a/analyzers/src/SonarAnalyzer.Common/Trackers/SyntaxTrackerBase.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Trackers/SyntaxTrackerBase.cs
@@ -59,5 +59,17 @@ void TrackAndReportIfNecessary(SonarSyntaxNodeReportingContext c)
}
}
}
+
+ public Condition ExceptWhen(Condition condition) =>
+ value => !condition(value);
+
+ public Condition And(Condition condition1, Condition condition2) =>
+ value => condition1(value) && condition2(value);
+
+ public Condition Or(Condition condition1, Condition condition2) =>
+ value => condition1(value) || condition2(value);
+
+ public Condition Or(Condition condition1, Condition condition2, Condition condition3) =>
+ value => condition1(value) || condition2(value) || condition3(value);
}
}
diff --git a/analyzers/src/SonarAnalyzer.RuleDescriptorGenerator/packages.lock.json b/analyzers/src/SonarAnalyzer.RuleDescriptorGenerator/packages.lock.json
deleted file mode 100644
index 29e4b65ee7c..00000000000
--- a/analyzers/src/SonarAnalyzer.RuleDescriptorGenerator/packages.lock.json
+++ /dev/null
@@ -1,938 +0,0 @@
-{
- "version": 1,
- "dependencies": {
- "net6.0": {
- "Microsoft.CodeAnalysis.CSharp.Workspaces": {
- "type": "Direct",
- "requested": "[1.3.2, )",
- "resolved": "1.3.2",
- "contentHash": "MwGmrrPx3okEJuCogSn4TM3yTtJUDdmTt8RXpnjVo0dPund0YSAq4bHQQ9bxgArbrrapcopJmkb7UOLAvanXkg==",
- "dependencies": {
- "Microsoft.CodeAnalysis.CSharp": "[1.3.2]",
- "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2]"
- }
- },
- "Microsoft.CodeAnalysis.VisualBasic.Workspaces": {
- "type": "Direct",
- "requested": "[1.3.2, )",
- "resolved": "1.3.2",
- "contentHash": "I5Z2WBgFsx0G22Na1uVFPDkT6Ob4XI+g91GPN8JWldYUMlmIBcUDBfGmfr8oQPdUipvThpaU1x1xZrnNwRR8JA==",
- "dependencies": {
- "Microsoft.CodeAnalysis.VisualBasic": "[1.3.2]",
- "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2]"
- }
- },
- "StyleCop.Analyzers": {
- "type": "Direct",
- "requested": "[1.2.0-beta.556, )",
- "resolved": "1.2.0-beta.556",
- "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==",
- "dependencies": {
- "StyleCop.Analyzers.Unstable": "1.2.0.556"
- }
- },
- "Google.Protobuf": {
- "type": "Transitive",
- "resolved": "3.6.1",
- "contentHash": "741fGeDQjixBJaU2j+0CbrmZXsNJkTn/hWbOh4fLVXndHsCclJmWznCPWrJmPoZKvajBvAz3e8ECJOUvRtwjNQ==",
- "dependencies": {
- "NETStandard.Library": "1.6.1"
- }
- },
- "Google.Protobuf.Tools": {
- "type": "Transitive",
- "resolved": "3.6.1",
- "contentHash": "mNgfZ1A7UtbZUOIA8+UcKOouKnbd2tu9CKctCvGXFunZGrViWk6QbNwSBc268Sle9Gwl+WQB+u6qQezp5f9y3w=="
- },
- "Microsoft.CodeAnalysis.Analyzers": {
- "type": "Transitive",
- "resolved": "1.1.0",
- "contentHash": "HS3iRWZKcUw/8eZ/08GXKY2Bn7xNzQPzf8gRPHGSowX7u7XXu9i9YEaBeBNKUXWfI7qjvT2zXtLUvbN0hds8vg=="
- },
- "Microsoft.CodeAnalysis.Common": {
- "type": "Transitive",
- "resolved": "1.3.2",
- "contentHash": "lOinFNbjpCvkeYQHutjKi+CfsjoKu88wAFT6hAumSR/XJSJmmVGvmnbzCWW8kUJnDVrw1RrcqS8BzgPMj263og==",
- "dependencies": {
- "Microsoft.CodeAnalysis.Analyzers": "1.1.0",
- "System.AppContext": "4.1.0",
- "System.Collections": "4.0.11",
- "System.Collections.Concurrent": "4.0.12",
- "System.Collections.Immutable": "1.2.0",
- "System.Console": "4.0.0",
- "System.Diagnostics.Debug": "4.0.11",
- "System.Diagnostics.FileVersionInfo": "4.0.0",
- "System.Diagnostics.StackTrace": "4.0.1",
- "System.Diagnostics.Tools": "4.0.1",
- "System.Dynamic.Runtime": "4.0.11",
- "System.Globalization": "4.0.11",
- "System.IO.FileSystem": "4.0.1",
- "System.IO.FileSystem.Primitives": "4.0.1",
- "System.Linq": "4.1.0",
- "System.Linq.Expressions": "4.1.0",
- "System.Reflection": "4.1.0",
- "System.Reflection.Metadata": "1.3.0",
- "System.Reflection.Primitives": "4.0.1",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Runtime.Handles": "4.0.1",
- "System.Runtime.InteropServices": "4.1.0",
- "System.Runtime.Numerics": "4.0.1",
- "System.Security.Cryptography.Algorithms": "4.2.0",
- "System.Security.Cryptography.Encoding": "4.0.0",
- "System.Security.Cryptography.X509Certificates": "4.1.0",
- "System.Text.Encoding": "4.0.11",
- "System.Text.Encoding.CodePages": "4.0.1",
- "System.Text.Encoding.Extensions": "4.0.11",
- "System.Threading": "4.0.11",
- "System.Threading.Tasks": "4.0.11",
- "System.Threading.Tasks.Parallel": "4.0.1",
- "System.Threading.Thread": "4.0.0",
- "System.Xml.ReaderWriter": "4.0.11",
- "System.Xml.XDocument": "4.0.11",
- "System.Xml.XPath.XDocument": "4.0.1",
- "System.Xml.XmlDocument": "4.0.1"
- }
- },
- "Microsoft.CodeAnalysis.CSharp": {
- "type": "Transitive",
- "resolved": "1.3.2",
- "contentHash": "GrYMp6ScZDOMR0fNn/Ce6SegNVFw1G/QRT/8FiKv7lAP+V6lEZx9e42n0FvFUgjjcKgcEJOI4muU6i+3LSvOBA==",
- "dependencies": {
- "Microsoft.CodeAnalysis.Common": "[1.3.2]"
- }
- },
- "Microsoft.CodeAnalysis.VisualBasic": {
- "type": "Transitive",
- "resolved": "1.3.2",
- "contentHash": "yllH3rSYEc0bV15CJ2T9Jtx+tSXO5/OVNb+xofuWrACn65Q5VqeFBKgcbgwpyVY/98ypPcGQIWNQL2A/L1seJg==",
- "dependencies": {
- "Microsoft.CodeAnalysis.Common": "1.3.2"
- }
- },
- "Microsoft.CodeAnalysis.Workspaces.Common": {
- "type": "Transitive",
- "resolved": "1.3.2",
- "contentHash": "kvdo+rkImlx5MuBgkayl4OV3Mg8/qirUdYgCIfQ9EqN15QasJFlQXmDAtCGqpkK9sYLLO/VK+y+4mvKjfh/FOA==",
- "dependencies": {
- "Microsoft.CodeAnalysis.Common": "[1.3.2]",
- "Microsoft.Composition": "1.0.27"
- }
- },
- "Microsoft.Composition": {
- "type": "Transitive",
- "resolved": "1.0.27",
- "contentHash": "pwu80Ohe7SBzZ6i69LVdzowp6V+LaVRzd5F7A6QlD42vQkX0oT7KXKWWPlM/S00w1gnMQMRnEdbtOV12z6rXdQ=="
- },
- "Microsoft.NETCore.Platforms": {
- "type": "Transitive",
- "resolved": "1.0.1",
- "contentHash": "2G6OjjJzwBfNOO8myRV/nFrbTw5iA+DEm0N+qUqhrOmaVtn4pC77h38I1jsXGw5VH55+dPfQsqHD0We9sCl9FQ=="
- },
- "Microsoft.NETCore.Targets": {
- "type": "Transitive",
- "resolved": "1.0.1",
- "contentHash": "rkn+fKobF/cbWfnnfBOQHKVKIOpxMZBvlSHkqDWgBpwGDcLRduvs3D9OLGeV6GWGvVwNlVi2CBbTjuPmtHvyNw=="
- },
- "runtime.native.System": {
- "type": "Transitive",
- "resolved": "4.0.0",
- "contentHash": "QfS/nQI7k/BLgmLrw7qm7YBoULEvgWnPI+cYsbfCVFTW8Aj+i8JhccxcFMu1RWms0YZzF+UHguNBK4Qn89e2Sg==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1"
- }
- },
- "runtime.native.System.Net.Http": {
- "type": "Transitive",
- "resolved": "4.0.1",
- "contentHash": "Nh0UPZx2Vifh8r+J+H2jxifZUD3sBrmolgiFWJd2yiNrxO0xTa6bAw3YwRn1VOiSen/tUXMS31ttNItCZ6lKuA==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1"
- }
- },
- "runtime.native.System.Security.Cryptography": {
- "type": "Transitive",
- "resolved": "4.0.0",
- "contentHash": "2CQK0jmO6Eu7ZeMgD+LOFbNJSXHFVQbCJJkEyEwowh1SCgYnrn9W9RykMfpeeVGw7h4IBvYikzpGUlmZTUafJw==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1"
- }
- },
- "StyleCop.Analyzers.Unstable": {
- "type": "Transitive",
- "resolved": "1.2.0.556",
- "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ=="
- },
- "System.AppContext": {
- "type": "Transitive",
- "resolved": "4.1.0",
- "contentHash": "3QjO4jNV7PdKkmQAVp9atA+usVnKRwI3Kx1nMwJ93T0LcQfx7pKAYk0nKz5wn1oP5iqlhZuy6RXOFdhr7rDwow==",
- "dependencies": {
- "System.Runtime": "4.1.0"
- }
- },
- "System.Collections": {
- "type": "Transitive",
- "resolved": "4.0.11",
- "contentHash": "YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1",
- "System.Runtime": "4.1.0"
- }
- },
- "System.Collections.Concurrent": {
- "type": "Transitive",
- "resolved": "4.0.12",
- "contentHash": "2gBcbb3drMLgxlI0fBfxMA31ec6AEyYCHygGse4vxceJan8mRIWeKJ24BFzN7+bi/NFTgdIgufzb94LWO5EERQ==",
- "dependencies": {
- "System.Collections": "4.0.11",
- "System.Diagnostics.Debug": "4.0.11",
- "System.Diagnostics.Tracing": "4.1.0",
- "System.Globalization": "4.0.11",
- "System.Reflection": "4.1.0",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Threading": "4.0.11",
- "System.Threading.Tasks": "4.0.11"
- }
- },
- "System.Collections.Immutable": {
- "type": "Transitive",
- "resolved": "1.2.0",
- "contentHash": "Cma8cBW6di16ZLibL8LYQ+cLjGzoKxpOTu/faZfDcx94ZjAGq6Nv5RO7+T1YZXqEXTZP9rt1wLVEONVpURtUqw==",
- "dependencies": {
- "System.Collections": "4.0.11",
- "System.Diagnostics.Debug": "4.0.11",
- "System.Globalization": "4.0.11",
- "System.Linq": "4.1.0",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Threading": "4.0.11"
- }
- },
- "System.Console": {
- "type": "Transitive",
- "resolved": "4.0.0",
- "contentHash": "qSKUSOIiYA/a0g5XXdxFcUFmv1hNICBD7QZ0QhGYVipPIhvpiydY8VZqr1thmCXvmn8aipMg64zuanB4eotK9A==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1",
- "System.IO": "4.1.0",
- "System.Runtime": "4.1.0",
- "System.Text.Encoding": "4.0.11"
- }
- },
- "System.Diagnostics.Debug": {
- "type": "Transitive",
- "resolved": "4.0.11",
- "contentHash": "w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1",
- "System.Runtime": "4.1.0"
- }
- },
- "System.Diagnostics.FileVersionInfo": {
- "type": "Transitive",
- "resolved": "4.0.0",
- "contentHash": "qjF74OTAU+mRhLaL4YSfiWy3vj6T3AOz8AW37l5zCwfbBfj0k7E94XnEsRaf2TnhE/7QaV6Hvqakoy2LoV8MVg==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "System.Globalization": "4.0.11",
- "System.IO": "4.1.0",
- "System.IO.FileSystem": "4.0.1",
- "System.IO.FileSystem.Primitives": "4.0.1",
- "System.Reflection.Metadata": "1.3.0",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Runtime.InteropServices": "4.1.0"
- }
- },
- "System.Diagnostics.StackTrace": {
- "type": "Transitive",
- "resolved": "4.0.1",
- "contentHash": "6i2EbRq0lgGfiZ+FDf0gVaw9qeEU+7IS2+wbZJmFVpvVzVOgZEt0ScZtyenuBvs6iDYbGiF51bMAa0oDP/tujQ==",
- "dependencies": {
- "System.Collections.Immutable": "1.2.0",
- "System.IO.FileSystem": "4.0.1",
- "System.Reflection": "4.1.0",
- "System.Reflection.Metadata": "1.3.0",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0"
- }
- },
- "System.Diagnostics.Tools": {
- "type": "Transitive",
- "resolved": "4.0.1",
- "contentHash": "xBfJ8pnd4C17dWaC9FM6aShzbJcRNMChUMD42I6772KGGrqaFdumwhn9OdM68erj1ueNo3xdQ1EwiFjK5k8p0g==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1",
- "System.Runtime": "4.1.0"
- }
- },
- "System.Diagnostics.Tracing": {
- "type": "Transitive",
- "resolved": "4.1.0",
- "contentHash": "vDN1PoMZCkkdNjvZLql592oYJZgS7URcJzJ7bxeBgGtx5UtR5leNm49VmfHGqIffX4FKacHbI3H6UyNSHQknBg==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1",
- "System.Runtime": "4.1.0"
- }
- },
- "System.Dynamic.Runtime": {
- "type": "Transitive",
- "resolved": "4.0.11",
- "contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==",
- "dependencies": {
- "System.Collections": "4.0.11",
- "System.Diagnostics.Debug": "4.0.11",
- "System.Globalization": "4.0.11",
- "System.Linq": "4.1.0",
- "System.Linq.Expressions": "4.1.0",
- "System.ObjectModel": "4.0.12",
- "System.Reflection": "4.1.0",
- "System.Reflection.Emit": "4.0.1",
- "System.Reflection.Emit.ILGeneration": "4.0.1",
- "System.Reflection.Primitives": "4.0.1",
- "System.Reflection.TypeExtensions": "4.1.0",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Threading": "4.0.11"
- }
- },
- "System.Globalization": {
- "type": "Transitive",
- "resolved": "4.0.11",
- "contentHash": "B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1",
- "System.Runtime": "4.1.0"
- }
- },
- "System.Globalization.Calendars": {
- "type": "Transitive",
- "resolved": "4.0.1",
- "contentHash": "L1c6IqeQ88vuzC1P81JeHmHA8mxq8a18NUBNXnIY/BVb+TCyAaGIFbhpZt60h9FJNmisymoQkHEFSE9Vslja1Q==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1",
- "System.Globalization": "4.0.11",
- "System.Runtime": "4.1.0"
- }
- },
- "System.IO": {
- "type": "Transitive",
- "resolved": "4.1.0",
- "contentHash": "3KlTJceQc3gnGIaHZ7UBZO26SHL1SHE4ddrmiwumFnId+CEHP+O8r386tZKaE6zlk5/mF8vifMBzHj9SaXN+mQ==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1",
- "System.Runtime": "4.1.0",
- "System.Text.Encoding": "4.0.11",
- "System.Threading.Tasks": "4.0.11"
- }
- },
- "System.IO.FileSystem": {
- "type": "Transitive",
- "resolved": "4.0.1",
- "contentHash": "IBErlVq5jOggAD69bg1t0pJcHaDbJbWNUZTPI96fkYWzwYbN6D9wRHMULLDd9dHsl7C2YsxXL31LMfPI1SWt8w==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1",
- "System.IO": "4.1.0",
- "System.IO.FileSystem.Primitives": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Handles": "4.0.1",
- "System.Text.Encoding": "4.0.11",
- "System.Threading.Tasks": "4.0.11"
- }
- },
- "System.IO.FileSystem.Primitives": {
- "type": "Transitive",
- "resolved": "4.0.1",
- "contentHash": "kWkKD203JJKxJeE74p8aF8y4Qc9r9WQx4C0cHzHPrY3fv/L/IhWnyCHaFJ3H1QPOH6A93whlQ2vG5nHlBDvzWQ==",
- "dependencies": {
- "System.Runtime": "4.1.0"
- }
- },
- "System.Linq": {
- "type": "Transitive",
- "resolved": "4.1.0",
- "contentHash": "bQ0iYFOQI0nuTnt+NQADns6ucV4DUvMdwN6CbkB1yj8i7arTGiTN5eok1kQwdnnNWSDZfIUySQY+J3d5KjWn0g==",
- "dependencies": {
- "System.Collections": "4.0.11",
- "System.Diagnostics.Debug": "4.0.11",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0"
- }
- },
- "System.Linq.Expressions": {
- "type": "Transitive",
- "resolved": "4.1.0",
- "contentHash": "I+y02iqkgmCAyfbqOmSDOgqdZQ5tTj80Akm5BPSS8EeB0VGWdy6X1KCoYe8Pk6pwDoAKZUOdLVxnTJcExiv5zw==",
- "dependencies": {
- "System.Collections": "4.0.11",
- "System.Diagnostics.Debug": "4.0.11",
- "System.Globalization": "4.0.11",
- "System.IO": "4.1.0",
- "System.Linq": "4.1.0",
- "System.ObjectModel": "4.0.12",
- "System.Reflection": "4.1.0",
- "System.Reflection.Emit": "4.0.1",
- "System.Reflection.Emit.ILGeneration": "4.0.1",
- "System.Reflection.Emit.Lightweight": "4.0.1",
- "System.Reflection.Extensions": "4.0.1",
- "System.Reflection.Primitives": "4.0.1",
- "System.Reflection.TypeExtensions": "4.1.0",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Threading": "4.0.11"
- }
- },
- "System.ObjectModel": {
- "type": "Transitive",
- "resolved": "4.0.12",
- "contentHash": "tAgJM1xt3ytyMoW4qn4wIqgJYm7L7TShRZG4+Q4Qsi2PCcj96pXN7nRywS9KkB3p/xDUjc2HSwP9SROyPYDYKQ==",
- "dependencies": {
- "System.Collections": "4.0.11",
- "System.Diagnostics.Debug": "4.0.11",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Threading": "4.0.11"
- }
- },
- "System.Reflection": {
- "type": "Transitive",
- "resolved": "4.1.0",
- "contentHash": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1",
- "System.IO": "4.1.0",
- "System.Reflection.Primitives": "4.0.1",
- "System.Runtime": "4.1.0"
- }
- },
- "System.Reflection.Emit": {
- "type": "Transitive",
- "resolved": "4.0.1",
- "contentHash": "P2wqAj72fFjpP6wb9nSfDqNBMab+2ovzSDzUZK7MVIm54tBJEPr9jWfSjjoTpPwj1LeKcmX3vr0ttyjSSFM47g==",
- "dependencies": {
- "System.IO": "4.1.0",
- "System.Reflection": "4.1.0",
- "System.Reflection.Emit.ILGeneration": "4.0.1",
- "System.Reflection.Primitives": "4.0.1",
- "System.Runtime": "4.1.0"
- }
- },
- "System.Reflection.Emit.ILGeneration": {
- "type": "Transitive",
- "resolved": "4.0.1",
- "contentHash": "Ov6dU8Bu15Bc7zuqttgHF12J5lwSWyTf1S+FJouUXVMSqImLZzYaQ+vRr1rQ0OZ0HqsrwWl4dsKHELckQkVpgA==",
- "dependencies": {
- "System.Reflection": "4.1.0",
- "System.Reflection.Primitives": "4.0.1",
- "System.Runtime": "4.1.0"
- }
- },
- "System.Reflection.Emit.Lightweight": {
- "type": "Transitive",
- "resolved": "4.0.1",
- "contentHash": "sSzHHXueZ5Uh0OLpUQprhr+ZYJrLPA2Cmr4gn0wj9+FftNKXx8RIMKvO9qnjk2ebPYUjZ+F2ulGdPOsvj+MEjA==",
- "dependencies": {
- "System.Reflection": "4.1.0",
- "System.Reflection.Emit.ILGeneration": "4.0.1",
- "System.Reflection.Primitives": "4.0.1",
- "System.Runtime": "4.1.0"
- }
- },
- "System.Reflection.Extensions": {
- "type": "Transitive",
- "resolved": "4.0.1",
- "contentHash": "GYrtRsZcMuHF3sbmRHfMYpvxZoIN2bQGrYGerUiWLEkqdEUQZhH3TRSaC/oI4wO0II1RKBPlpIa1TOMxIcOOzQ==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1",
- "System.Reflection": "4.1.0",
- "System.Runtime": "4.1.0"
- }
- },
- "System.Reflection.Metadata": {
- "type": "Transitive",
- "resolved": "1.3.0",
- "contentHash": "jMSCxA4LSyKBGRDm/WtfkO03FkcgRzHxwvQRib1bm2GZ8ifKM1MX1al6breGCEQK280mdl9uQS7JNPXRYk90jw==",
- "dependencies": {
- "System.Collections": "4.0.11",
- "System.Collections.Immutable": "1.2.0",
- "System.Diagnostics.Debug": "4.0.11",
- "System.IO": "4.1.0",
- "System.Linq": "4.1.0",
- "System.Reflection": "4.1.0",
- "System.Reflection.Extensions": "4.0.1",
- "System.Reflection.Primitives": "4.0.1",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Runtime.InteropServices": "4.1.0",
- "System.Text.Encoding": "4.0.11",
- "System.Text.Encoding.Extensions": "4.0.11",
- "System.Threading": "4.0.11"
- }
- },
- "System.Reflection.Primitives": {
- "type": "Transitive",
- "resolved": "4.0.1",
- "contentHash": "4inTox4wTBaDhB7V3mPvp9XlCbeGYWVEM9/fXALd52vNEAVisc1BoVWQPuUuD0Ga//dNbA/WeMy9u9mzLxGTHQ==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1",
- "System.Runtime": "4.1.0"
- }
- },
- "System.Reflection.TypeExtensions": {
- "type": "Transitive",
- "resolved": "4.1.0",
- "contentHash": "tsQ/ptQ3H5FYfON8lL4MxRk/8kFyE0A+tGPXmVP967cT/gzLHYxIejIYSxp4JmIeFHVP78g/F2FE1mUUTbDtrg==",
- "dependencies": {
- "System.Reflection": "4.1.0",
- "System.Runtime": "4.1.0"
- }
- },
- "System.Resources.ResourceManager": {
- "type": "Transitive",
- "resolved": "4.0.1",
- "contentHash": "TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1",
- "System.Globalization": "4.0.11",
- "System.Reflection": "4.1.0",
- "System.Runtime": "4.1.0"
- }
- },
- "System.Runtime": {
- "type": "Transitive",
- "resolved": "4.1.0",
- "contentHash": "v6c/4Yaa9uWsq+JMhnOFewrYkgdNHNG2eMKuNqRn8P733rNXeRCGvV5FkkjBXn2dbVkPXOsO0xjsEeM1q2zC0g==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1"
- }
- },
- "System.Runtime.Extensions": {
- "type": "Transitive",
- "resolved": "4.1.0",
- "contentHash": "CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1",
- "System.Runtime": "4.1.0"
- }
- },
- "System.Runtime.Handles": {
- "type": "Transitive",
- "resolved": "4.0.1",
- "contentHash": "nCJvEKguXEvk2ymk1gqj625vVnlK3/xdGzx0vOKicQkoquaTBJTP13AIYkocSUwHCLNBwUbXTqTWGDxBTWpt7g==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1",
- "System.Runtime": "4.1.0"
- }
- },
- "System.Runtime.InteropServices": {
- "type": "Transitive",
- "resolved": "4.1.0",
- "contentHash": "16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1",
- "System.Reflection": "4.1.0",
- "System.Reflection.Primitives": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Handles": "4.0.1"
- }
- },
- "System.Runtime.Numerics": {
- "type": "Transitive",
- "resolved": "4.0.1",
- "contentHash": "+XbKFuzdmLP3d1o9pdHu2nxjNr2OEPqGzKeegPLCUMM71a0t50A/rOcIRmGs9wR7a8KuHX6hYs/7/TymIGLNqg==",
- "dependencies": {
- "System.Globalization": "4.0.11",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0"
- }
- },
- "System.Security.Cryptography.Algorithms": {
- "type": "Transitive",
- "resolved": "4.2.0",
- "contentHash": "8JQFxbLVdrtIOKMDN38Fn0GWnqYZw/oMlwOUG/qz1jqChvyZlnUmu+0s7wLx7JYua/nAXoESpHA3iw11QFWhXg==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "System.Collections": "4.0.11",
- "System.IO": "4.1.0",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Runtime.Handles": "4.0.1",
- "System.Runtime.InteropServices": "4.1.0",
- "System.Runtime.Numerics": "4.0.1",
- "System.Security.Cryptography.Encoding": "4.0.0",
- "System.Security.Cryptography.Primitives": "4.0.0",
- "System.Text.Encoding": "4.0.11",
- "runtime.native.System.Security.Cryptography": "4.0.0"
- }
- },
- "System.Security.Cryptography.Cng": {
- "type": "Transitive",
- "resolved": "4.2.0",
- "contentHash": "cUJ2h+ZvONDe28Szw3st5dOHdjndhJzQ2WObDEXAWRPEQBtVItVoxbXM/OEsTthl3cNn2dk2k0I3y45igCQcLw==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "System.IO": "4.1.0",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Runtime.Handles": "4.0.1",
- "System.Runtime.InteropServices": "4.1.0",
- "System.Security.Cryptography.Algorithms": "4.2.0",
- "System.Security.Cryptography.Encoding": "4.0.0",
- "System.Security.Cryptography.Primitives": "4.0.0",
- "System.Text.Encoding": "4.0.11"
- }
- },
- "System.Security.Cryptography.Csp": {
- "type": "Transitive",
- "resolved": "4.0.0",
- "contentHash": "/i1Usuo4PgAqgbPNC0NjbO3jPW//BoBlTpcWFD1EHVbidH21y4c1ap5bbEMSGAXjAShhMH4abi/K8fILrnu4BQ==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "System.IO": "4.1.0",
- "System.Reflection": "4.1.0",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Runtime.Handles": "4.0.1",
- "System.Runtime.InteropServices": "4.1.0",
- "System.Security.Cryptography.Algorithms": "4.2.0",
- "System.Security.Cryptography.Encoding": "4.0.0",
- "System.Security.Cryptography.Primitives": "4.0.0",
- "System.Text.Encoding": "4.0.11",
- "System.Threading": "4.0.11"
- }
- },
- "System.Security.Cryptography.Encoding": {
- "type": "Transitive",
- "resolved": "4.0.0",
- "contentHash": "FbKgE5MbxSQMPcSVRgwM6bXN3GtyAh04NkV8E5zKCBE26X0vYW0UtTa2FIgkH33WVqBVxRgxljlVYumWtU+HcQ==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "System.Collections": "4.0.11",
- "System.Collections.Concurrent": "4.0.12",
- "System.Linq": "4.1.0",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Runtime.Handles": "4.0.1",
- "System.Runtime.InteropServices": "4.1.0",
- "System.Security.Cryptography.Primitives": "4.0.0",
- "System.Text.Encoding": "4.0.11",
- "runtime.native.System.Security.Cryptography": "4.0.0"
- }
- },
- "System.Security.Cryptography.OpenSsl": {
- "type": "Transitive",
- "resolved": "4.0.0",
- "contentHash": "HUG/zNUJwEiLkoURDixzkzZdB5yGA5pQhDP93ArOpDPQMteURIGERRNzzoJlmTreLBWr5lkFSjjMSk8ySEpQMw==",
- "dependencies": {
- "System.Collections": "4.0.11",
- "System.IO": "4.1.0",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Runtime.Handles": "4.0.1",
- "System.Runtime.InteropServices": "4.1.0",
- "System.Runtime.Numerics": "4.0.1",
- "System.Security.Cryptography.Algorithms": "4.2.0",
- "System.Security.Cryptography.Encoding": "4.0.0",
- "System.Security.Cryptography.Primitives": "4.0.0",
- "System.Text.Encoding": "4.0.11",
- "runtime.native.System.Security.Cryptography": "4.0.0"
- }
- },
- "System.Security.Cryptography.Primitives": {
- "type": "Transitive",
- "resolved": "4.0.0",
- "contentHash": "Wkd7QryWYjkQclX0bngpntW5HSlMzeJU24UaLJQ7YTfI8ydAVAaU2J+HXLLABOVJlKTVvAeL0Aj39VeTe7L+oA==",
- "dependencies": {
- "System.Diagnostics.Debug": "4.0.11",
- "System.Globalization": "4.0.11",
- "System.IO": "4.1.0",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Threading": "4.0.11",
- "System.Threading.Tasks": "4.0.11"
- }
- },
- "System.Security.Cryptography.X509Certificates": {
- "type": "Transitive",
- "resolved": "4.1.0",
- "contentHash": "4HEfsQIKAhA1+ApNn729Gi09zh+lYWwyIuViihoMDWp1vQnEkL2ct7mAbhBlLYm+x/L4Rr/pyGge1lIY635e0w==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "System.Collections": "4.0.11",
- "System.Diagnostics.Debug": "4.0.11",
- "System.Globalization": "4.0.11",
- "System.Globalization.Calendars": "4.0.1",
- "System.IO": "4.1.0",
- "System.IO.FileSystem": "4.0.1",
- "System.IO.FileSystem.Primitives": "4.0.1",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Runtime.Handles": "4.0.1",
- "System.Runtime.InteropServices": "4.1.0",
- "System.Runtime.Numerics": "4.0.1",
- "System.Security.Cryptography.Algorithms": "4.2.0",
- "System.Security.Cryptography.Cng": "4.2.0",
- "System.Security.Cryptography.Csp": "4.0.0",
- "System.Security.Cryptography.Encoding": "4.0.0",
- "System.Security.Cryptography.OpenSsl": "4.0.0",
- "System.Security.Cryptography.Primitives": "4.0.0",
- "System.Text.Encoding": "4.0.11",
- "System.Threading": "4.0.11",
- "runtime.native.System": "4.0.0",
- "runtime.native.System.Net.Http": "4.0.1",
- "runtime.native.System.Security.Cryptography": "4.0.0"
- }
- },
- "System.Text.Encoding": {
- "type": "Transitive",
- "resolved": "4.0.11",
- "contentHash": "U3gGeMlDZXxCEiY4DwVLSacg+DFWCvoiX+JThA/rvw37Sqrku7sEFeVBBBMBnfB6FeZHsyDx85HlKL19x0HtZA==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1",
- "System.Runtime": "4.1.0"
- }
- },
- "System.Text.Encoding.CodePages": {
- "type": "Transitive",
- "resolved": "4.0.1",
- "contentHash": "h4z6rrA/hxWf4655D18IIZ0eaLRa3tQC/j+e26W+VinIHY0l07iEXaAvO0YSYq3MvCjMYy8Zs5AdC1sxNQOB7Q==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "System.Collections": "4.0.11",
- "System.Globalization": "4.0.11",
- "System.IO": "4.1.0",
- "System.Reflection": "4.1.0",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Runtime.Handles": "4.0.1",
- "System.Runtime.InteropServices": "4.1.0",
- "System.Text.Encoding": "4.0.11",
- "System.Threading": "4.0.11"
- }
- },
- "System.Text.Encoding.Extensions": {
- "type": "Transitive",
- "resolved": "4.0.11",
- "contentHash": "jtbiTDtvfLYgXn8PTfWI+SiBs51rrmO4AAckx4KR6vFK9Wzf6tI8kcRdsYQNwriUeQ1+CtQbM1W4cMbLXnj/OQ==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1",
- "System.Runtime": "4.1.0",
- "System.Text.Encoding": "4.0.11"
- }
- },
- "System.Text.RegularExpressions": {
- "type": "Transitive",
- "resolved": "4.1.0",
- "contentHash": "i88YCXpRTjCnoSQZtdlHkAOx4KNNik4hMy83n0+Ftlb7jvV6ZiZWMpnEZHhjBp6hQVh8gWd/iKNPzlPF7iyA2g==",
- "dependencies": {
- "System.Collections": "4.0.11",
- "System.Globalization": "4.0.11",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Threading": "4.0.11"
- }
- },
- "System.Threading": {
- "type": "Transitive",
- "resolved": "4.0.11",
- "contentHash": "N+3xqIcg3VDKyjwwCGaZ9HawG9aC6cSDI+s7ROma310GQo8vilFZa86hqKppwTHleR/G0sfOzhvgnUxWCR/DrQ==",
- "dependencies": {
- "System.Runtime": "4.1.0",
- "System.Threading.Tasks": "4.0.11"
- }
- },
- "System.Threading.Tasks": {
- "type": "Transitive",
- "resolved": "4.0.11",
- "contentHash": "k1S4Gc6IGwtHGT8188RSeGaX86Qw/wnrgNLshJvsdNUOPP9etMmo8S07c+UlOAx4K/xLuN9ivA1bD0LVurtIxQ==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.0.1",
- "Microsoft.NETCore.Targets": "1.0.1",
- "System.Runtime": "4.1.0"
- }
- },
- "System.Threading.Tasks.Extensions": {
- "type": "Transitive",
- "resolved": "4.0.0",
- "contentHash": "pH4FZDsZQ/WmgJtN4LWYmRdJAEeVkyriSwrv2Teoe5FOU0Yxlb6II6GL8dBPOfRmutHGATduj3ooMt7dJ2+i+w==",
- "dependencies": {
- "System.Collections": "4.0.11",
- "System.Runtime": "4.1.0",
- "System.Threading.Tasks": "4.0.11"
- }
- },
- "System.Threading.Tasks.Parallel": {
- "type": "Transitive",
- "resolved": "4.0.1",
- "contentHash": "7Pc9t25bcynT9FpMvkUw4ZjYwUiGup/5cJFW72/5MgCG+np2cfVUMdh29u8d7onxX7d8PS3J+wL73zQRqkdrSA==",
- "dependencies": {
- "System.Collections.Concurrent": "4.0.12",
- "System.Diagnostics.Debug": "4.0.11",
- "System.Diagnostics.Tracing": "4.1.0",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Threading": "4.0.11",
- "System.Threading.Tasks": "4.0.11"
- }
- },
- "System.Threading.Thread": {
- "type": "Transitive",
- "resolved": "4.0.0",
- "contentHash": "gIdJqDXlOr5W9zeqFErLw3dsOsiShSCYtF9SEHitACycmvNvY8odf9kiKvp6V7aibc8C4HzzNBkWXjyfn7plbQ==",
- "dependencies": {
- "System.Runtime": "4.1.0"
- }
- },
- "System.Xml.ReaderWriter": {
- "type": "Transitive",
- "resolved": "4.0.11",
- "contentHash": "ZIiLPsf67YZ9zgr31vzrFaYQqxRPX9cVHjtPSnmx4eN6lbS/yEyYNr2vs1doGDEscF0tjCZFsk9yUg1sC9e8tg==",
- "dependencies": {
- "System.Collections": "4.0.11",
- "System.Diagnostics.Debug": "4.0.11",
- "System.Globalization": "4.0.11",
- "System.IO": "4.1.0",
- "System.IO.FileSystem": "4.0.1",
- "System.IO.FileSystem.Primitives": "4.0.1",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Runtime.InteropServices": "4.1.0",
- "System.Text.Encoding": "4.0.11",
- "System.Text.Encoding.Extensions": "4.0.11",
- "System.Text.RegularExpressions": "4.1.0",
- "System.Threading.Tasks": "4.0.11",
- "System.Threading.Tasks.Extensions": "4.0.0"
- }
- },
- "System.Xml.XDocument": {
- "type": "Transitive",
- "resolved": "4.0.11",
- "contentHash": "Mk2mKmPi0nWaoiYeotq1dgeNK1fqWh61+EK+w4Wu8SWuTYLzpUnschb59bJtGywaPq7SmTuPf44wrXRwbIrukg==",
- "dependencies": {
- "System.Collections": "4.0.11",
- "System.Diagnostics.Debug": "4.0.11",
- "System.Diagnostics.Tools": "4.0.1",
- "System.Globalization": "4.0.11",
- "System.IO": "4.1.0",
- "System.Reflection": "4.1.0",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Text.Encoding": "4.0.11",
- "System.Threading": "4.0.11",
- "System.Xml.ReaderWriter": "4.0.11"
- }
- },
- "System.Xml.XmlDocument": {
- "type": "Transitive",
- "resolved": "4.0.1",
- "contentHash": "2eZu6IP+etFVBBFUFzw2w6J21DqIN5eL9Y8r8JfJWUmV28Z5P0SNU01oCisVHQgHsDhHPnmq2s1hJrJCFZWloQ==",
- "dependencies": {
- "System.Collections": "4.0.11",
- "System.Diagnostics.Debug": "4.0.11",
- "System.Globalization": "4.0.11",
- "System.IO": "4.1.0",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Text.Encoding": "4.0.11",
- "System.Threading": "4.0.11",
- "System.Xml.ReaderWriter": "4.0.11"
- }
- },
- "System.Xml.XPath": {
- "type": "Transitive",
- "resolved": "4.0.1",
- "contentHash": "UWd1H+1IJ9Wlq5nognZ/XJdyj8qPE4XufBUkAW59ijsCPjZkZe0MUzKKJFBr+ZWBe5Wq1u1d5f2CYgE93uH7DA==",
- "dependencies": {
- "System.Collections": "4.0.11",
- "System.Diagnostics.Debug": "4.0.11",
- "System.Globalization": "4.0.11",
- "System.IO": "4.1.0",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Threading": "4.0.11",
- "System.Xml.ReaderWriter": "4.0.11"
- }
- },
- "System.Xml.XPath.XDocument": {
- "type": "Transitive",
- "resolved": "4.0.1",
- "contentHash": "FLhdYJx4331oGovQypQ8JIw2kEmNzCsjVOVYY/16kZTUoquZG85oVn7yUhBE2OZt1yGPSXAL0HTEfzjlbNpM7Q==",
- "dependencies": {
- "System.Diagnostics.Debug": "4.0.11",
- "System.Linq": "4.1.0",
- "System.Resources.ResourceManager": "4.0.1",
- "System.Runtime": "4.1.0",
- "System.Runtime.Extensions": "4.1.0",
- "System.Threading": "4.0.11",
- "System.Xml.ReaderWriter": "4.0.11",
- "System.Xml.XDocument": "4.0.11",
- "System.Xml.XPath": "4.0.1"
- }
- },
- "SonarAnalyzer": {
- "type": "Project",
- "dependencies": {
- "Google.Protobuf": "[3.6.1, )",
- "Google.Protobuf.Tools": "[3.6.1, )",
- "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2, )",
- "Microsoft.Composition": "[1.0.27, )",
- "SonarAnalyzer.CFG": "[1.0.0, )",
- "System.Collections.Immutable": "[1.1.37, )"
- }
- },
- "sonaranalyzer.cfg": {
- "type": "Project",
- "dependencies": {
- "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )",
- "Microsoft.Composition": "[1.0.27, )",
- "System.Collections.Immutable": "[1.1.37, )"
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Extensions/ExpressionSyntaxExtensions.Roslyn.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Extensions/ExpressionSyntaxExtensions.Roslyn.cs
new file mode 100644
index 00000000000..3d1668222e7
--- /dev/null
+++ b/analyzers/src/SonarAnalyzer.VisualBasic/Extensions/ExpressionSyntaxExtensions.Roslyn.cs
@@ -0,0 +1,175 @@
+/*
+ * SonarAnalyzer for .NET
+ * Copyright (C) 2015-2023 SonarSource SA
+ * mailto: contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using System.CodeDom.Compiler;
+
+namespace SonarAnalyzer.Extensions
+{
+ [GeneratedCode("Copied and converted from Roslyn", "5a1cc5f83e4baba57f0355a685a5d1f487bfac66")]
+ internal static class ExpressionSyntaxExtensions
+ {
+ // Copied and converted from
+ // https://github.com/dotnet/roslyn/blob/5a1cc5f83e4baba57f0355a685a5d1f487bfac66/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/ExpressionSyntaxExtensions.vb#L362
+ public static bool IsWrittenTo(this ExpressionSyntax expression, SemanticModel semanticModel, CancellationToken cancellationToken)
+ {
+ if (expression == null)
+ return false;
+
+ if (expression.IsOnlyWrittenTo())
+ return true;
+
+ if (expression.IsRightSideOfDot())
+ expression = expression.Parent as ExpressionSyntax;
+
+ if (expression != null)
+ {
+ if (expression.IsInRefContext(semanticModel, cancellationToken))
+ return true;
+
+ if (expression.Parent is AssignmentStatementSyntax)
+ {
+ var assignmentStatement = (AssignmentStatementSyntax)expression.Parent;
+ if (expression == assignmentStatement.Left)
+ return true;
+ }
+
+ if (expression.IsChildNode(n => n.Name))
+ return true;
+
+ // Extension method with a 'ref' parameter can write to the value it is called on.
+ if (expression.Parent is MemberAccessExpressionSyntax)
+ {
+ var memberAccess = (MemberAccessExpressionSyntax)expression.Parent;
+ if (memberAccess.Expression == expression)
+ {
+ var method = semanticModel.GetSymbolInfo(memberAccess, cancellationToken).Symbol as IMethodSymbol;
+ if (method != null)
+ {
+ if (method.MethodKind == MethodKind.ReducedExtension && method.ReducedFrom.Parameters.Length > 0 && method.ReducedFrom.Parameters.First().RefKind == RefKind.Ref)
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ return false;
+ }
+
+ // copied and converted from
+ // https://github.com/dotnet/roslyn/blob/5a1cc5f83e4baba57f0355a685a5d1f487bfac66/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/ExpressionSyntaxExtensions.vb#L325
+ public static bool IsOnlyWrittenTo(this ExpressionSyntax expression)
+ {
+ if (expression.IsRightSideOfDot())
+ expression = expression.Parent as ExpressionSyntax;
+
+ if (expression != null)
+ {
+ // Sonar: IsInOutContext deleted because not relevant for VB
+ if (expression.IsParentKind(SyntaxKind.SimpleAssignmentStatement))
+ {
+ var assignmentStatement = (AssignmentStatementSyntax)expression.Parent;
+ if (expression == assignmentStatement.Left)
+ return true;
+ }
+
+ if (expression.IsParentKind(SyntaxKind.NameColonEquals) && expression.Parent.IsParentKind(SyntaxKind.SimpleArgument))
+
+ //
+ // this is only a write to Prop
+ return true;
+
+ if (expression.IsChildNode(n => n.Name))
+ return true;
+
+ return false;
+ }
+
+ return false;
+ }
+
+ // Copied and converted from
+ // https://github.com/dotnet/roslyn/blob/5a1cc5f83e4baba57f0355a685a5d1f487bfac66/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/ExpressionSyntaxExtensions.vb#L73
+ public static bool IsRightSideOfDot(this ExpressionSyntax expression)
+ {
+ return expression.IsSimpleMemberAccessExpressionName() || expression.IsRightSideOfQualifiedName();
+ }
+
+ // Copied and converted from
+ // https://github.com/dotnet/roslyn/blob/5a1cc5f83e4baba57f0355a685a5d1f487bfac66/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/ExpressionSyntaxExtensions.vb#L56
+ public static bool IsSimpleMemberAccessExpressionName(this ExpressionSyntax expression)
+ {
+ return expression.IsParentKind(SyntaxKind.SimpleMemberAccessExpression) && ((MemberAccessExpressionSyntax)expression.Parent).Name == expression;
+ }
+
+ // Copied and converted from
+ // https://github.com/dotnet/roslyn/blob/5a1cc5f83e4baba57f0355a685a5d1f487bfac66/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/ExpressionSyntaxExtensions.vb#L78
+ public static bool IsRightSideOfQualifiedName(this ExpressionSyntax expression)
+ {
+ return expression.IsParentKind(SyntaxKind.QualifiedName) && ((QualifiedNameSyntax)expression.Parent).Right == expression;
+ }
+
+ // Copied and converted from
+ // https://github.com/dotnet/roslyn/blob/5a1cc5f83e4baba57f0355a685a5d1f487bfac66/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/ExpressionSyntaxExtensions.vb#L277
+ public static bool IsInRefContext(this ExpressionSyntax expression, SemanticModel semanticModel, CancellationToken cancellationToken)
+ {
+ var simpleArgument = expression?.Parent as SimpleArgumentSyntax;
+
+ if (simpleArgument == null)
+ return false;
+ else if (simpleArgument.IsNamed)
+ {
+ var info = semanticModel.GetSymbolInfo(simpleArgument.NameColonEquals.Name, cancellationToken);
+
+ var parameter = info.Symbol as IParameterSymbol;
+ return parameter != null && parameter.RefKind != RefKind.None;
+ }
+ else
+ {
+ var argumentList = simpleArgument.Parent as ArgumentListSyntax;
+
+ if (argumentList != null)
+ {
+ var parent = argumentList.Parent;
+ var index = argumentList.Arguments.IndexOf(simpleArgument);
+
+ var info = semanticModel.GetSymbolInfo(parent, cancellationToken);
+ var symbol = info.Symbol;
+
+ if (symbol is IMethodSymbol)
+ {
+ var method = (IMethodSymbol)symbol;
+ if (index < method.Parameters.Length)
+ return method.Parameters[index].RefKind != RefKind.None;
+ }
+ else if (symbol is IPropertySymbol)
+ {
+ var prop = (IPropertySymbol)symbol;
+ if (index < prop.Parameters.Length)
+ return prop.Parameters[index].RefKind != RefKind.None;
+ }
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Extensions/SyntaxNodeExtensions.Roslyn.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Extensions/SyntaxNodeExtensions.Roslyn.cs
new file mode 100644
index 00000000000..d11a30a5a72
--- /dev/null
+++ b/analyzers/src/SonarAnalyzer.VisualBasic/Extensions/SyntaxNodeExtensions.Roslyn.cs
@@ -0,0 +1,34 @@
+/*
+ * SonarAnalyzer for .NET
+ * Copyright (C) 2015-2023 SonarSource SA
+ * mailto: contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using System.CodeDom.Compiler;
+
+namespace SonarAnalyzer.Extensions;
+
+[GeneratedCode("Copied and converted from Roslyn", "5a1cc5f83e4baba57f0355a685a5d1f487bfac66")]
+internal static partial class SyntaxNodeExtensions
+{
+ // Copied and converted from
+ // https://github.com/dotnet/roslyn/blob/5a1cc5f83e4baba57f0355a685a5d1f487bfac66/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/SyntaxNodeExtensions.vb#L16
+ public static bool IsParentKind(this SyntaxNode node, SyntaxKind kind)
+ {
+ return node != null && node.Parent.IsKind(kind);
+ }
+}
diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Extensions/SyntaxNodeExtensions.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Extensions/SyntaxNodeExtensions.cs
index 0dc09b66547..3e555ee89ff 100644
--- a/analyzers/src/SonarAnalyzer.VisualBasic/Extensions/SyntaxNodeExtensions.cs
+++ b/analyzers/src/SonarAnalyzer.VisualBasic/Extensions/SyntaxNodeExtensions.cs
@@ -89,6 +89,7 @@ static bool TakesExpressionTree(SymbolInfo info)
EnumMemberDeclarationSyntax x => x.Identifier,
InvocationExpressionSyntax x => x.Expression?.GetIdentifier(),
ModifiedIdentifierSyntax x => x.Identifier,
+ ObjectCreationExpressionSyntax x=> x.Type?.GetIdentifier(),
PredefinedTypeSyntax x => x.Keyword,
ParameterSyntax x => x.Identifier?.GetIdentifier(),
PropertyStatementSyntax x => x.Identifier,
@@ -104,6 +105,7 @@ static bool TakesExpressionTree(SymbolInfo info)
public static ArgumentListSyntax ArgumentList(this SyntaxNode node) =>
node switch
{
+ ArgumentListSyntax argumentList => argumentList,
ArrayCreationExpressionSyntax arrayCreation => arrayCreation.ArrayBounds,
AttributeSyntax attribute => attribute.ArgumentList,
InvocationExpressionSyntax invocation => invocation.ArgumentList,
diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicFacade.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicFacade.cs
index b280347b9c8..3f7bb4d33d5 100644
--- a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicFacade.cs
+++ b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicFacade.cs
@@ -51,9 +51,16 @@ public object FindConstantValue(SemanticModel model, SyntaxNode node) =>
node.FindConstantValue(model);
public IMethodParameterLookup MethodParameterLookup(SyntaxNode invocation, IMethodSymbol methodSymbol) =>
- invocation?.ArgumentList() is { } argumentList
- ? new VisualBasicMethodParameterLookup(argumentList, methodSymbol)
- : null;
+ invocation switch
+ {
+ null => null,
+ AttributeSyntax x => new VisualBasicAttributeParameterLookup(x.ArgumentList.Arguments, methodSymbol),
+ IdentifierNameSyntax
+ {
+ Parent: NameColonEqualsSyntax { Parent: SimpleArgumentSyntax { IsNamed: true, Parent.Parent: AttributeSyntax attribute } }
+ } => new VisualBasicAttributeParameterLookup(attribute.ArgumentList.Arguments, methodSymbol),
+ _ => new VisualBasicMethodParameterLookup(invocation.ArgumentList(), methodSymbol),
+ };
public IMethodParameterLookup MethodParameterLookup(SyntaxNode invocation, SemanticModel semanticModel) =>
invocation?.ArgumentList() is { } argumentList
diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs
index 0cb42fd1178..d7a3764030a 100644
--- a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs
+++ b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs
@@ -108,6 +108,9 @@ public override bool IsMemberAccessOnKnownType(SyntaxNode memberAccess, string n
public override bool IsStatic(SyntaxNode node) =>
Cast(node).IsShared();
+ public override bool IsWrittenTo(SyntaxNode expression, SemanticModel semanticModel, CancellationToken cancellationToken) =>
+ expression is ExpressionSyntax ex && ex.IsWrittenTo(semanticModel, cancellationToken);
+
public override SyntaxKind Kind(SyntaxNode node) => node.Kind();
public override string LiteralText(SyntaxNode literal) =>
diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicTrackerFacade.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicTrackerFacade.cs
index d6912a40147..f573cc99a33 100644
--- a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicTrackerFacade.cs
+++ b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicTrackerFacade.cs
@@ -31,5 +31,6 @@ internal sealed class VisualBasicTrackerFacade : ITrackerFacade
public MethodDeclarationTracker MethodDeclaration { get; } = new VisualBasicMethodDeclarationTracker();
public ObjectCreationTracker ObjectCreation { get; } = new VisualBasicObjectCreationTracker();
public PropertyAccessTracker PropertyAccess { get; } = new VisualBasicPropertyAccessTracker();
+ public ArgumentTracker Argument => new VisualBasicArgumentTracker();
}
}
diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Helpers/VisualBasicAttributeParameterLookup.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Helpers/VisualBasicAttributeParameterLookup.cs
new file mode 100644
index 00000000000..60979bf8a55
--- /dev/null
+++ b/analyzers/src/SonarAnalyzer.VisualBasic/Helpers/VisualBasicAttributeParameterLookup.cs
@@ -0,0 +1,39 @@
+/*
+ * SonarAnalyzer for .NET
+ * Copyright (C) 2015-2024 SonarSource SA
+ * mailto: contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+namespace SonarAnalyzer.Helpers;
+
+internal class VisualBasicAttributeParameterLookup : MethodParameterLookupBase
+{
+ public VisualBasicAttributeParameterLookup(SeparatedSyntaxList argumentList, IMethodSymbol methodSymbol) : base(argumentList, methodSymbol)
+ {
+ }
+
+ protected override SyntaxNode Expression(ArgumentSyntax argument) =>
+ argument.GetExpression();
+
+ protected override SyntaxToken? GetNameColonArgumentIdentifier(ArgumentSyntax argument) =>
+ null;
+
+ protected override SyntaxToken? GetNameEqualsArgumentIdentifier(ArgumentSyntax argument) =>
+ argument is SimpleArgumentSyntax { NameColonEquals.Name.Identifier: var identifier }
+ ? identifier
+ : null;
+}
diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Helpers/VisualBasicMethodParameterLookup.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Helpers/VisualBasicMethodParameterLookup.cs
index 47be470b2fe..c1acde9d473 100644
--- a/analyzers/src/SonarAnalyzer.VisualBasic/Helpers/VisualBasicMethodParameterLookup.cs
+++ b/analyzers/src/SonarAnalyzer.VisualBasic/Helpers/VisualBasicMethodParameterLookup.cs
@@ -39,4 +39,7 @@ public VisualBasicMethodParameterLookup(ArgumentListSyntax argumentList, IMethod
protected override SyntaxNode Expression(ArgumentSyntax argument) =>
argument.GetExpression();
+
+ protected override SyntaxToken? GetNameEqualsArgumentIdentifier(ArgumentSyntax argument) =>
+ null;
}
diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Trackers/VisualBasicArgumentTracker.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Trackers/VisualBasicArgumentTracker.cs
new file mode 100644
index 00000000000..dabc2664fe6
--- /dev/null
+++ b/analyzers/src/SonarAnalyzer.VisualBasic/Trackers/VisualBasicArgumentTracker.cs
@@ -0,0 +1,54 @@
+/*
+ * SonarAnalyzer for .NET
+ * Copyright (C) 2015-2024 SonarSource SA
+ * mailto: contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+namespace SonarAnalyzer.Helpers.Trackers;
+
+public class VisualBasicArgumentTracker : ArgumentTracker
+{
+ protected override SyntaxKind[] TrackedSyntaxKinds => [SyntaxKind.SimpleArgument];
+
+ protected override ILanguageFacade Language => VisualBasicFacade.Instance;
+
+ protected override IReadOnlyCollection ArgumentList(SyntaxNode argumentNode) =>
+ argumentNode is ArgumentSyntax { Parent: ArgumentListSyntax { Arguments: { } list } }
+ ? list
+ : null;
+
+ protected override int? Position(SyntaxNode argumentNode) =>
+ argumentNode is ArgumentSyntax { IsNamed: true }
+ ? null
+ : ArgumentList(argumentNode).IndexOf(x => x == argumentNode);
+
+ protected override RefKind? ArgumentRefKind(SyntaxNode argumentNode) =>
+ null;
+
+ protected override bool InvocationFitsMemberKind(SyntaxNode invokedExpression, InvokedMemberKind memberKind) =>
+ memberKind switch
+ {
+ InvokedMemberKind.Method => invokedExpression is InvocationExpressionSyntax,
+ InvokedMemberKind.Constructor => invokedExpression is ObjectCreationExpressionSyntax,
+ InvokedMemberKind.Indexer => invokedExpression is InvocationExpressionSyntax,
+ InvokedMemberKind.Attribute => invokedExpression is AttributeSyntax,
+ _ => false,
+ };
+
+ protected override bool InvokedMemberFits(SemanticModel model, SyntaxNode invokedExpression, InvokedMemberKind memberKind, Func invokedMemberNameConstraint) =>
+ invokedMemberNameConstraint(invokedExpression.GetName());
+}
diff --git a/analyzers/tests/SonarAnalyzer.Test/Facade/VisualBasicFacadeTest.cs b/analyzers/tests/SonarAnalyzer.Test/Facade/VisualBasicFacadeTest.cs
index 7f765890eec..555296b2908 100644
--- a/analyzers/tests/SonarAnalyzer.Test/Facade/VisualBasicFacadeTest.cs
+++ b/analyzers/tests/SonarAnalyzer.Test/Facade/VisualBasicFacadeTest.cs
@@ -97,8 +97,8 @@ End Class
var root = tree.GetRoot();
var argumentList = root.DescendantNodes().OfType().First();
var method = model.GetDeclaredSymbol(root.DescendantNodes().OfType().First());
- var actual = () => sut.MethodParameterLookup(argumentList, method);
- actual.Should().Throw();
+ var actual = sut.MethodParameterLookup(argumentList, method);
+ actual.Should().NotBeNull().And.BeOfType();
}
[TestMethod]
diff --git a/analyzers/tests/SonarAnalyzer.Test/PackagingTests/RuleTypeMappingCS.cs b/analyzers/tests/SonarAnalyzer.Test/PackagingTests/RuleTypeMappingCS.cs
index b09c62fd2f3..cc4034c6cdc 100644
--- a/analyzers/tests/SonarAnalyzer.Test/PackagingTests/RuleTypeMappingCS.cs
+++ b/analyzers/tests/SonarAnalyzer.Test/PackagingTests/RuleTypeMappingCS.cs
@@ -6856,7 +6856,7 @@ internal static class RuleTypeMappingCS
// ["S6929"],
["S6930"] = "BUG",
// ["S6931"],
- // ["S6932"],
+ ["S6932"] = "CODE_SMELL",
// ["S6933"],
// ["S6934"],
// ["S6935"],
diff --git a/analyzers/tests/SonarAnalyzer.Test/Rules/UseAspNetModelBindingTest.cs b/analyzers/tests/SonarAnalyzer.Test/Rules/UseAspNetModelBindingTest.cs
new file mode 100644
index 00000000000..b9bc1410de5
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.Test/Rules/UseAspNetModelBindingTest.cs
@@ -0,0 +1,291 @@
+/*
+ * SonarAnalyzer for .NET
+ * Copyright (C) 2015-2024 SonarSource SA
+ * mailto: contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using SonarAnalyzer.Rules.CSharp;
+
+namespace SonarAnalyzer.Test.Rules;
+
+[TestClass]
+public class UseAspNetModelBindingTest
+{
+#if NET
+ private readonly VerifierBuilder builderAspNetCore = new VerifierBuilder()
+ .WithOptions(ParseOptionsHelper.FromCSharp12)
+ .AddReferences([
+ AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcCore,
+ AspNetCoreMetadataReference.MicrosoftAspNetCoreHttpAbstractions,
+ AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcViewFeatures,
+ AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcAbstractions,
+ AspNetCoreMetadataReference.MicrosoftAspNetCoreHttpFeatures,
+ AspNetCoreMetadataReference.MicrosoftExtensionsPrimitives,
+ ]);
+
+ [TestMethod]
+ public void UseAspNetModelBinding_NoRegistrationIfNotAspNet() =>
+ new VerifierBuilder().AddSnippet(string.Empty).Verify();
+
+ [TestMethod]
+ public void UseAspNetModelBinding_AspNetCore_CSharp12() =>
+ builderAspNetCore.AddPaths("UseAspNetModelBinding_AspNetCore.cs").Verify();
+
+ [DataTestMethod]
+ [DataRow("Form")]
+ [DataRow("Query")]
+ [DataRow("RouteValues")]
+ [DataRow("Headers")]
+ public void UseAspNetModelBinding_NonCompliantAccess(string property) =>
+ builderAspNetCore.AddSnippet($$""""
+ using Microsoft.AspNetCore.Http;
+ using Microsoft.AspNetCore.Mvc;
+ using Microsoft.AspNetCore.Mvc.Filters;
+ using System;
+ using System.Linq;
+ using System.Threading.Tasks;
+
+ public class TestController : Controller
+ {
+ async Task NoncompliantKeyVariations()
+ {
+ _ = Request.{{property}}[@"key"]; // Noncompliant
+ _ = Request.{{property}}.TryGetValue(@"key", out _); // Noncompliant
+ _ = Request.{{property}}["""key"""]; // Noncompliant
+ _ = Request.{{property}}.TryGetValue("""key""", out _); // Noncompliant
+
+ const string key = "id";
+ _ = Request.{{property}}[key]; // Noncompliant
+ _ = Request.{{property}}.TryGetValue(key, out _); // Noncompliant
+ _ = Request.{{property}}[$"prefix.{key}"]; // Noncompliant
+ _ = Request.{{property}}.TryGetValue($"prefix.{key}", out _); // Noncompliant
+ _ = Request.{{property}}[$"""prefix.{key}"""]; // Noncompliant
+ _ = Request.{{property}}.TryGetValue($"""prefix.{key}""", out _); // Noncompliant
+
+ _ = Request.{{property}}[key: "id"]; // Noncompliant
+ _ = Request.{{property}}.TryGetValue(value: out _, key: "id"); // Noncompliant
+ }
+
+ private static void HandleRequest(HttpRequest request)
+ {
+ _ = request.{{property}}["id"]; // Noncompliant: Containing type is a controller
+ void LocalFunction()
+ {
+ _ = request.{{property}}["id"]; // Noncompliant: Containing type is a controller
+ }
+ static void StaticLocalFunction(HttpRequest request)
+ {
+ _ = request.{{property}}["id"]; // Noncompliant: Containing type is a controller
+ }
+ }
+ }
+ """").Verify();
+
+ [TestMethod]
+ [CombinatorialData]
+ public void UseAspNetModelBinding_CompliantAccess(
+ [DataValues(
+ "_ = {0}.Keys",
+ "_ = {0}.Count",
+ "foreach (var kvp in {0}) {{ }}",
+ "_ = {0}.Select(x => x);",
+ "_ = {0}[key]; // Compliant: The accessed key is not a compile time constant")] string statementFormat,
+ [DataValues("Request", "this.Request", "ControllerContext.HttpContext.Request", "request")] string request,
+ [DataValues("Form", "Headers", "Query", "RouteValues")] string property,
+ [DataValues("[FromForm]", "[FromQuery]", "[FromRoute]", "[FromHeader]")] string attribute) =>
+ builderAspNetCore.AddSnippet($$"""
+ using Microsoft.AspNetCore.Http;
+ using Microsoft.AspNetCore.Mvc;
+ using Microsoft.AspNetCore.Mvc.Filters;
+ using System;
+ using System.Linq;
+ using System.Threading.Tasks;
+
+ public class TestController : Controller
+ {
+ async Task Compliant({{attribute}} string key, HttpRequest request)
+ {
+ {{string.Format(statementFormat, $"{request}.{property}")}};
+ }
+ }
+ """).Verify();
+
+ [DataTestMethod]
+ [DataRow("Form")]
+ [DataRow("Headers")]
+ [DataRow("Query")]
+ [DataRow("RouteValues")]
+ public void UseAspNetModelBinding_DottedExpressions(string property) =>
+ builderAspNetCore.AddSnippet($$"""
+ using Microsoft.AspNetCore.Http;
+ using Microsoft.AspNetCore.Mvc;
+ using Microsoft.AspNetCore.Mvc.Filters;
+ using Microsoft.AspNetCore.Routing;
+ using System;
+ using System.Linq;
+ using System.Threading.Tasks;
+
+ public class TestController : Controller
+ {
+ HttpRequest ValidRequest => Request;
+ IFormCollection Form => Request.Form;
+ IHeaderDictionary Headers => Request.Headers;
+ IQueryCollection Query => Request.Query;
+ RouteValueDictionary RouteValues => Request.RouteValues;
+
+ async Task DottedExpressions()
+ {
+ _ = (true ? Request : Request).{{property}}["id"]; // Noncompliant
+ _ = ValidatedRequest().{{property}}["id"]; // Noncompliant
+ _ = ValidRequest.{{property}}["id"]; // Noncompliant
+ _ = {{property}}["id"]; // Noncompliant
+ _ = this.{{property}}["id"]; // Noncompliant
+ _ = new TestController().{{property}}["id"]; // Noncompliant
+
+ _ = this.Request.{{property}}["id"]; // Noncompliant
+ _ = Request?.{{property}}?["id"]; // Noncompliant
+ _ = Request?.{{property}}?.TryGetValue("id", out _); // Noncompliant
+ _ = Request.{{property}}?.TryGetValue("id", out _); // Noncompliant
+ _ = Request.{{property}}?.TryGetValue("id", out _).ToString(); // Noncompliant
+ _ = HttpContext.Request.{{property}}["id"]; // Noncompliant
+ _ = Request.HttpContext.Request.{{property}}["id"]; // Noncompliant
+ _ = this.ControllerContext.HttpContext.Request.{{property}}["id"]; // Noncompliant
+ var r1 = HttpContext.Request;
+ _ = r1.{{property}}["id"]; // Noncompliant
+ var r2 = ControllerContext;
+ _ = r2.HttpContext.Request.{{property}}["id"]; // Noncompliant
+
+ HttpRequest ValidatedRequest() => Request;
+ }
+ }
+ """).Verify();
+
+ [DataTestMethod]
+ [DataRow("public class MyController: Controller")]
+ [DataRow("public class MyController: ControllerBase")]
+ [DataRow("[Controller] public class My: Controller")]
+ // [DataRow("public class MyController")] FN: Poco controller are not detected
+ public void UseAspNetModelBinding_PocoController(string classDeclaration) =>
+ builderAspNetCore.AddSnippet($$""""
+ using Microsoft.AspNetCore.Http;
+ using Microsoft.AspNetCore.Mvc;
+ using Microsoft.AspNetCore.Mvc.Filters;
+ using System;
+ using System.Linq;
+ using System.Threading.Tasks;
+
+ {{classDeclaration}}
+ {
+ public async Task Action([FromServices]IHttpContextAccessor httpContextAccessor)
+ {
+ _ = httpContextAccessor.HttpContext.Request.Form["id"]; // Noncompliant
+ }
+ }
+ """").Verify();
+
+ [DataTestMethod]
+ [DataRow("public class My")]
+ [DataRow("[NonController] public class My: Controller")]
+ [DataRow("[NonController] public class MyController: Controller")]
+ public void UseAspNetModelBinding_NoController(string classDeclaration) =>
+ builderAspNetCore.AddSnippet($$""""
+ using Microsoft.AspNetCore.Http;
+ using Microsoft.AspNetCore.Mvc;
+ using Microsoft.AspNetCore.Mvc.Filters;
+ using System;
+ using System.Linq;
+ using System.Threading.Tasks;
+
+ {{classDeclaration}}
+ {
+ public async Task Action([FromServices]IHttpContextAccessor httpContextAccessor)
+ {
+ _ = httpContextAccessor.HttpContext.Request.Form["id"]; // Compliant
+ }
+ }
+ """").Verify();
+
+ [DataTestMethod]
+ [DataRow("Form")]
+ [DataRow("Headers")]
+ [DataRow("Query")]
+ [DataRow("RouteValues")]
+ public void UseAspNetModelBinding_NoControllerHelpers(string property) =>
+ builderAspNetCore.AddSnippet($$""""
+ using Microsoft.AspNetCore.Http;
+ using Microsoft.AspNetCore.Mvc;
+ using Microsoft.AspNetCore.Mvc.Filters;
+ using System;
+ using System.Linq;
+ using System.Threading.Tasks;
+
+ static class HttpRequestExtensions
+ {
+ public static void Ext(this HttpRequest request)
+ {
+ _ = request.{{property}}["id"]; // Compliant: Not in a controller
+ }
+ }
+
+ class RequestService
+ {
+ public HttpRequest Request { get; }
+
+ public void HandleRequest(HttpRequest request)
+ {
+ _ = Request.{{property}}["id"]; // Compliant: Not in a controller
+ _ = request.{{property}}["id"]; // Compliant: Not in a controller
+ }
+ }
+ """").Verify();
+
+ [TestMethod]
+ [CombinatorialData]
+ public void UseAspNetModelBinding_InheritanceAccess(
+ [DataValues(
+ ": Controller",
+ ": ControllerBase",
+ ": MyBaseController",
+ ": MyBaseBaseController")]string baseList,
+ [DataValues(
+ """_ = Request.Form["id"]""",
+ """_ = Request.Form.TryGetValue("id", out var _)""",
+ """_ = Request.Headers["id"]""",
+ """_ = Request.Query["id"]""",
+ """_ = Request.RouteValues["id"]""")]string nonCompliantStatement) =>
+ builderAspNetCore.AddSnippet($$""""
+ using Microsoft.AspNetCore.Http;
+ using Microsoft.AspNetCore.Mvc;
+ using Microsoft.AspNetCore.Mvc.Filters;
+ using System;
+ using System.Linq;
+ using System.Threading.Tasks;
+
+ public class MyBaseController : ControllerBase { }
+ public class MyBaseBaseController : MyBaseController { }
+
+ public class MyTestController {{baseList}}
+ {
+ public void Action()
+ {
+ {{nonCompliantStatement}}; // Noncompliant
+ }
+ }
+ """").Verify();
+#endif
+}
diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/UseAspNetModelBinding_AspNetCore.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/UseAspNetModelBinding_AspNetCore.cs
new file mode 100644
index 00000000000..5e47dd444f9
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/UseAspNetModelBinding_AspNetCore.cs
@@ -0,0 +1,214 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+public class TestController : Controller
+{
+ private readonly string Key = "id";
+
+ public IActionResult Post(string key)
+ {
+ _ = Request.Form["id"]; // Noncompliant {{Use model binding instead of accessing the raw request data}}
+ // ^^^^^^^^^^^^
+ _ = Request.Form.TryGetValue("id", out _); // Noncompliant {{Use model binding instead of accessing the raw request data}}
+ // ^^^^^^^^^^^^^^^^^^^^^^^^
+ _ = Request.Form.ContainsKey("id"); // Noncompliant {{Use model binding instead of accessing the raw request data}}
+ // ^^^^^^^^^^^^^^^^^^^^^^^^
+ _ = Request.Headers["id"]; // Noncompliant {{Use model binding instead of accessing the raw request data}}
+ // ^^^^^^^^^^^^^^^
+ _ = Request.Headers.TryGetValue("id", out _); // Noncompliant {{Use model binding instead of accessing the raw request data}}
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ _ = Request.Headers.ContainsKey("id"); // Noncompliant {{Use model binding instead of accessing the raw request data}}
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ _ = Request.Query["id"]; // Noncompliant {{Use model binding instead of accessing the raw request data}}
+ // ^^^^^^^^^^^^^
+ _ = Request.Query.TryGetValue("id", out _); // Noncompliant {{Use model binding instead of accessing the raw request data}}
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^
+ _ = Request.RouteValues["id"]; // Noncompliant {{Use model binding instead of accessing the raw request data}}
+ // ^^^^^^^^^^^^^^^^^^^
+ _ = Request.RouteValues.TryGetValue("id", out _); // Noncompliant {{Use model binding instead of accessing the raw request data}}
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ _ = Request.Form.Files; // Noncompliant {{Use IFormFile or IFormFileCollection binding instead}}
+ // ^^^^^^^^^^^^^^^^^^
+ _ = Request.Form.File\u0073; // Noncompliant {{Use IFormFile or IFormFileCollection binding instead}}
+ // ^^^^^^^^^^^^^^^^^^^^^^^
+ _ = Request.Form.Files["file"]; // Noncompliant {{Use IFormFile or IFormFileCollection binding instead}}
+ // ^^^^^^^^^^^^^^^^^^
+ _ = Request.Form.Files[key]; // Noncompliant {{Use IFormFile or IFormFileCollection binding instead}}
+ // ^^^^^^^^^^^^^^^^^^
+ _ = Request.Form.Files[0]; // Noncompliant {{Use IFormFile or IFormFileCollection binding instead}}
+ // ^^^^^^^^^^^^^^^^^^
+ _ = Request.Form.Files.Any(); // Noncompliant {{Use IFormFile or IFormFileCollection binding instead}}
+ // ^^^^^^^^^^^^^^^^^^
+ _ = Request.Form.Files.Count; // Noncompliant {{Use IFormFile or IFormFileCollection binding instead}}
+ // ^^^^^^^^^^^^^^^^^^
+ _ = Request.Form.Files.GetFile("file"); // Noncompliant {{Use IFormFile or IFormFileCollection binding instead}}
+ // ^^^^^^^^^^^^^^^^^^
+ _ = Request.Form.Files.GetFiles("file"); // Noncompliant {{Use IFormFile or IFormFileCollection binding instead}}
+ // ^^^^^^^^^^^^^^^^^^
+ return default;
+ }
+
+ void MixedAccess_Form(string key)
+ {
+ _ = Request.Form["id"]; // Compliant (a mixed access with constant and non-constant keys is compliant)
+ _ = Request.Form[key]; // Compliant
+ }
+
+ void MixedAccess_Form_Query(string key)
+ {
+ _ = Request.Form["id"]; // Compliant (a mixed access with constant and non-constant keys is compliant)
+ _ = Request.Query[key]; // Compliant
+ }
+
+ void FalseNegatives()
+ {
+ string localKey = "id";
+ _ = Request.Form[localKey]; // FN (Requires SE)
+ _ = Request.Form[Key]; // FN: Key is a readonly field with a constant initializer (Requires cross procedure SE)
+ }
+
+ void FormCollection(IFormCollection form)
+ {
+ _ = form["id"]; // Compliant. Using IFormCollection is model binding
+ }
+
+ void WriteAccess()
+ {
+ Request.Headers["id"] = "Assignment"; // Compliant
+ Request.RouteValues["id"] = "Assignment"; // Compliant
+ }
+
+ async Task Compliant()
+ {
+ _ = Request.Cookies["cookie"]; // Compliant: Cookies are not bound by default
+ _ = Request.QueryString; // Compliant: Accessing the whole raw string is fine.
+ }
+
+ async Task CompliantFormAccess()
+ {
+ var form = await Request.ReadFormAsync(); // Compliant: This might be used for optimization purposes e.g. conditional form value access.
+ _ = form["id"];
+ }
+}
+
+public class CodeBlocksController : Controller
+{
+ public CodeBlocksController()
+ {
+ _ = Request.Form["id"]; // Noncompliant
+ }
+
+ public CodeBlocksController(object o) => _ = Request.Form["id"]; // Noncompliant
+
+ HttpRequest ValidRequest => Request;
+ IFormCollection Form => Request.Form;
+
+ string P1 => Request.Form["id"]; // Noncompliant
+ string P2
+ {
+ get => Request.Form["id"]; // Noncompliant
+ }
+ string P3
+ {
+ get
+ {
+ return Request.Form["id"]; // Noncompliant
+ }
+ }
+ void M1() => _ = Request.Form["id"]; // Noncompliant
+ void M2()
+ {
+ Func f1 = () => Request.Form["id"]; // Noncompliant
+ Func f2 = x => Request.Form["id"]; // Noncompliant
+ Func f3 = delegate (object x) { return Request.Form["id"]; }; // Noncompliant
+ }
+ void M3()
+ {
+ // see also parameterized test "DottedExpressions"
+ _ = Request.Form["id"][0]; // Noncompliant
+ _ = Request?.Form["id"][0]; // Noncompliant
+ _ = Request.Form?["id"][0]; // Noncompliant
+ _ = Request?.Form?["id"][0]; // Noncompliant
+ _ = Request.Form?["id"][0]; // Noncompliant
+
+ _ = Request.Form?["id"][0][0]; // Noncompliant
+ _ = Request.Form?["id"][0]?[0]; // Noncompliant
+ _ = Request.Form["id"][0]?[0]; // Noncompliant
+ }
+ ~CodeBlocksController() => _ = Request.Form["id"]; // Noncompliant
+}
+
+public class OverridesController : Controller
+{
+ public void Action()
+ {
+ _ = Request.Form["id"]; // Noncompliant
+ }
+ private void Undecidable(HttpContext context)
+ {
+ // Implementation: It might be difficult to distinguish between access to "Request" that originate from overrides vs. "Request" access that originate from action methods.
+ // This is especially true for "Request" which originate from parameters like here. We may need to redeclare such cases as FNs (see e.g HandleRequest above).
+ _ = context.Request.Form["id"]; // Undecidable: request may originate from an action method (which supports binding), or from one of the following overrides (which don't).
+ _ = context.Request.Form["id"][0];
+ _ = context.Request?.Form["id"][0];
+ _ = context.Request.Form?["id"][0];
+ _ = context.Request?.Form?["id"][0];
+ _ = context.Request.Form?["id"][0];
+
+ _ = context.Request.Form?["id"][0][0];
+ _ = context.Request.Form?["id"][0]?[0];
+ _ = context.Request.Form["id"][0]?[0];
+ }
+ private void Undecidable(HttpRequest request)
+ {
+ _ = request.Form["id"]; // Undecidable: request may originate from an action method (which supports binding), or from one of the following overloads (which don't).
+ }
+ public override void OnActionExecuted(ActionExecutedContext context)
+ {
+ _ = context.HttpContext.Request.Form["id"]; // Compliant: Model binding is not supported here
+ }
+ public override void OnActionExecuting(ActionExecutingContext context)
+ {
+ _ = context.HttpContext.Request.Form["id"]; // Compliant: Model binding is not supported here
+ }
+ public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
+ {
+ _ = context.HttpContext.Request.Form["id"]; // Compliant: Model binding is not supported here
+ return base.OnActionExecutionAsync(context, next);
+ }
+}
+
+[Controller]
+public class PocoController : IActionFilter, IAsyncActionFilter
+{
+ public void OnActionExecuted(ActionExecutedContext context)
+ {
+ _ = context.HttpContext.Request.Form["id"]; // Compliant: Model binding is not supported here
+ }
+ void IActionFilter.OnActionExecuted(Microsoft.AspNetCore.Mvc.Filters.ActionExecutedContext context)
+ {
+ _ = context.HttpContext.Request.Form["id"]; // Compliant: Model binding is not supported here
+ }
+ public void OnActionExecuting(ActionExecutingContext context)
+ {
+ _ = context.HttpContext.Request.Form["id"]; // Compliant: Model binding is not supported here
+ }
+ void IActionFilter.OnActionExecuting(ActionExecutingContext context)
+ {
+ _ = context.HttpContext.Request.Form["id"]; // Compliant: Model binding is not supported here
+ }
+ public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
+ {
+ _ = context.HttpContext.Request.Form["id"]; // Compliant: Model binding is not supported here
+ return Task.CompletedTask;
+ }
+ Task IAsyncActionFilter.OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
+ {
+ _ = context.HttpContext.Request.Form["id"]; // Compliant: Model binding is not supported here
+ return Task.CompletedTask;
+ }
+}
diff --git a/analyzers/tests/SonarAnalyzer.Test/Trackers/ArgumentTrackerTest.cs b/analyzers/tests/SonarAnalyzer.Test/Trackers/ArgumentTrackerTest.cs
new file mode 100644
index 00000000000..f2848434231
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.Test/Trackers/ArgumentTrackerTest.cs
@@ -0,0 +1,1288 @@
+/*
+ * SonarAnalyzer for .NET
+ * Copyright (C) 2015-2023 SonarSource SA
+ * mailto: contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using Microsoft.CodeAnalysis.Text;
+using SonarAnalyzer.Helpers.Trackers;
+using CS = Microsoft.CodeAnalysis.CSharp.Syntax;
+using VB = Microsoft.CodeAnalysis.VisualBasic.Syntax;
+
+namespace SonarAnalyzer.UnitTest.Trackers;
+
+[TestClass]
+public class ArgumentTrackerTest
+{
+ [TestMethod]
+ public void Method_SimpleArgument()
+ {
+ var snippet = """
+ System.IFormatProvider provider = null;
+ 1.ToString($$provider);
+ """;
+ var (node, model) = ArgumentAndModelCS(WrapInMethodCS(snippet));
+
+ var argument = ArgumentDescriptor.MethodInvocation(KnownType.System_Int32, "ToString", "provider", 0);
+ var (result, context) = MatchArgumentCS(model, node, argument);
+ result.Should().BeTrue();
+ context.Parameter.Name.Should().Be("provider");
+ context.Parameter.Type.Name.Should().Be("IFormatProvider");
+ }
+
+ [TestMethod]
+ public void Method_SimpleArgument_VB()
+ {
+ var snippet = """
+ Dim provider As System.IFormatProvider = Nothing
+ Dim i As Integer
+ i.ToString($$provider)
+ """;
+ var (node, model) = ArgumentAndModelVB(WrapInMethodVB(snippet));
+
+ var argument = ArgumentDescriptor.MethodInvocation(KnownType.System_Int32, "ToString", "provider", 0);
+ var (result, context) = MatchArgumentVB(model, node, argument);
+ result.Should().BeTrue();
+ context.Parameter.Name.Should().Be("provider");
+ context.Parameter.Type.Name.Should().Be("IFormatProvider");
+ }
+
+ [DataTestMethod]
+ [DataRow("""M( $$ , , 1)""", "i", true)]
+ [DataRow("""M( $$ , , 1)""", "j", false)]
+ [DataRow("""M( , $$ , 1)""", "j", true)]
+ [DataRow("""M( , , $$1)""", "k", true)]
+ [DataRow("""M( , , $$1)""", "i", false)]
+ public void Method_OmittedArgument_VB(string invocation, string parameterName, bool expected)
+ {
+ var snippet = $$"""
+ Public Class C
+ Public Sub M(Optional i As Integer = 0, Optional j As Integer = 0, Optional k As Integer = 0)
+ {{invocation}}
+ End Sub
+ End Class
+ """;
+ var (node, model) = ArgumentAndModelVB(snippet);
+
+ var argument = ArgumentDescriptor.MethodInvocation(m => m.Name == "M", (s, c) => s.Equals("M", c), p => p.Name == parameterName, _ => true, null);
+ var (result, _) = MatchArgumentVB(model, node, argument);
+ result.Should().Be(expected);
+ }
+
+ [DataTestMethod]
+ [DataRow("""1.ToString($$provider);""", 0, true)]
+ [DataRow("""1.ToString($$provider);""", 1, false)]
+ [DataRow("""1.ToString("", $$provider);""", 1, true)]
+ [DataRow("""1.ToString("", $$provider);""", 0, false)]
+ [DataRow("""1.ToString("", $$provider: provider);""", 1, true)]
+ [DataRow("""1.ToString("", $$provider: provider);""", 0, true)]
+ [DataRow("""1.ToString($$provider: provider, format: "");""", 1, true)]
+ [DataRow("""1.ToString($$provider: provider, format: "");""", 0, true)]
+ public void Method_Position(string invocation, int position, bool expected)
+ {
+ var snippet = $$"""
+ System.IFormatProvider provider = null;
+ {{invocation}}
+ """;
+ var (node, model) = ArgumentAndModelCS(WrapInMethodCS(snippet));
+
+ var argument = ArgumentDescriptor.MethodInvocation(KnownType.System_Int32, "ToString", "provider", position);
+ var (result, _) = MatchArgumentCS(model, node, argument);
+ result.Should().Be(expected);
+ }
+
+ [DataTestMethod]
+ [DataRow("""i.ToString($$provider)""", 0, true)]
+ [DataRow("""i.ToString($$provider)""", 1, false)]
+ [DataRow("""i.ToString("", $$provider)""", 1, true)]
+ [DataRow("""i.ToString("", $$provider)""", 0, false)]
+ [DataRow("""i.ToString("", $$provider:= provider)""", 1, true)]
+ [DataRow("""i.ToString("", $$provider:= provider)""", 0, true)]
+ [DataRow("""i.ToString($$provider:= provider, format:= "")""", 1, true)]
+ [DataRow("""i.ToString($$provider:= provider, format:= "")""", 0, true)]
+ public void Method_Position_VB(string invocation, int position, bool expected)
+ {
+ var snippet = $$"""
+ Dim provider As System.IFormatProvider = Nothing
+ Dim i As Integer
+ {{invocation}}
+ """;
+ var (node, model) = ArgumentAndModelVB(WrapInMethodVB(snippet));
+
+ var argument = ArgumentDescriptor.MethodInvocation(KnownType.System_Int32, "ToString", "provider", position);
+ var (result, _) = MatchArgumentVB(model, node, argument);
+ result.Should().Be(expected);
+ }
+
+ [DataTestMethod]
+ [DataRow("""int.TryParse("", $$out var result);""")]
+ [DataRow("""int.TryParse("", System.Globalization.NumberStyles.HexNumber, null, $$out var result);""")]
+ public void Method_RefOut_True(string invocation)
+ {
+ var snippet = $$"""
+ System.IFormatProvider provider = null;
+ {{invocation}}
+ """;
+ var (node, model) = ArgumentAndModelCS(WrapInMethodCS(snippet));
+
+ var argument = ArgumentDescriptor.MethodInvocation(KnownType.System_Int32, "TryParse", "result", x => true, RefKind.Out);
+ var (result, context) = MatchArgumentCS(model, node, argument);
+ result.Should().BeTrue();
+ context.Parameter.RefKind.Should().Be(RefKind.Out);
+ }
+
+ [DataTestMethod]
+ [DataRow("""Integer.TryParse("", $$result)""")]
+ [DataRow("""Integer.TryParse("", System.Globalization.NumberStyles.HexNumber, Nothing, $$result)""")]
+ public void Method_RefOut_True_VB(string invocation)
+ {
+ var snippet = $$"""
+ Dim provider As System.IFormatProvider = Nothing
+ Dim result As Integer
+ {{invocation}}
+ """;
+ var (node, model) = ArgumentAndModelVB(WrapInMethodVB(snippet));
+
+ var argument = ArgumentDescriptor.MethodInvocation(KnownType.System_Int32, "TryParse", "result", x => true, RefKind.Out);
+ var (result, _) = MatchArgumentVB(model, node, argument);
+ result.Should().BeTrue();
+ }
+
+ [DataTestMethod]
+ [DataRow("""int.TryParse("", $$out var result);""", RefKind.Ref)]
+ [DataRow("""int.TryParse($$"", out var result);""", RefKind.Out)]
+ [DataRow("""int.TryParse("", System.Globalization.NumberStyles.HexNumber, null, $$out var result);""", RefKind.Ref)]
+ [DataRow("""int.TryParse("", $$System.Globalization.NumberStyles.HexNumber, null, out var result);""", RefKind.Out)]
+ public void Method_RefOut_False(string invocation, RefKind refKind)
+ {
+ var snippet = $$"""
+ System.IFormatProvider provider = null;
+ {{invocation}}
+ """;
+ var (node, model) = ArgumentAndModelCS(WrapInMethodCS(snippet));
+
+ var argument = ArgumentDescriptor.MethodInvocation(KnownType.System_Int32, "TryParse", "result", x => true, refKind);
+ var (result, context) = MatchArgumentCS(model, node, argument);
+ result.Should().BeFalse();
+ context.Parameter.Should().BeNull();
+ }
+
+ [DataTestMethod]
+ [DataRow("""int.TryParse("", $$out var result);""")]
+ [DataRow("""int.TryParse("", System.Globalization.NumberStyles.HexNumber, null, $$out var result);""")]
+ public void Method_RefOut_Unspecified(string invocation)
+ {
+ var snippet = $$"""
+ System.IFormatProvider provider = null;
+ {{invocation}}
+ """;
+ var (node, model) = ArgumentAndModelCS(WrapInMethodCS(snippet));
+
+ var argument = ArgumentDescriptor.MethodInvocation(KnownType.System_Int32, "TryParse", "result", x => true);
+ var (result, context) = MatchArgumentCS(model, node, argument);
+ result.Should().BeTrue();
+ context.Parameter.RefKind.Should().Be(RefKind.Out);
+ }
+
+ [DataTestMethod]
+ [DataRow("""new Direct().M($$1);""", true)]
+ [DataRow("""new DirectDifferentParameterName().M($$1);""", false)] // FN. This would require ExplicitOrImplicitInterfaceImplementations from the internal ISymbolExtensions in Roslyn.
+ [DataRow("""(new Explicit() as I).M($$1);""", true)]
+ [DataRow("""(new ExplicitDifferentParameterName() as I).M($$1);""", true)]
+ public void Method_Inheritance_Interface(string invocation, bool expected)
+ {
+ var snippet = $$"""
+ interface I
+ {
+ void M(int parameter);
+ }
+ public class Direct: I
+ {
+ public void M(int parameter) { }
+ }
+ public class DirectDifferentParameterName: I
+ {
+ public void M(int renamed) { }
+ }
+ public class Explicit: I
+ {
+ void I.M(int parameter) { }
+ }
+ public class ExplicitDifferentParameterName: I
+ {
+ void I.M(int renamed) { }
+ }
+ public class Test
+ {
+ void M()
+ {
+ {{invocation}}
+ }
+ }
+ """;
+ var (node, model) = ArgumentAndModelCS(snippet);
+
+ var argument = ArgumentDescriptor.MethodInvocation(m => true, (m, c) => m.Equals("M", c), p => p.Name == "parameter", x => true, null);
+ var (result, _) = MatchArgumentCS(model, node, argument);
+ result.Should().Be(expected);
+ }
+
+ [DataTestMethod]
+ [DataRow("""Dim a = New Direct().M($$1)""", true)]
+ [DataRow("""Dim a = New DirectDifferentParameterName().M($$1)""", false)] // FN. This would require ExplicitOrImplicitInterfaceImplementations from the internal ISymbolExtensions in Roslyn.
+ [DataRow("""
+ Dim i As I = New Explicit()
+ i.M($$1)
+ """, true)]
+ [DataRow("""
+ Dim i As I = New ExplicitDifferentParameterName()
+ i.M($$1)
+ """, true)]
+ public void Method_Inheritance_Interface_VB(string invocation, bool expected)
+ {
+ var snippet = $$"""
+ Interface I
+ Function M(ByVal parameter As Integer) As Boolean
+ End Interface
+
+ Public Class Direct
+ Implements I
+
+ Public Function M(parameter As Integer) As Boolean Implements I.M
+ End Function
+ End Class
+
+ Public Class DirectDifferentParameterName
+ Implements I
+
+ Public Function M(ByVal renamed As Integer) As Boolean Implements I.M
+ End Function
+ End Class
+
+ Public Class Explicit
+ Implements I
+
+ Private Function M(ByVal parameter As Integer) As Boolean Implements I.M
+ End Function
+ End Class
+
+ Public Class ExplicitDifferentParameterName
+ Implements I
+
+ Private Function M(ByVal renamed As Integer) As Boolean Implements I.M
+ End Function
+ End Class
+
+ Public Class Test
+ Private Sub M()
+ {{invocation}}
+ End Sub
+ End Class
+ """;
+ var (node, model) = ArgumentAndModelVB(snippet);
+
+ var argument = ArgumentDescriptor.MethodInvocation(m => true, (m, c) => m.Equals("M", c), p => p.Name == "parameter", x => true, null);
+ var (result, _) = MatchArgumentVB(model, node, argument);
+ result.Should().Be(expected);
+ }
+
+ [DataTestMethod]
+ [DataRow("""comparer.Compare($$default, default);""")]
+ [DataRow("""new MyComparer().Compare($$1, 2);""")]
+ public void Method_Inheritance_BaseClasses_Generics(string invocation)
+ {
+ var snippet = $$"""
+ using System.Collections.Generic;
+ public class MyComparer : Comparer
+ {
+ public MyComparer() { }
+ public override int Compare(T a, T b) => 1; // The original definition uses x and y: int Compare(T? x, T? y)
+ }
+ public class Test
+ {
+ void M(MyComparer comparer)
+ {
+ {{invocation}}
+ }
+ }
+ """;
+ var (node, model) = ArgumentAndModelCS(snippet);
+
+ var argument = ArgumentDescriptor.MethodInvocation(KnownType.System_Collections_Generic_Comparer_T, "Compare", "x", 0);
+ var (result, _) = MatchArgumentCS(model, node, argument);
+ result.Should().BeTrue();
+ }
+
+ [DataTestMethod]
+ [DataRow("""comparer.Compare($$Nothing, Nothing)""")]
+ [DataRow("""Call New MyComparer(Of Integer)().Compare($$1, 2)""")]
+ public void Method_Inheritance_BaseClasses_Generics_VB(string invocation)
+ {
+ var snippet = $$"""
+ Imports System.Collections.Generic
+
+ Public Class MyComparer(Of T)
+ Inherits Comparer(Of T)
+
+ Public Sub New()
+ End Sub
+
+ Public Overrides Function Compare(ByVal a As T, ByVal b As T) As Integer ' The original definition uses x and y
+ Return 1
+ End Function
+ End Class
+
+ Public Class Test
+ Private Sub M(Of T)(ByVal comparer As MyComparer(Of T))
+ {{invocation}}
+ End Sub
+ End Class
+ """;
+ var (node, model) = ArgumentAndModelVB(snippet);
+
+ var argument = ArgumentDescriptor.MethodInvocation(KnownType.System_Collections_Generic_Comparer_T, "Compare", "x", 0);
+ var (result, _) = MatchArgumentVB(model, node, argument);
+ result.Should().BeTrue();
+ }
+
+ [DataTestMethod]
+ [DataRow("""OnInsert($$1, null);""")]
+ [DataRow("""OnInsert(position: $$1, null);""")]
+ public void Method_Inheritance_BaseClasses_Overrides(string invocation)
+ {
+ var snippet = $$"""
+ using System.Collections;
+ public class Collection : CollectionBase
+ {
+ protected override void OnInsert(int position, object value) { }
+
+ void M(T arg)
+ {
+ {{invocation}}
+ }
+ }
+ """;
+ var (node, model) = ArgumentAndModelCS(snippet);
+
+ var argument = ArgumentDescriptor.MethodInvocation(KnownType.System_Collections_CollectionBase, "OnInsert", "index", 0);
+ var (result, _) = MatchArgumentCS(model, node, argument);
+ result.Should().BeTrue();
+ }
+
+ [DataTestMethod]
+ [DataRow("""OnInsert($$1, Nothing)""")]
+ [DataRow("""OnInsert(position:= $$1, Nothing)""")]
+ public void Method_Inheritance_BaseClasses_Overrides_VB(string invocation)
+ {
+ var snippet = $$"""
+ Imports System.Collections
+
+ Public Class Collection(Of T)
+ Inherits CollectionBase
+
+ Protected Overrides Sub OnInsert(ByVal position As Integer, ByVal value As Object)
+ End Sub
+
+ Private Sub M(ByVal arg As T)
+ {{invocation}}
+ End Sub
+ End Class
+ """;
+ var (node, model) = ArgumentAndModelVB(snippet);
+
+ var argument = ArgumentDescriptor.MethodInvocation(KnownType.System_Collections_CollectionBase, "OnInsert", "index", 0);
+ var (result, _) = MatchArgumentVB(model, node, argument);
+ result.Should().BeTrue();
+ }
+
+ [DataTestMethod]
+ // learn.microsoft.com/en-us/dotnet/api/system.string.format
+ [DataRow("""string.Format("format", $$0)""", "arg0")]
+ [DataRow("""string.Format("format", 0, $$1)""", "arg1")]
+ [DataRow("""string.Format("format", 0, 1, $$2)""", "arg2")]
+ [DataRow("""string.Format("format", 0, 1, 2, $$3)""", "args")]
+ [DataRow("""string.Format("format", arg2: 2, arg1: 1, $$arg0:0)""", "arg0")]
+ [DataRow("""string.Format("format", $$new object[0])""", "args")]
+ public void Method_ParamsArray(string invocation, string parameterName)
+ {
+ var snippet = $$"""
+ _ = {{invocation}};
+ """;
+ var (node, model) = ArgumentAndModelCS(WrapInMethodCS(snippet));
+
+ var argument = ArgumentDescriptor.MethodInvocation(KnownType.System_String, "Format", parameterName, i => i >= 1);
+ var (result, _) = MatchArgumentCS(model, node, argument);
+ result.Should().BeTrue();
+ }
+
+ [DataTestMethod]
+ // learn.microsoft.com/en-us/dotnet/api/system.string.format
+ [DataRow("""String.Format("format", $$0)""", "arg0")]
+ [DataRow("""String.Format("format", 0, $$1)""", "arg1")]
+ [DataRow("""String.Format("format", 0, 1, $$2)""", "arg2")]
+ [DataRow("""String.Format("format", 0, 1, 2, $$3)""", "args")]
+ [DataRow("""String.Format("format", arg2:=2, arg1:=1, $$arg0:=0)""", "arg0")]
+ [DataRow("""String.Format("format", $$New Object(){ })""", "args")]
+ public void Method_ParamsArray_VB(string invocation, string parameterName)
+ {
+ var snippet = $$"""
+ Dim a = {{invocation}}
+ """;
+ var (node, model) = ArgumentAndModelVB(WrapInMethodVB(snippet));
+
+ var argument = ArgumentDescriptor.MethodInvocation(KnownType.System_String, "Format", parameterName, i => i >= 1);
+ var (result, _) = MatchArgumentVB(model, node, argument);
+ result.Should().BeTrue();
+ }
+
+ [TestMethod]
+ public void Method_NamelessMethod()
+ {
+ var snippet = $$"""
+ using System;
+ class C
+ {
+ Action ActionReturning() => null;
+
+ void M()
+ {
+ ActionReturning()($$1);
+ }
+ }
+ """;
+ var (node, model) = ArgumentAndModelCS(snippet);
+
+ var argument = ArgumentDescriptor.MethodInvocation(KnownType.System_Action_T, methodName: string.Empty, "obj", 0);
+ var (result, context) = MatchArgumentCS(model, node, argument);
+ result.Should().BeTrue();
+ context.Parameter.Name.Should().Be("obj");
+ context.Parameter.ContainingSymbol.Name.Should().Be("Invoke");
+ context.Parameter.ContainingType.Name.Should().Be("Action");
+ }
+
+ [TestMethod]
+ public void Method_InvocationOnProperty()
+ {
+ var snippet = $$"""
+ using System.Collections.Generic;
+ class C
+ {
+ public IList List { get; } = new List();
+ void M()
+ {
+ List.Add($$1); // Add is defined on ICollection while the List property is of type IList, invokedMemberNodeConstraint can figure this out
+ }
+ }
+ """;
+ var (node, model) = ArgumentAndModelCS(snippet);
+
+ var argument = ArgumentDescriptor.MethodInvocation(
+ invokedMethodSymbol: x => x.Is(KnownType.System_Collections_Generic_ICollection_T, "Add"),
+ invokedMemberNameConstraint: (x, c) => string.Equals(x, "Add", c),
+ invokedMemberNodeConstraint: (model, language, node) =>
+ node is CS.InvocationExpressionSyntax { Expression: CS.MemberAccessExpressionSyntax { Expression: CS.IdentifierNameSyntax { Identifier.ValueText: { } leftName } left } }
+ && language.NameComparer.Equals(leftName, "List")
+ && model.GetSymbolInfo(left).Symbol is IPropertySymbol property
+ && property.Type.Is(KnownType.System_Collections_Generic_IList_T),
+ parameterConstraint: _ => true,
+ argumentListConstraint: (_, _) => true,
+ refKind: null);
+ var (result, context) = MatchArgumentCS(model, node, argument);
+ result.Should().BeTrue();
+ context.Parameter.Name.Should().Be("item");
+ context.Parameter.ContainingSymbol.Name.Should().Be("Add");
+ context.Parameter.ContainingType.Name.Should().Be("ICollection");
+ }
+
+ [DataTestMethod]
+ [DataRow("""ProcessStartInfo($$"fileName")""", "fileName", 0, true)]
+ [DataRow("""ProcessStartInfo($$"fileName")""", "arguments", 1, false)]
+ [DataRow("""ProcessStartInfo("fileName", $$"arguments")""", "arguments", 1, true)]
+ [DataRow("""ProcessStartInfo("fileName", $$"arguments")""", "arguments", 0, false)]
+ [DataRow("""ProcessStartInfo($$"fileName", "arguments")""", "arguments", 1, false)]
+ [DataRow("""ProcessStartInfo(arguments: $$"arguments", fileName: "fileName")""", "arguments", 1, true)]
+ [DataRow("""ProcessStartInfo(arguments: $$"arguments", fileName: "fileName")""", "fileName", 0, false)]
+ [DataRow("""ProcessStartInfo(arguments: "arguments", $$fileName: "fileName")""", "fileName", 0, true)]
+ [DataRow("""ProcessStartInfo(arguments: "arguments", $$fileName: "fileName")""", "arguments", 1, false)]
+ public void Constructor_SimpleArgument(string constructor, string parameterName, int argumentPosition, bool expected)
+ {
+ var snippet = $$"""
+ _ = new System.Diagnostics.{{constructor}};
+ """;
+ var (node, model) = ArgumentAndModelCS(WrapInMethodCS(snippet));
+
+ var argument = ArgumentDescriptor.ConstructorInvocation(KnownType.System_Diagnostics_ProcessStartInfo, parameterName, argumentPosition);
+ var (result, context) = MatchArgumentCS(model, node, argument);
+ result.Should().Be(expected);
+ if (expected)
+ {
+ context.Parameter.Name.Should().Be(parameterName);
+ context.Parameter.ContainingSymbol.Name.Should().Be(".ctor");
+ context.Parameter.ContainingType.Name.Should().Be("ProcessStartInfo");
+ }
+ }
+
+ [DataTestMethod]
+ [DataRow("""ProcessStartInfo($$"fileName")""", "fileName", 0, true)]
+ [DataRow("""ProcessStartInfo($$"fileName")""", "arguments", 1, false)]
+ [DataRow("""ProcessStartInfo("fileName", $$"arguments")""", "arguments", 1, true)]
+ [DataRow("""ProcessStartInfo("fileName", $$"arguments")""", "arguments", 0, false)]
+ [DataRow("""ProcessStartInfo($$"fileName", "arguments")""", "arguments", 1, false)]
+ [DataRow("""ProcessStartInfo(arguments:= $$"arguments", fileName:= "fileName")""", "arguments", 1, true)]
+ [DataRow("""ProcessStartInfo(arguments:= $$"arguments", fileName:= "fileName")""", "fileName", 0, false)]
+ [DataRow("""ProcessStartInfo(arguments:= "arguments", $$fileName:= "fileName")""", "fileName", 0, true)]
+ [DataRow("""ProcessStartInfo(arguments:= "arguments", $$fileName:= "fileName")""", "arguments", 1, false)]
+ public void Constructor_SimpleArgument_VB(string constructor, string parameterName, int argumentPosition, bool expected)
+ {
+ var snippet = $$"""
+ Dim a = New System.Diagnostics.{{constructor}}
+ """;
+ var (node, model) = ArgumentAndModelVB(WrapInMethodVB(snippet));
+
+ var argument = ArgumentDescriptor.ConstructorInvocation(KnownType.System_Diagnostics_ProcessStartInfo, parameterName, argumentPosition);
+ var (result, _) = MatchArgumentVB(model, node, argument);
+ result.Should().Be(expected);
+ }
+
+ [DataTestMethod]
+ [DataRow("""($$"fileName")""", "fileName", 0, true)]
+ [DataRow("""($$"fileName")""", "arguments", 1, false)]
+ [DataRow("""("fileName", $$"arguments")""", "arguments", 1, true)]
+ [DataRow("""("fileName", $$"arguments")""", "arguments", 0, false)]
+ [DataRow("""($$"fileName", "arguments")""", "arguments", 1, false)]
+ [DataRow("""(arguments: $$"arguments", fileName: "fileName")""", "arguments", 1, true)]
+ [DataRow("""(arguments: $$"arguments", fileName: "fileName")""", "fileName", 0, false)]
+ [DataRow("""(arguments: "arguments", $$fileName: "fileName")""", "fileName", 0, true)]
+ [DataRow("""(arguments: "arguments", $$fileName: "fileName")""", "arguments", 1, false)]
+ public void Constructor_TargetTyped(string constructor, string parameterName, int argumentPosition, bool expected)
+ {
+ var snippet = $$"""
+ System.Diagnostics.ProcessStartInfo psi = new{{constructor}};
+ """;
+ var (node, model) = ArgumentAndModelCS(WrapInMethodCS(snippet));
+
+ var argument = ArgumentDescriptor.ConstructorInvocation(KnownType.System_Diagnostics_ProcessStartInfo, parameterName, argumentPosition);
+ var (result, _) = MatchArgumentCS(model, node, argument);
+ result.Should().Be(expected);
+ }
+
+ [DataTestMethod]
+ [DataRow("""new Dictionary($$1)""", "capacity", 0, true)]
+ [DataRow("""new Dictionary($$1)""", "capacity", 0, true)]
+ [DataRow("""new Dictionary($$1)""", "capacity", 0, true)]
+ [DataRow("""new Dictionary($$1, EqualityComparer.Default)""", "capacity", 0, true)]
+ [DataRow("""new Dictionary(1, $$EqualityComparer.Default)""", "comparer", 1, true)]
+ public void Constructor_Generic(string constructor, string parameterName, int argumentPosition, bool expected)
+ {
+ var snippet = $$"""
+ using System.Collections.Generic;
+ class C
+ {
+ public void M() where TKey : notnull
+ {
+ _ = {{constructor}};
+ }
+ }
+ """;
+ var (node, model) = ArgumentAndModelCS(snippet);
+
+ var argument = ArgumentDescriptor.ConstructorInvocation(KnownType.System_Collections_Generic_Dictionary_TKey_TValue, parameterName, argumentPosition);
+ var (result, _) = MatchArgumentCS(model, node, argument);
+ result.Should().Be(expected);
+ }
+
+ [DataTestMethod]
+ [DataRow("""Dim a = new Dictionary(Of TKey, TValue)($$1)""", "capacity", 0, true)]
+ [DataRow("""Dim a = new Dictionary(Of Integer, TValue)($$1)""", "capacity", 0, true)]
+ [DataRow("""Dim a = new Dictionary(Of Integer, String)($$1)""", "capacity", 0, true)]
+ [DataRow("""Dim a = New Dictionary(Of TKey, TValue)($$1, EqualityComparer(Of TKey).Default)""", "capacity", 0, true)]
+ [DataRow("""Dim a = new Dictionary(Of TKey, TValue)(1, $$EqualityComparer(Of TKey).Default)""", "comparer", 1, true)]
+ public void Constructor_Generic_VB(string constructor, string parameterName, int argumentPosition, bool expected)
+ {
+ var snippet = $$"""
+ Imports System.Collections.Generic
+
+ Class C
+ Public Sub M(Of TKey, TValue)()
+ {{constructor}}
+ End Sub
+ End Class
+ """;
+ var (node, model) = ArgumentAndModelVB(snippet);
+
+ var argument = ArgumentDescriptor.ConstructorInvocation(KnownType.System_Collections_Generic_Dictionary_TKey_TValue, parameterName, argumentPosition);
+ var (result, _) = MatchArgumentVB(model, node, argument);
+ result.Should().Be(expected);
+ }
+
+ [TestMethod]
+ public void Constructor_BaseCall()
+ {
+ var snippet = $$"""
+ using System.Collections.Generic;
+ class MyList: List
+ {
+ public MyList(int capacity) : base(capacity) // Unsupported
+ {
+ }
+ }
+ public class Test
+ {
+ public void M()
+ {
+ _ = new MyList($$1); // Requires tracking of the parameter to the base constructor
+ }
+ }
+ """;
+ var (node, model) = ArgumentAndModelCS(snippet);
+
+ var argument = ArgumentDescriptor.ConstructorInvocation(KnownType.System_Collections_Generic_List_T, "capacity", 0);
+ var (result, _) = MatchArgumentCS(model, node, argument);
+ result.Should().BeFalse();
+ }
+
+ [TestMethod]
+ public void Constructor_BaseCall_VB()
+ {
+ var snippet = $$"""
+ Imports System.Collections.Generic
+
+ Class MyList
+ Inherits List(Of Integer)
+
+ Public Sub New(ByVal capacity As Integer)
+ MyBase.New(capacity) ' Passing of the parameter to the base constructor is not followed
+ End Sub
+ End Class
+
+ Public Class Test
+ Public Sub M()
+ Dim a = New MyList($$1) ' Requires tracking of the parameter to the base constructor
+ End Sub
+ End Class
+ """;
+ var (node, model) = ArgumentAndModelVB(snippet);
+
+ var argument = ArgumentDescriptor.ConstructorInvocation(KnownType.System_Collections_Generic_List_T, "capacity", 0);
+ var (result, _) = MatchArgumentVB(model, node, argument);
+ result.Should().BeFalse();
+ }
+
+ [DataTestMethod]
+ [DataRow("""new NumberList($$1)""", "capacity", 0, false)] // FN. Syntactic checks bail out before the semantic model can resolve the alias
+ [DataRow("""new($$1)""", "capacity", 0, true)] // Target typed new resolves the alias
+ public void Constructor_TypeAlias(string constructor, string parameterName, int argumentPosition, bool expected)
+ {
+ var snippet = $$"""
+ using NumberList = System.Collections.Generic.List;
+ class C
+ {
+ public void M()
+ {
+ NumberList nl = {{constructor}};
+ }
+ }
+ """;
+ var (node, model) = ArgumentAndModelCS(snippet);
+
+ var argument = ArgumentDescriptor.ConstructorInvocation(KnownType.System_Collections_Generic_List_T, parameterName, argumentPosition);
+ var (result, _) = MatchArgumentCS(model, node, argument);
+ result.Should().Be(expected);
+ }
+
+ [TestMethod]
+ public void Constructor_TypeAlias_VB()
+ {
+ var snippet = $$"""
+ Imports NumberList = System.Collections.Generic.List(Of Integer)
+
+ Class C
+ Public Sub M()
+ Dim nl As NumberList = New NumberList($$1)
+ End Sub
+ End Class
+ """;
+ var (node, model) = ArgumentAndModelVB(snippet);
+
+ var argument = ArgumentDescriptor.ConstructorInvocation(KnownType.System_Collections_Generic_List_T, "capacity", 0);
+ var (result, _) = MatchArgumentVB(model, node, argument);
+ result.Should().BeFalse("FN. Syntactic check does not respect aliases.");
+ }
+
+ [DataTestMethod]
+ [DataRow("""new($$1, 2)""", true)]
+ [DataRow("""new C(1, $$2)""", true)]
+ [DataRow("""new CAlias(1, $$2)""", true)]
+ [DataRow("""new C($$1)""", false)] // Count constraint fails
+ [DataRow("""new C(1, 2, $$3)""", false)] // Parameter name constraint fails
+ [DataRow("""new C($$k: 1, j:2, i:3)""", false)] // Parameter name constraint fails
+ public void Constructor_CustomLogic(string constructor, bool expected)
+ {
+ var snippet = $$"""
+ using CAlias = C;
+ class C
+ {
+ public C(int i) { }
+ public C(int j, int i) { }
+ public C(int j, int i, int k) { }
+
+ public void M()
+ {
+ C c = {{constructor}};
+ }
+ }
+ """;
+ var (node, model) = ArgumentAndModelCS(snippet);
+
+ var argument = ArgumentDescriptor.ConstructorInvocation(invokedMethodSymbol: x => x is { MethodKind: MethodKind.Constructor, ContainingSymbol.Name: "C" },
+ invokedMemberNameConstraint: (c, n) => c.Equals("C", n) || c.Equals("CAlias"),
+ invokedMemberNodeConstraint: (_, _, _) => true,
+ parameterConstraint: p => p.Name is "i" or "j",
+ argumentListConstraint: (n, i) => i is null or 0 or 1 && n.Count > 1,
+ refKind: null);
+ var (result, _) = MatchArgumentCS(model, node, argument);
+ result.Should().Be(expected);
+ }
+
+ [TestMethod]
+ public void Constructor_InitializerCalls_This()
+ {
+ var snippet = $$"""
+ class Base
+ {
+ public Base(int i) : this($$i, 1) { }
+ public Base(int i, int j) { }
+ }
+ """;
+ var (node, model) = ArgumentAndModelCS(snippet);
+
+ var argument = ArgumentDescriptor.ConstructorInvocation(invokedMethodSymbol: x => x is { MethodKind: MethodKind.Constructor, ContainingSymbol.Name: "Base" },
+ invokedMemberNameConstraint: (c, n) => c.Equals("Base", n),
+ invokedMemberNodeConstraint: (_, _, _) => true,
+ parameterConstraint: p => p.Name is "i",
+ argumentListConstraint: (_, _) => true,
+ refKind: null);
+ var (result, _) = MatchArgumentCS(model, node, argument);
+ result.Should().BeTrue();
+ }
+
+ [TestMethod]
+ public void Constructor_InitializerCalls_Base()
+ {
+ var snippet = $$"""
+ class Base
+ {
+ public Base(int i) { }
+ }
+ class Derived: Base
+ {
+ public Derived() : base($$1) { }
+ }
+ """;
+ var (node, model) = ArgumentAndModelCS(snippet);
+
+ var argument = ArgumentDescriptor.ConstructorInvocation(invokedMethodSymbol: x => x is { MethodKind: MethodKind.Constructor, ContainingSymbol.Name: "Base" },
+ invokedMemberNameConstraint: (c, n) => c.Equals("Base", n),
+ invokedMemberNodeConstraint: (_, _, _) => true,
+ parameterConstraint: p => p.Name is "i",
+ argumentListConstraint: (_, _) => true,
+ refKind: null);
+ var (result, _) = MatchArgumentCS(model, node, argument);
+ result.Should().BeTrue();
+ }
+
+ [TestMethod]
+ public void Constructor_InitializerCalls_Base_MyException()
+ {
+ var snippet = """
+ using System;
+
+ class MyException: Exception
+ {
+ public MyException(string message) : base($$message)
+ { }
+ }
+ """;
+ var (node, model) = ArgumentAndModelCS(snippet);
+
+ var argument = ArgumentDescriptor.ConstructorInvocation(KnownType.System_Exception, "message", 0);
+ var (result, _) = MatchArgumentCS(model, node, argument);
+ result.Should().BeTrue();
+ }
+
+ [TestMethod]
+ public void Constructor_InitializerCalls_Base_MyException_VB()
+ {
+ var snippet = $$"""
+ Imports System
+
+ Public Class MyException
+ Inherits Exception
+
+ Public Sub New(ByVal message As String)
+ MyBase.New($$message)
+ End Sub
+ End Class
+ """;
+ var (node, model) = ArgumentAndModelVB(snippet);
+
+ var argument = ArgumentDescriptor.ConstructorInvocation(KnownType.System_Exception, "message", 0);
+ var (result, _) = MatchArgumentVB(model, node, argument);
+ result.Should().BeFalse("FN. MyBase.New and Me.New are not supported.");
+ }
+
+ [TestMethod]
+ public void Indexer_List_Get()
+ {
+ var snippet = $$"""
+ var list = new System.Collections.Generic.List();
+ _ = list[$$1];
+ """;
+ var (node, model) = ArgumentAndModelCS(WrapInMethodCS(snippet));
+
+ var argument = ArgumentDescriptor.ElementAccess(KnownType.System_Collections_Generic_List_T, "list",
+ p => p is { Name: "index", Type.SpecialType: SpecialType.System_Int32, ContainingSymbol: IMethodSymbol { MethodKind: MethodKind.PropertyGet } }, 0);
+ var (result, context) = MatchArgumentCS(model, node, argument);
+ result.Should().BeTrue();
+ context.Parameter.Name.Should().Be("index");
+ var associatedSymbol = context.Parameter.ContainingSymbol.Should().BeAssignableTo().Which.AssociatedSymbol.Should().BeAssignableTo().Which;
+ associatedSymbol.IsIndexer.Should().BeTrue();
+ associatedSymbol.Name.Should().Be("this[]");
+ }
+
+ [TestMethod]
+ public void Indexer_List_Get_VB()
+ {
+ var snippet = $$"""
+ Dim list = New System.Collections.Generic.List(Of Integer)()
+ Dim a = list($$1)
+ """;
+ var (node, model) = ArgumentAndModelVB(WrapInMethodVB(snippet));
+
+ var argument = ArgumentDescriptor.ElementAccess(KnownType.System_Collections_Generic_List_T, "list",
+ p => p is { Name: "index", Type.SpecialType: SpecialType.System_Int32, ContainingSymbol: IMethodSymbol { MethodKind: MethodKind.PropertyGet } }, 0);
+ var (result, context) = MatchArgumentVB(model, node, argument);
+ result.Should().BeTrue();
+ context.Parameter.Name.Should().Be("index");
+ var associatedSymbol = context.Parameter.ContainingSymbol.Should().BeAssignableTo().Which.AssociatedSymbol.Should().BeAssignableTo().Which;
+ associatedSymbol.IsIndexer.Should().BeTrue();
+ associatedSymbol.Name.Should().Be("Item");
+ }
+
+ [DataTestMethod]
+ [DataRow("list[$$1] = 1;")]
+ [DataRow("(list[$$1], list[2]) = (1, 2);")]
+ [DataRow("list[$$1]++;")]
+ [DataRow("list[$$1]--;")]
+ public void Indexer_List_Set(string writeExpression)
+ {
+ var snippet = $$"""
+ var list = new System.Collections.Generic.List();
+ {{writeExpression}};
+ """;
+ var (node, model) = ArgumentAndModelCS(WrapInMethodCS(snippet));
+
+ var argument = ArgumentDescriptor.ElementAccess(KnownType.System_Collections_Generic_List_T,
+ p => p is { Name: "index", ContainingSymbol: IMethodSymbol { MethodKind: MethodKind.PropertySet } }, 0);
+ var (result, context) = MatchArgumentCS(model, node, argument);
+ result.Should().BeTrue();
+ context.Parameter.ContainingSymbol.Should().BeAssignableTo().Which.MethodKind.Should().Be(MethodKind.PropertySet);
+ }
+
+ [DataTestMethod]
+ [DataRow("list($$1) = 1")]
+ [DataRow("list($$1) += 1")]
+ [DataRow("list($$1) -= 1")]
+ public void Indexer_List_Set_VB(string writeExpression)
+ {
+ var snippet = $$"""
+ Dim list = New System.Collections.Generic.List(Of Integer)()
+ {{writeExpression}}
+ """;
+ var (node, model) = ArgumentAndModelVB(WrapInMethodVB(snippet));
+
+ var argument = ArgumentDescriptor.ElementAccess(KnownType.System_Collections_Generic_List_T,
+ p => p is { Name: "index", ContainingSymbol: IMethodSymbol { MethodKind: MethodKind.PropertySet } }, 0);
+ var (result, context) = MatchArgumentVB(model, node, argument);
+ result.Should().BeTrue();
+ context.Parameter.ContainingSymbol.Should().BeAssignableTo().Which.MethodKind.Should().Be(MethodKind.PropertySet);
+ }
+
+ [DataTestMethod]
+ [DataRow("""Environment.GetEnvironmentVariables()[$$"TEMP"]""")]
+ [DataRow("""Environment.GetEnvironmentVariables()?[$$"TEMP"]""")]
+ public void Indexer_DictionaryGet(string environmentVariableAccess)
+ {
+ var snippet = $$"""
+ _ = {{environmentVariableAccess}};
+ """;
+ var (node, model) = ArgumentAndModelCS(WrapInMethodCS(snippet));
+
+ var argument = ArgumentDescriptor.ElementAccess(m => m is { MethodKind: MethodKind.PropertyGet, ContainingType: { } type } && type.Name == "IDictionary",
+ (n, c) => n.Equals("GetEnvironmentVariables", c), (_, _, _) => true, p => p.Name == "key", (_, p) => p is null or 0);
+ var (result, _) = MatchArgumentCS(model, node, argument);
+ result.Should().BeTrue();
+ }
+
+ [TestMethod]
+ public void Indexer_DictionaryGet_VB()
+ {
+ var snippet = """
+ Dim a = Environment.GetEnvironmentVariables()($$"TEMP")
+ """;
+ var (node, model) = ArgumentAndModelVB(WrapInMethodVB(snippet));
+
+ var argument = ArgumentDescriptor.ElementAccess(m => m is { MethodKind: MethodKind.PropertyGet, ContainingType: { } type } && type.Name == "IDictionary",
+ (n, c) => n.Equals("GetEnvironmentVariables", c), (_, _, _) => true, p => p.Name == "key", (_, p) => p is null or 0);
+ var (result, _) = MatchArgumentVB(model, node, argument);
+ result.Should().BeTrue();
+ }
+
+ [DataTestMethod]
+ [DataRow("""_ = this[$$0,0];""", "x", true)]
+ [DataRow("""_ = this[0,$$0];""", "y", true)]
+ [DataRow("""_ = this[$$y: 0,x: 0];""", "y", true)]
+ [DataRow("""_ = this[y: 0,$$x: 0];""", "x", true)]
+ [DataRow("""this[$$0, 0] = 1;""", "x", false)]
+ [DataRow("""this[0, $$0] = 1;""", "y", false)]
+ [DataRow("""this[y: $$0, x: 0] = 1;""", "y", false)]
+ [DataRow("""this[y: 0, $$x: 0] = 1;""", "x", false)]
+ public void Indexer_MultiDimensional(string access, string parameterName, bool isGetter)
+ {
+ var snippet = $$"""
+ public class C {
+ public int this[int x, int y]
+ {
+ get => 1;
+ set { }
+ }
+
+ public void M() {
+ {{access}}
+ }
+ }
+ """;
+ var (node, model) = ArgumentAndModelCS(snippet);
+
+ var argument = ArgumentDescriptor.ElementAccess(
+ m => m is { MethodKind: var kind, ContainingType: { } type } && type.Name == "C" && (isGetter ? kind == MethodKind.PropertyGet : kind == MethodKind.PropertySet),
+ (n, c) => true,
+ (_, _, _) => true,
+ p => p.Name == parameterName, (_, _) => true);
+ var (result, _) = MatchArgumentCS(model, node, argument);
+ result.Should().BeTrue();
+ }
+
+ [DataTestMethod]
+ [DataRow("""Dim a = Me($$0, 0)""", "x", true)]
+ [DataRow("""Dim a = Me(0, $$0)""", "y", true)]
+ [DataRow("""Dim a = Me(y := $$0, x := 0)""", "y", true)]
+ [DataRow("""Dim a = Me(y := 0, $$x := 0)""", "x", true)]
+ [DataRow("""Me($$0, 0) = 1""", "x", false)]
+ [DataRow("""Me(0, $$0) = 1""", "y", false)]
+ [DataRow("""Me(y := $$0, x := 0) = 1""", "y", false)]
+ [DataRow("""Me(y := 0, $$x := 0) = 1""", "x", false)]
+ public void Indexer_MultiDimensional_VB(string access, string parameterName, bool isGetter)
+ {
+ var snippet = $$"""
+ Public Class C
+ Default Public Property Item(ByVal x As Integer, ByVal y As Integer) As Integer
+ Get
+ Return 1
+ End Get
+ Set(ByVal value As Integer)
+ End Set
+ End Property
+
+ Public Sub M()
+ {{access}}
+ End Sub
+ End Class
+ """;
+ var (node, model) = ArgumentAndModelVB(snippet);
+
+ var argument = ArgumentDescriptor.ElementAccess(
+ m => m is { MethodKind: var kind, ContainingType: { } type } && type.Name == "C" && (isGetter ? kind == MethodKind.PropertyGet : kind == MethodKind.PropertySet),
+ (n, c) => true,
+ (_, _, _) => true,
+ p => p.Name == parameterName, (_, _) => true);
+ var (result, _) = MatchArgumentVB(model, node, argument);
+ result.Should().BeTrue();
+ }
+
+ [DataTestMethod]
+ [DataRow("""process.Modules[$$0]""")]
+ [DataRow("""process?.Modules[$$0]""")]
+ [DataRow("""process.Modules?[$$0]""")]
+ [DataRow("""process?.Modules?[$$0]""")]
+ [DataRow("""process.Modules[index: $$0]""")]
+ [DataRow("""process?.Modules?[index: $$0]""")]
+ public void Indexer_ModulesAccess(string modulesAccess)
+ {
+ var snippet = $$"""
+ public class Test
+ {
+ public void M(System.Diagnostics.Process process)
+ {
+ _ = {{modulesAccess}};
+ }
+ }
+ """;
+ var (node, model) = ArgumentAndModelCS(snippet);
+
+ var argument = ArgumentDescriptor.ElementAccess(m => m is { MethodKind: MethodKind.PropertyGet, ContainingType: { } type } && type.Name == "ProcessModuleCollection",
+ (n, c) => n.Equals("Modules", c), (_, _, _) => true, p => p.Name == "index", (_, p) => p is null or 0);
+ var (result, _) = MatchArgumentCS(model, node, argument);
+ result.Should().BeTrue();
+ }
+
+ [DataTestMethod]
+ [DataRow("""processStartInfo.Environment[$$"TEMP"]""")]
+ [DataRow("""processStartInfo?.Environment[$$"TEMP"]""")]
+ [DataRow("""processStartInfo.Environment?[$$"TEMP"]""")]
+ [DataRow("""processStartInfo?.Environment?[$$"TEMP"]""")]
+ [DataRow("""processStartInfo.Environment[key: $$"TEMP"]""")]
+ [DataRow("""processStartInfo?.Environment?[key: $$"TEMP"]""")]
+ public void Indexer_Environment(string environmentAccess)
+ {
+ var snippet = $$"""
+ public class Test
+ {
+ public void M(System.Diagnostics.ProcessStartInfo processStartInfo)
+ {
+ _ = {{environmentAccess}};
+ }
+ }
+ """;
+ var (node, model) = ArgumentAndModelCS(snippet);
+
+ var argument = ArgumentDescriptor.ElementAccess(KnownType.System_Collections_Generic_IDictionary_TKey_TValue, "Environment", p => p.Name == "key", 0);
+ var (result, _) = MatchArgumentCS(model, node, argument);
+ result.Should().BeTrue();
+ }
+
+#if NET5_0_OR_GREATER
+
+ [TestMethod]
+ public void Attribute_Obsolete()
+ {
+ var snippet = $$"""
+ using System;
+ public class Test
+ {
+ [Obsolete($$"message", UrlFormat = "")]
+ public void M()
+ {
+ }
+ }
+ """;
+ var (node, model) = ArgumentAndModelCS(snippet);
+
+ var argument = ArgumentDescriptor.AttributeArgument(x => x is { MethodKind: MethodKind.Constructor, ContainingType.Name: "ObsoleteAttribute" },
+ (s, c) => s.StartsWith("Obsolete", c), (_, _, _) => true, p => p.Name == "message", (_, i) => i is 0);
+ var (result, _) = MatchArgumentCS(model, node, argument);
+ result.Should().BeTrue();
+ }
+
+ [TestMethod]
+ public void Attribute_Obsolete_VB()
+ {
+ var snippet = $$"""
+ Imports System
+
+ Public Class Test
+
+ Public Sub M()
+ End Sub
+ End Class
+ """;
+ var (node, model) = ArgumentAndModelVB(snippet);
+
+ var argument = ArgumentDescriptor.AttributeArgument(x => x is { MethodKind: MethodKind.Constructor, ContainingType.Name: "ObsoleteAttribute" },
+ (s, c) => s.StartsWith("Obsolete", c), (_, _, _) => true, p => p.Name == "message", (_, i) => i is 0);
+ var (result, _) = MatchArgumentVB(model, node, argument);
+ result.Should().BeTrue();
+ }
+
+#endif
+
+ [DataTestMethod]
+ [DataRow("""[Designer($$"designerTypeName")]""", "designerTypeName", 0)]
+ [DataRow("""[DesignerAttribute($$"designerTypeName")]""", "designerTypeName", 0)]
+ [DataRow("""[DesignerAttribute($$"designerTypeName", "designerBaseTypeName")]""", "designerTypeName", 0)]
+ [DataRow("""[DesignerAttribute("designerTypeName", $$"designerBaseTypeName")]""", "designerBaseTypeName", 1)]
+ [DataRow("""[Designer($$designerBaseTypeName: "designerBaseTypeName", designerTypeName: "designerTypeName")]""", "designerBaseTypeName", 1)]
+ [DataRow("""[Designer(designerBaseTypeName: "designerBaseTypeName", $$designerTypeName: "designerTypeName")]""", "designerTypeName", 0)]
+ [DataRow("""[Designer(designerBaseTypeName: "designerBaseTypeName", $$designerTypeName: "designerTypeName")]""", "designerTypeName", 1)]
+ public void Attribute_Designer(string attribute, string parameterName, int argumentPosition)
+ {
+ var snippet = $$"""
+ using System.ComponentModel;
+
+ {{attribute}}
+ public class Test
+ {
+ public void M()
+ {
+ }
+ }
+ """;
+ var (node, model) = ArgumentAndModelCS(snippet);
+
+ var argument = ArgumentDescriptor.AttributeArgument("Designer", parameterName, argumentPosition);
+ var (result, _) = MatchArgumentCS(model, node, argument);
+ result.Should().BeTrue();
+ }
+
+ [DataTestMethod]
+ [DataRow("""""", "designerTypeName", 0)]
+ [DataRow("""""", "designerTypeName", 0)]
+ [DataRow("""""", "designerTypeName", 0)]
+ [DataRow("""""", "designerBaseTypeName", 1)]
+ public void Attribute_Designer_VB(string attribute, string parameterName, int argumentPosition)
+ {
+ var snippet = $$"""
+ Imports System.ComponentModel
+
+ {{attribute}}
+ Public Class Test
+ End Class
+ """;
+ var (node, model) = ArgumentAndModelVB(snippet);
+
+ var argument = ArgumentDescriptor.AttributeArgument("Designer", parameterName, argumentPosition);
+ var (result, _) = MatchArgumentVB(model, node, argument);
+ result.Should().BeTrue();
+ }
+
+ [DataTestMethod]
+ [DataRow("""[AttributeUsage(AttributeTargets.All, $$AllowMultiple = true)]""", "AllowMultiple", true)]
+ [DataRow("""[AttributeUsage(AttributeTargets.All, $$AllowMultiple = true, Inherited = true)]""", "AllowMultiple", true)]
+ [DataRow("""[AttributeUsage(AttributeTargets.All, $$AllowMultiple = true, Inherited = true)]""", "Inherited", false)]
+ [DataRow("""[AttributeUsage(AttributeTargets.All, AllowMultiple = true, $$Inherited = true)]""", "Inherited", true)]
+ public void Attribute_Property(string attribute, string propertyName, bool expected)
+ {
+ var snippet = $$"""
+ using System;
+
+ {{attribute}}
+ public sealed class TestAttribute: Attribute
+ {
+ }
+ """;
+ var (node, model) = ArgumentAndModelCS(snippet);
+
+ var argument = ArgumentDescriptor.AttributeProperty("AttributeUsage", propertyName);
+ var (result, context) = MatchArgumentCS(model, node, argument);
+ result.Should().Be(expected);
+ if (result)
+ {
+ // The mapped parameter is the "value" parameter of the property set method.
+ context.Parameter.Name.Should().Be("value");
+ var method = context.Parameter.ContainingSymbol.Should().BeAssignableTo().Which;
+ method.MethodKind.Should().Be(MethodKind.PropertySet);
+ method.AssociatedSymbol.Should().BeAssignableTo().Which.Name.Should().Be(propertyName);
+ }
+ }
+
+ [DataTestMethod]
+ [DataRow("""""", "AllowMultiple", true)]
+ [DataRow("""""", "AllowMultiple", true)]
+ [DataRow("""""", "Inherited", false)]
+ [DataRow("""""", "Inherited", true)]
+ public void Attribute_Property_VB(string attribute, string propertyName, bool expected)
+ {
+ var snippet = $$"""
+ Imports System
+
+ {{attribute}}
+ Public NotInheritable Class TestAttribute
+ Inherits Attribute
+ End Class
+ """;
+ var (node, model) = ArgumentAndModelVB(snippet);
+
+ var argument = ArgumentDescriptor.AttributeProperty("AttributeUsage", propertyName);
+ var (result, context) = MatchArgumentVB(model, node, argument);
+ result.Should().Be(expected);
+ if (result)
+ {
+ // The mapped parameter is the "value" parameter of the property set method.
+ context.Parameter.Name.Should().Be("value");
+ var method = context.Parameter.ContainingSymbol.Should().BeAssignableTo().Which;
+ method.MethodKind.Should().Be(MethodKind.PropertySet);
+ method.AssociatedSymbol.Should().BeAssignableTo().Which.Name.Should().Be(propertyName);
+ }
+ }
+
+ private static string WrapInMethodCS(string snippet) =>
+ $$"""
+ using System;
+ class C
+ {
+ public void M()
+ {
+ {{snippet}}
+ }
+ }
+ """;
+
+ private static string WrapInMethodVB(string snippet) =>
+ $$"""
+ Imports System
+ Class C
+ Public Sub M()
+ {{snippet}}
+ End Sub
+ End Class
+ """;
+
+ private static (SyntaxNode Node, SemanticModel Model) ArgumentAndModel(string snippet,
+ Func compile, params Type[] argumentNodeTypes)
+ {
+ var pos = snippet.IndexOf("$$");
+ if (pos == -1)
+ {
+ throw new InvalidOperationException("The $$ maker was not found");
+ }
+ snippet = snippet.Replace("$$", string.Empty);
+ var (tree, model) = compile(snippet, MetadataReferenceFacade.SystemCollections.Concat(MetadataReferenceFacade.SystemDiagnosticsProcess).ToArray());
+ var node = tree.GetRoot().DescendantNodesAndSelf(new TextSpan(pos, 1)).Reverse().First(x => argumentNodeTypes.Any(t => t.IsInstanceOfType(x))); // root.Find does not work with OmittedArgument
+ return (node, model);
+ }
+
+ private static (SyntaxNode Node, SemanticModel Model) ArgumentAndModelCS(string snippet) =>
+ ArgumentAndModel(snippet, TestHelper.CompileCS, typeof(CS.ArgumentSyntax), typeof(CS.AttributeArgumentSyntax));
+
+ private static (SyntaxNode Node, SemanticModel Model) ArgumentAndModelVB(string snippet) =>
+ ArgumentAndModel(snippet, TestHelper.CompileVB, typeof(VB.ArgumentSyntax));
+
+ private static (bool Match, ArgumentContext Context) MatchArgumentCS(SemanticModel model, SyntaxNode node, ArgumentDescriptor descriptor) =>
+ MatchArgument(model, node, descriptor);
+
+ private static (bool Match, ArgumentContext Context) MatchArgumentVB(SemanticModel model, SyntaxNode node, ArgumentDescriptor descriptor) =>
+ MatchArgument(model, node, descriptor);
+
+ private static (bool Match, ArgumentContext Context) MatchArgument(SemanticModel model, SyntaxNode node, ArgumentDescriptor descriptor)
+ where TTracker : ArgumentTracker, new()
+ where TSyntaxKind : struct
+ {
+ var context = new ArgumentContext(node, model);
+ var result = new TTracker().MatchArgument(descriptor)(context);
+ return (result, context);
+ }
+}
diff --git a/analyzers/tests/SonarAnalyzer.TestFramework.Test/Common/CombinatorialDataAttributeTest.cs b/analyzers/tests/SonarAnalyzer.TestFramework.Test/Common/CombinatorialDataAttributeTest.cs
new file mode 100644
index 00000000000..81c5f65b075
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.TestFramework.Test/Common/CombinatorialDataAttributeTest.cs
@@ -0,0 +1,122 @@
+/*
+ * SonarAnalyzer for .NET
+ * Copyright (C) 2015-2024 SonarSource SA
+ * mailto: contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+namespace SonarAnalyzer.Test.TestFramework.Tests.Common;
+
+[TestClass]
+
+public class CombinatorialDataAttributeTest_TwoDimensions
+{
+ private static List<(int X, int Y)> combinations;
+
+ [ClassInitialize]
+ public static void Initialize(TestContext context) =>
+ combinations = new();
+
+ [TestMethod]
+ [CombinatorialData]
+#pragma warning disable S2699 // Tests should include assertions. Assertion happens in cleanup
+ public void Combinatorial([DataValues(1, 2, 3)] int x, [DataValues(-1, -2, -3)] int y) =>
+ combinations.Add((x, y));
+#pragma warning restore S2699
+
+ [ClassCleanup]
+ public static void Cleanup() =>
+ combinations.Should().BeEquivalentTo([
+ (1, -1),
+ (1, -2),
+ (1, -3),
+ (2, -1),
+ (2, -2),
+ (2, -3),
+ (3, -1),
+ (3, -2),
+ (3, -3),
+ ]);
+}
+
+[TestClass]
+public class CombinatorialDataAttributeTest_ThreeDimensions
+{
+ private static List<(int X, string Y, bool Z)> combinations;
+
+ [ClassInitialize]
+ public static void Initialize(TestContext context)
+ {
+ combinations = new();
+ }
+
+ [TestMethod]
+ [CombinatorialData]
+#pragma warning disable S2699 // Tests should include assertions. Assertion happens in cleanup
+ public void Combinatorial([DataValues(1, 2, 3)] int x, [DataValues("A", "B")] string y, [DataValues(true, false)] bool z)
+#pragma warning restore S2699
+ {
+ combinations.Add((x, y, z));
+ }
+
+ [ClassCleanup]
+ public static void Cleanup()
+ {
+ combinations.Should().BeEquivalentTo([
+ (1, "A", true),
+ (1, "B", true),
+ (1, "A", false),
+ (1, "B", false),
+ (2, "A", true),
+ (2, "B", true),
+ (2, "A", false),
+ (2, "B", false),
+ (3, "A", true),
+ (3, "B", true),
+ (3, "A", false),
+ (3, "B", false),
+ ]);
+ }
+}
+
+[TestClass]
+public class CombinatorialDataAttributeTest_AttributeTest
+{
+ [TestMethod]
+ public void CombinatorialData()
+ {
+ var attribute = new CombinatorialDataAttribute();
+ var data = attribute.GetData(typeof(CombinatorialDataAttributeTest_AttributeTest).GetMethod(nameof(Combinatorial)));
+ data.Should().BeEquivalentTo([
+ [1, "A", true],
+ [1, "B", true],
+ [1, "A", false],
+ [1, "B", false],
+ [2, "A", true],
+ [2, "B", true],
+ [2, "A", false],
+ [2, "B", false],
+ [3, "A", true],
+ [3, "B", true],
+ [3, "A", false],
+ [3, "B", false],
+ ]);
+ }
+ public static void Combinatorial([DataValues(1, 2, 3)] int x, [DataValues("A", "B")] string y, [DataValues(true, false)] bool z)
+ {
+ // For Attribute reflection only
+ }
+}
diff --git a/analyzers/tests/SonarAnalyzer.TestFramework/Common/CombinatorialDataAttribute.cs b/analyzers/tests/SonarAnalyzer.TestFramework/Common/CombinatorialDataAttribute.cs
new file mode 100644
index 00000000000..38b4da2b813
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.TestFramework/Common/CombinatorialDataAttribute.cs
@@ -0,0 +1,83 @@
+/*
+ * SonarAnalyzer for .NET
+ * Copyright (C) 2015-2024 SonarSource SA
+ * mailto: contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using System.Globalization;
+using System.Reflection;
+
+namespace SonarAnalyzer.TestFramework.Common;
+
+[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
+public sealed class DataValuesAttribute : Attribute
+{
+ public object[] Values { get; }
+
+ public DataValuesAttribute(params object[] values)
+ {
+ Values = values;
+ }
+}
+
+[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+public sealed class CombinatorialDataAttribute : Attribute, ITestDataSource
+{
+ // Based on https://stackoverflow.com/a/75531690
+ public IEnumerable GetData(MethodInfo methodInfo)
+ {
+ var valuesPerParameter = methodInfo.GetParameters().Select(p => p.GetCustomAttribute()?.Values
+ ?? throw new InvalidOperationException("Combinatorial test requires all parameters to have the [DataValues] attribute set")).ToArray();
+ var parameterIndices = new int[valuesPerParameter.Length];
+
+ while (true)
+ {
+ // Create new arguments
+ var arg = new object[parameterIndices.Length];
+ for (var i = 0; i < parameterIndices.Length; i++)
+ {
+ arg[i] = valuesPerParameter[i][parameterIndices[i]];
+ }
+
+ yield return arg;
+
+ // Increment indices
+ for (var i = parameterIndices.Length - 1; i >= 0; i--)
+ {
+ parameterIndices[i]++;
+ if (parameterIndices[i] >= valuesPerParameter[i].Length)
+ {
+ parameterIndices[i] = 0;
+
+ if (i == 0)
+ {
+ yield break;
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ public string GetDisplayName(MethodInfo methodInfo, object[] data) =>
+ data == null
+ ? null
+ : $"{methodInfo.Name} ({string.Join(",", data)})";
+}
diff --git a/analyzers/tests/SonarAnalyzer.TestFramework/MetadataReferences/AspNetCoreMetadataReference.cs b/analyzers/tests/SonarAnalyzer.TestFramework/MetadataReferences/AspNetCoreMetadataReference.cs
index cbe770ec542..1e09a226b4d 100644
--- a/analyzers/tests/SonarAnalyzer.TestFramework/MetadataReferences/AspNetCoreMetadataReference.cs
+++ b/analyzers/tests/SonarAnalyzer.TestFramework/MetadataReferences/AspNetCoreMetadataReference.cs
@@ -42,5 +42,6 @@ public static class AspNetCoreMetadataReference
public static MetadataReference MicrosoftAspNetCoreRouting { get; } = Create(typeof(Microsoft.AspNetCore.Routing.IEndpointRouteBuilder));
public static MetadataReference MicrosoftAspNetCoreWebHost { get; } = Create(typeof(Microsoft.AspNetCore.WebHost));
public static MetadataReference MicrosoftExtensionsHostingAbstractions { get; } = Create(typeof(Microsoft.Extensions.Hosting.IHost));
+ public static MetadataReference MicrosoftExtensionsPrimitives { get; } = Create(typeof(Microsoft.Extensions.Primitives.CancellationChangeToken));
}
#endif