diff --git a/README.md b/README.md index 70435264..f970c166 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ The plugin also provides quick fixes for Dao methods where the required SQL file - Check the class name and package name for static property calls ![inspectionPackageName.png](images/inspectionPackageName.png) - Optional types are recognized as their element type (e.g. Optional is treated as String). +- Checks calls to custom functions and error-highlights any methods that aren’t defined in the classes registered via the settings. ## Completion Adds code completion functionality to support indexing of Doma directives and bind variables @@ -54,6 +55,7 @@ Adds code completion functionality to support indexing of Doma directives and bi - Directives such as Condition, Loop, Population are suggested after “%” - Suggest built-in functions after “@” - Optional types are recognized as their element type (e.g. Optional is treated as String). +- Suggest functions during code completion from the ExpressionFunctions implementation classes registered in the settings. ## Refactoring Along with the Dao name change, we will refactor the SQL file directory and file name. @@ -81,6 +83,7 @@ This feature works in source JARs as well, but in binary JARs, if the DAO method - The DAO method’s argument parameter definition - The field and method definitions on that parameter’s type - The class definition referenced by @ClassName@ +- Resolve references for custom functions using the ExpressionFunctions implementation class in which they are defined. - You can also jump using the **Go To > Declaration Usage** menu. ![reference.png](images/reference.png) @@ -92,3 +95,6 @@ Some functions of "Doma Tools" can be customized from the settings screen. - Highlight color settings for SQL elements ![setting_highlight.png](images/setting_highlight.png) - Customize action shortcut keys +- Toggle the SQL formatting feature on or off +- Specify the class names that define custom functions. + ![setting.png](images/setting.png) diff --git a/images/setting.png b/images/setting.png new file mode 100644 index 00000000..54cf66bf Binary files /dev/null and b/images/setting.png differ diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/CommonPathParameterHelper.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/CommonPathParameterHelper.kt new file mode 100644 index 00000000..2267912e --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/CommonPathParameterHelper.kt @@ -0,0 +1,29 @@ +/* + * 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 + +// TODO Dynamically build the source directory path and retrieve subproject info +// by inspecting file metadata instead of using string manipulation. +object CommonPathParameterHelper { + val SRC_MAIN_PATH: String + get() = "/src/main" + + val RESOURCES_PATH: String + get() = "resources" + + val RESOURCES_META_INF_PATH: String + get() = "META-INF" +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/FileTypeCheck.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/FileTypeCheck.kt index aa8d7ce8..97f3ec88 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/FileTypeCheck.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/FileTypeCheck.kt @@ -18,7 +18,7 @@ package org.domaframework.doma.intellij.common import com.intellij.openapi.fileTypes.FileTypeManager import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiFile -import org.domaframework.doma.intellij.common.CommonPathParameter.Companion.SRC_MAIN_PATH +import org.domaframework.doma.intellij.common.CommonPathParameterHelper.SRC_MAIN_PATH /** * Get extension by file type identifier @@ -46,14 +46,6 @@ fun isJavaOrKotlinFileType(daoFile: PsiFile): Boolean { } } -fun isJavaOrKotlinFileType(file: VirtualFile): Boolean { - val fileType = file.fileType - return when (fileType.name) { - "JAVA", "Kotlin", "CLASS" -> true - else -> false - } -} - /* * Determine whether the open file is an SQL template file extension */ diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/JarFileSearch.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/JarFileSearch.kt index c81e3def..98a47a72 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/JarFileSearch.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/JarFileSearch.kt @@ -18,7 +18,7 @@ package org.domaframework.doma.intellij.common import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiFile -import org.domaframework.doma.intellij.common.CommonPathParameter.Companion.RESOURCES_META_INF_PATH +import org.domaframework.doma.intellij.common.CommonPathParameterHelper.RESOURCES_META_INF_PATH fun getJarRoot( virtualFile: VirtualFile, diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/dao/DaoMethodUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/dao/DaoMethodUtil.kt index 439cb38f..b8294389 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/dao/DaoMethodUtil.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/dao/DaoMethodUtil.kt @@ -27,8 +27,8 @@ import com.intellij.psi.PsiManager import com.intellij.psi.PsiMethod import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.util.PsiTreeUtil -import org.domaframework.doma.intellij.common.CommonPathParameter.Companion.RESOURCES_META_INF_PATH -import org.domaframework.doma.intellij.common.CommonPathParameter.Companion.RESOURCES_PATH +import org.domaframework.doma.intellij.common.CommonPathParameterHelper.RESOURCES_META_INF_PATH +import org.domaframework.doma.intellij.common.CommonPathParameterHelper.RESOURCES_PATH import org.domaframework.doma.intellij.common.getExtension import org.domaframework.doma.intellij.common.getJarRoot import org.domaframework.doma.intellij.common.getMethodDaoFilePath diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/helper/ActiveProjectHelper.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/helper/ActiveProjectHelper.kt new file mode 100644 index 00000000..e3b92342 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/helper/ActiveProjectHelper.kt @@ -0,0 +1,48 @@ +/* + * 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.helper + +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.ProjectManager +import com.intellij.openapi.wm.IdeFocusManager + +object ActiveProjectHelper { + private var activeProject: Project? = null + + fun setCurrentActiveProject(value: Project?) { + activeProject = value + } + + fun getCurrentActiveProject(): Project? { + val initProject = activeProject + val active = getActiveUIProject() + return active ?: initProject + } + + private fun getActiveUIProject(): Project? { + val openProjects: Array = ProjectManager.getInstance().openProjects + var active: Project? = null + + for (project in openProjects) { + if (IdeFocusManager.getInstance(project).focusOwner != null) { + active = project + break + } + } + + return active + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/helper/ExpressionFunctionsHelper.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/helper/ExpressionFunctionsHelper.kt new file mode 100644 index 00000000..e6e58bb5 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/helper/ExpressionFunctionsHelper.kt @@ -0,0 +1,57 @@ +/* + * 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.helper + +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiClass +import org.domaframework.doma.intellij.extension.getJavaClazz +import org.domaframework.doma.intellij.extension.psi.psiClassType + +class ExpressionFunctionsHelper { + companion object { + var expressionFunction: PsiClass? = null + + fun setExpressionFunctionsInterface(project: Project): PsiClass? { + val expressionFunctionsClass = + project.getJavaClazz("org.seasar.doma.expr.ExpressionFunctions") + if (expressionFunctionsClass != null) { + expressionFunction = expressionFunctionsClass + } + return expressionFunction + } + + fun isInheritor(expressionClazz: PsiClass?): Boolean { + val project = expressionClazz?.project + + expressionFunction?.let { functionInterface -> + expressionClazz?.let { + if (expressionClazz.isInheritor(functionInterface, true)) return true + + val parentType = + expressionClazz.superTypes.firstOrNull()?.canonicalText + ?: expressionClazz.psiClassType.canonicalText + return project?.let { + expressionClazz.psiClassType.canonicalText + project + .getJavaClazz(parentType) + ?.isInheritor(functionInterface, true) == true + } == true + } + } + return false + } + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/psi/PsiDaoMethod.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/psi/PsiDaoMethod.kt index 0713df22..9b47697e 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/psi/PsiDaoMethod.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/psi/PsiDaoMethod.kt @@ -32,7 +32,7 @@ import com.intellij.psi.PsiManager import com.intellij.psi.PsiMethod import com.intellij.psi.PsiNameValuePair import com.intellij.util.IncorrectOperationException -import org.domaframework.doma.intellij.common.CommonPathParameter.Companion.RESOURCES_PATH +import org.domaframework.doma.intellij.common.CommonPathParameterHelper.RESOURCES_PATH import org.domaframework.doma.intellij.common.dao.formatSqlPathFromDaoPath import org.domaframework.doma.intellij.common.getExtension import org.domaframework.doma.intellij.extension.findFile diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/DirectiveCompletion.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/DirectiveCompletion.kt index 8f5e687c..27ed5922 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/DirectiveCompletion.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/DirectiveCompletion.kt @@ -16,7 +16,6 @@ package org.domaframework.doma.intellij.common.sql.directive import com.intellij.codeInsight.completion.CompletionResultSet -import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile @@ -24,7 +23,6 @@ class DirectiveCompletion( private val originalFile: PsiFile, private val bindText: String, private val element: PsiElement, - private val project: Project, private val result: CompletionResultSet, ) { fun directiveHandle(symbol: String): Boolean { @@ -56,7 +54,6 @@ class DirectiveCompletion( element = element, result = result, bindText = bindText, - project = project, ).directiveHandle() else -> return false diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/StaticDirectiveHandler.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/StaticDirectiveHandler.kt index 14d2d917..b979da2a 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/StaticDirectiveHandler.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/StaticDirectiveHandler.kt @@ -17,7 +17,6 @@ package org.domaframework.doma.intellij.common.sql.directive import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.openapi.module.Module -import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.elementType @@ -34,7 +33,6 @@ class StaticDirectiveHandler( private val element: PsiElement, private val result: CompletionResultSet, private val bindText: String, - private val project: Project, ) : DirectiveHandler(originalFile) { override fun directiveHandle(): Boolean { var handleResult = false @@ -95,7 +93,7 @@ class StaticDirectiveHandler( ): Boolean { if (BindDirectiveUtil.getDirectiveType(element) == DirectiveType.BUILT_IN) { val prefix = getBindSearchWord(element, bindText) - val collector = StaticBuildFunctionCollector(project, prefix) + val collector = StaticBuildFunctionCollector(element.project, prefix) val candidates = collector.collect() candidates?.let { it1 -> result.addAllElements(it1) } return true diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/collector/StaticBuildFunctionCollector.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/collector/StaticBuildFunctionCollector.kt index 618dd901..25284782 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/collector/StaticBuildFunctionCollector.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/collector/StaticBuildFunctionCollector.kt @@ -18,126 +18,68 @@ package org.domaframework.doma.intellij.common.sql.directive.collector import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.openapi.project.Project -import com.intellij.psi.PsiManager -import com.intellij.psi.PsiType -import com.intellij.psi.search.GlobalSearchScope -import org.domaframework.doma.intellij.common.sql.directive.DomaFunction +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiModifier +import org.domaframework.doma.intellij.common.helper.ExpressionFunctionsHelper +import org.domaframework.doma.intellij.extension.getJavaClazz +import org.domaframework.doma.intellij.setting.state.DomaToolsCustomFunctionSettings class StaticBuildFunctionCollector( private val project: Project, private val bind: String, ) : StaticDirectiveHandlerCollector() { - public override fun collect(): List? = - listOf( - DomaFunction( - "escape", - getJavaLangString(), - listOf( - getPsiTypeByClassName("java.lang.CharSequence"), - getPsiTypeByClassName("java.lang.Char"), - ), - ), - DomaFunction( - "prefix", - getJavaLangString(), - listOf( - getPsiTypeByClassName("java.lang.CharSequence"), - getPsiTypeByClassName("java.lang.Char"), - ), - ), - DomaFunction( - "infix", - getJavaLangString(), - listOf( - getPsiTypeByClassName("java.lang.CharSequence"), - getPsiTypeByClassName("java.lang.Char"), - ), - ), - DomaFunction( - "suffix", - getJavaLangString(), - listOf( - getPsiTypeByClassName("java.lang.CharSequence"), - getPsiTypeByClassName("java.lang.Char"), - ), - ), - DomaFunction( - "roundDownTimePart", - getPsiTypeByClassName("java.util.Date"), - listOf(getPsiTypeByClassName("java.util.Date")), - ), - DomaFunction( - "roundDownTimePart", - getPsiTypeByClassName("java.sql.Date"), - listOf(getPsiTypeByClassName("java.util.Date")), - ), - DomaFunction( - "roundDownTimePart", - getPsiTypeByClassName("java.sql.Timestamp"), - listOf(getPsiTypeByClassName("java.sql.Timestamp")), - ), - DomaFunction( - "roundDownTimePart", - getPsiTypeByClassName("java.time.LocalDateTime"), - listOf(getPsiTypeByClassName("java.time.LocalDateTime")), - ), - DomaFunction( - "roundUpTimePart", - getPsiTypeByClassName("java.util.Date"), - listOf(getPsiTypeByClassName("java.sql.Date")), - ), - DomaFunction( - "roundUpTimePart", - getPsiTypeByClassName("java.sql.Timestamp"), - listOf(getPsiTypeByClassName("java.sql.Timestamp")), - ), - DomaFunction( - "roundUpTimePart", - getPsiTypeByClassName("java.time.LocalDate"), - listOf(getPsiTypeByClassName("java.time.LocalDate")), - ), - DomaFunction( - "isEmpty", - getPsiTypeByClassName("boolean"), - listOf(getPsiTypeByClassName("java.lang.CharSequence")), - ), - DomaFunction( - "isNotEmpty", - getPsiTypeByClassName("boolean"), - listOf(getPsiTypeByClassName("java.lang.CharSequence")), - ), - DomaFunction( - "isBlank", - getPsiTypeByClassName("boolean"), - listOf(getPsiTypeByClassName("java.lang.CharSequence")), - ), - DomaFunction( - "isNotBlank", - getPsiTypeByClassName("boolean"), - listOf(getPsiTypeByClassName("java.lang.CharSequence")), - ), - ).filter { - it.name.startsWith(bind.substringAfter("@")) - }.map { - LookupElementBuilder - .create("${it.name}()") - .withPresentableText(it.name) - .withTailText( - "(${ - it.parameters.joinToString(",") { param -> - param.toString().replace("PsiType:", "") - } - })", - true, - ).withTypeText(it.returnType.presentableText) + public override fun collect(): List? { + var functions = mutableSetOf() + val setting = DomaToolsCustomFunctionSettings.getInstance(project) + val state = setting.state + val customFunctions = state.customFunctionClassNames + + val expressionFunctionInterface = + ExpressionFunctionsHelper.setExpressionFunctionsInterface(project) + ?: return null + + customFunctions.forEach { function -> + val expressionClazz = project.getJavaClazz(function) + if (expressionClazz != null && + ExpressionFunctionsHelper.isInheritor(expressionClazz) + ) { + val methods = expressionClazz.allMethods + functions.addAll( + methods.filter { + isPublicFunction(it) + }, + ) + } + } + + if (functions.isEmpty()) { + functions.addAll( + expressionFunctionInterface.allMethods.filter { + isPublicFunction(it) + }, + ) } - private fun getJavaLangString(): PsiType = - PsiType.getJavaLangString( - PsiManager.getInstance(project), - GlobalSearchScope.allScope(project), - ) + return functions + .filter { + it.name.startsWith(bind.substringAfter("@")) + }.map { + val parameters = it.parameterList.parameters.toList() + LookupElementBuilder + .create("${it.name}()") + .withPresentableText(it.name) + .withTailText( + "(${ + parameters.joinToString(",") { "${it.type.presentableText} ${it.name}" } + })", + true, + ).withTypeText(it.returnType?.presentableText ?: "void") + } + } - private fun getPsiTypeByClassName(className: String): PsiType = - PsiType.getTypeByName(className, project, GlobalSearchScope.allScope(project)) + private fun isPublicFunction(method: PsiMethod): Boolean = + !method.isConstructor && + method.hasModifierProperty( + PsiModifier.PUBLIC, + ) } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationClassPathResult.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationClassPathResult.kt index c64096b5..3f892439 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationClassPathResult.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationClassPathResult.kt @@ -16,7 +16,6 @@ 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 @@ -34,7 +33,6 @@ open class ValidationClassPathResult( identify: PsiElement, holder: ProblemsHolder, parent: PsiParentClass?, - project: Project, ) { val project = identify.project holder.registerProblem( diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationCompleteResult.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationCompleteResult.kt index 692798e8..bb5c2547 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationCompleteResult.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationCompleteResult.kt @@ -16,7 +16,6 @@ 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.common.psi.PsiParentClass @@ -33,7 +32,6 @@ class ValidationCompleteResult( identify: PsiElement, holder: ProblemsHolder, parent: PsiParentClass?, - project: Project, ) { } } 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 6ff281ba..1dcc01b5 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 @@ -16,7 +16,6 @@ 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 @@ -35,7 +34,6 @@ open class ValidationDaoParamResult( identify: PsiElement, holder: ProblemsHolder, parent: PsiParentClass?, - project: Project, ) { val project = identify.project holder.registerProblem( 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 index 827b0a0b..b980a31e 100644 --- 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 @@ -16,7 +16,6 @@ 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 @@ -31,7 +30,6 @@ class ValidationForDirectiveItemTypeResult( identify: PsiElement, holder: ProblemsHolder, parent: PsiParentClass?, - project: Project, ) { val project = identify.project holder.registerProblem( diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationInvalidFunctionCallResult.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationInvalidFunctionCallResult.kt new file mode 100644 index 00000000..c9350143 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationInvalidFunctionCallResult.kt @@ -0,0 +1,48 @@ +/* + * 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.util.TextRange +import com.intellij.psi.PsiElement +import org.domaframework.doma.intellij.bundle.MessageBundle +import org.domaframework.doma.intellij.common.psi.PsiParentClass + +/** + * Reports invalid function calls in SQL validation. + */ +open class ValidationInvalidFunctionCallResult( + override val identify: PsiElement, + override val shortName: 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.sql.customFunction", + identify.text ?: "", + ), + 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 index 96334044..50735156 100644 --- 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 @@ -16,7 +16,6 @@ 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 @@ -33,7 +32,6 @@ class ValidationNotFoundStaticPropertyResult( identify: PsiElement, holder: ProblemsHolder, parent: PsiParentClass?, - project: Project, ) { val project = identify.project holder.registerProblem( 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 index 550bf481..02bf657a 100644 --- 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 @@ -16,7 +16,6 @@ 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 @@ -31,7 +30,6 @@ class ValidationNotFoundTopTypeResult( identify: PsiElement, holder: ProblemsHolder, parent: PsiParentClass?, - project: Project, ) { val project = identify.project holder.registerProblem( 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 55fb1b55..8c024993 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 @@ -16,7 +16,6 @@ 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.PsiClassType import com.intellij.psi.PsiElement @@ -36,7 +35,6 @@ class ValidationPropertyResult( identify: PsiElement, holder: ProblemsHolder, parent: PsiParentClass?, - project: Project, ) { val project = identify.project val parentName = diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationResult.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationResult.kt index a566a7f6..0976410e 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationResult.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationResult.kt @@ -35,7 +35,6 @@ abstract class ValidationResult( val element = identify ?: return if (identify is PsiErrorElement) return - val project = element.project val highlightElm = element.originalElement val highlightRange = TextRange(0, element.textRange.length) @@ -45,7 +44,6 @@ abstract class ValidationResult( highlightElm, holder, parentClass, - project, ) } @@ -54,7 +52,6 @@ abstract class ValidationResult( identify: PsiElement, holder: ProblemsHolder, parent: PsiParentClass?, - project: Project, ) /** diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationTestDataResult.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationTestDataResult.kt index e01e0b95..0cc43a4c 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationTestDataResult.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/validator/result/ValidationTestDataResult.kt @@ -16,7 +16,6 @@ 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 @@ -31,7 +30,6 @@ class ValidationTestDataResult( identify: PsiElement, holder: ProblemsHolder, parent: PsiParentClass?, - project: Project, ) { val project = identify.project holder.registerProblem( diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/util/DomaToolsSettingUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/util/DomaToolsSettingUtil.kt new file mode 100644 index 00000000..9ff520fc --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/util/DomaToolsSettingUtil.kt @@ -0,0 +1,33 @@ +/* + * 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.openapi.project.Project +import org.domaframework.doma.intellij.setting.state.DomaToolsFormatEnableSettings + +object DomaToolsSettingUtil { + /** + * Checks if SQL formatting is enabled in the project settings. + * + * @param project The project to check. + * @return `true` if SQL formatting is enabled, `false` otherwise. + */ + fun isEnableFormat(project: Project): Boolean { + val setting = DomaToolsFormatEnableSettings.getInstance(project) + val isEnableFormat = setting.state.isEnableSqlFormat + return isEnableFormat + } +} 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 697af7ae..a742dc66 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 @@ -80,14 +80,13 @@ class SqlParameterCompletionProvider : CompletionProvider( var isDirective = false try { val originalFile = parameters.originalFile - val project = originalFile.project val pos = parameters.originalPosition ?: return val bindText = cleanString(pos.text) .substringAfter("/*") .substringBefore("*/") - val handler = DirectiveCompletion(originalFile, bindText, pos, project, result) + val handler = DirectiveCompletion(originalFile, bindText, pos, result) val directiveSymbols = listOf("%", "#", "^", "@") directiveSymbols.forEach { if (!isDirective) { diff --git a/src/main/kotlin/org/domaframework/doma/intellij/extension/ModuleExtensions.kt b/src/main/kotlin/org/domaframework/doma/intellij/extension/ModuleExtensions.kt index e2332841..f724fced 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/extension/ModuleExtensions.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/extension/ModuleExtensions.kt @@ -21,7 +21,7 @@ import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiClass import com.intellij.psi.search.GlobalSearchScope -import org.domaframework.doma.intellij.common.CommonPathParameter.Companion.RESOURCES_META_INF_PATH +import org.domaframework.doma.intellij.common.CommonPathParameterHelper.RESOURCES_META_INF_PATH import org.domaframework.doma.intellij.common.dao.formatSqlPathFromDaoPath import org.jetbrains.kotlin.idea.util.sourceRoots diff --git a/src/main/kotlin/org/domaframework/doma/intellij/extension/ProjectExtensions.kt b/src/main/kotlin/org/domaframework/doma/intellij/extension/ProjectExtensions.kt index 8528a76c..e900a9ef 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/extension/ProjectExtensions.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/extension/ProjectExtensions.kt @@ -45,4 +45,8 @@ fun Project.getJavaClazz(fqdn: String): PsiClass? { .getInstance(this) .findClasses(fqdn, scope) .firstOrNull() + ?: JavaPsiFacade.getInstance(this).findClass( + fqdn, + GlobalSearchScope.allScope(this), + ) } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/extension/psi/PsiMethodExtension.kt b/src/main/kotlin/org/domaframework/doma/intellij/extension/psi/PsiMethodExtension.kt index 692c9433..bbe4d100 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/extension/psi/PsiMethodExtension.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/extension/psi/PsiMethodExtension.kt @@ -15,7 +15,6 @@ */ package org.domaframework.doma.intellij.extension.psi -import com.intellij.codeInsight.AnnotationUtil import com.intellij.psi.PsiMethod import com.intellij.psi.PsiParameter @@ -25,13 +24,3 @@ val PsiMethod.methodParameters: List get() = this.parameterList.parameters.toList() fun PsiMethod.searchParameter(searchName: String): List = this.methodParameters.filter { it.name.startsWith(searchName) } - -@OptIn(ExperimentalStdlibApi::class) -fun PsiMethod.getDomaAnnotationType(): DomaAnnotationType { - DomaAnnotationType.entries.forEach { - if (AnnotationUtil.findAnnotation(this, it.fqdn) != null) { - return it - } - } - return DomaAnnotationType.Unknown -} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatPreProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatPreProcessor.kt index 8174c63e..3f8b55a3 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatPreProcessor.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatPreProcessor.kt @@ -28,13 +28,13 @@ import com.intellij.psi.impl.source.codeStyle.PreFormatProcessor import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.elementType import com.intellij.psi.util.prevLeafs +import org.domaframework.doma.intellij.common.util.DomaToolsSettingUtil import org.domaframework.doma.intellij.common.util.PluginLoggerUtil import org.domaframework.doma.intellij.extension.expr.isConditionOrLoopDirective import org.domaframework.doma.intellij.psi.SqlBlockComment import org.domaframework.doma.intellij.psi.SqlCustomElCommentExpr import org.domaframework.doma.intellij.psi.SqlTypes import org.domaframework.doma.intellij.setting.SqlLanguage -import org.domaframework.doma.intellij.state.DomaToolsFunctionEnableSettings import org.jetbrains.kotlin.psi.psiUtil.startOffset class SqlFormatPreProcessor : PreFormatProcessor { @@ -47,7 +47,7 @@ class SqlFormatPreProcessor : PreFormatProcessor { source: PsiFile, rangeToReformat: TextRange, ): TextRange { - if (!isEnableFormat()) return rangeToReformat + if (!DomaToolsSettingUtil.isEnableFormat(source.project)) return rangeToReformat if (source.language != SqlLanguage.INSTANCE) return rangeToReformat logging() @@ -157,12 +157,6 @@ class SqlFormatPreProcessor : PreFormatProcessor { return rangeToReformat.grown(visitor.replaces.size) } - private fun isEnableFormat(): Boolean { - val setting = DomaToolsFunctionEnableSettings.getInstance() - val isEnableFormat = setting.state.isEnableSqlFormat - return isEnableFormat - } - private fun removeSpacesAroundNewline( document: Document, range: TextRange, diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlFormattingModelBuilder.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlFormattingModelBuilder.kt index fbfb01ff..49646527 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlFormattingModelBuilder.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlFormattingModelBuilder.kt @@ -25,17 +25,18 @@ import com.intellij.formatting.Wrap import com.intellij.formatting.WrapType import com.intellij.psi.TokenType import com.intellij.psi.codeStyle.CodeStyleSettings +import org.domaframework.doma.intellij.common.helper.ActiveProjectHelper import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.psi.SqlTypes import org.domaframework.doma.intellij.setting.SqlLanguage -import org.domaframework.doma.intellij.state.DomaToolsFunctionEnableSettings +import org.domaframework.doma.intellij.setting.state.DomaToolsFormatEnableSettings class SqlFormattingModelBuilder : FormattingModelBuilder { override fun createModel(formattingContext: FormattingContext): FormattingModel { val codeStyleSettings = formattingContext.codeStyleSettings - val setting = DomaToolsFunctionEnableSettings.getInstance() - val isEnableFormat = setting.state.isEnableSqlFormat - + val project = ActiveProjectHelper.getCurrentActiveProject() + val setting = project?.let { DomaToolsFormatEnableSettings.getInstance(it) } + val isEnableFormat = setting?.state?.isEnableSqlFormat == true val spacingBuilder = if (!isEnableFormat) { SpacingBuilder(codeStyleSettings, SqlLanguage.INSTANCE) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlPostProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlPostProcessor.kt index ef60803e..a62c01fc 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlPostProcessor.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlPostProcessor.kt @@ -24,8 +24,8 @@ import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.codeStyle.CodeStyleSettings import com.intellij.psi.impl.source.codeStyle.PostFormatProcessor +import org.domaframework.doma.intellij.common.util.DomaToolsSettingUtil import org.domaframework.doma.intellij.setting.SqlLanguage -import org.domaframework.doma.intellij.state.DomaToolsFunctionEnableSettings class SqlPostProcessor : PostFormatProcessor { override fun processElement( @@ -38,7 +38,7 @@ class SqlPostProcessor : PostFormatProcessor { rangeToReformat: TextRange, settings: CodeStyleSettings, ): TextRange { - if (!isEnableFormat()) return rangeToReformat + if (!DomaToolsSettingUtil.isEnableFormat(source.project)) return rangeToReformat if (source.language != SqlLanguage.INSTANCE) return rangeToReformat val project: Project = source.project @@ -60,10 +60,4 @@ class SqlPostProcessor : PostFormatProcessor { } return TextRange(0, finalText.length) } - - private fun isEnableFormat(): Boolean { - val setting = DomaToolsFunctionEnableSettings.getInstance() - val isEnableFormat = setting.state.isEnableSqlFormat - return isEnableFormat - } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/processor/InspectionFunctionCallVisitorProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/processor/InspectionFunctionCallVisitorProcessor.kt new file mode 100644 index 00000000..eaa6f9a1 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/processor/InspectionFunctionCallVisitorProcessor.kt @@ -0,0 +1,61 @@ +/* + * 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.PsiMethod +import org.domaframework.doma.intellij.common.helper.ExpressionFunctionsHelper +import org.domaframework.doma.intellij.common.sql.validator.result.ValidationInvalidFunctionCallResult +import org.domaframework.doma.intellij.extension.getJavaClazz +import org.domaframework.doma.intellij.psi.SqlElFunctionCallExpr +import org.domaframework.doma.intellij.setting.state.DomaToolsCustomFunctionSettings + +class InspectionFunctionCallVisitorProcessor( + val shortName: String, + private val element: SqlElFunctionCallExpr, +) : InspectionVisitorProcessor(shortName) { + fun check(holder: ProblemsHolder) { + val project = element.project + val expressionHelper = ExpressionFunctionsHelper + val expressionFunctionalInterface = expressionHelper.setExpressionFunctionsInterface(project) + + val functionName = element.elIdExpr + val expressionFunctionSetting = DomaToolsCustomFunctionSettings.getInstance(project) + val customFunctionClassNames = expressionFunctionSetting.state.customFunctionClassNames + + var methods: Array = emptyArray() + for (clazz in customFunctionClassNames) { + val expressionClazz = project.getJavaClazz(clazz) + if (expressionClazz != null && expressionHelper.isInheritor(expressionClazz)) { + methods = expressionClazz.findMethodsByName(functionName.text, true) + if (methods.isNotEmpty()) { + break + } + } + } + + if (methods.isEmpty()) { + methods = expressionFunctionalInterface?.findMethodsByName(functionName.text, true) ?: emptyArray() + } + + if (methods.isEmpty()) { + ValidationInvalidFunctionCallResult( + functionName, + shortName, + ).highlightElement(holder) + } + } +} 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 6ae7b38d..38bdd2d6 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 @@ -24,10 +24,12 @@ import org.domaframework.doma.intellij.common.isJavaOrKotlinFileType import org.domaframework.doma.intellij.extension.psi.isFirstElement 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.InspectionFunctionCallVisitorProcessor 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.SqlElFunctionCallExpr import org.domaframework.doma.intellij.psi.SqlElPrimaryExpr import org.domaframework.doma.intellij.psi.SqlElStaticFieldAccessExpr import org.domaframework.doma.intellij.psi.SqlTypes @@ -48,6 +50,12 @@ class SqlInspectionVisitor( } } + override fun visitElFunctionCallExpr(element: SqlElFunctionCallExpr) { + super.visitElFunctionCallExpr(element) + val processor = InspectionFunctionCallVisitorProcessor(this.shortName, element) + processor.check(holder) + } + override fun visitElStaticFieldAccessExpr(element: SqlElStaticFieldAccessExpr) { super.visitElStaticFieldAccessExpr(element) val processor = InspectionStaticFieldAccessVisitorProcessor(this.shortName) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/refactoring/dao/DaoPackageRenameListenerProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/refactoring/dao/DaoPackageRenameListenerProcessor.kt index 4b75f41f..54ddfc97 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/refactoring/dao/DaoPackageRenameListenerProcessor.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/refactoring/dao/DaoPackageRenameListenerProcessor.kt @@ -28,7 +28,7 @@ import com.intellij.psi.PsiPackage import com.intellij.refactoring.listeners.RefactoringElementListener import com.intellij.refactoring.listeners.RefactoringElementListenerProvider import com.intellij.util.IncorrectOperationException -import org.domaframework.doma.intellij.common.CommonPathParameter.Companion.RESOURCES_META_INF_PATH +import org.domaframework.doma.intellij.common.CommonPathParameterHelper.RESOURCES_META_INF_PATH import org.domaframework.doma.intellij.common.dao.getDaoClass import org.domaframework.doma.intellij.common.util.PluginLoggerUtil import org.domaframework.doma.intellij.extension.getResourceRoot diff --git a/src/main/kotlin/org/domaframework/doma/intellij/reference/SqlElFunctionCallExprReference.kt b/src/main/kotlin/org/domaframework/doma/intellij/reference/SqlElFunctionCallExprReference.kt new file mode 100644 index 00000000..dc063451 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/reference/SqlElFunctionCallExprReference.kt @@ -0,0 +1,78 @@ +/* + * 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.reference + +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiMethod +import org.domaframework.doma.intellij.common.helper.ExpressionFunctionsHelper +import org.domaframework.doma.intellij.common.util.PluginLoggerUtil +import org.domaframework.doma.intellij.extension.getJavaClazz +import org.domaframework.doma.intellij.psi.SqlElFunctionCallExpr +import org.domaframework.doma.intellij.setting.state.DomaToolsCustomFunctionSettings + +class SqlElFunctionCallExprReference( + element: PsiElement, +) : SqlElExprReference(element) { + override fun superResolveLogic( + startTime: Long, + file: PsiFile, + ): PsiElement? { + val functionCallExpr = element.parent as? SqlElFunctionCallExpr ?: return null + val variableName = functionCallExpr.elIdExpr.text ?: "" + + val project = element.project + val expressionFunctionsInterface = + ExpressionFunctionsHelper.setExpressionFunctionsInterface(project) + ?: return null + + val setting = DomaToolsCustomFunctionSettings.getInstance(element.project) + val customFunctionClassNames = setting.state.customFunctionClassNames + val implementsClasses: MutableList = + customFunctionClassNames.mapNotNull { className -> + val expressionFunction = project.getJavaClazz(className) + if (ExpressionFunctionsHelper.isInheritor(expressionFunction)) { + expressionFunction + } else { + null + } + } as MutableList + + if (implementsClasses.isEmpty()) { + implementsClasses.add(expressionFunctionsInterface) + } + + var reference: PsiMethod? = null + implementsClasses.forEach { clazz -> + // TODO Type checking in parameters + val methods = clazz.findMethodsByName(variableName, true).firstOrNull() + if (methods != null) { + reference = methods + } + } + + if (reference == null) { + PluginLoggerUtil.countLogging( + this::class.java.simpleName, + "ReferenceCustomFunctions", + "Reference", + startTime, + ) + } + return reference + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/reference/SqlPsiReferenceProvider.kt b/src/main/kotlin/org/domaframework/doma/intellij/reference/SqlPsiReferenceProvider.kt index 0cf4ff63..922003d1 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/reference/SqlPsiReferenceProvider.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/reference/SqlPsiReferenceProvider.kt @@ -25,6 +25,7 @@ import com.intellij.util.ProcessingContext import org.domaframework.doma.intellij.psi.SqlCustomElExpr import org.domaframework.doma.intellij.psi.SqlElClass import org.domaframework.doma.intellij.psi.SqlElForDirective +import org.domaframework.doma.intellij.psi.SqlElFunctionCallExpr import org.domaframework.doma.intellij.psi.SqlElIdExpr import org.domaframework.doma.intellij.psi.SqlElStaticFieldAccessExpr import org.domaframework.doma.intellij.psi.SqlTypes @@ -36,34 +37,37 @@ class SqlPsiReferenceProvider : PsiReferenceProvider() { ): Array { if (element !is SqlCustomElExpr) return PsiReference.EMPTY_ARRAY - return when (element) { - is SqlElClass -> arrayOf(SqlElClassExprReference(element)) - is SqlElIdExpr -> - when { - getParentClassPfType(element, SqlElClass::class.java) != null -> - arrayOf( - SqlElClassExprReference(element), - ) + return if (element is SqlElClass) { + arrayOf(SqlElClassExprReference(element)) + } else if (element is SqlElIdExpr) { + when { + getParentClassPsiType(element, SqlElFunctionCallExpr::class.java) != null -> + arrayOf(SqlElFunctionCallExprReference(element)) - getParentClassPfType( - element, - SqlElStaticFieldAccessExpr::class.java, - ) != null -> arrayOf(SqlElStaticFieldReference(element)) + getParentClassPsiType(element, SqlElClass::class.java) != null -> + arrayOf( + SqlElClassExprReference(element), + ) - getParentClassPfType(element, SqlElForDirective::class.java) != null && - element.prevLeaf()?.prevLeaf()?.elementType == SqlTypes.EL_FOR -> - arrayOf( - SqlElForDirectiveIdExprReference(element), - ) + getParentClassPsiType( + element, + SqlElStaticFieldAccessExpr::class.java, + ) != null -> arrayOf(SqlElStaticFieldReference(element)) - else -> arrayOf(SqlElIdExprReference(element)) - } + getParentClassPsiType(element, SqlElForDirective::class.java) != null && + element.prevLeaf()?.prevLeaf()?.elementType == SqlTypes.EL_FOR -> + arrayOf( + SqlElForDirectiveIdExprReference(element), + ) - else -> PsiReference.EMPTY_ARRAY + else -> arrayOf(SqlElIdExprReference(element)) + } + } else { + PsiReference.EMPTY_ARRAY } } - private fun getParentClassPfType( + private fun getParentClassPsiType( element: PsiElement, parentClass: Class, ): R? = PsiTreeUtil.getParentOfType(element, parentClass) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/setting/DomaToolsConfigurable.kt b/src/main/kotlin/org/domaframework/doma/intellij/setting/DomaToolsConfigurable.kt index cdfbd415..d954c79b 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/setting/DomaToolsConfigurable.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/setting/DomaToolsConfigurable.kt @@ -17,34 +17,46 @@ package org.domaframework.doma.intellij.setting import com.intellij.openapi.options.Configurable import com.intellij.openapi.options.ConfigurationException -import org.domaframework.doma.intellij.state.DomaToolsFunctionEnableSettings -import java.util.Objects +import org.domaframework.doma.intellij.common.helper.ActiveProjectHelper +import org.domaframework.doma.intellij.setting.state.DomaToolsCustomFunctionSettings +import org.domaframework.doma.intellij.setting.state.DomaToolsFormatEnableSettings import javax.swing.JComponent class DomaToolsConfigurable : Configurable { private var mySettingsComponent: SettingComponent? = SettingComponent() + private val project = ActiveProjectHelper.getCurrentActiveProject() + + private var formatSettings: DomaToolsFormatEnableSettings? = null + private var customFunctionsSettings: DomaToolsCustomFunctionSettings? = null + + init { + project?.let { + customFunctionsSettings = DomaToolsCustomFunctionSettings.getInstance(it) + formatSettings = DomaToolsFormatEnableSettings.getInstance(it) + } + } override fun getDisplayName(): String = "Doma Tools" override fun createComponent(): JComponent? = mySettingsComponent?.panel override fun isModified(): Boolean { - val state: DomaToolsFunctionEnableSettings.State = - Objects.requireNonNull(DomaToolsFunctionEnableSettings.getInstance().getState()) - return mySettingsComponent?.enableFormat != state.isEnableSqlFormat + val enableFormatModified = formatSettings?.isModified(mySettingsComponent) != false + val customFunctionClassNamesModified = + customFunctionsSettings?.isModified(mySettingsComponent) != false + + return enableFormatModified || customFunctionClassNamesModified } @Throws(ConfigurationException::class) override fun apply() { - val state: DomaToolsFunctionEnableSettings.State = - Objects.requireNonNull(DomaToolsFunctionEnableSettings.getInstance().getState()) - state.isEnableSqlFormat = mySettingsComponent?.enableFormat == true + formatSettings?.apply(mySettingsComponent) + customFunctionsSettings?.apply(mySettingsComponent) } override fun reset() { - val state: DomaToolsFunctionEnableSettings.State = - Objects.requireNonNull(DomaToolsFunctionEnableSettings.getInstance().getState()) - mySettingsComponent?.enableFormat = state.isEnableSqlFormat + formatSettings?.reset(mySettingsComponent) + customFunctionsSettings?.reset(mySettingsComponent) } override fun disposeUIResources() { diff --git a/src/main/kotlin/org/domaframework/doma/intellij/setting/SettingComponent.kt b/src/main/kotlin/org/domaframework/doma/intellij/setting/SettingComponent.kt index 9aa4c2c5..3c0e27c6 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/setting/SettingComponent.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/setting/SettingComponent.kt @@ -15,22 +15,96 @@ */ package org.domaframework.doma.intellij.setting +import com.intellij.ui.ToolbarDecorator import com.intellij.ui.components.JBCheckBox import com.intellij.ui.components.JBLabel +import com.intellij.ui.components.JBList import com.intellij.util.ui.FormBuilder +import com.intellij.util.ui.UIUtil.setEnabledRecursively import org.domaframework.doma.intellij.bundle.MessageBundle +import org.domaframework.doma.intellij.common.helper.ActiveProjectHelper +import org.domaframework.doma.intellij.common.helper.ExpressionFunctionsHelper +import org.domaframework.doma.intellij.extension.getJavaClazz import javax.swing.JPanel +import javax.swing.ListSelectionModel class SettingComponent { val panel: JPanel? private val enableFormatCheckBox = JBCheckBox() + private val customFunctionClassListModel = javax.swing.DefaultListModel() + private val customFunctionClassList = + JBList(customFunctionClassListModel).apply { + selectionMode = ListSelectionModel.SINGLE_SELECTION + emptyText.text = MessageBundle.message("config.custom.functions.dialog.example") + } + init { + val customFunctionPanel = + ToolbarDecorator + .createDecorator(customFunctionClassList) + .setAddAction { actionButton -> + val project = ActiveProjectHelper.getCurrentActiveProject() + if (project != null) { + val expressionFunction = ExpressionFunctionsHelper + val expressionFunctionInterface = + expressionFunction.setExpressionFunctionsInterface(project) + val dialog = + com.intellij.openapi.ui.InputValidatorEx { inputString -> + if (inputString.isNullOrBlank()) { + MessageBundle.message("config.custom.functions.dialog.error.blank") + } else if (expressionFunctionInterface != null) { + val expressionClazz = project.getJavaClazz(inputString) + if (expressionClazz == null || + !expressionFunction.isInheritor(expressionClazz) + ) { + MessageBundle.message("config.custom.functions.dialog.error.invalidClass") + } else { + null + } + } else { + null + } + } + val input = + com.intellij.openapi.ui.Messages.showInputDialog( + "${MessageBundle.message("config.custom.functions.dialog.message")}\n" + + MessageBundle.message("config.custom.functions.dialog.example"), + MessageBundle.message("config.custom.functions.dialog.title"), + null, + "", + dialog, + ) + + if (!input.isNullOrBlank()) { + customFunctionClassListModel.addElement(input.trim()) + } + } + }.setRemoveAction { actionButton -> + val idx = customFunctionClassList.selectedIndex + if (idx >= 0) customFunctionClassListModel.remove(idx) + }.disableUpDownActions() + .createPanel() + + // Disable customFunctionPanel if no active project is available + if (ActiveProjectHelper.getCurrentActiveProject() == null) { + setEnabledRecursively(customFunctionPanel, false) + } + this.panel = FormBuilder .createFormBuilder() - .addLabeledComponent(JBLabel(MessageBundle.message("config.enable.sql.format")), enableFormatCheckBox, 1, false) - .addComponentFillVertically(JPanel(), 0) + .addLabeledComponent( + JBLabel(MessageBundle.message("config.enable.sql.format")), + enableFormatCheckBox, + 1, + false, + ).addLabeledComponent( + JBLabel(MessageBundle.message("config.custom.functions.title")), + customFunctionPanel, + 1, + false, + ).addComponentFillVertically(JPanel(), 0) .panel } @@ -39,4 +113,14 @@ class SettingComponent { set(enabled) { enableFormatCheckBox.isSelected = enabled } + + var customFunctionClassNames: List + get() = + (0 until customFunctionClassListModel.size()).map { + customFunctionClassListModel.getElementAt(it) + } + set(value) { + customFunctionClassListModel.clear() + value.forEach { customFunctionClassListModel.addElement(it) } + } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/setting/state/DomaToolsCustomFunctionSettings.kt b/src/main/kotlin/org/domaframework/doma/intellij/setting/state/DomaToolsCustomFunctionSettings.kt new file mode 100644 index 00000000..c82e2f53 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/setting/state/DomaToolsCustomFunctionSettings.kt @@ -0,0 +1,61 @@ +/* + * 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.setting.state + +import com.intellij.openapi.components.PersistentStateComponent +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage +import com.intellij.openapi.project.Project +import org.domaframework.doma.intellij.setting.SettingComponent + +@State( + name = "DomaToolsCustomFunctionSettings", + storages = [Storage("doma_tools_settings.xml")], +) +@Service(Service.Level.PROJECT) +class DomaToolsCustomFunctionSettings : + PersistentStateComponent, + DomaToolsSettings { + class State { + var customFunctionClassNames: MutableList = mutableListOf() + } + + private var state = State() + + override fun getState(): State = state + + override fun loadState(state: State) { + this.state = state + } + + override fun isModified(component: SettingComponent?): Boolean = component?.customFunctionClassNames != state.customFunctionClassNames + + override fun apply(component: SettingComponent?) { + state.customFunctionClassNames.clear() + component?.customFunctionClassNames?.let { state.customFunctionClassNames.addAll(it) } + } + + override fun reset(component: SettingComponent?) { + component?.let { + it.customFunctionClassNames = state.customFunctionClassNames + } + } + + companion object { + fun getInstance(project: Project): DomaToolsCustomFunctionSettings = project.getService(DomaToolsCustomFunctionSettings::class.java) + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/state/DomaToolsFunctionEnableSettings.kt b/src/main/kotlin/org/domaframework/doma/intellij/setting/state/DomaToolsFormatEnableSettings.kt similarity index 51% rename from src/main/kotlin/org/domaframework/doma/intellij/state/DomaToolsFunctionEnableSettings.kt rename to src/main/kotlin/org/domaframework/doma/intellij/setting/state/DomaToolsFormatEnableSettings.kt index 9f4c25a6..bde6ad97 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/state/DomaToolsFunctionEnableSettings.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/setting/state/DomaToolsFormatEnableSettings.kt @@ -13,22 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.domaframework.doma.intellij.state +package org.domaframework.doma.intellij.setting.state -import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.BaseState import com.intellij.openapi.components.PersistentStateComponent import com.intellij.openapi.components.Service import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage +import com.intellij.openapi.project.Project +import org.domaframework.doma.intellij.setting.SettingComponent -@Service(Service.Level.APP) +@Service(Service.Level.PROJECT) @State( - name = "org.domaframework.doma", + name = "DomaToolsFormatEnableSettings", reloadable = true, - storages = [Storage("DomaTools.xml")], + storages = [Storage("doma_tools_settings.xml")], ) -class DomaToolsFunctionEnableSettings : PersistentStateComponent { +class DomaToolsFormatEnableSettings : + PersistentStateComponent, + DomaToolsSettings { class State : BaseState() { var isEnableSqlFormat = false } @@ -37,12 +40,24 @@ class DomaToolsFunctionEnableSettings : PersistentStateComponent + serviceImplementation="org.domaframework.doma.intellij.setting.state.DomaToolsFormatEnableSettings"/> diff --git a/src/main/resources/messages/DomaToolsBundle.properties b/src/main/resources/messages/DomaToolsBundle.properties index 6f377056..9b8451b7 100644 --- a/src/main/resources/messages/DomaToolsBundle.properties +++ b/src/main/resources/messages/DomaToolsBundle.properties @@ -2,6 +2,12 @@ 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 config.enable.sql.format=Enable SQL Format +config.custom.functions.title=Custom Functions +config.custom.functions.dialog.title=Add Custom Function Class +config.custom.functions.dialog.message=Enter the package name and class name of your custom function +config.custom.functions.dialog.error.blank=Please enter class name +config.custom.functions.dialog.error.invalidClass=Please register a class that implements "org.seasar.doma.expr.ExpressionFunctions" +config.custom.functions.dialog.example=(ex: com.example.MyExpressionFunctions) 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}] @@ -11,4 +17,5 @@ inspection.invalid.sql.staticProperty=[{0}] is not a public or static property i 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}] 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 +inspection.invalid.dao.duplicate=An element name that is a duplicate of an element name defined in SQL is used +inspection.invalid.sql.customFunction=The function [{0}] is not defined in the registered custom function classes diff --git a/src/main/resources/messages/DomaToolsBundle_ja.properties b/src/main/resources/messages/DomaToolsBundle_ja.properties index b21f366a..7a81861c 100644 --- a/src/main/resources/messages/DomaToolsBundle_ja.properties +++ b/src/main/resources/messages/DomaToolsBundle_ja.properties @@ -2,6 +2,12 @@ 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 config.enable.sql.format=SQL\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u3092\u6709\u52B9\u5316 +config.custom.functions.title=\u30AB\u30B9\u30BF\u30E0\u95A2\u6570 +config.custom.functions.dialog.title=\u30AB\u30B9\u30BF\u30E0\u95A2\u6570\u30AF\u30E9\u30B9\u3092\u8FFD\u52A0 +config.custom.functions.dialog.message=\u30AB\u30B9\u30BF\u30E0\u95A2\u6570\u306E\u30D1\u30C3\u30B1\u30FC\u30B8\u540D\u3068\u30AF\u30E9\u30B9\u540D\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044 +config.custom.functions.dialog.example=(\u4F8B: com.example.MyExpressionFunctions) +config.custom.functions.dialog.error.blank=\u30AF\u30E9\u30B9\u540D\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044 +config.custom.functions.dialog.error.invalidClass="org.seasar.doma.expr.ExpressionFunctions"\u306E\u5B9F\u88C5\u30AF\u30E9\u30B9\u3092\u767B\u9332\u3057\u3066\u304F\u3060\u3055\u3044 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}] @@ -11,4 +17,5 @@ inspection.invalid.sql.staticProperty=[{0}]\u0020\u306F\u30AF\u30E9\u30B9\u0020[ 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=\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 +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 +inspection.invalid.sql.customFunction=\u767B\u9332\u3055\u308C\u305F\u30AB\u30B9\u30BF\u30E0\u95A2\u6570\u30AF\u30E9\u30B9\u306B\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u306A\u3044\u95A2\u6570\u304C\u547C\u3073\u51FA\u3055\u308C\u3066\u3044\u307E\u3059:[{0}] diff --git a/src/test/kotlin/org/domaframework/doma/intellij/DomaSqlTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/DomaSqlTest.kt index 3df983e4..2a4bda8d 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/DomaSqlTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/DomaSqlTest.kt @@ -29,6 +29,8 @@ import com.intellij.psi.PsiClass import com.intellij.testFramework.PsiTestUtil import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase import org.domaframework.doma.intellij.extension.getResourcesSQLFile +import org.domaframework.doma.intellij.setting.SettingComponent +import org.domaframework.doma.intellij.setting.state.DomaToolsCustomFunctionSettings import org.jetbrains.jps.model.java.JavaResourceRootType import org.jetbrains.jps.model.java.JavaSourceRootType import org.junit.Ignore @@ -55,6 +57,9 @@ open class DomaSqlTest : LightJavaCodeInsightFixtureTestCase() { addEntityJavaFile("Project.java") addEntityJavaFile("ProjectDetail.java") addEntityJavaFile("Principal.java") + + addExpressionJavaFile("TestExpressionFunctions.java") + addExpressionJavaFile("TestNotExpressionFunctions.java") } @Throws(Exception::class) @@ -97,7 +102,8 @@ open class DomaSqlTest : LightJavaCodeInsightFixtureTestCase() { } private fun setUpJdk(module: Module) { - val newJdk = JavaSdk.getInstance().createJdk("Doma Test JDK", System.getProperty("java.home"), false) + val newJdk = + JavaSdk.getInstance().createJdk("Doma Test JDK", System.getProperty("java.home"), false) WriteAction.runAndWait { ProjectJdkTable.getInstance().addJdk(newJdk) @@ -181,4 +187,20 @@ open class DomaSqlTest : LightJavaCodeInsightFixtureTestCase() { assertNotNull("Not Found [$testDaoName]", dao) return dao } + + protected fun addExpressionJavaFile(fileName: String) { + val file = File("$testDataPath/java/$packagePath/expression/$fileName") + myFixture.addFileToProject( + "main/java/$packagePath/expression/$fileName", + file.readText(), + ) + } + + protected fun settingCustomFunctions(newClassNames: MutableList) { + val settings = DomaToolsCustomFunctionSettings.getInstance(project) + val component = SettingComponent() + component.customFunctionClassNames = newClassNames.toMutableList() + settings.apply(component) + assertEquals(newClassNames, settings.getState().customFunctionClassNames) + } } diff --git a/src/test/kotlin/org/domaframework/doma/intellij/complate/sql/SqlCompleteTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/complate/sql/SqlCompleteTest.kt index 9e6b4738..e670c396 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/complate/sql/SqlCompleteTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/complate/sql/SqlCompleteTest.kt @@ -59,6 +59,7 @@ class SqlCompleteTest : DomaSqlTest() { "$testDapName/completeOptionalByForItem.sql", "$testDapName/completeOptionalBatchAnnotation.sql", "$testDapName/completeForDirectiveItem.sql", + "$testDapName/completeImplementCustomFunction.sql", ) myFixture.enableInspections(SqlBindVariableValidInspector()) } @@ -400,6 +401,36 @@ class SqlCompleteTest : DomaSqlTest() { ) } + fun testCompleteImplementCustomFunction() { + settingCustomFunctions( + mutableListOf("doma.example.expression.TestExpressionFunctions", "doma.example.expression.TestNotExpressionFunctions"), + ) + innerDirectiveCompleteTest( + "$testDapName/completeImplementCustomFunction.sql", + listOf("userId()", "userName()", "userAge()", "langCode()", "isGest()", "isBlank()", "isNotBlank()"), + listOf("getId()", "getName()", "getAge()", "getLangCode()", "isManager()"), + ) + } + + fun testCompleteNotImplementCustomFunction() { + innerDirectiveCompleteTest( + "$testDapName/completeImplementCustomFunction.sql", + listOf("isEmpty()", "roundDownTimePart()", "isBlank()", "isNotBlank()"), + listOf( + "userId()", + "userName()", + "userAge()", + "langCode()", + "isGest()", + "getId()", + "getName()", + "getAge()", + "getLangCode()", + "isManager()", + ), + ) + } + private fun innerDirectiveCompleteTest( sqlFileName: String, expectedSuggestions: List, diff --git a/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt index 82c49e86..901330de 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt @@ -19,7 +19,9 @@ import com.intellij.openapi.command.WriteCommandAction import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.intellij.util.ThrowableRunnable -import org.domaframework.doma.intellij.state.DomaToolsFunctionEnableSettings +import org.domaframework.doma.intellij.common.helper.ActiveProjectHelper +import org.domaframework.doma.intellij.setting.SettingComponent +import org.domaframework.doma.intellij.setting.state.DomaToolsFormatEnableSettings class SqlFormatterTest : BasePlatformTestCase() { override fun getBasePath(): String? = "src/test/testData/sql/formatter" @@ -28,56 +30,66 @@ class SqlFormatterTest : BasePlatformTestCase() { override fun setUp() { super.setUp() - val settings = DomaToolsFunctionEnableSettings.getInstance() - settings.state.isEnableSqlFormat = true + settingSqlFormat(true) + if (project != null) { + ActiveProjectHelper.setCurrentActiveProject(project) + } + } + + private fun settingSqlFormat(enabled: Boolean) { + val settings = DomaToolsFormatEnableSettings.getInstance(project) + val component = SettingComponent() + component.enableFormat = enabled + settings.apply(component) + assertEquals(enabled, settings.getState().isEnableSqlFormat) } override fun tearDown() { try { - val settings = DomaToolsFunctionEnableSettings.getInstance() - settings.state.isEnableSqlFormat = false + settingSqlFormat(false) + ActiveProjectHelper.setCurrentActiveProject(null) } finally { super.tearDown() } } fun testSelectFormatter() { - formatSqlFime("Select.sql", "FormattedSelect.sql") + formatSqlFile("Select.sql", "FormattedSelect.sql") } fun testCreateTableFormatter() { - formatSqlFime("CreateTable.sql", "FormattedCreateTable.sql") + formatSqlFile("CreateTable.sql", "FormattedCreateTable.sql") } fun testCreateViewFormatter() { - formatSqlFime("CreateView.sql", "FormattedCreateView.sql") + formatSqlFile("CreateView.sql", "FormattedCreateView.sql") } fun testInsertFormatter() { - formatSqlFime("Insert.sql", "FormattedInsert.sql") + formatSqlFile("Insert.sql", "FormattedInsert.sql") } fun testInsertWithBindVariableFormatter() { - formatSqlFime("InsertWithBindVariable.sql", "FormattedInsertWithBindVariable.sql") + formatSqlFile("InsertWithBindVariable.sql", "FormattedInsertWithBindVariable.sql") } fun testUpdateFormatter() { - formatSqlFime("Update.sql", "FormattedUpdate.sql") + formatSqlFile("Update.sql", "FormattedUpdate.sql") } fun testUpdateBindVariableFormatter() { - formatSqlFime("UpdateBindVariable.sql", "FormattedUpdateBindVariable.sql") + formatSqlFile("UpdateBindVariable.sql", "FormattedUpdateBindVariable.sql") } fun testUpdateTupleAssignmentFormatter() { - formatSqlFime("UpdateTupleAssignment.sql", "FormattedUpdateTupleAssignment.sql") + formatSqlFile("UpdateTupleAssignment.sql", "FormattedUpdateTupleAssignment.sql") } fun testDeleteFormatter() { - formatSqlFime("Delete.sql", "FormattedDelete.sql") + formatSqlFile("Delete.sql", "FormattedDelete.sql") } - private fun formatSqlFime( + private fun formatSqlFile( beforeFile: String, afterFile: String, ) { diff --git a/src/test/kotlin/org/domaframework/doma/intellij/inspection/sql/ParameterDefinedTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/inspection/sql/ParameterDefinedTest.kt index 817c55d1..d68636ca 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/inspection/sql/ParameterDefinedTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/inspection/sql/ParameterDefinedTest.kt @@ -16,6 +16,7 @@ package org.domaframework.doma.intellij.inspection.sql import org.domaframework.doma.intellij.DomaSqlTest +import org.domaframework.doma.intellij.common.helper.ActiveProjectHelper import org.domaframework.doma.intellij.inspection.sql.inspector.SqlBindVariableValidInspector /** @@ -39,6 +40,7 @@ class ParameterDefinedTest : DomaSqlTest() { "$testDaoName/callStaticPropertyPackageName.sql", "$testDaoName/bindVariableForItemHasNextAndIndex.sql", "$testDaoName/optionalDaoParameterFieldAccess.sql", + "$testDaoName/implementCustomFunctions.sql", ) myFixture.enableInspections(SqlBindVariableValidInspector()) } @@ -122,4 +124,20 @@ class ParameterDefinedTest : DomaSqlTest() { myFixture.testHighlighting(false, false, false, sqlFile) } + + fun testImplementCustomFunctions() { + if (project != null) { + ActiveProjectHelper.setCurrentActiveProject(project) + } + settingCustomFunctions( + mutableListOf("doma.example.expression.TestExpressionFunctions", "doma.example.expression.TestNotExpressionFunctions"), + ) + + val sqlFile = + findSqlFile("$testDaoName/implementCustomFunctions.sql") + assertNotNull("Not Found SQL File", sqlFile) + if (sqlFile == null) return + + myFixture.testHighlighting(false, false, false, sqlFile) + } } diff --git a/src/test/kotlin/org/domaframework/doma/intellij/reference/SqlReferenceTestCase.kt b/src/test/kotlin/org/domaframework/doma/intellij/reference/SqlReferenceTestCase.kt index 2589c929..f12c6fc8 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/reference/SqlReferenceTestCase.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/reference/SqlReferenceTestCase.kt @@ -45,6 +45,7 @@ class SqlReferenceTestCase : DomaSqlTest() { addSqlFile("$testPackage/$testDaoName/referenceStaticField.sql") addSqlFile("$testPackage/$testDaoName/referenceListFieldMethod.sql") addSqlFile("$testPackage/$testDaoName/referenceForItem.sql") + addSqlFile("$testPackage/$testDaoName/referenceCustomFunction.sql") } fun testReferenceDaoMethodParameter() { @@ -126,6 +127,20 @@ class SqlReferenceTestCase : DomaSqlTest() { ) } + fun testReferenceCustomFunction() { + settingCustomFunctions( + mutableListOf("doma.example.expression.TestExpressionFunctions", "doma.example.expression.TestNotExpressionFunctions"), + ) + referenceTest( + "referenceCustomFunction", + mapOf( + "detail" to listOf("$daoParameterResolve:detail"), + "projectDetailId" to listOf("$fieldResolve:projectDetailId"), + "userId" to listOf("$methodResolve:userId"), + ), + ) + } + private fun referenceTest( sqlFileName: String, resolveExpects: Map>, diff --git a/src/test/kotlin/org/domaframework/doma/intellij/setting/state/DomaToolsCustomFunctionSettingsTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/setting/state/DomaToolsCustomFunctionSettingsTest.kt new file mode 100644 index 00000000..96c726f1 --- /dev/null +++ b/src/test/kotlin/org/domaframework/doma/intellij/setting/state/DomaToolsCustomFunctionSettingsTest.kt @@ -0,0 +1,78 @@ +/* + * 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.setting.state + +import org.domaframework.doma.intellij.DomaSqlTest +import org.domaframework.doma.intellij.setting.SettingComponent + +class DomaToolsCustomFunctionSettingsTest : DomaSqlTest() { + private val packageName = "doma.example.expression" + + override fun setUp() { + super.setUp() + } + + fun testCustomFunctionClassNamesPersistence() { + val settings = DomaToolsCustomFunctionSettings.getInstance(project) + val testClassNames = + mutableListOf( + "$packageName.TestExpressionFunctions", + "$packageName.TestNotExpressionFunctions", + ) + settings.getState().customFunctionClassNames = testClassNames.toMutableList() + + val loaded = DomaToolsCustomFunctionSettings.State() + loaded.customFunctionClassNames = testClassNames.toMutableList() + settings.loadState(loaded) + + assertEquals(testClassNames, settings.getState().customFunctionClassNames) + } + + fun testApplyAndReset() { + val settings = DomaToolsCustomFunctionSettings.getInstance(project) + val component = SettingComponent() + val initialClassNames = mutableListOf("$packageName.TestExpressionFunctions") + val newClassNames = mutableListOf("$packageName.TestExpressionFunctions", "$packageName.TestNotExpressionFunctions") + + // init + settings.getState().customFunctionClassNames = initialClassNames.toMutableList() + settings.reset(component) + assertEquals(initialClassNames, component.customFunctionClassNames) + + // apply + component.customFunctionClassNames = newClassNames.toMutableList() + settings.apply(component) + assertEquals(newClassNames, settings.getState().customFunctionClassNames) + + // reset + settings.getState().customFunctionClassNames = initialClassNames.toMutableList() + settings.reset(component) + assertEquals(initialClassNames, component.customFunctionClassNames) + } + + fun testIsModified() { + val settings = DomaToolsCustomFunctionSettings.getInstance(project) + val component = SettingComponent() + val classNames = mutableListOf("$packageName.TestExpressionFunctions") + settings.getState().customFunctionClassNames = classNames.toMutableList() + component.customFunctionClassNames = classNames.toMutableList() + assertFalse(settings.isModified(component)) + + val addClassNames = mutableListOf("$packageName.TestExpressionFunctions", "$packageName.TestNotExpressionFunctions") + component.customFunctionClassNames = addClassNames + assertTrue(settings.isModified(component)) + } +} diff --git a/src/test/testData/sql/formatter/FormattedInsertWithBindVariable.sql b/src/test/testData/sql/formatter/FormattedInsertWithBindVariable.sql index 3f06b1bb..b5e7119b 100644 --- a/src/test/testData/sql/formatter/FormattedInsertWithBindVariable.sql +++ b/src/test/testData/sql/formatter/FormattedInsertWithBindVariable.sql @@ -7,7 +7,7 @@ INSERT INTO /*# tableName */ , x3 , x4) VALUES ( /* reportId */1 - , /* @tenantId() */1 + , /* reportId */1 /*%for entity : entities */ , /* entity.value */'abc' /*%end*/ diff --git a/src/test/testData/sql/formatter/InsertWithBindVariable.sql b/src/test/testData/sql/formatter/InsertWithBindVariable.sql index 9f5b2726..4b854f81 100644 --- a/src/test/testData/sql/formatter/InsertWithBindVariable.sql +++ b/src/test/testData/sql/formatter/InsertWithBindVariable.sql @@ -6,7 +6,7 @@ INSERT INTO /*# tableName */ /*%end*/ , x3 , x4) - VALUES ( /* reportId */1, /* @tenantId() */1 /*%for entity : entities */, /* entity.value */'abc' + VALUES ( /* reportId */1, /* reportId */1 /*%for entity : entities */, /* entity.value */'abc' /*%end*/ , /* @userId() */1 , x5 diff --git a/src/test/testData/src/main/java/doma/example/dao/EmployeeSummaryDao.java b/src/test/testData/src/main/java/doma/example/dao/EmployeeSummaryDao.java index dd2c3fd5..96f34b78 100644 --- a/src/test/testData/src/main/java/doma/example/dao/EmployeeSummaryDao.java +++ b/src/test/testData/src/main/java/doma/example/dao/EmployeeSummaryDao.java @@ -39,4 +39,7 @@ interface EmployeeSummaryDao { @Select Project optionalDaoParameterFieldAccess(Optional project, OptionalInt id, OptionalLong longId, OptionalDouble doubleId); + + @Select + EmployeeSummary implementCustomFunctions(EmployeeSummary employee); } \ No newline at end of file diff --git a/src/test/testData/src/main/java/doma/example/dao/SqlCompleteTestDao.java b/src/test/testData/src/main/java/doma/example/dao/SqlCompleteTestDao.java index df0becd9..74a77bb3 100644 --- a/src/test/testData/src/main/java/doma/example/dao/SqlCompleteTestDao.java +++ b/src/test/testData/src/main/java/doma/example/dao/SqlCompleteTestDao.java @@ -100,4 +100,7 @@ interface SqlCompleteTestDao { @Select Employee completeForDirectiveItem(List projects); + @Select + Employee completeImplementCustomFunction(Project project); + } \ No newline at end of file diff --git a/src/test/testData/src/main/java/doma/example/dao/reference/ReferenceTestDao.java b/src/test/testData/src/main/java/doma/example/dao/reference/ReferenceTestDao.java index 9cdede9f..704ccab2 100644 --- a/src/test/testData/src/main/java/doma/example/dao/reference/ReferenceTestDao.java +++ b/src/test/testData/src/main/java/doma/example/dao/reference/ReferenceTestDao.java @@ -22,4 +22,7 @@ public interface ReferenceTestDao { @Select List referenceForItem(List> employeesList); + @Select + Project referenceCustomFunction(ProjectDetail detail); + } \ No newline at end of file diff --git a/src/test/testData/src/main/java/doma/example/expression/TestExpressionFunctions.java b/src/test/testData/src/main/java/doma/example/expression/TestExpressionFunctions.java new file mode 100644 index 00000000..4542a5de --- /dev/null +++ b/src/test/testData/src/main/java/doma/example/expression/TestExpressionFunctions.java @@ -0,0 +1,41 @@ +package doma.example.expression; + +import org.seasar.doma.jdbc.dialect.StandardDialect; + +// doma.example.expression.TestExpressionFunctions +public class TestExpressionFunctions extends StandardDialect.StandardExpressionFunctions { + + private static final char DEFAULT_ESCAPE_CHAR = '\\'; + + public TestExpressionFunctions() { + super(DEFAULT_ESCAPE_CHAR, null); + } + + public TestExpressionFunctions(char[] wildcards) { + super(wildcards); + } + + protected TestExpressionFunctions(char escapeChar, char[] wildcards) { + super(escapeChar, wildcards); + } + + public Integer userId() { + return 10000000; + } + + public String userName() { + return "userName"; + } + + public Integer userAge() { + return 99; + } + + public String langCode() { + return "ja"; + } + + public boolean isGest() { + return true; + } +} \ No newline at end of file diff --git a/src/test/testData/src/main/java/doma/example/expression/TestNotExpressionFunctions.java b/src/test/testData/src/main/java/doma/example/expression/TestNotExpressionFunctions.java new file mode 100644 index 00000000..e278a2cf --- /dev/null +++ b/src/test/testData/src/main/java/doma/example/expression/TestNotExpressionFunctions.java @@ -0,0 +1,25 @@ +package doma.example.expression; + +// doma.example.expression.TestNotExpressionFunctions +public class TestNotExpressionFunctions { + + public Integer getId() { + return 10000000; + } + + public String getName() { + return "userName"; + } + + public Integer getAge() { + return 99; + } + + public String getLangCode() { + return "ja"; + } + + public boolean isManager() { + return true; + } +} \ No newline at end of file diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/bindVariableInFunctionParameters.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/bindVariableInFunctionParameters.sql index 214906b3..91553b7d 100644 --- a/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/bindVariableInFunctionParameters.sql +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/bindVariableInFunctionParameters.sql @@ -8,5 +8,5 @@ WHERE p.employee_id = /* employee.employeeParam(employee.count) */0 /*%if employee != null && (employee.employeeId > 100 || employee.employeeId < 50) */ AND p.employee_id = /* employee.employeeParam(employee.dist, rank) */0 -OR flag = /* @authUser() */false +OR flag = /* @isNotBlank() */false /*%end */ \ No newline at end of file diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/implementCustomFunctions.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/implementCustomFunctions.sql new file mode 100644 index 00000000..b2e0ffc5 --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/implementCustomFunctions.sql @@ -0,0 +1,10 @@ +SELECT + e.employee_id + , u.user_id + , u.user_name + FROM user u + WHERE p.employee_id = /* employee.employeeId */0 + AND p.user_id = /* employee.userId */0 + OR is_gest = /* @isGest() */false + OR flag = /* @authUser() */false + AND lang = /* @getLangCode() */'en' \ No newline at end of file diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeImplementCustomFunction.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeImplementCustomFunction.sql new file mode 100644 index 00000000..327b072a --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeImplementCustomFunction.sql @@ -0,0 +1,4 @@ +SELECT * FROM project +WHERE project_id = /* project.projectId */0 +AND manager = /* project.manager.userId */0 +AND lang = /* @ */'en' \ No newline at end of file diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/reference/ReferenceTestDao/referenceCustomFunction.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/reference/ReferenceTestDao/referenceCustomFunction.sql new file mode 100644 index 00000000..c1c96ef4 --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/reference/ReferenceTestDao/referenceCustomFunction.sql @@ -0,0 +1,4 @@ +SELECT * + FROM project_detail + WHERE detail_id = /* detail.projectDetailId */0 + AND manager_id = /* @userId() */0