Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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,
) : 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,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,24 @@ 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
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

/**
Expand Down Expand Up @@ -90,43 +96,60 @@ class DaoAnnotationOptionParameterCheckProcessor(
entityClass: PsiClass,
holder: ProblemsHolder,
) {
val arrayValues =
val expression =
annotation.parameterList.attributes
.find { it.name == optionName }
?.value
?.children
?.filter { it is PsiLiteralExpression } ?: return
?: return

val arrayValues = extractArrayValues(expression)
if (arrayValues.isEmpty()) return

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) }
// 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(
fields,
shortName,
fields.text.replace("\"", ""),
field,
optionName,
).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,
Expand All @@ -139,6 +162,15 @@ class DaoAnnotationOptionParameterCheckProcessor(
}
}

private fun extractArrayValues(expression: PsiAnnotationMemberValue): List<PsiElement> =
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"
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/messages/DomaToolsBundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 2 additions & 1 deletion src/main/resources/messages/DomaToolsBundle_ja.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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}]
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
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,18 @@ public interface AnnotationOptionTestDao {
@MultiInsert(returning = @Returning(include = {"email"},
exclude = {"embeddableEntity.salary"}))
List<Department> multiInsertReturning(List<Department> 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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,18 @@ public interface AnnotationOptionTestDao {
@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"},
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>}))
List<Department> multiInsertReturning(List<Department> departments);

// Dont array properties
@Update(returning = @Returning(include = "embeddableEntity.age"))
Department updateSingleInclude(Department department);

@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>))
Department insertSingleExclude(Department department);

// Primitive types
@Update(include = "embeddableEntity.subId")
int updatePrimitiveProperty(Department department);

@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>)
int insertPrimitiveProperty(Department department);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public class Department {
public String location;
public Integer managerCount;

int subId;

@Embedded
public ClientUser embeddableEntity;
@Embedded
Expand Down
Loading