From 353f2cfdebf3a5ceca2b4a32393d1f2b09cea0d2 Mon Sep 17 00:00:00 2001 From: xterao Date: Fri, 8 Aug 2025 16:15:59 +0900 Subject: [PATCH 1/4] Fix primitive type property check --- ...ionAnnotationOptionPrimitiveFieldResult.kt | 55 +++++++++++++++++++ ...AnnotationOptionParameterCheckProcessor.kt | 45 ++++++++++----- .../messages/DomaToolsBundle.properties | 1 + .../messages/DomaToolsBundle_ja.properties | 3 +- .../option/AnnotationOptionTestDao_base.java | 14 +++++ .../AnnotationOptionTestDao_highlight.java | 14 +++++ .../java/doma/example/entity/Department.java | 2 + 7 files changed, 120 insertions(+), 14 deletions(-) create mode 100644 src/main/kotlin/org/domaframework/doma/intellij/common/validation/result/ValidationAnnotationOptionPrimitiveFieldResult.kt diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/validation/result/ValidationAnnotationOptionPrimitiveFieldResult.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/validation/result/ValidationAnnotationOptionPrimitiveFieldResult.kt new file mode 100644 index 00000000..5e256f9c --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/validation/result/ValidationAnnotationOptionPrimitiveFieldResult.kt @@ -0,0 +1,55 @@ +/* + * 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.common.validation.result + +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement +import org.domaframework.doma.intellij.bundle.MessageBundle +import org.domaframework.doma.intellij.common.psi.PsiParentClass + +/** + * Validation result for annotation option primitive field errors. + * This is used when an include/exclude option references a nested property on a primitive type field. + */ +class ValidationAnnotationOptionPrimitiveFieldResult( + override val identify: PsiElement?, + override val shortName: String = "", + private val fieldPath: String, + private val primitiveFieldName: String, + private val optionName: String, + private val fieldType: String, // Keep for potential future use but not used in message +) : ValidationResult(identify, null, shortName) { + override fun setHighlight( + highlightRange: TextRange, + identify: PsiElement, + holder: ProblemsHolder, + parent: PsiParentClass?, + ) { + val project = identify.project + holder.registerProblem( + identify, + MessageBundle.message( + "inspection.invalid.dao.annotation.option.primitive", + fieldPath, + primitiveFieldName, + optionName, + ), + problemHighlightType(project, shortName), + highlightRange, + ) + } +} \ No newline at end of file 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 83cc98cf..cb933a7b 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 @@ -22,14 +22,18 @@ import com.intellij.psi.PsiClass import com.intellij.psi.PsiClassType import com.intellij.psi.PsiField import com.intellij.psi.PsiLiteralExpression +import com.intellij.psi.PsiPrimitiveType +import com.intellij.psi.PsiType import org.domaframework.doma.intellij.common.psi.PsiDaoMethod import org.domaframework.doma.intellij.common.util.TypeUtil import org.domaframework.doma.intellij.common.validation.result.ValidationAnnotationOptionEmbeddableResult import org.domaframework.doma.intellij.common.validation.result.ValidationAnnotationOptionParameterResult +import org.domaframework.doma.intellij.common.validation.result.ValidationAnnotationOptionPrimitiveFieldResult import org.domaframework.doma.intellij.extension.getJavaClazz import org.domaframework.doma.intellij.extension.psi.DomaAnnotationType import org.domaframework.doma.intellij.extension.psi.isEmbeddable import org.domaframework.doma.intellij.extension.psi.isEntity +import org.domaframework.doma.intellij.extension.psi.psiClassType import org.domaframework.doma.intellij.inspection.dao.processor.TypeCheckerProcessor /** @@ -101,32 +105,47 @@ class DaoAnnotationOptionParameterCheckProcessor( val project = method.project arrayValues.map { fields -> val valueFields = fields.text.replace("\"", "").split(".") - var searchParamClass: PsiClass? = entityClass - var preSearchParamClass: PsiClass? = entityClass + var searchParamType: PsiType = entityClass.psiClassType + var searchParamClass: PsiClass? = project.getJavaClazz(searchParamType) var hasError = false - valueFields.map { field -> - searchParamClass - ?.fields - ?.find { property -> isOptionTargetProperty(property, field, project) } - ?.let { f -> - preSearchParamClass = searchParamClass - searchParamClass = project.getJavaClazz(f.type) ?: return@map - } - ?: run { + + valueFields.forEachIndexed { index, field -> + val currentField = + searchParamClass + ?.fields + ?.find { property -> isOptionTargetProperty(property, field, project) } + if (searchParamType is PsiPrimitiveType) { + // This is a primitive/basic type but there are more fields after it + ValidationAnnotationOptionPrimitiveFieldResult( + fields, + shortName, + fields.text.replace("\"", ""), + field, + optionName, + field, + ).highlightElement(holder) + hasError = true + return@map + } else { + if (currentField != null) { + searchParamType = currentField.type + searchParamClass = project.getJavaClazz(searchParamType) + } else { ValidationAnnotationOptionParameterResult( fields, shortName, field, optionName, searchParamClass?.name ?: "Unknown", - getTargetOptionProperties(preSearchParamClass), + getTargetOptionProperties(searchParamClass), ).highlightElement(holder) hasError = true return@map } + } } // Error if the last field is Embeddable - if (!hasError && searchParamClass?.isEmbeddable() == true) { + if (searchParamClass?.isEmbeddable() == true) { ValidationAnnotationOptionEmbeddableResult( fields, shortName, diff --git a/src/main/resources/messages/DomaToolsBundle.properties b/src/main/resources/messages/DomaToolsBundle.properties index 2a920cd5..7a03dfff 100644 --- a/src/main/resources/messages/DomaToolsBundle.properties +++ b/src/main/resources/messages/DomaToolsBundle.properties @@ -38,3 +38,4 @@ inspection.invalid.dao.sqlProcessor.params.biFunction.param.second=The second ty inspection.invalid.dao.sqlProcessor.params.biFunction.param.invalid=The type argument at index {0} is not supported inspection.invalid.dao.annotation.option.field=Field [{0}] specified in [{1}] option does not exist in "{2}". Available fields: [{3}] inspection.invalid.dao.annotation.option.embeddable=Field [{0}] specified in [{1}] option is an Embeddable type "{2}". Must specify its properties. Available properties: [{3}] +inspection.invalid.dao.annotation.option.primitive=Field path [{0}] specified in [{2}] option is invalid. Field [{1}] is a primitive type and does not have nested properties diff --git a/src/main/resources/messages/DomaToolsBundle_ja.properties b/src/main/resources/messages/DomaToolsBundle_ja.properties index afba8377..5eacb914 100644 --- a/src/main/resources/messages/DomaToolsBundle_ja.properties +++ b/src/main/resources/messages/DomaToolsBundle_ja.properties @@ -37,4 +37,5 @@ inspection.invalid.dao.sqlProcessor.params.biFunction.param.first=\u0042\u0069\u inspection.invalid.dao.sqlProcessor.params.biFunction.param.second=\u0042\u0069\u0046\u0075\u006E\u0063\u0074\u0069\u006F\u006E\u306E\u0032\u756A\u76EE\u306E\u8981\u7D20\u306F\u300C{0}\u300D\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 inspection.invalid.dao.sqlProcessor.params.biFunction.param.invalid={0}\u756A\u76EE\u306E\u8981\u7D20\u306F\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u307E\u305B\u3093 inspection.invalid.dao.annotation.option.field=[{1}]\u30AA\u30D7\u30B7\u30E7\u30F3\u3067\u6307\u5B9A\u3055\u308C\u305F\u30D5\u30A3\u30FC\u30EB\u30C9[{0}]\u306F"{2}"\u306B\u5B58\u5728\u3057\u307E\u305B\u3093\u3002\u5229\u7528\u53EF\u80FD\u306A\u30D5\u30A3\u30FC\u30EB\u30C9: [{3}] -inspection.invalid.dao.annotation.option.embeddable=[{1}]\u30AA\u30D7\u30B7\u30E7\u30F3\u3067\u6307\u5B9A\u3055\u308C\u305F\u30D5\u30A3\u30FC\u30EB\u30C9[{0}]\u306FEmbeddable\u578B"{2}"\u3067\u3059\u3002\u305D\u306E\u30D7\u30ED\u30D1\u30C6\u30A3\u3092\u6307\u5B9A\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002\u5229\u7528\u53EF\u80FD\u306A\u30D7\u30ED\u30D1\u30C6\u30A3: [{3}] \ No newline at end of file +inspection.invalid.dao.annotation.option.embeddable=[{1}]\u30AA\u30D7\u30B7\u30E7\u30F3\u3067\u6307\u5B9A\u3055\u308C\u305F\u30D5\u30A3\u30FC\u30EB\u30C9[{0}]\u306FEmbeddable\u578B"{2}"\u3067\u3059\u3002\u305D\u306E\u30D7\u30ED\u30D1\u30C6\u30A3\u3092\u6307\u5B9A\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002\u5229\u7528\u53EF\u80FD\u306A\u30D7\u30ED\u30D1\u30C6\u30A3: [{3}] +inspection.invalid.dao.annotation.option.primitive=[{2}]\u30AA\u30D7\u30B7\u30E7\u30F3\u3067\u6307\u5B9A\u3055\u308C\u305F\u30D5\u30A3\u30FC\u30EB\u30C9\u30D1\u30B9[{0}]\u306F\u7121\u52B9\u3067\u3059\u3002\u30D5\u30A3\u30FC\u30EB\u30C9[{1}]\u306F\u30D7\u30EA\u30DF\u30C6\u30A3\u30D6\u578B\u3067\u3042\u308A\u3001\u30CD\u30B9\u30C8\u3055\u308C\u305F\u30D7\u30ED\u30D1\u30C6\u30A3\u3092\u6301\u3061\u307E\u305B\u3093 \ No newline at end of file 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 index 1ce7aef2..051053c3 100644 --- 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 @@ -77,4 +77,18 @@ public interface AnnotationOptionTestDao { @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 index ac32ddbc..2f99d076 100644 --- 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 @@ -77,4 +77,18 @@ public interface AnnotationOptionTestDao { @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/entity/Department.java b/src/test/testData/src/main/java/doma/example/entity/Department.java index 4c60e8e4..df597748 100644 --- a/src/test/testData/src/main/java/doma/example/entity/Department.java +++ b/src/test/testData/src/main/java/doma/example/entity/Department.java @@ -11,6 +11,8 @@ public class Department { public String location; public Integer managerCount; + int subId; + @Embedded public ClientUser embeddableEntity; @Embedded From a4b354b1f010cc89f8a5d3ba23f2d00c7db397d8 Mon Sep 17 00:00:00 2001 From: xterao Date: Fri, 8 Aug 2025 16:16:12 +0900 Subject: [PATCH 2/4] Add support for non-array property specifications. --- .../DaoAnnotationOptionParameterCheckProcessor.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) 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 cb933a7b..21742537 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 @@ -94,12 +94,19 @@ class DaoAnnotationOptionParameterCheckProcessor( entityClass: PsiClass, holder: ProblemsHolder, ) { - val arrayValues = + val expression = annotation.parameterList.attributes .find { it.name == optionName } ?.value - ?.children - ?.filter { it is PsiLiteralExpression } ?: return + + val arrayValues = + if (expression is PsiLiteralExpression) { + listOf(expression) + } else { + expression + ?.children + ?.filter { it is PsiLiteralExpression } ?: return + } if (arrayValues.isEmpty()) return val project = method.project From 0a0018e6884d23b10ee038eda0d87607e677cfbc Mon Sep 17 00:00:00 2001 From: xterao Date: Fri, 8 Aug 2025 16:17:03 +0900 Subject: [PATCH 3/4] Remove unused field parameters from validation and processing classes --- .../result/ValidationAnnotationOptionPrimitiveFieldResult.kt | 3 +-- .../option/DaoAnnotationOptionParameterCheckProcessor.kt | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/validation/result/ValidationAnnotationOptionPrimitiveFieldResult.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/validation/result/ValidationAnnotationOptionPrimitiveFieldResult.kt index 5e256f9c..e1b5c19d 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/validation/result/ValidationAnnotationOptionPrimitiveFieldResult.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/validation/result/ValidationAnnotationOptionPrimitiveFieldResult.kt @@ -31,7 +31,6 @@ class ValidationAnnotationOptionPrimitiveFieldResult( private val fieldPath: String, private val primitiveFieldName: String, private val optionName: String, - private val fieldType: String, // Keep for potential future use but not used in message ) : ValidationResult(identify, null, shortName) { override fun setHighlight( highlightRange: TextRange, @@ -52,4 +51,4 @@ class ValidationAnnotationOptionPrimitiveFieldResult( highlightRange, ) } -} \ No newline at end of file +} 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 21742537..fcd6da3c 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 @@ -129,7 +129,6 @@ class DaoAnnotationOptionParameterCheckProcessor( fields.text.replace("\"", ""), field, optionName, - field, ).highlightElement(holder) hasError = true return@map From 551bc525e8212bca9d1503bb9007fd2e05ff39f6 Mon Sep 17 00:00:00 2001 From: xterao Date: Fri, 8 Aug 2025 16:47:47 +0900 Subject: [PATCH 4/4] Refactor annotation option parameter check to extract array values into a separate function --- ...AnnotationOptionParameterCheckProcessor.kt | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) 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 fcd6da3c..bcdd6196 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 @@ -18,8 +18,10 @@ package org.domaframework.doma.intellij.inspection.dao.processor.option import com.intellij.codeInspection.ProblemsHolder import com.intellij.openapi.project.Project import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiAnnotationMemberValue import com.intellij.psi.PsiClass import com.intellij.psi.PsiClassType +import com.intellij.psi.PsiElement import com.intellij.psi.PsiField import com.intellij.psi.PsiLiteralExpression import com.intellij.psi.PsiPrimitiveType @@ -98,15 +100,9 @@ class DaoAnnotationOptionParameterCheckProcessor( annotation.parameterList.attributes .find { it.name == optionName } ?.value + ?: return - val arrayValues = - if (expression is PsiLiteralExpression) { - listOf(expression) - } else { - expression - ?.children - ?.filter { it is PsiLiteralExpression } ?: return - } + val arrayValues = extractArrayValues(expression) if (arrayValues.isEmpty()) return val project = method.project @@ -121,6 +117,8 @@ class DaoAnnotationOptionParameterCheckProcessor( searchParamClass ?.fields ?.find { property -> isOptionTargetProperty(property, 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) { // This is a primitive/basic type but there are more fields after it ValidationAnnotationOptionPrimitiveFieldResult( @@ -164,6 +162,15 @@ class DaoAnnotationOptionParameterCheckProcessor( } } + private fun extractArrayValues(expression: PsiAnnotationMemberValue): List = + if (expression is PsiLiteralExpression) { + listOf(expression) + } else { + expression + .children + .filter { it is PsiLiteralExpression } + } + private fun getTargetOptionProperties(paramClass: PsiClass?) = paramClass?.fields?.filter { isOptionTargetProperty(it, it.name, project) }?.joinToString(", ") { it.name.substringAfter(":") } ?: "No fields found"