forked from dotnet/roslyn-analyzers
/
CSharpImmutableObjectMethodAnalyzer.cs
113 lines (95 loc) · 6.2 KB
/
CSharpImmutableObjectMethodAnalyzer.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Immutable;
using System.Linq;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis.Analyzers;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.CodeAnalysis.CSharp.Analyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class CSharpImmutableObjectMethodAnalyzer : DiagnosticAnalyzer
{
// Each analyzer needs a public id to identify each DiagnosticDescriptor and subsequently fix diagnostics in CodeFixProvider.cs
private static readonly LocalizableString s_localizableTitle = new LocalizableResourceString(nameof(CodeAnalysisDiagnosticsResources.DoNotIgnoreReturnValueOnImmutableObjectMethodInvocationTitle), CodeAnalysisDiagnosticsResources.ResourceManager, typeof(CodeAnalysisDiagnosticsResources));
private static readonly LocalizableString s_localizableMessage = new LocalizableResourceString(nameof(CodeAnalysisDiagnosticsResources.DoNotIgnoreReturnValueOnImmutableObjectMethodInvocationMessage), CodeAnalysisDiagnosticsResources.ResourceManager, typeof(CodeAnalysisDiagnosticsResources));
private static readonly LocalizableString s_localizableDescription = new LocalizableResourceString(nameof(CodeAnalysisDiagnosticsResources.DoNotIgnoreReturnValueOnImmutableObjectMethodInvocationDescription), CodeAnalysisDiagnosticsResources.ResourceManager, typeof(CodeAnalysisDiagnosticsResources));
public static readonly DiagnosticDescriptor DoNotIgnoreReturnValueDiagnosticRule = new DiagnosticDescriptor(
DiagnosticIds.DoNotIgnoreReturnValueOnImmutableObjectMethodInvocation,
s_localizableTitle,
s_localizableMessage,
DiagnosticCategory.MicrosoftCodeAnalysisCorrectness,
DiagnosticHelpers.DefaultDiagnosticSeverity,
isEnabledByDefault: DiagnosticHelpers.EnabledByDefaultIfNotBuildingVSIX,
description: s_localizableDescription,
customTags: WellKnownDiagnosticTags.Telemetry);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(DoNotIgnoreReturnValueDiagnosticRule);
private const string SolutionFullName = @"Microsoft.CodeAnalysis.Solution";
private const string ProjectFullName = @"Microsoft.CodeAnalysis.Project";
private const string DocumentFullName = @"Microsoft.CodeAnalysis.Document";
private const string SyntaxNodeFullName = @"Microsoft.CodeAnalysis.SyntaxNode";
private const string CompilationFullName = @"Microsoft.CodeAnalysis.Compilation";
private static readonly ImmutableArray<string> s_immutableMethodNames = ImmutableArray.Create(
"Add",
"Remove",
"Replace",
"With");
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterCompilationStartAction(compilationContext =>
{
INamedTypeSymbol? solutionSymbol = compilationContext.Compilation.GetOrCreateTypeByMetadataName(SolutionFullName);
INamedTypeSymbol? projectSymbol = compilationContext.Compilation.GetOrCreateTypeByMetadataName(ProjectFullName);
INamedTypeSymbol? documentSymbol = compilationContext.Compilation.GetOrCreateTypeByMetadataName(DocumentFullName);
INamedTypeSymbol? syntaxNodeSymbol = compilationContext.Compilation.GetOrCreateTypeByMetadataName(SyntaxNodeFullName);
INamedTypeSymbol? compilationSymbol = compilationContext.Compilation.GetOrCreateTypeByMetadataName(CompilationFullName);
ImmutableArray<INamedTypeSymbol> immutableSymbols = ImmutableArray.CreateRange(new[] { solutionSymbol, projectSymbol, documentSymbol, syntaxNodeSymbol, compilationSymbol }.WhereNotNull());
//Only register our node action if we can find the symbols for our immutable types
if (immutableSymbols.IsEmpty)
{
return;
}
compilationContext.RegisterSyntaxNodeAction(sc => AnalyzeInvocationForIgnoredReturnValue(sc, immutableSymbols), SyntaxKind.InvocationExpression);
});
}
public static void AnalyzeInvocationForIgnoredReturnValue(SyntaxNodeAnalysisContext context, ImmutableArray<INamedTypeSymbol> immutableTypeSymbols)
{
SemanticModel model = context.SemanticModel;
var candidateInvocation = (InvocationExpressionSyntax)context.Node;
//We're looking for invocations that are direct children of expression statements
if (!(candidateInvocation.Parent.IsKind(SyntaxKind.ExpressionStatement)))
{
return;
}
//If we can't find the method symbol, quit
if (!(model.GetSymbolInfo(candidateInvocation).Symbol is IMethodSymbol methodSymbol))
{
return;
}
//If the method doesn't start with something like "With" or "Replace", quit
string methodName = methodSymbol.Name;
if (!s_immutableMethodNames.Any(n => methodName.StartsWith(n, StringComparison.Ordinal)))
{
return;
}
//If we're not in one of the known immutable types, quit
if (!(methodSymbol.ReceiverType is INamedTypeSymbol parentType))
{
return;
}
var baseTypesAndSelf = methodSymbol.ReceiverType.GetBaseTypes().ToList();
baseTypesAndSelf.Add(parentType);
if (!baseTypesAndSelf.Any(n => immutableTypeSymbols.Contains(n)))
{
return;
}
Location location = candidateInvocation.GetLocation();
Diagnostic diagnostic = Diagnostic.Create(DoNotIgnoreReturnValueDiagnosticRule, location, methodSymbol.ReceiverType.Name, methodSymbol.Name);
context.ReportDiagnostic(diagnostic);
}
}
}