Skip to content

Commit 9214e89

Browse files
committed
Apply individual formatting logic to SQL injected via SQL annotations.
1 parent dac1f8f commit 9214e89

File tree

5 files changed

+207
-12
lines changed

5 files changed

+207
-12
lines changed

src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlFormattingModelBuilder.kt

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,25 +38,35 @@ import org.domaframework.doma.intellij.setting.state.DomaToolsFormatEnableSettin
3838
class SqlFormattingModelBuilder : FormattingModelBuilder {
3939
override fun createModel(formattingContext: FormattingContext): FormattingModel {
4040
val codeStyleSettings = formattingContext.codeStyleSettings
41+
42+
return createRegularSQLModel(formattingContext, codeStyleSettings)
43+
}
44+
45+
private fun createRegularSQLModel(
46+
formattingContext: FormattingContext,
47+
settings: CodeStyleSettings,
48+
): FormattingModel {
4149
val setting = DomaToolsFormatEnableSettings.getInstance()
4250
val isEnableFormat = setting.state.isEnableSqlFormat == true
4351
val formatMode = formattingContext.formattingMode
44-
val spacingBuilder = createSpaceBuilder(codeStyleSettings)
52+
val spacingBuilder = createSpaceBuilder(settings)
4553
val customSpacingBuilder = createCustomSpacingBuilder()
4654

55+
val block =
56+
SqlFileBlock(
57+
formattingContext.node,
58+
Wrap.createWrap(WrapType.NONE, false),
59+
Alignment.createAlignment(),
60+
customSpacingBuilder,
61+
spacingBuilder,
62+
isEnableFormat,
63+
formatMode,
64+
)
4765
return FormattingModelProvider
4866
.createFormattingModelForPsiFile(
4967
formattingContext.containingFile,
50-
SqlFileBlock(
51-
formattingContext.node,
52-
Wrap.createWrap(WrapType.NONE, false),
53-
Alignment.createAlignment(),
54-
customSpacingBuilder,
55-
spacingBuilder,
56-
isEnableFormat,
57-
formatMode,
58-
),
59-
codeStyleSettings,
68+
block,
69+
settings,
6070
)
6171
}
6272

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright Doma Tools Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.domaframework.doma.intellij.formatter.processor
17+
18+
import com.intellij.openapi.project.Project
19+
import com.intellij.openapi.util.TextRange
20+
import com.intellij.psi.PsiElement
21+
import com.intellij.psi.PsiFile
22+
import com.intellij.psi.codeStyle.CodeStyleSettings
23+
import com.intellij.psi.impl.source.codeStyle.PostFormatProcessor
24+
import org.domaframework.doma.intellij.common.isJavaOrKotlinFileType
25+
import org.domaframework.doma.intellij.formatter.visitor.DaoInjectionSqlVisitor
26+
27+
class SqlInjectionPostProcessor : PostFormatProcessor {
28+
override fun processElement(
29+
element: PsiElement,
30+
settings: CodeStyleSettings,
31+
): PsiElement = element
32+
33+
override fun processText(
34+
source: PsiFile,
35+
rangeToReformat: TextRange,
36+
settings: CodeStyleSettings,
37+
): TextRange {
38+
if (!isJavaOrKotlinFileType(source)) return rangeToReformat
39+
40+
processInjected(source)
41+
return rangeToReformat
42+
}
43+
44+
private fun processInjected(element: PsiFile) {
45+
val project: Project = element.project
46+
val visitor = DaoInjectionSqlVisitor(element, project)
47+
element.accept(visitor)
48+
visitor.processAll()
49+
}
50+
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package org.domaframework.doma.intellij.formatter.processor
1717

18-
import com.intellij.lang.injection.InjectedLanguageManager
1918
import com.intellij.openapi.application.ApplicationManager
2019
import com.intellij.openapi.command.WriteCommandAction
2120
import com.intellij.openapi.editor.Document
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright Doma Tools Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.domaframework.doma.intellij.formatter.visitor
17+
18+
import com.intellij.openapi.command.WriteCommandAction
19+
import com.intellij.openapi.fileTypes.FileTypeManager
20+
import com.intellij.openapi.project.Project
21+
import com.intellij.openapi.util.TextRange
22+
import com.intellij.psi.JavaRecursiveElementVisitor
23+
import com.intellij.psi.PsiDocumentManager
24+
import com.intellij.psi.PsiFile
25+
import com.intellij.psi.PsiFileFactory
26+
import com.intellij.psi.PsiLiteralExpression
27+
import com.intellij.psi.codeStyle.CodeStyleManager
28+
import org.domaframework.doma.intellij.common.util.InjectionSqlUtil
29+
import org.domaframework.doma.intellij.common.util.StringUtil
30+
31+
class DaoInjectionSqlVisitor(
32+
private val element: PsiFile,
33+
private val project: Project,
34+
) : JavaRecursiveElementVisitor() {
35+
private data class FormattingTask(
36+
val expression: PsiLiteralExpression,
37+
val formattedText: String,
38+
)
39+
40+
companion object {
41+
private const val TEMP_FILE_PREFIX = "temp_format"
42+
private const val SQL_FILE_EXTENSION = ".sql"
43+
}
44+
45+
private val formattingTasks = mutableListOf<FormattingTask>()
46+
47+
override fun visitLiteralExpression(expression: PsiLiteralExpression) {
48+
super.visitLiteralExpression(expression)
49+
val injected: PsiFile? = InjectionSqlUtil.initInjectionElement(element, project, expression)
50+
if (injected != null) {
51+
// Format SQL and store the task
52+
val formattedSql = formatInjectedSql(injected)
53+
val originalText = expression.value?.toString() ?: return
54+
if (formattedSql != originalText) {
55+
formattingTasks.add(FormattingTask(expression, formattedSql))
56+
}
57+
}
58+
}
59+
60+
fun processAll() {
61+
if (formattingTasks.isEmpty()) return
62+
63+
// Apply all formatting tasks in a single write action
64+
WriteCommandAction.runWriteCommandAction(project, "Format Injected SQL", null, {
65+
// Sort by text range in descending order to maintain offsets
66+
formattingTasks.sortedByDescending { it.expression.textRange.startOffset }.forEach { task ->
67+
if (task.expression.isValid) {
68+
replaceHostStringLiteral(task.expression, task.formattedText)
69+
}
70+
}
71+
})
72+
}
73+
74+
private fun formatInjectedSql(injectedFile: PsiFile): String =
75+
try {
76+
val originalSqlText = injectedFile.text
77+
formatAsTemporarySqlFile(originalSqlText)
78+
} catch (_: Exception) {
79+
injectedFile.text
80+
}
81+
82+
/**
83+
* Execute formatting as a temporary SQL file
84+
*/
85+
private fun formatAsTemporarySqlFile(sqlText: String): String =
86+
try {
87+
val tempFileName = "${TEMP_FILE_PREFIX}${SQL_FILE_EXTENSION}"
88+
val fileType = FileTypeManager.getInstance().getFileTypeByExtension("sql")
89+
90+
val tempSqlFile =
91+
PsiFileFactory
92+
.getInstance(project)
93+
.createFileFromText(tempFileName, fileType, sqlText)
94+
95+
val codeStyleManager = CodeStyleManager.getInstance(project)
96+
val textRange = TextRange(0, tempSqlFile.textLength)
97+
codeStyleManager.reformatText(tempSqlFile, textRange.startOffset, textRange.endOffset)
98+
99+
tempSqlFile.text
100+
} catch (_: Exception) {
101+
sqlText
102+
}
103+
104+
/**
105+
* Directly replace host Java string literal
106+
*/
107+
private fun replaceHostStringLiteral(
108+
literalExpression: PsiLiteralExpression,
109+
formattedSqlText: String,
110+
) {
111+
try {
112+
// Create new string literal
113+
val newLiteralText = createFormattedLiteralText(formattedSqlText)
114+
115+
// Replace PSI element
116+
val elementFactory =
117+
com.intellij.psi.JavaPsiFacade
118+
.getElementFactory(project)
119+
val newLiteral = elementFactory.createExpressionFromText(newLiteralText, literalExpression)
120+
val manager = PsiDocumentManager.getInstance(literalExpression.project)
121+
val document = manager.getDocument(literalExpression.containingFile) ?: return
122+
document.replaceString(literalExpression.textRange.startOffset, literalExpression.textRange.endOffset, newLiteral.text)
123+
} catch (_: Exception) {
124+
// Host literal replacement failed: ${e.message}
125+
}
126+
}
127+
128+
/**
129+
* Create appropriate Java string literal from formatted SQL
130+
*/
131+
private fun createFormattedLiteralText(formattedSqlText: String): String {
132+
val lines = formattedSqlText.split(StringUtil.LINE_SEPARATE)
133+
return "\"\"\"${StringUtil.LINE_SEPARATE}${lines.joinToString(StringUtil.LINE_SEPARATE)}\"\"\""
134+
}
135+
}

src/main/resources/META-INF/plugin.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
implementationClass="org.domaframework.doma.intellij.formatter.builder.SqlFormattingModelBuilder"/>
6868
<preFormatProcessor implementation="org.domaframework.doma.intellij.formatter.processor.SqlFormatPreProcessor" />
6969
<postFormatProcessor implementation="org.domaframework.doma.intellij.formatter.processor.SqlPostProcessor" />
70+
<postFormatProcessor implementation="org.domaframework.doma.intellij.formatter.processor.SqlInjectionPostProcessor" />
7071

7172
<!-- CustomLanguage -->
7273
<fileType

0 commit comments

Comments
 (0)