Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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,7 +18,11 @@ package org.domaframework.doma.intellij.extension.psi
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiParameter

fun PsiMethod.findParameter(searchName: String): PsiParameter? = this.methodParameters.firstOrNull { it.name == searchName }
fun PsiMethod.findParameter(searchName: String): PsiParameter? =
this.methodParameters.firstOrNull {
it.name == searchName &&
!it.isIgnoreUsageCheck()
}

val PsiMethod.methodParameters: List<PsiParameter>
get() = this.parameterList.parameters.toList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ fun PsiParameter.isIgnoreUsageCheck(): Boolean =
when (type) {
DomaClassName.SELECT_OPTIONS -> {
// Only ignore the exact SelectOptions class, not its subtypes
val clazzType = this.typeElement?.type as? PsiClassType
val clazzType = this.type as? PsiClassType
clazzType?.rawType()?.canonicalText == type.className
}
else -> getSuperClassType(type) != null
}
}

fun PsiParameter.getSuperClassType(superClassType: DomaClassName): PsiClassType? {
val clazzType = this.typeElement?.type as? PsiClassType
val clazzType = this.type as? PsiClassType
var superCollection: PsiClassType? = clazzType
while (superCollection != null &&
!superClassType.isTargetClassNameStartsWith(superCollection.canonicalText)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down Expand Up @@ -90,43 +94,64 @@ 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
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,
).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 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 @@ -24,7 +24,7 @@ import org.domaframework.doma.intellij.inspection.sql.inspector.SqlLoopDirective
* A test that inspects whether a bind variable's parameters are defined.
*/
class ParameterDefinedTest : DomaSqlTest() {
private val testDaoName = "EmployeeSummaryDao"
private val testDaoName = "inspection/ParamDefinedTestDao"

override fun setUp() {
super.setUp()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* 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.sql

import org.domaframework.doma.intellij.DomaSqlTest
import org.domaframework.doma.intellij.inspection.sql.inspector.SqlBindVariableInspection
import org.domaframework.doma.intellij.inspection.sql.inspector.SqlFunctionCallInspection
import org.domaframework.doma.intellij.inspection.sql.inspector.SqlLoopDirectiveTypeInspection

/**
* Test cases for error highlighting when specific parameter types are used within SQL files.
*
* This test class verifies that the inspection correctly identifies and highlights errors
* when method parameters of specific types (such as Optional, List, Entity classes, etc.)
* are referenced in SQL bind variables and directives.
*/
class SpecificParameterTypeDefinedTest : DomaSqlTest() {
private val testDaoName = "inspection/option/SpecificParamTypeBindVariableInspectionTestDao"

override fun setUp() {
super.setUp()
addDaoJavaFile("$testDaoName.java")
addOtherJavaFile("function", "HogeFunction.java")
addOtherJavaFile("function", "HogeBiFunction.java")
addOtherJavaFile("option", "HogeSelectOptions.java")
addOtherJavaFile("collector", "HogeCollector.java")
myFixture.enableInspections(
SqlBindVariableInspection(),
SqlLoopDirectiveTypeInspection(),
SqlFunctionCallInspection(),
)
}

fun testSelectOptions() {
val sqlFileName = "$testDaoName/selectSelectOption.sql"
inspectionSql(sqlFileName)
}

fun testSubTypeSelectOption() {
val sqlFileName = "$testDaoName/selectSubTypeSelectOption.sql"
inspectionSql(sqlFileName)
}

fun testCollector() {
val sqlFileName = "$testDaoName/selectCollector.sql"
inspectionSql(sqlFileName)
}

fun testSubTypeCollector() {
val sqlFileName = "$testDaoName/selectSubTypeCollector.sql"
inspectionSql(sqlFileName)
}

fun testFunction() {
val sqlFileName = "$testDaoName/selectFunction.sql"
inspectionSql(sqlFileName)
}

fun testSubTypeFunction() {
val sqlFileName = "$testDaoName/selectSubTypeFunction.sql"
inspectionSql(sqlFileName)
}

fun testBiFunction() {
val sqlFileName = "$testDaoName/executeBiFunction.sql"
inspectionSql(sqlFileName)
}

fun testSubTypeBiFunction() {
val sqlFileName = "$testDaoName/executeSubTypeBiFunction.sql"
inspectionSql(sqlFileName)
}

private fun inspectionSql(sqlFileName: String) {
addSqlFile(sqlFileName)
val sqlFile = findSqlFile(sqlFileName)
assertNotNull("Not Found SQL File", sqlFile)
if (sqlFile == null) return

myFixture.testHighlighting(false, false, false, sqlFile)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package doma.example.dao;
package doma.example.dao.inspection;

import doma.example.entity.*:
import org.seasar.doma.*;
Expand All @@ -11,7 +11,7 @@
import java.util.function.BiFunction;

@Dao
interface EmployeeSummaryDao {
interface ParamDefinedTestDao {

@Select
EmployeeSummary bindVariableForNonEntityClass(EmployeeSummary employee, User user);
Expand Down
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);
}
Loading
Loading