Skip to content

Commit 1bfffff

Browse files
authored
Merge pull request #411 from domaframework/fix/sql-formatter
Fix: SQL Formatter
2 parents b0fc814 + 6a26654 commit 1bfffff

File tree

57 files changed

+1728
-419
lines changed

Some content is hidden

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

57 files changed

+1728
-419
lines changed

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

Lines changed: 98 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,28 @@ open class SqlBlock(
6767
open val childBlocks = mutableListOf<SqlBlock>()
6868
open var prevBlocks = emptyList<SqlBlock>()
6969

70+
companion object {
71+
private const val DEFAULT_INDENT_SIZE = 4
72+
private const val DEFAULT_TEXT_LENGTH_INCREMENT = 1
73+
private val EXCLUDED_FROM_TEXT_LENGTH = setOf(SqlTypes.DOT, SqlTypes.RIGHT_PAREN)
74+
private val SPACING_ONE = Spacing.createSpacing(1, 1, 0, true, 0)
75+
private val SPACING_ZERO = Spacing.createSpacing(0, 0, 0, true, 0)
76+
private val SPACING_ONE_NO_KEEP = Spacing.createSpacing(1, 1, 0, false, 0)
77+
}
78+
7079
fun getChildrenTextLen(): Int = childBlocks.sumOf { child -> calculateChildTextLength(child) }
7180

7281
private fun calculateChildTextLength(child: SqlBlock): Int {
7382
val nonCommentChildren = child.childBlocks.filterNot { it is SqlDefaultCommentBlock }
7483

75-
if (nonCommentChildren.isNotEmpty()) {
76-
return child.getChildrenTextLen() + child.getNodeText().length
84+
return when {
85+
nonCommentChildren.isNotEmpty() -> child.getChildrenTextLen() + child.getNodeText().length
86+
isExcludedFromTextLength(child) -> 0
87+
else -> child.getNodeText().length + DEFAULT_TEXT_LENGTH_INCREMENT
7788
}
78-
if (isExcludedFromTextLength(child)) {
79-
return 0
80-
}
81-
return child.getNodeText().length + 1
8289
}
8390

84-
private fun isExcludedFromTextLength(block: SqlBlock): Boolean = block.node.elementType in setOf(SqlTypes.DOT, SqlTypes.RIGHT_PAREN)
91+
private fun isExcludedFromTextLength(block: SqlBlock): Boolean = block.node.elementType in EXCLUDED_FROM_TEXT_LENGTH
8592

8693
/**
8794
* Checks if a conditional loop directive is registered before the parent block.
@@ -112,9 +119,9 @@ open class SqlBlock(
112119
*
113120
* @example
114121
* ```sql
115-
* WHERE
116-
* /*%if status == "pending" */
117-
* status = 'pending'
122+
* WHERE -- grand
123+
* /*%if status == "pending" */ -- parent
124+
* status = 'pending' -- child
118125
* ```
119126
*/
120127
protected fun isElementAfterConditionLoopDirective(): Boolean =
@@ -123,15 +130,33 @@ open class SqlBlock(
123130
(parent.parentBlock is SqlNewGroupBlock || parent.parentBlock is SqlElConditionLoopCommentBlock)
124131
} == true
125132

133+
protected fun isElementAfterConditionLoopEnd(): Boolean {
134+
val prevChildren =
135+
prevBlocks
136+
.firstOrNull()
137+
?.childBlocks
138+
139+
val firstConditionBlock = (prevChildren?.firstOrNull() as? SqlElConditionLoopCommentBlock)
140+
val endBlock = firstConditionBlock?.conditionEnd
141+
if (endBlock == null) return false
142+
val lastBlock = prevBlocks.lastOrNull()
143+
144+
return endBlock.node.startOffset > (lastBlock?.node?.startOffset ?: 0)
145+
}
146+
126147
protected fun isFirstChildConditionLoopDirective(): Boolean = childBlocks.firstOrNull() is SqlElConditionLoopCommentBlock
127148

128149
fun getChildBlocksDropLast(
129150
dropIndex: Int = 1,
130151
skipCommentBlock: Boolean = true,
152+
skipConditionLoopCommentBlock: Boolean = true,
131153
): List<SqlBlock> {
132-
val children = childBlocks.dropLast(dropIndex)
154+
var children = childBlocks.dropLast(dropIndex)
133155
if (skipCommentBlock) {
134-
return children.filter { it !is SqlDefaultCommentBlock }
156+
children = children.filter { it !is SqlDefaultCommentBlock }
157+
}
158+
if (skipConditionLoopCommentBlock) {
159+
children = children.filter { it !is SqlElConditionLoopCommentBlock }
135160
}
136161
return children
137162
}
@@ -167,15 +192,14 @@ open class SqlBlock(
167192
open fun isSaveSpace(lastGroup: SqlBlock?): Boolean =
168193
when (lastGroup) {
169194
is SqlNewGroupBlock -> shouldSaveSpaceForNewGroup(lastGroup)
170-
else -> {
171-
shouldSaveSpaceForConditionLoop()
172-
}
173-
} == true
195+
else -> shouldSaveSpaceForConditionLoop()
196+
}
174197

175198
private fun shouldSaveSpaceForConditionLoop(): Boolean =
176199
isConditionLoopDirectiveRegisteredBeforeParent() ||
177200
isElementAfterConditionLoopDirective() ||
178-
isFirstChildConditionLoopDirective()
201+
isFirstChildConditionLoopDirective() ||
202+
isElementAfterConditionLoopEnd()
179203

180204
private fun shouldSaveSpaceForNewGroup(parent: SqlNewGroupBlock): Boolean {
181205
val prevWord = prevBlocks.lastOrNull { it !is SqlCommentBlock }
@@ -184,15 +208,22 @@ open class SqlBlock(
184208
return false
185209
}
186210

187-
return isFollowedByConditionLoop() || isPrecededByConditionLoop(parent)
211+
return hasConditionLoopAround(parent)
188212
}
189213

190214
private fun isNonBreakingKeywordCombination(
191215
parent: SqlNewGroupBlock,
192216
prevWord: SqlBlock?,
193-
): Boolean =
194-
SqlKeywordUtil.isSetLineKeyword(getNodeText(), parent.getNodeText()) ||
195-
SqlKeywordUtil.isSetLineKeyword(getNodeText(), prevWord?.getNodeText() ?: "")
217+
): Boolean {
218+
val currentText = getNodeText()
219+
val parentText = parent.getNodeText()
220+
val prevText = prevWord?.getNodeText() ?: ""
221+
222+
return SqlKeywordUtil.isSetLineKeyword(currentText, parentText) ||
223+
SqlKeywordUtil.isSetLineKeyword(currentText, prevText)
224+
}
225+
226+
private fun hasConditionLoopAround(parent: SqlNewGroupBlock): Boolean = isFollowedByConditionLoop() || isPrecededByConditionLoop(parent)
196227

197228
private fun isFollowedByConditionLoop(): Boolean = childBlocks.lastOrNull() is SqlElConditionLoopCommentBlock
198229

@@ -202,6 +233,9 @@ open class SqlBlock(
202233
lastPrevBlock.node.psi.startOffset > parent.node.psi.startOffset
203234
}
204235

236+
protected fun getLastBlockHasConditionLoopDirective(): SqlElConditionLoopCommentBlock? =
237+
(prevBlocks.lastOrNull()?.childBlocks?.firstOrNull() as? SqlElConditionLoopCommentBlock)
238+
205239
/**
206240
* Creates the indentation length for the block.
207241
*
@@ -249,158 +283,67 @@ open class SqlBlock(
249283
/**
250284
* Creates a spacing builder specifically for directive block comments.
251285
*/
252-
protected fun createBlockDirectiveCommentSpacingBuilder(): SqlCustomSpacingBuilder =
253-
SqlCustomSpacingBuilder()
254-
.withSpacing(
255-
SqlTypes.BLOCK_COMMENT_START,
256-
SqlTypes.EL_ID_EXPR,
257-
Spacing.createSpacing(1, 1, 0, true, 0),
258-
).withSpacing(
259-
SqlTypes.BLOCK_COMMENT_START,
260-
SqlTypes.EL_PRIMARY_EXPR,
261-
Spacing.createSpacing(1, 1, 0, true, 0),
262-
).withSpacing(
263-
SqlTypes.BLOCK_COMMENT_START,
264-
SqlTypes.EL_STRING,
265-
Spacing.createSpacing(1, 1, 0, true, 0),
266-
).withSpacing(
267-
SqlTypes.BLOCK_COMMENT_START,
268-
SqlTypes.EL_NUMBER,
269-
Spacing.createSpacing(1, 1, 0, true, 0),
270-
).withSpacing(
271-
SqlTypes.BLOCK_COMMENT_START,
272-
SqlTypes.BOOLEAN,
273-
Spacing.createSpacing(1, 1, 0, true, 0),
274-
).withSpacing(
275-
SqlTypes.BLOCK_COMMENT_START,
276-
SqlTypes.EL_NULL,
277-
Spacing.createSpacing(1, 1, 0, true, 0),
278-
).withSpacing(
279-
SqlTypes.BLOCK_COMMENT_START,
280-
SqlTypes.EL_FIELD_ACCESS_EXPR,
281-
Spacing.createSpacing(1, 1, 0, true, 0),
282-
).withSpacing(
283-
SqlTypes.BLOCK_COMMENT_START,
284-
SqlTypes.EL_STATIC_FIELD_ACCESS_EXPR,
285-
Spacing.createSpacing(1, 1, 0, true, 0),
286-
).withSpacing(
287-
SqlTypes.BLOCK_COMMENT_START,
288-
SqlTypes.HASH,
289-
Spacing.createSpacing(0, 0, 0, true, 0),
290-
).withSpacing(
291-
SqlTypes.HASH,
292-
SqlTypes.EL_ID_EXPR,
293-
Spacing.createSpacing(1, 1, 0, true, 0),
294-
).withSpacing(
295-
SqlTypes.HASH,
296-
SqlTypes.EL_PRIMARY_EXPR,
297-
Spacing.createSpacing(1, 1, 0, true, 0),
298-
).withSpacing(
299-
SqlTypes.HASH,
300-
SqlTypes.EL_STRING,
301-
Spacing.createSpacing(1, 1, 0, true, 0),
302-
).withSpacing(
303-
SqlTypes.HASH,
304-
SqlTypes.EL_NUMBER,
305-
Spacing.createSpacing(1, 1, 0, true, 0),
306-
).withSpacing(
307-
SqlTypes.HASH,
308-
SqlTypes.BOOLEAN,
309-
Spacing.createSpacing(1, 1, 0, true, 0),
310-
).withSpacing(
311-
SqlTypes.HASH,
312-
SqlTypes.EL_NULL,
313-
Spacing.createSpacing(1, 1, 0, true, 0),
314-
).withSpacing(
315-
SqlTypes.HASH,
316-
SqlTypes.EL_FIELD_ACCESS_EXPR,
317-
Spacing.createSpacing(1, 1, 0, true, 0),
318-
).withSpacing(
319-
SqlTypes.HASH,
320-
SqlTypes.EL_STATIC_FIELD_ACCESS_EXPR,
321-
Spacing.createSpacing(1, 1, 0, true, 0),
322-
).withSpacing(
323-
SqlTypes.BLOCK_COMMENT_START,
324-
SqlTypes.CARET,
325-
Spacing.createSpacing(0, 0, 0, true, 0),
326-
).withSpacing(
327-
SqlTypes.CARET,
286+
protected open fun createBlockDirectiveCommentSpacingBuilder(): SqlCustomSpacingBuilder {
287+
val builder = SqlCustomSpacingBuilder()
288+
289+
// Types that need spacing after BLOCK_COMMENT_START
290+
val typesNeedingSpaceAfterStart =
291+
listOf(
328292
SqlTypes.EL_ID_EXPR,
329-
Spacing.createSpacing(1, 1, 0, true, 0),
330-
).withSpacing(
331-
SqlTypes.CARET,
332293
SqlTypes.EL_PRIMARY_EXPR,
333-
Spacing.createSpacing(1, 1, 0, true, 0),
334-
).withSpacing(
335-
SqlTypes.CARET,
336294
SqlTypes.EL_STRING,
337-
Spacing.createSpacing(1, 1, 0, true, 0),
338-
).withSpacing(
339-
SqlTypes.CARET,
340295
SqlTypes.EL_NUMBER,
341-
Spacing.createSpacing(1, 1, 0, true, 0),
342-
).withSpacing(
343-
SqlTypes.CARET,
344296
SqlTypes.BOOLEAN,
345-
Spacing.createSpacing(1, 1, 0, true, 0),
346-
).withSpacing(
347-
SqlTypes.CARET,
348297
SqlTypes.EL_NULL,
349-
Spacing.createSpacing(1, 1, 0, true, 0),
350-
).withSpacing(
351-
SqlTypes.CARET,
352298
SqlTypes.EL_FIELD_ACCESS_EXPR,
353-
Spacing.createSpacing(1, 1, 0, true, 0),
354-
).withSpacing(
355-
SqlTypes.CARET,
356299
SqlTypes.EL_STATIC_FIELD_ACCESS_EXPR,
357-
Spacing.createSpacing(1, 1, 0, true, 0),
358-
).withSpacing(
359-
SqlTypes.BLOCK_COMMENT_CONTENT,
360-
SqlTypes.BLOCK_COMMENT_END,
361-
Spacing.createSpacing(0, 0, 0, true, 0),
362-
).withSpacing(
363-
SqlTypes.EL_FIELD_ACCESS_EXPR,
364-
SqlTypes.OTHER,
365-
Spacing.createSpacing(1, 1, 0, false, 0),
366-
).withSpacing(
367-
SqlTypes.EL_STATIC_FIELD_ACCESS_EXPR,
368-
SqlTypes.OTHER,
369-
Spacing.createSpacing(1, 1, 0, false, 0),
370-
).withSpacing(
300+
)
301+
302+
// Types that need spacing before BLOCK_COMMENT_END
303+
val typesNeedingSpaceBeforeEnd =
304+
listOf(
371305
SqlTypes.EL_ID_EXPR,
372-
SqlTypes.BLOCK_COMMENT_END,
373-
Spacing.createSpacing(1, 1, 0, true, 0),
374-
).withSpacing(
375306
SqlTypes.EL_PRIMARY_EXPR,
376-
SqlTypes.BLOCK_COMMENT_END,
377-
Spacing.createSpacing(1, 1, 0, true, 0),
378-
).withSpacing(
379307
SqlTypes.STRING,
380-
SqlTypes.BLOCK_COMMENT_END,
381-
Spacing.createSpacing(1, 1, 0, true, 0),
382-
).withSpacing(
383308
SqlTypes.EL_NUMBER,
384-
SqlTypes.BLOCK_COMMENT_END,
385-
Spacing.createSpacing(1, 1, 0, true, 0),
386-
).withSpacing(
387309
SqlTypes.EL_NULL,
388-
SqlTypes.BLOCK_COMMENT_END,
389-
Spacing.createSpacing(1, 1, 0, true, 0),
390-
).withSpacing(
391310
SqlTypes.BOOLEAN,
392-
SqlTypes.BLOCK_COMMENT_END,
393-
Spacing.createSpacing(1, 1, 0, true, 0),
394-
).withSpacing(
395311
SqlTypes.EL_FIELD_ACCESS_EXPR,
396-
SqlTypes.BLOCK_COMMENT_END,
397-
Spacing.createSpacing(1, 1, 0, true, 0),
398-
).withSpacing(
399312
SqlTypes.EL_STATIC_FIELD_ACCESS_EXPR,
400-
SqlTypes.BLOCK_COMMENT_END,
401-
Spacing.createSpacing(1, 1, 0, true, 0),
402313
)
403314

315+
// Add spacing rules for BLOCK_COMMENT_START
316+
typesNeedingSpaceAfterStart.forEach { type ->
317+
builder.withSpacing(SqlTypes.BLOCK_COMMENT_START, type, SPACING_ONE)
318+
}
319+
320+
// Special cases for BLOCK_COMMENT_START
321+
builder.withSpacing(SqlTypes.BLOCK_COMMENT_START, SqlTypes.HASH, SPACING_ZERO)
322+
builder.withSpacing(SqlTypes.BLOCK_COMMENT_START, SqlTypes.CARET, SPACING_ZERO)
323+
324+
// Add spacing rules for HASH
325+
typesNeedingSpaceAfterStart.forEach { type ->
326+
builder.withSpacing(SqlTypes.HASH, type, SPACING_ONE)
327+
}
328+
329+
// Add spacing rules for CARET
330+
typesNeedingSpaceAfterStart.forEach { type ->
331+
builder.withSpacing(SqlTypes.CARET, type, SPACING_ONE)
332+
}
333+
334+
// Special spacing rules
335+
builder.withSpacing(SqlTypes.BLOCK_COMMENT_CONTENT, SqlTypes.BLOCK_COMMENT_END, SPACING_ZERO)
336+
builder.withSpacing(SqlTypes.EL_FIELD_ACCESS_EXPR, SqlTypes.OTHER, SPACING_ONE_NO_KEEP)
337+
builder.withSpacing(SqlTypes.EL_STATIC_FIELD_ACCESS_EXPR, SqlTypes.OTHER, SPACING_ONE_NO_KEEP)
338+
339+
// Add spacing rules before BLOCK_COMMENT_END
340+
typesNeedingSpaceBeforeEnd.forEach { type ->
341+
builder.withSpacing(type, SqlTypes.BLOCK_COMMENT_END, SPACING_ONE)
342+
}
343+
344+
return builder
345+
}
346+
404347
/**
405348
* Returns the child indentation for the block.
406349
*
@@ -413,10 +356,6 @@ open class SqlBlock(
413356
Indent.getSpaceIndent(0)
414357
}
415358

416-
companion object {
417-
private const val DEFAULT_INDENT_SIZE = 4
418-
}
419-
420359
/**
421360
* Determines whether the block is a leaf node.
422361
*

0 commit comments

Comments
 (0)