Skip to content

Commit 4f6ca85

Browse files
authored
VS-79: Linq expressions are not analyzed without "using MongoDB.Driver.Linq;" (#28)
1 parent 7fd36a5 commit 4f6ca85

File tree

11 files changed

+429
-30
lines changed

11 files changed

+429
-30
lines changed

src/MongoDB.Analyzer/Core/Linq/LinqExpressionProcessor.cs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,22 @@ public static ExpressionsAnalysis ProcessSemanticModel(MongoAnalyzerContext cont
7676
{
7777
var methodSymbol = invocationNode.GetMethodSymbol(semanticModel);
7878

79-
if (!methodSymbol.IsDefinedInMongoLinq() ||
80-
!methodSymbol.ReceiverType.IsIMongoQueryable() ||
81-
!methodSymbol.ReturnType.IsIMongoQueryable())
79+
if (!methodSymbol.IsDefinedInMongoLinqOrSystemLinq() ||
80+
!methodSymbol.ReceiverType.IsIQueryable() ||
81+
!methodSymbol.ReturnType.IsIQueryable())
8282
{
8383
continue;
8484
}
8585

8686
deepestMongoQueryableNode = invocationNode
8787
.NestedInvocations()
8888
.FirstOrDefault(n =>
89-
n.GetMethodSymbol(semanticModel)?.ReducedFrom?.ReceiverType.IsMongoQueryable() != true);
89+
{
90+
var currentMethodSymbol = n.GetMethodSymbol(semanticModel);
91+
92+
// Find the first method that is not receiving IQueryable or is not defined in System.Linq or MongoDB.Driver.Linq
93+
return !((currentMethodSymbol?.ReceiverType).IsIQueryable() && currentMethodSymbol.IsDefinedInMongoLinqOrSystemLinq());
94+
});
9095
}
9196
else
9297
{
@@ -245,6 +250,11 @@ private static (SyntaxNode RewrittenLinqExpression, ConstantsMapper ConstantsMap
245250

246251
var symbolInfo = semanticModel.GetSymbolInfo(identifierNode);
247252

253+
if (symbolInfo.Symbol == null)
254+
{
255+
return default;
256+
}
257+
248258
var rewriteResult = symbolInfo.Symbol.Kind switch
249259
{
250260
SymbolKind.Field => HandleField(rewriteContext, nodeToHandle, symbolInfo),
@@ -262,7 +272,7 @@ SymbolKind.Parameter or
262272
nodesRemapping[rewriteResult.NodeToReplace] = rewriteResult.NewNode;
263273
break;
264274
case RewriteAction.Invalid:
265-
return (null, null);
275+
return default;
266276
}
267277
}
268278

@@ -272,7 +282,7 @@ SymbolKind.Parameter or
272282
}
273283
catch
274284
{
275-
return (null, null);
285+
return default;
276286
}
277287
}
278288

@@ -359,8 +369,8 @@ private static RewriteResult HandleMethod(
359369
SymbolInfo symbolInfo)
360370
{
361371
var methodSymbol = symbolInfo.Symbol as IMethodSymbol;
362-
if (methodSymbol.ReceiverType.IsIMongoQueryable() ||
363-
methodSymbol.ReturnType.IsIMongoQueryable() ||
372+
if (methodSymbol.ReceiverType.IsIQueryable() ||
373+
methodSymbol.ReturnType.IsIQueryable() ||
364374
methodSymbol.ReturnType == null ||
365375
IsChildOfLambdaOrQueryParameter(rewriteContext, identifierNode, symbolInfo))
366376
{

src/MongoDB.Analyzer/Core/Utilities/SymbolExtensions.cs

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ namespace MongoDB.Analyzer.Core;
1616

1717
internal static class SymbolExtensions
1818
{
19+
private const string AssemblyMongoDBDriver = "MongoDB.Driver";
20+
private const string NamespaceMongoDBDriver = "MongoDB.Driver";
21+
private const string NamespaceMongoDBLinq = "MongoDB.Driver.Linq";
22+
private const string NamespaceSystemLinq = "System.Linq";
23+
1924
private static readonly HashSet<string> s_supportedCollections = new()
2025
{
2126
"System.Collections.Generic.List<T>",
@@ -41,9 +46,16 @@ public static bool IsContainedInLambdaOrQueryParameter(this ISymbol symbol, Synt
4146
_ => symbol.IsContainedInLambda(parentNode)
4247
};
4348

44-
public static bool IsDefinedInMongoLinq(this ISymbol symbol) =>
45-
symbol?.ContainingModule?.Name?.ToLowerInvariant() == "mongodb.driver.dll" &&
46-
symbol?.ContainingNamespace?.Name == "Linq";
49+
public static bool IsDefinedInMongoLinqOrSystemLinq(this ISymbol symbol)
50+
{
51+
var containingNamespace = symbol?.ContainingNamespace?.ToDisplayString();
52+
53+
// In case of system linq, the containing module is not validated for simplicity,
54+
// as it can differ in different .net frameworks.
55+
return containingNamespace == NamespaceSystemLinq ||
56+
containingNamespace == NamespaceMongoDBLinq &&
57+
symbol?.ContainingAssembly.Name == AssemblyMongoDBDriver;
58+
}
4759

4860
public static bool IsBuilder(this ITypeSymbol typeSymbol) =>
4961
typeSymbol?.Name switch
@@ -92,16 +104,16 @@ public static bool IsFindFluentMethod(this IMethodSymbol methodSymbol) =>
92104

93105
public static bool IsFindOptions(this ITypeSymbol namedTypeSymbol) =>
94106
namedTypeSymbol?.Name == "FindOptions" &&
95-
namedTypeSymbol?.ContainingAssembly.Name == "MongoDB.Driver";
107+
namedTypeSymbol?.ContainingAssembly.Name == AssemblyMongoDBDriver;
96108

97-
public static bool IsIMongoCollection(this ITypeSymbol typeSymbol) => ImplementsOrIsInterface(typeSymbol, "MongoDB.Driver", "IMongoCollection");
109+
public static bool IsIMongoCollection(this ITypeSymbol typeSymbol) => ImplementsOrIsInterface(typeSymbol, NamespaceMongoDBDriver, "IMongoCollection");
98110

99111
public static bool IsIMongoQueryable(this ITypeSymbol typeSymbol) =>
100-
ImplementsOrIsInterface(typeSymbol, "MongoDB.Driver.Linq", "IMongoQueryable") ||
101-
ImplementsOrIsInterface(typeSymbol, "MongoDB.Driver.Linq", "IOrderedMongoQueryable");
112+
ImplementsOrIsInterface(typeSymbol, NamespaceMongoDBLinq, "IMongoQueryable") ||
113+
ImplementsOrIsInterface(typeSymbol, NamespaceMongoDBLinq, "IOrderedMongoQueryable");
102114

103-
public static bool IsLinqEnumerable(this ITypeSymbol typeSymbol) =>
104-
typeSymbol?.Name == "Enumerable" && typeSymbol.ContainingNamespace.Name == "Linq";
115+
public static bool IsIQueryable(this ITypeSymbol typeSymbol) =>
116+
ImplementsOrIsInterface(typeSymbol, NamespaceSystemLinq, nameof(IQueryable));
105117

106118
public static bool IsMongoQueryable(this ITypeSymbol typeSymbol) =>
107119
typeSymbol?.Name == "MongoQueryable";
@@ -132,11 +144,16 @@ typeSymbol is INamedTypeSymbol namedType &&
132144
namedType.TypeArguments.Length == 1 &&
133145
namedType.TypeArguments[0].IsSupportedMongoCollectionType();
134146

135-
private static bool ImplementsOrIsInterface(this ITypeSymbol typeSymbol, string @namespace, string interfaceName) =>
136-
typeSymbol?.TypeKind switch
147+
private static bool ImplementsOrIsInterface(this ITypeSymbol typeSymbol, string @namespace, string interfaceName)
148+
{
149+
if (typeSymbol == null)
137150
{
138-
TypeKind.Class => typeSymbol.Interfaces.Any(i => i.Name == interfaceName && i.ContainingNamespace.ToDisplayString() == @namespace),
139-
TypeKind.Interface => typeSymbol.Name == interfaceName && typeSymbol.ContainingNamespace.ToDisplayString() == @namespace,
140-
_ => false
141-
};
151+
return false;
152+
}
153+
154+
return IsType(typeSymbol) || typeSymbol.Interfaces.Any(IsType);
155+
156+
bool IsType(ITypeSymbol typeSymbol) =>
157+
typeSymbol.Name == interfaceName && typeSymbol.ContainingNamespace.ToDisplayString() == @namespace;
158+
}
142159
}

0 commit comments

Comments
 (0)