Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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,97 @@
/*
* 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.util

import com.intellij.codeInsight.AnnotationUtil
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiClassType
import com.intellij.psi.PsiType
import org.domaframework.doma.intellij.common.psi.PsiTypeChecker
import org.domaframework.doma.intellij.extension.getJavaClazz
import org.domaframework.doma.intellij.extension.psi.getClassAnnotation
import org.domaframework.doma.intellij.extension.psi.isDomain
import org.domaframework.doma.intellij.extension.psi.isEntity

object TypeUtil {
/**
* Unwraps the type parameter from Optional if present, otherwise returns the original type.
*/
fun unwrapOptional(type: PsiType?): PsiType? {
if (type == null) return null
if (DomaClassName.OPTIONAL.isTargetClassNameStartsWith(type.canonicalText)) {
val classType = type as? PsiClassType
return classType?.parameters?.firstOrNull()
}
return type
}

/**
* Checks if the given type is an entity.
*/
fun isEntity(
type: PsiType?,
project: Project,
): Boolean {
val clazz = type?.canonicalText?.let { project.getJavaClazz(it) }
return clazz?.isEntity() == true
}

fun isImmutableEntity(
project: Project,
canonicalText: String,
): Boolean {
val returnTypeClass = project.getJavaClazz(canonicalText)
val entity =
returnTypeClass?.getClassAnnotation(DomaClassName.ENTITY.className) ?: return false
return entity.let { entity ->
AnnotationUtil.getBooleanAttributeValue(entity, "immutable") == true
} == true ||
returnTypeClass.isRecord == true
}

/**
* Checks if the given type is a domain.
*/
fun isDomain(
type: PsiType?,
project: Project,
): Boolean {
val clazz = type?.canonicalText?.let { project.getJavaClazz(it) }
return clazz?.isDomain() == true
}

/**
* Checks if the given type is a valid Map<String, Object>.
*/
fun isValidMapType(type: PsiType?): Boolean {
val canonical = type?.canonicalText?.replace(" ", "") ?: return false
val expected =
DomaClassName.MAP
.getGenericParamCanonicalText(
DomaClassName.STRING.className,
DomaClassName.OBJECT.className,
).replace(" ", "")
return canonical == expected
}

/**
* Checks if the given type is a base class type or an optional wrapper type.
*/
fun isBaseOrOptionalWrapper(type: PsiType?): Boolean {
if (type == null) return false
return PsiTypeChecker.isBaseClassType(type) || DomaClassName.isOptionalWrapperType(type.canonicalText)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import org.domaframework.doma.intellij.bundle.MessageBundle
import org.domaframework.doma.intellij.common.psi.PsiParentClass

class ValidationMethodParamsIterableEntityResult(
override val identify: PsiElement?,
override val identify: PsiElement,
override val shortName: String = "",
) : ValidationResult(identify, null, shortName) {
override fun setHighlight(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import org.domaframework.doma.intellij.common.psi.PsiParentClass
class ValidationMethodSelectStrategyReturnTypeResult(
override val identify: PsiElement?,
override val shortName: String = "",
private val resultType: String,
private val matchResultType: String,
private val paramType: String,
) : ValidationResult(identify, null, shortName) {
override fun setHighlight(
highlightRange: TextRange,
Expand All @@ -37,7 +38,8 @@ class ValidationMethodSelectStrategyReturnTypeResult(
identify,
MessageBundle.message(
"inspection.invalid.dao.select.returnType.strategy",
resultType,
matchResultType,
paramType,
),
problemHighlightType(project, shortName),
highlightRange,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@
*/
package org.domaframework.doma.intellij.inspection.dao.processor

/**
* Enum representing factory annotation types for Doma DAO methods.
*
* Each entry defines the fully qualified annotation name, the expected return type,
* and the required parameter count for the factory method.
*
* @property fqdn The fully qualified name of the annotation.
* @property returnType The expected return type for the factory.
* @property paramCount The number of parameters required by the factory method.
*/
enum class FactoryAnnotationType(
val fqdn: String,
val returnType: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,30 @@ package org.domaframework.doma.intellij.inspection.dao.processor

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

/**
* Represents a strategy parameter for Doma DAO method inspections.
*
* Provides methods to determine if the parameter is a stream or collect type
* in the context of select operations.
*
* @property fieldName The name of the field.
* @property isSelectType True if the parent class is a select type.
*/
class StrategyParam(
val fieldName: String = "",
parentClassName: String?,
) {
private val isSelectType: Boolean = parentClassName == DomaClassName.SELECT_TYPE.className

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

/**
* Checks if the parameter represents a collect type.
* @return True if the field is COLLECT and the parent is select type.
*/
fun isCollect(): Boolean = fieldName == "COLLECT" && isSelectType
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ import org.domaframework.doma.intellij.extension.getJavaClazz
import org.domaframework.doma.intellij.extension.psi.getSuperType
import org.domaframework.doma.intellij.extension.psi.isDomain

/**
* Abstract base class for type checking processors in DAO inspections.
*
* Provides utility methods for retrieving annotations, parameters, and checking types
* in the context of Doma DAO method inspections.
*
* @property method The inspected DAO method.
* @property project The IntelliJ project instance.
*/
abstract class TypeCheckerProcessor(
psiDaoMethod: PsiDaoMethod,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiClassType
import org.domaframework.doma.intellij.common.psi.PsiDaoMethod
import org.domaframework.doma.intellij.common.sql.PsiClassTypeUtil
import org.domaframework.doma.intellij.common.util.TypeUtil
import org.domaframework.doma.intellij.common.validation.result.ValidationMethodParamsCountResult
import org.domaframework.doma.intellij.common.validation.result.ValidationMethodParamsIterableEntityResult
import org.domaframework.doma.intellij.extension.getJavaClazz
import org.domaframework.doma.intellij.extension.psi.isEntity
import org.domaframework.doma.intellij.extension.psi.psiClassType

/**
Expand Down Expand Up @@ -86,13 +86,9 @@ class BatchParamTypeCheckProcessor(

val iterableClassType = param.type as? PsiClassType
iterableClassType?.parameters?.firstOrNull()?.let { iterableParam ->
project
.getJavaClazz(iterableParam.canonicalText)
?.let {
if (!it.isEntity()) {
resultParamType.highlightElement(holder)
}
}
if (!TypeUtil.isEntity(iterableParam, project)) {
resultParamType.highlightElement(holder)
}
return
}
resultParamType.highlightElement(holder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ class ProcedureParamTypeCheckProcessor(
/**
* Validates the type of parameter based on its annotation.
*
* Checks if the parameter type is valid for the given annotation type in the context of the DAO method.
*
* @param psiDaoMethod The DAO method containing the parameter.
* @param paramAnnotationType The type of the annotation on the parameter.
* @param param The parameter to be validated.
* @param holder The ProblemsHolder instance used to report validation issues.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import org.domaframework.doma.intellij.common.validation.result.ValidationMethod
import org.domaframework.doma.intellij.common.validation.result.ValidationMethodParamsSupportGenericParamResult
import org.domaframework.doma.intellij.common.validation.result.ValidationMethodSelectStrategyParamResult
import org.domaframework.doma.intellij.extension.getJavaClazz
import org.domaframework.doma.intellij.extension.psi.getSuperClassType
import org.domaframework.doma.intellij.extension.psi.isDomain
import org.domaframework.doma.intellij.extension.psi.isEntity
import org.domaframework.doma.intellij.inspection.dao.processor.StrategyParam
Expand Down Expand Up @@ -99,19 +100,11 @@ class SelectParamTypeCheckProcessor(
return
}

val functionType = function.type
val identifier = function.nameIdentifier ?: return

// Check if the first parameter of the function is a stream type
val functionClass = project.getJavaClazz(functionType.canonicalText) ?: return
var superCollection: PsiClassType? = functionType as PsiClassType?
while (superCollection != null &&
!DomaClassName.JAVA_FUNCTION.isTargetClassNameStartsWith(superCollection.canonicalText)
) {
superCollection =
functionClass.superTypes
.find { sp -> DomaClassName.JAVA_FUNCTION.isTargetClassNameStartsWith(sp.canonicalText) }
}
val targetType = DomaClassName.JAVA_FUNCTION
var superCollection: PsiClassType? = function.getSuperClassType(targetType)

val functionFirstParam = superCollection?.parameters?.firstOrNull()
if (functionFirstParam == null ||
Expand Down Expand Up @@ -157,19 +150,10 @@ class SelectParamTypeCheckProcessor(
return
}

val collectionType = collection.type
val identifier = collection.nameIdentifier ?: return

val collectionClass = project.getJavaClazz(collectionType.canonicalText) ?: return
var superCollection: PsiClassType? = collection.type as? PsiClassType
while (superCollection != null &&
!DomaClassName.JAVA_COLLECTOR.isTargetClassNameStartsWith(superCollection.canonicalText)
) {
superCollection =
collectionClass.superTypes
.find { sp -> DomaClassName.JAVA_COLLECTOR.isTargetClassNameStartsWith(sp.canonicalText) }
}

val targetType = DomaClassName.JAVA_COLLECTOR
var superCollection: PsiClassType? = collection.getSuperClassType(targetType)
val collectorTargetParam = superCollection?.parameters?.firstOrNull()
if (collectorTargetParam == null) {
generateTargetTypeResult(identifier, "Unknown", collector).highlightElement(holder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,7 @@ class SqlProcessorParamTypeCheckProcessor(
* @param holder The ProblemsHolder instance used to report validation issues.
*/
override fun checkParams(holder: ProblemsHolder) {
val parameters = method.parameterList.parameters
val biFunctionParam =
parameters.firstOrNull { param ->
param.type.canonicalText.startsWith(biFunctionClassName)
}
val biFunctionParam = getMethodParamTargetType(biFunctionClassName)
if (biFunctionParam == null) {
ValidationMethodHasRequireClassParamResult(
method.nameIdentifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ package org.domaframework.doma.intellij.inspection.dao.processor.paramtype

import com.intellij.codeInspection.ProblemsHolder
import org.domaframework.doma.intellij.common.psi.PsiDaoMethod
import org.domaframework.doma.intellij.common.util.TypeUtil
import org.domaframework.doma.intellij.common.validation.result.ValidationMethodParamTypeResult
import org.domaframework.doma.intellij.extension.getJavaClazz
import org.domaframework.doma.intellij.extension.psi.isEntity

/**
* Processor for checking the parameter types of DAO methods annotated with @Insert,@Update,@Delete.
Expand Down Expand Up @@ -58,11 +57,9 @@ class UpdateParamTypeCheckProcessor(

// Check if the method has a parameter of type entity
val param = method.parameterList.parameters.firstOrNull()
val paramClass = project.getJavaClazz(param?.type?.canonicalText ?: "")
val identifier = param?.nameIdentifier ?: return
if (paramClass == null || !paramClass.isEntity()) {
if (!TypeUtil.isEntity(param?.type, project)) {
ValidationMethodParamTypeResult(
identifier,
param?.nameIdentifier,
shortName,
"an entity",
).highlightElement(holder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import com.intellij.psi.PsiTypes
import com.intellij.psi.impl.source.PsiClassReferenceType
import org.domaframework.doma.intellij.common.psi.PsiDaoMethod
import org.domaframework.doma.intellij.common.sql.PsiClassTypeUtil
import org.domaframework.doma.intellij.common.util.TypeUtil
import org.domaframework.doma.intellij.common.validation.result.ValidationResult
import org.domaframework.doma.intellij.extension.getJavaClazz

/**
* Processor for checking the return type of batch annotations.
Expand All @@ -43,25 +43,23 @@ class BatchReturnTypeCheckProcessor(
val parameters = method.parameterList.parameters
val immutableEntityParam = parameters.firstOrNull() ?: return null

val convertOptional = PsiClassTypeUtil.convertOptionalType(immutableEntityParam.type, project)
val convertOptional =
PsiClassTypeUtil.convertOptionalType(immutableEntityParam.type, project)
val parameterType = convertOptional as PsiClassReferenceType
val nestPsiType = parameterType.reference.typeParameters.firstOrNull() ?: return null
val nestClass: PsiType = PsiClassTypeUtil.convertOptionalType(nestPsiType, project)

if (psiDaoMethod.useSqlAnnotation() || psiDaoMethod.sqlFileOption) {
nestClass.let { methodParam ->
val paramTypeName = methodParam.canonicalText
project.getJavaClazz(paramTypeName)?.let {
if (isImmutableEntity(paramTypeName)) {
return checkReturnTypeImmutableEntity(nestClass)
}
if (TypeUtil.isImmutableEntity(project, methodParam.canonicalText)) {
return checkReturnTypeImmutableEntity(nestClass)
}
}
return generatePsiTypeReturnTypeResult(methodOtherReturnType)
}

// Check if it has an immutable entity parameter
if (isImmutableEntity(nestClass.canonicalText)) {
if (TypeUtil.isImmutableEntity(project, nestClass.canonicalText)) {
return checkReturnTypeImmutableEntity(nestClass)
}

Expand Down
Loading
Loading