diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationDaoParamResult.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationDaoParamResult.kt index e22d0355..6ff281ba 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationDaoParamResult.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationDaoParamResult.kt @@ -41,7 +41,7 @@ open class ValidationDaoParamResult( holder.registerProblem( identify, MessageBundle.message( - "inspector.invalid.dao.parameter", + "inspection.invalid.dao.parameter", daoName, identify.text ?: "", ), diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationForDirectiveItemTypeResult.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationForDirectiveItemTypeResult.kt new file mode 100644 index 00000000..827b0a0b --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationForDirectiveItemTypeResult.kt @@ -0,0 +1,44 @@ +/* + * 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.sql.validator.result + +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project +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 + +class ValidationForDirectiveItemTypeResult( + override val identify: PsiElement?, + override val shortName: String = "", +) : ValidationResult(identify, null, shortName) { + override fun setHighlight( + highlightRange: TextRange, + identify: PsiElement, + holder: ProblemsHolder, + parent: PsiParentClass?, + project: Project, + ) { + val project = identify.project + holder.registerProblem( + identify, + MessageBundle.message("inspection.invalid.sql.iterable"), + problemHighlightType(project, shortName), + highlightRange, + ) + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationNotFoundStaticPropertyResult.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationNotFoundStaticPropertyResult.kt new file mode 100644 index 00000000..96334044 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationNotFoundStaticPropertyResult.kt @@ -0,0 +1,50 @@ +/* + * 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.sql.validator.result + +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project +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 +import org.domaframework.doma.intellij.psi.SqlElClass + +class ValidationNotFoundStaticPropertyResult( + override val identify: PsiElement?, + val clazz: SqlElClass, + override val shortName: String = "", +) : ValidationResult(identify, null, shortName) { + override fun setHighlight( + highlightRange: TextRange, + identify: PsiElement, + holder: ProblemsHolder, + parent: PsiParentClass?, + project: Project, + ) { + val project = identify.project + holder.registerProblem( + identify, + MessageBundle.message( + "inspection.invalid.sql.staticProperty", + identify.text, + clazz.text, + ), + problemHighlightType(project, shortName), + highlightRange, + ) + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationNotFoundTopTypeResult.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationNotFoundTopTypeResult.kt new file mode 100644 index 00000000..550bf481 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationNotFoundTopTypeResult.kt @@ -0,0 +1,44 @@ +/* + * 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.sql.validator.result + +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project +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 + +class ValidationNotFoundTopTypeResult( + override val identify: PsiElement?, + override val shortName: String = "", +) : ValidationResult(identify, null, shortName) { + override fun setHighlight( + highlightRange: TextRange, + identify: PsiElement, + holder: ProblemsHolder, + parent: PsiParentClass?, + project: Project, + ) { + val project = identify.project + holder.registerProblem( + identify, + MessageBundle.message("inspection.invalid.sql.topType"), + problemHighlightType(project, shortName), + highlightRange, + ) + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationPropertyResult.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationPropertyResult.kt index 13eb6208..55fb1b55 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationPropertyResult.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationPropertyResult.kt @@ -46,7 +46,7 @@ class ValidationPropertyResult( holder.registerProblem( identify, MessageBundle.message( - "inspector.invalid.class.property", + "inspection.invalid.sql.property", parentName, identify.text ?: "", ), diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/util/ForDirectiveUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/util/ForDirectiveUtil.kt index c73fab47..c8b1ac0d 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/util/ForDirectiveUtil.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/util/ForDirectiveUtil.kt @@ -19,6 +19,7 @@ import com.intellij.openapi.project.Project import com.intellij.psi.PsiClass import com.intellij.psi.PsiClassType import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod import com.intellij.psi.PsiType import com.intellij.psi.PsiTypes import com.intellij.psi.util.CachedValue @@ -34,6 +35,7 @@ import org.domaframework.doma.intellij.common.sql.PsiClassTypeUtil import org.domaframework.doma.intellij.common.sql.cleanString import org.domaframework.doma.intellij.common.sql.foritem.ForItem import org.domaframework.doma.intellij.common.sql.validator.result.ValidationCompleteResult +import org.domaframework.doma.intellij.common.sql.validator.result.ValidationNotFoundTopTypeResult import org.domaframework.doma.intellij.common.sql.validator.result.ValidationPropertyResult import org.domaframework.doma.intellij.common.sql.validator.result.ValidationResult import org.domaframework.doma.intellij.extension.expr.accessElements @@ -173,39 +175,77 @@ class ForDirectiveUtil { fun getForDirectiveItemClassType( project: Project, forDirectiveBlocks: List, + targetForItem: PsiElement? = null, ): PsiParentClass? { // Get the type of the top for directive definition element // Defined in Dao parameters or static property calls if (forDirectiveBlocks.isEmpty()) return null + val topDirectiveItem = forDirectiveBlocks.first().item + val file = topDirectiveItem.containingFile ?: return null + val daoMethod = findDaoMethod(file) + var parentClassType = getTopForDirectiveDeclarationClassType( forDirectiveBlocks.first().item, project, - ) ?: return null + daoMethod, + ) forDirectiveBlocks.drop(1).forEach { directive -> // Get the definition type of the target directive val formItem = ForItem(directive.item) + if (targetForItem != null && formItem.element.textOffset > targetForItem.textOffset) { + return parentClassType + } val forDirectiveExpr = formItem.getParentForDirectiveExpr() val forDirectiveDeclaration = forDirectiveExpr?.getForItemDeclaration() if (forDirectiveDeclaration != null) { - val forItemDeclarationBlocks = forDirectiveDeclaration.getDeclarationChildren() - getFieldAccessLastPropertyClassType( - forItemDeclarationBlocks, - project, - parentClassType, - complete = { lastType -> - val classType = lastType.type as? PsiClassType - val nestClass = - if (classType != null && - PsiClassTypeUtil.Companion.isIterableType(classType, project) - ) { - classType.parameters.firstOrNull() - } else { - null - } - nestClass?.let { parentClassType = PsiParentClass(it) } - }, - ) + val declarationTopElement = + forDirectiveDeclaration.getDeclarationChildren().first() + val findDeclarationForItem = + findForItem(declarationTopElement, forDirectives = forDirectiveBlocks) + + if (findDeclarationForItem == null && daoMethod != null) { + val matchParam = daoMethod.findParameter(declarationTopElement.text) + if (matchParam != null) { + val convertOptional = + PsiClassTypeUtil.convertOptionalType(matchParam.type, project) + parentClassType = PsiParentClass(convertOptional) + } + } + if (parentClassType != null) { + val isBatchAnnotation = daoMethod?.let { PsiDaoMethod(project, it).daoType.isBatchAnnotation() } == true + val forItemDeclarationBlocks = + forDirectiveDeclaration.getDeclarationChildren() + getFieldAccessLastPropertyClassType( + forItemDeclarationBlocks, + project, + parentClassType, + isBatchAnnotation = isBatchAnnotation, + complete = { lastType -> + val classType = lastType.type as? PsiClassType + val nestClass = + if (classType != null && + PsiClassTypeUtil.Companion.isIterableType( + classType, + project, + ) + ) { + classType.parameters.firstOrNull() + } else { + null + } + parentClassType = + if (nestClass != null) { + PsiParentClass(nestClass) + } else { + null + } + }, + ) + if (targetForItem != null && formItem.element.text == targetForItem.text) { + return parentClassType + } + } } } @@ -215,6 +255,7 @@ class ForDirectiveUtil { fun getTopForDirectiveDeclarationClassType( topForDirectiveItem: PsiElement, project: Project, + daoMethod: PsiMethod?, ): PsiParentClass? { var result: PsiParentClass? = null var fieldAccessTopParentClass: PsiParentClass? = null @@ -250,8 +291,8 @@ class ForDirectiveUtil { ) } else { // Defined by Dao parameter - val file = topForDirectiveItem.containingFile ?: return null - val daoMethod = findDaoMethod(file) ?: return null + if (daoMethod == null) return null + val topElementText = forDirectiveDeclaration.getDeclarationChildren().firstOrNull()?.text ?: return null @@ -337,9 +378,10 @@ class ForDirectiveUtil { val convertOptional = PsiClassTypeUtil.convertOptionalType(topParent.type, project) PsiParentClass(convertOptional) } - // TODO: Display an error message that the property cannot be called. val parentType = PsiClassTypeUtil.convertOptionalType(parent.type, project) - val classType = parentType as? PsiClassType ?: return null + val classType = + parentType as? PsiClassType + ?: return ValidationNotFoundTopTypeResult(blocks.first(), shortName) var competeResult: ValidationCompleteResult? = null diff --git a/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt b/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt index f80a9047..8809cf0f 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt @@ -421,9 +421,9 @@ class SqlParameterCompletionProvider : CompletionProvider( ): Boolean { val project = top.project val forDirectiveBlocks = ForDirectiveUtil.getForDirectiveBlocks(top) - ForDirectiveUtil.findForItem(top, forDirectives = forDirectiveBlocks) ?: return false + val forItem = ForDirectiveUtil.findForItem(top, forDirectives = forDirectiveBlocks) ?: return false - val forItemClassType = ForDirectiveUtil.getForDirectiveItemClassType(project, forDirectiveBlocks) ?: return false + val forItemClassType = ForDirectiveUtil.getForDirectiveItemClassType(project, forDirectiveBlocks, forItem) ?: return false val specifiedClassType = ForDirectiveUtil.resolveForDirectiveItemClassTypeBySuffixElement(top.text) val topClassType = if (specifiedClassType != null) { diff --git a/src/main/kotlin/org/domaframework/doma/intellij/document/generator/DocumentDaoParameterGenerator.kt b/src/main/kotlin/org/domaframework/doma/intellij/document/generator/DocumentDaoParameterGenerator.kt index 20333ef7..348c28b4 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/document/generator/DocumentDaoParameterGenerator.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/document/generator/DocumentDaoParameterGenerator.kt @@ -45,8 +45,9 @@ class DocumentDaoParameterGenerator( val searchElement = fieldAccessBlocks?.firstOrNull() ?: originalElement var isBatchAnnotation = false - if (ForDirectiveUtil.findForItem(searchElement, forDirectives = forDirectives) != null) { - val forItemClassType = ForDirectiveUtil.getForDirectiveItemClassType(project, forDirectives) + val forItem = ForDirectiveUtil.findForItem(searchElement, forDirectives = forDirectives) + if (forItem != null) { + val forItemClassType = ForDirectiveUtil.getForDirectiveItemClassType(project, forDirectives, forItem) val specifiedClassType = ForDirectiveUtil.resolveForDirectiveItemClassTypeBySuffixElement( searchElement.text, diff --git a/src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/inspector/DaoMethodVariableInspector.kt b/src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/inspector/DaoMethodVariableInspector.kt index 39f3a955..d8cf70a7 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/inspector/DaoMethodVariableInspector.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/inspector/DaoMethodVariableInspector.kt @@ -33,18 +33,26 @@ import org.domaframework.doma.intellij.bundle.MessageBundle import org.domaframework.doma.intellij.common.dao.getDaoClass import org.domaframework.doma.intellij.common.isJavaOrKotlinFileType import org.domaframework.doma.intellij.common.psi.PsiDaoMethod +import org.domaframework.doma.intellij.common.util.ForDirectiveUtil import org.domaframework.doma.intellij.extension.findFile +import org.domaframework.doma.intellij.extension.psi.getForItem import org.domaframework.doma.intellij.extension.psi.isCollector import org.domaframework.doma.intellij.extension.psi.isFunctionClazz import org.domaframework.doma.intellij.extension.psi.isSelectOption import org.domaframework.doma.intellij.extension.psi.methodParameters -import org.domaframework.doma.intellij.psi.SqlElStaticFieldAccessExpr +import org.domaframework.doma.intellij.psi.SqlElForDirective +import org.domaframework.doma.intellij.psi.SqlElPrimaryExpr import org.domaframework.doma.intellij.psi.SqlTypes /** * Check if Dao method arguments are used in the corresponding SQL file */ class DaoMethodVariableInspector : AbstractBaseJavaLocalInspectionTool() { + private data class DaoMethodVariableVisitorResult( + val elements: List, + val deplicateForItemElements: List, + ) + override fun getDisplayName(): String = "Method argument usage check" override fun getShortName(): String = "org.domaframework.doma.intellij.variablechecker" @@ -76,38 +84,65 @@ class DaoMethodVariableInspector : AbstractBaseJavaLocalInspectionTool() { method.project.findFile(it) } ?: return - findElementsInSqlFile(sqlFileManager, methodParameters.toList()).forEach { arg -> - holder.registerProblem( - (arg.originalElement as PsiParameterImpl).nameIdentifier, - MessageBundle.message("inspection.dao.method.variable.error", arg.name), - ProblemHighlightType.ERROR, - ) + val params = methodParameters.toList() + val result = findElementsInSqlFile(sqlFileManager, params) + params.forEach { param -> + if (!result.elements.contains(param)) { + val message = + if (result.deplicateForItemElements.contains(param)) { + MessageBundle.message("inspection.invalid.dao.duplicate") + } else { + MessageBundle.message( + "inspection.invalid.dao.paramUse", + param.name, + ) + } + holder.registerProblem( + (param.originalElement as PsiParameterImpl).nameIdentifier, + message, + ProblemHighlightType.ERROR, + ) + } } } } } - fun findElementsInSqlFile( + private fun findElementsInSqlFile( sqlFile: PsiFile, args: List, - ): List { + ): DaoMethodVariableVisitorResult { val elements = mutableListOf() + val deplicateForItemElements = mutableListOf() var iterator: Iterator sqlFile.accept( object : PsiRecursiveElementVisitor() { // Recursively explore child elements in a file with PsiRecursiveElementVisitor. override fun visitElement(element: PsiElement) { - if (element.elementType == SqlTypes.EL_IDENTIFIER && element.prevSibling?.elementType != SqlTypes.DOT) { + if (( + element.elementType == SqlTypes.EL_IDENTIFIER || + element is SqlElPrimaryExpr + ) && + element.prevSibling?.elementType != SqlTypes.DOT + ) { iterator = args.minus(elements.toSet()).iterator() while (iterator.hasNext()) { val arg = iterator.next() - val fieldAccessExpr = - PsiTreeUtil.getParentOfType( - element, - SqlElStaticFieldAccessExpr::class.java, - ) - if (fieldAccessExpr == null && element.text == arg.name) { - elements.add(arg) + if (element.text == arg.name) { + // Check if you are in a For directive + val elementParent = PsiTreeUtil.getParentOfType(element, SqlElForDirective::class.java) + val isForItemSide = elementParent?.getForItem()?.textOffset == element.textOffset + + // Check if the element name definition source is in the for directive + val forDirectiveBlocks = + ForDirectiveUtil.getForDirectiveBlocks(element) + val forItem = ForDirectiveUtil.findForItem(element, forDirectives = forDirectiveBlocks) + + if (forItem != null || isForItemSide) { + deplicateForItemElements.add(arg) + } else { + elements.add(arg) + } break } } @@ -116,6 +151,7 @@ class DaoMethodVariableInspector : AbstractBaseJavaLocalInspectionTool() { } }, ) - return args.minus(elements.toSet()) + val result = DaoMethodVariableVisitorResult(elements, deplicateForItemElements) + return result } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/inspector/SqlFileExistInspector.kt b/src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/inspector/SqlFileExistInspector.kt index a737ec46..b1ea94d6 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/inspector/SqlFileExistInspector.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/inspector/SqlFileExistInspector.kt @@ -68,7 +68,7 @@ class SqlFileExistInspector : AbstractBaseJavaLocalInspectionTool() { if (psiDaoMethod.sqlFile == null) { problemHolder.registerProblem( identifier, - MessageBundle.message("inspection.sql.not.exist.error"), + MessageBundle.message("inspection.invalid.dao.notExistSql"), ProblemHighlightType.ERROR, GenerateSQLFileQuickFixFactory.createSql(psiDaoMethod), ) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/processor/InspectionFieldAccessVisitorProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/processor/InspectionFieldAccessVisitorProcessor.kt new file mode 100644 index 00000000..439a564d --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/processor/InspectionFieldAccessVisitorProcessor.kt @@ -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.inspection.sql.processor + +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.psi.PsiFile +import org.domaframework.doma.intellij.common.dao.findDaoMethod +import org.domaframework.doma.intellij.common.psi.PsiDaoMethod +import org.domaframework.doma.intellij.common.psi.PsiParentClass +import org.domaframework.doma.intellij.common.sql.cleanString +import org.domaframework.doma.intellij.common.util.ForDirectiveUtil +import org.domaframework.doma.intellij.extension.expr.accessElements +import org.domaframework.doma.intellij.extension.psi.findParameter +import org.domaframework.doma.intellij.psi.SqlElFieldAccessExpr +import org.domaframework.doma.intellij.psi.SqlElIdExpr +import org.domaframework.doma.intellij.psi.SqlElPrimaryExpr + +class InspectionFieldAccessVisitorProcessor( + val shortName: String, + private val element: SqlElFieldAccessExpr, +) : InspectionVisitorProcessor(shortName) { + fun check( + holder: ProblemsHolder, + file: PsiFile, + ) { + // Get element inside block comment + val blockElement = getFieldAccessBlocks(element) + val topElm = blockElement.firstOrNull() as SqlElPrimaryExpr + + // Exclude fixed Literal + if (isLiteralOrStatic(topElm)) return + + val topElement = blockElement.firstOrNull() ?: return + val daoMethod = findDaoMethod(file) ?: return + val project = topElement.project + val forDirectiveBlocks = ForDirectiveUtil.getForDirectiveBlocks(topElement) + val forItem = ForDirectiveUtil.findForItem(topElement, forDirectives = forDirectiveBlocks) + var isBatchAnnotation = false + val topElementParentClass = + if (forItem != null) { + val result = ForDirectiveUtil.getForDirectiveItemClassType(project, forDirectiveBlocks, forItem) + if (result == null) { + // If the for directive definition element of the top element is invalid, + // an error is displayed by InspectionPrimaryVisitorProcessor. + return + } + val specifiedClassType = + ForDirectiveUtil.resolveForDirectiveItemClassTypeBySuffixElement(topElement.text) + if (specifiedClassType != null) { + PsiParentClass(specifiedClassType) + } else { + result + } + } else { + val paramType = daoMethod.findParameter(cleanString(topElement.text))?.type + if (paramType == null) { + errorHighlight(topElement, daoMethod, holder) + return + } + isBatchAnnotation = PsiDaoMethod(project, daoMethod).daoType.isBatchAnnotation() + PsiParentClass(paramType) + } + + val result = + ForDirectiveUtil.getFieldAccessLastPropertyClassType( + blockElement, + project, + topElementParentClass, + shortName = this.shortName, + isBatchAnnotation = isBatchAnnotation, + ) + + result?.highlightElement(holder) + } + + fun getFieldAccessBlocks(element: SqlElFieldAccessExpr): List { + val blockElements = element.accessElements + (blockElements.firstOrNull() as? SqlElPrimaryExpr) + ?.let { if (isLiteralOrStatic(it)) return emptyList() } + ?: return emptyList() + + return blockElements.mapNotNull { it as SqlElIdExpr } + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/processor/InspectionForDirectiveVisitorProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/processor/InspectionForDirectiveVisitorProcessor.kt new file mode 100644 index 00000000..791ea0b1 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/processor/InspectionForDirectiveVisitorProcessor.kt @@ -0,0 +1,41 @@ +/* + * 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.processor + +import com.intellij.codeInspection.ProblemsHolder +import org.domaframework.doma.intellij.common.sql.validator.result.ValidationForDirectiveItemTypeResult +import org.domaframework.doma.intellij.common.util.ForDirectiveUtil +import org.domaframework.doma.intellij.extension.psi.getForItem +import org.domaframework.doma.intellij.psi.SqlElForDirective + +class InspectionForDirectiveVisitorProcessor( + val shortName: String, + private val element: SqlElForDirective, +) : InspectionVisitorProcessor(shortName) { + fun check(holder: ProblemsHolder) { + val forItem = element.getForItem() ?: return + val directiveBlocks = ForDirectiveUtil.getForDirectiveBlocks(forItem, false) + val declarationType = + ForDirectiveUtil.getForDirectiveItemClassType(element.project, directiveBlocks) + + if (declarationType == null) { + ValidationForDirectiveItemTypeResult( + forItem, + this.shortName, + ).highlightElement(holder) + } + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/processor/InspectionPrimaryVisitorProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/processor/InspectionPrimaryVisitorProcessor.kt new file mode 100644 index 00000000..37d7130c --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/processor/InspectionPrimaryVisitorProcessor.kt @@ -0,0 +1,81 @@ +/* + * 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.processor + +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.psi.PsiFile +import com.intellij.psi.util.PsiTreeUtil +import org.domaframework.doma.intellij.common.dao.findDaoMethod +import org.domaframework.doma.intellij.common.sql.cleanString +import org.domaframework.doma.intellij.common.sql.validator.result.ValidationDaoParamResult +import org.domaframework.doma.intellij.common.sql.validator.result.ValidationForDirectiveItemTypeResult +import org.domaframework.doma.intellij.common.util.ForDirectiveUtil +import org.domaframework.doma.intellij.extension.psi.findParameter +import org.domaframework.doma.intellij.extension.psi.getForItem +import org.domaframework.doma.intellij.psi.SqlElForDirective +import org.domaframework.doma.intellij.psi.SqlElPrimaryExpr +import org.domaframework.doma.intellij.psi.SqlElStaticFieldAccessExpr + +class InspectionPrimaryVisitorProcessor( + val shortName: String, + private val element: SqlElPrimaryExpr, +) : InspectionVisitorProcessor(shortName) { + fun check( + holder: ProblemsHolder, + file: PsiFile, + ) { + if (isLiteralOrStatic(element)) return + PsiTreeUtil.getParentOfType(element, SqlElStaticFieldAccessExpr::class.java)?.let { return } + + val forDirectiveExp = PsiTreeUtil.getParentOfType(element, SqlElForDirective::class.java) + val isSkip = forDirectiveExp != null && forDirectiveExp.getForItem() != element + + var forDirectiveBlocks = ForDirectiveUtil.getForDirectiveBlocks(element, skipSelf = isSkip) + val forItem = + ForDirectiveUtil.findForItem( + element, + skipSelf = isSkip, + forDirectives = forDirectiveBlocks, + ) + if (forDirectiveExp?.getForItem() == element) return + + if (forItem != null) { + val forDeclarationType = + ForDirectiveUtil.getForDirectiveItemClassType( + element.project, + forDirectiveBlocks, + forItem, + ) + if (forDeclarationType == null) { + ValidationForDirectiveItemTypeResult( + element, + this.shortName, + ).highlightElement(holder) + } + return + } + + val daoMethod = findDaoMethod(file) ?: return + val param = daoMethod.findParameter(cleanString(element.text)) + if (param != null) return + + ValidationDaoParamResult( + element, + daoMethod.name, + this.shortName, + ).highlightElement(holder) + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/processor/InspectionStaticFieldAccessVisitorProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/processor/InspectionStaticFieldAccessVisitorProcessor.kt new file mode 100644 index 00000000..79b82c2a --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/processor/InspectionStaticFieldAccessVisitorProcessor.kt @@ -0,0 +1,69 @@ +/* + * 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.processor + +import com.intellij.codeInspection.ProblemsHolder +import org.domaframework.doma.intellij.common.psi.PsiStaticElement +import org.domaframework.doma.intellij.common.sql.validator.result.ValidationClassPathResult +import org.domaframework.doma.intellij.common.sql.validator.result.ValidationNotFoundStaticPropertyResult +import org.domaframework.doma.intellij.common.util.ForDirectiveUtil +import org.domaframework.doma.intellij.extension.expr.accessElements +import org.domaframework.doma.intellij.psi.SqlElStaticFieldAccessExpr + +class InspectionStaticFieldAccessVisitorProcessor( + val shortName: String, +) : InspectionVisitorProcessor(shortName) { + /** + * Check for existence of static field + */ + fun check( + staticAccuser: SqlElStaticFieldAccessExpr, + holder: ProblemsHolder, + ) { + val blockElements = staticAccuser.accessElements + val psiStaticClass = PsiStaticElement(staticAccuser.elClass.elIdExprList, staticAccuser.containingFile) + val referenceClass = psiStaticClass.getRefClazz() + if (referenceClass == null) { + ValidationClassPathResult( + staticAccuser.elClass, + shortName, + ).highlightElement(holder) + return + } + + val topParentClass = ForDirectiveUtil.getStaticFieldAccessTopElementClassType(staticAccuser, referenceClass) + if (topParentClass == null) { + blockElements.firstOrNull()?.let { + ValidationNotFoundStaticPropertyResult( + it, + staticAccuser.elClass, + shortName, + ).highlightElement(holder) + } + return + } + val result = + topParentClass.let { + ForDirectiveUtil.getFieldAccessLastPropertyClassType( + blockElements, + staticAccuser.project, + it, + shortName = shortName, + ) + } + result?.highlightElement(holder) + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/processor/InspectionVisitorProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/processor/InspectionVisitorProcessor.kt new file mode 100644 index 00000000..8be0d357 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/processor/InspectionVisitorProcessor.kt @@ -0,0 +1,52 @@ +/* + * 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.processor + +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod +import com.intellij.psi.util.elementType +import org.domaframework.doma.intellij.common.sql.validator.result.ValidationDaoParamResult +import org.domaframework.doma.intellij.psi.SqlElIdExpr +import org.domaframework.doma.intellij.psi.SqlElNewExpr +import org.domaframework.doma.intellij.psi.SqlTypes + +abstract class InspectionVisitorProcessor( + private val shortName: String, +) { + protected fun errorHighlight( + topElement: SqlElIdExpr, + daoMethod: PsiMethod, + holder: ProblemsHolder, + ) { + ValidationDaoParamResult( + topElement, + daoMethod.name, + this.shortName, + ).highlightElement(holder) + } + + protected fun isLiteralOrStatic(targetElement: PsiElement): Boolean = + ( + targetElement.firstChild?.elementType == SqlTypes.EL_STRING || + targetElement.firstChild?.elementType == SqlTypes.EL_CHAR || + targetElement.firstChild?.elementType == SqlTypes.EL_NUMBER || + targetElement.firstChild?.elementType == SqlTypes.EL_NULL || + targetElement.firstChild?.elementType == SqlTypes.BOOLEAN || + targetElement.firstChild is SqlElNewExpr || + targetElement.text.startsWith("@") + ) +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/visitor/SqlInspectionVisitor.kt b/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/visitor/SqlInspectionVisitor.kt index e6d414aa..9b8c0e7c 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/visitor/SqlInspectionVisitor.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/visitor/SqlInspectionVisitor.kt @@ -19,28 +19,16 @@ import com.intellij.codeInspection.ProblemsHolder import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiLiteralExpression -import com.intellij.psi.PsiMethod -import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.elementType -import org.domaframework.doma.intellij.common.dao.findDaoMethod import org.domaframework.doma.intellij.common.isInjectionSqlFile import org.domaframework.doma.intellij.common.isJavaOrKotlinFileType -import org.domaframework.doma.intellij.common.psi.PsiDaoMethod -import org.domaframework.doma.intellij.common.psi.PsiParentClass -import org.domaframework.doma.intellij.common.psi.PsiStaticElement -import org.domaframework.doma.intellij.common.sql.cleanString -import org.domaframework.doma.intellij.common.sql.validator.result.ValidationClassPathResult -import org.domaframework.doma.intellij.common.sql.validator.result.ValidationDaoParamResult -import org.domaframework.doma.intellij.common.sql.validator.result.ValidationPropertyResult -import org.domaframework.doma.intellij.common.util.ForDirectiveUtil -import org.domaframework.doma.intellij.extension.expr.accessElements -import org.domaframework.doma.intellij.extension.psi.findParameter -import org.domaframework.doma.intellij.extension.psi.getForItem import org.domaframework.doma.intellij.extension.psi.isFirstElement -import org.domaframework.doma.intellij.extension.psi.psiClassType +import org.domaframework.doma.intellij.inspection.sql.processor.InspectionFieldAccessVisitorProcessor +import org.domaframework.doma.intellij.inspection.sql.processor.InspectionForDirectiveVisitorProcessor +import org.domaframework.doma.intellij.inspection.sql.processor.InspectionPrimaryVisitorProcessor +import org.domaframework.doma.intellij.inspection.sql.processor.InspectionStaticFieldAccessVisitorProcessor import org.domaframework.doma.intellij.psi.SqlElFieldAccessExpr import org.domaframework.doma.intellij.psi.SqlElForDirective -import org.domaframework.doma.intellij.psi.SqlElIdExpr import org.domaframework.doma.intellij.psi.SqlElPrimaryExpr import org.domaframework.doma.intellij.psi.SqlElStaticFieldAccessExpr import org.domaframework.doma.intellij.psi.SqlTypes @@ -64,7 +52,8 @@ class SqlInspectionVisitor( override fun visitElStaticFieldAccessExpr(element: SqlElStaticFieldAccessExpr) { super.visitElStaticFieldAccessExpr(element) - checkStaticFieldAndMethodAccess(element, holder) + val processor = InspectionStaticFieldAccessVisitorProcessor(this.shortName) + processor.check(element, holder) } override fun visitElFieldAccessExpr(element: SqlElFieldAccessExpr) { @@ -72,14 +61,14 @@ class SqlInspectionVisitor( if (setFile(element)) return val visitFile: PsiFile = file ?: return - // Get element inside block comment - val blockElement = getFieldAccessBlocks(element) - val topElm = blockElement.firstOrNull() as SqlElPrimaryExpr - - // Exclude fixed Literal - if (isLiteralOrStatic(topElm)) return + val processor = InspectionFieldAccessVisitorProcessor(shortName, element) + processor.check(holder, visitFile) + } - checkAccessFieldAndMethod(holder, blockElement, visitFile) + override fun visitElForDirective(element: SqlElForDirective) { + super.visitElForDirective(element) + val process = InspectionForDirectiveVisitorProcessor(shortName, element) + process.check(holder) } override fun visitElPrimaryExpr(element: SqlElPrimaryExpr) { @@ -88,133 +77,7 @@ class SqlInspectionVisitor( if (setFile(element)) return val visitFile: PsiFile = file ?: return - if (isLiteralOrStatic(element)) return - PsiTreeUtil.getParentOfType(element, SqlElStaticFieldAccessExpr::class.java)?.let { return } - - val forDirectiveExp = PsiTreeUtil.getParentOfType(element, SqlElForDirective::class.java) - if (forDirectiveExp != null && forDirectiveExp.getForItem() == element) return - - val forItem = ForDirectiveUtil.findForItem(element) - if (forItem != null) return - - val daoMethod = findDaoMethod(visitFile) ?: return - val param = daoMethod.findParameter(cleanString(element.text)) - if (param != null) return - - ValidationDaoParamResult( - element, - daoMethod.name, - this.shortName, - ).highlightElement(holder) - } - - private fun getFieldAccessBlocks(element: SqlElFieldAccessExpr): List { - val blockElements = element.accessElements - (blockElements.firstOrNull() as? SqlElPrimaryExpr) - ?.let { if (isLiteralOrStatic(it)) return emptyList() } - ?: return emptyList() - - return blockElements.mapNotNull { it as SqlElIdExpr } - } - - private fun checkAccessFieldAndMethod( - holder: ProblemsHolder, - blockElement: List, - file: PsiFile, - ) { - val topElement = blockElement.firstOrNull() ?: return - val daoMethod = findDaoMethod(file) ?: return - val project = topElement.project - val forDirectiveBlocks = ForDirectiveUtil.getForDirectiveBlocks(topElement) - val forItem = ForDirectiveUtil.findForItem(topElement, forDirectives = forDirectiveBlocks) - var isBatchAnnotation = false - val topElementParentClass = - if (forItem != null) { - val result = ForDirectiveUtil.getForDirectiveItemClassType(project, forDirectiveBlocks) - if (result == null) { - // TODO Add an error message when the type of element used in the For directory is not a List type. - errorHighlight(topElement, daoMethod, holder) - return - } - val specifiedClassType = - ForDirectiveUtil.resolveForDirectiveItemClassTypeBySuffixElement(topElement.text) - if (specifiedClassType != null) { - PsiParentClass(specifiedClassType) - } else { - result - } - } else { - val paramType = daoMethod.findParameter(cleanString(topElement.text))?.type - if (paramType == null) { - errorHighlight(topElement, daoMethod, holder) - return - } - isBatchAnnotation = PsiDaoMethod(project, daoMethod).daoType.isBatchAnnotation() - PsiParentClass(paramType) - } - - val result = - ForDirectiveUtil.getFieldAccessLastPropertyClassType( - blockElement, - project, - topElementParentClass, - shortName = this.shortName, - isBatchAnnotation = isBatchAnnotation, - ) - - result?.highlightElement(holder) - } - - private fun errorHighlight( - topElement: SqlElIdExpr, - daoMethod: PsiMethod, - holder: ProblemsHolder, - ) { - ValidationDaoParamResult( - topElement, - daoMethod.name, - this.shortName, - ).highlightElement(holder) - } - - /** - * Check for existence of static field - */ - private fun checkStaticFieldAndMethodAccess( - staticAccuser: SqlElStaticFieldAccessExpr, - holder: ProblemsHolder, - ) { - val blockElements = staticAccuser.accessElements - val psiStaticClass = PsiStaticElement(staticAccuser.elClass.elIdExprList, staticAccuser.containingFile) - val referenceClass = psiStaticClass.getRefClazz() - if (referenceClass == null) { - ValidationClassPathResult( - staticAccuser.elClass, - this.shortName, - ).highlightElement(holder) - return - } - - val topParentClass = ForDirectiveUtil.getStaticFieldAccessTopElementClassType(staticAccuser, referenceClass) - if (topParentClass == null) { - blockElements.firstOrNull()?.let { - ValidationPropertyResult( - it, - PsiParentClass(referenceClass.psiClassType), - this.shortName, - ).highlightElement(holder) - } - return - } - val result = - topParentClass.let { - ForDirectiveUtil.getFieldAccessLastPropertyClassType( - blockElements, - staticAccuser.project, - it, - shortName = this.shortName, - ) - } - result?.highlightElement(holder) + val processor = InspectionPrimaryVisitorProcessor(this.shortName, element) + processor.check(holder, visitFile) } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/visitor/SqlVisitorBase.kt b/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/visitor/SqlVisitorBase.kt index 67fab189..d228201b 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/visitor/SqlVisitorBase.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/visitor/SqlVisitorBase.kt @@ -20,10 +20,7 @@ import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiLiteralExpression -import com.intellij.psi.util.elementType import org.domaframework.doma.intellij.common.isJavaOrKotlinFileType -import org.domaframework.doma.intellij.psi.SqlElNewExpr -import org.domaframework.doma.intellij.psi.SqlTypes import org.domaframework.doma.intellij.psi.SqlVisitor open class SqlVisitorBase : SqlVisitor() { @@ -56,15 +53,4 @@ open class SqlVisitorBase : SqlVisitor() { false -> null } - - protected fun isLiteralOrStatic(targetElement: PsiElement): Boolean = - ( - targetElement.firstChild?.elementType == SqlTypes.EL_STRING || - targetElement.firstChild?.elementType == SqlTypes.EL_CHAR || - targetElement.firstChild?.elementType == SqlTypes.EL_NUMBER || - targetElement.firstChild?.elementType == SqlTypes.EL_NULL || - targetElement.firstChild?.elementType == SqlTypes.BOOLEAN || - targetElement.firstChild is SqlElNewExpr || - targetElement.text.startsWith("@") - ) } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/reference/SqlElIdExprReference.kt b/src/main/kotlin/org/domaframework/doma/intellij/reference/SqlElIdExprReference.kt index 8e2970f0..d8fe4261 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/reference/SqlElIdExprReference.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/reference/SqlElIdExprReference.kt @@ -48,7 +48,7 @@ class SqlElIdExprReference( // Refers to an element defined in the for directive val isSelfSkip = isSelfSkip(topElm) val forDirectiveBlocks = ForDirectiveUtil.getForDirectiveBlocks(element, isSelfSkip) - val forItem = ForDirectiveUtil.findForItem(element, forDirectives = forDirectiveBlocks) + val forItem = ForDirectiveUtil.findForItem(topElm, forDirectives = forDirectiveBlocks) if (forItem != null && element.textOffset == topElm.textOffset) { PluginLoggerUtil.countLogging( this::class.java.simpleName, @@ -69,18 +69,29 @@ class SqlElIdExprReference( ) } - val tolElementForItem = - ForDirectiveUtil.getForDirectiveItemClassType(topElm.project, forDirectiveBlocks) + // Reference to field access elements + var parentClass: PsiParentClass? = null var isBatchAnnotation = false - var parentClass = - if (tolElementForItem != null) { - tolElementForItem - } else { - val daoMethod = findDaoMethod(file) ?: return null - val param = daoMethod.findParameter(topElm.text) ?: return null - isBatchAnnotation = PsiDaoMethod(topElm.project, daoMethod).daoType.isBatchAnnotation() - PsiParentClass(param.type) - } + if (forItem != null) { + val project = topElm.project + val forItemClassType = + ForDirectiveUtil.getForDirectiveItemClassType(project, forDirectiveBlocks, forItem) + ?: return null + val specifiedClassType = + ForDirectiveUtil.resolveForDirectiveItemClassTypeBySuffixElement(topElm.text) + parentClass = + if (specifiedClassType != null) { + PsiParentClass(specifiedClassType) + } else { + forItemClassType + } + } else { + val daoMethod = findDaoMethod(file) ?: return null + val param = daoMethod.findParameter(topElm.text) ?: return null + parentClass = PsiParentClass(param.type) + isBatchAnnotation = PsiDaoMethod(topElm.project, daoMethod).daoType.isBatchAnnotation() + } + val result = ForDirectiveUtil.getFieldAccessLastPropertyClassType( targetElements, diff --git a/src/main/resources/messages/DomaToolsBundle.properties b/src/main/resources/messages/DomaToolsBundle.properties index 51c200ea..6f377056 100644 --- a/src/main/resources/messages/DomaToolsBundle.properties +++ b/src/main/resources/messages/DomaToolsBundle.properties @@ -1,10 +1,14 @@ jump.to.sql.tooltip.title=Open SQL file jump.to.dao.tooltip.title=Jump to Dao method definition generate.sql.quickfix.title=Create SQL file -inspection.sql.not.exist.error=SQL file does not exist -inspector.invalid.class.property=The field or method [{1}] does not exist in the class [{0}] -inspection.dao.method.variable.error=There are unused parameters in the SQL [{0}] -inspector.invalid.dao.parameter=The bind variable [{1}] does not exist in the Dao method [{0}] config.enable.sql.format=Enable SQL Format +inspection.invalid.dao.notExistSql=SQL file does not exist +inspection.invalid.sql.property=The field or method [{1}] does not exist in the class [{0}] +inspection.invalid.dao.paramUse=There are unused parameters in the SQL [{0}] +inspection.invalid.dao.parameter=The bind variable [{1}] does not exist in the Dao method [{0}] +inspection.invalid.sql.topType=Can't get type of first element +inspection.invalid.sql.staticProperty=[{0}] is not a public or static property in the class [{1}] inspection.invalid.sql.testdata=Bind variables must be followed by test data -inspection.invalid.sql.classpath=A non-existent package or class name was specified. [{0}] \ No newline at end of file +inspection.invalid.sql.classpath=A non-existent package or class name was specified [{0}] +inspection.invalid.sql.iterable=The type that can be used in the for directive is an Iterable type +inspection.invalid.dao.duplicate=An element name that is a duplicate of an element name defined in SQL is used \ No newline at end of file diff --git a/src/main/resources/messages/DomaToolsBundle_ja.properties b/src/main/resources/messages/DomaToolsBundle_ja.properties index 269d3216..b21f366a 100644 --- a/src/main/resources/messages/DomaToolsBundle_ja.properties +++ b/src/main/resources/messages/DomaToolsBundle_ja.properties @@ -1,10 +1,14 @@ jump.to.sql.tooltip.title=SQL\u30D5\u30A1\u30A4\u30EB\u3092\u958B\u304F jump.to.dao.tooltip.title=Dao\u30E1\u30BD\u30C3\u30C9\u5B9A\u7FA9\u306B\u9077\u79FB\u3059\u308B generate.sql.quickfix.title=SQL\u30D5\u30A1\u30A4\u30EB\u3092\u4F5C\u6210 -inspection.sql.not.exist.error=SQL\u30D5\u30A1\u30A4\u30EB\u304C\u5B58\u5728\u3057\u307E\u305B\u3093 -inspector.invalid.class.property=\u30AF\u30E9\u30B9[{0}]\u306B\u5B58\u5728\u3057\u306A\u3044\u30D5\u30A3\u30FC\u30EB\u30C9\u3001\u307E\u305F\u306F\u30E1\u30BD\u30C3\u30C9\u304C\u4F7F\u7528\u3055\u308C\u3066\u3044\u307E\u3059:[{1}] -inspection.dao.method.variable.error=SQL\u3067\u4F7F\u7528\u3055\u308C\u3066\u3044\u306A\u3044\u5F15\u6570\u304C\u3042\u308A\u307E\u3059[{0}] -inspector.invalid.dao.parameter=Dao\u30E1\u30BD\u30C3\u30C9[{0}]\u306B\u5B58\u5728\u3057\u306A\u3044\u30D0\u30A4\u30F3\u30C9\u5909\u6570\u304C\u4F7F\u7528\u3055\u308C\u3066\u3044\u307E\u3059:[{1}] config.enable.sql.format=SQL\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u3092\u6709\u52B9\u5316 +inspection.invalid.dao.notExistSql=SQL\u30D5\u30A1\u30A4\u30EB\u304C\u5B58\u5728\u3057\u307E\u305B\u3093 +inspection.invalid.sql.property=\u30AF\u30E9\u30B9[{0}]\u306B\u5B58\u5728\u3057\u306A\u3044\u30D5\u30A3\u30FC\u30EB\u30C9\u3001\u307E\u305F\u306F\u30E1\u30BD\u30C3\u30C9\u304C\u4F7F\u7528\u3055\u308C\u3066\u3044\u307E\u3059:[{1}] +inspection.invalid.dao.paramUse=SQL\u3067\u4F7F\u7528\u3055\u308C\u3066\u3044\u306A\u3044\u5F15\u6570\u304C\u3042\u308A\u307E\u3059:[{0}] +inspection.invalid.dao.parameter=Dao\u30E1\u30BD\u30C3\u30C9[{0}]\u306B\u5B58\u5728\u3057\u306A\u3044\u30D0\u30A4\u30F3\u30C9\u5909\u6570\u304C\u4F7F\u7528\u3055\u308C\u3066\u3044\u307E\u3059:[{1}] +inspection.invalid.sql.topType=\u6700\u521D\u306E\u8981\u7D20\u306E\u578B\u304C\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093 +inspection.invalid.sql.staticProperty=[{0}]\u0020\u306F\u30AF\u30E9\u30B9\u0020[{1}]\u0020\u306E\u0020\u0070\u0075\u0062\u006C\u0069\u0063\u0020\u307E\u305F\u0020\u0073\u0074\u0061\u0074\u0069\u0063\u0020\u30D7\u30ED\u30D1\u30C6\u30A3\u3067\u306F\u3042\u308A\u307E\u305B\u3093 inspection.invalid.sql.testdata=\u30D0\u30A4\u30F3\u30C9\u5909\u6570\u306E\u5F8C\u308D\u306B\u306F\u30C6\u30B9\u30C8\u30C7\u30FC\u30BF\u304C\u5FC5\u8981\u3067\u3059 -inspection.invalid.sql.classpath=\u0041\u0020\u006E\u006F\u006E\u002D\u0065\u0078\u0069\u0073\u0074\u0065\u006E\u0074\u0020\u0070\u0061\u0063\u006B\u0061\u0067\u0065\u0020\u006F\u0072\u0020\u0063\u006C\u0061\u0073\u0073\u0020\u006E\u0061\u006D\u0065\u0020\u0077\u0061\u0073\u0020\u0073\u0070\u0065\u0063\u0069\u0066\u0069\u0065\u0064\u002E [{0}] \ No newline at end of file +inspection.invalid.sql.classpath=\u5B58\u5728\u3057\u306A\u3044\u30D1\u30C3\u30B1\u30FC\u30B8\u307E\u305F\u306F\u30AF\u30E9\u30B9\u540D\u304C\u6307\u5B9A\u3055\u308C\u307E\u3057\u305F\u3002:[{0}] +inspection.invalid.sql.iterable=\u0066\u006F\u0072\u30C7\u30A3\u30EC\u30AF\u30C6\u30A3\u30D6\u306B\u4F7F\u7528\u3067\u304D\u308B\u578B\u306F\u0049\u0074\u0065\u0072\u0061\u0062\u006C\u0065\u578B\u3067\u3059 +inspection.invalid.dao.duplicate=\u0053\u0051\u004C\u5185\u3067\u5B9A\u7FA9\u3055\u308C\u305F\u8981\u7D20\u540D\u3068\u91CD\u8907\u3057\u305F\u8981\u7D20\u540D\u304C\u4F7F\u7528\u3055\u308C\u3066\u3044\u307E\u3059 \ No newline at end of file diff --git a/src/test/kotlin/org/domaframework/doma/intellij/inspection/dao/DomaUseVariableTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/inspection/dao/DomaUseVariableTest.kt index 910056de..c939953b 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/inspection/dao/DomaUseVariableTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/inspection/dao/DomaUseVariableTest.kt @@ -35,6 +35,7 @@ class DomaUseVariableTest : DomaSqlTest() { "$testDaoName/collectDoesNotCauseError.sql", "$testDaoName/collectDoesCauseError.sql", "$testDaoName/noErrorWhenUsedInFunctionParameters.sql", + "$testDaoName/duplicateForDirectiveDefinitionNames.sql", ) myFixture.enableInspections(DaoMethodVariableInspector()) } diff --git a/src/test/testData/src/main/java/doma/example/dao/DaoMethodVariableInspectionTestDao.java b/src/test/testData/src/main/java/doma/example/dao/DaoMethodVariableInspectionTestDao.java index 05da5b26..aec91b45 100644 --- a/src/test/testData/src/main/java/doma/example/dao/DaoMethodVariableInspectionTestDao.java +++ b/src/test/testData/src/main/java/doma/example/dao/DaoMethodVariableInspectionTestDao.java @@ -50,4 +50,10 @@ interface DaoMethodVariableInspectionTestDao { @Select Project noErrorWhenUsedInFunctionParameters(Employee employee, Integer count); + @Select + Employee duplicateForDirectiveDefinitionNames(Employee member, Integer count, + List users, + String searchName, + Boolean inForm); + } \ No newline at end of file diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/DaoMethodVariableInspectionTestDao/duplicateForDirectiveDefinitionNames.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/DaoMethodVariableInspectionTestDao/duplicateForDirectiveDefinitionNames.sql new file mode 100644 index 00000000..42db98e9 --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/DaoMethodVariableInspectionTestDao/duplicateForDirectiveDefinitionNames.sql @@ -0,0 +1,9 @@ +SELECT * + FROM users + WHERE count = /* ids.size() */0 + /*%for member : users */ + OR (id = /* member.userId */0 + AND count < /* users.size() */0) + AND form = /* inForm */false + /*%end */ + AND searchName = /* searchName */'search' diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/accessStaticProperty.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/accessStaticProperty.sql index 6b0af1b3..89390917 100644 --- a/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/accessStaticProperty.sql +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/accessStaticProperty.sql @@ -33,7 +33,7 @@ where /*%end */ ) -- Static field call that does not exist - /*%if @doma.example.entity.ProjectDetail@priority >= 3 */ + /*%if @doma.example.entity.ProjectDetail@priority >= 3 */ -- Static method call that does not exist - AND pd.limit_date = /* @doma.example.entity.ProjectDetail@getLimit() */0 + AND pd.limit_date = /* @doma.example.entity.ProjectDetail@getLimit() */0 /*%end */ \ No newline at end of file diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/bindVariableForItemHasNextAndIndex.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/bindVariableForItemHasNextAndIndex.sql index 0b2c6733..44cf89d9 100644 --- a/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/bindVariableForItemHasNextAndIndex.sql +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/bindVariableForItemHasNextAndIndex.sql @@ -18,7 +18,7 @@ select p.project_id /*%end */ p.employee_id = /* member.employeeId */0 and p.not_next = /* member_has_next */false - and p.next = /* member_has_next */false + and p.next = /* member_has_next.a */false and p.not_index = /* member_index */999 and p.index = /* member_index */0 /*%end */ diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/bindVariableForNonEntityClass.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/bindVariableForNonEntityClass.sql index 3c5cee80..0019dc19 100644 --- a/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/bindVariableForNonEntityClass.sql +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/bindVariableForNonEntityClass.sql @@ -25,9 +25,9 @@ AND pe.end_date >= CURRENT_DATE /*%end*/ -- Reference error for a non-existent field - /*%for child : employee.projectIds */ + /*%for child : employee.projectIds */ -- An error occurred because the referenced element was not correctly defined. - AND pe.parent_project = /* child.projectId */0 + AND pe.parent_project = /* child.projectId */0 -- Reference error for a non-existent method AND pe.member_id IN /* employee.getTopProject() */(0,1,2) /*%end */ diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/callStaticPropertyPackageName.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/callStaticPropertyPackageName.sql index 2c90eb10..8ff52613 100644 --- a/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/callStaticPropertyPackageName.sql +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/callStaticPropertyPackageName.sql @@ -1,4 +1,4 @@ Select COUNT(*) from employee - where rank = /* @doma.example.entity.EmployeeBase.Rank@MANAGER */1 - and exist_class = /* @doma.example.entity.Employee.Exists@T */false - and exist_pkg = /* @doma.example.entity@T */false \ No newline at end of file + where rank = /* @doma.example.entity.EmployeeBase.Rank@MANAGER */1 + and exist_class = /* @doma.example.entity.Employee.Exists@T */false + and exist_pkg = /* @doma.example.entity@T */false \ No newline at end of file