diff --git a/.claude/guidelines/TEST_CASE_GUIDELINE.md b/.claude/guidelines/TEST_CASE_GUIDELINE.md index 9f8d9fec..131a536b 100644 --- a/.claude/guidelines/TEST_CASE_GUIDELINE.md +++ b/.claude/guidelines/TEST_CASE_GUIDELINE.md @@ -4,7 +4,7 @@ Follow the rules below when implementing test code. ## Code Inspection Functionality Implement tests for code inspection functionality using the following steps: -### **Implementation of Test Case Classes** +## Implementation of Test Case Classes - Implement test case classes in `@src/test/kotlin/org/domaframework/doma/intellij/inspection`. - Implement test cases for each subclass of **AbstractBaseJavaLocalInspectionTool**. - Name the test case class as `InspectionTest`. @@ -17,14 +17,31 @@ Implement tests for code inspection functionality using the following steps: Create the target Dao class or SQL file for inspection. Wrap the elements to be error-highlighted with the **** tag and specify the error message to be displayed using the **descr** option. -#### Test Data for Dao Classes +**Test Data for Dao Classes** - Implement test data Dao classes in **Java**. - Annotate test data Dao classes with **@Dao**. - Place them under the appropriate subpackage in `@src/test/testData/src/main/java/doma/example/dao/inspection`. - Implement any other necessary classes as needed. +**Entity Test Data** +When creating test cases, prepare and verify the following Entity classes to test parent-child relationships: +- Parent class annotated with @Entity +- Subclass without @Entity annotation +- Subclass annotated with @Entity + +Using the above, create the following test cases for features that use `PsiClass`: +- Test cases using fields/methods defined in parent classes +- Test cases using fields/methods defined in subclasses + +**Examples** +- Test that bind variable definition inspection doesn't highlight errors when using fields defined in parent classes +- Test that include/exclude option checking in DAO method annotations doesn't highlight errors when using fields defined in parent classes +- Test that code completion shows fields/methods defined in parent classes as completion candidates + #### Test Data for SQL Files - 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`. +- 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. + For cases combining multiple variables, ensure comprehensive coverage of all variable combination patterns. ### Reference 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). diff --git a/CLAUDE.md b/CLAUDE.md index a1a30f72..b59105a3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -107,6 +107,19 @@ Feature Package └── AnAction subclass ``` +### Common + +**Accessing `PsiClass` Members** +When retrieving fields and methods from `PsiClass`, use `allFields` and `allMethods` instead of `fields` and `methods`. +This is necessary to include members defined in parent classes. + +Alternatively, you can use [PsiParentClass](src/main/kotlin/org/domaframework/doma/intellij/common/psi/PsiParentClass.kt) +to achieve equivalent functionality with `findField()` and `findMethod()`. + +**Separating Complex Logic** +Each feature requires implementing corresponding classes following the IntelliJ platform conventions. +Complex logic should not be implemented directly in these corresponding classes but delegated to separate classes (Processors or Handlers). + ### Actions Action functionality for navigating between DAO files and SQL files @@ -171,6 +184,10 @@ Code inspection functionality for DAO methods and DOMA directives in SQL - [ValidationResult](src/main/kotlin/org/domaframework/doma/intellij/common/validation/result): Classes that provide error messages and highlights for code inspection results - **Class Naming Rules**: Use `ValidationResult` as a suffix. Name classes according to the message resources to be displayed +**Coding Rule** +For code inspection features, always implement `InspectionTool` and `Visitor` as separate classes. +When the logic within `Visitor` becomes complex, implement separate Processor classes for main processing or Handler classes for error highlighting. + ### Completion Code completion functionality for DOMA directive syntax in SQL - **[Feature Package](src/main/kotlin/org/domaframework/doma/intellij/contributor/sql)** diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/psi/PsiPatternUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/psi/PsiPatternUtil.kt index 80f233aa..ffdd30e4 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/psi/PsiPatternUtil.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/psi/PsiPatternUtil.kt @@ -157,7 +157,7 @@ object PsiPatternUtil { targetType: IElementType?, ): MutableList { var prevElement = PsiTreeUtil.prevLeaf(element, true) - var prevElements = mutableListOf() + val prevElements = mutableListOf() while (prevElement != null && prevElement !is PsiWhiteSpace && prevElement.elementType != targetType && diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/util/TypeUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/util/TypeUtil.kt index 31040a53..bbe5c051 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/util/TypeUtil.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/util/TypeUtil.kt @@ -89,8 +89,7 @@ object TypeUtil { returnTypeClass?.getClassAnnotation(DomaClassName.ENTITY.className) ?: return false return entity.let { entity -> AnnotationUtil.getBooleanAttributeValue(entity, "immutable") == true - } == true || - returnTypeClass.isRecord == true + } || returnTypeClass.isRecord } /** diff --git a/src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/processor/option/DaoAnnotationOptionParameterCheckProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/processor/option/DaoAnnotationOptionParameterCheckProcessor.kt index bcdd6196..a23e632e 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/processor/option/DaoAnnotationOptionParameterCheckProcessor.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/processor/option/DaoAnnotationOptionParameterCheckProcessor.kt @@ -109,14 +109,12 @@ class DaoAnnotationOptionParameterCheckProcessor( arrayValues.map { fields -> val valueFields = fields.text.replace("\"", "").split(".") var searchParamType: PsiType = entityClass.psiClassType + var prevFieldVal = valueFields.first() var searchParamClass: PsiClass? = project.getJavaClazz(searchParamType) - var hasError = false - valueFields.forEachIndexed { index, field -> - val currentField = - searchParamClass - ?.fields - ?.find { property -> isOptionTargetProperty(property, field, project) } + valueFields.forEachIndexed { _, field -> + // Error when specifying a property not defined in the Entity or the Embeddable. + val currentField = getMatchFields(searchParamClass).find { f -> isOptionTargetProperty(f, field, project) } // Given that the first `searchParamType` is assumed to contain the type of Entity class, // checking the index for a primitive type is unnecessary. if (searchParamType is PsiPrimitiveType) { @@ -125,13 +123,13 @@ class DaoAnnotationOptionParameterCheckProcessor( fields, shortName, fields.text.replace("\"", ""), - field, + prevFieldVal, optionName, ).highlightElement(holder) - hasError = true return@map } else { if (currentField != null) { + prevFieldVal = currentField.nameIdentifier.text searchParamType = currentField.type searchParamClass = project.getJavaClazz(searchParamType) } else { @@ -143,7 +141,6 @@ class DaoAnnotationOptionParameterCheckProcessor( searchParamClass?.name ?: "Unknown", getTargetOptionProperties(searchParamClass), ).highlightElement(holder) - hasError = true return@map } } @@ -171,16 +168,28 @@ class DaoAnnotationOptionParameterCheckProcessor( .filter { it is PsiLiteralExpression } } + private fun getMatchFields(paramClass: PsiClass?): List = + paramClass?.allFields?.filter { f -> + isValidMatchField(f) + } ?: emptyList() + + /** + * Helper method to encapsulate field validation logic for getMatchFields. + */ + private fun isValidMatchField(f: PsiField): Boolean { + val parentClass = f.parent as? PsiClass + val isEntityOrEmbeddable = parentClass?.isEntity() == true || parentClass?.isEmbeddable() == true + val isBaseOrEmbeddableType = TypeUtil.isBaseOrOptionalWrapper(f.type) || TypeUtil.isEmbeddable(f.type, project) + return isEntityOrEmbeddable && isBaseOrEmbeddableType + } + private fun getTargetOptionProperties(paramClass: PsiClass?) = - paramClass?.fields?.filter { isOptionTargetProperty(it, it.name, project) }?.joinToString(", ") { it.name.substringAfter(":") } - ?: "No fields found" + getMatchFields(paramClass).joinToString(", ") { it.name.substringAfter(":") } - private fun getEmbeddableProperties(embeddableClass: PsiClass?) = - embeddableClass - ?.fields - ?.filter { !TypeUtil.isEntity(it.type, project) && !TypeUtil.isEmbeddable(it.type, project) } - ?.joinToString(", ") { it.name } - ?: "No properties found" + /** + * If the last field access is Embeddable, get its property list + */ + private fun getEmbeddableProperties(embeddableClass: PsiClass?) = getMatchFields(embeddableClass).joinToString(", ") { it.name } private fun isOptionTargetProperty( field: PsiField, @@ -189,7 +198,7 @@ class DaoAnnotationOptionParameterCheckProcessor( ): Boolean = ( field.name == optionPropertyName && ( - !TypeUtil.isEntity(field.type, project) || + TypeUtil.isBaseOrOptionalWrapper(field.type) || TypeUtil.isEmbeddable(field.type, project) ) ) diff --git a/src/test/kotlin/org/domaframework/doma/intellij/inspection/dao/AnnotationOptionParameterInspectionTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/inspection/dao/AnnotationOptionParameterInspectionTest.kt new file mode 100644 index 00000000..2edd1464 --- /dev/null +++ b/src/test/kotlin/org/domaframework/doma/intellij/inspection/dao/AnnotationOptionParameterInspectionTest.kt @@ -0,0 +1,48 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.domaframework.doma.intellij.inspection.dao + +import org.domaframework.doma.intellij.DomaSqlTest +import kotlin.test.Ignore + +/** + * Test class for annotation option parameter inspection. + * Tests include/exclude options with parent class properties. + */ +@Ignore +class AnnotationOptionParameterInspectionTest : DomaSqlTest() { + /** + * Since error highlight tags for annotation options cannot be set in the test data, verify manually. + * There are no automated test cases, so perform manual checks using the following as a reference. + * + * 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. + * Relation: [AnnotationOptionTestInValidDao] + * - Error check when specifying fields not defined in the parameter type with `include` option. + * - Error check when specifying fields not defined in the parameter type with `exclude` option. + * - Error check for specifying fields not defined in immutable Entity with `MultiInsert` (also for fields not defined in parameter type). + * - Error check for specifying fields not defined in mutable Entity with `MultiInsert`. + * - Error check for specifying fields not defined in the parameter type with batch annotations. + * - Error when ending with an embedded property. + * - Error when specifying incorrect properties in an embedded class. + * - Error check for invalid field specification in `Returning` option. + * - Error check for invalid field specification in `Returning` option for mutable Entity. + * - Error check for specifying fields not defined in embedded property. + * - Error when specifying further properties from a primitive type. + * - Error check for specifying parent class properties in subclass with `@Entity`. + * - Error check for specifying parent class properties in subclass without `@Entity`. + * - Error check for specifying fields from a parent class that is not an Entity. + */ +} diff --git a/src/test/testData/src/main/java/doma/example/dao/inspection/option/AnnotationOptionTestDao_base.java b/src/test/testData/src/main/java/doma/example/dao/inspection/option/AnnotationOptionTestDao_base.java deleted file mode 100644 index 051053c3..00000000 --- a/src/test/testData/src/main/java/doma/example/dao/inspection/option/AnnotationOptionTestDao_base.java +++ /dev/null @@ -1,94 +0,0 @@ -package doma.example.dao.inspection.option; - -import doma.example.entity.*; -import org.seasar.doma.*; -import java.util.List; - -/** - * When using this test data with [LightJavaCodeInsightFixtureTestCase], - * the highlighted elements are not parsed correctly, resulting in errors. - * Compare the highlights in this test data with the actual highlights in IDEA, - * and visually confirm that the highlights and errors occur as expected based on the test data. - */ -@Dao -public interface AnnotationOptionTestDao { - - // Valid include option - @Insert(include = {"name", "location"}) - int insertWithValidInclude(Department department); - - // Invalid include option - @Update(include = {"name", "invalidField"}) - int updateWithInvalidInclude(Department department); - - // Valid exclude option - @Update(exclude = {"managerCount"}) - int updateWithValidExclude(Department department); - - // Invalid exclude option - @Insert(exclude = {"salary", "location"}) - int insertWithInvalidExclude(Department department); - - // Mixed valid and invalid - @Update(include = {"name"}, exclude = {"bonus"}) - int updateWithMixedOptions(Department department); - - // sqlFile = true should ignore include/exclude - @Update(sqlFile = true, include = {"invalidField"}) - int updateWithSqlFile(Department department); - - // BatchUpdate with invalid include - @BatchUpdate(include = {"email"}) - int batchUpdateWithInvalidInclude(List departments); - - // BatchUpdate with valid exclude - @BatchInsert(exclude = {"id", "managerCount"}) - int batchInsertWithValidExclude(List departments); - - // Non-entity parameter - no validation - @Update(include = {"name"}) - int updateNonEntity(String name); - - // End Embedded Property - should show error for Embeddable type needing property specification - @Update(include = {"embeddableEntity"}) - int updateEmbedded(Department department); - - // Valid Embedded Property specification - @Insert(include = {"embeddableEntity.age", "embeddableEntity.id"}) - int insertEmbeddedWithProperties(Department department); - - // Valid returning options - @Update(returning = @Returning(include = {"embeddableEntity.name"})) - Department updateReturning(Department department); - - @Insert(returning = @Returning(exclude = {"embeddableEntity.id"})) - Department insertReturning(Department department); - - @MultiInsert(returning = @Returning(include = {"embeddableEntity.name"},exclude = {"embeddableEntity.id"})) - List multiInsertReturning(List departments); - - // InValid returning options - @Update(returning = @Returning(include = {"embeddableEntity.age"})) - Department updateReturning(Department department); - - @Insert(returning = @Returning(exclude = {"embeddableEntity"})) - Department insertReturning(Department department); - - @MultiInsert(returning = @Returning(include = {"email"}, - exclude = {"embeddableEntity.salary"})) - List multiInsertReturning(List departments); - - // Dont array properties - @Update(returning = @Returning(include = "embeddableEntity.age")) - Department updateSingleInclude(Department department); - - @Insert(returning = @Returning(exclude = "embeddableEntity")) - Department insertSingleExclude(Department department); - - // Primitive types - @Update(include = "embeddableEntity.subId") - int updatePrimitiveProperty(Department department); - - @Insert(exclude = "embeddableEntity.subId.get") - int insertPrimitiveProperty(Department department); -} \ No newline at end of file diff --git a/src/test/testData/src/main/java/doma/example/dao/inspection/option/AnnotationOptionTestDao_highlight.java b/src/test/testData/src/main/java/doma/example/dao/inspection/option/AnnotationOptionTestDao_highlight.java deleted file mode 100644 index 2f99d076..00000000 --- a/src/test/testData/src/main/java/doma/example/dao/inspection/option/AnnotationOptionTestDao_highlight.java +++ /dev/null @@ -1,94 +0,0 @@ -package doma.example.dao.inspection.option; - -import doma.example.entity.*; -import org.seasar.doma.*; -import java.util.List; - -/** - * When using this test data with [LightJavaCodeInsightFixtureTestCase], - * the highlighted elements are not parsed correctly, resulting in errors. - * Compare the highlights in this test data with the actual highlights in IDEA, - * and visually confirm that the highlights and errors occur as expected based on the test data. - */ -@Dao -public interface AnnotationOptionTestDao { - - // Valid include option - @Insert(include = {"name", "location"}) - int insertWithValidInclude(Department department); - - // Invalid include option - @Update(include = {"name", "invalidField"}) - int updateWithInvalidInclude(Department department); - - // Valid exclude option - @Update(exclude = {"managerCount"}) - int updateWithValidExclude(Department department); - - // Invalid exclude option - @Insert(exclude = {"salary", "location"}) - int insertWithInvalidExclude(Department department); - - // Mixed valid and invalid - @Update(include = {"name"}, exclude = {"bonus"}) - int updateWithMixedOptions(Department department); - - // sqlFile = true should ignore include/exclude - @Update(sqlFile = true, include = {"invalidField"}) - int updateWithSqlFile(Department department); - - // BatchUpdate with invalid include - @BatchUpdate(include = {"email"}) - int[] batchUpdateWithInvalidInclude(List departments); - - // BatchUpdate with valid exclude - @BatchInsert(exclude = {"id", "managerCount"}) - int[] batchInsertWithValidExclude(List departments); - - // Non-entity parameter - no validation - @Update(include = {"name"}) - int updateNonEntity(String name); - - // End Embedded Property - should show error for Embeddable type needing property specification - @Update(include = {"embeddableEntity"}) - int updateEmbedded(Department department); - - // Valid Embedded Property specification - @Insert(include = {"embeddableEntity.age", "embeddableEntity.id"}) - int insertEmbeddedWithProperties(Department department); - - // Valid returning options - @Update(returning = @Returning(include = {"embeddableEntity.name"})) - Department updateReturning(Department department); - - @Insert(returning = @Returning(exclude = {"embeddableEntity.id"})) - Department insertReturning(Department department); - - @MultiInsert(returning = @Returning(include = {"embeddableEntity.name"},exclude = {"embeddableEntity.id"})) - List multiInsertReturning(List departments); - - // InValid returning options - @Update(returning = @Returning(include = {"embeddableEntity.age"})) - Department updateReturning(Department department); - - @Insert(returning = @Returning(exclude = {"embeddableEntity"})) - Department insertReturning(Department department); - - @MultiInsert(returning = @Returning(include = {"email"}, - exclude = {"embeddableEntity.salary"})) - List multiInsertReturning(List departments); - - // Dont array properties - @Update(returning = @Returning(include = "embeddableEntity.age")) - Department updateSingleInclude(Department department); - - @Insert(returning = @Returning(exclude = "embeddableEntity")) - Department insertSingleExclude(Department department); - - // Primitive types - @Update(include = "embeddableEntity.subId") - int updatePrimitiveProperty(Department department); - - @Insert(exclude = "embeddableEntity.subId.get") - int insertPrimitiveProperty(Department department); -} \ No newline at end of file diff --git a/src/test/testData/src/main/java/doma/example/dao/inspection/option/AnnotationOptionTestInValidDao.java b/src/test/testData/src/main/java/doma/example/dao/inspection/option/AnnotationOptionTestInValidDao.java new file mode 100644 index 00000000..949bac3b --- /dev/null +++ b/src/test/testData/src/main/java/doma/example/dao/inspection/option/AnnotationOptionTestInValidDao.java @@ -0,0 +1,145 @@ +package doma.example.dao.inspection.option; + +import doma.example.entity.Department; +import doma.example.entity.DepartmentHasNonEmmbedable; +import doma.example.entity.FacetEntity; +import doma.example.entity.Pckt; +import org.seasar.doma.*; +import org.seasar.doma.jdbc.MultiResult; + +import java.util.List; + +/** + * When using this test data with [LightJavaCodeInsightFixtureTestCase], + * the highlighted elements are not parsed correctly, resulting in errors. + * Compare the highlights in this test data with the actual highlights in IDEA, + * and visually confirm that the highlights and errors occur as expected based on the test data. + */ +@Dao +public interface AnnotationOptionTestInValidDao { + + /** + * include: Error highlight when specifying fields not defined in the parameter type + * Error: Field [invalidField] specified in [include] option does not exist in "Department". Available fields: [id, name, location, managerCount, subId, embeddableEntity, embeddableEntity2] + * @param department + * @return + */ + @Update(include = {"name", "invalidField"}) + int updateWithInvalidInclude(Department department); + + /** + * exclude: Error highlight when specifying fields not defined in the parameter type + * Error: Field [salary] specified in [exclude] option does not exist in "Department". Available fields: [id, name, location, managerCount, subId, embeddableEntity, embeddableEntity2] + * @param department + * @return + */ + @Insert(exclude = {"salary", "location"}) + int insertWithInvalidExclude(Department department); + + /** + * MultiInsert: Error highlight when specifying fields not defined in immutable Entity + * Also error highlight when specifying fields not defined in the parameter type + * Error: Field [salary] specified in [include] option does not exist in "Department". Available fields: [id, name, location, managerCount, subId, embeddableEntity, embeddableEntity2] + * Field [bonus] specified in [exclude] option does not exist in "Department". Available fields: [id, name, location, managerCount, subId, embeddableEntity, embeddableEntity2] + * @param departments + * @return + */ + @MultiInsert(include = {"salary"}, exclude = {"bonus"}) + int multiInsert(List departments); + + /** + * MultiInsert: Error highlight when specifying fields not defined in mutable Entity in include/exclude + * Error: Field [salary] specified in [include] option does not exist in "Pckt". Available fields: [id, name] + * Field [bonus] specified in [exclude] option does not exist in "Pckt". Available fields: [id, name] + * @param pckts + * @return + */ + @MultiInsert(include = {"salary"}, exclude = {"bonus"}) + MultiResult multiInsertImpairmentEntity(List pckts); + + /** + * Batch annotations: Error highlight when specifying fields not defined in the parameter type + * Error: Field [email] specified in [include] option does not exist in "Department". Available fields: [id, name, location, managerCount, subId, embeddableEntity, embeddableEntity2] + * @param departments + * @return + */ + @BatchUpdate(include = {"email"}) + int[] batchUpdateWithInvalidInclude(List departments); + + /** + * Error when ending with an Embedded property + * Error: Field [embeddableEntity] specified in [include] option is an Embeddable type "ClientUser". Must specify its properties. Available properties: [id, name, number] + */ + @Update(include = {"embeddableEntity"}) + int updateEmbedded(Department department); + + /** + * Error when there is a mistake in Embedded class properties + * Error: Field [age] specified in [include] option does not exist in "ClientUser". Available fields: [id, name, number, childEmbedded, childEmbedded2] + */ + @Insert(include = {"embeddableEntity.age", "embeddableEntity.id"}) + int insertEmbeddedWithProperties(Department department); + + /** + * Same check applies when using Returning + * Error: Field [age] specified in [include] option does not exist in "ClientUser". Available fields: [id, name, number, childEmbedded, childEmbedded2] + */ + @Update(returning = @Returning(include = {"embeddableEntity.age"})) + Department updateReturning(Department department); + + /** + * Error highlight when specifying invalid fields in Returning option + * Error: Field [embeddableEntity] specified in [exclude] option is an Embeddable type "ClientUser". Must specify its properties. Available properties: [id, name, number] + * @param department + * @return + */ + @Insert(returning = @Returning(exclude = {"embeddableEntity"})) + Department insertReturning(Department department); + + /** + * MultiInsert Returning option: Error highlight in both include/exclude when specifying invalid fields + * Error: Field [email] specified in [include] option does not exist in "Department". Available fields: [id, name, location, managerCount, subId, embeddableEntity, embeddableEntity2] + * @param departments + * @return + */ + @MultiInsert(returning = @Returning(include = {"email"}, exclude = {"embeddableEntity.salary"})) + List multiInsertReturning(List departments); + + /** + * Error highlight when specifying fields not defined in mutable Entity within Returning option + * Error: Field [embeddableEntity] specified in [include] option does not exist in "Pckt". Available fields: [id, name] + */ + @Update(returning = @Returning(include = {"embeddableEntity.age"})) + Pckt updateReturning(Pckt pckt); + + /** + * Error highlight when specifying fields not defined in Embedded property + * Error: Field [age] specified in [include] option does not exist in "ClientUser". Available fields: [id, name, number, childEmbedded, childEmbedded2] + */ + @Update(returning = @Returning(include = "embeddableEntity.age")) + Department updateSingleInclude(Department department); + + /** + * Error highlight when specifying further properties from a Primitive type + * Error: Field path [subId.get] specified in [exclude] option is invalid. Field [get] is a primitive type and does not have nested properties + */ + @Insert(exclude = "subId.get") + int insertPrimitiveProperty(Department department); + + /** + * Case specifying properties defined in an intermediate non-Entity class (Entity subclass) + * + * @param subEntity + * @return + */ + @Insert(exclude = "subName") + int insertReferenceNonEntityProperty2(FacetEntity subEntity); + + /** + * Case specifying properties defined in a parent Embeddable class + * Error occurs first on Entity side for non-Embeddable property + * */ + @Update(include = "embeddableEntity.childEmbedded3.subAccountNumber") + int updateReferenceParentEmbeddableProperty(DepartmentHasNonEmmbedable department); + +} \ No newline at end of file diff --git a/src/test/testData/src/main/java/doma/example/dao/inspection/option/AnnotationOptionTestValidDao.java b/src/test/testData/src/main/java/doma/example/dao/inspection/option/AnnotationOptionTestValidDao.java new file mode 100644 index 00000000..dfaddaf0 --- /dev/null +++ b/src/test/testData/src/main/java/doma/example/dao/inspection/option/AnnotationOptionTestValidDao.java @@ -0,0 +1,93 @@ +package doma.example.dao.inspection.option; + +import doma.example.entity.*; +import org.seasar.doma.*; + +import java.util.List; + +/** + * When using this test data with [LightJavaCodeInsightFixtureTestCase], + * the highlighted elements are not parsed correctly, resulting in errors. + * Compare the highlights in this test data with the actual highlights in IDEA, + * and visually confirm that the highlights and errors occur as expected based on the test data. + */ +@Dao +public interface AnnotationOptionTestValidDao { + + // include normal case + @Insert(include = {"name", "location"}) + int insertWithValidInclude(Department department); + + // exclude normal case + @Update(exclude = {"managerCount"}) + int updateWithValidExclude(Department department); + + @MultiInsert( + include = {"name"}, + exclude = {"location"}) + int multiInsert(List departments); + + // sqlFile = true should ignore include/exclude + @Update(sqlFile = true, include = {"invalidField"}) + int updateWithSqlFile(Department department); + + // With @Sql annotation, include/exclude checks are ignored + @Update(include = {"invalidField"}) + @Sql( + """ + UPDATE department + SET + WHERE id = /* department.id */0 + """) + int updateWithSqlAnnotation(Department department); + + // Batch annotation include/exclude checks + @BatchInsert(exclude = {"id", "managerCount"}) + int[] batchInsertWithValidExclude(List departments); + + // Non-entity parameter - no validation + @Update(include = {"name"}) + int updateNonEntity(String name); + + // Specifying properties from Embedded property + @Insert(include = {"embeddableEntity.name", "embeddableEntity.id"}) + int insertEmbeddedWithProperties(Department department); + + // Same check applies when using Returning + @Update(returning = @Returning(include = {"embeddableEntity.name"})) + Department updateReturning(Department department); + + @Insert(returning = @Returning(exclude = {"embeddableEntity.id"})) + Department insertReturning(Department department); + + @MultiInsert( + returning = + @Returning( + include = {"embeddableEntity.name"}, + exclude = {"embeddableEntity.id"})) + List multiInsertReturning(List departments); + + // Non-array property specification + @Insert(returning = @Returning(exclude = "embeddableEntity.id")) + Department insertSingleExclude(Department department); + + // Using Primitive type + @Update(include = "subId") + int updatePrimitiveProperty(Department department); + + /** + * Case of specifying properties defined in parent class (subclass with @Entity) + * @param subEntity + * @return + */ + @Insert(exclude = "name") + int insertReferenceParentEntityProperty1(SubEntity subEntity); + + /** + * Case of specifying properties defined in parent class (subclass without @Entity) + * @param subEntity + * @return + */ + @Insert(exclude = "name") + int insertReferenceParentEntityProperty2(NonSubEntity subEntity); +} diff --git a/src/test/testData/src/main/java/doma/example/entity/DepartmentHasNonEmmbedable.java b/src/test/testData/src/main/java/doma/example/entity/DepartmentHasNonEmmbedable.java new file mode 100644 index 00000000..97291ed8 --- /dev/null +++ b/src/test/testData/src/main/java/doma/example/entity/DepartmentHasNonEmmbedable.java @@ -0,0 +1,23 @@ +package doma.example.entity; + +import doma.example.domain.ClientUser; +import doma.example.domain.ClientUserHasNonEmbeddable; +import org.seasar.doma.Column; +import org.seasar.doma.Entity; + +/** Entity class with Embeddable property */ +@Entity +public class DepartmentHasNonEmmbedable { + public Integer id; + public String name; + + @Column(updatable = false) + public String location; + + public Integer managerCount; + + int subId; + + public ClientUserHasNonEmbeddable embeddableEntity; + +} diff --git a/src/test/testData/src/main/java/doma/example/entity/FacetEntity.java b/src/test/testData/src/main/java/doma/example/entity/FacetEntity.java new file mode 100644 index 00000000..f7fa0002 --- /dev/null +++ b/src/test/testData/src/main/java/doma/example/entity/FacetEntity.java @@ -0,0 +1,15 @@ +package doma.example.entity; + +import org.seasar.doma.Entity; + +/** + * Entity class at the lowest layer of the inheritance hierarchy + * {@code FoundationEntity}->{@code LayerNonEntity}->{@code FacetNonEntity} + */ +@Entity +public class FacetEntity extends LayerNonEntity { + + public Integer firstId; + public String firstName; + public String lastName; +} diff --git a/src/test/testData/src/main/java/doma/example/entity/FoundationEntity.java b/src/test/testData/src/main/java/doma/example/entity/FoundationEntity.java new file mode 100644 index 00000000..56124ab0 --- /dev/null +++ b/src/test/testData/src/main/java/doma/example/entity/FoundationEntity.java @@ -0,0 +1,18 @@ +package doma.example.entity; + +import org.seasar.doma.Entity; +import org.seasar.doma.Id; + +/** + * Entity class at the top of the inheritance hierarchy + * {@code FoundationEntity} + */ +@Entity +public class FoundationEntity { + + @Id + Integer id; + + String name; + +} diff --git a/src/test/testData/src/main/java/doma/example/entity/LayerEntity.java b/src/test/testData/src/main/java/doma/example/entity/LayerEntity.java new file mode 100644 index 00000000..0102d6e2 --- /dev/null +++ b/src/test/testData/src/main/java/doma/example/entity/LayerEntity.java @@ -0,0 +1,16 @@ +package doma.example.entity; + +import org.seasar.doma.Entity; +import org.seasar.doma.Table; + +/** + * Entity class in the middle of the inheritance hierarchy + * {@code FoundationEntity}->{@code LayerEntity} + */ +@Entity +@Table(name = "sub_entity_1") +public class LayerEntity extends FoundationEntity { + Integer amount; + + String subName; +} diff --git a/src/test/testData/src/main/java/doma/example/entity/LayerNonEntity.java b/src/test/testData/src/main/java/doma/example/entity/LayerNonEntity.java new file mode 100644 index 00000000..bfe0e93a --- /dev/null +++ b/src/test/testData/src/main/java/doma/example/entity/LayerNonEntity.java @@ -0,0 +1,17 @@ +package doma.example.entity; + +import org.seasar.doma.Table; + +/** + * Non-Entity class in the middle of the inheritance hierarchy + * {@code FoundationEntity}->{@code LayerNonEntity} + * Cannot be used with include/exclude options + * Cannot be specified as a parameter for certain annotation type DAO methods + */ +@Table(name = "sub_entity_2") +public class LayerNonEntity extends FoundationEntity { + + Integer amount; + + String subName; +} diff --git a/src/test/testData/src/main/java/doma/example/entity/SubGuestUser.java b/src/test/testData/src/main/java/doma/example/entity/SubGuestUser.java new file mode 100644 index 00000000..2bed963f --- /dev/null +++ b/src/test/testData/src/main/java/doma/example/entity/SubGuestUser.java @@ -0,0 +1,19 @@ +package doma.example.entity; + +import doma.example.domain.GuestUser; + +/** + * Class that extends the Embeddable GuestUser + */ +public class SubGuestUser extends GuestUser { + + public String subAccountNumber; + + public SubGuestUser(Integer id, String name, Integer number) { + super(id, name, number); + } + + public String getSubAccountNumber() { + return subAccountNumber; + } +}