Skip to content

Commit b4937ca

Browse files
authored
Merge pull request #448 from domaframework/feature/inspection-function-call-param
Check Parameter Types for Method Calls in Bind Variables
2 parents c70f555 + ab6a352 commit b4937ca

File tree

75 files changed

+2465
-653
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+2465
-653
lines changed

src/main/kotlin/org/domaframework/doma/intellij/common/helper/ExpressionFunctionsHelper.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ class ExpressionFunctionsHelper {
4545
expressionClazz.superTypes.firstOrNull()?.canonicalText
4646
?: expressionClazz.psiClassType.canonicalText
4747
return project?.let {
48-
expressionClazz.psiClassType.canonicalText
4948
project
5049
.getJavaClazz(parentType)
5150
?.isInheritor(functionInterface, true) == true
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright Doma Tools Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.domaframework.doma.intellij.common.psi
17+
18+
import com.intellij.psi.PsiTypes
19+
20+
class DummyPsiParentClass : PsiParentClass(PsiTypes.voidType())
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright Doma Tools Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.domaframework.doma.intellij.common.psi
17+
18+
import com.intellij.psi.PsiElement
19+
import com.intellij.psi.util.PsiTreeUtil
20+
import org.domaframework.doma.intellij.psi.SqlElFunctionCallExpr
21+
import org.domaframework.doma.intellij.psi.SqlElParameters
22+
23+
class MethodParamContext(
24+
val methodIdExp: PsiElement,
25+
val methodParams: SqlElParameters?,
26+
) {
27+
companion object {
28+
fun of(method: PsiElement): MethodParamContext =
29+
MethodParamContext(
30+
setMethodIdExp(method),
31+
setParameter(method),
32+
)
33+
34+
private fun setParameter(method: PsiElement): SqlElParameters? =
35+
if (method is SqlElFunctionCallExpr) {
36+
method.elParameters
37+
} else {
38+
PsiTreeUtil.nextLeaf(method)?.parent as? SqlElParameters
39+
}
40+
41+
private fun setMethodIdExp(method: PsiElement): PsiElement =
42+
if (method is SqlElFunctionCallExpr) {
43+
method.elIdExpr
44+
} else {
45+
method
46+
}
47+
}
48+
}

src/main/kotlin/org/domaframework/doma/intellij/common/psi/PsiParentClass.kt

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,21 @@
1616
package org.domaframework.doma.intellij.common.psi
1717

1818
import com.intellij.psi.PsiClass
19+
import com.intellij.psi.PsiElement
1920
import com.intellij.psi.PsiField
21+
import com.intellij.psi.PsiManager
2022
import com.intellij.psi.PsiMethod
2123
import com.intellij.psi.PsiModifier
2224
import com.intellij.psi.PsiType
2325
import com.intellij.psi.util.PsiTypesUtil
26+
import org.domaframework.doma.intellij.common.util.MethodMatcher
27+
import org.domaframework.doma.intellij.extension.expr.extractParameterTypes
2428

2529
/**
2630
* When parsing a field access element with SQL,
2731
* manage the reference class information of the previous element
2832
*/
29-
class PsiParentClass(
33+
open class PsiParentClass(
3034
val type: PsiType,
3135
) {
3236
var clazz: PsiClass? = psiClass()
@@ -61,6 +65,34 @@ class PsiParentClass(
6165
m.name.substringBefore("(") == methodName.substringBefore("(")
6266
}
6367

68+
fun findMethod(
69+
methodExpr: PsiElement,
70+
shortName: String = "",
71+
): MethodMatcher.MatchResult {
72+
val context = MethodParamContext.of(methodExpr)
73+
val methods = findMethods(context.methodIdExp.text)
74+
if (context.methodParams == null) return MethodMatcher.MatchResult(validation = null)
75+
76+
val actualCount = context.methodParams.elExprList.size
77+
val paramTypes = context.methodParams.extractParameterTypes(PsiManager.getInstance(methodExpr.project))
78+
val matchResult =
79+
MethodMatcher.findMatchingMethod(
80+
context.methodIdExp,
81+
methods,
82+
paramTypes,
83+
actualCount,
84+
shortName,
85+
)
86+
87+
return matchResult
88+
}
89+
90+
fun findMethods(methodName: String): List<PsiMethod> =
91+
getMethods()
92+
?.filter { m ->
93+
m.hasModifierProperty(PsiModifier.PUBLIC) && m.name.substringBefore("(") == methodName.substringBefore("(")
94+
} ?: emptyList()
95+
6496
fun searchMethod(methodName: String): List<PsiMethod>? =
6597
getMethods()?.filter { m ->
6698
m.name.substringBefore("(").startsWith(methodName.substringBefore("(")) &&
@@ -81,4 +113,11 @@ class PsiParentClass(
81113
m.hasModifierProperty(PsiModifier.PUBLIC) &&
82114
m.name.substringBefore("(").startsWith(methodName.substringBefore("("))
83115
}
116+
117+
fun findStaticMethods(methodName: String): List<PsiMethod> =
118+
getMethods()
119+
?.filter { m ->
120+
m.hasModifierProperty(PsiModifier.STATIC) &&
121+
m.hasModifierProperty(PsiModifier.PUBLIC) && m.name.substringBefore("(") == methodName.substringBefore("(")
122+
} ?: emptyList()
84123
}

src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/collector/FunctionCallCollector.kt

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class FunctionCallCollector(
3434
private val bind: String,
3535
) : StaticDirectiveHandlerCollector() {
3636
public override fun collect(): List<LookupElement>? {
37-
var functions = mutableSetOf<PsiMethod>()
37+
val functions = mutableSetOf<PsiMethod>()
3838
val project = file?.project
3939
val module = file?.module ?: return null
4040
val isTest = CommonPathParameterUtil.isTest(module, file.virtualFile)
@@ -50,7 +50,10 @@ class FunctionCallCollector(
5050
project?.let { ExpressionFunctionsHelper.setExpressionFunctionsInterface(it) }
5151
?: return null
5252

53-
val expressionClazz = customFunctionClassName?.let { project.getJavaClazz(it) }
53+
val expressionClazz =
54+
customFunctionClassName?.let {
55+
if (it.isNotEmpty()) project.getJavaClazz(it) else null
56+
}
5457
if (expressionClazz != null &&
5558
ExpressionFunctionsHelper.isInheritor(expressionClazz)
5659
) {
@@ -71,17 +74,17 @@ class FunctionCallCollector(
7174
return functions
7275
.filter {
7376
it.name.startsWith(bind.substringAfter("@"))
74-
}.map {
75-
val parameters = it.parameterList.parameters.toList()
77+
}.map { m ->
78+
val parameters = m.parameterList.parameters.toList()
7679
LookupElementBuilder
77-
.create(createMethodLookupElement(caretNextText, it))
78-
.withPresentableText(it.name)
80+
.create(createMethodLookupElement(caretNextText, m))
81+
.withPresentableText(m.name)
7982
.withTailText(
8083
"(${
81-
parameters.joinToString(",") { "${it.type.presentableText} ${it.name}" }
84+
parameters.joinToString(",") { p -> "${p.type.presentableText} ${p.name}" }
8285
})",
8386
true,
84-
).withTypeText(it.returnType?.presentableText ?: "void")
87+
).withTypeText(m.returnType?.presentableText ?: "void")
8588
.withAutoCompletionPolicy(AutoCompletionPolicy.ALWAYS_AUTOCOMPLETE)
8689
}
8790
}
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/*
2+
* Copyright Doma Tools Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.domaframework.doma.intellij.common.util
17+
18+
import com.intellij.openapi.project.Project
19+
import com.intellij.psi.PsiClassType
20+
import com.intellij.psi.PsiElement
21+
import com.intellij.psi.PsiManager
22+
import com.intellij.psi.PsiMethod
23+
import com.intellij.psi.PsiType
24+
import org.domaframework.doma.intellij.common.psi.MethodParamContext
25+
import org.domaframework.doma.intellij.common.psi.PsiParentClass
26+
import org.domaframework.doma.intellij.common.sql.PsiClassTypeUtil
27+
import org.domaframework.doma.intellij.common.validation.result.ValidationCompleteResult
28+
import org.domaframework.doma.intellij.common.validation.result.ValidationNotFoundStaticPropertyResult
29+
import org.domaframework.doma.intellij.common.validation.result.ValidationPropertyResult
30+
import org.domaframework.doma.intellij.common.validation.result.ValidationResult
31+
import org.domaframework.doma.intellij.extension.expr.extractParameterTypes
32+
import org.domaframework.doma.intellij.psi.SqlElParameters
33+
34+
class FieldMethodResolver {
35+
data class ResolveContext(
36+
var parent: PsiParentClass,
37+
var parentListBaseType: PsiType?,
38+
var nestIndex: Int,
39+
var completeResult: ValidationCompleteResult? = null,
40+
var validationResult: ValidationResult? = null,
41+
)
42+
43+
data class ResolveResult(
44+
val type: PsiParentClass? = null,
45+
val validation: ValidationResult? = null,
46+
)
47+
48+
companion object {
49+
fun resolveField(
50+
context: ResolveContext,
51+
fieldName: String,
52+
project: Project,
53+
): PsiParentClass? {
54+
val field = context.parent.findField(fieldName) ?: return null
55+
val convertedType = PsiClassTypeUtil.convertOptionalType(field.type, project)
56+
57+
val resolvedType =
58+
context.parentListBaseType?.let {
59+
PsiClassTypeUtil.getParameterType(project, convertedType, it, context.nestIndex)
60+
} ?: convertedType
61+
62+
updateContextForIterableType(context, resolvedType, project)
63+
return PsiParentClass(resolvedType)
64+
}
65+
66+
/**
67+
* Retrieve, from the defined methods with that name, those whose parameter count and parameter types match.
68+
*/
69+
fun resolveMethod(
70+
context: ResolveContext,
71+
element: PsiElement,
72+
methodName: String,
73+
project: Project,
74+
shortName: String = "",
75+
): ResolveResult {
76+
val methodContext: MethodParamContext = MethodParamContext.of(element)
77+
val candidateMethods = context.parent.findMethods(methodName)
78+
if (candidateMethods.isEmpty()) {
79+
return ResolveResult(
80+
validation = ValidationPropertyResult(element, context.parent, shortName),
81+
)
82+
}
83+
return resolveMethodWithParameters(
84+
context,
85+
element,
86+
candidateMethods,
87+
methodContext.methodParams,
88+
project,
89+
shortName,
90+
)
91+
}
92+
93+
fun resolveStaticMethod(
94+
context: ResolveContext,
95+
element: PsiElement,
96+
parent: PsiParentClass,
97+
methodName: String,
98+
parametersExpr: SqlElParameters?,
99+
project: Project,
100+
shortName: String = "",
101+
): ResolveResult {
102+
val candidateMethods = parent.findStaticMethods(methodName)
103+
104+
if (parametersExpr == null) {
105+
return ResolveResult()
106+
}
107+
108+
if (candidateMethods.isEmpty()) {
109+
return ResolveResult(
110+
validation = ValidationNotFoundStaticPropertyResult(element, parent.type.canonicalText, shortName),
111+
)
112+
}
113+
114+
val paramTypes =
115+
parametersExpr.extractParameterTypes(
116+
PsiManager.getInstance(project),
117+
)
118+
119+
val matchResult =
120+
MethodMatcher.findMatchingMethod(
121+
element,
122+
candidateMethods,
123+
paramTypes,
124+
parametersExpr.elExprList.size,
125+
shortName,
126+
)
127+
128+
fun result(): ResolveResult {
129+
if (matchResult.validation != null) {
130+
context.validationResult = matchResult.validation
131+
return ResolveResult(validation = matchResult.validation)
132+
}
133+
134+
val returnType = matchResult.method?.returnType ?: return ResolveResult()
135+
136+
val convertedType = PsiClassTypeUtil.convertOptionalType(returnType, project)
137+
val resolvedType =
138+
context.parentListBaseType?.let {
139+
PsiClassTypeUtil.getParameterType(project, convertedType, it, context.nestIndex)
140+
} ?: convertedType
141+
142+
updateContextForIterableType(context, resolvedType, project)
143+
return ResolveResult(type = PsiParentClass(resolvedType))
144+
}
145+
146+
return result()
147+
}
148+
149+
private fun resolveMethodWithParameters(
150+
context: ResolveContext,
151+
element: PsiElement,
152+
candidateMethods: List<PsiMethod>,
153+
paramExpr: SqlElParameters?,
154+
project: Project,
155+
shortName: String,
156+
): ResolveResult {
157+
val paramTypes =
158+
paramExpr?.extractParameterTypes(
159+
PsiManager.getInstance(project),
160+
) ?: emptyList()
161+
162+
val matchResult =
163+
MethodMatcher.findMatchingMethod(
164+
element,
165+
candidateMethods,
166+
paramTypes,
167+
paramExpr?.elExprList?.size ?: 0,
168+
shortName,
169+
)
170+
171+
fun result(): ResolveResult {
172+
if (matchResult.validation != null) {
173+
context.validationResult = matchResult.validation
174+
return ResolveResult(validation = matchResult.validation)
175+
}
176+
177+
val returnType = matchResult.method?.returnType ?: return ResolveResult()
178+
179+
val convertedType = PsiClassTypeUtil.convertOptionalType(returnType, project)
180+
val resolvedType =
181+
context.parentListBaseType?.let {
182+
PsiClassTypeUtil.getParameterType(project, convertedType, it, context.nestIndex)
183+
} ?: convertedType
184+
185+
updateContextForIterableType(context, resolvedType, project)
186+
return ResolveResult(type = PsiParentClass(resolvedType))
187+
}
188+
189+
return result()
190+
}
191+
192+
private fun updateContextForIterableType(
193+
context: ResolveContext,
194+
type: PsiType,
195+
project: Project,
196+
) {
197+
val classType = type as? PsiClassType
198+
if (classType != null && PsiClassTypeUtil.isIterableType(classType, project)) {
199+
context.parentListBaseType = PsiClassTypeUtil.convertOptionalType(type, project)
200+
context.nestIndex = 0
201+
}
202+
}
203+
}
204+
}

0 commit comments

Comments
 (0)