Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ public static ExpressionsAnalysis ProcessSemanticModel(MongoAnalysisContext cont

try
{
foreach (var typeArgument in namedType.TypeArguments)
if (!ProcessTypeArguments(namedType.TypeArguments, typesProcessor))
{
typesProcessor.ProcessTypeSymbol(typeArgument);
continue;
}

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

return (nodeType, namedType, expressionNode);
}

private static bool ProcessTypeArguments(IEnumerable<ITypeSymbol> typeArguments, TypesProcessor typesProcessor)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could simplify with
namedType.TypeArguments.All(t => typesProcessor.ProcessTypeSymbol(t) != null)
Maybe no need for the helper method.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

{
foreach (var typeArgument in typeArguments)
{
var remappedName = typesProcessor.ProcessTypeSymbol(typeArgument);
if (remappedName == null)
{
return false;
}
}

return true;
}
}
4 changes: 4 additions & 0 deletions src/MongoDB.Analyzer/Core/Linq/LinqExpressionProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ mongoQueryableTypeInfo.Type is not INamedTypeSymbol mongoQueryableNamedType ||
if (PreanalyzeLinqExpression(node, semanticModel, invalidExpressionNodes))
{
var generatedMongoQueryableTypeName = typesProcessor.ProcessTypeSymbol(mongoQueryableNamedType.TypeArguments[0]);
if (generatedMongoQueryableTypeName == null)
{
continue;
}

var rewriteContext = RewriteContext.Linq(node, deepestMongoQueryableNode, semanticModel, typesProcessor);
var (newLinqExpression, constantsMapper) = RewriteExpression(rewriteContext);
Expand Down
5 changes: 5 additions & 0 deletions src/MongoDB.Analyzer/Core/Poco/PocoExpressionProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ public static ExpressionsAnalysis ProcessSemanticModel(MongoAnalysisContext cont
if (PreanalyzeClassDeclaration(context, classSymbol))
{
var generatedClassName = typesProcessor.ProcessTypeSymbol(classSymbol);
if (generatedClassName == null)
{
continue;
}

var generatedClassNode = (ClassDeclarationSyntax)(typesProcessor.GetTypeSymbolToMemberDeclarationMapping(classSymbol));
var expressionContext = new ExpressionAnalysisContext(new ExpressionAnalysisNode(classNode, null, generatedClassNode, null, classNode.GetLocation()));
analysisContexts.Add(expressionContext);
Expand Down
1 change: 1 addition & 0 deletions src/MongoDB.Analyzer/Core/ReferencesProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public static ReferencesContainer GetReferences(IEnumerable<MetadataReference> m
resultReferences.Add(MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location));
resultReferences.Add(MetadataReference.CreateFromFile(typeof(IEnumerator<int>).Assembly.Location));
resultReferences.Add(MetadataReference.CreateFromFile(typeof(Queryable).Assembly.Location));
resultReferences.Add(MetadataReference.CreateFromFile(typeof(Stack<int>).Assembly.Location));
resultReferences.Add(MetadataReference.CreateFromFile(typeof(System.Dynamic.DynamicObject).Assembly.Location));
resultReferences.Add(MetadataReference.CreateFromFile(typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location));
resultReferences.Add(MetadataReference.CreateFromFile(typeof(Task).Assembly.Location));
Expand Down
77 changes: 65 additions & 12 deletions src/MongoDB.Analyzer/Core/TypesProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ public string ProcessTypeSymbol(ITypeSymbol typeSymbol)

var rewrittenDeclarationSyntax = GetSyntaxNodeFromSymbol(typeSymbol, remappedName);

if (rewrittenDeclarationSyntax == null)
{
_processedTypes.Remove(fullTypeName);
return null;
}

var typeCode = rewrittenDeclarationSyntax.ToFullString();
var newTypeDeclaration = SyntaxFactory.ParseMemberDeclaration(typeCode);

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

if (nextTypeSyntax == null)
{
return null;
}

result = SyntaxFactory.ArrayType(nextTypeSyntax, arrayRankSpecifiers.Value);
}
// TODO optimize
else if (typeSymbol is INamedTypeSymbol namedTypeSymbol &&
namedTypeSymbol.TypeArguments.Length == 1 &&
namedTypeSymbol.TypeArguments.Length >= 1 &&
namedTypeSymbol.IsSupportedCollection())
{
var underlyingTypeSyntax = CreateTypeSyntaxForSymbol(namedTypeSymbol.TypeArguments.Single());
var listSyntax = SyntaxFactory.GenericName(
SyntaxFactory.Identifier("List"),
SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList(new[] { underlyingTypeSyntax })));
var underlyingTypeSyntaxes = namedTypeSymbol.TypeArguments.Select(typeArgument => CreateTypeSyntaxForSymbol(typeArgument));
if (underlyingTypeSyntaxes.Any(underlyingTypeSyntax => underlyingTypeSyntax == null))
{
return null;
}

var collectionIdentifier = namedTypeSymbol.Name;

var collectionSyntax = SyntaxFactory.GenericName(
SyntaxFactory.Identifier(collectionIdentifier),
SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList(underlyingTypeSyntaxes)));

result = SyntaxFactory.QualifiedName(
SyntaxFactory.QualifiedName(
SyntaxFactory.QualifiedName(
SyntaxFactory.IdentifierName("System"),
SyntaxFactory.IdentifierName("Collections")),
SyntaxFactory.IdentifierName("Generic")),
listSyntax);
collectionSyntax);
}
else if (typeSymbol.IsUserDefinedCollection())
{
return null;
}
else
{
var (isNullable, underlingTypeSymbol) = typeSymbol.DiscardNullable();

var newTypeName = ProcessTypeSymbol(underlingTypeSymbol);

if (newTypeName == null)
{
return null;
}

result = isNullable ? SyntaxFactoryUtilities.GetNullableType(newTypeName) :
SyntaxFactory.ParseTypeName(newTypeName);
}
Expand Down Expand Up @@ -171,7 +198,7 @@ private ExpressionSyntax GenerateExpressionFromBsonAttributeArgumentInfo(TypedCo
_ => null
};

private void GenerateFields(ITypeSymbol typeSymbol, List<MemberDeclarationSyntax> members)
private bool GenerateFields(ITypeSymbol typeSymbol, List<MemberDeclarationSyntax> members)
{
var typeFields = typeSymbol
.GetMembers()
Expand All @@ -184,6 +211,10 @@ private void GenerateFields(ITypeSymbol typeSymbol, List<MemberDeclarationSyntax
foreach (var fieldSymbol in typeFields)
{
var typeSyntax = CreateTypeSyntaxForSymbol(fieldSymbol.Type);
if (typeSyntax == null)
{
return false;
}

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

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

members.Add(fieldDeclaration);
}

return true;
}

private void GenerateProperties(ITypeSymbol typeSymbol, List<MemberDeclarationSyntax> members)
private bool GenerateProperties(ITypeSymbol typeSymbol, List<MemberDeclarationSyntax> members)
{
var typeProperties = typeSymbol
.GetMembers()
Expand All @@ -215,6 +248,10 @@ private void GenerateProperties(ITypeSymbol typeSymbol, List<MemberDeclarationSy
foreach (var propertySymbol in typeProperties)
{
var typeSyntax = CreateTypeSyntaxForSymbol(propertySymbol.Type);
if (typeSyntax == null)
{
return false;
}

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

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

members.Add(propertyDeclaration);
}

return true;
}

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

private (string RemappedName, string FullTypeName) GetGeneratedTypeMapping(ITypeSymbol typeSymbol, bool userOnlyTypes)
{
if (typeSymbol == null)
if (typeSymbol == null ||
typeSymbol.IsUserDefinedCollection())
{
return default;
}
Expand Down Expand Up @@ -285,8 +325,11 @@ private TypeDeclarationSyntax GetSyntaxForClassOrStruct(ITypeSymbol typeSymbol,

var members = new List<MemberDeclarationSyntax>();

GenerateProperties(typeSymbol, members);
GenerateFields(typeSymbol, members);
if (!GenerateProperties(typeSymbol, members) ||
!GenerateFields(typeSymbol, members))
{
return null;
}

typeDeclaration = typeDeclaration
.AddMembers(members.ToArray())
Expand All @@ -298,6 +341,11 @@ private TypeDeclarationSyntax GetSyntaxForClassOrStruct(ITypeSymbol typeSymbol,
{
var baseTypeNameGenerated = ProcessTypeSymbol(typeSymbol.BaseType);

if (baseTypeNameGenerated == null)
{
return null;
}

typeDeclaration = typeDeclaration.WithBaseList(GetBaseListSyntax(baseTypeNameGenerated));
}

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

if (typeDeclaration == null)
{
return null;
}

typeDeclaration = typeDeclaration.NormalizeWhitespace();
return typeDeclaration;
}
Expand Down
75 changes: 49 additions & 26 deletions src/MongoDB.Analyzer/Core/Utilities/SymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ namespace MongoDB.Analyzer.Core;

internal static class SymbolExtensions
{
private const string AssemblyMongoDBBson = "MongoDB.Bson";
private const string AssemblyMongoDBDriver = "MongoDB.Driver";
private const string NamespaceCollectionGeneric = "System.Collections.Generic";
private const string NamespaceEF = "Microsoft.EntityFrameworkCore";
private const string NamespaceMongoDBBson = "MongoDB.Bson";
private const string NamespaceMongoDBBsonAttributes = "MongoDB.Bson.Serialization.Attributes";
Expand Down Expand Up @@ -54,13 +56,6 @@ internal static class SymbolExtensions
"MongoDB.Bson.Serialization.Options.TimeSpanUnits"
};

private static readonly HashSet<string> s_supportedCollections = new()
{
"System.Collections.Generic.IEnumerable<T>",
"System.Collections.Generic.IList<T>",
"System.Collections.Generic.List<T>"
};

private static readonly HashSet<string> s_supportedSystemTypes = new()
{
"System.DateTime",
Expand Down Expand Up @@ -160,7 +155,11 @@ public static bool IsDBSet(this ITypeSymbol typeSymbol) =>
typeSymbol?.Name == "DbSet" &&
typeSymbol?.ContainingNamespace?.ToDisplayString() == NamespaceEF;

public static bool IsDefinedInMongoDriver(this ISymbol symbol) => symbol?.ContainingAssembly.Name == AssemblyMongoDBDriver;
public static bool IsDefinedInMongoBson(this ISymbol symbol) => symbol?.ContainingNamespace?.ToDisplayString() == NamespaceMongoDBBson &&
symbol?.ContainingAssembly.Name == AssemblyMongoDBBson;

public static bool IsDefinedInMongoDriver(this ISymbol symbol) => symbol?.ContainingNamespace?.ToDisplayString() == NamespaceMongoDBDriver &&
symbol?.ContainingAssembly.Name == AssemblyMongoDBDriver;

public static bool IsDefinedInMongoLinqOrSystemLinq(this ISymbol symbol)
{
Expand All @@ -173,6 +172,22 @@ public static bool IsDefinedInMongoLinqOrSystemLinq(this ISymbol symbol)
symbol?.ContainingAssembly.Name == AssemblyMongoDBDriver;
}

public static bool IsDefinedInSystem(this ISymbol symbol)
{
var containingNamespace = symbol?.ContainingNamespace;
while (containingNamespace != null)
{
if (containingNamespace.Name == NamespaceSystem)
{
return true;
}

containingNamespace = containingNamespace.ContainingNamespace;
}

return false;
}

public static bool IsFindFluent(this ITypeSymbol typeSymbol) =>
typeSymbol?.Name switch
{
Expand Down Expand Up @@ -222,21 +237,43 @@ TypeKind.Enum or
_ => false
};

public static bool IsSupportedCollection(this ITypeSymbol typeSymbol)
public static bool IsSupportedCollection(this ITypeSymbol typeSymbol) =>
typeSymbol is INamedTypeSymbol namedTypeSymbol &&
namedTypeSymbol.ContainingNamespace.ToDisplayString() == NamespaceCollectionGeneric;

public static bool IsSupportedIMongoCollection(this ITypeSymbol typeSymbol) =>
typeSymbol.IsIMongoCollection() &&
typeSymbol is INamedTypeSymbol namedType &&
namedType.TypeArguments.Length == 1 &&
namedType.TypeArguments[0].IsSupportedMongoCollectionType();

public static bool IsSupportedMongoCollectionType(this ITypeSymbol typeSymbol) =>
typeSymbol.TypeKind == TypeKind.Class &&
!typeSymbol.IsAnonymousType;

public static bool IsSupportedSystemType(this ITypeSymbol typeSymbol, string fullTypeName) =>
(typeSymbol.SpecialType != SpecialType.None || s_supportedSystemTypes.Contains(fullTypeName)) &&
typeSymbol?.ContainingNamespace?.ToDisplayString() == NamespaceSystem;

public static bool IsUserDefinedCollection(this ITypeSymbol typeSymbol)
{
if (typeSymbol is not INamedTypeSymbol namedTypeSymbol)
if (typeSymbol is not INamedTypeSymbol namedTypeSymbol ||
namedTypeSymbol.IsDefinedInMongoBson() ||
namedTypeSymbol.IsDefinedInMongoDriver() ||
namedTypeSymbol.IsDefinedInSystem())
{
return false;
}

while (namedTypeSymbol != null)
{
if (s_supportedCollections.Contains(namedTypeSymbol.ConstructedFrom?.ToDisplayString()))
if (namedTypeSymbol.IsSupportedCollection())
{
return true;
}

if (namedTypeSymbol.Interfaces.Any(i => s_supportedCollections.Contains(i.ConstructedFrom?.ToDisplayString()))){
if (namedTypeSymbol.Interfaces.Any(i => i.IsSupportedCollection()))
{
return true;
}

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

public static bool IsSupportedIMongoCollection(this ITypeSymbol typeSymbol) =>
typeSymbol.IsIMongoCollection() &&
typeSymbol is INamedTypeSymbol namedType &&
namedType.TypeArguments.Length == 1 &&
namedType.TypeArguments[0].IsSupportedMongoCollectionType();

public static bool IsSupportedMongoCollectionType(this ITypeSymbol typeSymbol) =>
typeSymbol.TypeKind == TypeKind.Class &&
!typeSymbol.IsAnonymousType;

public static bool IsSupportedSystemType(this ITypeSymbol typeSymbol, string fullTypeName) =>
(typeSymbol.SpecialType != SpecialType.None || s_supportedSystemTypes.Contains(fullTypeName)) &&
typeSymbol?.ContainingNamespace?.ToDisplayString() == NamespaceSystem;

private static SyntaxToken[] GetPublicFieldModifiers() =>
new[] { SyntaxFactory.Token(SyntaxKind.PublicKeyword) };

Expand Down
Loading