-
Notifications
You must be signed in to change notification settings - Fork 3.1k
/
UninitializedDbSetDiagnosticSuppressor.cs
93 lines (78 loc) · 4.08 KB
/
UninitializedDbSetDiagnosticSuppressor.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
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.EntityFrameworkCore;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class UninitializedDbSetDiagnosticSuppressor : DiagnosticSuppressor
{
private static readonly SuppressionDescriptor SuppressUninitializedDbSetRule = new(
id: EFDiagnostics.SuppressUninitializedDbSetRule,
suppressedDiagnosticId: "CS8618",
justification: AnalyzerStrings.UninitializedDbSetWarningSuppressionJustification);
/// <inheritdoc />
public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions { get; }
= ImmutableArray.Create(SuppressUninitializedDbSetRule);
/// <inheritdoc />
public override void ReportSuppressions(SuppressionAnalysisContext context)
{
INamedTypeSymbol? dbSetTypeSymbol = null;
INamedTypeSymbol? dbContextTypeSymbol = null;
foreach (var diagnostic in context.ReportedDiagnostics)
{
// We have an warning about an uninitialized non-nullable property.
// CS8618 contains the location of the uninitialized property in AdditionalLocations; note that if the class has a constructor,
// the diagnostic main Location points to the constructor rather than to the uninitialized property.
// The AdditionalLocations was added in 7.0.0-preview.3, fall back to the main location just in case and older compiler is
// being used (the check below for PropertyDeclarationSyntax will filter out the diagnostic if it's pointing to a constructor).
var location = diagnostic.AdditionalLocations.Count > 0
? diagnostic.AdditionalLocations[0]
: diagnostic.Location;
// Get the node, and make sure it's a property whose type syntactically contains DbSet (fast check before getting the semantic
// model, which is heavier).
if (location.SourceTree is not { } sourceTree
|| sourceTree.GetRoot().FindNode(location.SourceSpan) is not PropertyDeclarationSyntax propertyDeclarationSyntax
|| !propertyDeclarationSyntax.Type.ToString().Contains("DbSet"))
{
continue;
}
// Get the semantic symbol and do some basic checks
if (context.GetSemanticModel(sourceTree).GetDeclaredSymbol(propertyDeclarationSyntax) is not IPropertySymbol propertySymbol
|| propertySymbol.IsStatic
|| propertySymbol.IsReadOnly)
{
continue;
}
if (dbSetTypeSymbol is null || dbContextTypeSymbol is null)
{
dbSetTypeSymbol = context.Compilation.DbSetType();
dbContextTypeSymbol = context.Compilation.DbContextType();
if (dbSetTypeSymbol is null || dbContextTypeSymbol is null)
{
return;
}
}
// Check that the property is actually a DbSet<T>, and that its containing type inherits from DbContext
if (propertySymbol.Type.OriginalDefinition.Equals(dbSetTypeSymbol, SymbolEqualityComparer.Default)
&& InheritsFrom(propertySymbol.ContainingType, dbContextTypeSymbol))
{
context.ReportSuppression(Suppression.Create(SuppressUninitializedDbSetRule, diagnostic));
}
static bool InheritsFrom(ITypeSymbol typeSymbol, ITypeSymbol baseTypeSymbol)
{
var baseType = typeSymbol.BaseType;
while (baseType is not null)
{
if (baseType.Equals(baseTypeSymbol, SymbolEqualityComparer.Default))
{
return true;
}
baseType = baseType.BaseType;
}
return false;
}
}
}
}