Skip to content

Commit 3b89ec3

Browse files
authored
Merge pull request #378 from domaframework/fix/check-annotation-option-primitive-property
Fixes to Annotation Option Validation
2 parents c48bff5 + f4f7157 commit 3b89ec3

File tree

7 files changed

+135
-17
lines changed

7 files changed

+135
-17
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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.common.validation.result
17+
18+
import com.intellij.codeInspection.ProblemsHolder
19+
import com.intellij.openapi.util.TextRange
20+
import com.intellij.psi.PsiElement
21+
import org.domaframework.doma.intellij.bundle.MessageBundle
22+
import org.domaframework.doma.intellij.common.psi.PsiParentClass
23+
24+
/**
25+
* Validation result for annotation option primitive field errors.
26+
* This is used when an include/exclude option references a nested property on a primitive type field.
27+
*/
28+
class ValidationAnnotationOptionPrimitiveFieldResult(
29+
override val identify: PsiElement?,
30+
override val shortName: String = "",
31+
private val fieldPath: String,
32+
private val primitiveFieldName: String,
33+
private val optionName: String,
34+
) : ValidationResult(identify, null, shortName) {
35+
override fun setHighlight(
36+
highlightRange: TextRange,
37+
identify: PsiElement,
38+
holder: ProblemsHolder,
39+
parent: PsiParentClass?,
40+
) {
41+
val project = identify.project
42+
holder.registerProblem(
43+
identify,
44+
MessageBundle.message(
45+
"inspection.invalid.dao.annotation.option.primitive",
46+
fieldPath,
47+
primitiveFieldName,
48+
optionName,
49+
),
50+
problemHighlightType(project, shortName),
51+
highlightRange,
52+
)
53+
}
54+
}

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

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,24 @@ package org.domaframework.doma.intellij.inspection.dao.processor.option
1818
import com.intellij.codeInspection.ProblemsHolder
1919
import com.intellij.openapi.project.Project
2020
import com.intellij.psi.PsiAnnotation
21+
import com.intellij.psi.PsiAnnotationMemberValue
2122
import com.intellij.psi.PsiClass
2223
import com.intellij.psi.PsiClassType
24+
import com.intellij.psi.PsiElement
2325
import com.intellij.psi.PsiField
2426
import com.intellij.psi.PsiLiteralExpression
27+
import com.intellij.psi.PsiPrimitiveType
28+
import com.intellij.psi.PsiType
2529
import org.domaframework.doma.intellij.common.psi.PsiDaoMethod
2630
import org.domaframework.doma.intellij.common.util.TypeUtil
2731
import org.domaframework.doma.intellij.common.validation.result.ValidationAnnotationOptionEmbeddableResult
2832
import org.domaframework.doma.intellij.common.validation.result.ValidationAnnotationOptionParameterResult
33+
import org.domaframework.doma.intellij.common.validation.result.ValidationAnnotationOptionPrimitiveFieldResult
2934
import org.domaframework.doma.intellij.extension.getJavaClazz
3035
import org.domaframework.doma.intellij.extension.psi.DomaAnnotationType
3136
import org.domaframework.doma.intellij.extension.psi.isEmbeddable
3237
import org.domaframework.doma.intellij.extension.psi.isEntity
38+
import org.domaframework.doma.intellij.extension.psi.psiClassType
3339
import org.domaframework.doma.intellij.inspection.dao.processor.TypeCheckerProcessor
3440

3541
/**
@@ -90,43 +96,60 @@ class DaoAnnotationOptionParameterCheckProcessor(
9096
entityClass: PsiClass,
9197
holder: ProblemsHolder,
9298
) {
93-
val arrayValues =
99+
val expression =
94100
annotation.parameterList.attributes
95101
.find { it.name == optionName }
96102
?.value
97-
?.children
98-
?.filter { it is PsiLiteralExpression } ?: return
103+
?: return
104+
105+
val arrayValues = extractArrayValues(expression)
99106
if (arrayValues.isEmpty()) return
100107

101108
val project = method.project
102109
arrayValues.map { fields ->
103110
val valueFields = fields.text.replace("\"", "").split(".")
104-
var searchParamClass: PsiClass? = entityClass
105-
var preSearchParamClass: PsiClass? = entityClass
111+
var searchParamType: PsiType = entityClass.psiClassType
112+
var searchParamClass: PsiClass? = project.getJavaClazz(searchParamType)
106113
var hasError = false
107-
valueFields.map { field ->
108-
searchParamClass
109-
?.fields
110-
?.find { property -> isOptionTargetProperty(property, field, project) }
111-
?.let { f ->
112-
preSearchParamClass = searchParamClass
113-
searchParamClass = project.getJavaClazz(f.type) ?: return@map
114-
}
115-
?: run {
114+
115+
valueFields.forEachIndexed { index, field ->
116+
val currentField =
117+
searchParamClass
118+
?.fields
119+
?.find { property -> isOptionTargetProperty(property, field, project) }
120+
// Given that the first `searchParamType` is assumed to contain the type of Entity class,
121+
// checking the index for a primitive type is unnecessary.
122+
if (searchParamType is PsiPrimitiveType) {
123+
// This is a primitive/basic type but there are more fields after it
124+
ValidationAnnotationOptionPrimitiveFieldResult(
125+
fields,
126+
shortName,
127+
fields.text.replace("\"", ""),
128+
field,
129+
optionName,
130+
).highlightElement(holder)
131+
hasError = true
132+
return@map
133+
} else {
134+
if (currentField != null) {
135+
searchParamType = currentField.type
136+
searchParamClass = project.getJavaClazz(searchParamType)
137+
} else {
116138
ValidationAnnotationOptionParameterResult(
117139
fields,
118140
shortName,
119141
field,
120142
optionName,
121143
searchParamClass?.name ?: "Unknown",
122-
getTargetOptionProperties(preSearchParamClass),
144+
getTargetOptionProperties(searchParamClass),
123145
).highlightElement(holder)
124146
hasError = true
125147
return@map
126148
}
149+
}
127150
}
128151
// Error if the last field is Embeddable
129-
if (!hasError && searchParamClass?.isEmbeddable() == true) {
152+
if (searchParamClass?.isEmbeddable() == true) {
130153
ValidationAnnotationOptionEmbeddableResult(
131154
fields,
132155
shortName,
@@ -139,6 +162,15 @@ class DaoAnnotationOptionParameterCheckProcessor(
139162
}
140163
}
141164

165+
private fun extractArrayValues(expression: PsiAnnotationMemberValue): List<PsiElement> =
166+
if (expression is PsiLiteralExpression) {
167+
listOf(expression)
168+
} else {
169+
expression
170+
.children
171+
.filter { it is PsiLiteralExpression }
172+
}
173+
142174
private fun getTargetOptionProperties(paramClass: PsiClass?) =
143175
paramClass?.fields?.filter { isOptionTargetProperty(it, it.name, project) }?.joinToString(", ") { it.name.substringAfter(":") }
144176
?: "No fields found"

src/main/resources/messages/DomaToolsBundle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,4 @@ inspection.invalid.dao.sqlProcessor.params.biFunction.param.second=The second ty
3838
inspection.invalid.dao.sqlProcessor.params.biFunction.param.invalid=The type argument at index {0} is not supported
3939
inspection.invalid.dao.annotation.option.field=Field [{0}] specified in [{1}] option does not exist in "{2}". Available fields: [{3}]
4040
inspection.invalid.dao.annotation.option.embeddable=Field [{0}] specified in [{1}] option is an Embeddable type "{2}". Must specify its properties. Available properties: [{3}]
41+
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

src/main/resources/messages/DomaToolsBundle_ja.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,5 @@ inspection.invalid.dao.sqlProcessor.params.biFunction.param.first=\u0042\u0069\u
3737
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
3838
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
3939
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}]
40-
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}]
40+
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}]
41+
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

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,18 @@ public interface AnnotationOptionTestDao {
7777
@MultiInsert(returning = @Returning(include = {"email"},
7878
exclude = {"embeddableEntity.salary"}))
7979
List<Department> multiInsertReturning(List<Department> departments);
80+
81+
// Dont array properties
82+
@Update(returning = @Returning(include = "embeddableEntity.age"))
83+
Department updateSingleInclude(Department department);
84+
85+
@Insert(returning = @Returning(exclude = "embeddableEntity"))
86+
Department insertSingleExclude(Department department);
87+
88+
// Primitive types
89+
@Update(include = "embeddableEntity.subId")
90+
int updatePrimitiveProperty(Department department);
91+
92+
@Insert(exclude = "embeddableEntity.subId.get")
93+
int insertPrimitiveProperty(Department department);
8094
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,18 @@ public interface AnnotationOptionTestDao {
7777
@MultiInsert(returning = @Returning(include = {<error descr="Field [email] specified in [include] option does not exist in \"Department\". Available fields: [id, name, location, managerCount, embeddableEntity, embeddableEntity2] ">"email"},
7878
exclude = {<error descr="Field [salary] specified in [exclude] option does not exist in \"ClientUser\". Available fields: [id, name, location, managerCount, embeddableEntity, embeddableEntity2] ">"embeddableEntity.salary"</error>}))
7979
List<Department> multiInsertReturning(List<Department> departments);
80+
81+
// Dont array properties
82+
@Update(returning = @Returning(include = "embeddableEntity.age"))
83+
Department updateSingleInclude(Department department);
84+
85+
@Insert(returning = @Returning(exclude = <error descr="Field [age] specified in [include] option does not exist in \"ClientUser\". Available fields: [id, name, number, childEmbedded, childEmbedded2] ">"embeddableEntity"</error>))
86+
Department insertSingleExclude(Department department);
87+
88+
// Primitive types
89+
@Update(include = "embeddableEntity.subId")
90+
int updatePrimitiveProperty(Department department);
91+
92+
@Insert(exclude = <error descr="Field path [subId. get] specified in [exclude] option is invalid. Field [get] is a primitive type and does not have nested properties ">"embeddableEntity.subId.get"</error>)
93+
int insertPrimitiveProperty(Department department);
8094
}

src/test/testData/src/main/java/doma/example/entity/Department.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ public class Department {
1111
public String location;
1212
public Integer managerCount;
1313

14+
int subId;
15+
1416
@Embedded
1517
public ClientUser embeddableEntity;
1618
@Embedded

0 commit comments

Comments
 (0)