Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ import com.intellij.psi.PsiFile
import com.intellij.psi.PsiMethod
import com.intellij.psi.util.PsiTreeUtil
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
import org.domaframework.doma.intellij.extension.psi.DomaAnnotationType

/**
* Intention action to convert @Sql annotation to SQL file
Expand All @@ -47,25 +47,15 @@ class ConvertSqlAnnotationToFileAction : PsiElementBaseIntentionAction() {
val psiDaoMethod = PsiDaoMethod(project, method)

// Check if method has @Sql annotation
if (!psiDaoMethod.useSqlAnnotation()) {
// 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
) {
return false
}

// Check if method has @Insert, @Update, or @Delete annotation
val supportedTypes =
listOf(
DomaAnnotationType.Select,
DomaAnnotationType.Script,
DomaAnnotationType.SqlProcessor,
DomaAnnotationType.Insert,
DomaAnnotationType.Update,
DomaAnnotationType.Delete,
DomaAnnotationType.BatchInsert,
DomaAnnotationType.BatchUpdate,
DomaAnnotationType.BatchDelete,
)

return supportedTypes.any { it.getPsiAnnotation(method) != null }
return SqlAnnotationConverter.supportedTypes.any { it.getPsiAnnotation(method) != null }
}

override fun generatePreview(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.psi.util.PsiTreeUtil
import org.domaframework.doma.intellij.bundle.MessageBundle
import org.domaframework.doma.intellij.common.dao.findDaoMethod
import org.domaframework.doma.intellij.common.dao.getDaoClass
import org.domaframework.doma.intellij.common.isJavaOrKotlinFileType
import org.domaframework.doma.intellij.common.isSupportFileType
import org.domaframework.doma.intellij.common.psi.PsiDaoMethod
import org.domaframework.doma.intellij.common.util.PluginLoggerUtil
import org.domaframework.doma.intellij.extension.psi.DomaAnnotationType

/**
* Intention action to convert SQL file to @Sql annotation
Expand All @@ -41,32 +44,47 @@ class ConvertSqlFileToAnnotationAction : PsiElementBaseIntentionAction() {
editor: Editor?,
element: PsiElement,
): Boolean {
if (!isSupportFileType(element.containingFile)) return false
val file = element.containingFile ?: return false
if (isJavaOrKotlinFileType(file) && getDaoClass(file) != null) {
return checkOnMethod(element, project)
}

if (isSupportFileType(file)) {
return checkOnSqlFile(element, project)
}

return false
}

private fun checkOnMethod(
element: PsiElement,
project: Project,
): Boolean {
val daoMethod = PsiTreeUtil.getParentOfType(element, PsiMethod::class.java) ?: return false
return checkAvailable(project, daoMethod)
}

private fun checkOnSqlFile(
element: PsiElement,
project: Project,
): Boolean {
val daoMethod = findDaoMethod(element.containingFile) ?: return false
return checkAvailable(project, daoMethod)
}

private fun checkAvailable(
project: Project,
daoMethod: PsiMethod,
): Boolean {
val psiDaoMethod = PsiDaoMethod(project, daoMethod)

// Check if method doesn't have @Sql annotation
if (psiDaoMethod.useSqlAnnotation()) {
if (psiDaoMethod.sqlFile == null || psiDaoMethod.useSqlAnnotation()) {
return false
}

// Check if method has @Insert, @Update, or @Delete annotation with sqlFile=true
val supportedTypes =
listOf(
DomaAnnotationType.Select,
DomaAnnotationType.Script,
DomaAnnotationType.SqlProcessor,
DomaAnnotationType.Insert,
DomaAnnotationType.Update,
DomaAnnotationType.Delete,
DomaAnnotationType.BatchInsert,
DomaAnnotationType.BatchUpdate,
DomaAnnotationType.BatchDelete,
)

val hasAnnotation =
supportedTypes.any { type ->
SqlAnnotationConverter.supportedTypes.any { type ->
val annotation = type.getPsiAnnotation(daoMethod)
annotation != null
}
Expand All @@ -82,8 +100,42 @@ class ConvertSqlFileToAnnotationAction : PsiElementBaseIntentionAction() {
) {
// Do nothing when previewing
if (IntentionPreviewUtils.isIntentionPreviewActive()) return
if (!isSupportFileType(element.containingFile)) return

val file = element.containingFile
if (isJavaOrKotlinFileType(file)) {
return processOnMethod(element, project)
}

// Process if the file type is SQL
if (isSupportFileType(file)) {
return processOnSqlFile(element, project)
}
}

private fun processOnMethod(
element: PsiElement,
project: Project,
) {
val daoMethod = PsiTreeUtil.getParentOfType(element, PsiMethod::class.java) ?: return

val startTime = System.nanoTime()
val converter = SqlAnnotationConverter(project, daoMethod)
WriteCommandAction.runWriteCommandAction(project) {
converter.convertToSqlAnnotation()
}

PluginLoggerUtil.countLogging(
className = this::class.java.simpleName,
actionName = "convertSqlFileToAnnotationOnMethod",
inputName = "IntentionAction",
start = startTime,
)
}

private fun processOnSqlFile(
element: PsiElement,
project: Project,
) {
val daoMethod = findDaoMethod(element.containingFile) ?: return

val startTime = System.nanoTime()
Expand All @@ -94,7 +146,7 @@ class ConvertSqlFileToAnnotationAction : PsiElementBaseIntentionAction() {

PluginLoggerUtil.countLogging(
className = this::class.java.simpleName,
actionName = "convertSqlFileToAnnotation",
actionName = "convertSqlFileToAnnotationOnSQL",
inputName = "IntentionAction",
start = startTime,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ class SqlAnnotationConverter(
private val psiDaoMethod = PsiDaoMethod(project, method)
private val elementFactory = JavaPsiFacade.getElementFactory(project)

companion object {
val supportedTypes =
setOf(
DomaAnnotationType.Select,
DomaAnnotationType.Script,
DomaAnnotationType.SqlProcessor,
DomaAnnotationType.Insert,
DomaAnnotationType.Update,
DomaAnnotationType.Delete,
DomaAnnotationType.BatchInsert,
DomaAnnotationType.BatchUpdate,
DomaAnnotationType.BatchDelete,
)
}

/**
* Convert @Sql annotation to SQL file
*/
Expand Down Expand Up @@ -95,19 +110,6 @@ class SqlAnnotationConverter(
}

private fun findTargetAnnotation(): PsiAnnotation? {
val supportedTypes =
listOf(
DomaAnnotationType.Select,
DomaAnnotationType.Script,
DomaAnnotationType.SqlProcessor,
DomaAnnotationType.Insert,
DomaAnnotationType.Update,
DomaAnnotationType.Delete,
DomaAnnotationType.BatchInsert,
DomaAnnotationType.BatchUpdate,
DomaAnnotationType.BatchDelete,
)

for (type in supportedTypes) {
val annotation = type.getPsiAnnotation(method)
if (annotation != null) {
Expand Down Expand Up @@ -243,7 +245,7 @@ class SqlAnnotationConverter(

private fun generateSqlFileWithContent(content: String) {
// First generate the empty SQL file using existing functionality
psiDaoMethod.generateSqlFile()
psiDaoMethod.generateSqlFile(false)

// Then update its content
ApplicationManager.getApplication().invokeLater {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ package org.domaframework.doma.intellij.common.dao
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiTreeUtil
import org.domaframework.doma.intellij.common.util.DomaClassName

fun getDaoClass(file: PsiFile): PsiClass? =
PsiTreeUtil
.findChildrenOfType(file, PsiClass::class.java)
.firstOrNull { it.hasAnnotation("org.seasar.doma.Dao") }
.firstOrNull { it.hasAnnotation(DomaClassName.DAO.className) }
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ class PsiDaoMethod(
}
}

fun generateSqlFile() {
fun generateSqlFile(fileJump: Boolean = true) {
ApplicationManager.getApplication().runReadAction {
if (sqlFilePath.isEmpty()) return@runReadAction
val rootDir = psiProject.getContentRoot(daoFile) ?: return@runReadAction
Expand Down Expand Up @@ -202,9 +202,11 @@ class PsiDaoMethod(
.findDirectory(virtualFile) ?: return@runWriteCommandAction
sqlOutputDirPath.findFile(sqlFileName)?.delete()
val sqlVirtualFile = sqlOutputDirPath.createFile(sqlFileName).virtualFile ?: return@runWriteCommandAction
FileEditorManager
.getInstance(psiProject)
.openFile(sqlVirtualFile, true)
if (fileJump) {
FileEditorManager
.getInstance(psiProject)
.openFile(sqlVirtualFile, true)
}
writeEmptyElementSqlFile(sqlVirtualFile)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package org.domaframework.doma.intellij.common.util
enum class DomaClassName(
val className: String,
) {
DAO("org.seasar.doma.Dao"),

OPTIONAL("java.util.Optional"),
OPTIONAL_INT("java.util.OptionalInt"),
OPTIONAL_DOUBLE("java.util.OptionalDouble"),
Expand Down
6 changes: 6 additions & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@
<category>Doma tools</category>
<skipBeforeAfter>true</skipBeforeAfter>
</intentionAction>
<intentionAction>
<language>JAVA</language>
<className>org.domaframework.doma.intellij.action.dao.ConvertSqlFileToAnnotationAction</className>
<category>Doma tools</category>
<skipBeforeAfter>true</skipBeforeAfter>
</intentionAction>
</extensions>

<extensions defaultExtensionNs="org.intellij.intelliLang">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* 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.FileDocumentManager
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.psi.PsiDocumentManager
import org.domaframework.doma.intellij.DomaSqlTest

abstract class ConvertSqlActionTest : DomaSqlTest() {
protected fun doConvertAction(
daoName: String,
convertFamilyName: String,
sqlConversionPackage: String,
convertActionName: String,
) {
addDaoJavaFile("$sqlConversionPackage/$daoName.java")

val daoClass = findDaoClass("$sqlConversionPackage.$daoName")
myFixture.configureFromExistingVirtualFile(daoClass.containingFile.virtualFile)
println("convertActionName: $convertActionName")
Copy link

Copilot AI Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statement should be removed from production test code. Use proper logging or remove this line entirely.

Suggested change
println("convertActionName: $convertActionName")

Copilot uses AI. Check for mistakes.
val intention = myFixture.findSingleIntention(convertActionName)

assertNotNull(
"$convertActionName intention should be available",
intention,
)
assertEquals(convertActionName, intention.text)
assertEquals(convertFamilyName, intention.familyName)

myFixture.launchAction(intention)
myFixture.checkResultByFile("java/doma/example/dao/$sqlConversionPackage/$daoName.after.java")
}

protected fun doConvertActionTest(
daoName: String,
sqlToAnnotationPackage: String,
convertFamilyName: String,
) {
addDaoJavaFile("$sqlToAnnotationPackage/$daoName.java")
val daoClass = findDaoClass("$sqlToAnnotationPackage.$daoName")
myFixture.configureFromExistingVirtualFile(daoClass.containingFile.virtualFile)

val intentions = myFixture.availableIntentions
val convertIntention = intentions.find { it is ConvertSqlFileToAnnotationAction }

assertNull("$convertFamilyName intention should NOT be available without @Sql annotation", convertIntention)
}

protected fun doTestSqlFormat(
daoName: String,
sqlFileName: String,
sqlConversionPackage: String,
isScript: Boolean = false,
) {
val openedEditor = FileEditorManager.getInstance(project).selectedEditors
val extension = if (isScript) "script" else "sql"
val openSqlFile = openedEditor.find { it.file.name == sqlFileName.substringAfter("/").plus(".$extension") }

if (openSqlFile != null) {
fail("SQL file $sqlFileName.$extension should be opened after conversion")
return
}
// If the generated `PsiFile` has an associated `Document`, explicitly reload it to ensure memory–disk consistency.
// If not reloaded, the test may produce: *Unexpected memory–disk conflict in tests for*.
val fdm = FileDocumentManager.getInstance()
fdm.saveAllDocuments()
PsiDocumentManager.getInstance(project).commitAllDocuments()

val newSqlFile = findSqlFile("$sqlConversionPackage/$daoName/$sqlFileName.$extension")
if (newSqlFile == null) {
fail("Not Found $sqlFileName.$extension")
return
}
fdm.getDocument(newSqlFile)?.let { fdm.reloadFromDisk(it) }
myFixture.configureFromExistingVirtualFile(newSqlFile)
myFixture.checkResultByFile("resources/META-INF/doma/example/dao/$sqlConversionPackage/$daoName/$sqlFileName.after.$extension")
}
}
Loading
Loading