Skip to content

Commit f125228

Browse files
Merge branch 'mongodb:main' into VS-153
2 parents deb0db6 + 2b40a8a commit f125228

19 files changed

+398
-19
lines changed

src/MongoDB.Analyzer/AnalyzerReleases.Shipped.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,12 @@ Rule ID | Category | Severity | Notes
1818
--------|----------|----------|--------------------
1919
MAPoco1001 | MongoDB.Analyzer.Poco | Info | Poco to Json [Documentation](https://www.mongodb.com/docs/mongodb-analyzer/current/rules/#mapoco1001)
2020
MAPoco2001 | MongoDB.Analyzer.Poco | Warning | Unsupported POCO [Documentation](https://www.mongodb.com/docs/mongodb-analyzer/current/rules/#mapoco2001)
21+
22+
## Release 1.4.0
23+
24+
### New Rules
25+
26+
Rule ID | Category | Severity | Notes
27+
--------|----------|----------|--------------------
28+
MAEF1001 | MongoDB.Analyzer.EF | Info | EF to MQL [Documentation](https://www.mongodb.com/docs/mongodb-analyzer/current/rules/#maef1001)
29+
MAEF2001 | MongoDB.Analyzer.EF | Warning | Unsupported EF [Documentation](https://www.mongodb.com/docs/mongodb-analyzer/current/rules/#maef2001)

src/MongoDB.Analyzer/Core/Builders/BuildersAnalyzer.cs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,6 @@ public static bool AnalyzeBuilders(MongoAnalysisContext context)
5151
return telemetry.ExpressionsFound > 0;
5252
}
5353

54-
private static bool IsDriverOrBsonException(MQLResult mqlResult)
55-
{
56-
var source = mqlResult.Exception.InnerException?.Source;
57-
return source.IsNotEmpty() && (source.Contains("MongoDB.Driver") ||
58-
source.Contains("MongoDB.Bson"));
59-
}
60-
6154
private static AnalysisStats ReportMqlOrInvalidExpressions(MongoAnalysisContext context, ExpressionsAnalysis buildersAnalysis)
6255
{
6356
var semanticContext = context.SemanticModelAnalysisContext;
@@ -94,7 +87,7 @@ private static AnalysisStats ReportMqlOrInvalidExpressions(MongoAnalysisContext
9487
}
9588
else if (mqlResult.Exception != null)
9689
{
97-
var isDriverOrBsonException = IsDriverOrBsonException(mqlResult);
90+
var isDriverOrBsonException = AnalysisUtilities.IsDriverOrBsonException(mqlResult);
9891

9992
if (isDriverOrBsonException || settings.OutputInternalExceptions)
10093
{

src/MongoDB.Analyzer/Core/ExpressionAnalysis.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ internal enum AnalysisType
1818
{
1919
Unknown,
2020
Builders,
21+
EF,
2122
Linq,
2223
Poco
2324
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Copyright 2021-present MongoDB Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License")
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using MongoDB.Analyzer.Core.HelperResources;
16+
using MongoDB.Analyzer.Core.Utilities;
17+
18+
namespace MongoDB.Analyzer.Core.Linq;
19+
20+
internal static class EFAnalyzer
21+
{
22+
public static bool AnalyzeEFQueryable(MongoAnalysisContext context)
23+
{
24+
var sw = Stopwatch.StartNew();
25+
var stats = AnalysisStats.Empty;
26+
ExpressionsAnalysis efAnalysis = null;
27+
28+
try
29+
{
30+
context.Logger.Log("Started EF analysis");
31+
32+
efAnalysis = LinqExpressionProcessor.ProcessSemanticModel(context, AnalysisType.EF);
33+
34+
ReportInvalidExpressions(context, efAnalysis);
35+
stats = ReportMqlOrInvalidExpressions(context, efAnalysis);
36+
37+
sw.Stop();
38+
context.Logger.Log($"EF analysis ended: with {stats.MqlCount} mql translations, {stats.DriverExceptionsCount} unsupported expressions, {stats.InternalExceptionsCount} internal exceptions in {sw.ElapsedMilliseconds}ms.");
39+
}
40+
catch (Exception ex)
41+
{
42+
sw.Stop();
43+
context.Logger.Log($"EF analysis ended after {sw.ElapsedMilliseconds} ms with exception {ex}");
44+
}
45+
46+
var telemetry = AnalysisUtilities.GetTelemetry(efAnalysis, stats, sw);
47+
if (telemetry.ExpressionsFound > 0)
48+
{
49+
context.Telemetry.EFAnalysisResult(telemetry);
50+
}
51+
52+
return telemetry.ExpressionsFound > 0;
53+
}
54+
55+
private static void ReportInvalidExpressions(MongoAnalysisContext context, ExpressionsAnalysis efExpressionAnalysis)
56+
{
57+
var semanticContext = context.SemanticModelAnalysisContext;
58+
59+
if (efExpressionAnalysis.InvalidExpressionNodes.EmptyOrNull())
60+
{
61+
return;
62+
}
63+
64+
var driverVersion = ReferencesProvider.GetMongoDBDriverVersion(semanticContext.SemanticModel.Compilation.References);
65+
var driverVersionString = driverVersion?.ToString(3);
66+
67+
foreach (var invalidExpressionNode in efExpressionAnalysis.InvalidExpressionNodes)
68+
{
69+
var diagnostics = Diagnostic.Create(
70+
DiagnosticsRules.DiagnosticRuleNotSupportedEFExpression,
71+
invalidExpressionNode.OriginalExpression.GetLocation(),
72+
AnalysisUtilities.DecorateMessage(invalidExpressionNode.Errors.FirstOrDefault(), driverVersionString, context.Settings));
73+
74+
semanticContext.ReportDiagnostic(diagnostics);
75+
}
76+
}
77+
78+
private static AnalysisStats ReportMqlOrInvalidExpressions(MongoAnalysisContext context, ExpressionsAnalysis efExpressionAnalysis)
79+
{
80+
var semanticContext = context.SemanticModelAnalysisContext;
81+
82+
if (efExpressionAnalysis.AnalysisNodeContexts.EmptyOrNull())
83+
{
84+
return AnalysisStats.Empty;
85+
}
86+
87+
var compilationResult = AnalysisCodeGenerator.Compile(context, efExpressionAnalysis);
88+
if (!compilationResult.Success)
89+
{
90+
return AnalysisStats.Empty;
91+
}
92+
93+
var driverVersion = compilationResult.LinqTestCodeExecutor.DriverVersion;
94+
var settings = context.Settings;
95+
int mqlCount = 0, internalExceptionsCount = 0, driverExceptionsCount = 0;
96+
var typesMapper = new TypesMapper(
97+
MqlGeneratorSyntaxElements.Linq.MqlGeneratorNamespace,
98+
context.TypesProcessor.GeneratedTypeToOriginalTypeMapping);
99+
100+
foreach (var analysisContext in efExpressionAnalysis.AnalysisNodeContexts)
101+
{
102+
var mqlResult = compilationResult.LinqTestCodeExecutor.GenerateMql(analysisContext.EvaluationMethodName);
103+
var locations = analysisContext.Node.Locations;
104+
105+
if (mqlResult.Mql != null)
106+
{
107+
var mql = analysisContext.Node.ConstantsRemapper.RemapConstants(mqlResult.Mql);
108+
var diagnosticDescriptor = DiagnosticsRules.DiagnosticRuleEF2MQL;
109+
var decoratedMessage = AnalysisUtilities.DecorateMessage(mql, driverVersion, settings);
110+
semanticContext.ReportDiagnostics(diagnosticDescriptor, decoratedMessage, locations);
111+
mqlCount++;
112+
}
113+
else if (mqlResult.Exception != null)
114+
{
115+
var isDriverOrLinqException = AnalysisUtilities.IsDriverOrLinqException(mqlResult);
116+
117+
if (isDriverOrLinqException || settings.OutputInternalExceptions)
118+
{
119+
var diagnosticDescriptor = DiagnosticsRules.DiagnosticRuleNotSupportedEFExpression;
120+
var message = AnalysisUtilities.GetExceptionMessage(mqlResult.Exception, typesMapper, AnalysisType.EF);
121+
var decoratedMessage = AnalysisUtilities.DecorateMessage(message, driverVersion, context.Settings);
122+
123+
semanticContext.ReportDiagnostics(diagnosticDescriptor, decoratedMessage, locations);
124+
}
125+
126+
if (!isDriverOrLinqException)
127+
{
128+
context.Logger.Log($"Exception while analyzing {analysisContext.Node}: {mqlResult.Exception.InnerException?.Message}");
129+
internalExceptionsCount++;
130+
}
131+
else
132+
{
133+
driverExceptionsCount++;
134+
}
135+
}
136+
}
137+
138+
return new AnalysisStats(mqlCount, 0, internalExceptionsCount, driverExceptionsCount, compilationResult.MongoDBDriverVersion.ToString(3), null);
139+
}
140+
}

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

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,6 @@ public static bool AnalyzeIMongoQueryable(MongoAnalysisContext context)
5252
return telemetry.ExpressionsFound > 0;
5353
}
5454

55-
private static bool IsDriverOrLinqException(MQLResult mqlResult)
56-
{
57-
var source = mqlResult.Exception.InnerException?.Source;
58-
return source.IsNotEmpty() && (source.Contains("MongoDB.Driver") ||
59-
source.Contains("MongoDB.Bson") || source.Contains("System.Linq"));
60-
}
61-
6255
private static void ReportInvalidExpressions(MongoAnalysisContext context, ExpressionsAnalysis linqExpressionAnalysis)
6356
{
6457
var semanticContext = context.SemanticModelAnalysisContext;
@@ -119,7 +112,7 @@ private static AnalysisStats ReportMqlOrInvalidExpressions(MongoAnalysisContext
119112
}
120113
else if (mqlResult.Exception != null)
121114
{
122-
var isDriverOrLinqException = IsDriverOrLinqException(mqlResult);
115+
var isDriverOrLinqException = AnalysisUtilities.IsDriverOrLinqException(mqlResult);
123116

124117
if (isDriverOrLinqException || settings.OutputInternalExceptions)
125118
{

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,13 @@ namespace MongoDB.Analyzer.Core.Linq;
1818

1919
internal static class LinqExpressionProcessor
2020
{
21-
public static ExpressionsAnalysis ProcessSemanticModel(MongoAnalysisContext context)
21+
public static ExpressionsAnalysis ProcessSemanticModel(MongoAnalysisContext context, AnalysisType analysisType = AnalysisType.Linq)
2222
{
23+
if (analysisType != AnalysisType.Linq && analysisType != AnalysisType.EF)
24+
{
25+
throw new ArgumentOutOfRangeException(nameof(analysisType), analysisType, "Unsupported analysis type");
26+
}
27+
2328
var semanticModel = context.SemanticModelAnalysisContext.SemanticModel;
2429
var syntaxTree = semanticModel.SyntaxTree;
2530
var root = syntaxTree.GetRoot();
@@ -79,7 +84,8 @@ public static ExpressionsAnalysis ProcessSemanticModel(MongoAnalysisContext cont
7984
}
8085

8186
var mongoQueryableTypeInfo = semanticModel.GetTypeInfo(deepestMongoQueryableNode);
82-
if (!mongoQueryableTypeInfo.Type.IsIMongoQueryable() ||
87+
88+
if ((!mongoQueryableTypeInfo.Type.IsIMongoQueryable() && analysisType == AnalysisType.Linq) || (!mongoQueryableTypeInfo.Type.IsDBSet() && analysisType == AnalysisType.EF) ||
8389
mongoQueryableTypeInfo.Type is not INamedTypeSymbol mongoQueryableNamedType ||
8490
mongoQueryableNamedType.TypeArguments.Length != 1 ||
8591
!mongoQueryableNamedType.TypeArguments[0].IsSupportedMongoCollectionType())

src/MongoDB.Analyzer/Core/Telemetry/ITelemetryServiceExtensions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ public static void AnalysisStarted(
5757
public static void BuildersAnalysisResult(this ITelemetryService telemetryService, AnalysisTelemetry analysisStatistics) =>
5858
telemetryService.Event("Builders analyzed", analysisStatistics.ToKeyValues());
5959

60+
public static void EFAnalysisResult(this ITelemetryService telemetryService, AnalysisTelemetry analysisStatistics) =>
61+
telemetryService.Event("EF analyzed", analysisStatistics.ToKeyValues());
62+
6063
public static void LinqAnalysisResult(this ITelemetryService telemetryService, AnalysisTelemetry analysisStatistics) =>
6164
telemetryService.Event("LINQ analyzed", analysisStatistics.ToKeyValues());
6265

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,20 @@ public static string GetExceptionMessage(Exception exception, TypesMapper typesM
2626
_ => typesMapper.RemapTypes(exception.InnerException?.Message)
2727
};
2828

29+
public static bool IsDriverOrBsonException(Builders.MQLResult mqlResult)
30+
{
31+
var source = mqlResult.Exception?.InnerException?.Source ?? string.Empty;
32+
return source.IsNotEmpty() && (source.Contains("MongoDB.Driver") ||
33+
source.Contains("MongoDB.Bson"));
34+
}
35+
36+
public static bool IsDriverOrLinqException(Linq.MQLResult mqlResult)
37+
{
38+
var source = mqlResult.Exception?.InnerException?.Source ?? string.Empty;
39+
return source.IsNotEmpty() && (source.Contains("MongoDB.Driver") ||
40+
source.Contains("MongoDB.Bson") || source.Contains("System.Linq"));
41+
}
42+
2943
public static AnalysisTelemetry GetTelemetry(ExpressionsAnalysis expressionsAnalysis, AnalysisStats analysisStats, Stopwatch sw)
3044
{
3145
var expressionsCount = (expressionsAnalysis?.InvalidExpressionNodes?.Length ?? 0) + (expressionsAnalysis?.AnalysisNodeContexts?.Length ?? 0);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ namespace MongoDB.Analyzer.Core;
1717
internal static class SymbolExtensions
1818
{
1919
private const string AssemblyMongoDBDriver = "MongoDB.Driver";
20+
private const string NamespaceEF = "Microsoft.EntityFrameworkCore";
2021
private const string NamespaceMongoDBBson = "MongoDB.Bson";
2122
private const string NamespaceMongoDBBsonAttributes = "MongoDB.Bson.Serialization.Attributes";
2223
private const string NamespaceMongoDBDriver = "MongoDB.Driver";
@@ -157,6 +158,10 @@ public static bool IsContainedInLambdaOrQueryParameter(this ISymbol symbol, Synt
157158
_ => symbol.IsContainedInLambda(parentNode)
158159
};
159160

161+
public static bool IsDBSet(this ITypeSymbol typeSymbol) =>
162+
typeSymbol?.Name == "DbSet" &&
163+
typeSymbol?.ContainingNamespace?.ToDisplayString() == NamespaceEF;
164+
160165
public static bool IsDefinedInMongoDriver(this ISymbol symbol) => symbol?.ContainingAssembly.Name == AssemblyMongoDBDriver;
161166

162167
public static bool IsDefinedInMongoLinqOrSystemLinq(this ISymbol symbol)

src/MongoDB.Analyzer/DiagnosticsRules.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ internal static class DiagnosticsRules
2525
private const string DiagnosticIdBuilders2MQL = "MABuilders1001";
2626
private const string DiagnosticIdNotSupportedBuilderExpression = "MABuilders2001";
2727

28+
//EF
29+
private const string DiagnosticIdEF2MQL = "MAEF1001";
30+
private const string DiagnosticIdNotSupportedEFExpression = "MAEF2001";
31+
private const string EFCategory = "MongoDB.Analyzer.EF";
32+
2833
// LINQ
2934
private const string DiagnosticIdLinq2MQL = "MALinq1001";
3035
private const string DiagnosticIdNotSupportedLinqExpression = "MALinq2001";
@@ -55,6 +60,25 @@ internal static class DiagnosticsRules
5560
isEnabledByDefault: true,
5661
helpLinkUri: GetRuleUrl(DiagnosticIdNotSupportedBuilderExpression));
5762

63+
//EF
64+
public static DiagnosticDescriptor DiagnosticRuleEF2MQL { get; } = new DiagnosticDescriptor(
65+
id: DiagnosticIdEF2MQL,
66+
title: "EF to MQL",
67+
messageFormat: "{0}",
68+
category: EFCategory,
69+
defaultSeverity: DiagnosticSeverity.Info,
70+
isEnabledByDefault: true,
71+
helpLinkUri: GetRuleUrl(DiagnosticIdEF2MQL));
72+
73+
public static DiagnosticDescriptor DiagnosticRuleNotSupportedEFExpression { get; } = new DiagnosticDescriptor(
74+
id: DiagnosticIdNotSupportedEFExpression,
75+
title: "Not supported EF Expression",
76+
messageFormat: "{0}",
77+
category: EFCategory,
78+
defaultSeverity: DiagnosticSeverity.Warning,
79+
isEnabledByDefault: true,
80+
helpLinkUri: GetRuleUrl(DiagnosticIdNotSupportedEFExpression));
81+
5882
// LINQ
5983
public static DiagnosticDescriptor DiagnosticRuleLinq2MQL { get; } = new DiagnosticDescriptor(
6084
id: DiagnosticIdLinq2MQL,
@@ -108,6 +132,10 @@ internal static class DiagnosticsRules
108132
DiagnosticRuleBuilder2MQL,
109133
DiagnosticRuleNotSupportedBuilderExpression,
110134

135+
//EF
136+
DiagnosticRuleEF2MQL,
137+
DiagnosticRuleNotSupportedEFExpression,
138+
111139
// LINQ
112140
DiagnosticRuleLinq2MQL,
113141
DiagnosticRuleNotSupportedLinqExpression,

0 commit comments

Comments
 (0)