Skip to content

Commit 71fffd6

Browse files
authored
Merge pull request #454 from domaframework/fix/annotation-option-check-when-using-a-parent-property
Fix: Annotation Option Checks Respect Entity Inheritance
2 parents ff2ad95 + c428de9 commit 71fffd6

File tree

16 files changed

+459
-211
lines changed

16 files changed

+459
-211
lines changed

.claude/guidelines/TEST_CASE_GUIDELINE.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Follow the rules below when implementing test code.
44
## Code Inspection Functionality
55
Implement tests for code inspection functionality using the following steps:
66

7-
### **Implementation of Test Case Classes**
7+
## Implementation of Test Case Classes
88
- Implement test case classes in `@src/test/kotlin/org/domaframework/doma/intellij/inspection`.
99
- Implement test cases for each subclass of **AbstractBaseJavaLocalInspectionTool**.
1010
- Name the test case class as `<InspectionToolName>InspectionTest`.
@@ -17,14 +17,31 @@ Implement tests for code inspection functionality using the following steps:
1717
Create the target Dao class or SQL file for inspection.
1818
Wrap the elements to be error-highlighted with the **<error>** tag and specify the error message to be displayed using the **descr** option.
1919

20-
#### Test Data for Dao Classes
20+
**Test Data for Dao Classes**
2121
- Implement test data Dao classes in **Java**.
2222
- Annotate test data Dao classes with **@Dao**.
2323
- Place them under the appropriate subpackage in `@src/test/testData/src/main/java/doma/example/dao/inspection`.
2424
- Implement any other necessary classes as needed.
2525

26+
**Entity Test Data**
27+
When creating test cases, prepare and verify the following Entity classes to test parent-child relationships:
28+
- Parent class annotated with @Entity
29+
- Subclass without @Entity annotation
30+
- Subclass annotated with @Entity
31+
32+
Using the above, create the following test cases for features that use `PsiClass`:
33+
- Test cases using fields/methods defined in parent classes
34+
- Test cases using fields/methods defined in subclasses
35+
36+
**Examples**
37+
- Test that bind variable definition inspection doesn't highlight errors when using fields defined in parent classes
38+
- Test that include/exclude option checking in DAO method annotations doesn't highlight errors when using fields defined in parent classes
39+
- Test that code completion shows fields/methods defined in parent classes as completion candidates
40+
2641
#### Test Data for SQL Files
2742
- Create and place SQL files in a directory named after the corresponding Dao class under `@src/test/testData/src/main/resources/META-INF/doma/example/dao`.
43+
- When creating test cases for bind variables, prepare test data that checks instance field methods, static field methods, field methods of elements defined in loop directives, custom functions, and built-in functions.
44+
For cases combining multiple variables, ensure comprehensive coverage of all variable combination patterns.
2845

2946
### Reference
3047
For actual implementation examples using Doma, refer to the [Doma GitHub repository](https://github.com/domaframework/doma/tree/master/integration-test-java/src/main/java/org/seasar/doma/it/dao).

CLAUDE.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,19 @@ Feature Package
107107
└── AnAction subclass
108108
```
109109

110+
### Common
111+
112+
**Accessing `PsiClass` Members**
113+
When retrieving fields and methods from `PsiClass`, use `allFields` and `allMethods` instead of `fields` and `methods`.
114+
This is necessary to include members defined in parent classes.
115+
116+
Alternatively, you can use [PsiParentClass](src/main/kotlin/org/domaframework/doma/intellij/common/psi/PsiParentClass.kt)
117+
to achieve equivalent functionality with `findField()` and `findMethod()`.
118+
119+
**Separating Complex Logic**
120+
Each feature requires implementing corresponding classes following the IntelliJ platform conventions.
121+
Complex logic should not be implemented directly in these corresponding classes but delegated to separate classes (Processors or Handlers).
122+
110123
### Actions
111124
Action functionality for navigating between DAO files and SQL files
112125

@@ -171,6 +184,10 @@ Code inspection functionality for DAO methods and DOMA directives in SQL
171184
- [ValidationResult](src/main/kotlin/org/domaframework/doma/intellij/common/validation/result): Classes that provide error messages and highlights for code inspection results
172185
- **Class Naming Rules**: Use `ValidationResult` as a suffix. Name classes according to the message resources to be displayed
173186

187+
**Coding Rule**
188+
For code inspection features, always implement `InspectionTool` and `Visitor` as separate classes.
189+
When the logic within `Visitor` becomes complex, implement separate Processor classes for main processing or Handler classes for error highlighting.
190+
174191
### Completion
175192
Code completion functionality for DOMA directive syntax in SQL
176193
- **[Feature Package](src/main/kotlin/org/domaframework/doma/intellij/contributor/sql)**

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ object PsiPatternUtil {
157157
targetType: IElementType?,
158158
): MutableList<PsiElement> {
159159
var prevElement = PsiTreeUtil.prevLeaf(element, true)
160-
var prevElements = mutableListOf<PsiElement>()
160+
val prevElements = mutableListOf<PsiElement>()
161161
while (prevElement != null &&
162162
prevElement !is PsiWhiteSpace &&
163163
prevElement.elementType != targetType &&

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,7 @@ object TypeUtil {
8989
returnTypeClass?.getClassAnnotation(DomaClassName.ENTITY.className) ?: return false
9090
return entity.let { entity ->
9191
AnnotationUtil.getBooleanAttributeValue(entity, "immutable") == true
92-
} == true ||
93-
returnTypeClass.isRecord == true
92+
} || returnTypeClass.isRecord
9493
}
9594

9695
/**

src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/processor/option/DaoAnnotationOptionParameterCheckProcessor.kt

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,12 @@ class DaoAnnotationOptionParameterCheckProcessor(
109109
arrayValues.map { fields ->
110110
val valueFields = fields.text.replace("\"", "").split(".")
111111
var searchParamType: PsiType = entityClass.psiClassType
112+
var prevFieldVal = valueFields.first()
112113
var searchParamClass: PsiClass? = project.getJavaClazz(searchParamType)
113-
var hasError = false
114114

115-
valueFields.forEachIndexed { index, field ->
116-
val currentField =
117-
searchParamClass
118-
?.fields
119-
?.find { property -> isOptionTargetProperty(property, field, project) }
115+
valueFields.forEachIndexed { _, field ->
116+
// Error when specifying a property not defined in the Entity or the Embeddable.
117+
val currentField = getMatchFields(searchParamClass).find { f -> isOptionTargetProperty(f, field, project) }
120118
// Given that the first `searchParamType` is assumed to contain the type of Entity class,
121119
// checking the index for a primitive type is unnecessary.
122120
if (searchParamType is PsiPrimitiveType) {
@@ -125,13 +123,13 @@ class DaoAnnotationOptionParameterCheckProcessor(
125123
fields,
126124
shortName,
127125
fields.text.replace("\"", ""),
128-
field,
126+
prevFieldVal,
129127
optionName,
130128
).highlightElement(holder)
131-
hasError = true
132129
return@map
133130
} else {
134131
if (currentField != null) {
132+
prevFieldVal = currentField.nameIdentifier.text
135133
searchParamType = currentField.type
136134
searchParamClass = project.getJavaClazz(searchParamType)
137135
} else {
@@ -143,7 +141,6 @@ class DaoAnnotationOptionParameterCheckProcessor(
143141
searchParamClass?.name ?: "Unknown",
144142
getTargetOptionProperties(searchParamClass),
145143
).highlightElement(holder)
146-
hasError = true
147144
return@map
148145
}
149146
}
@@ -171,16 +168,28 @@ class DaoAnnotationOptionParameterCheckProcessor(
171168
.filter { it is PsiLiteralExpression }
172169
}
173170

171+
private fun getMatchFields(paramClass: PsiClass?): List<PsiField> =
172+
paramClass?.allFields?.filter { f ->
173+
isValidMatchField(f)
174+
} ?: emptyList()
175+
176+
/**
177+
* Helper method to encapsulate field validation logic for getMatchFields.
178+
*/
179+
private fun isValidMatchField(f: PsiField): Boolean {
180+
val parentClass = f.parent as? PsiClass
181+
val isEntityOrEmbeddable = parentClass?.isEntity() == true || parentClass?.isEmbeddable() == true
182+
val isBaseOrEmbeddableType = TypeUtil.isBaseOrOptionalWrapper(f.type) || TypeUtil.isEmbeddable(f.type, project)
183+
return isEntityOrEmbeddable && isBaseOrEmbeddableType
184+
}
185+
174186
private fun getTargetOptionProperties(paramClass: PsiClass?) =
175-
paramClass?.fields?.filter { isOptionTargetProperty(it, it.name, project) }?.joinToString(", ") { it.name.substringAfter(":") }
176-
?: "No fields found"
187+
getMatchFields(paramClass).joinToString(", ") { it.name.substringAfter(":") }
177188

178-
private fun getEmbeddableProperties(embeddableClass: PsiClass?) =
179-
embeddableClass
180-
?.fields
181-
?.filter { !TypeUtil.isEntity(it.type, project) && !TypeUtil.isEmbeddable(it.type, project) }
182-
?.joinToString(", ") { it.name }
183-
?: "No properties found"
189+
/**
190+
* If the last field access is Embeddable, get its property list
191+
*/
192+
private fun getEmbeddableProperties(embeddableClass: PsiClass?) = getMatchFields(embeddableClass).joinToString(", ") { it.name }
184193

185194
private fun isOptionTargetProperty(
186195
field: PsiField,
@@ -189,7 +198,7 @@ class DaoAnnotationOptionParameterCheckProcessor(
189198
): Boolean =
190199
(
191200
field.name == optionPropertyName && (
192-
!TypeUtil.isEntity(field.type, project) ||
201+
TypeUtil.isBaseOrOptionalWrapper(field.type) ||
193202
TypeUtil.isEmbeddable(field.type, project)
194203
)
195204
)
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.inspection.dao
17+
18+
import org.domaframework.doma.intellij.DomaSqlTest
19+
import kotlin.test.Ignore
20+
21+
/**
22+
* Test class for annotation option parameter inspection.
23+
* Tests include/exclude options with parent class properties.
24+
*/
25+
@Ignore
26+
class AnnotationOptionParameterInspectionTest : DomaSqlTest() {
27+
/**
28+
* Since error highlight tags for annotation options cannot be set in the test data, verify manually.
29+
* There are no automated test cases, so perform manual checks using the following as a reference.
30+
*
31+
* Here is an updated summary of the test case coverage based on the revised method documentation. This can be used as a test case overview document.
32+
* Relation: [AnnotationOptionTestInValidDao]
33+
* - Error check when specifying fields not defined in the parameter type with `include` option.
34+
* - Error check when specifying fields not defined in the parameter type with `exclude` option.
35+
* - Error check for specifying fields not defined in immutable Entity with `MultiInsert` (also for fields not defined in parameter type).
36+
* - Error check for specifying fields not defined in mutable Entity with `MultiInsert`.
37+
* - Error check for specifying fields not defined in the parameter type with batch annotations.
38+
* - Error when ending with an embedded property.
39+
* - Error when specifying incorrect properties in an embedded class.
40+
* - Error check for invalid field specification in `Returning` option.
41+
* - Error check for invalid field specification in `Returning` option for mutable Entity.
42+
* - Error check for specifying fields not defined in embedded property.
43+
* - Error when specifying further properties from a primitive type.
44+
* - Error check for specifying parent class properties in subclass with `@Entity`.
45+
* - Error check for specifying parent class properties in subclass without `@Entity`.
46+
* - Error check for specifying fields from a parent class that is not an Entity.
47+
*/
48+
}

src/test/testData/src/main/java/doma/example/dao/inspection/option/AnnotationOptionTestDao_base.java

Lines changed: 0 additions & 94 deletions
This file was deleted.

0 commit comments

Comments
 (0)