Skip to content

Commit 626bb3f

Browse files
VS-146: Support Fully Qualified Names for Data Models (#73)
1 parent 55effbc commit 626bb3f

File tree

9 files changed

+287
-10
lines changed

9 files changed

+287
-10
lines changed

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

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,35 @@ private static IEnumerable<SyntaxNode> GetBuildersDefinitionNodes(SemanticModel
143143
{
144144
nodesProcessed.Add(node);
145145

146-
// Skip MongoDB.Driver.Builders<T> nodes
146+
var isBuildersContainer = false;
147+
var containsAlias = false;
148+
149+
// Check if Member Expression is Builders Container
150+
// Example: Builders<T>
147151
if (node is MemberAccessExpressionSyntax memberAccessExpressionSyntax &&
148152
semanticModel.GetSymbolInfo(memberAccessExpressionSyntax.Expression).Symbol is INamedTypeSymbol namedTypeSymbol &&
149-
namedTypeSymbol.IsBuildersContainer() &&
150-
semanticModel.GetAliasInfo(memberAccessExpressionSyntax.Expression) == null)
153+
namedTypeSymbol.IsBuildersContainer())
154+
{
155+
isBuildersContainer = true;
156+
}
157+
158+
// Check if Node contains alias
159+
// Example: driverAlias.Builders<T>, mongoDBNamespaceAlias.Driver.Builders<T>
160+
var syntaxNode = (node as MemberAccessExpressionSyntax)?.Expression;
161+
while (syntaxNode != null)
162+
{
163+
if (semanticModel.GetAliasInfo(syntaxNode) != null)
164+
{
165+
containsAlias = true;
166+
break;
167+
}
168+
169+
syntaxNode = (syntaxNode as MemberAccessExpressionSyntax)?.Expression;
170+
}
171+
172+
// Skip Nodes that are Builder Containers and that don't contain alias
173+
// Examples: MongoDB.Driver.Builders
174+
if (isBuildersContainer && !containsAlias)
151175
{
152176
continue;
153177
}

src/MongoDB.Analyzer/Core/ExpressionProcessor.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,13 @@ private static bool RewriteIdentifiers(RewriteContext rewriteContext, HashSet<Sy
131131
var processGenerics = false;
132132
var removeFluentParameters = false;
133133
IdentifierNameSyntax[] lambdaAndQueryIdentifiers = null;
134-
IEnumerable<SimpleNameSyntax> expressionDescendants;
134+
IEnumerable<NameSyntax> expressionDescendants;
135135

136136
switch (rewriteContext.AnalysisType)
137137
{
138138
case AnalysisType.Builders:
139139
{
140-
expressionDescendants = expressionNode.DescendantNodesWithSkipList<SimpleNameSyntax>(nodesProcessed);
140+
expressionDescendants = expressionNode.DescendantNodesWithSkipList<NameSyntax>(nodesProcessed);
141141
processGenerics = true;
142142
removeFluentParameters = true;
143143
break;
@@ -154,7 +154,15 @@ private static bool RewriteIdentifiers(RewriteContext rewriteContext, HashSet<Sy
154154
})
155155
.ToArray();
156156

157-
expressionDescendants = expressionNode.DescendantNodes(n => !rootNodes.Contains(n)).OfType<IdentifierNameSyntax>();
157+
expressionDescendants = expressionNode
158+
.DescendantNodes(n => !rootNodes.Contains(n))
159+
.OfType<NameSyntax>()
160+
.Where(nameSyntax =>
161+
{
162+
var qualifiedNameSyntax = nameSyntax as QualifiedNameSyntax;
163+
return nameSyntax is IdentifierNameSyntax || qualifiedNameSyntax?.Right is IdentifierNameSyntax;
164+
});
165+
158166
break;
159167
}
160168
default:
@@ -385,7 +393,7 @@ private static RewriteResult HandleMethod(
385393

386394
private static RewriteResult HandleRemappedType(
387395
RewriteContext rewriteContext,
388-
SimpleNameSyntax identifierNode,
396+
NameSyntax identifierNode,
389397
TypeInfo typeInfo,
390398
bool processGenericTypes = false)
391399
{

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public static IdentifierNameSyntax GetSingleIdentifier(this SyntaxNode syntaxNod
6969
public static MethodDeclarationSyntax GetSingleMethod(this SyntaxNode syntaxNode, string name) =>
7070
syntaxNode.DescendantNodes().OfType<MethodDeclarationSyntax>().Single(m => m.Identifier.Text == name);
7171

72-
public static SyntaxNode GetTopMostInvocationOrBinaryExpressionSyntax(SimpleNameSyntax identifier, IdentifierNameSyntax[] lambdaAndQueryIdentifiers)
72+
public static SyntaxNode GetTopMostInvocationOrBinaryExpressionSyntax(NameSyntax identifier, IdentifierNameSyntax[] lambdaAndQueryIdentifiers)
7373
{
7474
SyntaxNode previous = null;
7575
SyntaxNode result = identifier;
@@ -119,7 +119,7 @@ bool IsValid(SyntaxNode previousSyntaxNode, SyntaxNode syntaxNode)
119119
return result;
120120
}
121121

122-
public static bool IsLeaf(this SimpleNameSyntax identifier)
122+
public static bool IsLeaf(this NameSyntax identifier)
123123
{
124124
// parent is access expression
125125
if (identifier.Parent is MemberAccessExpressionSyntax memberAccessExpressionSyntax)
@@ -146,6 +146,10 @@ public static bool IsLeaf(this SimpleNameSyntax identifier)
146146
return true;
147147
}
148148
}
149+
else if (identifier.Parent is AliasQualifiedNameSyntax || identifier.Parent is QualifiedNameSyntax)
150+
{
151+
return false;
152+
}
149153
else if (IsMethodAndNotLeaf(identifier.Parent))
150154
{
151155
// part of methods access chain
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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.Tests.Common.DataModel;
16+
using MongoDB.Bson;
17+
using MongoDB.Driver;
18+
19+
#pragma warning disable IDE0005
20+
using common = MongoDB.Analyzer.Tests.Common;
21+
using dataModel = MongoDB.Analyzer.Tests.Common.DataModel;
22+
using driver = MongoDB.Driver;
23+
using mongo = MongoDB;
24+
using user = MongoDB.Analyzer.Tests.Common.DataModel.User;
25+
#pragma warning restore IDE0005
26+
27+
namespace MongoDB.Analyzer.Tests.Common.TestCases.Builders
28+
{
29+
public sealed class BuildersQualifiedNames : TestCasesBase
30+
{
31+
[BuildersMQL("{ \"Age\" : 22 }")]
32+
[BuildersMQL("{ \"Age\" : 22 }")]
33+
[BuildersMQL("{ \"Age\" : 22 }")]
34+
[BuildersMQL("{ \"Age\" : 22 }")]
35+
[BuildersMQL("{ \"Age\" : 22 }")]
36+
[BuildersMQL("{ \"ByteNullable\" : common::DataModel.StaticHolder.ReadonlyByteNullable }")]
37+
[BuildersMQL("{ \"ByteNullable\" : dataModel::StaticHolder.ReadonlyByteNullable }")]
38+
public void Qualified_alias()
39+
{
40+
_ = Builders<user>.Filter.Eq(user => user.Age, 22);
41+
_ = Builders<dataModel::User>.Filter.Eq(user => user.Age, 22);
42+
_ = Builders<common::DataModel.User>.Filter.Eq(user => user.Age, 22);
43+
44+
_ = driver.Builders<User>.Filter.Eq(user => user.Age, 22);
45+
_ = mongo.Driver.Builders<User>.Filter.Eq(user => user.Age, 22);
46+
47+
_ = Builders<NullableHolder>.Filter.Eq(n => n.ByteNullable, common::DataModel.StaticHolder.ReadonlyByteNullable);
48+
_ = Builders<NullableHolder>.Filter.Eq(n => n.ByteNullable, dataModel::StaticHolder.ReadonlyByteNullable);
49+
}
50+
51+
[BuildersMQL("find({ \"$or\" : [{ \"Age\" : { \"$lt\" : 10 } }, { \"Age\" : { \"$gt\" : 20 } }, { \"Name\" : { \"$ne\" : \"Bob\" }, \"LastName\" : { \"$ne\" : null } }] })")]
52+
public void Qualified_fluent_api()
53+
{
54+
var mongoCollection = GetMongoCollection();
55+
mongoCollection.Find(
56+
u => u.Age < 10 || u.Age > 20 || (u.Name != "Bob" && u.LastName != null),
57+
new MongoDB.Driver.FindOptions() { MaxTime = System.TimeSpan.FromSeconds(3), Max = new MongoDB.Bson.BsonDocument("min", 2) });
58+
}
59+
60+
[BuildersMQL("{ \"field\" : { \"$elemMatch\" : { \"field\" : fieldValue } } }")]
61+
[BuildersMQL("{ \"ByteNullable\" : MongoDB.Analyzer.Tests.Common.DataModel.StaticHolder.ReadonlyByteNullable }")]
62+
[BuildersMQL("{ \"$or\" : [{ \"SiblingsCount\" : MongoDB.Analyzer.Tests.Common.DataModel.StaticHolder.ReadonlyByte }, { \"SiblingsCount\" : Analyzer.Tests.Common.DataModel.StaticHolder.ReadonlyShort }, { \"SiblingsCount\" : Tests.Common.DataModel.StaticHolder.ReadonlyInt }, { \"TicksSinceBirth\" : NumberLong(Common.DataModel.StaticHolder.ReadonlyLong) }, { \"Name\" : DataModel.StaticHolder.ReadonlyString }, { \"Name\" : StaticHolder.ReadonlyString }] }")]
63+
[BuildersMQL("{ \"Age\" : 22 }")]
64+
[BuildersMQL("{ \"Age\" : 22 }")]
65+
[BuildersMQL("{ \"Age\" : 22 }")]
66+
[BuildersMQL("{ \"Age\" : 22 }")]
67+
[BuildersMQL("{ \"Age\" : 22 }")]
68+
[BuildersMQL("{ \"Age\" : 22 }")]
69+
[BuildersMQL("{ \"Age\" : 22 }")]
70+
[BuildersMQL("{ \"Age\" : 22 }")]
71+
public void Qualified_type_names()
72+
{
73+
var fieldValue = "fieldValue";
74+
_ = Builders<BsonDocument>.Filter.ElemMatch("field", MongoDB.Driver.Builders<MongoDB.Bson.BsonValue>.Filter.Eq("field", fieldValue));
75+
76+
_ = Builders<NullableHolder>.Filter.Eq(n => n.ByteNullable, MongoDB.Analyzer.Tests.Common.DataModel.StaticHolder.ReadonlyByteNullable);
77+
78+
_ = Builders<Person>.Filter.Eq(p => p.SiblingsCount, MongoDB.Analyzer.Tests.Common.DataModel.StaticHolder.ReadonlyByte) |
79+
Builders<Person>.Filter.Eq(p => p.SiblingsCount, Analyzer.Tests.Common.DataModel.StaticHolder.ReadonlyShort) |
80+
Builders<Person>.Filter.Eq(p => p.SiblingsCount, Tests.Common.DataModel.StaticHolder.ReadonlyInt) |
81+
Builders<Person>.Filter.Eq(p => p.TicksSinceBirth, Common.DataModel.StaticHolder.ReadonlyLong) |
82+
Builders<Person>.Filter.Eq(p => p.Name, DataModel.StaticHolder.ReadonlyString) |
83+
Builders<Person>.Filter.Eq(p => p.Name, StaticHolder.ReadonlyString);
84+
85+
_ = Builders<MongoDB.Analyzer.Tests.Common.DataModel.User>.Filter.Eq(user => user.Age, 22);
86+
_ = Builders<Analyzer.Tests.Common.DataModel.User>.Filter.Eq(user => user.Age, 22);
87+
_ = Builders<Tests.Common.DataModel.User>.Filter.Eq(user => user.Age, 22);
88+
_ = Builders<Common.DataModel.User>.Filter.Eq(user => user.Age, 22);
89+
_ = Builders<DataModel.User>.Filter.Eq(user => user.Age, 22);
90+
91+
_ = MongoDB.Driver.Builders<User>.Filter.Eq(user => user.Age, 22);
92+
_ = Driver.Builders<User>.Filter.Eq(user => user.Age, 22);
93+
_ = Builders<User>.Filter.Eq(user => user.Age, 22);
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)