Skip to content

Commit adb9df5

Browse files
authored
Merge pull request #274 from domaframework/chore/dao-method-inspections
Refactoring dao method inspections
2 parents 7bd4492 + 4c0b4c2 commit adb9df5

26 files changed

+276
-178
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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.util
17+
18+
import com.intellij.codeInsight.AnnotationUtil
19+
import com.intellij.openapi.project.Project
20+
import com.intellij.psi.PsiClassType
21+
import com.intellij.psi.PsiType
22+
import org.domaframework.doma.intellij.common.psi.PsiTypeChecker
23+
import org.domaframework.doma.intellij.extension.getJavaClazz
24+
import org.domaframework.doma.intellij.extension.psi.getClassAnnotation
25+
import org.domaframework.doma.intellij.extension.psi.isDomain
26+
import org.domaframework.doma.intellij.extension.psi.isEntity
27+
28+
object TypeUtil {
29+
/**
30+
* Unwraps the type parameter from Optional if present, otherwise returns the original type.
31+
*/
32+
fun unwrapOptional(type: PsiType?): PsiType? {
33+
if (type == null) return null
34+
if (DomaClassName.OPTIONAL.isTargetClassNameStartsWith(type.canonicalText)) {
35+
val classType = type as? PsiClassType
36+
return classType?.parameters?.firstOrNull()
37+
}
38+
return type
39+
}
40+
41+
/**
42+
* Checks if the given type is an entity.
43+
*/
44+
fun isEntity(
45+
type: PsiType?,
46+
project: Project,
47+
): Boolean {
48+
val clazz = type?.canonicalText?.let { project.getJavaClazz(it) }
49+
return clazz?.isEntity() == true
50+
}
51+
52+
fun isImmutableEntity(
53+
project: Project,
54+
canonicalText: String,
55+
): Boolean {
56+
val returnTypeClass = project.getJavaClazz(canonicalText)
57+
val entity =
58+
returnTypeClass?.getClassAnnotation(DomaClassName.ENTITY.className) ?: return false
59+
return entity.let { entity ->
60+
AnnotationUtil.getBooleanAttributeValue(entity, "immutable") == true
61+
} == true ||
62+
returnTypeClass.isRecord == true
63+
}
64+
65+
/**
66+
* Checks if the given type is a domain.
67+
*/
68+
fun isDomain(
69+
type: PsiType?,
70+
project: Project,
71+
): Boolean {
72+
val clazz = type?.canonicalText?.let { project.getJavaClazz(it) }
73+
return clazz?.isDomain() == true
74+
}
75+
76+
/**
77+
* Checks if the given type is a valid Map<String, Object>.
78+
*/
79+
fun isValidMapType(type: PsiType?): Boolean {
80+
val canonical = type?.canonicalText?.replace(" ", "") ?: return false
81+
val expected =
82+
DomaClassName.MAP
83+
.getGenericParamCanonicalText(
84+
DomaClassName.STRING.className,
85+
DomaClassName.OBJECT.className,
86+
).replace(" ", "")
87+
return canonical == expected
88+
}
89+
90+
/**
91+
* Checks if the given type is a base class type or an optional wrapper type.
92+
*/
93+
fun isBaseOrOptionalWrapper(type: PsiType?): Boolean {
94+
if (type == null) return false
95+
return PsiTypeChecker.isBaseClassType(type) || DomaClassName.isOptionalWrapperType(type.canonicalText)
96+
}
97+
}

src/main/kotlin/org/domaframework/doma/intellij/common/validation/result/ValidationMethodParamsIterableEntityResult.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import org.domaframework.doma.intellij.bundle.MessageBundle
2222
import org.domaframework.doma.intellij.common.psi.PsiParentClass
2323

2424
class ValidationMethodParamsIterableEntityResult(
25-
override val identify: PsiElement?,
25+
override val identify: PsiElement,
2626
override val shortName: String = "",
2727
) : ValidationResult(identify, null, shortName) {
2828
override fun setHighlight(

src/main/kotlin/org/domaframework/doma/intellij/common/validation/result/ValidationMethodSelectStrategyReturnTypeResult.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ import org.domaframework.doma.intellij.common.psi.PsiParentClass
2424
class ValidationMethodSelectStrategyReturnTypeResult(
2525
override val identify: PsiElement?,
2626
override val shortName: String = "",
27-
private val resultType: String,
27+
private val matchResultType: String,
28+
private val paramType: String,
2829
) : ValidationResult(identify, null, shortName) {
2930
override fun setHighlight(
3031
highlightRange: TextRange,
@@ -37,7 +38,8 @@ class ValidationMethodSelectStrategyReturnTypeResult(
3738
identify,
3839
MessageBundle.message(
3940
"inspection.invalid.dao.select.returnType.strategy",
40-
resultType,
41+
matchResultType,
42+
paramType,
4143
),
4244
problemHighlightType(project, shortName),
4345
highlightRange,

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@
1515
*/
1616
package org.domaframework.doma.intellij.inspection.dao.processor
1717

18+
/**
19+
* Enum representing factory annotation types for Doma DAO methods.
20+
*
21+
* Each entry defines the fully qualified annotation name, the expected return type,
22+
* and the required parameter count for the factory method.
23+
*
24+
* @property fqdn The fully qualified name of the annotation.
25+
* @property returnType The expected return type for the factory.
26+
* @property paramCount The number of parameters required by the factory method.
27+
*/
1828
enum class FactoryAnnotationType(
1929
val fqdn: String,
2030
val returnType: String,

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,30 @@ package org.domaframework.doma.intellij.inspection.dao.processor
1717

1818
import org.domaframework.doma.intellij.common.util.DomaClassName
1919

20+
/**
21+
* Represents a strategy parameter for Doma DAO method inspections.
22+
*
23+
* Provides methods to determine if the parameter is a stream or collect type
24+
* in the context of select operations.
25+
*
26+
* @property fieldName The name of the field.
27+
* @property isSelectType True if the parent class is a select type.
28+
*/
2029
class StrategyParam(
2130
val fieldName: String = "",
2231
parentClassName: String?,
2332
) {
2433
private val isSelectType: Boolean = parentClassName == DomaClassName.SELECT_TYPE.className
2534

35+
/**
36+
* Checks if the parameter represents a stream type.
37+
* @return True if the field is STREAM and the parent is select type.
38+
*/
2639
fun isStream(): Boolean = fieldName == "STREAM" && isSelectType
2740

41+
/**
42+
* Checks if the parameter represents a collect type.
43+
* @return True if the field is COLLECT and the parent is select type.
44+
*/
2845
fun isCollect(): Boolean = fieldName == "COLLECT" && isSelectType
2946
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ import org.domaframework.doma.intellij.extension.getJavaClazz
2828
import org.domaframework.doma.intellij.extension.psi.getSuperType
2929
import org.domaframework.doma.intellij.extension.psi.isDomain
3030

31+
/**
32+
* Abstract base class for type checking processors in DAO inspections.
33+
*
34+
* Provides utility methods for retrieving annotations, parameters, and checking types
35+
* in the context of Doma DAO method inspections.
36+
*
37+
* @property method The inspected DAO method.
38+
* @property project The IntelliJ project instance.
39+
*/
3140
abstract class TypeCheckerProcessor(
3241
psiDaoMethod: PsiDaoMethod,
3342
) {

src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/processor/paramtype/BatchParamTypeCheckProcessor.kt

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ import com.intellij.codeInspection.ProblemsHolder
1919
import com.intellij.psi.PsiClassType
2020
import org.domaframework.doma.intellij.common.psi.PsiDaoMethod
2121
import org.domaframework.doma.intellij.common.sql.PsiClassTypeUtil
22+
import org.domaframework.doma.intellij.common.util.TypeUtil
2223
import org.domaframework.doma.intellij.common.validation.result.ValidationMethodParamsCountResult
2324
import org.domaframework.doma.intellij.common.validation.result.ValidationMethodParamsIterableEntityResult
2425
import org.domaframework.doma.intellij.extension.getJavaClazz
25-
import org.domaframework.doma.intellij.extension.psi.isEntity
2626
import org.domaframework.doma.intellij.extension.psi.psiClassType
2727

2828
/**
@@ -86,13 +86,9 @@ class BatchParamTypeCheckProcessor(
8686

8787
val iterableClassType = param.type as? PsiClassType
8888
iterableClassType?.parameters?.firstOrNull()?.let { iterableParam ->
89-
project
90-
.getJavaClazz(iterableParam.canonicalText)
91-
?.let {
92-
if (!it.isEntity()) {
93-
resultParamType.highlightElement(holder)
94-
}
95-
}
89+
if (!TypeUtil.isEntity(iterableParam, project)) {
90+
resultParamType.highlightElement(holder)
91+
}
9692
return
9793
}
9894
resultParamType.highlightElement(holder)

src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/processor/paramtype/ProcedureParamTypeCheckProcessor.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ class ProcedureParamTypeCheckProcessor(
6969
/**
7070
* Validates the type of parameter based on its annotation.
7171
*
72+
* Checks if the parameter type is valid for the given annotation type in the context of the DAO method.
73+
*
74+
* @param psiDaoMethod The DAO method containing the parameter.
7275
* @param paramAnnotationType The type of the annotation on the parameter.
7376
* @param param The parameter to be validated.
7477
* @param holder The ProblemsHolder instance used to report validation issues.

src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/processor/paramtype/SelectParamTypeCheckProcessor.kt

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import org.domaframework.doma.intellij.common.validation.result.ValidationMethod
2626
import org.domaframework.doma.intellij.common.validation.result.ValidationMethodParamsSupportGenericParamResult
2727
import org.domaframework.doma.intellij.common.validation.result.ValidationMethodSelectStrategyParamResult
2828
import org.domaframework.doma.intellij.extension.getJavaClazz
29+
import org.domaframework.doma.intellij.extension.psi.getSuperClassType
2930
import org.domaframework.doma.intellij.extension.psi.isDomain
3031
import org.domaframework.doma.intellij.extension.psi.isEntity
3132
import org.domaframework.doma.intellij.inspection.dao.processor.StrategyParam
@@ -99,19 +100,11 @@ class SelectParamTypeCheckProcessor(
99100
return
100101
}
101102

102-
val functionType = function.type
103103
val identifier = function.nameIdentifier ?: return
104104

105105
// Check if the first parameter of the function is a stream type
106-
val functionClass = project.getJavaClazz(functionType.canonicalText) ?: return
107-
var superCollection: PsiClassType? = functionType as PsiClassType?
108-
while (superCollection != null &&
109-
!DomaClassName.JAVA_FUNCTION.isTargetClassNameStartsWith(superCollection.canonicalText)
110-
) {
111-
superCollection =
112-
functionClass.superTypes
113-
.find { sp -> DomaClassName.JAVA_FUNCTION.isTargetClassNameStartsWith(sp.canonicalText) }
114-
}
106+
val targetType = DomaClassName.JAVA_FUNCTION
107+
var superCollection: PsiClassType? = function.getSuperClassType(targetType)
115108

116109
val functionFirstParam = superCollection?.parameters?.firstOrNull()
117110
if (functionFirstParam == null ||
@@ -157,19 +150,10 @@ class SelectParamTypeCheckProcessor(
157150
return
158151
}
159152

160-
val collectionType = collection.type
161153
val identifier = collection.nameIdentifier ?: return
162154

163-
val collectionClass = project.getJavaClazz(collectionType.canonicalText) ?: return
164-
var superCollection: PsiClassType? = collection.type as? PsiClassType
165-
while (superCollection != null &&
166-
!DomaClassName.JAVA_COLLECTOR.isTargetClassNameStartsWith(superCollection.canonicalText)
167-
) {
168-
superCollection =
169-
collectionClass.superTypes
170-
.find { sp -> DomaClassName.JAVA_COLLECTOR.isTargetClassNameStartsWith(sp.canonicalText) }
171-
}
172-
155+
val targetType = DomaClassName.JAVA_COLLECTOR
156+
var superCollection: PsiClassType? = collection.getSuperClassType(targetType)
173157
val collectorTargetParam = superCollection?.parameters?.firstOrNull()
174158
if (collectorTargetParam == null) {
175159
generateTargetTypeResult(identifier, "Unknown", collector).highlightElement(holder)

src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/processor/paramtype/SqlProcessorParamTypeCheckProcessor.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,7 @@ class SqlProcessorParamTypeCheckProcessor(
5151
* @param holder The ProblemsHolder instance used to report validation issues.
5252
*/
5353
override fun checkParams(holder: ProblemsHolder) {
54-
val parameters = method.parameterList.parameters
55-
val biFunctionParam =
56-
parameters.firstOrNull { param ->
57-
param.type.canonicalText.startsWith(biFunctionClassName)
58-
}
54+
val biFunctionParam = getMethodParamTargetType(biFunctionClassName)
5955
if (biFunctionParam == null) {
6056
ValidationMethodHasRequireClassParamResult(
6157
method.nameIdentifier,

0 commit comments

Comments
 (0)