Skip to content

Commit ea8884b

Browse files
committed
What to do when the for directive definition element is used as the top element of a field access
1 parent 895ebd1 commit ea8884b

File tree

11 files changed

+158
-63
lines changed

11 files changed

+158
-63
lines changed

src/main/kotlin/org/domaframework/doma/intellij/common/util/ForDirectiveUtil.kt

Lines changed: 62 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import com.intellij.openapi.project.Project
1919
import com.intellij.psi.PsiClass
2020
import com.intellij.psi.PsiClassType
2121
import com.intellij.psi.PsiElement
22+
import com.intellij.psi.PsiMethod
2223
import com.intellij.psi.PsiType
2324
import com.intellij.psi.PsiTypes
2425
import com.intellij.psi.util.CachedValue
@@ -34,6 +35,7 @@ import org.domaframework.doma.intellij.common.sql.PsiClassTypeUtil
3435
import org.domaframework.doma.intellij.common.sql.cleanString
3536
import org.domaframework.doma.intellij.common.sql.foritem.ForItem
3637
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationCompleteResult
38+
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationNotFoundTopTypeResult
3739
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationPropertyResult
3840
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationResult
3941
import org.domaframework.doma.intellij.extension.expr.accessElements
@@ -178,40 +180,72 @@ class ForDirectiveUtil {
178180
// Get the type of the top for directive definition element
179181
// Defined in Dao parameters or static property calls
180182
if (forDirectiveBlocks.isEmpty()) return null
183+
val topDirectiveItem = forDirectiveBlocks.first().item
184+
val file = topDirectiveItem.containingFile ?: return null
185+
val daoMethod = findDaoMethod(file)
186+
181187
var parentClassType =
182188
getTopForDirectiveDeclarationClassType(
183189
forDirectiveBlocks.first().item,
184190
project,
185-
) ?: return null
191+
daoMethod,
192+
)
186193
forDirectiveBlocks.drop(1).forEach { directive ->
187194
// Get the definition type of the target directive
188195
val formItem = ForItem(directive.item)
189-
if (targetForItem != null &&
190-
formItem.element.textOffset > targetForItem.textOffset
191-
) {
196+
if (targetForItem != null && formItem.element.textOffset > targetForItem.textOffset) {
192197
return parentClassType
193198
}
194199
val forDirectiveExpr = formItem.getParentForDirectiveExpr()
195200
val forDirectiveDeclaration = forDirectiveExpr?.getForItemDeclaration()
196201
if (forDirectiveDeclaration != null) {
197-
val forItemDeclarationBlocks = forDirectiveDeclaration.getDeclarationChildren()
198-
getFieldAccessLastPropertyClassType(
199-
forItemDeclarationBlocks,
200-
project,
201-
parentClassType,
202-
complete = { lastType ->
203-
val classType = lastType.type as? PsiClassType
204-
val nestClass =
205-
if (classType != null &&
206-
PsiClassTypeUtil.Companion.isIterableType(classType, project)
207-
) {
208-
classType.parameters.firstOrNull()
209-
} else {
210-
null
211-
}
212-
nestClass?.let { parentClassType = PsiParentClass(it) }
213-
},
214-
)
202+
val declarationTopElement =
203+
forDirectiveDeclaration.getDeclarationChildren().first()
204+
val findDeclarationForItem =
205+
findForItem(declarationTopElement, forDirectives = forDirectiveBlocks)
206+
207+
if (findDeclarationForItem == null && daoMethod != null) {
208+
val matchParam = daoMethod.findParameter(declarationTopElement.text)
209+
if (matchParam != null) {
210+
val convertOptional =
211+
PsiClassTypeUtil.convertOptionalType(matchParam.type, project)
212+
parentClassType = PsiParentClass(convertOptional)
213+
}
214+
}
215+
if (parentClassType != null) {
216+
val isBatchAnnotation = daoMethod?.let { PsiDaoMethod(project, it).daoType.isBatchAnnotation() } == true
217+
val forItemDeclarationBlocks =
218+
forDirectiveDeclaration.getDeclarationChildren()
219+
getFieldAccessLastPropertyClassType(
220+
forItemDeclarationBlocks,
221+
project,
222+
parentClassType,
223+
isBatchAnnotation = isBatchAnnotation,
224+
complete = { lastType ->
225+
val classType = lastType.type as? PsiClassType
226+
val nestClass =
227+
if (classType != null &&
228+
PsiClassTypeUtil.Companion.isIterableType(
229+
classType,
230+
project,
231+
)
232+
) {
233+
classType.parameters.firstOrNull()
234+
} else {
235+
null
236+
}
237+
parentClassType =
238+
if (nestClass != null) {
239+
PsiParentClass(nestClass)
240+
} else {
241+
null
242+
}
243+
},
244+
)
245+
if (targetForItem != null && formItem.element.text == targetForItem.text) {
246+
return parentClassType
247+
}
248+
}
215249
}
216250
}
217251

@@ -221,6 +255,7 @@ class ForDirectiveUtil {
221255
fun getTopForDirectiveDeclarationClassType(
222256
topForDirectiveItem: PsiElement,
223257
project: Project,
258+
daoMethod: PsiMethod?,
224259
): PsiParentClass? {
225260
var result: PsiParentClass? = null
226261
var fieldAccessTopParentClass: PsiParentClass? = null
@@ -256,8 +291,8 @@ class ForDirectiveUtil {
256291
)
257292
} else {
258293
// Defined by Dao parameter
259-
val file = topForDirectiveItem.containingFile ?: return null
260-
val daoMethod = findDaoMethod(file) ?: return null
294+
if (daoMethod == null) return null
295+
261296
val topElementText =
262297
forDirectiveDeclaration.getDeclarationChildren().firstOrNull()?.text
263298
?: return null
@@ -343,9 +378,10 @@ class ForDirectiveUtil {
343378
val convertOptional = PsiClassTypeUtil.convertOptionalType(topParent.type, project)
344379
PsiParentClass(convertOptional)
345380
}
346-
// TODO: Display an error message that the property cannot be called.
347381
val parentType = PsiClassTypeUtil.convertOptionalType(parent.type, project)
348-
val classType = parentType as? PsiClassType ?: return null
382+
val classType =
383+
parentType as? PsiClassType
384+
?: return ValidationNotFoundTopTypeResult(blocks.first(), shortName)
349385

350386
var competeResult: ValidationCompleteResult? = null
351387

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,14 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package org.domaframework.doma.intellij.inspection.sql.handler
16+
package org.domaframework.doma.intellij.inspection.sql.processor
1717

1818
import com.intellij.codeInspection.ProblemsHolder
1919
import com.intellij.psi.PsiFile
2020
import org.domaframework.doma.intellij.common.dao.findDaoMethod
2121
import org.domaframework.doma.intellij.common.psi.PsiDaoMethod
2222
import org.domaframework.doma.intellij.common.psi.PsiParentClass
2323
import org.domaframework.doma.intellij.common.sql.cleanString
24-
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationForDirectiveItemTypeResult
2524
import org.domaframework.doma.intellij.common.util.ForDirectiveUtil
2625
import org.domaframework.doma.intellij.extension.expr.accessElements
2726
import org.domaframework.doma.intellij.extension.psi.findParameter
@@ -54,10 +53,8 @@ class InspectionFieldAccessVisitorProcessor(
5453
if (forItem != null) {
5554
val result = ForDirectiveUtil.getForDirectiveItemClassType(project, forDirectiveBlocks, forItem)
5655
if (result == null) {
57-
ValidationForDirectiveItemTypeResult(
58-
topElement,
59-
this.shortName,
60-
).highlightElement(holder)
56+
// If the for directive definition element of the top element is invalid,
57+
// an error is displayed by InspectionPrimaryVisitorProcessor.
6158
return
6259
}
6360
val specifiedClassType =
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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.inspection.sql.processor
17+
18+
import com.intellij.codeInspection.ProblemsHolder
19+
import org.domaframework.doma.intellij.common.sql.validator.result.ValidationForDirectiveItemTypeResult
20+
import org.domaframework.doma.intellij.common.util.ForDirectiveUtil
21+
import org.domaframework.doma.intellij.extension.psi.getForItem
22+
import org.domaframework.doma.intellij.psi.SqlElForDirective
23+
24+
class InspectionForDirectiveVisitorProcessor(
25+
val shortName: String,
26+
private val element: SqlElForDirective,
27+
) : InspectionVisitorProcessor(shortName) {
28+
fun check(holder: ProblemsHolder) {
29+
val forItem = element.getForItem() ?: return
30+
val directiveBlocks = ForDirectiveUtil.getForDirectiveBlocks(forItem, false)
31+
val declarationType =
32+
ForDirectiveUtil.getForDirectiveItemClassType(element.project, directiveBlocks)
33+
34+
if (declarationType == null) {
35+
ValidationForDirectiveItemTypeResult(
36+
forItem,
37+
this.shortName,
38+
).highlightElement(holder)
39+
}
40+
}
41+
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package org.domaframework.doma.intellij.inspection.sql.handler
16+
package org.domaframework.doma.intellij.inspection.sql.processor
1717

1818
import com.intellij.codeInspection.ProblemsHolder
1919
import com.intellij.psi.PsiFile
@@ -50,6 +50,8 @@ class InspectionPrimaryVisitorProcessor(
5050
skipSelf = isSkip,
5151
forDirectives = forDirectiveBlocks,
5252
)
53+
if (forDirectiveExp?.getForItem() == element) return
54+
5355
if (forItem != null) {
5456
val forDeclarationType =
5557
ForDirectiveUtil.getForDirectiveItemClassType(
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package org.domaframework.doma.intellij.inspection.sql.handler
16+
package org.domaframework.doma.intellij.inspection.sql.processor
1717

1818
import com.intellij.codeInspection.ProblemsHolder
1919
import org.domaframework.doma.intellij.common.psi.PsiStaticElement
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package org.domaframework.doma.intellij.inspection.sql.handler
16+
package org.domaframework.doma.intellij.inspection.sql.processor
1717

1818
import com.intellij.codeInspection.ProblemsHolder
1919
import com.intellij.psi.PsiElement

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

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ import com.intellij.psi.util.elementType
2323
import org.domaframework.doma.intellij.common.isInjectionSqlFile
2424
import org.domaframework.doma.intellij.common.isJavaOrKotlinFileType
2525
import org.domaframework.doma.intellij.extension.psi.isFirstElement
26-
import org.domaframework.doma.intellij.inspection.sql.handler.InspectionFieldAccessVisitorProcessor
27-
import org.domaframework.doma.intellij.inspection.sql.handler.InspectionPrimaryVisitorProcessor
28-
import org.domaframework.doma.intellij.inspection.sql.handler.InspectionStaticFieldAccessVisitorProcessor
26+
import org.domaframework.doma.intellij.inspection.sql.processor.InspectionFieldAccessVisitorProcessor
27+
import org.domaframework.doma.intellij.inspection.sql.processor.InspectionForDirectiveVisitorProcessor
28+
import org.domaframework.doma.intellij.inspection.sql.processor.InspectionPrimaryVisitorProcessor
29+
import org.domaframework.doma.intellij.inspection.sql.processor.InspectionStaticFieldAccessVisitorProcessor
2930
import org.domaframework.doma.intellij.psi.SqlElFieldAccessExpr
31+
import org.domaframework.doma.intellij.psi.SqlElForDirective
3032
import org.domaframework.doma.intellij.psi.SqlElPrimaryExpr
3133
import org.domaframework.doma.intellij.psi.SqlElStaticFieldAccessExpr
3234
import org.domaframework.doma.intellij.psi.SqlTypes
@@ -50,17 +52,23 @@ class SqlInspectionVisitor(
5052

5153
override fun visitElStaticFieldAccessExpr(element: SqlElStaticFieldAccessExpr) {
5254
super.visitElStaticFieldAccessExpr(element)
53-
val handler = InspectionStaticFieldAccessVisitorProcessor(this.shortName)
54-
handler.check(element, holder)
55+
val processor = InspectionStaticFieldAccessVisitorProcessor(this.shortName)
56+
processor.check(element, holder)
5557
}
5658

5759
override fun visitElFieldAccessExpr(element: SqlElFieldAccessExpr) {
5860
super.visitElFieldAccessExpr(element)
5961
if (setFile(element)) return
6062
val visitFile: PsiFile = file ?: return
6163

62-
val handler = InspectionFieldAccessVisitorProcessor(shortName, element)
63-
handler.check(holder, visitFile)
64+
val processor = InspectionFieldAccessVisitorProcessor(shortName, element)
65+
processor.check(holder, visitFile)
66+
}
67+
68+
override fun visitElForDirective(element: SqlElForDirective) {
69+
super.visitElForDirective(element)
70+
val process = InspectionForDirectiveVisitorProcessor(shortName, element)
71+
process.check(holder)
6472
}
6573

6674
override fun visitElPrimaryExpr(element: SqlElPrimaryExpr) {
@@ -69,7 +77,7 @@ class SqlInspectionVisitor(
6977
if (setFile(element)) return
7078
val visitFile: PsiFile = file ?: return
7179

72-
val handler = InspectionPrimaryVisitorProcessor(this.shortName, element)
73-
handler.check(holder, visitFile)
80+
val processor = InspectionPrimaryVisitorProcessor(this.shortName, element)
81+
processor.check(holder, visitFile)
7482
}
7583
}

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

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class SqlElIdExprReference(
4848
// Refers to an element defined in the for directive
4949
val isSelfSkip = isSelfSkip(topElm)
5050
val forDirectiveBlocks = ForDirectiveUtil.getForDirectiveBlocks(element, isSelfSkip)
51-
val forItem = ForDirectiveUtil.findForItem(element, forDirectives = forDirectiveBlocks)
51+
val forItem = ForDirectiveUtil.findForItem(topElm, forDirectives = forDirectiveBlocks)
5252
if (forItem != null && element.textOffset == topElm.textOffset) {
5353
PluginLoggerUtil.countLogging(
5454
this::class.java.simpleName,
@@ -69,18 +69,29 @@ class SqlElIdExprReference(
6969
)
7070
}
7171

72-
val tolElementForItem =
73-
ForDirectiveUtil.getForDirectiveItemClassType(topElm.project, forDirectiveBlocks)
72+
// Reference to field access elements
73+
var parentClass: PsiParentClass? = null
7474
var isBatchAnnotation = false
75-
var parentClass =
76-
if (tolElementForItem != null) {
77-
tolElementForItem
78-
} else {
79-
val daoMethod = findDaoMethod(file) ?: return null
80-
val param = daoMethod.findParameter(topElm.text) ?: return null
81-
isBatchAnnotation = PsiDaoMethod(topElm.project, daoMethod).daoType.isBatchAnnotation()
82-
PsiParentClass(param.type)
83-
}
75+
if (forItem != null) {
76+
val project = topElm.project
77+
val forItemClassType =
78+
ForDirectiveUtil.getForDirectiveItemClassType(project, forDirectiveBlocks, forItem)
79+
?: return null
80+
val specifiedClassType =
81+
ForDirectiveUtil.resolveForDirectiveItemClassTypeBySuffixElement(topElm.text)
82+
parentClass =
83+
if (specifiedClassType != null) {
84+
PsiParentClass(specifiedClassType)
85+
} else {
86+
forItemClassType
87+
}
88+
} else {
89+
val daoMethod = findDaoMethod(file) ?: return null
90+
val param = daoMethod.findParameter(topElm.text) ?: return null
91+
parentClass = PsiParentClass(param.type)
92+
isBatchAnnotation = PsiDaoMethod(topElm.project, daoMethod).daoType.isBatchAnnotation()
93+
}
94+
8495
val result =
8596
ForDirectiveUtil.getFieldAccessLastPropertyClassType(
8697
targetElements,

src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/accessStaticProperty.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ where
3333
/*%end */
3434
)
3535
-- Static field call that does not exist
36-
/*%if @doma.example.entity.ProjectDetail@<error descr="The field or method [priority] does not exist in the class [ProjectDetail]">priority</error> >= 3 */
36+
/*%if @doma.example.entity.ProjectDetail@<error descr="[priority] is not a public or static property in the class [doma.example.entity.ProjectDetail]">priority</error> >= 3 */
3737
-- Static method call that does not exist
38-
AND pd.limit_date = /* @doma.example.entity.ProjectDetail@<error descr="The field or method [getLimit] does not exist in the class [ProjectDetail]">getLimit</error>() */0
38+
AND pd.limit_date = /* @doma.example.entity.ProjectDetail@<error descr="[getLimit] is not a public or static property in the class [doma.example.entity.ProjectDetail]">getLimit</error>() */0
3939
/*%end */

src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/bindVariableForNonEntityClass.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
AND pe.end_date >= CURRENT_DATE
2626
/*%end*/
2727
-- Reference error for a non-existent field
28-
/*%for child : employee.<error descr="The field or method [projectIds] does not exist in the class [EmployeeSummary]">projectIds</error> */
28+
/*%for <error descr="The type that can be used in the for directive is an Iterable type">child</error> : employee.<error descr="The field or method [projectIds] does not exist in the class [EmployeeSummary]">projectIds</error> */
2929
-- An error occurred because the referenced element was not correctly defined.
30-
AND pe.parent_project = /* <error descr="The bind variable [child] does not exist in the Dao method [bindVariableForNonEntityClass]">child</error>.projectId */0
30+
AND pe.parent_project = /* <error descr="The type that can be used in the for directive is an Iterable type">child</error>.projectId */0
3131
-- Reference error for a non-existent method
3232
AND pe.member_id IN /* employee.<error descr="The field or method [getTopProject] does not exist in the class [EmployeeSummary]">getTopProject</error>() */(0,1,2)
3333
/*%end */

0 commit comments

Comments
 (0)