Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: basic parser for queries with the java driver INTELLIJ-19 #15

Merged
merged 10 commits into from
Jul 15, 2024
Original file line number Diff line number Diff line change
@@ -1,33 +1,179 @@
package com.mongodb.jbplugin.dialects.javadriver.glossary

import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
import com.mongodb.jbplugin.dialects.DialectParser
import com.mongodb.jbplugin.mql.Namespace
import com.mongodb.jbplugin.mql.Node
import com.mongodb.jbplugin.mql.components.HasCollectionReference
import com.mongodb.jbplugin.mql.components.*
import com.mongodb.jbplugin.mql.toBsonType

object JavaDriverDialectParser : DialectParser<PsiElement> {
override fun isCandidateForQuery(source: PsiElement): Boolean =
(source as? PsiMethodCallExpression)?.findMongoDbCollectionReference(source.project) != null
override fun isCandidateForQuery(source: PsiElement): Boolean {
if (source !is PsiMethodCallExpression) {
// if it's not a method call, like .find(), it's not a query
return false
}
val sourceMethod = source.resolveMethod() ?: return false

override fun attachment(source: PsiElement): PsiElement =
(source as PsiMethodCallExpression).findMongoDbCollectionReference(source.project)!!
if ( // if the method is of MongoCollection, then we are in a query
sourceMethod.containingClass?.isMongoDbCollectionClass(source.project) == true
) {
return true
}

if ( // if it's any driver class, check inner calls
sourceMethod.containingClass?.isMongoDbClass(source.project) == true ||
sourceMethod.containingClass?.isMongoDbCursorClass(source.project) == true
) {
val allChildrenCandidates = PsiTreeUtil.findChildrenOfType(source, PsiMethodCallExpression::class.java)
return allChildrenCandidates.any { isCandidateForQuery(it) }
}

return false
}

override fun attachment(source: PsiElement): PsiElement = source.findMongoDbCollectionReference()!!
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it common to do !! towards the end of a method call. I am assuming this is a non-null assertion. If yes, does the TS recommendation hold true here as well - to make it verbose, add some conditional check instead of doing this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Usually you don't need to do it, but in this case the check that the node exists is happening in another method (the isCandidate) so it's fine and if someone is not using the API correctly they will see a NPE really fast.


override fun parse(source: PsiElement): Node<PsiElement> {
val owningMethod =
PsiTreeUtil.getParentOfType(source, PsiMethod::class.java)
?: return Node(source, emptyList())
val namespace = NamespaceExtractor.extractNamespace(owningMethod)

return Node(
source,
listOf(
namespace?.let {
HasCollectionReference(HasCollectionReference.Known(namespace))
} ?: HasCollectionReference(HasCollectionReference.Unknown),
),
)
val namespace = NamespaceExtractor.extractNamespace(source)
val collectionReference = namespaceComponent(namespace)

val currentCall = source as PsiMethodCallExpression? ?: return Node(source, listOf(collectionReference))
Copy link
Contributor

Choose a reason for hiding this comment

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

Another question similar to above - how safe is it to do this - source as PsiMethodCallExpression. I am assuming that this is type casting. Would it be better if we do some instanceOf check or there's no such thing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This specific cast is like an instance of and cast at the same time. Being a nullable type, if the casting is not successful it will return null, going through the return expression on the right side.

Copy link
Contributor

Choose a reason for hiding this comment

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

ohh that's interesting - thanks for the context


val calledMethod = currentCall.resolveMethod()
if (calledMethod?.containingClass?.isMongoDbCollectionClass(source.project) == true) {
val hasChildren =
if (currentCall.argumentList.expressionCount > 0) {
// we have at least 1 argument in the current method call
val argumentAsFilters = resolveToFiltersCall(currentCall.argumentList.expressions[0])
argumentAsFilters?.let {
val parsedQuery = parseFilterExpression(argumentAsFilters) // assume it's a Filters call
parsedQuery?.let {
HasChildren(
listOf(
parseFilterExpression(
argumentAsFilters,
)!!,
),
)
} ?: HasChildren(emptyList())
} ?: HasChildren(emptyList())
} else {
HasChildren(emptyList())
}

return Node(
source,
listOf(
collectionReference,
hasChildren,
),
)
} else {
calledMethod?.let {
// if it's another class, try to resolve the query from the method body
val allReturns = PsiTreeUtil.findChildrenOfType(calledMethod.body, PsiReturnStatement::class.java)
return allReturns
.mapNotNull { it.returnValue }
.flatMap {
it.collectTypeUntil(PsiMethodCallExpression::class.java, PsiReturnStatement::class.java)
}.firstNotNullOfOrNull {
val innerQuery = parse(it)
if (!innerQuery.hasComponent<HasChildren<PsiElement>>()) {
null
} else {
innerQuery
}
} ?: Node(source, listOf(collectionReference))
} ?: return Node(source, listOf(collectionReference))
}
}

private fun parseFilterExpression(filter: PsiMethodCallExpression): Node<PsiElement>? {
val method = filter.resolveMethod() ?: return null
if (method.isVarArgs) {
// Filters.and, Filters.or... are varargs
return Node(
filter,
listOf(
Named(method.name),
HasChildren(
filter.argumentList.expressions
.mapNotNull { resolveToFiltersCall(it) }
.mapNotNull { parseFilterExpression(it) },
),
),
)
} else if (method.parameters.size == 2) {
// If it has two parameters, it's field/value.
val fieldNameAsString = filter.argumentList.expressions[0].tryToResolveAsConstantString()
val fieldReference =
fieldNameAsString?.let {
HasFieldReference.Known(filter.argumentList.expressions[0], fieldNameAsString)
} ?: HasFieldReference.Unknown

val constantValue = filter.argumentList.expressions[1].tryToResolveAsConstant()
val typeOfConstantValue = constantValue?.javaClass?.toBsonType()

val valueReference =
if (constantValue != null && typeOfConstantValue != null) {
HasValueReference.Constant(constantValue, typeOfConstantValue)
} else {
val psiTypeOfValue =
filter.argumentList.expressions[1]
.type
?.toBsonType()
psiTypeOfValue?.let {
HasValueReference.Runtime(psiTypeOfValue)
} ?: HasValueReference.Unknown
}

return Node(
filter,
listOf(
Named(method.name),
HasFieldReference(
fieldReference,
),
HasValueReference(
valueReference,
),
),
)
}
// here we really don't know much, so just don't attempt to parse the query
return null
}

private fun resolveToFiltersCall(element: PsiElement): PsiMethodCallExpression? {
when (element) {
is PsiMethodCallExpression -> {
val method = element.resolveMethod() ?: return null
if (method.containingClass?.qualifiedName == "com.mongodb.client.model.Filters") {
return element
}
val allReturns = PsiTreeUtil.findChildrenOfType(method.body, PsiReturnStatement::class.java)
return allReturns.mapNotNull { it.returnValue }.firstNotNullOfOrNull {
resolveToFiltersCall(it)
}
}
is PsiVariable -> {
element.initializer ?: return null
return resolveToFiltersCall(element.initializer!!)
}

is PsiReferenceExpression -> {
val referredValue = element.resolve() ?: return null
return resolveToFiltersCall(referredValue)
}

else -> return null
}
}

private fun namespaceComponent(namespace: Namespace?): HasCollectionReference =
namespace?.let {
HasCollectionReference(HasCollectionReference.Known(namespace))
} ?: HasCollectionReference(HasCollectionReference.Unknown)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@ private typealias FoundAssignedPsiFields = List<Pair<AssignmentConcept, PsiField
object NamespaceExtractor {
fun extractNamespace(query: PsiElement): Namespace? {
val currentClass = query.findContainingClass()
val queryCollectionRef = query.findMongoDbCollectionReference() ?: query

val allMethodCallsInMethod = query.collectTypeUntil(
val allMethodCallsInMethod = queryCollectionRef.collectTypeUntil(
PsiMethodCallExpression::class.java,
PsiMethod::class.java
) + findChildrenOfType(query, PsiMethodCallExpression::class.java)

val referencesToMongoDbClasses =
allMethodCallsInMethod.mapNotNull {
it.findCurrentReferenceToMongoDbObject()
}
}.distinct() + listOfNotNull(queryCollectionRef.reference)

val constructorAssignmentFromConstructorRefs: List<FieldAndConstructorAssignment> =
referencesToMongoDbClasses.flatMap { ref ->
Expand Down
Loading
Loading