Skip to content

Commit 4263b3a

Browse files
VS-153: Handle Custom Collections
1 parent a4249bf commit 4263b3a

File tree

10 files changed

+934
-59
lines changed

10 files changed

+934
-59
lines changed

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,9 @@ public static ExpressionsAnalysis ProcessSemanticModel(MongoAnalysisContext cont
8181

8282
try
8383
{
84-
foreach (var typeArgument in namedType.TypeArguments)
84+
if (!ProcessTypeArguments(namedType.TypeArguments, typesProcessor))
8585
{
86-
typesProcessor.ProcessTypeSymbol(typeArgument);
86+
continue;
8787
}
8888

8989
var rewriteContext = RewriteContext.Builders(expressionNode, nodesToRewrite, semanticModel, typesProcessor);
@@ -253,4 +253,18 @@ private static (NodeType NodeType, INamedTypeSymbol NamedSymbol, SyntaxNode Expr
253253

254254
return (nodeType, namedType, expressionNode);
255255
}
256+
257+
private static bool ProcessTypeArguments(IEnumerable<ITypeSymbol> typeArguments, TypesProcessor typesProcessor)
258+
{
259+
foreach (var typeArgument in typeArguments)
260+
{
261+
var remappedName = typesProcessor.ProcessTypeSymbol(typeArgument);
262+
if (remappedName == null)
263+
{
264+
return false;
265+
}
266+
}
267+
268+
return true;
269+
}
256270
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ mongoQueryableTypeInfo.Type is not INamedTypeSymbol mongoQueryableNamedType ||
9898
if (PreanalyzeLinqExpression(node, semanticModel, invalidExpressionNodes))
9999
{
100100
var generatedMongoQueryableTypeName = typesProcessor.ProcessTypeSymbol(mongoQueryableNamedType.TypeArguments[0]);
101+
if (generatedMongoQueryableTypeName == null)
102+
{
103+
continue;
104+
}
101105

102106
var rewriteContext = RewriteContext.Linq(node, deepestMongoQueryableNode, semanticModel, typesProcessor);
103107
var (newLinqExpression, constantsMapper) = RewriteExpression(rewriteContext);

src/MongoDB.Analyzer/Core/Poco/PocoExpressionProcessor.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ public static ExpressionsAnalysis ProcessSemanticModel(MongoAnalysisContext cont
4747
if (PreanalyzeClassDeclaration(context, classSymbol))
4848
{
4949
var generatedClassName = typesProcessor.ProcessTypeSymbol(classSymbol);
50+
if (generatedClassName == null)
51+
{
52+
continue;
53+
}
54+
5055
var generatedClassNode = (ClassDeclarationSyntax)(typesProcessor.GetTypeSymbolToMemberDeclarationMapping(classSymbol));
5156
var expressionContext = new ExpressionAnalysisContext(new ExpressionAnalysisNode(classNode, null, generatedClassNode, null, classNode.GetLocation()));
5257
analysisContexts.Add(expressionContext);

src/MongoDB.Analyzer/Core/ReferencesProvider.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public static ReferencesContainer GetReferences(IEnumerable<MetadataReference> m
8888
resultReferences.Add(MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location));
8989
resultReferences.Add(MetadataReference.CreateFromFile(typeof(IEnumerator<int>).Assembly.Location));
9090
resultReferences.Add(MetadataReference.CreateFromFile(typeof(Queryable).Assembly.Location));
91+
resultReferences.Add(MetadataReference.CreateFromFile(typeof(Stack<int>).Assembly.Location));
9192
resultReferences.Add(MetadataReference.CreateFromFile(typeof(System.Dynamic.DynamicObject).Assembly.Location));
9293
resultReferences.Add(MetadataReference.CreateFromFile(typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location));
9394
resultReferences.Add(MetadataReference.CreateFromFile(typeof(Task).Assembly.Location));

src/MongoDB.Analyzer/Core/TypesProcessor.cs

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ public string ProcessTypeSymbol(ITypeSymbol typeSymbol)
6969

7070
var rewrittenDeclarationSyntax = GetSyntaxNodeFromSymbol(typeSymbol, remappedName);
7171

72+
if (rewrittenDeclarationSyntax == null)
73+
{
74+
_processedTypes.Remove(fullTypeName);
75+
return null;
76+
}
77+
7278
var typeCode = rewrittenDeclarationSyntax.ToFullString();
7379
var newTypeDeclaration = SyntaxFactory.ParseMemberDeclaration(typeCode);
7480

@@ -93,31 +99,52 @@ private TypeSyntax CreateTypeSyntaxForSymbol(ITypeSymbol typeSymbol)
9399
arrayRankSpecifiers = SyntaxFactory.List(new[] { SyntaxFactory.ArrayRankSpecifier(ranksList) });
94100
var nextTypeSyntax = CreateTypeSyntaxForSymbol(arrayTypeSymbol.ElementType);
95101

102+
if (nextTypeSyntax == null)
103+
{
104+
return null;
105+
}
106+
96107
result = SyntaxFactory.ArrayType(nextTypeSyntax, arrayRankSpecifiers.Value);
97108
}
98109
// TODO optimize
99110
else if (typeSymbol is INamedTypeSymbol namedTypeSymbol &&
100-
namedTypeSymbol.TypeArguments.Length == 1 &&
111+
namedTypeSymbol.TypeArguments.Length >= 1 &&
101112
namedTypeSymbol.IsSupportedCollection())
102113
{
103-
var underlyingTypeSyntax = CreateTypeSyntaxForSymbol(namedTypeSymbol.TypeArguments.Single());
104-
var listSyntax = SyntaxFactory.GenericName(
105-
SyntaxFactory.Identifier("List"),
106-
SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList(new[] { underlyingTypeSyntax })));
114+
var underlyingTypeSyntaxes = namedTypeSymbol.TypeArguments.Select(typeArgument => CreateTypeSyntaxForSymbol(typeArgument));
115+
if (underlyingTypeSyntaxes.Any(underlyingTypeSyntax => underlyingTypeSyntax == null))
116+
{
117+
return null;
118+
}
119+
120+
var collectionIdentifier = namedTypeSymbol.Name;
121+
122+
var collectionSyntax = SyntaxFactory.GenericName(
123+
SyntaxFactory.Identifier(collectionIdentifier),
124+
SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList(underlyingTypeSyntaxes)));
107125

108126
result = SyntaxFactory.QualifiedName(
109127
SyntaxFactory.QualifiedName(
110128
SyntaxFactory.QualifiedName(
111129
SyntaxFactory.IdentifierName("System"),
112130
SyntaxFactory.IdentifierName("Collections")),
113131
SyntaxFactory.IdentifierName("Generic")),
114-
listSyntax);
132+
collectionSyntax);
133+
}
134+
else if (typeSymbol.IsUserDefinedCollection())
135+
{
136+
return null;
115137
}
116138
else
117139
{
118140
var (isNullable, underlingTypeSymbol) = typeSymbol.DiscardNullable();
119-
120141
var newTypeName = ProcessTypeSymbol(underlingTypeSymbol);
142+
143+
if (newTypeName == null)
144+
{
145+
return null;
146+
}
147+
121148
result = isNullable ? SyntaxFactoryUtilities.GetNullableType(newTypeName) :
122149
SyntaxFactory.ParseTypeName(newTypeName);
123150
}
@@ -171,7 +198,7 @@ private ExpressionSyntax GenerateExpressionFromBsonAttributeArgumentInfo(TypedCo
171198
_ => null
172199
};
173200

174-
private void GenerateFields(ITypeSymbol typeSymbol, List<MemberDeclarationSyntax> members)
201+
private bool GenerateFields(ITypeSymbol typeSymbol, List<MemberDeclarationSyntax> members)
175202
{
176203
var typeFields = typeSymbol
177204
.GetMembers()
@@ -184,6 +211,10 @@ private void GenerateFields(ITypeSymbol typeSymbol, List<MemberDeclarationSyntax
184211
foreach (var fieldSymbol in typeFields)
185212
{
186213
var typeSyntax = CreateTypeSyntaxForSymbol(fieldSymbol.Type);
214+
if (typeSyntax == null)
215+
{
216+
return false;
217+
}
187218

188219
var variableDeclaration = SyntaxFactory.VariableDeclaration(typeSyntax, SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(fieldSymbol.Name)));
189220

@@ -199,9 +230,11 @@ private void GenerateFields(ITypeSymbol typeSymbol, List<MemberDeclarationSyntax
199230

200231
members.Add(fieldDeclaration);
201232
}
233+
234+
return true;
202235
}
203236

204-
private void GenerateProperties(ITypeSymbol typeSymbol, List<MemberDeclarationSyntax> members)
237+
private bool GenerateProperties(ITypeSymbol typeSymbol, List<MemberDeclarationSyntax> members)
205238
{
206239
var typeProperties = typeSymbol
207240
.GetMembers()
@@ -215,6 +248,10 @@ private void GenerateProperties(ITypeSymbol typeSymbol, List<MemberDeclarationSy
215248
foreach (var propertySymbol in typeProperties)
216249
{
217250
var typeSyntax = CreateTypeSyntaxForSymbol(propertySymbol.Type);
251+
if (typeSyntax == null)
252+
{
253+
return false;
254+
}
218255

219256
var propertyDeclaration = SyntaxFactory.PropertyDeclaration(typeSyntax, propertySymbol.Name);
220257

@@ -229,6 +266,8 @@ private void GenerateProperties(ITypeSymbol typeSymbol, List<MemberDeclarationSy
229266

230267
members.Add(propertyDeclaration);
231268
}
269+
270+
return true;
232271
}
233272

234273
private BaseListSyntax GetBaseListSyntax(string typeName)
@@ -244,7 +283,8 @@ private string GetFullName(ITypeSymbol typeSymbol) =>
244283

245284
private (string RemappedName, string FullTypeName) GetGeneratedTypeMapping(ITypeSymbol typeSymbol, bool userOnlyTypes)
246285
{
247-
if (typeSymbol == null)
286+
if (typeSymbol == null ||
287+
typeSymbol.IsUserDefinedCollection())
248288
{
249289
return default;
250290
}
@@ -285,8 +325,11 @@ private TypeDeclarationSyntax GetSyntaxForClassOrStruct(ITypeSymbol typeSymbol,
285325

286326
var members = new List<MemberDeclarationSyntax>();
287327

288-
GenerateProperties(typeSymbol, members);
289-
GenerateFields(typeSymbol, members);
328+
if (!GenerateProperties(typeSymbol, members) ||
329+
!GenerateFields(typeSymbol, members))
330+
{
331+
return null;
332+
}
290333

291334
typeDeclaration = typeDeclaration
292335
.AddMembers(members.ToArray())
@@ -298,6 +341,11 @@ private TypeDeclarationSyntax GetSyntaxForClassOrStruct(ITypeSymbol typeSymbol,
298341
{
299342
var baseTypeNameGenerated = ProcessTypeSymbol(typeSymbol.BaseType);
300343

344+
if (baseTypeNameGenerated == null)
345+
{
346+
return null;
347+
}
348+
301349
typeDeclaration = typeDeclaration.WithBaseList(GetBaseListSyntax(baseTypeNameGenerated));
302350
}
303351

@@ -351,6 +399,11 @@ private BaseTypeDeclarationSyntax GetSyntaxNodeFromSymbol(ITypeSymbol typeSymbol
351399
throw new NotSupportedException($"Symbol type {typeSymbol.TypeKind} is not supported.");
352400
}
353401

402+
if (typeDeclaration == null)
403+
{
404+
return null;
405+
}
406+
354407
typeDeclaration = typeDeclaration.NormalizeWhitespace();
355408
return typeDeclaration;
356409
}

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

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ namespace MongoDB.Analyzer.Core;
1616

1717
internal static class SymbolExtensions
1818
{
19+
private const string AssemblyMongoDBBson = "MongoDB.Bson";
1920
private const string AssemblyMongoDBDriver = "MongoDB.Driver";
21+
private const string NamespaceCollectionGeneric = "System.Collections.Generic";
2022
private const string NamespaceEF = "Microsoft.EntityFrameworkCore";
2123
private const string NamespaceMongoDBBson = "MongoDB.Bson";
2224
private const string NamespaceMongoDBBsonAttributes = "MongoDB.Bson.Serialization.Attributes";
@@ -54,13 +56,6 @@ internal static class SymbolExtensions
5456
"MongoDB.Bson.Serialization.Options.TimeSpanUnits"
5557
};
5658

57-
private static readonly HashSet<string> s_supportedCollections = new()
58-
{
59-
"System.Collections.Generic.IEnumerable<T>",
60-
"System.Collections.Generic.IList<T>",
61-
"System.Collections.Generic.List<T>"
62-
};
63-
6459
private static readonly HashSet<string> s_supportedSystemTypes = new()
6560
{
6661
"System.DateTime",
@@ -160,7 +155,11 @@ public static bool IsDBSet(this ITypeSymbol typeSymbol) =>
160155
typeSymbol?.Name == "DbSet" &&
161156
typeSymbol?.ContainingNamespace?.ToDisplayString() == NamespaceEF;
162157

163-
public static bool IsDefinedInMongoDriver(this ISymbol symbol) => symbol?.ContainingAssembly.Name == AssemblyMongoDBDriver;
158+
public static bool IsDefinedInMongoBson(this ISymbol symbol) => symbol?.ContainingNamespace?.ToDisplayString() == NamespaceMongoDBBson &&
159+
symbol?.ContainingAssembly.Name == AssemblyMongoDBBson;
160+
161+
public static bool IsDefinedInMongoDriver(this ISymbol symbol) => symbol?.ContainingNamespace?.ToDisplayString() == NamespaceMongoDBDriver &&
162+
symbol?.ContainingAssembly.Name == AssemblyMongoDBDriver;
164163

165164
public static bool IsDefinedInMongoLinqOrSystemLinq(this ISymbol symbol)
166165
{
@@ -173,6 +172,22 @@ public static bool IsDefinedInMongoLinqOrSystemLinq(this ISymbol symbol)
173172
symbol?.ContainingAssembly.Name == AssemblyMongoDBDriver;
174173
}
175174

175+
public static bool IsDefinedInSystem(this ISymbol symbol)
176+
{
177+
var containingNamespace = symbol?.ContainingNamespace;
178+
while (containingNamespace != null)
179+
{
180+
if (containingNamespace.Name == NamespaceSystem)
181+
{
182+
return true;
183+
}
184+
185+
containingNamespace = containingNamespace.ContainingNamespace;
186+
}
187+
188+
return false;
189+
}
190+
176191
public static bool IsFindFluent(this ITypeSymbol typeSymbol) =>
177192
typeSymbol?.Name switch
178193
{
@@ -222,21 +237,43 @@ TypeKind.Enum or
222237
_ => false
223238
};
224239

225-
public static bool IsSupportedCollection(this ITypeSymbol typeSymbol)
240+
public static bool IsSupportedCollection(this ITypeSymbol typeSymbol) =>
241+
typeSymbol is INamedTypeSymbol namedTypeSymbol &&
242+
namedTypeSymbol.ContainingNamespace.ToDisplayString() == NamespaceCollectionGeneric;
243+
244+
public static bool IsSupportedIMongoCollection(this ITypeSymbol typeSymbol) =>
245+
typeSymbol.IsIMongoCollection() &&
246+
typeSymbol is INamedTypeSymbol namedType &&
247+
namedType.TypeArguments.Length == 1 &&
248+
namedType.TypeArguments[0].IsSupportedMongoCollectionType();
249+
250+
public static bool IsSupportedMongoCollectionType(this ITypeSymbol typeSymbol) =>
251+
typeSymbol.TypeKind == TypeKind.Class &&
252+
!typeSymbol.IsAnonymousType;
253+
254+
public static bool IsSupportedSystemType(this ITypeSymbol typeSymbol, string fullTypeName) =>
255+
(typeSymbol.SpecialType != SpecialType.None || s_supportedSystemTypes.Contains(fullTypeName)) &&
256+
typeSymbol?.ContainingNamespace?.ToDisplayString() == NamespaceSystem;
257+
258+
public static bool IsUserDefinedCollection(this ITypeSymbol typeSymbol)
226259
{
227-
if (typeSymbol is not INamedTypeSymbol namedTypeSymbol)
260+
if (typeSymbol is not INamedTypeSymbol namedTypeSymbol ||
261+
namedTypeSymbol.IsDefinedInMongoBson() ||
262+
namedTypeSymbol.IsDefinedInMongoDriver() ||
263+
namedTypeSymbol.IsDefinedInSystem())
228264
{
229265
return false;
230266
}
231267

232268
while (namedTypeSymbol != null)
233269
{
234-
if (s_supportedCollections.Contains(namedTypeSymbol.ConstructedFrom?.ToDisplayString()))
270+
if (namedTypeSymbol.IsSupportedCollection())
235271
{
236272
return true;
237273
}
238274

239-
if (namedTypeSymbol.Interfaces.Any(i => s_supportedCollections.Contains(i.ConstructedFrom?.ToDisplayString()))){
275+
if (namedTypeSymbol.Interfaces.Any(i => i.IsSupportedCollection()))
276+
{
240277
return true;
241278
}
242279

@@ -246,20 +283,6 @@ public static bool IsSupportedCollection(this ITypeSymbol typeSymbol)
246283
return false;
247284
}
248285

249-
public static bool IsSupportedIMongoCollection(this ITypeSymbol typeSymbol) =>
250-
typeSymbol.IsIMongoCollection() &&
251-
typeSymbol is INamedTypeSymbol namedType &&
252-
namedType.TypeArguments.Length == 1 &&
253-
namedType.TypeArguments[0].IsSupportedMongoCollectionType();
254-
255-
public static bool IsSupportedMongoCollectionType(this ITypeSymbol typeSymbol) =>
256-
typeSymbol.TypeKind == TypeKind.Class &&
257-
!typeSymbol.IsAnonymousType;
258-
259-
public static bool IsSupportedSystemType(this ITypeSymbol typeSymbol, string fullTypeName) =>
260-
(typeSymbol.SpecialType != SpecialType.None || s_supportedSystemTypes.Contains(fullTypeName)) &&
261-
typeSymbol?.ContainingNamespace?.ToDisplayString() == NamespaceSystem;
262-
263286
private static SyntaxToken[] GetPublicFieldModifiers() =>
264287
new[] { SyntaxFactory.Token(SyntaxKind.PublicKeyword) };
265288

0 commit comments

Comments
 (0)