diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0adc53a1..19a73de5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,7 +36,7 @@ jobs: uses: gradle/actions/wrapper-validation@v4 - name: Setup Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: zulu java-version: 17 @@ -116,7 +116,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: zulu java-version: 17 @@ -174,7 +174,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: zulu java-version: 17 diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 74cf50c6..38b205a3 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -57,7 +57,7 @@ jobs: ref: main - name: Setup Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: zulu java-version: 17 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 958fd6c1..9ffef11c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,7 +26,7 @@ jobs: # Set up Java environment for the next steps - name: Setup Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: zulu java-version: 17 diff --git a/.github/workflows/run-ui-tests.yml b/.github/workflows/run-ui-tests.yml index e1ab7ca2..2fa423ed 100644 --- a/.github/workflows/run-ui-tests.yml +++ b/.github/workflows/run-ui-tests.yml @@ -37,7 +37,7 @@ jobs: # Set up Java environment for the next steps - name: Setup Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: zulu java-version: 17 diff --git a/.github/workflows/update_changelog.yml b/.github/workflows/update_changelog.yml index e733a718..e22d2cb3 100644 --- a/.github/workflows/update_changelog.yml +++ b/.github/workflows/update_changelog.yml @@ -45,7 +45,7 @@ jobs: ref: ${{ github.event.release.tag_name }} - name: Setup Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: zulu java-version: 17 diff --git a/CHANGELOG.md b/CHANGELOG.md index e38028d0..b92586b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,30 @@ ## [Unreleased] +## [2.3.0] - 2025-09-17 + +### New Features + +- Enable SQL Annotation Conversion Action from DAO Methods and Control SQL File Opening ([#451]) +- Check Parameter Types for Method Calls in Bind Variables ([#448]) + +### Bug Fixes + +- Fix: Annotation Option Checks Respect Entity Inheritance ([#454]) +- Fix Multiple Registration Error for SQL File to Annotation Conversion Actions ([#452]) + +### Maintenance + +- Set Upper Limit for Plugin Support Versions ([#456]) +- Refactor SQL Formatter Block Relations and Translate Comments ([#444]) + +### Dependency Updates + +- Update actions/setup-java action to v5 ([#407]) +- Update dependency org.jetbrains.kotlinx.kover to v0.9.2 ([#455]) +- Update dependency org.jetbrains.kotlin.jvm to v2.2.20 ([#447]) +- Update dependency org.jetbrains.intellij.platform to v2.9.0 ([#443]) + ## [2.2.1] - 2025-09-03 ### Bug Fixes @@ -358,7 +382,8 @@ - Rename SQL file directory when renaming DAO - Change DAO package name or SQL file directory configuration when changing configuration -[Unreleased]: https://github.com/domaframework/doma-tools-for-intellij/compare/v2.2.1...HEAD +[Unreleased]: https://github.com/domaframework/doma-tools-for-intellij/compare/v2.3.0...HEAD +[2.3.0]: https://github.com/domaframework/doma-tools-for-intellij/compare/v2.2.1...v2.3.0 [2.2.1]: https://github.com/domaframework/doma-tools-for-intellij/compare/v2.2.0...v2.2.1 [2.2.0]: https://github.com/domaframework/doma-tools-for-intellij/compare/v2.1.2...v2.2.0 [2.1.2]: https://github.com/domaframework/doma-tools-for-intellij/compare/v2.1.1...v2.1.2 @@ -409,7 +434,16 @@ [#56]: https://github.com/domaframework/doma-tools-for-intellij/pull/56 [#49]: https://github.com/domaframework/doma-tools-for-intellij/pull/49 [#48]: https://github.com/domaframework/doma-tools-for-intellij/pull/48 +[#456]: https://github.com/domaframework/doma-tools-for-intellij/pull/456 +[#455]: https://github.com/domaframework/doma-tools-for-intellij/pull/455 +[#454]: https://github.com/domaframework/doma-tools-for-intellij/pull/454 +[#452]: https://github.com/domaframework/doma-tools-for-intellij/pull/452 +[#451]: https://github.com/domaframework/doma-tools-for-intellij/pull/451 [#45]: https://github.com/domaframework/doma-tools-for-intellij/pull/45 +[#448]: https://github.com/domaframework/doma-tools-for-intellij/pull/448 +[#447]: https://github.com/domaframework/doma-tools-for-intellij/pull/447 +[#444]: https://github.com/domaframework/doma-tools-for-intellij/pull/444 +[#443]: https://github.com/domaframework/doma-tools-for-intellij/pull/443 [#442]: https://github.com/domaframework/doma-tools-for-intellij/pull/442 [#441]: https://github.com/domaframework/doma-tools-for-intellij/pull/441 [#440]: https://github.com/domaframework/doma-tools-for-intellij/pull/440 @@ -424,6 +458,7 @@ [#419]: https://github.com/domaframework/doma-tools-for-intellij/pull/419 [#417]: https://github.com/domaframework/doma-tools-for-intellij/pull/417 [#411]: https://github.com/domaframework/doma-tools-for-intellij/pull/411 +[#407]: https://github.com/domaframework/doma-tools-for-intellij/pull/407 [#396]: https://github.com/domaframework/doma-tools-for-intellij/pull/396 [#394]: https://github.com/domaframework/doma-tools-for-intellij/pull/394 [#389]: https://github.com/domaframework/doma-tools-for-intellij/pull/389 diff --git a/gradle.properties b/gradle.properties index 908ca28d..c8f886c2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,10 @@ pluginGroup = org.domaframework.doma.intellij pluginName = Doma Tools for IntelliJ pluginRepositoryUrl = https://github.com/domaframework/doma-tools-for-intellij -pluginVersion = 2.3.0-beta +pluginVersion = 2.3.1-beta pluginSinceBuild=233 +pluginUntilBuild=252.* platformType = IC platformVersion = 2024.3.1 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7616d1c7..43219271 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ pluginVerifier = "1.395" changelog = "2.4.0" intelliJPlatform = "2.9.0" kotlin = "2.2.20" -kover = "0.9.1" +kover = "0.9.2" qodana = "2024.3.4" grammarkit = "2022.3.2.2" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2a84e188..2e111328 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index ef07e016..adff685a 100755 --- a/gradlew +++ b/gradlew @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/gradlew.bat b/gradlew.bat index 5eed7ee8..e509b2dd 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/src/main/kotlin/org/domaframework/doma/intellij/action/dao/BulkConvertSqlAnnotationToFileAction.kt b/src/main/kotlin/org/domaframework/doma/intellij/action/dao/BulkConvertSqlAnnotationToFileAction.kt new file mode 100644 index 00000000..be6fee83 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/action/dao/BulkConvertSqlAnnotationToFileAction.kt @@ -0,0 +1,105 @@ +/* + * 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.action.dao + +import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction +import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo +import com.intellij.codeInsight.intention.preview.IntentionPreviewUtils +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +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.bundle.MessageBundle +import org.domaframework.doma.intellij.common.dao.getDaoClass +import org.domaframework.doma.intellij.common.psi.PsiDaoMethod +import org.domaframework.doma.intellij.common.util.PluginLoggerUtil + +/** + * Intention action to convert @Sql annotation to SQL file + */ +class BulkConvertSqlAnnotationToFileAction : PsiElementBaseIntentionAction() { + override fun getFamilyName(): String = MessageBundle.message("bulk.convert.sql.annotation.to.file.family") + + override fun getText(): String = MessageBundle.message("bulk.convert.sql.annotation.to.file.text") + + override fun isAvailable( + project: Project, + editor: Editor?, + element: PsiElement, + ): Boolean { + val daoClass = findDaoClassElement(element) ?: return false + if (getDaoClass(daoClass.containingFile) == null) return false + + val methods = getTargetMethods(daoClass, project) + + return methods.isNotEmpty() + } + + override fun generatePreview( + project: Project, + editor: Editor, + file: PsiFile, + ): IntentionPreviewInfo = IntentionPreviewInfo.EMPTY + + override fun invoke( + project: Project, + editor: Editor?, + element: PsiElement, + ) { + // Do nothing when previewing + if (IntentionPreviewUtils.isIntentionPreviewActive()) return + val startTime = System.nanoTime() + val daoClass = findDaoClassElement(element) ?: return + // Already checked in isAvailable, should not be null here + check(getDaoClass(daoClass.containingFile) != null) { "DAO class should be available" } + + val methods = getTargetMethods(daoClass, project) + + if (methods.isEmpty()) return + + WriteCommandAction.runWriteCommandAction(project) { + methods.reversed().forEach { method -> + val converter = SqlAnnotationConverter(project, method) + converter.convertToSqlFile() + } + } + + PluginLoggerUtil.countLogging( + className = this::class.java.simpleName, + actionName = "convertSqlAnnotationToFileBatch", + inputName = "IntentionAction", + start = startTime, + ) + } + + private fun getTargetMethods( + daoClass: PsiClass, + project: Project, + ): List = + daoClass.methods.filter { method -> + val isSupportedDaoMethod = SqlAnnotationConverter.supportedTypes.any { it.getPsiAnnotation(method) != null } + val psiDaoMethod = PsiDaoMethod(project, method) + val isSqlAnnotationUsed = psiDaoMethod.useSqlAnnotation() + val hasSqlFileWithParent = psiDaoMethod.sqlFile?.parent != null + + isSqlAnnotationUsed && !hasSqlFileWithParent && isSupportedDaoMethod + } + + private fun findDaoClassElement(element: PsiElement): PsiClass? = element as? PsiClass ?: element.parent as? PsiClass +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/action/dao/BulkConvertSqlFileToAnnotationAction.kt b/src/main/kotlin/org/domaframework/doma/intellij/action/dao/BulkConvertSqlFileToAnnotationAction.kt new file mode 100644 index 00000000..248044b3 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/action/dao/BulkConvertSqlFileToAnnotationAction.kt @@ -0,0 +1,107 @@ +/* + * 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.action.dao + +import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction +import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo +import com.intellij.codeInsight.intention.preview.IntentionPreviewUtils +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +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.bundle.MessageBundle +import org.domaframework.doma.intellij.common.dao.getDaoClass +import org.domaframework.doma.intellij.common.psi.PsiDaoMethod +import org.domaframework.doma.intellij.common.util.PluginLoggerUtil + +/** + * Intention action to convert SQL file to @Sql annotation + */ +class BulkConvertSqlFileToAnnotationAction : PsiElementBaseIntentionAction() { + override fun getFamilyName(): String = MessageBundle.message("bulk.convert.sql.file.to.annotation.family") + + override fun getText(): String = MessageBundle.message("bulk.convert.sql.file.to.annotation.text") + + override fun isAvailable( + project: Project, + editor: Editor?, + element: PsiElement, + ): Boolean { + val daoClass = findDaoClassElement(element) ?: return false + if (getDaoClass(daoClass.containingFile) == null) return false + + val methods = getTargetMethods(daoClass, project) + + return methods.isNotEmpty() + } + + override fun generatePreview( + project: Project, + editor: Editor, + file: PsiFile, + ): IntentionPreviewInfo = IntentionPreviewInfo.EMPTY + + override fun invoke( + project: Project, + editor: Editor?, + element: PsiElement, + ) { + // Do nothing when previewing + if (IntentionPreviewUtils.isIntentionPreviewActive()) return + val startTime = System.nanoTime() + val daoClass = findDaoClassElement(element) ?: return + // Already checked in isAvailable, should not be null here + check(getDaoClass(daoClass.containingFile) != null) { "DAO class should be available" } + + val methods = getTargetMethods(daoClass, project) + if (methods.isEmpty()) return + + WriteCommandAction.runWriteCommandAction(project) { + methods.reversed().forEach { method -> + val converter = SqlAnnotationConverter(project, method) + converter.convertToSqlAnnotation() + } + } + + PluginLoggerUtil.countLogging( + className = this::class.java.simpleName, + actionName = "convertSqlFileToAnnotationBatch", + inputName = "IntentionAction", + start = startTime, + ) + } + + private fun getTargetMethods( + daoClass: PsiClass, + project: Project, + ): List = + daoClass.methods.filter { method -> + val isSupportedDaoMethod = SqlAnnotationConverter.supportedTypes.any { it.getPsiAnnotation(method) != null } + val psiDaoMethod = PsiDaoMethod(project, method) + val isSqlAnnotationUsed = psiDaoMethod.useSqlAnnotation() + val hasSqlFileWithParent = psiDaoMethod.sqlFile?.parent != null + + // For SQL file to annotation conversion, we need: + // - SQL file exists + // - @Sql annotation is NOT used + !isSqlAnnotationUsed && hasSqlFileWithParent && isSupportedDaoMethod + } + + private fun findDaoClassElement(element: PsiElement): PsiClass? = element as? PsiClass ?: element.parent as? PsiClass +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/action/dao/ConvertSqlAnnotationToFileAction.kt b/src/main/kotlin/org/domaframework/doma/intellij/action/dao/ConvertSqlAnnotationToFileAction.kt index 6d3ddfa6..c2fa480e 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/action/dao/ConvertSqlAnnotationToFileAction.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/action/dao/ConvertSqlAnnotationToFileAction.kt @@ -49,9 +49,10 @@ class ConvertSqlAnnotationToFileAction : PsiElementBaseIntentionAction() { // Check if method has @Sql annotation // When a Sql annotation is present, a virtual SQL file is associated; // therefore, check the parent and exclude the injected (inline) SQL. - if (getDaoClass(method.containingFile) == null || !psiDaoMethod.useSqlAnnotation() || - psiDaoMethod.sqlFile != null && psiDaoMethod.sqlFile?.parent != null - ) { + val isDaoClassMissing = getDaoClass(method.containingFile) == null + val isSqlAnnotationNotUsed = !psiDaoMethod.useSqlAnnotation() + val hasSqlFileWithParent = psiDaoMethod.sqlFile != null && psiDaoMethod.sqlFile?.parent != null + if (isDaoClassMissing || isSqlAnnotationNotUsed || hasSqlFileWithParent) { return false } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/action/dao/SqlAnnotationConverter.kt b/src/main/kotlin/org/domaframework/doma/intellij/action/dao/SqlAnnotationConverter.kt index 70b5940d..fcc19325 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/action/dao/SqlAnnotationConverter.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/action/dao/SqlAnnotationConverter.kt @@ -28,15 +28,15 @@ import com.intellij.psi.PsiJavaFile import com.intellij.psi.PsiLiteralExpression import com.intellij.psi.PsiMethod import com.intellij.psi.PsiNameValuePair +import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.JavaCodeStyleManager +import com.intellij.testFramework.LightVirtualFile import org.domaframework.doma.intellij.common.dao.jumpToDaoMethod import org.domaframework.doma.intellij.common.psi.PsiDaoMethod -import org.domaframework.doma.intellij.common.util.InjectionSqlUtil import org.domaframework.doma.intellij.common.util.StringUtil import org.domaframework.doma.intellij.extension.findFile import org.domaframework.doma.intellij.extension.psi.DomaAnnotationType import org.domaframework.doma.intellij.formatter.processor.InjectionSqlFormatter -import org.domaframework.doma.intellij.formatter.visitor.FormattingTask /** * Class that handles conversion between @Sql annotation and SQL files @@ -81,13 +81,16 @@ class SqlAnnotationConverter( */ fun convertToSqlAnnotation() { val sqlFile = psiDaoMethod.sqlFile ?: return + val sqlPsiFile = project.findFile(sqlFile) ?: return + formatSql(sqlPsiFile) + val sqlContent = readSqlFileContent(sqlFile) ?: return val targetAnnotation = findTargetAnnotation() ?: return - setSqlFileOption(targetAnnotation, false) addSqlAnnotation(sqlContent) + + if (sqlFile is LightVirtualFile) return deleteSqlFile(sqlFile) - formatInjectionSql(targetAnnotation) } private fun extractSqlContent(sqlAnnotation: PsiAnnotation): String? { @@ -180,32 +183,60 @@ class SqlAnnotationConverter( } private fun addSqlAnnotation(sqlContent: String) { + val sqlAnnotation = createSqlAnnotation(sqlContent) + val modifierList = method.modifierList + val targetAnnotation = findTargetAnnotation() + val documentManager = PsiDocumentManager.getInstance(project) ?: return + + // Disable Java formatting to apply custom indentation to text blocks. + CodeStyleManager.getInstance(project).performActionWithFormatterDisabled { + if (targetAnnotation != null) { + modifierList.addAfter(sqlAnnotation, targetAnnotation) + } else { + modifierList.add(sqlAnnotation) + } + + val psiFile = method.containingFile + val document = documentManager.getDocument(psiFile) + if (document != null) { + documentManager.doPostponedOperationsAndUnblockDocument(document) + } + } + + addImports(documentManager) + + val newDaoFile = method.containingFile + val newDocument = documentManager.getDocument(newDaoFile) + if (newDocument != null) { + documentManager.doPostponedOperationsAndUnblockDocument(newDocument) + } + jumpToDaoMethod(project, psiDaoMethod.sqlFile?.nameWithoutExtension ?: return, newDaoFile.virtualFile) + } + + private fun createSqlAnnotation(sqlContent: String): PsiAnnotation { val escapedContent = sqlContent .replace("\\", "\\\\") .replace("\"", "\\\"") - val annotationText = "@Sql(\"\"\"\n$escapedContent\"\"\")" - val sqlAnnotation = elementFactory.createAnnotationFromText(annotationText, null) + // During Sql annotation indentation correction, add spaces equal to the number of “(” characters. + val indentationSpaces = InjectionSqlFormatter.createSpaceIndent(project) + val replaceIndentContent = escapedContent.replace("\n", "\n" + indentationSpaces) + val annotationText = + buildString { + append("@Sql(") + .append(StringUtil.TRIPLE_QUOTE) + .append(StringUtil.LINE_SEPARATE) + .append(indentationSpaces) + .append(replaceIndentContent) + .append(StringUtil.TRIPLE_QUOTE) + .append(")") + } - val modifierList = method.modifierList - val targetAnnotation = findTargetAnnotation() + return elementFactory.createAnnotationFromText(annotationText, null) + } - if (targetAnnotation != null) { - // Add after the target annotation - modifierList.addAfter(sqlAnnotation, targetAnnotation) - // modifierList.addAfter(elementFactory.createWhiteSpaceFromText("\n"), targetAnnotation) - } else { - // Add at the beginning - modifierList.add(sqlAnnotation) - } - val psiFile = method.containingFile - val documentManager = PsiDocumentManager.getInstance(project) - val document = documentManager.getDocument(psiFile) - if (document != null) { - documentManager.doPostponedOperationsAndUnblockDocument(document) - } - // Add import if needed + private fun addImports(documentManager: PsiDocumentManager): Boolean { val containingFile = method.containingFile // TODO Support Kotlin files in the future if (containingFile is PsiJavaFile) { @@ -223,7 +254,7 @@ class SqlAnnotationConverter( JavaPsiFacade.getInstance(project).findClass( sqlImport, method.resolveScope, - ) ?: return, + ) ?: return true, ) importList?.add(importStatement) val psiFile = method.containingFile @@ -233,14 +264,7 @@ class SqlAnnotationConverter( } } } - - // Jump to method - val newDaoFile = method.containingFile - val newDocument = documentManager.getDocument(newDaoFile) - if (newDocument != null) { - documentManager.doPostponedOperationsAndUnblockDocument(newDocument) - } - jumpToDaoMethod(project, psiDaoMethod.sqlFile?.nameWithoutExtension ?: return, newDaoFile.virtualFile) + return false } private fun generateSqlFileWithContent(content: String) { @@ -258,24 +282,18 @@ class SqlAnnotationConverter( val documentManager = PsiDocumentManager.getInstance(project) val document = documentManager.getDocument(psiFile) ?: return@runWriteCommandAction - // Replace the default content with the actual SQL content document.setText(content) documentManager.commitDocument(document) - - // Format Sql formatSql(psiFile) } } } private fun deleteSqlFile(virtualFile: VirtualFile) { - // Close the file if it's open in the editor val editorManager = FileEditorManager.getInstance(project) if (editorManager.isFileOpen(virtualFile)) { editorManager.closeFile(virtualFile) } - - // Delete the file virtualFile.delete(null) } @@ -288,23 +306,6 @@ class SqlAnnotationConverter( } } - private fun formatInjectionSql(annotation: PsiAnnotation) { - val method = psiDaoMethod.psiMethod - val sqlAnnotation = DomaAnnotationType.Sql.getPsiAnnotation(method) ?: return - val valueExpression = getSqlAnnotationLiteralExpression(sqlAnnotation) ?: return - val injectedSql = InjectionSqlUtil.initInjectionElement(method.containingFile, project, valueExpression) ?: return - - val host = InjectionSqlUtil.getLiteralExpressionHost(injectedSql.containingFile) ?: return - val originalText = host.value?.toString() ?: return - - val injectionFormatter = InjectionSqlFormatter(annotation.project) - val formattingTask = FormattingTask(host, originalText, false) - injectionFormatter.convertExpressionToTextBlock(formattingTask.expression) - injectionFormatter.processFormattingTask(formattingTask) { text -> - processDocumentText(text) - } - } - private fun processDocumentText(originalText: String): String { val withoutTrailingSpaces = removeTrailingSpaces(originalText) return ensureProperFileEnding(withoutTrailingSpaces) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/util/InjectionSqlUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/util/InjectionSqlUtil.kt index b182beb4..58af7bdb 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/util/InjectionSqlUtil.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/util/InjectionSqlUtil.kt @@ -38,11 +38,13 @@ object InjectionSqlUtil { } fun isInjectedSqlFile(source: PsiFile): Boolean { + if (!source.isValid) return false val injectedLanguageManager = InjectedLanguageManager.getInstance(source.project) return injectedLanguageManager.isInjectedFragment(source) } fun getLiteralExpressionHost(source: PsiFile): PsiLiteralExpression? { + if (!source.isValid) return null val injectedLanguageManager = InjectedLanguageManager.getInstance(source.project) return injectedLanguageManager.getInjectionHost(source) as? PsiLiteralExpression } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/util/PluginUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/util/PluginUtil.kt index f9639a05..80f38ade 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/util/PluginUtil.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/util/PluginUtil.kt @@ -19,7 +19,7 @@ import com.intellij.ide.plugins.PluginManagerCore import com.intellij.openapi.extensions.PluginId const val PLUGIN_ID = "org.domaframework.doma.intellij" -const val PLUGIN_VERSION = "2.3.0-beta" +const val PLUGIN_VERSION = "2.3.1-beta" open class PluginUtil { companion object { diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/util/StringUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/util/StringUtil.kt index 4c82b9ca..3fa67f38 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/util/StringUtil.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/util/StringUtil.kt @@ -16,8 +16,9 @@ package org.domaframework.doma.intellij.common.util object StringUtil { - const val LINE_SEPARATE: String = "\n" + const val LINE_SEPARATE: Char = '\n' const val SINGLE_SPACE: String = " " + const val TRIPLE_QUOTE: String = "\"\"\"" fun getSqlElClassText(text: String): String = text diff --git a/src/main/kotlin/org/domaframework/doma/intellij/document/ForItemElementDocumentationProvider.kt b/src/main/kotlin/org/domaframework/doma/intellij/document/ForItemElementDocumentationProvider.kt index f3ac8b0e..64fe6d28 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/document/ForItemElementDocumentationProvider.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/document/ForItemElementDocumentationProvider.kt @@ -63,7 +63,7 @@ class ForItemElementDocumentationProvider : AbstractDocumentationProvider() { generator.generateDocument() - return result.joinToString(StringUtil.LINE_SEPARATE) + return result.joinToString(StringUtil.LINE_SEPARATE.toString()) } override fun generateHoverDoc( @@ -74,10 +74,10 @@ class ForItemElementDocumentationProvider : AbstractDocumentationProvider() { override fun getQuickNavigateInfo( element: PsiElement, originalElement: PsiElement?, - ): String? { + ): String { val result: MutableList = LinkedList() val typeDocument = generateDoc(element, originalElement) result.add(typeDocument) - return result.joinToString(StringUtil.LINE_SEPARATE) + return result.joinToString(StringUtil.LINE_SEPARATE.toString()) } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt index ea8c8a2c..955bdd40 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt @@ -134,7 +134,7 @@ open class SqlFileBlock( updateCommentParentAndIdent(childBlock) blocks.add(childBlock) } else { - if (lastBlock !is SqlLineCommentBlock) { + if (lastBlock !is SqlLineCommentBlock && child.prevSibling != null) { blocks.add( SqlWhitespaceBlock( child, diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlFormattingModelBuilder.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlFormattingModelBuilder.kt index a653063c..00f527ed 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlFormattingModelBuilder.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlFormattingModelBuilder.kt @@ -47,7 +47,7 @@ class SqlFormattingModelBuilder : FormattingModelBuilder { settings: CodeStyleSettings, ): FormattingModel { val setting = DomaToolsFormatEnableSettings.getInstance() - val isEnableFormat = setting.state.isEnableSqlFormat == true + val isEnableFormat = setting.state.isEnableSqlFormat val formatMode = formattingContext.formattingMode val spacingBuilder = createSpaceBuilder(settings) val customSpacingBuilder = createCustomSpacingBuilder() diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/InjectionSqlFormatter.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/InjectionSqlFormatter.kt index 95711ca3..e4a1f0ef 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/InjectionSqlFormatter.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/InjectionSqlFormatter.kt @@ -38,21 +38,20 @@ class InjectionSqlFormatter( companion object { private const val TEMP_FILE_PREFIX = "temp_format" private const val SQL_FILE_EXTENSION = ".sql" - private const val TRIPLE_QUOTE = "\"\"\"" private const val WRITE_COMMAND_NAME = "Format Injected SQL" private val COMMENT_START_REGEX = Regex("^[ \t]*/[*][ \t]*\\*") + + fun createSpaceIndent(project: Project): String { + val settings = CodeStyle.getSettings(project) + val java = settings.getIndentOptions(JavaFileType.INSTANCE) + val indentSize = java.INDENT_SIZE + val prefixLen = "@Sql(${StringUtil.TRIPLE_QUOTE}".length + return StringUtil.SINGLE_SPACE.repeat(indentSize.plus(prefixLen)) + } } private var baseIndent = createSpaceIndent(project) - private fun createSpaceIndent(project: Project): String { - val settings = CodeStyle.getSettings(project) - val java = settings.getIndentOptions(JavaFileType.INSTANCE) - val indentSize = java.INDENT_SIZE - val prefixLen = "@Sql(${TRIPLE_QUOTE}".length - return StringUtil.SINGLE_SPACE.repeat(indentSize.plus(prefixLen)) - } - private val injectionManager by lazy { InjectedLanguageManager.getInstance(project) } private val documentManager by lazy { PsiDocumentManager.getInstance(project) } private val codeStyleManager by lazy { CodeStyleManager.getInstance(project) } @@ -70,14 +69,9 @@ class InjectionSqlFormatter( ?.firstOrNull() ?.first as? PsiFile ?: return - val formattedText = - if (!task.isOriginalTextBlock) { - val result = - SqlFormatPreProcessor().updateDocument(injectionFile, injectionFile.textRange) - result.document?.text ?: return - } else { - task.formattedText - } + val result = + SqlFormatPreProcessor().updateDocument(injectionFile, injectionFile.textRange) + val formattedText = result.document?.text ?: return replaceHostStringLiteral(FormattingTask(task.expression, formattedText, task.isOriginalTextBlock), removeSpace) } @@ -98,7 +92,7 @@ class InjectionSqlFormatter( } private fun removeIndentLines(sqlText: String): String = - sqlText.lines().joinToString(StringUtil.LINE_SEPARATE) { line -> + sqlText.lines().joinToString(StringUtil.LINE_SEPARATE.toString()) { line -> val processedLine = if (COMMENT_START_REGEX.containsMatchIn(line)) { // Remove spaces between /* and comment content, as IntelliJ Java formatter may insert them @@ -181,7 +175,7 @@ class InjectionSqlFormatter( */ private fun updateBaseIndent(task: FormattingTask) { val input = PsiTreeUtil.prevLeaf(task.expression)?.text - val prevTexts = countCharsToLineBreak(task.expression).plus(TRIPLE_QUOTE.length) + val prevTexts = countCharsToLineBreak(task.expression).plus(StringUtil.TRIPLE_QUOTE.length) if (input != null) { val matches = StringUtil.SINGLE_SPACE.repeat(prevTexts) baseIndent = matches @@ -210,10 +204,10 @@ class InjectionSqlFormatter( */ private fun createFormattedLiteralText(formattedSqlText: String): String = buildString { - append(TRIPLE_QUOTE) + append(StringUtil.TRIPLE_QUOTE) append(StringUtil.LINE_SEPARATE) append(formattedSqlText) - append(TRIPLE_QUOTE) + append(StringUtil.TRIPLE_QUOTE) } /** @@ -238,7 +232,7 @@ class InjectionSqlFormatter( append(literalSeparator) if (normalizedLines.isNotEmpty()) { append(StringUtil.LINE_SEPARATE) - append(normalizedLines.joinToString(StringUtil.LINE_SEPARATE)) + append(normalizedLines.joinToString(StringUtil.LINE_SEPARATE.toString())) } } } @@ -284,5 +278,11 @@ class InjectionSqlFormatter( documentManager.commitDocument(document) } - private fun convertToTextBlock(content: String): String = "\"\"\"\n${content.replace("\"\"\"", "\\\"\\\"\\\"")}${TRIPLE_QUOTE}" + private fun convertToTextBlock(content: String): String = + buildString { + append(StringUtil.TRIPLE_QUOTE) + .append(StringUtil.LINE_SEPARATE) + .append(content.replace(StringUtil.TRIPLE_QUOTE, "\\\"\\\"\\\"")) + .append(StringUtil.TRIPLE_QUOTE) + } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPostProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPostProcessor.kt index 90b43a33..695b1df2 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPostProcessor.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPostProcessor.kt @@ -43,7 +43,7 @@ class SqlFormatPostProcessor : SqlPostProcessor() { } val document = getDocument(source) ?: return rangeToReformat - val processedText = processDocumentText(document.text) + val processedText = processDocumentText(source.text) if (document.text == processedText) { return rangeToReformat diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPreProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPreProcessor.kt index c0cdbccf..28c9342f 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPreProcessor.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPreProcessor.kt @@ -17,6 +17,7 @@ package org.domaframework.doma.intellij.formatter.processor import com.intellij.lang.ASTNode import com.intellij.lang.injection.InjectedLanguageManager +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.Document import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiDocumentManager @@ -26,8 +27,10 @@ import com.intellij.psi.PsiLiteralExpression import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.TokenType import com.intellij.psi.impl.source.codeStyle.PreFormatProcessor +import com.intellij.psi.tree.IElementType import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.elementType +import com.intellij.psi.util.endOffset import com.intellij.psi.util.prevLeafs import org.domaframework.doma.intellij.common.util.InjectionSqlUtil.isInjectedSqlFile import org.domaframework.doma.intellij.common.util.PluginLoggerUtil @@ -38,8 +41,15 @@ import org.domaframework.doma.intellij.formatter.visitor.SqlFormatVisitor import org.domaframework.doma.intellij.psi.SqlTypes import org.domaframework.doma.intellij.setting.SqlLanguage import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.utils.rethrow class SqlFormatPreProcessor : PreFormatProcessor { + data class TextReplacement( + val offset: Int, + val length: Int, + val replacement: String, + ) + private val targetElementTypes = setOf( SqlTypes.KEYWORD, @@ -79,7 +89,7 @@ class SqlFormatPreProcessor : PreFormatProcessor { return updateDocument(source, rangeToReformat).range } - private fun shouldFormatFile(source: PsiFile): Boolean = source.language == SqlLanguage.INSTANCE || isInjectedSqlFile(source) + private fun shouldFormatFile(source: PsiFile): Boolean = source.language == SqlLanguage.INSTANCE && !isInjectedSqlFile(source) private fun shouldProcessInjectedFile(source: PsiFile): Boolean { if (!isInjectedSqlFile(source)) return true @@ -91,160 +101,295 @@ class SqlFormatPreProcessor : PreFormatProcessor { return host?.isTextBlock == true } + /** + * PSI-safe fast processing + */ fun updateDocument( source: PsiFile, rangeToReformat: TextRange, ): ProcessResult { - val visitor = SqlFormatVisitor() - source.accept(visitor) - val docManager = PsiDocumentManager.getInstance(source.project) val document = docManager.getDocument(source) ?: return ProcessResult(null, rangeToReformat) - val keywordList = visitor.replaces.filter { it.elementType != TokenType.WHITE_SPACE } - val replaceKeywordList = visitor.replaces.filter { it.elementType == SqlTypes.KEYWORD } - var index = keywordList.size - var keywordIndex = replaceKeywordList.size + if (!source.isValid) { + return ProcessResult(document, rangeToReformat) + } + + try { + val visitor = SqlFormatVisitor() + source.accept(visitor) - visitor.replaces.asReversed().forEach { current -> - if (current.elementType != TokenType.WHITE_SPACE) { - processNonWhiteSpaceElement(current, document) - index-- - if (current.elementType == SqlTypes.KEYWORD) keywordIndex-- - } else { - removeSpacesAroundNewline(document, current as PsiWhiteSpace) + if (!source.isValid) { + return ProcessResult(document, rangeToReformat) } - } - trimLeadingWhitespaceAtFileHead(document, source, rangeToReformat) - docManager.commitDocument(document) + // Bulk string-based processing + val originalText = document.getText(rangeToReformat) + val formattedText = + processTextWithPsiInfo(originalText, visitor.replaces, rangeToReformat.startOffset, document) + + if (formattedText != originalText) { + val newRange = + ApplicationManager.getApplication().runWriteAction { + if (isInjectedSqlFile(source)) { + updateInjectedDocument(source, formattedText, rangeToReformat, docManager) + } else { + updateRegularDocument(document, formattedText, rangeToReformat, source, docManager) + } + } + + return ProcessResult(document, newRange) + } - return ProcessResult(document, rangeToReformat.grown(visitor.replaces.size)) + return ProcessResult(document, rangeToReformat.grown(visitor.replaces.size)) + } catch (e: Exception) { + rethrow(e) + return ProcessResult(document, rangeToReformat) + } } - private fun trimLeadingWhitespaceAtFileHead( - document: Document, - source: PsiFile, + /** + * Update injection document + */ + private fun updateInjectedDocument( + injectedFile: PsiFile, + formattedText: String, rangeToReformat: TextRange, - ) { - if (rangeToReformat.startOffset != 0) return - if (isInjectedSqlFile(source)) return - - val chars = document.charsSequence - if (chars.isEmpty()) return - - var start = 0 - // Save Bom - if (chars[0] == '\uFEFF') start = 1 - - var i = start - while (i < chars.length) { - val c = chars[i] - if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { - i++ - } else { - break + docManager: PsiDocumentManager, + ): TextRange { + val injectionManager = InjectedLanguageManager.getInstance(injectedFile.project) + val injectionHost = + injectionManager.getInjectionHost(injectedFile) as? PsiLiteralExpression + ?: return rangeToReformat + + val hostFile = injectionHost.containingFile + val hostDocument = docManager.getDocument(hostFile) ?: return rangeToReformat + + // Update injection document directly + val injectedDocument = docManager.getDocument(injectedFile) + if (injectedDocument != null && injectedDocument.isWritable) { + injectedDocument.replaceString( + rangeToReformat.startOffset, + rangeToReformat.endOffset, + formattedText, + ) + + docManager.commitDocument(injectedDocument) + + // Wait for PSI resynchronization + PsiDocumentManager.getInstance(injectedFile.project).performForCommittedDocument(hostDocument) { + if (injectedFile.isValid) { + return@performForCommittedDocument + } } } - if (i > start) { - document.deleteString(start, i) - } + + return TextRange(rangeToReformat.startOffset, rangeToReformat.startOffset + formattedText.length) } - private fun processNonWhiteSpaceElement( - current: PsiElement, + /** + * Regular document update + */ + private fun updateRegularDocument( document: Document, - ) { - val textRangeStart = current.startOffset - val textRangeEnd = textRangeStart + current.text.length - val newText = getProcessedText(current) + formattedText: String, + rangeToReformat: TextRange, + source: PsiFile, + docManager: PsiDocumentManager, + ): TextRange { + if (rangeToReformat.endOffset <= document.textLength) { + val newText = trimLeadingWhitespaceAtFileHead(formattedText, source, rangeToReformat) + document.replaceString( + rangeToReformat.startOffset, + rangeToReformat.endOffset, + newText, + ) + docManager.commitDocument(document) + + return TextRange(rangeToReformat.startOffset, rangeToReformat.startOffset + newText.length) + } - document.deleteString(textRangeStart, textRangeEnd) - document.insertString(textRangeStart, newText) + return rangeToReformat } - private fun getProcessedText(current: PsiElement): String { - val newKeyword = - when (current.elementType) { - SqlTypes.KEYWORD -> processKeyword(current) - SqlTypes.LEFT_PAREN -> getNewLineLeftParenString(current.prevSibling, getUpperText(current)) - SqlTypes.RIGHT_PAREN -> getRightPatternNewText(current) - SqlTypes.WORD, SqlTypes.FUNCTION_NAME -> getWordNewText(current) - SqlTypes.COMMA, SqlTypes.OTHER -> getNewLineString(current.prevSibling, getUpperText(current)) - SqlTypes.BLOCK_COMMENT_START -> processBlockCommentStart(current) - else -> getUpperText(current) - } + /** + * Fast string processing using PSI information + */ + private fun processTextWithPsiInfo( + originalText: String, + psiElements: List, + baseOffset: Int, + document: Document, + ): String { + val validElements = + psiElements + .filter { element -> + element.isValid && element.containingFile.isValid + }.reversed() - return newKeyword + if (validElements.isEmpty()) return originalText + + val replacements = buildReplacementMap(validElements, baseOffset, document.charsSequence) + return applyReplacements(originalText, replacements) } - private fun processKeyword(current: PsiElement): String { - val escapes = current.prevLeafs.filter { it.elementType == SqlTypes.OTHER }.toList() - return if (hasEscapeBeforeWhiteSpace(escapes, current.node)) { - current.text - } else { - getKeywordNewText(current) + /** + * Efficient replacement map construction + */ + private fun buildReplacementMap( + elements: List, + baseOffset: Int, + documentText: CharSequence, + ): List { + val replacements = mutableListOf() + + var newNextElementStartOffset: Int? = elements.first().nextSibling?.startOffset + var newNextElementText = elements.first().nextSibling?.text ?: "" + var nextElementType: IElementType? = elements.first().nextSibling?.elementType + elements.forEach { element -> + val offset = element.startOffset + if (offset < baseOffset || offset >= documentText.length) return@forEach + + val relativeOffset = offset - baseOffset + val originalText = element.text + + val newText = + when (element.elementType) { + TokenType.WHITE_SPACE -> { + if (element is PsiWhiteSpace) { + processWhiteSpaceElement(element, originalText, newNextElementStartOffset, newNextElementText, nextElementType) + } else { + originalText + } + } + SqlTypes.KEYWORD -> processKeywordElement(element) + SqlTypes.LEFT_PAREN -> processLeftParenElement(element) + SqlTypes.RIGHT_PAREN -> processRightParenElement(element) + SqlTypes.WORD, SqlTypes.FUNCTION_NAME -> processWordElement(element) + SqlTypes.COMMA, SqlTypes.OTHER -> processCommaOtherElement(element) + SqlTypes.BLOCK_COMMENT_START -> processBlockCommentStartElement(element) + else -> getUpperText(element) + } + newNextElementText = newText + newNextElementStartOffset = offset + nextElementType = element.elementType + if (newText != originalText && relativeOffset >= 0) { + replacements.add( + TextReplacement(relativeOffset, originalText.length, newText), + ) + } } + + return replacements.sortedBy { it.offset } } - private fun processBlockCommentStart(current: PsiElement): String = - if (current.nextSibling?.elementType == SqlTypes.BLOCK_COMMENT_CONTENT) { - "$LINE_SEPARATE${getUpperText(current)}" - } else { - getNewLineString(PsiTreeUtil.prevLeaf(current), getUpperText(current)) - } + /** + * Fast string replacement + */ + private fun applyReplacements( + originalText: String, + replacements: List, + ): String { + if (replacements.isEmpty()) return originalText - private fun removeSpacesAroundNewline( - document: Document, - element: PsiWhiteSpace, - ) { - val range = element.textRange - val originalText = document.getText(range) - val nextElement = element.nextSibling - val nextElementText = nextElement?.let { document.getText(it.textRange) } ?: "" - val preElement = element.prevSibling + val result = StringBuilder() + var currentIndex = 0 + + replacements.forEach { replacement -> + if (replacement.offset > currentIndex) { + result.append(originalText.substring(currentIndex, replacement.offset)) + } + result.append(replacement.replacement) + currentIndex = replacement.offset + replacement.length + } + if (currentIndex < originalText.length) { + result.append(originalText.substring(currentIndex)) + } - val newText = calculateNewWhiteSpaceText(element, nextElement, originalText, nextElementText, preElement) - document.replaceString(range.startOffset, range.endOffset, newText) + return result.toString() } - private fun calculateNewWhiteSpaceText( + /** + * PSI element modification processing + */ + private fun processWhiteSpaceElement( element: PsiWhiteSpace, - nextElement: PsiElement?, originalText: String, - nextElementText: String, - preElement: PsiElement?, + newNextElementStartOffset: Int?, + newNextElementText: String, + nextElementType: IElementType?, ): String { - if (!targetElementTypes.contains(nextElement?.elementType) && preElement?.elementType != SqlTypes.BLOCK_COMMENT) { + val nextElementText = + if (newNextElementStartOffset == element.textRange.endOffset) { + newNextElementText + } else { + element.nextSibling?.text ?: "" + } + val nextElementType = + if (newNextElementStartOffset == element.textRange.endOffset) { + nextElementType + } else { + element.nextSibling?.elementType + } + val preElement = element.prevSibling + + if (!targetElementTypes.contains(nextElementType) && + preElement?.elementType != SqlTypes.BLOCK_COMMENT + ) { return SINGLE_SPACE } if (element.prevSibling == null) return "" - return when (nextElement.elementType) { - SqlTypes.LINE_COMMENT -> processLineCommentWhiteSpace(originalText, nextElementText) - else -> processDefaultWhiteSpace(originalText, nextElementText) + val hasNewline = originalText.indexOf(LINE_SEPARATE) >= 0 + + return when (nextElementType) { + SqlTypes.LINE_COMMENT -> { + when { + nextElementText.startsWith(LINE_SEPARATE) -> SINGLE_SPACE + hasNewline -> LINE_SEPARATE.toString() + else -> SINGLE_SPACE + } + } + else -> { + when { + nextElementText.indexOf(LINE_SEPARATE) >= 0 -> SINGLE_SPACE + hasNewline -> LINE_SEPARATE.toString() + else -> LINE_SEPARATE.toString() + } + } } } - private fun processLineCommentWhiteSpace( - originalText: String, - nextElementText: String, - ): String = - when { - nextElementText.startsWith(LINE_SEPARATE) -> SINGLE_SPACE - originalText.contains(LINE_SEPARATE) -> originalText.replace(Regex("\\s*\\n\\s*"), LINE_SEPARATE) - else -> SINGLE_SPACE + private fun processKeywordElement(element: PsiElement): String { + val escapes = element.prevLeafs.filter { it.elementType == SqlTypes.OTHER }.toList() + return if (hasEscapeBeforeWhiteSpace(escapes, element.node)) { + element.text + } else { + getKeywordNewText(element) + } + } + + private fun processLeftParenElement(element: PsiElement): String = getNewLineLeftParenString(element.prevSibling, getUpperText(element)) + + private fun processRightParenElement(element: PsiElement): String = getNewLineString(element.prevSibling, element.text) + + private fun processWordElement(element: PsiElement): String { + val prev = findPreviousNonWhiteSpaceElement(element) + return if (prev?.elementType == SqlTypes.BLOCK_COMMENT) { + getNewLineString(element.prevSibling, getUpperText(element)) + } else { + getUpperText(element) } + } - private fun processDefaultWhiteSpace( - originalText: String, - nextElementText: String, - ): String = - when { - nextElementText.contains(LINE_SEPARATE) -> SINGLE_SPACE - originalText.contains(LINE_SEPARATE) -> originalText.replace(Regex("\\s*\\n\\s*"), LINE_SEPARATE) - else -> LINE_SEPARATE + private fun processCommaOtherElement(element: PsiElement): String = getNewLineString(element.prevSibling, getUpperText(element)) + + private fun processBlockCommentStartElement(element: PsiElement): String = + if (element.nextSibling?.elementType == SqlTypes.BLOCK_COMMENT_CONTENT) { + "$LINE_SEPARATE${getUpperText(element)}" + } else { + getNewLineString(PsiTreeUtil.prevLeaf(element), getUpperText(element)) } private fun hasEscapeBeforeWhiteSpace( @@ -255,10 +400,7 @@ class SqlFormatPreProcessor : PreFormatProcessor { prevBlocks.count { it.elementType == SqlTypes.OTHER && it.text in escapeCharacters } - - if (escapeCount % 2 == 0) return false - - return findEscapeEndCharacter(start) + return if (escapeCount % 2 == 0) false else findEscapeEndCharacter(start) } private fun findEscapeEndCharacter(start: ASTNode): Boolean { @@ -281,7 +423,7 @@ class SqlFormatPreProcessor : PreFormatProcessor { val upperText = getUpperText(element) val shouldAddNewLine = SqlKeywordUtil.getIndentType(keywordText).isNewLineGroup() || - element.prevSibling.elementType == SqlTypes.BLOCK_COMMENT + element.prevSibling?.elementType == SqlTypes.BLOCK_COMMENT return if (shouldAddNewLine) { getNewLineString(element.prevSibling, upperText) @@ -290,21 +432,6 @@ class SqlFormatPreProcessor : PreFormatProcessor { } } - private fun getRightPatternNewText(element: PsiElement): String { - val elementText = element.text - return getNewLineString(element.prevSibling, elementText) - } - - private fun getWordNewText(element: PsiElement): String { - val prev = findPreviousNonWhiteSpaceElement(element) - - return if (prev?.elementType == SqlTypes.BLOCK_COMMENT) { - getNewLineString(element.prevSibling, getUpperText(element)) - } else { - getUpperText(element) - } - } - private fun findPreviousNonWhiteSpaceElement(element: PsiElement): PsiElement? { var prev = element.prevSibling while (prev != null && prev.elementType != SqlTypes.LEFT_PAREN && prev.elementType != SqlTypes.COMMA) { @@ -350,8 +477,36 @@ class SqlFormatPreProcessor : PreFormatProcessor { element.text } + private fun trimLeadingWhitespaceAtFileHead( + document: String, + source: PsiFile, + rangeToReformat: TextRange, + ): String { + if (rangeToReformat.startOffset != 0 || !source.isValid) return document + if (isInjectedSqlFile(source)) return document + + if (document.isEmpty()) return document + + var start = 0 + if (document[0] == '\uFEFF') start = 1 + + var i = start + while (i < document.length) { + val c = document[i] + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { + i++ + } else { + break + } + } + if (i > start && i <= document.length) { + document.removeRange(start, i) + } + return document + } + private fun logging() { - PluginLoggerUtil.Companion.countLogging( + PluginLoggerUtil.countLogging( this::class.java.simpleName, "SqlFormat", "Format", diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlInjectionPostProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlInjectionPostProcessor.kt index 409f51fd..bb796fb8 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlInjectionPostProcessor.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlInjectionPostProcessor.kt @@ -79,7 +79,7 @@ class SqlInjectionPostProcessor : SqlPostProcessor() { */ private fun processInjectedFragment(source: PsiFile) { val host = InjectionSqlUtil.getLiteralExpressionHost(source) ?: return - val originalText = host.value?.toString() ?: return + val originalText = source.text ?: return val injectionFormatter = InjectionSqlFormatter(source.project) val formattingTask = FormattingTask(host, originalText, host.isTextBlock) diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index eeecc60f..e5ba1b09 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -75,7 +75,7 @@ implementationClass="org.domaframework.doma.intellij.formatter.builder.SqlFormattingModelBuilder"/> - + Doma tools true + + JAVA + org.domaframework.doma.intellij.action.dao.BulkConvertSqlAnnotationToFileAction + Doma tools + true + + + JAVA + org.domaframework.doma.intellij.action.dao.BulkConvertSqlFileToAnnotationAction + Doma tools + true + diff --git a/src/main/resources/intentionDescriptions/BulkConvertSqlAnnotationToFileAction/description.html b/src/main/resources/intentionDescriptions/BulkConvertSqlAnnotationToFileAction/description.html new file mode 100644 index 00000000..12a4bc66 --- /dev/null +++ b/src/main/resources/intentionDescriptions/BulkConvertSqlAnnotationToFileAction/description.html @@ -0,0 +1,7 @@ + + +

+ Provides an intention action to bulk convert `@Sql` annotations in DAO class methods to references to corresponding SQL files. +

+ + \ No newline at end of file diff --git a/src/main/resources/intentionDescriptions/BulkConvertSqlFileToAnnotationAction/description.html b/src/main/resources/intentionDescriptions/BulkConvertSqlFileToAnnotationAction/description.html new file mode 100644 index 00000000..62a3886c --- /dev/null +++ b/src/main/resources/intentionDescriptions/BulkConvertSqlFileToAnnotationAction/description.html @@ -0,0 +1,7 @@ + + +

+ Provides an intention action to bulk convert SQL file references in DAO class methods to `@Sql` annotations containing the SQL file contents. +

+ + \ No newline at end of file diff --git a/src/main/resources/logback-test.xml b/src/main/resources/logback-test.xml index ba01d37a..8a42bbaf 100644 --- a/src/main/resources/logback-test.xml +++ b/src/main/resources/logback-test.xml @@ -2,7 +2,7 @@ - + diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index ba01d37a..8a42bbaf 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -2,7 +2,7 @@ - + diff --git a/src/main/resources/messages/DomaToolsBundle.properties b/src/main/resources/messages/DomaToolsBundle.properties index 37c85408..d05349f4 100644 --- a/src/main/resources/messages/DomaToolsBundle.properties +++ b/src/main/resources/messages/DomaToolsBundle.properties @@ -40,10 +40,14 @@ inspection.invalid.dao.annotation.option.field=Field [{0}] specified in [{1}] op inspection.invalid.dao.annotation.option.embeddable=Field [{0}] specified in [{1}] option is an Embeddable type "{2}". Must specify its properties. Available properties: [{3}] inspection.invalid.dao.annotation.option.primitive=Field path [{0}] specified in [{2}] option is invalid. Field [{1}] is a primitive type and does not have nested properties convert.sql.annotation.to.file.family=Convert @Sql annotation to SQL file -convert.sql.annotation.to.file.text=Convert to SQL file (set sqlFile=true) +convert.sql.annotation.to.file.text=Convert @Sql annotation to SQL file convert.sql.file.to.annotation.from.dao.family=Convert SQL file to @Sql annotation convert.sql.file.to.annotation.from.dao.text=Convert SQL file to @Sql annotation -convert.sql.file.to.annotation.from.sql.family=Convert to @Sql annotation -convert.sql.file.to.annotation.from.sql.text=Convert to @Sql annotation in DAO method +convert.sql.file.to.annotation.from.sql.family=Convert SQL file to @Sql annotation +convert.sql.file.to.annotation.from.sql.text=Convert SQL file to @Sql annotation +bulk.convert.sql.annotation.to.file.family=Bulk convert @sql annotation to SQL file +bulk.convert.sql.annotation.to.file.text=Bulk convert @Sql Annotation to SQL File inspection.invalid.sql.function.call.parameter.count=Function {0} definition with {1} parameters not found -inspection.invalid.sql.function.call.parameter.type.mismatch=Function {0} parameter type mismatch. Actual types: ({1})
Definition candidates:
{2} \ No newline at end of file +inspection.invalid.sql.function.call.parameter.type.mismatch=Function {0} parameter type mismatch. Actual types: ({1})
Definition candidates:
{2} +bulk.convert.sql.file.to.annotation.text=Bulk convert SQL File to @Sql Annotation +bulk.convert.sql.file.to.annotation.family=Bulk convert SQL File to @Sql Annotation \ 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 229311aa..12a782a1 100644 --- a/src/main/resources/messages/DomaToolsBundle_ja.properties +++ b/src/main/resources/messages/DomaToolsBundle_ja.properties @@ -40,10 +40,14 @@ inspection.invalid.dao.annotation.option.field=[{1}]\u30AA\u30D7\u30B7\u30E7\u30 inspection.invalid.dao.annotation.option.embeddable=[{1}]\u30AA\u30D7\u30B7\u30E7\u30F3\u3067\u6307\u5B9A\u3055\u308C\u305F\u30D5\u30A3\u30FC\u30EB\u30C9[{0}]\u306FEmbeddable\u578B"{2}"\u3067\u3059\u3002\u305D\u306E\u30D7\u30ED\u30D1\u30C6\u30A3\u3092\u6307\u5B9A\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002\u5229\u7528\u53EF\u80FD\u306A\u30D7\u30ED\u30D1\u30C6\u30A3: [{3}] inspection.invalid.dao.annotation.option.primitive=[{2}]\u30AA\u30D7\u30B7\u30E7\u30F3\u3067\u6307\u5B9A\u3055\u308C\u305F\u30D5\u30A3\u30FC\u30EB\u30C9\u30D1\u30B9[{0}]\u306F\u7121\u52B9\u3067\u3059\u3002\u30D5\u30A3\u30FC\u30EB\u30C9[{1}]\u306F\u30D7\u30EA\u30DF\u30C6\u30A3\u30D6\u578B\u3067\u3042\u308A\u3001\u30CD\u30B9\u30C8\u3055\u308C\u305F\u30D7\u30ED\u30D1\u30C6\u30A3\u3092\u6301\u3061\u307E\u305B\u3093 convert.sql.annotation.to.file.family=@Sql\u30A2\u30CE\u30C6\u30FC\u30B7\u30E7\u30F3\u3092SQL\u30D5\u30A1\u30A4\u30EB\u306B\u5909\u63DB -convert.sql.annotation.to.file.text=SQL\u30D5\u30A1\u30A4\u30EB\u306B\u5909\u63DB (sqlFile=true\u306B\u8A2D\u5B9A) -convert.sql.file.to.annotation.from.dao.family=SQL\u30D5\u30A1\u30A4\u30EB\u3092@Sql\u30A2\u30CE\u30C6\u30FC\u30B7\u30E7\u30F3\u306B\u5909\u63DB -convert.sql.file.to.annotation.from.dao.text=@Sql\u30A2\u30CE\u30C6\u30FC\u30B7\u30E7\u30F3\u306B\u5909\u63DB (sqlFile=false\u306B\u8A2D\u5B9A) -convert.sql.file.to.annotation.from.sql.family=SQL\u30D5\u30A1\u30A4\u30EB\u3092@Sql\u30A2\u30CE\u30C6\u30FC\u30B7\u30E7\u30F3\u306B\u5909\u63DB -convert.sql.file.to.annotation.from.sql.text=@Sql\u30A2\u30CE\u30C6\u30FC\u30B7\u30E7\u30F3\u306B\u5909\u63DB (sqlFile=false\u306B\u8A2D\u5B9A) +convert.sql.annotation.to.file.text=@Sql\u30A2\u30CE\u30C6\u30FC\u30B7\u30E7\u30F3\u3092SQL\u30D5\u30A1\u30A4\u30EB\u306B\u5909\u63DB +convert.sql.file.to.annotation.from.dao.family=@Sql\u30A2\u30CE\u30C6\u30FC\u30B7\u30E7\u30F3\u306B\u5909\u63DB +convert.sql.file.to.annotation.from.dao.text=@Sql\u30A2\u30CE\u30C6\u30FC\u30B7\u30E7\u30F3\u306B\u5909\u63DB +convert.sql.file.to.annotation.from.sql.family=@Sql\u30A2\u30CE\u30C6\u30FC\u30B7\u30E7\u30F3\u306B\u5909\u63DB +convert.sql.file.to.annotation.from.sql.text=@Sql\u30A2\u30CE\u30C6\u30FC\u30B7\u30E7\u30F3\u306B\u5909\u63DB inspection.invalid.sql.function.call.parameter.count=\u95a2\u6570{0}\u306e\u30d1\u30e9\u30e1\u30fc\u30bf\u6570\u304c{1}\u306e\u5b9a\u7fa9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093 -inspection.invalid.sql.function.call.parameter.type.mismatch=\u95a2\u6570{0}\u306e\u5f15\u6570\u306e\u578b\u304c\u4e00\u81f4\u3057\u307e\u305b\u3093\u3002\u5b9f\u969b\u306e\u578b: ({1})
\u5b9a\u7fa9\u5019\u88dc:
{2} \ No newline at end of file +inspection.invalid.sql.function.call.parameter.type.mismatch=\u95a2\u6570{0}\u306e\u5f15\u6570\u306e\u578b\u304c\u4e00\u81f4\u3057\u307e\u305b\u3093\u3002\u5b9f\u969b\u306e\u578b: ({1})
\u5b9a\u7fa9\u5019\u88dc:
{2} +bulk.convert.sql.file.to.annotation.text=\u0053\u0051\u004C\u30D5\u30A1\u30A4\u30EB\u3092\u0040\u0053\u0071\u006C\u30A2\u30CE\u30C6\u30FC\u30B7\u30E7\u30F3\u306B\u4E00\u62EC\u5909\u63DB +bulk.convert.sql.file.to.annotation.family=\u0053\u0051\u004C\u30D5\u30A1\u30A4\u30EB\u3092\u0040\u0053\u0071\u006C\u30A2\u30CE\u30C6\u30FC\u30B7\u30E7\u30F3\u306B\u4E00\u62EC\u5909\u63DB +bulk.convert.sql.annotation.to.file.family=\u0040\u0053\u0071\u006C\u30A2\u30CE\u30C6\u30FC\u30B7\u30E7\u30F3\u3092\u0053\u0051\u004C\u30D5\u30A1\u30A4\u30EB\u306B\u4E00\u62EC\u5909\u63DB +bulk.convert.sql.annotation.to.file.text=\u0040\u0053\u0071\u006C\u30A2\u30CE\u30C6\u30FC\u30B7\u30E7\u30F3\u3092\u0053\u0051\u004C\u30D5\u30A1\u30A4\u30EB\u306B\u4E00\u62EC\u5909\u63DB \ No newline at end of file diff --git a/src/test/kotlin/org/domaframework/doma/intellij/action/dao/BulkConvertSqlAnnotationToFileActionTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/action/dao/BulkConvertSqlAnnotationToFileActionTest.kt new file mode 100644 index 00000000..db6c6e1d --- /dev/null +++ b/src/test/kotlin/org/domaframework/doma/intellij/action/dao/BulkConvertSqlAnnotationToFileActionTest.kt @@ -0,0 +1,73 @@ +/* + * 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.action.dao + +import com.intellij.openapi.fileEditor.FileEditorManager +import org.domaframework.doma.intellij.bundle.MessageBundle + +class BulkConvertSqlAnnotationToFileActionTest : ConvertSqlActionTest() { + private val sqlConversionPackage = "sqltofile/bulk" + private val convertActionName = MessageBundle.message("bulk.convert.sql.annotation.to.file.text") + private val convertFamilyName = MessageBundle.message("bulk.convert.sql.annotation.to.file.family") + + fun testBulkConvertAnnotationToSqlFile() { + val daoName = "BulkConvertToSqlFileDao" + val targetSqlFileNames = + listOf( + "selectEmployee.sql", + "insertEmployee.sql", + "updateEmployee.sql", + "deleteEmployee.sql", + "batchInsertEmployees.sql", + "batchUpdateEmployees.sql", + "batchDeleteEmployees.sql", + "createTables.script", + "processData.sql", + ) + doTest(daoName, targetSqlFileNames) + } + + private fun doTest( + daoName: String, + targetMethods: List, + ) { + doConvertAction( + daoName, + convertFamilyName, + sqlConversionPackage, + convertActionName, + ) + + targetMethods.forEach { file -> + val generatedSql = findSqlFile("$sqlConversionPackage/$daoName/$file") + if (generatedSql == null) { + fail("Not Found SQL File [$file]") + return + } + val extension = generatedSql.extension == "script" + val fileNameWithoutExtension = generatedSql.nameWithoutExtension + doTestSqlFormat(daoName, fileNameWithoutExtension, sqlConversionPackage, extension) + } + + // Test SQL File Generation + val openedEditor = FileEditorManager.getInstance(project).selectedEditors + val openSqlFile = openedEditor.find { it.file.extension in listOf("sql", "script") } + assertFalse( + "Open File is SQL File [${openSqlFile?.file?.name}]", + openSqlFile == null, + ) + } +} diff --git a/src/test/kotlin/org/domaframework/doma/intellij/action/dao/BulkConvertSqlFileToAnnotationActionTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/action/dao/BulkConvertSqlFileToAnnotationActionTest.kt new file mode 100644 index 00000000..d2bca311 --- /dev/null +++ b/src/test/kotlin/org/domaframework/doma/intellij/action/dao/BulkConvertSqlFileToAnnotationActionTest.kt @@ -0,0 +1,59 @@ +/* + * 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.action.dao + +import org.domaframework.doma.intellij.bundle.MessageBundle + +class BulkConvertSqlFileToAnnotationActionTest : ConvertSqlActionTest() { + private val sqlConversionPackage = "sqltoannotation/bulk" + private val convertFamilyName = MessageBundle.message("bulk.convert.sql.file.to.annotation.family") + + fun testBulkConvertToSqlAnnotation() { + val daoName = "BulkConvertToSqlAnnotationDao" + val targetSqlFileNames = + listOf( + "generateSqlFile.sql", + "insertEmployee.sql", + "updateEmployee.sql", + "deleteEmployee.sql", + "batchInsertEmployees.sql", + "batchUpdateEmployees.sql", + "batchDeleteEmployees.sql", + "createTables.script", + "processData.sql", + ) + doTest(daoName, targetSqlFileNames) + } + + private fun doTest( + daoName: String, + targetSqlFileNames: List, + ) { + targetSqlFileNames.forEach { file -> + addSqlFile("$sqlConversionPackage/$daoName/$file") + } + doAvailableConvertActionTest( + daoName, + sqlConversionPackage, + convertFamilyName, + ) + // Test SQL File Removed + targetSqlFileNames.forEach { file -> + val generatedSql = findSqlFile("$sqlConversionPackage/$daoName/$file") + assertNull("SQL File [$file] should exists ", generatedSql) + } + } +} diff --git a/src/test/kotlin/org/domaframework/doma/intellij/action/dao/ConvertSqlActionTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/action/dao/ConvertSqlActionTest.kt index bed0ea05..3ebc5483 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/action/dao/ConvertSqlActionTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/action/dao/ConvertSqlActionTest.kt @@ -19,7 +19,6 @@ import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.psi.PsiDocumentManager import org.domaframework.doma.intellij.DomaSqlTest -import kotlin.reflect.KClass abstract class ConvertSqlActionTest : DomaSqlTest() { protected fun doConvertAction( @@ -32,7 +31,6 @@ abstract class ConvertSqlActionTest : DomaSqlTest() { val daoClass = findDaoClass("$sqlConversionPackage.$daoName") myFixture.configureFromExistingVirtualFile(daoClass.containingFile.virtualFile) - println("convertActionName: $convertActionName") val intention = myFixture.findSingleIntention(convertActionName) assertNotNull( @@ -46,20 +44,42 @@ abstract class ConvertSqlActionTest : DomaSqlTest() { myFixture.checkResultByFile("java/doma/example/dao/$sqlConversionPackage/$daoName.after.java") } - protected fun doConvertActionTest( + /** + * Execute the intention action with the specified family name. + */ + protected fun doAvailableConvertActionTest( daoName: String, sqlToAnnotationPackage: String, convertFamilyName: String, - convertActionClass: KClass, ) { addDaoJavaFile("$sqlToAnnotationPackage/$daoName.java") val daoClass = findDaoClass("$sqlToAnnotationPackage.$daoName") myFixture.configureFromExistingVirtualFile(daoClass.containingFile.virtualFile) val intentions = myFixture.availableIntentions - val convertIntention = intentions.find { convertActionClass.java.isInstance(it) } + val convertIntention = intentions.find { it.familyName == convertFamilyName } + if (convertIntention == null) { + fail("[$convertFamilyName] intention should NOT be available without @Sql annotation") + return + } + convertIntention.invoke(myFixture.project, myFixture.editor, myFixture.file) + } + + protected fun doNotAvailableConvertActionTest( + daoName: String, + sqlToAnnotationPackage: String, + convertFamilyName: String, + ) { + addDaoJavaFile("$sqlToAnnotationPackage/$daoName.java") + val daoClass = findDaoClass("$sqlToAnnotationPackage.$daoName") + myFixture.configureFromExistingVirtualFile(daoClass.containingFile.virtualFile) - assertNull("$convertFamilyName intention should NOT be available without @Sql annotation", convertIntention) + val intentions = myFixture.availableIntentions + val convertIntention = intentions.find { it.familyName == convertFamilyName } + if (convertIntention != null) { + fail("[$convertFamilyName] intention should be available without @Sql annotation") + return + } } protected fun doTestSqlFormat( diff --git a/src/test/kotlin/org/domaframework/doma/intellij/action/dao/ConvertSqlAnnotationToFileActionTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/action/dao/ConvertSqlAnnotationToFileActionTest.kt index 408b730d..a51f71cd 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/action/dao/ConvertSqlAnnotationToFileActionTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/action/dao/ConvertSqlAnnotationToFileActionTest.kt @@ -110,12 +110,12 @@ class ConvertSqlAnnotationToFileActionTest : ConvertSqlActionTest() { fun testIntentionNotAvailableForMethodWithoutSqlAnnotation() { val daoName = "NoSqlAnnotationDao" - doConvertActionTest(daoName, sqlConversionPackage, convertFamilyName, ConvertSqlFileToAnnotationFromDaoAction::class) + doNotAvailableConvertActionTest(daoName, sqlConversionPackage, convertFamilyName) } fun testIntentionNotAvailableForUnsupportedAnnotation() { val daoName = "UnsupportedAnnotationDao" - doConvertActionTest(daoName, sqlConversionPackage, convertFamilyName, ConvertSqlFileToAnnotationFromDaoAction::class) + doNotAvailableConvertActionTest(daoName, sqlConversionPackage, convertFamilyName) } private fun doTest( diff --git a/src/test/kotlin/org/domaframework/doma/intellij/action/dao/ConvertSqlFileToAnnotationActionTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/action/dao/ConvertSqlFileToAnnotationActionTest.kt index 07f05ed1..a65ac87b 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/action/dao/ConvertSqlFileToAnnotationActionTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/action/dao/ConvertSqlFileToAnnotationActionTest.kt @@ -118,15 +118,16 @@ class ConvertSqlFileToAnnotationActionTest : ConvertSqlActionTest() { fun testSelectWithSqlFileConvertAnnotation() { val daoName = "SelectWithSqlFileConvertAnnotationDao" val sqlFileName = "selectEmployee.sql" - doConvertActionTest( + addSqlFile("$sqlToAnnotationPackage/$daoName/$sqlFileName") + doAvailableConvertActionTest( daoName, sqlToAnnotationPackage, convertFamilyName, - ConvertSqlFileToAnnotationFromDaoAction::class, ) + // Test SQL File Removed val generatedSql = findSqlFile("$sqlToAnnotationPackage/$daoName/$sqlFileName") - assertTrue("SQL File [$sqlFileName] should exists ", generatedSql == null) + assertNull("SQL File [$sqlFileName] should exists ", generatedSql) } private fun doTest( 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 98147835..6a27ae6c 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt @@ -18,14 +18,13 @@ package org.domaframework.doma.intellij.formatter 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.setting.SettingComponent import org.domaframework.doma.intellij.setting.state.DomaToolsFormatEnableSettings class SqlFormatterTest : BasePlatformTestCase() { - override fun getBasePath(): String? = "src/test/testData/sql/formatter" + override fun getBasePath(): String = "src/test/testData/sql/formatter" - override fun getTestDataPath(): String? = "src/test/testData/sql/formatter" + override fun getTestDataPath(): String = "src/test/testData/sql/formatter" private val formatDataPrefix = "_format" @@ -314,14 +313,12 @@ class SqlFormatterTest : BasePlatformTestCase() { val currentFile = myFixture.file WriteCommandAction .writeCommandAction(project) - .run( - ThrowableRunnable { - CodeStyleManager.getInstance(project).reformatText( - currentFile, - arrayListOf(currentFile.textRange), - ) - }, - ) + .run { + CodeStyleManager.getInstance(project).reformatText( + currentFile, + arrayListOf(currentFile.textRange), + ) + } myFixture.checkResultByFile(afterFile) } } 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 91608a10..df4c186d 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/reference/SqlReferenceTestCase.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/reference/SqlReferenceTestCase.kt @@ -351,13 +351,6 @@ class SqlReferenceTestCase : DomaSqlTest() { for (reference in references) { val resolveResult = reference.references.firstOrNull()?.resolve() val expectedResults = resolveExpects[reference.text] - println( - "Reference: ${reference.text}, Resolve: ${(resolveResult as? PsiMethod) - ?.parameterList - ?.parameters - ?.map{ it.type } - ?.joinToString() ?: resolveResult}", - ) assertTrue(expectedResults?.contains(resolveResult?.toString()) == true) } } diff --git a/src/test/testData/src/main/java/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao.after.java b/src/test/testData/src/main/java/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao.after.java new file mode 100644 index 00000000..147d382f --- /dev/null +++ b/src/test/testData/src/main/java/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao.after.java @@ -0,0 +1,106 @@ +package doma.example.dao.sqltoannotation.bulk; + +import doma.example.entity.Employee; +import doma.example.entity.Project; +import java.util.List; +import java.util.function.BiFunction; +import org.seasar.doma.BatchDelete; +import org.seasar.doma.BatchInsert; +import org.seasar.doma.BatchUpdate; +import org.seasar.doma.Dao; +import org.seasar.doma.Delete; +import org.seasar.doma.Function; +import org.seasar.doma.Insert; +import org.seasar.doma.Script; +import org.seasar.doma.Select; +import org.seasar.doma.Sql; +import org.seasar.doma.SqlProcessor; +import org.seasar.doma.Update; +import org.seasar.doma.jdbc.Config; +import org.seasar.doma.jdbc.PreparedSql; + +/** + * Bulk annotation type conversion test (SQL file ⇒ Sql annotation) + */ +@Dao +public interface BulkConvertToSqlAnnotationDao { + + @Select + @Sql("select * from employee where id = /* id */1") + String generateSqlFile(Employee employee, Project project); + + /** + * Confirm that formatting is performed after the action + * + * @param id + * @return + */ + @Select + @Sql(""" + SELECT * + FROM employee WHERE id = /* id */1 + """) + Employee selectEmployee(int id); + + @Insert + @Sql(""" + INSERT INTO employee(id, name) + VALUES ( /* employee.employeeId */0, /* employee.userName */'name' )""") + int insertEmployee(Employee employee); + + @Update + @Sql(""" + UPDATE employee + SET name = /* employee.employeeName */'John', rank = /* employee.rank */30 + WHERE id = /* employee.managerId */1 + """) + int updateEmployee(Employee employee); + + @Delete + @Sql(""" + DELETE + FROM employee + WHERE id = /* employee.managerId */1 + """) + int deleteEmployee(Employee employee); + + @BatchInsert + @Sql(""" + INSERT INTO employee + (id, name, age) + VALUES ( /* employees.employeeId */1, /* employees.employeeName */'John', /* employees.rank */30 ) + """) + int[] batchInsertEmployees(List employees); + + @BatchUpdate + @Sql(""" + UPDATE employee + SET name = /* employees.employeeName */'John', rank = /* employees.rank */30 + WHERE id = /* employees.employeeId */1 + """) + int[] batchUpdateEmployees(List employees); + + @BatchDelete + @Sql(""" + DELETE FROM employee WHERE id = /* employees.employeeId */1 + """) + int[] batchDeleteEmployees(List employees); + + @Script + @Sql(""" + CREATE TABLE employee + ( id INTEGER PRIMARY KEY, name VARCHAR(100), age INTEGER) ; + CREATE INDEX idx_employee_name ON employee(name) ; + """) + void createTables(); + + @SqlProcessor + @Sql(""" + SELECT count(*) FROM employee WHERE age > 25 + """) + R processData(BiFunction processor); + + // Annotation not supported + @Function + void executeFunc(); +} diff --git a/src/test/testData/src/main/java/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao.java b/src/test/testData/src/main/java/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao.java new file mode 100644 index 00000000..c643b7d5 --- /dev/null +++ b/src/test/testData/src/main/java/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao.java @@ -0,0 +1,62 @@ +package doma.example.dao.sqltoannotation.bulk; + +import doma.example.entity.Employee; +import doma.example.entity.Project; +import java.util.List; +import java.util.function.BiFunction; +import org.seasar.doma.BatchDelete; +import org.seasar.doma.BatchInsert; +import org.seasar.doma.BatchUpdate; +import org.seasar.doma.Dao; +import org.seasar.doma.Delete; +import org.seasar.doma.Function; +import org.seasar.doma.Insert; +import org.seasar.doma.Script; +import org.seasar.doma.Select; +import org.seasar.doma.Sql; +import org.seasar.doma.SqlProcessor; +import org.seasar.doma.Update; +import org.seasar.doma.jdbc.Config; +import org.seasar.doma.jdbc.PreparedSql; + +/** + * Bulk annotation type conversion test (SQL file ⇒ Sql annotation) + */ +@Dao +public interface BulkConvertToSqlAnnotationDao { + + @Select + String generateSqlFile(Employee employee, Project project); + + @Select + @Sql("select * from employee where id = /* id */1") + Employee selectEmployee(int id); + + @Insert(sqlFile = true) + int insertEmployee(Employee employee); + + @Update(sqlFile = true) + int updateEmployee(Employee employee); + + @Delete(sqlFile = true) + int deleteEmployee(Employee employee); + + @BatchInsert(sqlFile = true) + int[] batchInsertEmployees(List employees); + + @BatchUpdate(sqlFile = true) + int[] batchUpdateEmployees(List employees); + + @BatchDelete(sqlFile = true) + int[] batchDeleteEmployees(List employees); + + @Script + void createTables(); + + @SqlProcessor + R processData(BiFunction processor); + + // Annotation not supported + @Function + void executeFunc(); +} diff --git a/src/test/testData/src/main/java/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao.after.java b/src/test/testData/src/main/java/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao.after.java new file mode 100644 index 00000000..9b1561e1 --- /dev/null +++ b/src/test/testData/src/main/java/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao.after.java @@ -0,0 +1,67 @@ +package doma.example.dao.sqltofile.bulk; + +import doma.example.entity.Employee; +import doma.example.entity.Project; +import java.util.List; +import java.util.function.BiFunction; +import org.seasar.doma.BatchDelete; +import org.seasar.doma.BatchInsert; +import org.seasar.doma.BatchUpdate; +import org.seasar.doma.Dao; +import org.seasar.doma.Delete; +import org.seasar.doma.Function; +import org.seasar.doma.Insert; +import org.seasar.doma.Script; +import org.seasar.doma.Select; +import org.seasar.doma.Sql; +import org.seasar.doma.SqlProcessor; +import org.seasar.doma.Update; +import org.seasar.doma.jdbc.Config; +import org.seasar.doma.jdbc.PreparedSql; + +/** + * Bulk annotation type conversion test (Sql annotation ⇒ SQL file) + */ +@Dao +public interface BulkConvertToSqlFileDao { + + @Select + String generateSqlFile(Employee employee, Project project); + + /** + * Confirm that formatting is performed after the action + * + * @param id + * @return + */ + @Select + Employee selectEmployee(int id); + + @Insert(sqlFile = true) + int insertEmployee(Employee employee); + + @Update(sqlFile = true) + int updateEmployee(Employee employee); + + @Delete(sqlFile = true) + int deleteEmployee(Employee employee); + + @BatchInsert(sqlFile = true) + int[] batchInsertEmployees(List employees); + + @BatchUpdate(sqlFile = true) + int[] batchUpdateEmployees(List employees); + + @BatchDelete(sqlFile = true) + int[] batchDeleteEmployees(List employees); + + @Script + void createTables(); + + @SqlProcessor + R processData(BiFunction processor); + + // Annotation not supported + @Function + void executeFunc(); +} diff --git a/src/test/testData/src/main/java/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao.java b/src/test/testData/src/main/java/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao.java new file mode 100644 index 00000000..7abfc23f --- /dev/null +++ b/src/test/testData/src/main/java/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao.java @@ -0,0 +1,106 @@ +package doma.example.dao.sqltofile.bulk; + +import doma.example.entity.Employee; +import doma.example.entity.Project; +import java.util.List; +import java.util.function.BiFunction; +import org.seasar.doma.BatchDelete; +import org.seasar.doma.BatchInsert; +import org.seasar.doma.BatchUpdate; +import org.seasar.doma.Dao; +import org.seasar.doma.Delete; +import org.seasar.doma.Function; +import org.seasar.doma.Insert; +import org.seasar.doma.Script; +import org.seasar.doma.Select; +import org.seasar.doma.Sql; +import org.seasar.doma.SqlProcessor; +import org.seasar.doma.Update; +import org.seasar.doma.jdbc.Config; +import org.seasar.doma.jdbc.PreparedSql; + +/** + * Bulk annotation type conversion test (Sql annotation ⇒ SQL file) + */ +@Dao +public interface BulkConvertToSqlFileDao { + + @Select + @Sql("SELECT * FROM emp WHERE id = /* employee.getSubEmployee(project).projectId */0") + String generateSqlFile(Employee employee, Project project); + + /** + * Confirm that formatting is performed after the action + * + * @param id + * @return + */ + @Select + @Sql(""" + SELECT * + FROM employee WHERE id = /* id */1 + """) + Employee selectEmployee(int id); + + @Insert + @Sql(""" + INSERT INTO employee(id, name) + VALUES ( /* employee.employeeId */0, /* employee.userName */'name' )""") + int insertEmployee(Employee employee); + + @Update + @Sql(""" + UPDATE employee + SET name = /* employee.employeeName */'John', rank = /* employee.rank */30 + WHERE id = /* employee.managerId */1 + """) + int updateEmployee(Employee employee); + + @Delete + @Sql(""" + DELETE + FROM employee + WHERE id = /* employee.managerId */1 + """) + int deleteEmployee(Employee employee); + + @BatchInsert + @Sql(""" + INSERT INTO employee + (id, name, age) + VALUES ( /* employees.employeeId */1, /* employees.employeeName */'John', /* employees.rank */30 ) + """) + int[] batchInsertEmployees(List employees); + + @BatchUpdate + @Sql(""" + UPDATE employee + SET name = /* employees.employeeName */'John', rank = /* employees.rank */30 + WHERE id = /* employees.employeeId */1 + """) + int[] batchUpdateEmployees(List employees); + + @BatchDelete + @Sql(""" + DELETE FROM employee WHERE id = /* employees.employeeId */1 + """) + int[] batchDeleteEmployees(List employees); + + @Script + @Sql(""" + CREATE TABLE employee + ( id INTEGER PRIMARY KEY, name VARCHAR(100), age INTEGER) ; + CREATE INDEX idx_employee_name ON employee(name) ; + """) + void createTables(); + + @SqlProcessor + @Sql(""" + SELECT count(*) FROM employee WHERE age > 25 + """) + R processData(BiFunction processor); + + // Annotation not supported + @Function + void executeFunc(); +} diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/batchDeleteEmployees.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/batchDeleteEmployees.sql new file mode 100644 index 00000000..705a9135 --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/batchDeleteEmployees.sql @@ -0,0 +1,2 @@ +delete from employee + where id = /* employees.employeeId */1 \ No newline at end of file diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/batchInsertEmployees.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/batchInsertEmployees.sql new file mode 100644 index 00000000..e34e580c --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/batchInsertEmployees.sql @@ -0,0 +1,2 @@ +insert into employee (id, name, age) + values (/* employees.employeeId */1, /* employees.employeeName */'John', /* employees.rank */30) \ No newline at end of file diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/batchUpdateEmployees.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/batchUpdateEmployees.sql new file mode 100644 index 00000000..96a60721 --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/batchUpdateEmployees.sql @@ -0,0 +1,2 @@ +update employee set name = /* employees.employeeName */'John', rank = /* employees.rank */30 + where id = /* employees.employeeId */1 \ No newline at end of file diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/createTables.script b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/createTables.script new file mode 100644 index 00000000..992cc705 --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/createTables.script @@ -0,0 +1,7 @@ +CREATE TABLE employee ( + id INTEGER PRIMARY KEY, +name VARCHAR(100), +age INTEGER +); + +CREATE INDEX idx_employee_name ON employee(name); \ No newline at end of file diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/deleteEmployee.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/deleteEmployee.sql new file mode 100644 index 00000000..c40fd4cc --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/deleteEmployee.sql @@ -0,0 +1 @@ +delete from employee where id = /* employee.managerId */1 \ No newline at end of file diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/generateSqlFile.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/generateSqlFile.sql new file mode 100644 index 00000000..5d1e3375 --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/generateSqlFile.sql @@ -0,0 +1 @@ +SELECT * FROM emp WHERE id = /* employee.getSubEmployee(project).projectId */0 diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/insertEmployee.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/insertEmployee.sql new file mode 100644 index 00000000..1ae3657f --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/insertEmployee.sql @@ -0,0 +1,5 @@ +INSERT INTO employee + (id + , name) + VALUES ( /* employee.employeeId */0 + , /* employee.userName */'name' ) \ No newline at end of file diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/processData.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/processData.sql new file mode 100644 index 00000000..05c390e2 --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/processData.sql @@ -0,0 +1 @@ +select count(*) from employee where age > 25 \ No newline at end of file diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/updateEmployee.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/updateEmployee.sql new file mode 100644 index 00000000..993ccd27 --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltoannotation/bulk/BulkConvertToSqlAnnotationDao/updateEmployee.sql @@ -0,0 +1 @@ +update employee set name = /* employee.employeeName */'John', rank = /* employee.rank */30 where id = /* employee.managerId */1 \ No newline at end of file diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/batchDeleteEmployees.after.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/batchDeleteEmployees.after.sql new file mode 100644 index 00000000..7c243263 --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/batchDeleteEmployees.after.sql @@ -0,0 +1,2 @@ +DELETE FROM employee + WHERE id = /* employees.employeeId */1 diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/batchInsertEmployees.after.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/batchInsertEmployees.after.sql new file mode 100644 index 00000000..5242175e --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/batchInsertEmployees.after.sql @@ -0,0 +1,7 @@ +INSERT INTO employee + (id + , name + , age) + VALUES ( /* employees.employeeId */1 + , /* employees.employeeName */'John' + , /* employees.rank */30 ) diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/batchUpdateEmployees.after.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/batchUpdateEmployees.after.sql new file mode 100644 index 00000000..25238627 --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/batchUpdateEmployees.after.sql @@ -0,0 +1,4 @@ +UPDATE employee + SET name = /* employees.employeeName */'John' + , rank = /* employees.rank */30 + WHERE id = /* employees.employeeId */1 diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/createTables.after.script b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/createTables.after.script new file mode 100644 index 00000000..bd9b4c38 --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/createTables.after.script @@ -0,0 +1,8 @@ +CREATE TABLE employee + ( + id INTEGER PRIMARY KEY + , name VARCHAR(100) + , age INTEGER + ) ; +CREATE INDEX idx_employee_name +ON employee(name) ; diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/deleteEmployee.after.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/deleteEmployee.after.sql new file mode 100644 index 00000000..57e92b30 --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/deleteEmployee.after.sql @@ -0,0 +1,2 @@ +DELETE FROM employee + WHERE id = /* employee.managerId */1 diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/generateSqlFile.after.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/generateSqlFile.after.sql new file mode 100644 index 00000000..b18bc8e7 --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/generateSqlFile.after.sql @@ -0,0 +1,3 @@ +SELECT * + FROM emp + WHERE id = /* employee.getSubEmployee(project).projectId */0 diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/insertEmployee.after.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/insertEmployee.after.sql new file mode 100644 index 00000000..441a0bc5 --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/insertEmployee.after.sql @@ -0,0 +1,5 @@ +INSERT INTO employee + (id + , name) + VALUES ( /* employee.employeeId */0 + , /* employee.userName */'name' ) diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/processData.after.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/processData.after.sql new file mode 100644 index 00000000..e4dad13e --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/processData.after.sql @@ -0,0 +1,3 @@ +SELECT count(*) + FROM employee + WHERE age > 25 diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/selectEmployee.after.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/selectEmployee.after.sql new file mode 100644 index 00000000..99ee2141 --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/selectEmployee.after.sql @@ -0,0 +1,3 @@ +SELECT * + FROM employee + WHERE id = /* id */1 diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/updateEmployee.after.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/updateEmployee.after.sql new file mode 100644 index 00000000..791a39b0 --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/sqltofile/bulk/BulkConvertToSqlFileDao/updateEmployee.after.sql @@ -0,0 +1,4 @@ +UPDATE employee + SET name = /* employee.employeeName */'John' + , rank = /* employee.rank */30 + WHERE id = /* employee.managerId */1