Skip to content

Commit cb2755d

Browse files
committed
Refactor: Use validator classes in code completion
1 parent 62ebb0d commit cb2755d

18 files changed

+482
-166
lines changed

src/main/kotlin/org/domaframework/doma/intellij/common/sql/PsiClassTypeUtil.kt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
*/
1616
package org.domaframework.doma.intellij.common.sql
1717

18+
import com.intellij.openapi.project.Project
1819
import com.intellij.psi.PsiClassType
1920
import com.intellij.psi.PsiType
21+
import com.intellij.psi.search.GlobalSearchScope
2022

2123
class PsiClassTypeUtil {
2224
companion object {
@@ -29,5 +31,45 @@ class PsiClassTypeUtil {
2931
}
3032
return classType
3133
}
34+
35+
fun isIterableType(
36+
type: PsiClassType,
37+
project: Project,
38+
): Boolean {
39+
val iterableType =
40+
PsiType.getTypeByName(
41+
"java.lang.Iterable",
42+
project,
43+
GlobalSearchScope.allScope(project),
44+
)
45+
return iterableType.isAssignableFrom(type)
46+
}
47+
48+
fun getParameterType(
49+
project: Project,
50+
baseType: PsiType?,
51+
preReturnListType: PsiType,
52+
index: Int,
53+
): PsiClassType? {
54+
val returnType = baseType as? PsiClassType ?: return null
55+
val preReturnType = preReturnListType as? PsiClassType ?: return null
56+
57+
if (returnType.name == "E" && isIterableType(preReturnListType, project)) {
58+
var count = 1
59+
var type: PsiType? = preReturnType.parameters.firstOrNull()
60+
while (index > count && type != null && type is PsiClassType) {
61+
type = type.parameters.firstOrNull()
62+
count++
63+
}
64+
return type as? PsiClassType
65+
}
66+
return null
67+
}
68+
69+
fun isCollect(type: PsiType): Boolean =
70+
type.superTypes
71+
.firstOrNull()
72+
?.presentableText
73+
?.startsWith("Collection") == true
3274
}
3375
}

src/main/kotlin/org/domaframework/doma/intellij/common/sql/foritem/ForDeclarationDaoBaseItem.kt

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@
1616
package org.domaframework.doma.intellij.common.sql.foritem
1717

1818
import com.intellij.psi.PsiClassType
19-
import com.intellij.psi.PsiElement
2019
import com.intellij.psi.PsiParameter
2120
import org.domaframework.doma.intellij.common.psi.PsiParentClass
21+
import org.domaframework.doma.intellij.common.sql.PsiClassTypeUtil
22+
import org.domaframework.doma.intellij.common.sql.validator.SqlElFieldAccessorChildElementValidator
2223
import org.domaframework.doma.intellij.extension.psi.DomaAnnotationType
23-
import org.domaframework.doma.intellij.extension.psi.getIterableClazz
24+
import org.domaframework.doma.intellij.psi.SqlElIdExpr
2425

2526
/**
2627
* In the For directive, define the element based on the Dao parameter.
@@ -29,21 +30,39 @@ import org.domaframework.doma.intellij.extension.psi.getIterableClazz
2930
* ...
3031
*/
3132
class ForDeclarationDaoBaseItem(
32-
override val element: PsiElement,
33-
val daoParameter: PsiParameter,
33+
private val blocks: List<SqlElIdExpr>,
3434
val nestIndex: Int,
3535
val domaAnnotationType: DomaAnnotationType,
36-
) : ForDeclarationItem(element) {
36+
val daoParameter: PsiParameter? = null,
37+
) : ForDeclarationItem(blocks.first()) {
3738
fun getPsiParentClass(): PsiParentClass? {
39+
val daoParamFieldValidator =
40+
SqlElFieldAccessorChildElementValidator(
41+
blocks,
42+
element.containingFile,
43+
"",
44+
daoParameter,
45+
)
46+
47+
var lastType: PsiParentClass? = null
48+
val errorElement =
49+
daoParamFieldValidator.validateChildren(
50+
complete = { parent ->
51+
lastType = parent
52+
},
53+
)
54+
if (errorElement != null || lastType == null) return null
55+
56+
val topElm = blocks.first()
57+
(lastType.type as? PsiClassType)?.let { if (!PsiClassTypeUtil.isIterableType(it, topElm.project)) return null }
58+
59+
var nestClassType: PsiClassType? = (lastType.type as? PsiClassType)
3860
var i = 0
39-
var nestClassType: PsiClassType? =
40-
(daoParameter.getIterableClazz(domaAnnotationType.isBatchAnnotation()).type as PsiClassType)
4161
if (domaAnnotationType.isBatchAnnotation()) {
42-
nestClassType?.parameters?.firstOrNull() as PsiClassType?
62+
nestClassType?.parameters?.firstOrNull() as? PsiClassType?
4363
}
44-
45-
while (i <= nestIndex) {
46-
nestClassType = nestClassType?.parameters?.firstOrNull() as PsiClassType?
64+
while (nestClassType != null && i <= nestIndex && PsiClassTypeUtil.isIterableType(nestClassType, topElm.project)) {
65+
nestClassType = nestClassType.parameters.firstOrNull() as? PsiClassType?
4766
i++
4867
}
4968
return nestClassType?.let { PsiParentClass(it) }

src/main/kotlin/org/domaframework/doma/intellij/common/sql/foritem/ForDeclarationItem.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package org.domaframework.doma.intellij.common.sql.foritem
1818
import com.intellij.psi.PsiElement
1919
import org.domaframework.doma.intellij.extension.expr.accessElements
2020
import org.domaframework.doma.intellij.psi.SqlElFieldAccessExpr
21+
import org.domaframework.doma.intellij.psi.SqlElIdExpr
2122

2223
/**
2324
* definition source in the For directive
@@ -26,7 +27,7 @@ import org.domaframework.doma.intellij.psi.SqlElFieldAccessExpr
2627
open class ForDeclarationItem(
2728
override val element: PsiElement,
2829
) : ForDirectiveItemBase(element) {
29-
fun getDeclarationChildren(): List<PsiElement> =
30-
(element as? SqlElFieldAccessExpr)?.accessElements?.mapNotNull { it as PsiElement }
31-
?: listOf(element)
30+
fun getDeclarationChildren(): List<SqlElIdExpr> =
31+
(element as? SqlElFieldAccessExpr)?.accessElements?.mapNotNull { it as SqlElIdExpr }
32+
?: listOf(element as SqlElIdExpr)
3233
}

src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/SqlElChildElementValidator.kt

Lines changed: 94 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,49 +16,117 @@
1616
package org.domaframework.doma.intellij.common.sql.validator
1717

1818
import com.intellij.psi.PsiElement
19+
import com.intellij.psi.PsiType
20+
import com.intellij.psi.util.elementType
1921
import org.domaframework.doma.intellij.common.psi.PsiParentClass
22+
import org.domaframework.doma.intellij.common.sql.PsiClassTypeUtil
23+
import org.domaframework.doma.intellij.common.sql.cleanString
24+
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationCompleteResult
2025
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationPropertyResult
2126
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationResult
2227
import org.domaframework.doma.intellij.extension.psi.getMethodReturnType
28+
import org.domaframework.doma.intellij.extension.psi.getParameterType
29+
import org.domaframework.doma.intellij.psi.SqlElIdExpr
30+
import org.domaframework.doma.intellij.psi.SqlElParameters
31+
import org.domaframework.doma.intellij.psi.SqlTypes
2332

2433
abstract class SqlElChildElementValidator(
2534
open val blocks: List<PsiElement>,
2635
open val shorName: String,
2736
) {
2837
abstract fun validateChildren(): ValidationResult?
2938

30-
protected fun validateFieldAccess(topParent: PsiParentClass): ValidationResult? {
39+
open fun validateChildren(
40+
findFieldMethod: (PsiType) -> PsiParentClass = { type: PsiType -> PsiParentClass(type) },
41+
complete: (PsiParentClass) -> Unit,
42+
): ValidationResult? = null
43+
44+
protected fun validateFieldAccess(
45+
topParent: PsiParentClass,
46+
findFieldMethod: ((PsiType) -> PsiParentClass)? = { type -> PsiParentClass(type) },
47+
complete: ((PsiParentClass) -> Unit) = { parent: PsiParentClass? -> },
48+
): ValidationResult? {
3149
var parent = topParent
32-
val topElementType = topParent.type
33-
var listTypeSearchIndex = 0
34-
for (eml in blocks.drop(1)) {
35-
var isExist = false
36-
parent
37-
.findField(eml.text)
38-
?.let { match ->
39-
isExist = true
40-
parent = PsiParentClass(match.type)
41-
}
42-
if (isExist) continue
43-
parent
44-
.findMethod(eml.text)
45-
?.let { match ->
46-
val returnType =
47-
match.getMethodReturnType(topElementType, listTypeSearchIndex)
48-
?: return null
49-
isExist = true
50-
parent = PsiParentClass(returnType)
51-
}
52-
listTypeSearchIndex++
53-
if (!isExist) {
50+
var competeResult: ValidationCompleteResult? = null
51+
52+
var getMethodReturnType: PsiType? =
53+
if (PsiClassTypeUtil.isCollect(topParent.type)) {
54+
topParent.type
55+
} else {
56+
null
57+
}
58+
var listParamIndex = 0
59+
for (element in blocks.drop(1)) {
60+
val searchElm = cleanString(getSearchElementText(element))
61+
if (searchElm.isEmpty()) {
62+
complete.invoke(parent)
63+
return ValidationCompleteResult(
64+
element,
65+
parent,
66+
)
67+
}
68+
69+
val field =
70+
parent
71+
.findField(element.text)
72+
?.let { match ->
73+
val type = match.type
74+
val methodReturnType =
75+
getMethodReturnType?.let { match.getParameterType(it, type, listParamIndex) }
76+
?: type
77+
if (PsiClassTypeUtil.isCollect(type)) {
78+
getMethodReturnType = type
79+
listParamIndex = 0
80+
}
81+
findFieldMethod?.invoke(methodReturnType)
82+
}
83+
val method =
84+
parent
85+
.findMethod(element.text)
86+
?.let { match ->
87+
val returnType = match.returnType ?: return null
88+
val methodReturnType =
89+
getMethodReturnType?.let { match.getMethodReturnType(it, listParamIndex) }
90+
?: returnType
91+
if (PsiClassTypeUtil.isCollect(returnType)) {
92+
getMethodReturnType = returnType
93+
listParamIndex = 0
94+
}
95+
findFieldMethod?.invoke(methodReturnType)
96+
}
97+
listParamIndex++
98+
if (field == null && method == null) {
5499
return ValidationPropertyResult(
55-
eml,
100+
element,
56101
parent,
57102
shorName,
58-
eml.textRange,
59103
)
104+
} else {
105+
if (field != null && element.nextSibling !is SqlElParameters) {
106+
parent = field
107+
competeResult =
108+
ValidationCompleteResult(
109+
element,
110+
parent,
111+
)
112+
} else if (method != null) {
113+
parent = method
114+
competeResult =
115+
ValidationCompleteResult(
116+
element,
117+
parent,
118+
)
119+
}
60120
}
61121
}
62-
return null
122+
complete.invoke(parent)
123+
return competeResult
63124
}
125+
126+
private fun getSearchElementText(elm: PsiElement): String =
127+
if (elm is SqlElIdExpr || elm.elementType == SqlTypes.EL_IDENTIFIER) {
128+
elm.text
129+
} else {
130+
""
131+
}
64132
}

src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/SqlElFieldAccessorChildElementValidator.kt

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ package org.domaframework.doma.intellij.common.sql.validator
1717

1818
import com.intellij.psi.PsiElement
1919
import com.intellij.psi.PsiFile
20+
import com.intellij.psi.PsiParameter
21+
import com.intellij.psi.PsiType
2022
import org.domaframework.doma.intellij.common.dao.findDaoMethod
23+
import org.domaframework.doma.intellij.common.psi.PsiParentClass
2124
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationDaoParamResult
2225
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationResult
2326
import org.domaframework.doma.intellij.extension.psi.findParameter
@@ -27,25 +30,54 @@ import org.domaframework.doma.intellij.extension.psi.getIterableClazz
2730
class SqlElFieldAccessorChildElementValidator(
2831
override val blocks: List<PsiElement>,
2932
private val file: PsiFile,
30-
override val shorName: String,
33+
override val shorName: String = "",
34+
private val topDaoParameter: PsiParameter? = null,
3135
) : SqlElChildElementValidator(blocks, shorName) {
32-
override fun validateChildren(): ValidationResult? {
36+
override fun validateChildren(
37+
findFieldMethod: (PsiType) -> PsiParentClass,
38+
complete: (PsiParentClass) -> Unit,
39+
): ValidationResult? {
3340
val daoMethod = findDaoMethod(file) ?: return null
3441
val topElement: PsiElement = blocks.firstOrNull() ?: return null
3542
val validDaoParam =
36-
daoMethod.findParameter(topElement.text)
37-
?: return ValidationDaoParamResult(
38-
topElement,
39-
daoMethod.name,
40-
shorName,
41-
topElement.textRange,
42-
)
43+
topDaoParameter
44+
?: daoMethod.findParameter(topElement.text)
45+
46+
if (validDaoParam == null) {
47+
return ValidationDaoParamResult(
48+
topElement,
49+
daoMethod.name,
50+
shorName,
51+
)
52+
}
4353

4454
val parentClass =
4555
validDaoParam.getIterableClazz(daoMethod.getDomaAnnotationType())
4656

4757
return validateFieldAccess(
4858
parentClass,
59+
complete = complete,
4960
)
5061
}
62+
63+
override fun validateChildren(): ValidationResult? {
64+
val daoMethod = findDaoMethod(file) ?: return null
65+
val topElement: PsiElement = blocks.firstOrNull() ?: return null
66+
val validDaoParam =
67+
topDaoParameter
68+
?: daoMethod.findParameter(topElement.text)
69+
70+
if (validDaoParam == null) {
71+
return ValidationDaoParamResult(
72+
topElement,
73+
daoMethod.name,
74+
shorName,
75+
)
76+
}
77+
78+
val parentClass =
79+
validDaoParam.getIterableClazz(daoMethod.getDomaAnnotationType())
80+
81+
return validateFieldAccess(parentClass)
82+
}
5183
}

src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/SqlElForItemFieldAccessorChildElementValidator.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,23 @@
1616
package org.domaframework.doma.intellij.common.sql.validator
1717

1818
import com.intellij.psi.PsiElement
19+
import com.intellij.psi.PsiType
1920
import org.domaframework.doma.intellij.common.psi.PsiParentClass
2021
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationResult
2122

2223
class SqlElForItemFieldAccessorChildElementValidator(
2324
override val blocks: List<PsiElement>,
2425
private val declarationType: PsiParentClass,
25-
override val shorName: String,
26+
override val shorName: String = "",
2627
) : SqlElChildElementValidator(blocks, shorName) {
28+
override fun validateChildren(
29+
findFieldMethod: (PsiType) -> PsiParentClass,
30+
complete: (PsiParentClass) -> Unit,
31+
): ValidationResult? =
32+
validateFieldAccess(
33+
declarationType,
34+
complete = complete,
35+
)
36+
2737
override fun validateChildren(): ValidationResult? = validateFieldAccess(declarationType)
2838
}

0 commit comments

Comments
 (0)