Skip to content

Commit c70f555

Browse files
authored
Merge pull request #444 from domaframework/chore/sql-format-refactoring-blocks-relation
Refactor SQL Formatter Block Relations and Translate Comments
2 parents dff8591 + cff367e commit c70f555

File tree

70 files changed

+780
-1051
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+780
-1051
lines changed

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

Lines changed: 118 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,17 @@ open class SqlBlock(
6363
var groupIndentLen: Int,
6464
)
6565

66+
// Maintain the conditional loop directive block associated with itself.
67+
var conditionLoopDirective: SqlElConditionLoopCommentBlock? = null
68+
69+
// A flag that exists within a conditional loop directive but is not associated with the directive
70+
// (representing the top element of multiple lines).
71+
var multipleInlineDirective = false
72+
6673
open var parentBlock: SqlBlock? = null
6774
open val childBlocks = mutableListOf<SqlBlock>()
6875
open var prevBlocks = emptyList<SqlBlock>()
76+
open val offset = 0
6977

7078
companion object {
7179
private const val DEFAULT_INDENT_SIZE = 4
@@ -93,76 +101,6 @@ open class SqlBlock(
93101

94102
private fun isExcludedFromTextLength(block: SqlBlock): Boolean = block.node.elementType in EXCLUDED_FROM_TEXT_LENGTH
95103

96-
/**
97-
* Checks if a conditional loop directive is registered before the parent block.
98-
*
99-
* @note
100-
* If the next element after a conditional directive is not a conditional directive block,
101-
* the directive becomes a child of the next element block.
102-
* Therefore, if the first element in [childBlocks] is a conditional directive,
103-
* it can be determined that—syntactically—the conditional directive was placed immediately before the current block.
104-
*/
105-
protected fun isConditionLoopDirectiveRegisteredBeforeParent(): Boolean {
106-
val firstPrevBlock = (prevBlocks.lastOrNull() as? SqlElConditionLoopCommentBlock)
107-
parentBlock?.let { parent ->
108-
return (childBlocks.firstOrNull() as? SqlElConditionLoopCommentBlock)?.isBeforeParentBlock() == true ||
109-
firstPrevBlock != null &&
110-
firstPrevBlock.conditionEnd != null &&
111-
firstPrevBlock.node.startOffset > parent.node.startOffset
112-
}
113-
return false
114-
}
115-
116-
/**
117-
* Determines if this is the element immediately after a conditional loop directive.
118-
*
119-
* @note
120-
* The parent conditional loop directive becomes a child of the element immediately after the conditional loop directive.
121-
* In the following case, "%if" is a child of "status", and the following "=" and "'pending'" are children of "%if".
122-
* Therefore, set the condition to break line only when the parent of the conditional loop directive is a group block.
123-
*
124-
* @example
125-
* ```sql
126-
* WHERE -- grand
127-
* /*%if status == "pending" */ -- parent
128-
* status = 'pending' -- child
129-
* ```
130-
*/
131-
protected fun isElementAfterConditionLoopDirective(): Boolean =
132-
(parentBlock as? SqlElConditionLoopCommentBlock)?.let { parent ->
133-
parent.childBlocks.firstOrNull() == this &&
134-
(parent.parentBlock is SqlNewGroupBlock || parent.isParentConditionLoopDirective())
135-
} == true
136-
137-
protected fun isElementAfterConditionLoopEnd(): Boolean {
138-
val prevChildren =
139-
prevBlocks
140-
.firstOrNull()
141-
?.childBlocks
142-
143-
val firstConditionBlock = prevChildren?.firstOrNull { it is SqlElConditionLoopCommentBlock } as? SqlElConditionLoopCommentBlock
144-
val endBlock =
145-
findConditionEndBlock(firstConditionBlock)
146-
if (endBlock == null) return false
147-
val lastBlock = prevBlocks.lastOrNull()
148-
149-
return endBlock.node.startOffset > (lastBlock?.node?.startOffset ?: 0)
150-
}
151-
152-
private fun findConditionEndBlock(firstConditionBlock: SqlElConditionLoopCommentBlock?): SqlElConditionLoopCommentBlock? =
153-
(
154-
firstConditionBlock?.conditionEnd
155-
?: (
156-
firstConditionBlock?.childBlocks?.lastOrNull {
157-
it is SqlElConditionLoopCommentBlock
158-
} as? SqlElConditionLoopCommentBlock
159-
)?.conditionEnd
160-
)
161-
162-
fun isParentConditionLoopDirective(): Boolean = parentBlock is SqlElConditionLoopCommentBlock
163-
164-
protected fun isFirstChildConditionLoopDirective(): Boolean = childBlocks.firstOrNull() is SqlElConditionLoopCommentBlock
165-
166104
fun getChildBlocksDropLast(
167105
dropIndex: Int = 1,
168106
skipCommentBlock: Boolean = true,
@@ -185,11 +123,103 @@ open class SqlBlock(
185123
0,
186124
)
187125

126+
/**
127+
* Calculate indentation and line breaks based on the parent block and conditional loop directives with no dependency target set.
128+
* @param lastGroup The last group block
129+
*/
188130
open fun setParentGroupBlock(lastGroup: SqlBlock?) {
189131
parentBlock = lastGroup
190-
prevBlocks = parentBlock?.childBlocks?.toList() ?: emptyList()
132+
setPrevBlocks()
191133
parentBlock?.addChildBlock(this)
192134
setParentPropertyBlock(lastGroup)
135+
setIndentLen()
136+
}
137+
138+
fun setPrevBlocks(parent: SqlBlock? = parentBlock) {
139+
parent?.let { p ->
140+
// Retrieve the first conditional loop directive and the closing tag of the last conditional loop directive.
141+
val firstConditionStart = p.childBlocks.firstOrNull { it.conditionLoopDirective != null }?.conditionLoopDirective
142+
val lastConditionEnd =
143+
p.childBlocks
144+
.lastOrNull {
145+
it.conditionLoopDirective != null &&
146+
it.conditionLoopDirective?.conditionEnd != null
147+
}?.conditionLoopDirective
148+
?.conditionEnd
149+
var openDirective: SqlElConditionLoopCommentBlock? = null
150+
151+
val filterBlockInlineOpenDirectives =
152+
if (firstConditionStart != null && lastConditionEnd != null) {
153+
p.childBlocks.filterNot {
154+
it.node.startOffset in
155+
(firstConditionStart.node.startOffset until lastConditionEnd.node.startOffset)
156+
}
157+
} else if (firstConditionStart != null) {
158+
openDirective = firstConditionStart
159+
p.childBlocks.filter { it.node.startOffset >= firstConditionStart.node.startOffset }
160+
} else {
161+
p.childBlocks
162+
}
163+
164+
prevBlocks = filterBlockInlineOpenDirectives.filter { it != this }
165+
}
166+
}
167+
168+
/**
169+
* Indentation calculation
170+
*
171+
* * When `multipleInlineDirective` is **true**: sibling blocks whose parent is **outside** the conditional loop directive.
172+
* * When `multipleInlineDirective` is **false**: sibling blocks whose parent is **inside** the conditional loop directive, or the block body that the conditional loop directive depends on.
173+
*
174+
*/
175+
protected fun setIndentLen(baseDirective: SqlElConditionLoopCommentBlock? = conditionLoopDirective): Int {
176+
indent.indentLen =
177+
if (multipleInlineDirective) {
178+
baseDirective?.indent?.indentLen ?: indent.indentLen
179+
} else {
180+
if (baseDirective?.getDependsOnBlock() == this) {
181+
baseDirective.indent.indentLen // The block body that the conditional loop directive depends on.
182+
} else {
183+
createBlockIndentLen() // Sibling blocks whose parent is within a conditional loop directive.
184+
}
185+
}
186+
return indent.indentLen
187+
}
188+
189+
fun createBlockIndentLenDirective(
190+
parent: SqlBlock?,
191+
dependDirective: SqlElConditionLoopCommentBlock?,
192+
) {
193+
val dependDirectiveOnBlock = dependDirective?.getDependsOnBlock()
194+
if (dependDirectiveOnBlock == null) {
195+
conditionLoopDirective = dependDirective
196+
conditionLoopDirective?.setDependsOnBlock(this)
197+
conditionLoopDirective?.createBlockIndentLenFromDependOn(this)
198+
}
199+
setPrevBlocks(parent)
200+
// Check its own parent block and the parent of the block that `notDependDirective` depends on,
201+
// and adjust the indentation as needed so that it fits within the conditional loop directive block.
202+
val directiveDependent = conditionLoopDirective?.getDependsOnBlock()
203+
if (directiveDependent == null || parent == directiveDependent.parentBlock) {
204+
// Search among sibling blocks for those associated with a conditional loop directive.
205+
// Even if a sibling block is associated with a conditional loop directive,
206+
// there are cases where the indentation is aligned with the parent rather than the conditional loop directive.
207+
val inlineDirectiveParentBlock =
208+
parent?.let { p -> p.node.startOffset >= (dependDirective?.node?.startOffset ?: 0) } == true
209+
multipleInlineDirective = dependDirective?.getDependsOnBlock() != this && !inlineDirectiveParentBlock
210+
setIndentLen(dependDirective)
211+
}
212+
}
213+
214+
/**
215+
* Trace back from the associated directive and recalculate the indentation of the nested directives.
216+
*/
217+
fun recalculateDirectiveIndent() {
218+
conditionLoopDirective?.let { directive ->
219+
directive.recalculateIndentLen(createBlockIndentLen())
220+
indent.indentLen = directive.indent.indentLen
221+
indent.groupIndentLen = createGroupIndentLen()
222+
}
193223
}
194224

195225
open fun setParentPropertyBlock(lastGroup: SqlBlock?) {
@@ -206,18 +236,15 @@ open class SqlBlock(
206236

207237
fun isEnableFormat(): Boolean = enableFormat
208238

239+
/**
240+
* Block-specific line break determination.
241+
*/
209242
open fun isSaveSpace(lastGroup: SqlBlock?): Boolean =
210243
when (lastGroup) {
211244
is SqlNewGroupBlock -> shouldSaveSpaceForNewGroup(lastGroup)
212-
else -> shouldSaveSpaceForConditionLoop()
245+
else -> false
213246
}
214247

215-
private fun shouldSaveSpaceForConditionLoop(): Boolean =
216-
isConditionLoopDirectiveRegisteredBeforeParent() ||
217-
isElementAfterConditionLoopDirective() ||
218-
isFirstChildConditionLoopDirective() ||
219-
isElementAfterConditionLoopEnd()
220-
221248
private fun shouldSaveSpaceForNewGroup(parent: SqlNewGroupBlock): Boolean {
222249
val prevWord = prevBlocks.lastOrNull { it !is SqlCommentBlock }
223250

@@ -250,16 +277,16 @@ open class SqlBlock(
250277
lastPrevBlock.node.psi.startOffset > parent.node.psi.startOffset
251278
}
252279

253-
protected fun getLastBlockHasConditionLoopDirective(): SqlElConditionLoopCommentBlock? =
254-
(prevBlocks.lastOrNull()?.childBlocks?.firstOrNull() as? SqlElConditionLoopCommentBlock)
255-
256280
/**
257-
* Creates the indentation length for the block.
258-
*
281+
* Set the indentation for line breaks caused by conditional loop directives based on the parent block and the conditional loop directive.
259282
* @return The number of spaces to use for indentation
260283
*/
261284
open fun createBlockIndentLen(): Int = 0
262285

286+
/**
287+
* Calculate the indentation to apply to its own child blocks.
288+
* @return The number of spaces to use for child block indentation
289+
*/
263290
open fun createGroupIndentLen(): Int = 0
264291

265292
open fun getBlock(child: ASTNode): SqlBlock = this
@@ -329,31 +356,25 @@ open class SqlBlock(
329356
SqlTypes.EL_STATIC_FIELD_ACCESS_EXPR,
330357
)
331358

332-
// Add spacing rules for BLOCK_COMMENT_START
333359
typesNeedingSpaceAfterStart.forEach { type ->
334360
builder.withSpacing(SqlTypes.BLOCK_COMMENT_START, type, SPACING_ONE)
335361
}
336362

337-
// Special cases for BLOCK_COMMENT_START
338363
builder.withSpacing(SqlTypes.BLOCK_COMMENT_START, SqlTypes.HASH, SPACING_ZERO)
339364
builder.withSpacing(SqlTypes.BLOCK_COMMENT_START, SqlTypes.CARET, SPACING_ZERO)
340365

341-
// Add spacing rules for HASH
342366
typesNeedingSpaceAfterStart.forEach { type ->
343367
builder.withSpacing(SqlTypes.HASH, type, SPACING_ONE)
344368
}
345369

346-
// Add spacing rules for CARET
347370
typesNeedingSpaceAfterStart.forEach { type ->
348371
builder.withSpacing(SqlTypes.CARET, type, SPACING_ONE)
349372
}
350373

351-
// Special spacing rules
352374
builder.withSpacing(SqlTypes.BLOCK_COMMENT_CONTENT, SqlTypes.BLOCK_COMMENT_END, SPACING_ZERO)
353375
builder.withSpacing(SqlTypes.EL_FIELD_ACCESS_EXPR, SqlTypes.OTHER, SPACING_ONE_NO_KEEP)
354376
builder.withSpacing(SqlTypes.EL_STATIC_FIELD_ACCESS_EXPR, SqlTypes.OTHER, SPACING_ONE_NO_KEEP)
355377

356-
// Add spacing rules before BLOCK_COMMENT_END
357378
typesNeedingSpaceBeforeEnd.forEach { type ->
358379
builder.withSpacing(type, SqlTypes.BLOCK_COMMENT_END, SPACING_ONE)
359380
}
@@ -365,20 +386,13 @@ open class SqlBlock(
365386
children: List<SqlBlock>,
366387
parent: SqlBlock,
367388
): Int {
368-
// Add the parent's text length to the indentation if the parent is a conditional loop directive.
369-
val directiveParentIndent =
370-
if (parent is SqlElConditionLoopCommentBlock) {
371-
parent.parentBlock
372-
?.getNodeText()
373-
?.length ?: 0
374-
} else {
375-
0
376-
}
377-
378389
var prevBlock: SqlBlock? = null
379-
return children
380-
.filter { it !is SqlDefaultCommentBlock && it !is SqlElConditionLoopCommentBlock }
381-
.sumOf { prev ->
390+
val prevChildren =
391+
children
392+
.filter { it !is SqlDefaultCommentBlock }
393+
394+
val prevSumLength =
395+
prevChildren.sumOf { prev ->
382396
val sum =
383397
prev
384398
.getChildrenTextLen()
@@ -397,8 +411,8 @@ open class SqlBlock(
397411
)
398412
prevBlock = prev
399413
return@sumOf sum
400-
}.plus(parent.indent.groupIndentLen)
401-
.plus(directiveParentIndent)
414+
}
415+
return prevSumLength.plus(parent.indent.groupIndentLen)
402416
}
403417

404418
fun isOperationSymbol(): Boolean =

0 commit comments

Comments
 (0)