Skip to content

Commit ee96e0b

Browse files
committed
Support for code inspection, code completion, and reference resolution when there is a field access in the middle of a for directive
1 parent 558c2b4 commit ee96e0b

File tree

6 files changed

+82
-93
lines changed

6 files changed

+82
-93
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ class ForDeclarationDaoBaseItem(
3131
val daoParameter: PsiParameter? = null,
3232
val index: Int = 0,
3333
) : ForDeclarationItem(blocks.first()) {
34+
/***
35+
* Obtain the type information of the For item defined from the Dao parameters.
36+
*/
3437
fun getPsiParentClass(): PsiParentClass? {
3538
val daoParamFieldValidator =
3639
SqlElFieldAccessorChildElementValidator(

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,18 @@ class SqlElStaticFieldAccessorChildElementValidator(
3838
findFieldMethod: (PsiType) -> PsiParentClass,
3939
complete: (PsiParentClass) -> Unit,
4040
): ValidationResult? {
41-
val errorElement = getFieldTopParent()
42-
when (errorElement) {
41+
val getParentResult = getFieldTopParent()
42+
when (getParentResult) {
4343
is ValidationCompleteResult -> {
44-
val parent = errorElement.parentClass
44+
val parent = getParentResult.parentClass
4545
return validateFieldAccess(
4646
parent,
4747
dropLastIndex = dropIndex,
4848
complete = complete,
4949
)
5050
}
5151
is ValidationIgnoreResult -> return null
52-
else -> return errorElement
52+
else -> return getParentResult
5353
}
5454
}
5555

src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ class SqlParameterCompletionProvider : CompletionProvider<CompletionParameters>(
266266
}
267267

268268
if (topElementType == null) {
269-
if (getForItemTopElementType(top, elements, searchText, result)) return
269+
if (isFieldAccessByForItem(top, elements, searchText, result)) return
270270
topElementType =
271271
getElementTypeByFieldAccess(originalFile, topText, elements, result) ?: return
272272
}
@@ -396,17 +396,18 @@ class SqlParameterCompletionProvider : CompletionProvider<CompletionParameters>(
396396
}
397397
}
398398

399-
private fun getForItemTopElementType(
399+
private fun isFieldAccessByForItem(
400400
top: PsiElement,
401401
elements: List<PsiElement>,
402402
positionText: String,
403403
result: CompletionResultSet,
404404
): Boolean {
405405
val file = top.containingFile ?: return false
406-
val forDeclaration = ForDirectiveInspection(file = file)
406+
val daoMethod = findDaoMethod(file) ?: return false
407+
val forDeclaration = ForDirectiveInspection(daoMethod)
407408
val forItem = forDeclaration.getForItem(top)
408409
if (forItem != null) {
409-
val errorElement = forDeclaration.validateFieldAccessByForItem(elements.dropLast(1))
410+
val errorElement = forDeclaration.validateFieldAccessByForItem(elements)
410411
if (errorElement is ValidationCompleteResult) {
411412
val validator =
412413
SqlElForItemFieldAccessorChildElementValidator(

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

Lines changed: 24 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,12 @@ package org.domaframework.doma.intellij.inspection
1717

1818
import com.intellij.psi.PsiClassType
1919
import com.intellij.psi.PsiElement
20-
import com.intellij.psi.PsiFile
20+
import com.intellij.psi.PsiMethod
2121
import com.intellij.psi.util.CachedValue
2222
import com.intellij.psi.util.CachedValueProvider
2323
import com.intellij.psi.util.CachedValuesManager
2424
import com.intellij.psi.util.PsiTreeUtil
2525
import com.intellij.psi.util.elementType
26-
import org.domaframework.doma.intellij.common.dao.findDaoMethod
2726
import org.domaframework.doma.intellij.common.psi.PsiParentClass
2827
import org.domaframework.doma.intellij.common.sql.PsiClassTypeUtil
2928
import org.domaframework.doma.intellij.common.sql.foritem.ForDeclarationDaoBaseItem
@@ -32,6 +31,7 @@ import org.domaframework.doma.intellij.common.sql.foritem.ForItem
3231
import org.domaframework.doma.intellij.common.sql.validator.SqlElForItemFieldAccessorChildElementValidator
3332
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationCompleteResult
3433
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationDaoParamResult
34+
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationPropertyResult
3535
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationResult
3636
import org.domaframework.doma.intellij.extension.psi.findParameter
3737
import org.domaframework.doma.intellij.extension.psi.getDomaAnnotationType
@@ -42,8 +42,8 @@ import org.domaframework.doma.intellij.psi.SqlElIdExpr
4242
import org.domaframework.doma.intellij.psi.SqlTypes
4343

4444
class ForDirectiveInspection(
45+
private val daoMethod: PsiMethod,
4546
private val shortName: String = "",
46-
private val file: PsiFile,
4747
) {
4848
data class BlockToken(
4949
val type: BlockType,
@@ -64,12 +64,7 @@ class ForDirectiveInspection(
6464

6565
fun getForItem(targetElement: PsiElement): ForItem? {
6666
val forBlocks = getForDirectiveBlock(targetElement)
67-
val targetName =
68-
targetElement.text
69-
.replace("_has_next", "")
70-
.replace("_index", "")
71-
val forItem = forBlocks.lastOrNull { it.item.text == targetName }
72-
forItem?.let { return ForItem(it.item) } ?: return null
67+
return getForItem(targetElement, forBlocks)
7368
}
7469

7570
fun getForItem(
@@ -95,12 +90,11 @@ class ForDirectiveInspection(
9590
val firDirectives = getForDirectiveBlock(targetElement)
9691
val forItem = getForItem(targetElement, firDirectives)
9792
var errorElement: ValidationResult? = ValidationDaoParamResult(targetElement, "", shortName)
98-
val daoMethod = findDaoMethod(file) ?: return null
9993
val domaAnnotationType = daoMethod.getDomaAnnotationType()
10094

10195
if (forItem != null) {
10296
val declarationItem =
103-
getDeclarationItem(forItem, file)
97+
getDeclarationTopItem(forItem, 0)
10498

10599
if (declarationItem != null && declarationItem is ForDeclarationDaoBaseItem) {
106100
val daoParamDeclarativeType =
@@ -123,12 +117,7 @@ class ForDirectiveInspection(
123117

124118
// Each time searching for the next for directive item, recursively analyze the type if the field is Access.
125119
while (nestClassType != null &&
126-
i <= nestIndex &&
127-
128-
PsiClassTypeUtil.isIterableType(
129-
nestClassType,
130-
topElm.project,
131-
)
120+
i <= nestIndex
132121
) {
133122
// Get the definition type for each for directive passed through
134123
val targetForDirective = firDirectives[i]
@@ -147,6 +136,18 @@ class ForDirectiveInspection(
147136
)
148137
val currentLastType = validator.validateChildren()
149138
nestClassType = currentLastType?.parentClass?.type as? PsiClassType?
139+
if (nestClassType != null &&
140+
!PsiClassTypeUtil.isIterableType(
141+
nestClassType,
142+
topElm.project,
143+
)
144+
) {
145+
return ValidationPropertyResult(
146+
currentForItem.element,
147+
currentLastType?.parentClass,
148+
"",
149+
)
150+
}
150151
listIndex = 1
151152
} else {
152153
// Get the nesting count from the List type definition obtained along the way
@@ -175,32 +176,6 @@ class ForDirectiveInspection(
175176
return errorElement
176177
}
177178

178-
fun getFieldAccessParentClass(blockElements: List<PsiElement>): ValidationResult? {
179-
val targetElement: PsiElement = blockElements.firstOrNull() ?: return null
180-
val file = targetElement.containingFile ?: return null
181-
182-
val forItem = getForItem(targetElement)
183-
var errorElement: ValidationResult? = ValidationDaoParamResult(targetElement, "", shortName)
184-
if (forItem != null) {
185-
val declarationItem =
186-
getDeclarationItem(forItem, file)
187-
188-
if (declarationItem != null && declarationItem is ForDeclarationDaoBaseItem) {
189-
val forItemElementsParentClass = declarationItem.getPsiParentClass()
190-
if (forItemElementsParentClass != null) {
191-
val validator =
192-
SqlElForItemFieldAccessorChildElementValidator(
193-
blockElements,
194-
forItemElementsParentClass,
195-
shortName,
196-
)
197-
errorElement = validator.validateChildren(dropIndex = 1)
198-
}
199-
}
200-
}
201-
return errorElement
202-
}
203-
204179
fun getForDirectiveBlockSize(target: PsiElement): Int = getForDirectiveBlock(target).size
205180

206181
/**
@@ -212,6 +187,7 @@ class ForDirectiveInspection(
212187
val cachedValue =
213188
cachedForDirectiveBlocks.getOrPut(targetElement) {
214189
CachedValuesManager.getManager(targetElement.project).createCachedValue {
190+
val file = targetElement.containingFile ?: return@createCachedValue null
215191
val directiveBlocks =
216192
PsiTreeUtil
217193
.findChildrenOfType(file, PsiElement::class.java)
@@ -255,9 +231,11 @@ class ForDirectiveInspection(
255231
return cachedValue.value
256232
}
257233

258-
private fun getDeclarationItem(
234+
/***
235+
* Get the top element to define the item.
236+
*/
237+
private fun getDeclarationTopItem(
259238
forItem: ForItem,
260-
file: PsiFile,
261239
searchIndex: Int = 0,
262240
): ForDirectiveItemBase? {
263241
val forDirectiveParent = forItem.getParentForDirectiveExpr() ?: return null
@@ -269,19 +247,10 @@ class ForDirectiveInspection(
269247
val parentForItem = getForItem(topElm)
270248
val index = searchIndex + 1
271249
if (parentForItem != null) {
272-
val parentDeclaration = getDeclarationItem(parentForItem, file, index)
250+
val parentDeclaration = getDeclarationTopItem(parentForItem, index)
273251
if (parentDeclaration is ForDeclarationDaoBaseItem) return parentDeclaration
274252
}
275253

276-
return getForDeclarationDaoParamBase(topElm, searchIndex, file)
277-
}
278-
279-
private fun getForDeclarationDaoParamBase(
280-
topElm: PsiElement,
281-
searchIndex: Int,
282-
file: PsiFile,
283-
): ForDeclarationDaoBaseItem? {
284-
val daoMethod = findDaoMethod(file) ?: return null
285254
val validDaoParam = daoMethod.findParameter(topElm.text)
286255
if (validDaoParam == null) return null
287256

src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/SqlInspectionVisitor.kt

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import org.domaframework.doma.intellij.common.dao.findDaoMethod
2727
import org.domaframework.doma.intellij.common.isInjectionSqlFile
2828
import org.domaframework.doma.intellij.common.isJavaOrKotlinFileType
2929
import org.domaframework.doma.intellij.common.sql.validator.SqlElFieldAccessorChildElementValidator
30+
import org.domaframework.doma.intellij.common.sql.validator.SqlElForItemFieldAccessorChildElementValidator
3031
import org.domaframework.doma.intellij.common.sql.validator.SqlElStaticFieldAccessorChildElementValidator
3132
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationCompleteResult
3233
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationDaoParamResult
@@ -99,8 +100,10 @@ class SqlInspectionVisitor(
99100
if (isLiteralOrStatic(element)) return
100101
PsiTreeUtil.getParentOfType(element, SqlElStaticFieldAccessExpr::class.java)?.let { return }
101102

103+
val daoMethod = findDaoMethod(visitFile) ?: return
104+
102105
val forDirectiveInspection =
103-
ForDirectiveInspection(this.shortName, visitFile)
106+
ForDirectiveInspection(daoMethod, this.shortName)
104107

105108
val forDirectivesSize = forDirectiveInspection.getForDirectiveBlockSize(element)
106109
if (forDirectivesSize == 0) return
@@ -111,9 +114,7 @@ class SqlInspectionVisitor(
111114
return
112115
}
113116

114-
val daoMethod = findDaoMethod(visitFile) ?: return
115117
val validDaoParam = daoMethod.findParameter(element.text)
116-
117118
if (validDaoParam == null) {
118119
val errorElement =
119120
ValidationDaoParamResult(
@@ -171,10 +172,20 @@ class SqlInspectionVisitor(
171172
blockElement: List<SqlElIdExpr>,
172173
file: PsiFile,
173174
) {
174-
val forDirectiveInspection = ForDirectiveInspection(this.shortName, file)
175+
val daoMethod = findDaoMethod(file) ?: return
176+
val forDirectiveInspection = ForDirectiveInspection(daoMethod, this.shortName)
175177
var errorElement: ValidationResult? =
176178
forDirectiveInspection.validateFieldAccessByForItem(blockElement.toList())
177-
if (errorElement is ValidationCompleteResult) return
179+
if (errorElement is ValidationCompleteResult) {
180+
val currentFieldAccessValidator =
181+
SqlElForItemFieldAccessorChildElementValidator(
182+
blockElement,
183+
errorElement.parentClass,
184+
this.shortName,
185+
)
186+
errorElement = currentFieldAccessValidator.validateChildren()
187+
if (errorElement is ValidationCompleteResult) return
188+
}
178189
if (errorElement is ValidationPropertyResult) {
179190
errorElement.highlightElement(holder)
180191
return

src/main/kotlin/org/domaframework/doma/intellij/reference/SqlElIdExprReference.kt

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ class SqlElIdExprReference(
4646
val topElm = targetElements.firstOrNull() as? PsiElement ?: return null
4747

4848
if (topElm.prevSibling.elementType == SqlTypes.AT_SIGN) return null
49-
50-
val forDirectiveInspection = ForDirectiveInspection(file = file)
49+
val daoMethod = findDaoMethod(file) ?: return null
50+
val forDirectiveInspection = ForDirectiveInspection(daoMethod)
5151
val forItem = forDirectiveInspection.getForItem(topElm)
5252
if (forItem != null && element.textOffset == topElm.textOffset) {
5353
PluginLoggerUtil.countLogging(
@@ -59,38 +59,43 @@ class SqlElIdExprReference(
5959
return forItem.element
6060
}
6161

62-
val errorElement = forDirectiveInspection.getFieldAccessParentClass(targetElements)
62+
val errorElement = forDirectiveInspection.validateFieldAccessByForItem(targetElements)
6363
var parentClass = (errorElement as? ValidationCompleteResult)?.parentClass
6464
if (errorElement is ValidationCompleteResult && parentClass != null) {
65-
val searchText = targetElements.lastOrNull()?.let { cleanString(it.text) } ?: ""
66-
val reference = parentClass.findField(searchText) ?: parentClass.findMethod(searchText)
67-
PluginLoggerUtil.countLogging(
68-
this::class.java.simpleName,
69-
"ReferenceEntityProperty",
70-
"Reference",
71-
startTime,
72-
)
73-
return reference
65+
val validator =
66+
SqlElForItemFieldAccessorChildElementValidator(
67+
targetElements,
68+
parentClass,
69+
)
70+
val targetReferenceClass = validator.validateChildren(1)
71+
if (targetReferenceClass is ValidationCompleteResult) {
72+
val searchText = targetElements.lastOrNull()?.let { cleanString(it.text) } ?: ""
73+
val targetParent = targetReferenceClass.parentClass
74+
val reference =
75+
targetParent.findField(searchText) ?: targetParent.findMethod(searchText)
76+
PluginLoggerUtil.countLogging(
77+
this::class.java.simpleName,
78+
"ReferenceEntityProperty",
79+
"Reference",
80+
startTime,
81+
)
82+
return reference
83+
}
7484
}
7585

76-
val daoMethod = findDaoMethod(file)
77-
if (daoMethod != null) {
78-
val topParam = daoMethod.findParameter(topElm.text) ?: return null
79-
parentClass = topParam.getIterableClazz(daoMethod.getDomaAnnotationType())
80-
}
86+
val topParam = daoMethod.findParameter(topElm.text) ?: return null
87+
parentClass = topParam.getIterableClazz(daoMethod.getDomaAnnotationType())
8188

8289
val symbolElement =
8390
when (element.textOffset) {
8491
targetElements.first().textOffset ->
85-
daoMethod?.let {
86-
getReferenceDaoMethodParameter(
87-
it,
88-
element,
89-
startTime,
90-
)
91-
}
92+
getReferenceDaoMethodParameter(
93+
daoMethod,
94+
element,
95+
startTime,
96+
)
9297

93-
else -> parentClass?.let { getReferenceEntity(it, targetElements, startTime) }
98+
else -> getReferenceEntity(parentClass, targetElements, startTime)
9499
}
95100

96101
return symbolElement

0 commit comments

Comments
 (0)