Skip to content

Commit fb4e727

Browse files
committed
Refactor SQL formatting logic to improve readability and handling of condition loop comments
1 parent f70c6cd commit fb4e727

File tree

8 files changed

+329
-229
lines changed

8 files changed

+329
-229
lines changed

src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -65,23 +65,21 @@ open class SqlBlock(
6565
open val childBlocks = mutableListOf<SqlBlock>()
6666
open var prevBlocks = emptyList<SqlBlock>()
6767

68-
fun getChildrenTextLen(): Int =
69-
childBlocks.sumOf { child ->
70-
val children =
71-
child.childBlocks.filter { it !is SqlDefaultCommentBlock }
72-
if (children.isNotEmpty()) {
73-
child
74-
.getChildrenTextLen()
75-
.plus(child.getNodeText().length)
76-
} else if (child.node.elementType == SqlTypes.DOT ||
77-
child.node.elementType == SqlTypes.RIGHT_PAREN
78-
) {
79-
// Since elements do not include surrounding spaces, they should be excluded from the character count.
80-
0
81-
} else {
82-
child.getNodeText().length.plus(1)
83-
}
68+
fun getChildrenTextLen(): Int = childBlocks.sumOf { child -> calculateChildTextLength(child) }
69+
70+
private fun calculateChildTextLength(child: SqlBlock): Int {
71+
val nonCommentChildren = child.childBlocks.filterNot { it is SqlDefaultCommentBlock }
72+
73+
if (nonCommentChildren.isNotEmpty()) {
74+
return child.getChildrenTextLen() + child.getNodeText().length
8475
}
76+
if (isExcludedFromTextLength(child)) {
77+
return 0
78+
}
79+
return child.getNodeText().length + 1
80+
}
81+
82+
private fun isExcludedFromTextLength(block: SqlBlock): Boolean = block.node.elementType in setOf(SqlTypes.DOT, SqlTypes.RIGHT_PAREN)
8583

8684
fun getChildBlocksDropLast(
8785
dropIndex: Int = 1,
@@ -122,34 +120,48 @@ open class SqlBlock(
122120

123121
fun isEnableFormat(): Boolean = enableFormat
124122

125-
open fun isSaveSpace(lastGroup: SqlBlock?): Boolean {
123+
open fun isSaveSpace(lastGroup: SqlBlock?): Boolean =
126124
parentBlock?.let { parent ->
127-
if (parent is SqlElConditionLoopCommentBlock) {
128-
val prevBlock =
129-
prevBlocks.lastOrNull { it !is SqlDefaultCommentBlock }
130-
return prevBlock is SqlElConditionLoopCommentBlock &&
131-
(prevBlock.conditionType.isElse() || prevBlock.conditionType.isEnd()) ||
132-
parent.childBlocks.dropLast(1).isEmpty()
133-
}
134-
// Checks for non-breaking keyword combinations, ignoring comment blocks
135-
if (parent is SqlNewGroupBlock) {
136-
val prevWord = prevBlocks.lastOrNull { it !is SqlCommentBlock }
137-
if (SqlKeywordUtil.isSetLineKeyword(getNodeText(), parent.getNodeText()) ||
138-
SqlKeywordUtil.isSetLineKeyword(getNodeText(), prevWord?.getNodeText() ?: "")
139-
) {
140-
return false
141-
}
142-
// Breaks a line if it is a child of itself or preceded by a condition/loop directive
143-
return childBlocks.lastOrNull() is SqlElConditionLoopCommentBlock ||
144-
(
145-
prevBlocks.lastOrNull() is SqlElConditionLoopCommentBlock &&
146-
prevBlocks
147-
.last()
148-
.node.psi.startOffset > parent.node.psi.startOffset
149-
)
125+
when (parent) {
126+
is SqlElConditionLoopCommentBlock -> shouldSaveSpaceForConditionLoop(parent)
127+
is SqlNewGroupBlock -> shouldSaveSpaceForNewGroup(parent)
128+
else -> false
150129
}
130+
} == true
131+
132+
private fun shouldSaveSpaceForConditionLoop(parent: SqlElConditionLoopCommentBlock): Boolean {
133+
val prevBlock = prevBlocks.lastOrNull { it !is SqlDefaultCommentBlock }
134+
val isPrevBlockElseOrEnd =
135+
prevBlock is SqlElConditionLoopCommentBlock &&
136+
(prevBlock.conditionType.isElse() || prevBlock.conditionType.isEnd())
137+
val hasNoChildrenExceptLast = parent.childBlocks.dropLast(1).isEmpty()
138+
139+
return isPrevBlockElseOrEnd || hasNoChildrenExceptLast
140+
}
141+
142+
private fun shouldSaveSpaceForNewGroup(parent: SqlNewGroupBlock): Boolean {
143+
val prevWord = prevBlocks.lastOrNull { it !is SqlCommentBlock }
144+
145+
if (isNonBreakingKeywordCombination(parent, prevWord)) {
146+
return false
151147
}
152-
return false
148+
149+
return isFollowedByConditionLoop() || isPrecededByConditionLoop(parent)
150+
}
151+
152+
private fun isNonBreakingKeywordCombination(
153+
parent: SqlNewGroupBlock,
154+
prevWord: SqlBlock?,
155+
): Boolean =
156+
SqlKeywordUtil.isSetLineKeyword(getNodeText(), parent.getNodeText()) ||
157+
SqlKeywordUtil.isSetLineKeyword(getNodeText(), prevWord?.getNodeText() ?: "")
158+
159+
private fun isFollowedByConditionLoop(): Boolean = childBlocks.lastOrNull() is SqlElConditionLoopCommentBlock
160+
161+
private fun isPrecededByConditionLoop(parent: SqlNewGroupBlock): Boolean {
162+
val lastPrevBlock = prevBlocks.lastOrNull()
163+
return lastPrevBlock is SqlElConditionLoopCommentBlock &&
164+
lastPrevBlock.node.psi.startOffset > parent.node.psi.startOffset
153165
}
154166

155167
/**
@@ -193,11 +205,15 @@ open class SqlBlock(
193205
*/
194206
override fun getChildIndent(): Indent? =
195207
if (isEnableFormat()) {
196-
Indent.getSpaceIndent(4)
208+
Indent.getSpaceIndent(DEFAULT_INDENT_SIZE)
197209
} else {
198210
Indent.getSpaceIndent(0)
199211
}
200212

213+
companion object {
214+
private const val DEFAULT_INDENT_SIZE = 4
215+
}
216+
201217
/**
202218
* Determines whether the block is a leaf node.
203219
*/

src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ import org.domaframework.doma.intellij.formatter.processor.SqlSetParentGroupProc
6161
import org.domaframework.doma.intellij.formatter.util.CreateTableUtil
6262
import org.domaframework.doma.intellij.formatter.util.IndentType
6363
import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext
64-
import org.domaframework.doma.intellij.formatter.util.SqlBlockUtil
64+
import org.domaframework.doma.intellij.formatter.util.SqlBlockGenerator
6565
import org.domaframework.doma.intellij.psi.SqlTypes
6666

6767
class SqlFileBlock(
@@ -95,7 +95,7 @@ class SqlFileBlock(
9595

9696
private val blockBuilder = SqlBlockBuilder()
9797
private val parentSetProcessor = SqlSetParentGroupProcessor(blockBuilder)
98-
private val blockUtil = SqlBlockUtil(this, isEnableFormat(), formatMode)
98+
private val blockUtil = SqlBlockGenerator(this, isEnableFormat(), formatMode)
9999

100100
private val pendingCommentBlocks = mutableListOf<SqlBlock>()
101101

@@ -114,9 +114,6 @@ class SqlFileBlock(
114114
updateCommentParentAndIdent(childBlock)
115115
updateBlockParentAndLAddGroup(childBlock)
116116
updateWhiteSpaceInclude(lastBlock, childBlock, lastGroup)
117-
// TODO After processing the END directive block,
118-
// if there is only one element (with two or fewer spaces),
119-
// remove the line breaks within the if~end block and consolidate it into a single line.
120117
blocks.add(childBlock)
121118
} else {
122119
if (lastBlock !is SqlLineCommentBlock) {

src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,11 @@ class SqlElConditionLoopCommentBlock(
270270

271271
override fun createGroupIndentLen(): Int = indent.indentLen
272272

273+
/**
274+
* Count the number of [SqlElConditionLoopCommentBlock] within the same parent block.
275+
* Since the current directive is included in the count,
276+
* **subtract 1 at the end** to exclude itself.
277+
*/
273278
private fun getOpenDirectiveCount(parent: SqlBlock): Int {
274279
val conditionLoopDirectives: List<SqlElConditionLoopCommentBlock> =
275280
parent

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

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ import com.intellij.psi.impl.source.codeStyle.PostFormatProcessor
2727
import org.domaframework.doma.intellij.setting.SqlLanguage
2828

2929
class SqlPostProcessor : PostFormatProcessor {
30+
companion object {
31+
private const val FILE_END_PADDING = " \n"
32+
}
33+
34+
private val trailingSpacesRegex = Regex(" +(\r?\n)")
35+
3036
override fun processElement(
3137
source: PsiElement,
3238
settings: CodeStyleSettings,
@@ -37,25 +43,44 @@ class SqlPostProcessor : PostFormatProcessor {
3743
rangeToReformat: TextRange,
3844
settings: CodeStyleSettings,
3945
): TextRange {
40-
if (source.language != SqlLanguage.INSTANCE) return rangeToReformat
41-
42-
val project: Project = source.project
43-
val document = PsiDocumentManager.getInstance(project).getDocument(source) ?: return rangeToReformat
46+
if (!isSqlFile(source)) {
47+
return rangeToReformat
48+
}
4449

45-
val originalText = document.text
46-
val withoutTrailingSpaces = originalText.replace(Regex(" +(\r?\n)"), "$1")
47-
val finalText = withoutTrailingSpaces.trimEnd() + " \n"
50+
val document = getDocument(source) ?: return rangeToReformat
51+
val processedText = processDocumentText(document.text)
4852

49-
if (originalText == finalText) {
53+
if (document.text == processedText) {
5054
return rangeToReformat
5155
}
5256

57+
updateDocument(source.project, document, processedText)
58+
return TextRange(0, processedText.length)
59+
}
60+
61+
private fun isSqlFile(source: PsiFile): Boolean = source.language == SqlLanguage.INSTANCE
62+
63+
private fun getDocument(source: PsiFile) = PsiDocumentManager.getInstance(source.project).getDocument(source)
64+
65+
private fun processDocumentText(originalText: String): String {
66+
val withoutTrailingSpaces = removeTrailingSpaces(originalText)
67+
return ensureProperFileEnding(withoutTrailingSpaces)
68+
}
69+
70+
private fun removeTrailingSpaces(text: String): String = text.replace(trailingSpacesRegex, "$1")
71+
72+
private fun ensureProperFileEnding(text: String): String = text.trimEnd() + FILE_END_PADDING
73+
74+
private fun updateDocument(
75+
project: Project,
76+
document: com.intellij.openapi.editor.Document,
77+
newText: String,
78+
) {
5379
ApplicationManager.getApplication().invokeAndWait {
5480
WriteCommandAction.runWriteCommandAction(project) {
55-
document.setText(finalText)
81+
document.setText(newText)
5682
PsiDocumentManager.getInstance(project).commitDocument(document)
5783
}
5884
}
59-
return TextRange(0, finalText.length)
6085
}
6186
}

0 commit comments

Comments
 (0)