Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rule S6423: Always log failures in Azure Functions #5645

Merged
merged 107 commits into from
May 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
7906ec2
Scaffolding
martin-strecker-sonarsource May 9, 2022
47bc600
Add Microsoft.NET.Sdk.Functions and Microsoft.AspNetCore.App nuget re…
martin-strecker-sonarsource May 9, 2022
fbc3731
Add verifier builder
martin-strecker-sonarsource May 9, 2022
d1db01b
Report on any method declaration
martin-strecker-sonarsource May 9, 2022
55699fb
Minimal set of nuget refernces for standard azure function.
martin-strecker-sonarsource May 9, 2022
eee593b
Make test pass.
martin-strecker-sonarsource May 9, 2022
4aa2b06
Use GetTypeByMetadataName
martin-strecker-sonarsource May 9, 2022
18ad111
WIP: Check for valid LogLevel invocations
martin-strecker-sonarsource May 10, 2022
a26d00f
refavtoring
martin-strecker-sonarsource May 10, 2022
c0a9f54
better check for ILogger.Log calls
martin-strecker-sonarsource May 10, 2022
296d651
Add secondary location
martin-strecker-sonarsource May 10, 2022
421ab5a
Add test case for passed logger argument.
martin-strecker-sonarsource May 10, 2022
1806b56
Some more test cases and formatting
martin-strecker-sonarsource May 10, 2022
49a112d
Use CSharpFacade.Instance to get the parameter lookup
martin-strecker-sonarsource May 11, 2022
8f6b259
Inline missleading/broken IsExpressionAnILogger method and add test case
martin-strecker-sonarsource May 11, 2022
c6972e3
Test for presence of ILogger parameter in the entry point method
martin-strecker-sonarsource May 11, 2022
d54c459
Add test case for "no ILogger in the entry points parameter list"
martin-strecker-sonarsource May 11, 2022
f06a8a4
Add test for secondary locations on more than one unsufficient logger…
martin-strecker-sonarsource May 11, 2022
f2eb558
Test documentation
martin-strecker-sonarsource May 11, 2022
a3cff6a
Test coverage
martin-strecker-sonarsource May 11, 2022
0ed6896
Fix SonarCloud complains
martin-strecker-sonarsource May 11, 2022
5842a04
Conditional Tests: Use FrameworkTestMethodAttribute
martin-strecker-sonarsource May 11, 2022
4f05d42
Move test file to CloudNative
martin-strecker-sonarsource May 11, 2022
ce667f3
Move rule to CloudNative
martin-strecker-sonarsource May 11, 2022
01c32d0
Move TestCases
martin-strecker-sonarsource May 11, 2022
82b066b
Revert FrameworkTestAttributes
martin-strecker-sonarsource May 11, 2022
1e85ac5
Fix tests
martin-strecker-sonarsource May 11, 2022
da4a5cb
Remove verbatim
martin-strecker-sonarsource May 11, 2022
946905b
Apply suggestions from code review
martin-strecker-sonarsource May 11, 2022
2cf1ab8
Simplify test file
martin-strecker-sonarsource May 11, 2022
2b13a43
Update rspec.
martin-strecker-sonarsource May 11, 2022
b05fc02
Fixes from Code review
martin-strecker-sonarsource May 11, 2022
61d8a0a
Add loglevel "Information" as compliant
martin-strecker-sonarsource May 11, 2022
40bf401
Add support for custom extension methods on ILogger
martin-strecker-sonarsource May 11, 2022
1cd3a0f
Formatting
martin-strecker-sonarsource May 11, 2022
0f99b75
Registration re-factorings
martin-strecker-sonarsource May 12, 2022
63a652d
Keep public methods together
martin-strecker-sonarsource May 12, 2022
2405bc0
Formatting
martin-strecker-sonarsource May 12, 2022
e066f76
Add more test cases.
martin-strecker-sonarsource May 13, 2022
5b4cfcf
Refactor AzureFunctionMethod
martin-strecker-sonarsource May 13, 2022
ebd08af
Remove immutable builder
martin-strecker-sonarsource May 13, 2022
0a85a91
Move private fields to top
martin-strecker-sonarsource May 13, 2022
b14c9fb
Add test for DI scenarios
martin-strecker-sonarsource May 13, 2022
6219ebc
Refactor GetSymbolType
martin-strecker-sonarsource May 13, 2022
278e801
Add GetAccessibleMembersAndBaseMembers extension method.
martin-strecker-sonarsource May 13, 2022
37d1942
Support accessible ILogger members in DI scenarios
martin-strecker-sonarsource May 13, 2022
d399831
Use InvalidLogLevel
martin-strecker-sonarsource May 13, 2022
54ba1ca
Fix test
martin-strecker-sonarsource May 13, 2022
f24d840
Add generic logger
martin-strecker-sonarsource May 13, 2022
a796a99
Add LogInformationCustomExtension call
martin-strecker-sonarsource May 13, 2022
8908244
Code comment
martin-strecker-sonarsource May 13, 2022
e1b5df6
typo
martin-strecker-sonarsource May 13, 2022
c65ad58
Simplify
martin-strecker-sonarsource May 13, 2022
4e5dde0
Simplify
martin-strecker-sonarsource May 13, 2022
2854c5d
Add test case for LogMetric
martin-strecker-sonarsource May 13, 2022
03c0723
Add header
martin-strecker-sonarsource May 13, 2022
d54193e
Remove ILanguageFacade
martin-strecker-sonarsource May 23, 2022
0dadc5f
private Replace props with fields
martin-strecker-sonarsource May 23, 2022
cb6c9d6
Simplify Visit
martin-strecker-sonarsource May 23, 2022
2cad2cd
Rename namspace in test
martin-strecker-sonarsource May 23, 2022
534f6c7
Add tests for custom ILogger classes.
martin-strecker-sonarsource May 23, 2022
00bff49
Use FindConstantValue
martin-strecker-sonarsource May 23, 2022
5b03875
Add test cases for different loglevels
martin-strecker-sonarsource May 23, 2022
765ecd0
Rename to AllAccessibleMembers
martin-strecker-sonarsource May 23, 2022
ae7ac9f
Add cancellationToken to call
martin-strecker-sonarsource May 23, 2022
f0feae6
Re-factor AllAccessibleMembers
martin-strecker-sonarsource May 23, 2022
1d42dd1
Refactor AzureFunctionMethod
martin-strecker-sonarsource May 23, 2022
e7a1a48
Remove conditional compilation
martin-strecker-sonarsource May 23, 2022
21e8c6f
Move WithBasePath("CloudNative").
martin-strecker-sonarsource May 23, 2022
ad90adf
Remove WithConcurrentAnalysis(false)
martin-strecker-sonarsource May 23, 2022
6dc29d1
Renaming in test
martin-strecker-sonarsource May 23, 2022
bb5b653
typo
martin-strecker-sonarsource May 23, 2022
d97f3ab
Clarify comment for LogMetric
martin-strecker-sonarsource May 23, 2022
09d8e38
Add test case for undefined method invocation
martin-strecker-sonarsource May 23, 2022
6525913
Make lazy init and add call in two steps.
martin-strecker-sonarsource May 23, 2022
64aa829
Re-factor IsValidLogCall and add some test cases.
martin-strecker-sonarsource May 23, 2022
80a0228
Cleanup
martin-strecker-sonarsource May 23, 2022
b49b700
Intendation
martin-strecker-sonarsource May 23, 2022
aebecf4
Re-factoring
martin-strecker-sonarsource May 23, 2022
35c3486
re-name
martin-strecker-sonarsource May 23, 2022
adb48a6
Unsed using
martin-strecker-sonarsource May 24, 2022
40468dc
Add LoggerAlias sample
martin-strecker-sonarsource May 24, 2022
46712f1
Add test for GetSymbolType for alias
martin-strecker-sonarsource May 24, 2022
3336432
Add non Azure Function test case.
martin-strecker-sonarsource May 24, 2022
5b06173
Wrap tests in namespace
martin-strecker-sonarsource May 24, 2022
4c9d3e6
Add ILogger not in using list test
martin-strecker-sonarsource May 24, 2022
35b448b
Add DI Test with Logger out of scope
martin-strecker-sonarsource May 24, 2022
3d378f1
Add test cases for non-logging ILogger and logger extension methods
martin-strecker-sonarsource May 24, 2022
25565ba
Reduce branches in IsValidLogCall and add test cases for implicit ILo…
martin-strecker-sonarsource May 24, 2022
589ea9c
remove condition
martin-strecker-sonarsource May 24, 2022
1070ddd
Re-factor IsValidLogCall
martin-strecker-sonarsource May 24, 2022
f41c21a
Look for the ILogger at compilation start and add a test case where I…
martin-strecker-sonarsource May 25, 2022
4bfc921
Remove array creation from initializer
martin-strecker-sonarsource May 25, 2022
f4b1848
Update rspec
martin-strecker-sonarsource May 25, 2022
fdd54cf
Typo
martin-strecker-sonarsource May 25, 2022
582bec6
Remove ILogger lookup.
martin-strecker-sonarsource May 30, 2022
a857272
Reimplement "HasLoggerInScope"
martin-strecker-sonarsource May 30, 2022
b9e706f
Formatting
martin-strecker-sonarsource May 30, 2022
40b90b3
Add tests with trace logging before and after an error logging
martin-strecker-sonarsource May 30, 2022
a921866
Apply suggestions from code review
martin-strecker-sonarsource May 30, 2022
5e73805
GetSymbolType: make ITypeSymbol its own branch
martin-strecker-sonarsource May 30, 2022
c3aaf48
Move FirstAccessibleMemberOfType from Extensions into the analyzer.
martin-strecker-sonarsource May 30, 2022
1203a75
Move properties to top
martin-strecker-sonarsource May 30, 2022
f2f1d8d
HasLoggerMember only looks at fields and properties and test cases ad…
martin-strecker-sonarsource May 30, 2022
923df2d
Fix intendation.
martin-strecker-sonarsource May 30, 2022
2c5ce31
Fix Code Smell
martin-strecker-sonarsource May 31, 2022
935c3b1
Apply suggetstions from code Review
martin-strecker-sonarsource May 31, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions analyzers/rspec/cs/S6423_c#.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<p>Capturing and logging errors is critical to monitoring the health of your Azure Functions application.</p>
<p>Each <code>catch</code> block inside an Azure Function should log helpful details about the failure. Moreover, the logging should not be done at
<code>Debug</code> or <code>Trace</code> level.</p>
<p>Consider using the built-in integration with Application Insights for better monitoring of your Application.</p>
<h2>Noncompliant Code Example</h2>
<pre>
[FunctionName("Foo")]
public static async Task&lt;IActionResult&gt; Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
try
{
// do stuff that can fail
}
catch (Exception ex)
{
// the failure is not logged at all OR is logged at DEBUG/TRACE level
}
}
</pre>
<h2>Compliant Solution</h2>
<pre>
[FunctionName("Foo")]
public static async Task&lt;IActionResult&gt; Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
try
{
// do stuff that can fail
}
catch (Exception ex)
{
log.LogError("Give details that will help investigations", ex);
}
}
</pre>
<h2>See</h2>
<ul>
<li> <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-error-pages?tabs=csharp">Azure Functions error handling and
retries</a> </li>
<li> <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-monitoring">Monitor Azure Functions</a> </li>
<li> <a href="https://docs.microsoft.com/en-us/azure/azure-monitor/app/azure-functions-supported-features">Application Insights for Azure Functions
supported features</a> </li>
</ul>

17 changes: 17 additions & 0 deletions analyzers/rspec/cs/S6423_c#.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"title": "Azure Functions should log all failures.",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "5min"
},
"tags": [
"error-handling"
],
"defaultSeverity": "Major",
"ruleSpecification": "RSPEC-6423",
"sqKey": "S6423",
"scope": "Main",
"quickfix": "unknown"
}
33 changes: 33 additions & 0 deletions analyzers/src/SonarAnalyzer.CSharp/RspecStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -12844,6 +12844,39 @@
<data name="S6354_Type" xml:space="preserve">
<value>CODE_SMELL</value>
</data>
<data name="S6423_Category" xml:space="preserve">
<value>Major Code Smell</value>
</data>
<data name="S6423_Description" xml:space="preserve">
<value>Capturing and logging errors is critical to monitoring the health of your Azure Functions application.</value>
</data>
<data name="S6423_IsActivatedByDefault" xml:space="preserve">
<value>False</value>
</data>
<data name="S6423_Remediation" xml:space="preserve">
<value>Constant/Issue</value>
</data>
<data name="S6423_RemediationCost" xml:space="preserve">
<value>5min</value>
</data>
<data name="S6423_Scope" xml:space="preserve">
<value>Main</value>
</data>
<data name="S6423_Severity" xml:space="preserve">
<value>Major</value>
</data>
<data name="S6423_Status" xml:space="preserve">
<value>ready</value>
</data>
<data name="S6423_Tags" xml:space="preserve">
<value>error-handling</value>
</data>
<data name="S6423_Title" xml:space="preserve">
<value>Azure Functions should log all failures.</value>
</data>
<data name="S6423_Type" xml:space="preserve">
<value>CODE_SMELL</value>
</data>
<data name="S818_Category" xml:space="preserve">
<value>Minor Code Smell</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2022 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.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using SonarAnalyzer.Extensions;
using SonarAnalyzer.Helpers;

namespace SonarAnalyzer.Rules.CSharp
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class AzureFunctionsLogFailures : SonarDiagnosticAnalyzer
{
private const string DiagnosticId = "S6423";
private const string MessageFormat = "Log exception via ILogger with LogLevel Information, Warning, Error, or Critical.";

private static readonly int[] InvalidLogLevel =
{
0, // Trace
1, // Debug
6, // None
};

private static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorBuilder.GetDescriptor(DiagnosticId, MessageFormat, RspecStrings.ResourceManager);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

protected override void Initialize(SonarAnalysisContext context) =>
context.RegisterSyntaxNodeActionInNonGenerated(c =>
{
var catchClause = (CatchClauseSyntax)c.Node;
if (c.AzureFunctionMethod() is { } entryPoint
&& HasLoggerInScope(entryPoint))
{
var walker = new LoggerCallWalker(c.SemanticModel, c.CancellationToken);
walker.SafeVisit(catchClause.Block);
// Exception handling in the filter clause preserves log scopes and is therefore recommended
// See https://blog.stephencleary.com/2020/06/a-new-pattern-for-exception-logging.html
costin-zaharia-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
walker.SafeVisit(catchClause.Filter?.FilterExpression);
if (!walker.HasValidLoggerCall)
{
c.ReportIssue(Diagnostic.Create(Rule, catchClause.CatchKeyword.GetLocation(), walker.InvalidLoggerInvocationLocations));
}
}
},
SyntaxKind.CatchClause);

private static bool HasLoggerInScope(IMethodSymbol entryPoint) =>
entryPoint.Parameters.Any(x => x.Type.DerivesOrImplements(KnownType.Microsoft_Extensions_Logging_ILogger))
// Instance method entry points might have access to an ILogger via injected fields/properties
// https://docs.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection
|| (entryPoint is { IsStatic: false, ContainingType: { } container }
&& HasLoggerMember(container));

internal static bool HasLoggerMember(ITypeSymbol typeSymbol)
{
var isOriginalType = true;
foreach (var type in typeSymbol.GetSelfAndBaseTypes())
{
if (type.GetMembers().Any(x => x.Kind is SymbolKind.Field or SymbolKind.Property
&& (isOriginalType || x.GetEffectiveAccessibility() != Accessibility.Private)
&& x.GetSymbolType()?.DerivesOrImplements(KnownType.Microsoft_Extensions_Logging_ILogger) is true))
{
return true;
}
isOriginalType = false;
}
return false;
}

private sealed class LoggerCallWalker : SafeCSharpSyntaxWalker
{
private readonly SemanticModel model;
private readonly CancellationToken cancellationToken;
private List<Location> invalidInvocations;

public bool HasValidLoggerCall { get; private set; }
public IEnumerable<Location> InvalidLoggerInvocationLocations => invalidInvocations;

public LoggerCallWalker(SemanticModel model, CancellationToken cancellationToken)
{
this.model = model;
this.cancellationToken = cancellationToken;
}

public override void Visit(SyntaxNode node)
{
if (!HasValidLoggerCall)
{
cancellationToken.ThrowIfCancellationRequested();
base.Visit(node);
}
}

public override void VisitInvocationExpression(InvocationExpressionSyntax node)
{
if (model.GetSymbolInfo(node, cancellationToken).Symbol is IMethodSymbol { ReceiverType: { } receiver } methodSymbol
&& receiver.DerivesOrImplements(KnownType.Microsoft_Extensions_Logging_ILogger))
{
costin-zaharia-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
if (IsValidLogCall(node, methodSymbol))
{
HasValidLoggerCall = true;
}
else
{
invalidInvocations ??= new();
invalidInvocations.Add(node.GetLocation());
}
}
base.VisitInvocationExpression(node);
pavel-mikula-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
}

public override void VisitArgument(ArgumentSyntax node)
pavel-mikula-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
pavel-mikula-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
{
pavel-mikula-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
HasValidLoggerCall = HasValidLoggerCall || model.GetTypeInfo(node.Expression, cancellationToken).Type?.DerivesOrImplements(KnownType.Microsoft_Extensions_Logging_ILogger) is true;
base.VisitArgument(node);
}

private bool IsValidLogCall(InvocationExpressionSyntax invocation, IMethodSymbol methodSymbol) =>
methodSymbol switch
{
// Wellknown LoggerExtensions methods invocations
{ IsExtensionMethod: true } when methodSymbol.ContainingType.Is(KnownType.Microsoft_Extensions_Logging_LoggerExtensions) =>
methodSymbol.Name switch
{
"LogInformation" or "LogWarning" or "LogError" or "LogCritical" => true,
"Log" => IsPassingValidLogLevel(invocation, methodSymbol),
"LogTrace" or "LogDebug" or "BeginScope" => false,
_ => true, // Some unknown extension method on LoggerExtensions was called. Avoid FPs and assume it logs something.
},
{ IsExtensionMethod: true } => true, // Any other extension method is assumed to log something to avoid FP.
_ => // Instance invocations on an ILogger instance.
methodSymbol.Name switch
{
"Log" => IsPassingValidLogLevel(invocation, methodSymbol),
"IsEnabled" or "BeginScope" => false,
_ => true, // Some unknown method on an ILogger was called. Avoid FPs and assume it logs something.
},
pavel-mikula-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
};

private bool IsPassingValidLogLevel(InvocationExpressionSyntax invocation, IMethodSymbol symbol) =>
symbol.Parameters.FirstOrDefault(x => x.Name == "logLevel") is { } logLevelParameter
&& new CSharpMethodParameterLookup(invocation, symbol).TryGetNonParamsSyntax(logLevelParameter, out var argumentSyntax)
&& argumentSyntax.FindConstantValue(model) is int logLevel
? !InvalidLogLevel.Contains(logLevel)
: true; // Compliant: Some non-constant value is passed as loglevel or there is no logLevel parameter
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using SonarAnalyzer.Helpers;

namespace SonarAnalyzer.Extensions
{
Expand All @@ -37,5 +38,10 @@ internal static class SyntaxNodeAnalysisContextExtensions
/// <seealso href="https://github.com/dotnet/roslyn/issues/50989"/>
internal static bool IsRedundantPositionalRecordContext(this SyntaxNodeAnalysisContext context) =>
context.ContainingSymbol.Kind == SymbolKind.Method;

public static IMethodSymbol AzureFunctionMethod(this SyntaxNodeAnalysisContext context) =>
context.ContainingSymbol is IMethodSymbol method && method.HasAttribute(KnownType.Microsoft_Azure_WebJobs_FunctionNameAttribute)
? method
: null;
}
}
3 changes: 3 additions & 0 deletions analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ internal sealed class KnownType
internal static readonly KnownType Microsoft_AspNetCore_Mvc_RequestFormLimitsAttribute = new("Microsoft.AspNetCore.Mvc.RequestFormLimitsAttribute");
internal static readonly KnownType Microsoft_AspNetCore_Mvc_RequestSizeLimitAttribute = new("Microsoft.AspNetCore.Mvc.RequestSizeLimitAttribute");
internal static readonly KnownType Microsoft_AspNetCore_Razor_Hosting_RazorCompiledItemAttribute = new("Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute");
internal static readonly KnownType Microsoft_Azure_WebJobs_FunctionNameAttribute = new("Microsoft.Azure.WebJobs.FunctionNameAttribute");
internal static readonly KnownType Microsoft_Data_Sqlite_SqliteCommand = new("Microsoft.Data.Sqlite.SqliteCommand");
internal static readonly KnownType Microsoft_EntityFrameworkCore_DbContextOptionsBuilder = new("Microsoft.EntityFrameworkCore.DbContextOptionsBuilder");
internal static readonly KnownType Microsoft_EntityFrameworkCore_Migrations_Migration = new("Microsoft.EntityFrameworkCore.Migrations.Migration");
Expand All @@ -85,7 +86,9 @@ internal sealed class KnownType
internal static readonly KnownType Microsoft_Extensions_Logging_DebugLoggerFactoryExtensions = new("Microsoft.Extensions.Logging.DebugLoggerFactoryExtensions");
internal static readonly KnownType Microsoft_Extensions_Logging_EventLoggerFactoryExtensions = new("Microsoft.Extensions.Logging.EventLoggerFactoryExtensions");
internal static readonly KnownType Microsoft_Extensions_Logging_EventSourceLoggerFactoryExtensions = new("Microsoft.Extensions.Logging.EventSourceLoggerFactoryExtensions");
internal static readonly KnownType Microsoft_Extensions_Logging_ILogger = new("Microsoft.Extensions.Logging.ILogger");
internal static readonly KnownType Microsoft_Extensions_Logging_ILoggerFactory = new("Microsoft.Extensions.Logging.ILoggerFactory");
internal static readonly KnownType Microsoft_Extensions_Logging_LoggerExtensions = new("Microsoft.Extensions.Logging.LoggerExtensions");
internal static readonly KnownType Microsoft_Extensions_Primitives_StringValues = new("Microsoft.Extensions.Primitives.StringValues");
internal static readonly KnownType Microsoft_Net_Http_Headers_HeaderNames = new("Microsoft.Net.Http.Headers.HeaderNames");
internal static readonly KnownType Microsoft_VisualBasic_Interaction = new("Microsoft.VisualBasic.Interaction");
Expand Down
47 changes: 12 additions & 35 deletions analyzers/src/SonarAnalyzer.Common/Helpers/TypeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,41 +188,18 @@ public static bool DerivesOrImplements(this ITypeSymbol type, ITypeSymbol baseTy
public static bool DerivesOrImplementsAny(this ITypeSymbol type, ImmutableArray<KnownType> baseTypes) =>
type.ImplementsAny(baseTypes) || type.DerivesFromAny(baseTypes);

public static ITypeSymbol GetSymbolType(this ISymbol symbol)
{
if (symbol is ILocalSymbol localSymbol)
{
return localSymbol.Type;
}

if (symbol is IFieldSymbol fieldSymbol)
{
return fieldSymbol.Type;
}

if (symbol is IPropertySymbol propertySymbol)
{
return propertySymbol.Type;
}

if (symbol is IParameterSymbol parameterSymbol)
public static ITypeSymbol GetSymbolType(this ISymbol symbol) =>
symbol switch
{
return parameterSymbol.Type;
}

if (symbol is IAliasSymbol aliasSymbol)
{
return aliasSymbol.Target as ITypeSymbol;
}

if (symbol is IMethodSymbol methodSymbol)
{
return methodSymbol.MethodKind == MethodKind.Constructor
? methodSymbol.ContainingType
: methodSymbol.ReturnType as INamedTypeSymbol;
}

return symbol as ITypeSymbol;
}
ILocalSymbol x => x.Type,
IFieldSymbol x => x.Type,
IPropertySymbol x => x.Type,
IParameterSymbol x => x.Type,
IAliasSymbol x => x.Target as ITypeSymbol,
IMethodSymbol { MethodKind: MethodKind.Constructor } x => x.ContainingType,
IMethodSymbol x => x.ReturnType,
ITypeSymbol x => x,
_ => null,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class SymbolHelperTest
namespace NS
{
using System.Collections.Generic;
using PropertyBag = System.Collections.Generic.Dictionary<string, object>;

public class Base
{
Expand Down
Loading