Skip to content

Commit d23d437

Browse files
committed
Type resolution of For items defined in SqlElStaticFieldAccessExpr
1 parent dac1126 commit d23d437

File tree

7 files changed

+166
-95
lines changed

7 files changed

+166
-95
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ import org.domaframework.doma.intellij.psi.SqlElIdExpr
2626
* %for [ForItem] : [ForDeclarationItem]
2727
* ...
2828
*/
29-
class ForDeclarationDaoBaseItem(
29+
open class ForDeclarationDaoBaseItem(
3030
private val blocks: List<SqlElIdExpr>,
3131
val daoParameter: PsiParameter? = null,
32-
val index: Int = 0,
32+
open val index: Int = 0,
3333
) : ForDeclarationItem(blocks.first()) {
3434
/***
3535
* Obtain the type information of the For item defined from the Dao parameters.
3636
*/
37-
fun getPsiParentClass(): PsiParentClass? {
37+
open fun getPsiParentClass(): PsiParentClass? {
3838
val daoParamFieldValidator =
3939
SqlElFieldAccessorChildElementValidator(
4040
blocks,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import org.domaframework.doma.intellij.psi.SqlElIdExpr
2727
open class ForDeclarationItem(
2828
override val element: PsiElement,
2929
) : ForDirectiveItemBase(element) {
30-
fun getDeclarationChildren(): List<SqlElIdExpr> =
30+
open fun getDeclarationChildren(): List<SqlElIdExpr> =
3131
(element as? SqlElFieldAccessExpr)?.accessElements?.mapNotNull { it as SqlElIdExpr }
3232
?: listOf(element as SqlElIdExpr)
3333
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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.sql.foritem
17+
18+
import org.domaframework.doma.intellij.common.psi.PsiParentClass
19+
import org.domaframework.doma.intellij.common.sql.cleanString
20+
import org.domaframework.doma.intellij.common.sql.validator.SqlElStaticFieldAccessorChildElementValidator
21+
import org.domaframework.doma.intellij.psi.SqlElIdExpr
22+
import org.domaframework.doma.intellij.psi.SqlElStaticFieldAccessExpr
23+
24+
/**
25+
* In the For directive, define the element based on the Dao parameter.
26+
* %for [ForItem] : [ForDeclarationStaticFieldAccessorItem]
27+
* %for [ForItem] : [ForDeclarationItem]
28+
* ...
29+
*/
30+
class ForDeclarationStaticFieldAccessorItem(
31+
private val blocks: List<SqlElIdExpr>,
32+
private val staticFieldAccessExpr: SqlElStaticFieldAccessExpr,
33+
override val index: Int = 0,
34+
private val shortName: String = "",
35+
) : ForDeclarationDaoBaseItem(blocks, null, index) {
36+
/***
37+
* Obtain the type information of the For item defined from the Dao parameters.
38+
*/
39+
override fun getPsiParentClass(): PsiParentClass? {
40+
val staticFieldValidator =
41+
SqlElStaticFieldAccessorChildElementValidator(
42+
blocks,
43+
staticFieldAccessExpr,
44+
shorName = shortName,
45+
)
46+
47+
var lastPsiParentClass: PsiParentClass? = null
48+
staticFieldValidator.validateChildren(
49+
complete = { parent ->
50+
lastPsiParentClass = parent
51+
},
52+
)
53+
if (lastPsiParentClass == null) return null
54+
55+
if (blocks.size == 1) {
56+
val searchElementText = cleanString(blocks.first().text)
57+
val field = lastPsiParentClass.findField(searchElementText)
58+
if (field != null) {
59+
val fieldType = field.type
60+
return PsiParentClass(fieldType)
61+
}
62+
63+
val method = lastPsiParentClass.findMethod(searchElementText)
64+
if (method != null) {
65+
val methodReturnType = method.returnType ?: return null
66+
return PsiParentClass(methodReturnType)
67+
}
68+
}
69+
return lastPsiParentClass
70+
}
71+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import org.domaframework.doma.intellij.common.psi.PsiParentClass
2525
/**
2626
* no Dao parameter matching the name of the field access top was found
2727
*/
28-
class ValidationDaoParamResult(
28+
open class ValidationDaoParamResult(
2929
override val identify: PsiElement,
3030
private val daoName: String,
3131
override val shortName: String,

src/main/kotlin/org/domaframework/doma/intellij/extension/expr/SqlElExtensions.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import org.domaframework.doma.intellij.psi.SqlElIfDirective
2828
import org.domaframework.doma.intellij.psi.SqlElStaticFieldAccessExpr
2929
import org.domaframework.doma.intellij.psi.SqlTypes
3030

31-
val SqlElStaticFieldAccessExpr.accessElements: List<PsiElement>
31+
val SqlElStaticFieldAccessExpr.accessElements: List<SqlElIdExpr>
3232
get() =
3333
this.elIdExprList
3434
.sortedBy { it.textOffset }

src/main/kotlin/org/domaframework/doma/intellij/extension/psi/SqlElForDirectiveExtension.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import org.domaframework.doma.intellij.psi.SqlBlockComment
2525
import org.domaframework.doma.intellij.psi.SqlElFieldAccessExpr
2626
import org.domaframework.doma.intellij.psi.SqlElForDirective
2727
import org.domaframework.doma.intellij.psi.SqlElIdExpr
28+
import org.domaframework.doma.intellij.psi.SqlElStaticFieldAccessExpr
2829
import org.domaframework.doma.intellij.psi.SqlTypes
2930

3031
fun SqlElForDirective.getForItem(): PsiElement? =
@@ -51,7 +52,8 @@ fun SqlElForDirective.getForItemDeclaration(): ForDeclarationItem? {
5152
return null
5253
}
5354
val declarationElm =
54-
PsiTreeUtil.getChildrenOfType(this, SqlElFieldAccessExpr::class.java)?.last()
55+
PsiTreeUtil.getChildrenOfType(this, SqlElStaticFieldAccessExpr::class.java)?.last()
56+
?: PsiTreeUtil.getChildrenOfType(this, SqlElFieldAccessExpr::class.java)?.last()
5557
?: PsiTreeUtil.getChildrenOfType(this, SqlElIdExpr::class.java)?.last()
5658
return declarationElm?.let { ForDeclarationItem(it) }
5759
}

src/main/kotlin/org/domaframework/doma/intellij/inspection/ForDirectiveInspection.kt

Lines changed: 86 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,21 @@ import com.intellij.psi.util.elementType
2626
import org.domaframework.doma.intellij.common.psi.PsiParentClass
2727
import org.domaframework.doma.intellij.common.sql.PsiClassTypeUtil
2828
import org.domaframework.doma.intellij.common.sql.foritem.ForDeclarationDaoBaseItem
29+
import org.domaframework.doma.intellij.common.sql.foritem.ForDeclarationItem
30+
import org.domaframework.doma.intellij.common.sql.foritem.ForDeclarationStaticFieldAccessorItem
2931
import org.domaframework.doma.intellij.common.sql.foritem.ForDirectiveItemBase
3032
import org.domaframework.doma.intellij.common.sql.foritem.ForItem
3133
import org.domaframework.doma.intellij.common.sql.validator.SqlElForItemFieldAccessorChildElementValidator
3234
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationCompleteResult
3335
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationDaoParamResult
34-
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationPropertyResult
3536
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationResult
37+
import org.domaframework.doma.intellij.extension.expr.accessElements
3638
import org.domaframework.doma.intellij.extension.psi.findParameter
3739
import org.domaframework.doma.intellij.extension.psi.getDomaAnnotationType
3840
import org.domaframework.doma.intellij.extension.psi.getForItem
3941
import org.domaframework.doma.intellij.extension.psi.getForItemDeclaration
4042
import org.domaframework.doma.intellij.psi.SqlElForDirective
41-
import org.domaframework.doma.intellij.psi.SqlElIdExpr
43+
import org.domaframework.doma.intellij.psi.SqlElStaticFieldAccessExpr
4244
import org.domaframework.doma.intellij.psi.SqlTypes
4345

4446
class ForDirectiveInspection(
@@ -57,8 +59,6 @@ class ForDirectiveInspection(
5759
END,
5860
}
5961

60-
var declarationFieldElements = mutableListOf<SqlElIdExpr>()
61-
6262
private val cachedForDirectiveBlocks: MutableMap<PsiElement, CachedValue<List<BlockToken>>> =
6363
mutableMapOf()
6464

@@ -87,97 +87,90 @@ class ForDirectiveInspection(
8787
val targetElement: PsiElement = blockElements.firstOrNull() ?: return null
8888
val topElm = blockElements.first()
8989

90-
val firDirectives = getForDirectiveBlock(targetElement)
91-
val forItem = getForItem(targetElement, firDirectives)
92-
var errorElement: ValidationResult? = ValidationDaoParamResult(targetElement, "", shortName)
90+
val forDirectives = getForDirectiveBlock(targetElement)
91+
val forItem = getForItem(targetElement, forDirectives) ?: return createErrorResult(targetElement)
9392
val domaAnnotationType = daoMethod.getDomaAnnotationType()
9493

95-
if (forItem != null) {
96-
val declarationItem =
97-
getDeclarationTopItem(forItem, 0)
94+
val declarationItem = getDeclarationTopItem(forItem, 0)
95+
if (declarationItem !is ForDeclarationDaoBaseItem) return createErrorResult(targetElement)
9896

99-
if (declarationItem != null && declarationItem is ForDeclarationDaoBaseItem) {
100-
val daoParamDeclarativeType =
101-
declarationItem.getPsiParentClass()
102-
?: return ValidationDaoParamResult(targetElement, daoMethod.name, shortName)
97+
val daoParamDeclarativeType =
98+
declarationItem.getPsiParentClass()
99+
?: return ValidationDaoParamResult(targetElement, daoMethod.name, shortName)
103100

104-
(daoParamDeclarativeType.type as? PsiClassType)
105-
?.let { if (!PsiClassTypeUtil.isIterableType(it, topElm.project)) return null }
101+
val initialType = daoParamDeclarativeType.type as? PsiClassType
102+
if (initialType == null || !PsiClassTypeUtil.isIterableType(initialType, topElm.project)) return null
106103

107-
val nestIndex = declarationItem.index
108-
var lastType = daoParamDeclarativeType.type
109-
var nestClassType: PsiClassType? = (lastType as? PsiClassType)
104+
val finalType = analyzeNestedForDirectives(forDirectives, initialType, domaAnnotationType.isBatchAnnotation(), topElm)
105+
return finalType?.let { ValidationCompleteResult(topElm, PsiParentClass(it)) } ?: createErrorResult(targetElement)
106+
}
110107

111-
var i = 0
112-
var listIndex = 1
108+
private fun createErrorResult(targetElement: PsiElement): ValidationResult = ValidationDaoParamResult(targetElement, "", shortName)
113109

114-
if (domaAnnotationType.isBatchAnnotation()) {
115-
nestClassType = nestClassType?.parameters?.firstOrNull() as? PsiClassType?
116-
}
110+
private fun analyzeNestedForDirectives(
111+
forDirectives: List<BlockToken>,
112+
initialType: PsiClassType,
113+
isBatchAnnotation: Boolean,
114+
topElm: PsiElement,
115+
): PsiClassType? {
116+
var nestClassType: PsiClassType? = if (isBatchAnnotation) initialType.parameters.firstOrNull() as? PsiClassType else initialType
117+
var listIndex = 1
117118

118-
// Each time searching for the next for directive item, recursively analyze the type if the field is Access.
119-
while (nestClassType != null &&
120-
i <= nestIndex
121-
) {
122-
// TODO: Refactoring
123-
// Get the definition type for each for directive passed through
124-
val targetForDirective = firDirectives[i]
125-
val currentForItem = ForItem(targetForDirective.item)
126-
val currentForDirectiveExpr = currentForItem.getParentForDirectiveExpr() ?: return null
127-
val currentDeclaration =
128-
currentForDirectiveExpr.getForItemDeclaration() ?: return null
129-
val declarationChildren = currentDeclaration.getDeclarationChildren()
130-
if (declarationChildren.size > 1) {
131-
// If the for item is defined by a field access, the final type is obtained.
132-
val validator =
133-
SqlElForItemFieldAccessorChildElementValidator(
134-
declarationChildren,
135-
PsiParentClass(nestClassType),
136-
shortName,
137-
)
138-
val currentLastType = validator.validateChildren()
139-
val currentLastTypeParentType = currentLastType?.parentClass?.type as? PsiClassType?
140-
if (currentLastTypeParentType != null) {
141-
if (!PsiClassTypeUtil.isIterableType(
142-
currentLastTypeParentType,
143-
topElm.project,
144-
)
145-
) {
146-
return ValidationPropertyResult(
147-
currentForItem.element,
148-
currentLastType.parentClass,
149-
"",
150-
)
151-
} else {
152-
nestClassType = currentLastTypeParentType.parameters.firstOrNull() as? PsiClassType?
153-
}
154-
}
155-
listIndex = 1
156-
} else {
157-
// Get the nesting count from the List type definition obtained along the way
158-
repeat(listIndex) {
159-
if (nestClassType != null &&
160-
PsiClassTypeUtil.isIterableType(nestClassType, topElm.project)
161-
) {
162-
nestClassType =
163-
nestClassType.parameters.firstOrNull() as? PsiClassType?
164-
}
165-
}
166-
listIndex++
167-
}
168-
i++
169-
}
170-
return nestClassType?.let {
171-
ValidationCompleteResult(
172-
topElm,
173-
PsiParentClass(nestClassType),
174-
)
119+
for ((i, targetForDirective) in forDirectives.withIndex()) {
120+
if (nestClassType == null) break
121+
122+
val currentForItem = ForItem(targetForDirective.item)
123+
val currentDeclaration = currentForItem.getParentForDirectiveExpr()?.getForItemDeclaration() ?: continue
124+
125+
val declarationType = processDeclarationElement(currentDeclaration, nestClassType, i)
126+
if (declarationType != null) {
127+
if (!PsiClassTypeUtil.isIterableType(declarationType, topElm.project)) {
128+
return null
175129
}
176-
?: errorElement
130+
nestClassType = declarationType.parameters.firstOrNull() as? PsiClassType
131+
listIndex = 1
132+
} else {
133+
nestClassType = processListType(nestClassType, listIndex, topElm)
134+
listIndex++
177135
}
178136
}
137+
return nestClassType
138+
}
139+
140+
private fun processDeclarationElement(
141+
currentDeclaration: ForDeclarationItem,
142+
nestClassType: PsiClassType,
143+
index: Int,
144+
): PsiClassType? {
145+
val declarationElement = currentDeclaration.element
146+
if (declarationElement is SqlElStaticFieldAccessExpr) {
147+
val forItem = ForDeclarationStaticFieldAccessorItem(declarationElement.accessElements, declarationElement, index)
148+
val declarationType = forItem.getPsiParentClass()?.type as? PsiClassType
149+
return declarationType
150+
}
151+
152+
val declarationChildren = currentDeclaration.getDeclarationChildren()
153+
if (declarationChildren.size > 1) {
154+
val validator = SqlElForItemFieldAccessorChildElementValidator(declarationChildren, PsiParentClass(nestClassType), shortName)
155+
return validator.validateChildren()?.parentClass?.type as? PsiClassType
156+
}
157+
return null
158+
}
179159

180-
return errorElement
160+
private fun processListType(
161+
nestClassType: PsiClassType,
162+
listIndex: Int,
163+
topElm: PsiElement,
164+
): PsiClassType? {
165+
var currentType: PsiClassType? = nestClassType
166+
repeat(listIndex) {
167+
currentType?.let { type ->
168+
if (PsiClassTypeUtil.isIterableType(type, topElm.project)) {
169+
currentType = type.parameters.firstOrNull() as? PsiClassType
170+
}
171+
}
172+
}
173+
return currentType
181174
}
182175

183176
fun getForDirectiveBlockSize(target: PsiElement): Int = getForDirectiveBlock(target).size
@@ -249,19 +242,24 @@ class ForDirectiveInspection(
249242
searchIndex: Int = 0,
250243
): ForDirectiveItemBase? {
251244
val forDirectiveParent = forItem.getParentForDirectiveExpr() ?: return null
252-
val declarationElement =
245+
val declarationSideElement =
253246
forDirectiveParent.getForItemDeclaration() ?: return null
254-
declarationFieldElements = declarationElement.getDeclarationChildren().toMutableList()
255-
val topElm = declarationFieldElements.firstOrNull() ?: return null
247+
val declarationElement = declarationSideElement.element
248+
if (declarationElement is SqlElStaticFieldAccessExpr) {
249+
return ForDeclarationStaticFieldAccessorItem(declarationElement.accessElements, declarationElement, searchIndex)
250+
}
251+
252+
val declarationFieldElements = declarationSideElement.getDeclarationChildren().toMutableList()
253+
val declarationSideElements = declarationFieldElements.firstOrNull() ?: return null
256254

257-
val parentForItem = getForItem(topElm)
255+
val parentForItem = getForItem(declarationSideElements)
258256
val index = searchIndex + 1
259257
if (parentForItem != null) {
260258
val parentDeclaration = getDeclarationTopItem(parentForItem, index)
261259
if (parentDeclaration is ForDeclarationDaoBaseItem) return parentDeclaration
262260
}
263261

264-
val validDaoParam = daoMethod.findParameter(topElm.text)
262+
val validDaoParam = daoMethod.findParameter(declarationSideElements.text)
265263
if (validDaoParam == null) return null
266264

267265
return ForDeclarationDaoBaseItem(

0 commit comments

Comments
 (0)