Skip to content

Commit 46eecd4

Browse files
authored
Merge pull request #364 from domaframework/fix/sql-format-one-line-injection
Formatting Support for Single-Line Injected SQL in @SQL Annotations
2 parents b53a58c + ae32b37 commit 46eecd4

File tree

12 files changed

+203
-118
lines changed

12 files changed

+203
-118
lines changed

src/main/kotlin/org/domaframework/doma/intellij/common/FileTypeCheck.kt

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ package org.domaframework.doma.intellij.common
1818
import com.intellij.openapi.fileTypes.FileTypeManager
1919
import com.intellij.psi.PsiFile
2020

21-
val sourceExtensionNames: List<String> = listOf("JAVA", "Kotlin", "CLASS")
22-
2321
/**
2422
* Get extension by file type identifier
2523
*/
@@ -56,13 +54,3 @@ fun isSupportFileType(file: PsiFile): Boolean {
5654
else -> false
5755
}
5856
}
59-
60-
fun isInjectionSqlFile(file: PsiFile): Boolean {
61-
val extension = file.fileType.defaultExtension
62-
val filePath = file.virtualFile?.path ?: return false
63-
return when (extension) {
64-
"sql" -> true
65-
else -> false
66-
} &&
67-
!(filePath.endsWith(".sql") || filePath.endsWith(".script"))
68-
}

src/main/kotlin/org/domaframework/doma/intellij/common/dao/DaoMethodUtil.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ import org.domaframework.doma.intellij.common.RESOURCES_META_INF_PATH
3232
import org.domaframework.doma.intellij.common.getExtension
3333
import org.domaframework.doma.intellij.common.getJarRoot
3434
import org.domaframework.doma.intellij.common.getMethodDaoFilePath
35-
import org.domaframework.doma.intellij.common.isInjectionSqlFile
3635
import org.domaframework.doma.intellij.common.isSupportFileType
36+
import org.domaframework.doma.intellij.common.util.InjectionSqlUtil.isInjectedSqlFile
3737
import org.domaframework.doma.intellij.extension.getContentRoot
3838
import org.domaframework.doma.intellij.extension.getJavaClazz
3939
import org.domaframework.doma.intellij.extension.getModule
@@ -51,7 +51,7 @@ fun findDaoMethod(
5151
val virtualFile = originalFile.virtualFile ?: return null
5252
val module = project.getModule(virtualFile)
5353

54-
if (isInjectionSqlFile(originalFile)) {
54+
if (isInjectedSqlFile(originalFile)) {
5555
originalFile.let {
5656
return PsiTreeUtil.getParentOfType(originalFile.context, PsiMethod::class.java)
5757
}
@@ -135,7 +135,7 @@ private fun getDaoPathFromSqlFilePath(
135135
sqlFile: PsiFile,
136136
contentRootPath: String,
137137
): PsiClass? {
138-
if (isInjectionSqlFile(sqlFile)) {
138+
if (isInjectedSqlFile(sqlFile)) {
139139
return null
140140
}
141141
val module = sqlFile.module ?: return null

src/main/kotlin/org/domaframework/doma/intellij/common/util/InjectionSqlUtil.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,9 @@ object InjectionSqlUtil {
4141
val injectedLanguageManager = InjectedLanguageManager.getInstance(source.project)
4242
return injectedLanguageManager.isInjectedFragment(source)
4343
}
44+
45+
fun getLiteralExpressionHost(source: PsiFile): PsiLiteralExpression? {
46+
val injectedLanguageManager = InjectedLanguageManager.getInstance(source.project)
47+
return injectedLanguageManager.getInjectionHost(source) as? PsiLiteralExpression
48+
}
4449
}

src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPreProcessor.kt

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
package org.domaframework.doma.intellij.formatter.processor
1717

1818
import com.intellij.lang.ASTNode
19+
import com.intellij.lang.injection.InjectedLanguageManager
1920
import com.intellij.openapi.editor.Document
2021
import com.intellij.openapi.util.TextRange
2122
import com.intellij.psi.PsiDocumentManager
2223
import com.intellij.psi.PsiElement
2324
import com.intellij.psi.PsiFile
25+
import com.intellij.psi.PsiLiteralExpression
2426
import com.intellij.psi.PsiWhiteSpace
2527
import com.intellij.psi.TokenType
2628
import com.intellij.psi.impl.source.codeStyle.PreFormatProcessor
@@ -50,6 +52,11 @@ class SqlFormatPreProcessor : PreFormatProcessor {
5052
SqlTypes.OTHER,
5153
)
5254

55+
data class ProcessResult(
56+
val document: Document?,
57+
val range: TextRange,
58+
)
59+
5360
override fun process(
5461
node: ASTNode,
5562
rangeToReformat: TextRange,
@@ -65,13 +72,26 @@ class SqlFormatPreProcessor : PreFormatProcessor {
6572
return rangeToReformat
6673
}
6774

75+
// Do not execute processor processing in single-line text state
76+
if (isInjectedSqlFile(source)) {
77+
val host = InjectedLanguageManager.getInstance(source.project).getInjectionHost(source) as? PsiLiteralExpression
78+
if (host?.isTextBlock != true) return rangeToReformat
79+
}
80+
val result = updateDocument(source, rangeToReformat)
81+
return result.range
82+
}
83+
84+
fun updateDocument(
85+
source: PsiFile,
86+
rangeToReformat: TextRange,
87+
): ProcessResult {
6888
logging()
6989

7090
val visitor = SqlFormatVisitor()
7191
source.accept(visitor)
7292

7393
val docManager = PsiDocumentManager.getInstance(source.project)
74-
val document = docManager.getDocument(source) ?: return rangeToReformat
94+
val document = docManager.getDocument(source) ?: return ProcessResult(null, rangeToReformat)
7595

7696
val keywordList = visitor.replaces.filter { it.elementType != TokenType.WHITE_SPACE }
7797
val replaceKeywordList = visitor.replaces.filter { it.elementType == SqlTypes.KEYWORD }
@@ -122,7 +142,7 @@ class SqlFormatPreProcessor : PreFormatProcessor {
122142

123143
docManager.commitDocument(document)
124144

125-
return rangeToReformat.grown(visitor.replaces.size)
145+
return ProcessResult(document, rangeToReformat.grown(visitor.replaces.size))
126146
}
127147

128148
private fun removeSpacesAroundNewline(

src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlInjectionPostProcessor.kt

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,27 @@ import com.intellij.lang.injection.InjectedLanguageManager
1919
import com.intellij.openapi.util.TextRange
2020
import com.intellij.psi.PsiElement
2121
import com.intellij.psi.PsiFile
22-
import com.intellij.psi.PsiLiteralExpression
2322
import com.intellij.psi.codeStyle.CodeStyleSettings
2423
import org.domaframework.doma.intellij.common.dao.getDaoClass
2524
import org.domaframework.doma.intellij.common.isJavaOrKotlinFileType
2625
import org.domaframework.doma.intellij.common.isSupportFileType
26+
import org.domaframework.doma.intellij.common.util.InjectionSqlUtil
2727
import org.domaframework.doma.intellij.formatter.visitor.DaoInjectionSqlVisitor
28+
import org.domaframework.doma.intellij.formatter.visitor.FormattingTask
2829

30+
/**
31+
* Post-processor for SQL injection formatting.
32+
*
33+
* This processor handles SQL formatting in two contexts:
34+
* 1. **File formatting**: When formatting entire DAO files (Java/Kotlin) containing SQL annotations
35+
* 2. **Code formatting**: When formatting injected SQL fragments within string literals
36+
*
37+
* The context is determined by checking:
38+
* - If the source is an injected fragment (`InjectedLanguageManager.isInjectedFragment()` returns true),
39+
* it's being called from code formatting for a specific SQL string literal
40+
* - If the source is a regular DAO file (Java/Kotlin with @Dao annotation),
41+
* it's being called from file formatting to process all SQL strings in the file
42+
*/
2943
class SqlInjectionPostProcessor : SqlPostProcessor() {
3044
override fun processElement(
3145
element: PsiElement,
@@ -37,49 +51,55 @@ class SqlInjectionPostProcessor : SqlPostProcessor() {
3751
rangeToReformat: TextRange,
3852
settings: CodeStyleSettings,
3953
): TextRange {
40-
if (!shouldProcessFile(source)) {
54+
if (!isProcessFile(source)) {
4155
return rangeToReformat
4256
}
4357

4458
val manager = InjectedLanguageManager.getInstance(source.project)
4559
if (manager.isInjectedFragment(source)) {
46-
processInjectedFragment(source, manager)
60+
processInjectedFragment(source)
4761
} else {
4862
processRegularFile(source)
4963
}
5064

5165
return rangeToReformat
5266
}
5367

54-
private fun shouldProcessFile(source: PsiFile): Boolean {
68+
private fun isProcessFile(source: PsiFile): Boolean {
5569
val manager = InjectedLanguageManager.getInstance(source.project)
5670
val isInjectedSql = if (isSupportFileType(source)) manager.isInjectedFragment(source) else false
5771
val isDaoFile = isJavaOrKotlinFileType(source) && getDaoClass(source) != null
5872

5973
return isInjectedSql || isDaoFile
6074
}
6175

62-
private fun processInjectedFragment(
63-
source: PsiFile,
64-
manager: InjectedLanguageManager,
65-
) {
66-
val host = manager.getInjectionHost(source) as? PsiLiteralExpression ?: return
67-
val hostDaoFile = host.containingFile
76+
/**
77+
* Processes all SQL injections in a DAO file during file formatting.
78+
* This is called when formatting an entire DAO file containing SQL annotations.
79+
*/
80+
private fun processInjectedFragment(source: PsiFile) {
81+
val host = InjectionSqlUtil.getLiteralExpressionHost(source) ?: return
6882
val originalText = host.value?.toString() ?: return
6983

70-
val visitor = DaoInjectionSqlVisitor(hostDaoFile, source.project)
71-
val formattingTask = DaoInjectionSqlVisitor.FormattingTask(host, originalText)
84+
val visitor = DaoInjectionSqlVisitor(source.project)
85+
val formattingTask = FormattingTask(host, originalText, host.isTextBlock)
7286

73-
visitor.replaceHostStringLiteral(formattingTask) { text ->
87+
visitor.convertExpressionToTextBlock(formattingTask.expression)
88+
visitor.processFormattingTask(formattingTask) { text ->
7489
processDocumentText(text)
7590
}
7691
}
7792

93+
/**
94+
* Processes injected SQL fragments during code formatting.
95+
* This is called when formatting a specific SQL string literal within a DAO file.
96+
*/
7897
private fun processRegularFile(source: PsiFile) {
79-
val visitor = DaoInjectionSqlVisitor(source, source.project)
98+
val visitor = DaoInjectionSqlVisitor(source.project)
8099
source.accept(visitor)
81-
82-
visitor.processAll { text ->
100+
visitor.processAllTextBlock()
101+
source.accept(visitor)
102+
visitor.processAllReFormat { text ->
83103
processDocumentText(text)
84104
}
85105
}

0 commit comments

Comments
 (0)