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

Add analyzer for SemanticModel.GetDeclaredSymbol #6779

Merged
merged 17 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
; Please do not edit this file manually, it should only be updated through code fix application.

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
RS1039 | MicrosoftCodeAnalysisCorrectness | Warning | SemanticModelGetDeclaredSymbolAlwaysReturnsNullAnalyzer
RS1040 | MicrosoftCodeAnalysisCorrectness | Warning | CSharpSemanticModelGetDeclaredSymbolAlwaysReturnsNullAnalyzer
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis.Analyzers;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;

namespace Microsoft.CodeAnalysis.CSharp.Analyzers.MetaAnalyzers
{
using static CodeAnalysisDiagnosticsResources;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class CSharpSemanticModelGetDeclaredSymbolAlwaysReturnsNullAnalyzer : DiagnosticAnalyzer
{
internal static readonly DiagnosticDescriptor DiagnosticDescriptor = new(
DiagnosticIds.SemanticModelGetDeclaredSymbolAlwaysReturnsNull,
CreateLocalizableResourceString(nameof(SemanticModelGetDeclaredSymbolAlwaysReturnsNullTitle)),
CreateLocalizableResourceString(nameof(SemanticModelGetDeclaredSymbolAlwaysReturnsNullMessage)),
DiagnosticCategory.MicrosoftCodeAnalysisCorrectness,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: CreateLocalizableResourceString(nameof(SemanticModelGetDeclaredSymbolAlwaysReturnsNullDescription)),
helpLinkUri: null,
customTags: WellKnownDiagnosticTagsExtensions.Telemetry);

internal static readonly DiagnosticDescriptor FieldDiagnosticDescriptor = new(
DiagnosticIds.SemanticModelGetDeclaredSymbolAlwaysReturnsNullForField,
CreateLocalizableResourceString(nameof(SemanticModelGetDeclaredSymbolAlwaysReturnsNullTitle)),
CreateLocalizableResourceString(nameof(SemanticModelGetDeclaredSymbolAlwaysReturnsNullForFieldMessage)),
DiagnosticCategory.MicrosoftCodeAnalysisCorrectness,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: CreateLocalizableResourceString(nameof(SemanticModelGetDeclaredSymbolAlwaysReturnsNullForFieldDescription)),
helpLinkUri: null,
customTags: WellKnownDiagnosticTagsExtensions.Telemetry);

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(static context =>
{
var typeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation);
IMethodSymbol? getDeclaredSymbolMethod;
if (!typeProvider.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftCodeAnalysisCSharpCSharpExtensions, out var csharpExtensions)
|| !typeProvider.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftCodeAnalysisModelExtensions, out var modelExtensions)
|| !typeProvider.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftCodeAnalysisCSharpSyntaxBaseFieldDeclarationSyntax, out var baseFieldDeclaration)
|| !typeProvider.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftCodeAnalysisSyntaxNode, out var syntaxNode)
|| (getDeclaredSymbolMethod = modelExtensions.GetMembers(nameof(ModelExtensions.GetDeclaredSymbol)).FirstOrDefault() as IMethodSymbol) is null)
{
return;
}

var allowedTypes = csharpExtensions.GetMembers(nameof(CSharpExtensions.GetDeclaredSymbol))
.OfType<IMethodSymbol>()
.Where(m => m.Parameters.Length >= 2)
.Select(m => m.Parameters[1].Type);

context.RegisterOperationAction(ctx => AnalyzeInvocation(ctx, getDeclaredSymbolMethod, allowedTypes, baseFieldDeclaration, syntaxNode), OperationKind.Invocation);
});
}

private static void AnalyzeInvocation(OperationAnalysisContext context, IMethodSymbol getDeclaredSymbolMethod, IEnumerable<ITypeSymbol> allowedTypes, INamedTypeSymbol baseFieldDeclarationType, INamedTypeSymbol syntaxNodeType)
{
var invocation = (IInvocationOperation)context.Operation;
if (SymbolEqualityComparer.Default.Equals(invocation.TargetMethod, getDeclaredSymbolMethod))
{
var syntaxNodeDerivingType = invocation.Arguments[1].Value.WalkDownConversion().Type;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do we ensure that getDeclaredSymbolMethod has at least 2 parameters?

Copy link
Contributor

@mavasani mavasani Oct 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would also not work as expected if named arguments are used and arguments are out of order. You probably want to use one of the below helper methods:

public static bool TryGetArgumentForParameterAtIndex(

public static IArgumentOperation GetArgumentForParameterAtIndex(

if (syntaxNodeDerivingType is null || syntaxNodeDerivingType.Equals(syntaxNodeType))
{
return;
}

Diagnostic? diagnostic = null;
if (syntaxNodeDerivingType.DerivesFrom(baseFieldDeclarationType))
{
diagnostic = invocation.CreateDiagnostic(FieldDiagnosticDescriptor, syntaxNodeDerivingType.Name);
}
else if (allowedTypes.All(type => !syntaxNodeDerivingType.DerivesFrom(type, baseTypesOnly: true, checkTypeParameterConstraints: false)))
{
diagnostic = invocation.CreateDiagnostic(DiagnosticDescriptor, syntaxNodeDerivingType.Name);
}

if (diagnostic is not null)
{
context.ReportDiagnostic(diagnostic);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

Suggested change
Diagnostic? diagnostic = null;
if (syntaxNodeDerivingType.DerivesFrom(baseFieldDeclarationType))
{
diagnostic = invocation.CreateDiagnostic(FieldDiagnosticDescriptor, syntaxNodeDerivingType.Name);
}
else if (allowedTypes.All(type => !syntaxNodeDerivingType.DerivesFrom(type, baseTypesOnly: true, checkTypeParameterConstraints: false)))
{
diagnostic = invocation.CreateDiagnostic(DiagnosticDescriptor, syntaxNodeDerivingType.Name);
}
if (diagnostic is not null)
{
context.ReportDiagnostic(diagnostic);
}
DiagnosticDescriptor descriptor;
if (syntaxNodeDerivingType.DerivesFrom(baseFieldDeclarationType))
{
descriptor = FieldDiagnosticDescriptor;
}
else if (allowedTypes.All(type => !syntaxNodeDerivingType.DerivesFrom(type, baseTypesOnly: true, checkTypeParameterConstraints: false)))
{
descriptor = DiagnosticDescriptor;
}
else
{
return;
}
context.ReportDiagnostic(invocation.CreateDiagnostic(descriptor, syntaxNodeDerivingType.Name));

}
}

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(DiagnosticDescriptor, FieldDiagnosticDescriptor);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this to the top of the file above Initialize method

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -571,4 +571,19 @@
<data name="DoNotRegisterCompilerTypesWithBadAssemblyReferenceRuleTitle" xml:space="preserve">
<value>Compiler extensions should be implemented in assemblies with compiler-provided references</value>
</data>
<data name="SemanticModelGetDeclaredSymbolAlwaysReturnsNullDescription" xml:space="preserve">
<value>Calling 'SemanticModel.GetDeclaredSymbol' with an argument of type 'GlobalStatementSyntax', 'IncompleteMemberSyntax' or a type inheriting from 'BaseFieldDeclarationSyntax' will always return 'null'.</value>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like fields should get a specific error message that instructs the user to extract the variable declarators.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this description needs adjustment as fields now have a separate descriptor/description. Additionally, not sure if we should limit the description to just 'GlobalStatementSyntax' and 'IncompleteMemberSyntax' as the code handles all sub-types of SyntaxNode that do not have an overload. It's fine to mentioned these two as examples, but not as the only node types being handled.

</data>
<data name="SemanticModelGetDeclaredSymbolAlwaysReturnsNullMessage" xml:space="preserve">
<value>A call to 'SemanticModel.GetDeclaredSymbol({0})' will always return 'null'</value>
</data>
<data name="SemanticModelGetDeclaredSymbolAlwaysReturnsNullTitle" xml:space="preserve">
<value>This call to 'SemanticModel.GetDeclaredSymbol()' will always return 'null'</value>
</data>
<data name="SemanticModelGetDeclaredSymbolAlwaysReturnsNullForFieldDescription" xml:space="preserve">
<value>Calling 'SemanticModel.GetDeclaredSymbol' with an argument of type 'FieldDeclarationSyntax' or 'EventFieldDeclarationSyntax' will always return 'null'. Call 'GetDeclaredSymbol' with the variable declarators from the field instead.</value>
</data>
<data name="SemanticModelGetDeclaredSymbolAlwaysReturnsNullForFieldMessage" xml:space="preserve">
<value>A call to 'SemanticModel.GetDeclaredSymbol({0})' will always return 'null'</value>
333fred marked this conversation as resolved.
Show resolved Hide resolved
</data>
</root>
2 changes: 2 additions & 0 deletions src/Microsoft.CodeAnalysis.Analyzers/Core/DiagnosticIds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ internal static class DiagnosticIds
public const string NoSettingSpecifiedSymbolIsBannedInAnalyzersRuleId = "RS1036";
public const string AddCompilationEndCustomTagRuleId = "RS1037";
public const string DoNotRegisterCompilerTypesWithBadAssemblyReferenceRuleId = "RS1038";
public const string SemanticModelGetDeclaredSymbolAlwaysReturnsNull = "RS1039";
public const string SemanticModelGetDeclaredSymbolAlwaysReturnsNullForField = "RS1040";

// Release tracking analyzer IDs
public const string DeclareDiagnosticIdInAnalyzerReleaseRuleId = "RS2000";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,31 @@
<target state="translated">Nepřidávat odebraná ID diagnostiky analyzátoru do nevydané verze analyzátoru</target>
<note />
</trans-unit>
<trans-unit id="SemanticModelGetDeclaredSymbolAlwaysReturnsNullDescription">
<source>Calling 'SemanticModel.GetDeclaredSymbol' with an argument of type 'GlobalStatementSyntax', 'IncompleteMemberSyntax' or a type inheriting from 'BaseFieldDeclarationSyntax' will always return 'null'.</source>
<target state="new">Calling 'SemanticModel.GetDeclaredSymbol' with an argument of type 'GlobalStatementSyntax', 'IncompleteMemberSyntax' or a type inheriting from 'BaseFieldDeclarationSyntax' will always return 'null'.</target>
<note />
</trans-unit>
<trans-unit id="SemanticModelGetDeclaredSymbolAlwaysReturnsNullForFieldDescription">
<source>Calling 'SemanticModel.GetDeclaredSymbol' with an argument of type 'FieldDeclarationSyntax' or 'EventFieldDeclarationSyntax' will always return 'null'. Call 'GetDeclaredSymbol' with the variable declarators from the field instead.</source>
<target state="new">Calling 'SemanticModel.GetDeclaredSymbol' with an argument of type 'FieldDeclarationSyntax' or 'EventFieldDeclarationSyntax' will always return 'null'. Call 'GetDeclaredSymbol' with the variable declarators from the field instead.</target>
<note />
</trans-unit>
<trans-unit id="SemanticModelGetDeclaredSymbolAlwaysReturnsNullForFieldMessage">
<source>A call to 'SemanticModel.GetDeclaredSymbol({0})' will always return 'null'</source>
<target state="new">A call to 'SemanticModel.GetDeclaredSymbol({0})' will always return 'null'</target>
<note />
</trans-unit>
<trans-unit id="SemanticModelGetDeclaredSymbolAlwaysReturnsNullMessage">
<source>A call to 'SemanticModel.GetDeclaredSymbol({0})' will always return 'null'</source>
<target state="new">A call to 'SemanticModel.GetDeclaredSymbol({0})' will always return 'null'</target>
<note />
</trans-unit>
<trans-unit id="SemanticModelGetDeclaredSymbolAlwaysReturnsNullTitle">
<source>This call to 'SemanticModel.GetDeclaredSymbol()' will always return 'null'</source>
<target state="new">This call to 'SemanticModel.GetDeclaredSymbol()' will always return 'null'</target>
<note />
</trans-unit>
<trans-unit id="SymbolIsBannedInAnalyzersDescription">
<source>The symbol has been marked as banned for use in analyzers, and an alternate should be used instead.</source>
<target state="translated">Symbol byl označen jako zakázaný pro použití v analyzátorech a místo toho by se měla použít náhrada.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,31 @@
<target state="translated">Fügen Sie einem nicht veröffentlichten Analysetoolrelease keine entfernten Analysetooldiagnose-IDs hinzu.</target>
<note />
</trans-unit>
<trans-unit id="SemanticModelGetDeclaredSymbolAlwaysReturnsNullDescription">
<source>Calling 'SemanticModel.GetDeclaredSymbol' with an argument of type 'GlobalStatementSyntax', 'IncompleteMemberSyntax' or a type inheriting from 'BaseFieldDeclarationSyntax' will always return 'null'.</source>
<target state="new">Calling 'SemanticModel.GetDeclaredSymbol' with an argument of type 'GlobalStatementSyntax', 'IncompleteMemberSyntax' or a type inheriting from 'BaseFieldDeclarationSyntax' will always return 'null'.</target>
<note />
</trans-unit>
<trans-unit id="SemanticModelGetDeclaredSymbolAlwaysReturnsNullForFieldDescription">
<source>Calling 'SemanticModel.GetDeclaredSymbol' with an argument of type 'FieldDeclarationSyntax' or 'EventFieldDeclarationSyntax' will always return 'null'. Call 'GetDeclaredSymbol' with the variable declarators from the field instead.</source>
<target state="new">Calling 'SemanticModel.GetDeclaredSymbol' with an argument of type 'FieldDeclarationSyntax' or 'EventFieldDeclarationSyntax' will always return 'null'. Call 'GetDeclaredSymbol' with the variable declarators from the field instead.</target>
<note />
</trans-unit>
<trans-unit id="SemanticModelGetDeclaredSymbolAlwaysReturnsNullForFieldMessage">
<source>A call to 'SemanticModel.GetDeclaredSymbol({0})' will always return 'null'</source>
<target state="new">A call to 'SemanticModel.GetDeclaredSymbol({0})' will always return 'null'</target>
<note />
</trans-unit>
<trans-unit id="SemanticModelGetDeclaredSymbolAlwaysReturnsNullMessage">
<source>A call to 'SemanticModel.GetDeclaredSymbol({0})' will always return 'null'</source>
<target state="new">A call to 'SemanticModel.GetDeclaredSymbol({0})' will always return 'null'</target>
<note />
</trans-unit>
<trans-unit id="SemanticModelGetDeclaredSymbolAlwaysReturnsNullTitle">
<source>This call to 'SemanticModel.GetDeclaredSymbol()' will always return 'null'</source>
<target state="new">This call to 'SemanticModel.GetDeclaredSymbol()' will always return 'null'</target>
<note />
</trans-unit>
<trans-unit id="SymbolIsBannedInAnalyzersDescription">
<source>The symbol has been marked as banned for use in analyzers, and an alternate should be used instead.</source>
<target state="translated">Das Symbol wurde für die Verwendung in dem Analysetool als gesperrt gekennzeichnet, und es muss stattdessen eine Alternative verwendet werden.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,31 @@
<target state="translated">No agregar identificadores de diagnóstico del analizador eliminados a una versión del analizador no incluida</target>
<note />
</trans-unit>
<trans-unit id="SemanticModelGetDeclaredSymbolAlwaysReturnsNullDescription">
<source>Calling 'SemanticModel.GetDeclaredSymbol' with an argument of type 'GlobalStatementSyntax', 'IncompleteMemberSyntax' or a type inheriting from 'BaseFieldDeclarationSyntax' will always return 'null'.</source>
<target state="new">Calling 'SemanticModel.GetDeclaredSymbol' with an argument of type 'GlobalStatementSyntax', 'IncompleteMemberSyntax' or a type inheriting from 'BaseFieldDeclarationSyntax' will always return 'null'.</target>
<note />
</trans-unit>
<trans-unit id="SemanticModelGetDeclaredSymbolAlwaysReturnsNullForFieldDescription">
<source>Calling 'SemanticModel.GetDeclaredSymbol' with an argument of type 'FieldDeclarationSyntax' or 'EventFieldDeclarationSyntax' will always return 'null'. Call 'GetDeclaredSymbol' with the variable declarators from the field instead.</source>
<target state="new">Calling 'SemanticModel.GetDeclaredSymbol' with an argument of type 'FieldDeclarationSyntax' or 'EventFieldDeclarationSyntax' will always return 'null'. Call 'GetDeclaredSymbol' with the variable declarators from the field instead.</target>
<note />
</trans-unit>
<trans-unit id="SemanticModelGetDeclaredSymbolAlwaysReturnsNullForFieldMessage">
<source>A call to 'SemanticModel.GetDeclaredSymbol({0})' will always return 'null'</source>
<target state="new">A call to 'SemanticModel.GetDeclaredSymbol({0})' will always return 'null'</target>
<note />
</trans-unit>
<trans-unit id="SemanticModelGetDeclaredSymbolAlwaysReturnsNullMessage">
<source>A call to 'SemanticModel.GetDeclaredSymbol({0})' will always return 'null'</source>
<target state="new">A call to 'SemanticModel.GetDeclaredSymbol({0})' will always return 'null'</target>
<note />
</trans-unit>
<trans-unit id="SemanticModelGetDeclaredSymbolAlwaysReturnsNullTitle">
<source>This call to 'SemanticModel.GetDeclaredSymbol()' will always return 'null'</source>
<target state="new">This call to 'SemanticModel.GetDeclaredSymbol()' will always return 'null'</target>
<note />
</trans-unit>
<trans-unit id="SymbolIsBannedInAnalyzersDescription">
<source>The symbol has been marked as banned for use in analyzers, and an alternate should be used instead.</source>
<target state="translated">El símbolo ha sido marcado como de uso prohibido en los analizadores, y en su lugar debe usarse uno alternativo.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,31 @@
<target state="translated">Ne pas ajouter d'ID de diagnostic d'analyseur supprimé à une version d'analyseur non fournie</target>
<note />
</trans-unit>
<trans-unit id="SemanticModelGetDeclaredSymbolAlwaysReturnsNullDescription">
<source>Calling 'SemanticModel.GetDeclaredSymbol' with an argument of type 'GlobalStatementSyntax', 'IncompleteMemberSyntax' or a type inheriting from 'BaseFieldDeclarationSyntax' will always return 'null'.</source>
<target state="new">Calling 'SemanticModel.GetDeclaredSymbol' with an argument of type 'GlobalStatementSyntax', 'IncompleteMemberSyntax' or a type inheriting from 'BaseFieldDeclarationSyntax' will always return 'null'.</target>
<note />
</trans-unit>
<trans-unit id="SemanticModelGetDeclaredSymbolAlwaysReturnsNullForFieldDescription">
<source>Calling 'SemanticModel.GetDeclaredSymbol' with an argument of type 'FieldDeclarationSyntax' or 'EventFieldDeclarationSyntax' will always return 'null'. Call 'GetDeclaredSymbol' with the variable declarators from the field instead.</source>
<target state="new">Calling 'SemanticModel.GetDeclaredSymbol' with an argument of type 'FieldDeclarationSyntax' or 'EventFieldDeclarationSyntax' will always return 'null'. Call 'GetDeclaredSymbol' with the variable declarators from the field instead.</target>
<note />
</trans-unit>
<trans-unit id="SemanticModelGetDeclaredSymbolAlwaysReturnsNullForFieldMessage">
<source>A call to 'SemanticModel.GetDeclaredSymbol({0})' will always return 'null'</source>
<target state="new">A call to 'SemanticModel.GetDeclaredSymbol({0})' will always return 'null'</target>
<note />
</trans-unit>
<trans-unit id="SemanticModelGetDeclaredSymbolAlwaysReturnsNullMessage">
<source>A call to 'SemanticModel.GetDeclaredSymbol({0})' will always return 'null'</source>
<target state="new">A call to 'SemanticModel.GetDeclaredSymbol({0})' will always return 'null'</target>
<note />
</trans-unit>
<trans-unit id="SemanticModelGetDeclaredSymbolAlwaysReturnsNullTitle">
<source>This call to 'SemanticModel.GetDeclaredSymbol()' will always return 'null'</source>
<target state="new">This call to 'SemanticModel.GetDeclaredSymbol()' will always return 'null'</target>
<note />
</trans-unit>
<trans-unit id="SymbolIsBannedInAnalyzersDescription">
<source>The symbol has been marked as banned for use in analyzers, and an alternate should be used instead.</source>
<target state="translated">Le symbole a été marqué comme étant interdit d’utilisation dans les analyseurs, et un autre symbole doit être utilisé à la place.</target>
Expand Down
Loading