From 0472fb994f7faf0815e01addeecf0d0e767cf0be Mon Sep 17 00:00:00 2001 From: xterao Date: Wed, 20 Aug 2025 15:57:28 +0900 Subject: [PATCH 01/21] Add block class for IN clause and update test data to prevent unintended line breaks --- .../doma/intellij/formatter/block/SqlBlock.kt | 29 +++++-- .../comment/SqlElConditionLoopCommentBlock.kt | 14 +++- .../block/group/keyword/SqlJoinGroupBlock.kt | 1 + .../SqlConditionKeywordGroupBlock.kt | 2 +- .../group/keyword/option/SqlInGroupBlock.kt | 79 +++++++++++++++++++ .../{ => option}/SqlLateralGroupBlock.kt | 3 +- .../SqlSecondOptionKeywordGroupBlock.kt | 3 +- .../keyword/top/SqlTopQueryGroupBlock.kt | 6 +- .../block/group/subgroup/SqlSubGroupBlock.kt | 15 ++-- .../builder/SqlBlockRelationBuilder.kt | 4 +- .../formatter/handler/NotQueryGroupHandler.kt | 27 ++++--- .../formatter/util/SqlBlockGenerator.kt | 11 ++- .../intellij/formatter/SqlFormatterTest.kt | 20 ++++- 13 files changed, 178 insertions(+), 36 deletions(-) create mode 100644 src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlInGroupBlock.kt rename src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/{ => option}/SqlLateralGroupBlock.kt (95%) rename src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/{ => option}/SqlSecondOptionKeywordGroupBlock.kt (96%) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt index 6ac0a487..c8b40950 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt @@ -28,6 +28,7 @@ import org.domaframework.doma.intellij.formatter.block.comment.SqlCommentBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlDefaultCommentBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock import org.domaframework.doma.intellij.formatter.block.group.SqlNewGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock import org.domaframework.doma.intellij.formatter.builder.SqlCustomSpacingBuilder import org.domaframework.doma.intellij.formatter.util.IndentType import org.domaframework.doma.intellij.formatter.util.SqlKeywordUtil @@ -112,9 +113,9 @@ open class SqlBlock( * * @example * ```sql - * WHERE - * /*%if status == "pending" */ - * status = 'pending' + * WHERE -- grand + * /*%if status == "pending" */ -- parent + * status = 'pending' -- child * ``` */ protected fun isElementAfterConditionLoopDirective(): Boolean = @@ -123,15 +124,27 @@ open class SqlBlock( (parent.parentBlock is SqlNewGroupBlock || parent.parentBlock is SqlElConditionLoopCommentBlock) } == true + protected fun isElementAfterConditionLoopEnd(): Boolean = + ( + prevBlocks + .lastOrNull() + ?.childBlocks + ?.firstOrNull() as? SqlElConditionLoopCommentBlock + )?.conditionEnd != null + protected fun isFirstChildConditionLoopDirective(): Boolean = childBlocks.firstOrNull() is SqlElConditionLoopCommentBlock fun getChildBlocksDropLast( dropIndex: Int = 1, skipCommentBlock: Boolean = true, + skipConditionLoopCommentBlock: Boolean = true, ): List { - val children = childBlocks.dropLast(dropIndex) + var children = childBlocks.dropLast(dropIndex) if (skipCommentBlock) { - return children.filter { it !is SqlDefaultCommentBlock } + children = children.filter { it !is SqlDefaultCommentBlock } + } + if (skipConditionLoopCommentBlock) { + children = children.filter { it !is SqlElConditionLoopCommentBlock } } return children } @@ -175,7 +188,8 @@ open class SqlBlock( private fun shouldSaveSpaceForConditionLoop(): Boolean = isConditionLoopDirectiveRegisteredBeforeParent() || isElementAfterConditionLoopDirective() || - isFirstChildConditionLoopDirective() + isFirstChildConditionLoopDirective() || + isElementAfterConditionLoopEnd() private fun shouldSaveSpaceForNewGroup(parent: SqlNewGroupBlock): Boolean { val prevWord = prevBlocks.lastOrNull { it !is SqlCommentBlock } @@ -202,6 +216,9 @@ open class SqlBlock( lastPrevBlock.node.psi.startOffset > parent.node.psi.startOffset } + protected fun getLastBlockHasConditionLoopDirective(): SqlElConditionLoopCommentBlock? = + (prevBlocks.lastOrNull()?.childBlocks?.firstOrNull() as? SqlElConditionLoopCommentBlock) + /** * Creates the indentation length for the block. * diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt index bc7e0924..f8693c27 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt @@ -248,9 +248,7 @@ class SqlElConditionLoopCommentBlock( is SqlKeywordGroupBlock -> { // At this point, it's not possible to determine whether the parent keyword group appears before or after this block based solely on the parent-child relationship. // Therefore, determine the position directly using the text offset. - return if (parent.node.startOffset < - node.startOffset - ) { + return if (!isBeforeParentBlock()) { // The child branch applies in cases where a conditional directive is included as a child of this block. val questOffset = if (parent is SqlWithQueryGroupBlock) 0 else 1 parent.indent.groupIndentLen @@ -285,4 +283,14 @@ class SqlElConditionLoopCommentBlock( val diffCount = startDirectives.minus(endDirectives) return if (diffCount > 0) diffCount.minus(1) else 0 } + + /** + * Determine if this conditional loop directive block is positioned before its parent block. + */ + fun isBeforeParentBlock(): Boolean { + parentBlock?.let { parent -> + return parent.node.startOffset > node.startOffset + } + return false + } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlJoinGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlJoinGroupBlock.kt index 7541feb0..795a6c52 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlJoinGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlJoinGroupBlock.kt @@ -19,6 +19,7 @@ import com.intellij.lang.ASTNode import com.intellij.psi.formatter.common.AbstractBlock import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlLateralGroupBlock import org.domaframework.doma.intellij.formatter.util.IndentType import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/condition/SqlConditionKeywordGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/condition/SqlConditionKeywordGroupBlock.kt index 12cab407..18277162 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/condition/SqlConditionKeywordGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/condition/SqlConditionKeywordGroupBlock.kt @@ -18,7 +18,7 @@ package org.domaframework.doma.intellij.formatter.block.group.keyword.condition import com.intellij.lang.ASTNode import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlSecondOptionKeywordGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlSecondOptionKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlInGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlInGroupBlock.kt new file mode 100644 index 00000000..0ed05b2f --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlInGroupBlock.kt @@ -0,0 +1,79 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.domaframework.doma.intellij.formatter.block.group.keyword.option + +import com.intellij.lang.ASTNode +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.block.comment.SqlDefaultCommentBlock +import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock +import org.domaframework.doma.intellij.formatter.util.IndentType +import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext +import org.domaframework.doma.intellij.psi.SqlTypes + +class SqlInGroupBlock( + node: ASTNode, + context: SqlBlockFormattingContext, +) : SqlKeywordGroupBlock( + node, + IndentType.OPTIONS, + context, + ) { + override fun setParentGroupBlock(lastGroup: SqlBlock?) { + super.setParentGroupBlock(lastGroup) + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = createGroupIndentLen() + } + + override fun createBlockIndentLen(): Int { + parentBlock?.let { parent -> + if (parent is SqlElConditionLoopCommentBlock) return parent.indent.groupIndentLen + val prevChildren = this.prevBlocks + val children = prevChildren.filter { it !is SqlDefaultCommentBlock } + val firstChild = children.firstOrNull() + val sumChildren = + if (firstChild is SqlElConditionLoopCommentBlock) { + children.drop(1).dropLastWhile { it == this } + } else { + children + } + + val dotCount = sumChildren.count { it.node.elementType == SqlTypes.DOT } + return sumChildren + .sumOf { prev -> + prev + .getChildrenTextLen() + .plus(prev.getNodeText().length.plus(1)) + }.minus(dotCount * 2) + .plus(parent.indent.groupIndentLen) + .plus(1) + } + return 0 + } + + override fun createGroupIndentLen(): Int = indent.indentLen.plus(getNodeText().length) + + override fun isSaveSpace(lastGroup: SqlBlock?): Boolean { + if (lastGroup is SqlElConditionLoopCommentBlock) { + return if (lastGroup.conditionType.isElse()) { + return false + } else { + !lastGroup.isBeforeParentBlock() + } + } + return false + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlLateralGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlLateralGroupBlock.kt similarity index 95% rename from src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlLateralGroupBlock.kt rename to src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlLateralGroupBlock.kt index f7bdfc7d..ceeeae7f 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlLateralGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlLateralGroupBlock.kt @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.domaframework.doma.intellij.formatter.block.group.keyword +package org.domaframework.doma.intellij.formatter.block.group.keyword.option import com.intellij.lang.ASTNode import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlFromGroupBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock import org.domaframework.doma.intellij.formatter.util.IndentType diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlSecondOptionKeywordGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlSecondOptionKeywordGroupBlock.kt similarity index 96% rename from src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlSecondOptionKeywordGroupBlock.kt rename to src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlSecondOptionKeywordGroupBlock.kt index 2ec0be52..88b8e91b 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlSecondOptionKeywordGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlSecondOptionKeywordGroupBlock.kt @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.domaframework.doma.intellij.formatter.block.group.keyword +package org.domaframework.doma.intellij.formatter.block.group.keyword.option import com.intellij.lang.ASTNode import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.formatter.block.SqlKeywordBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubQueryGroupBlock import org.domaframework.doma.intellij.formatter.util.IndentType diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/top/SqlTopQueryGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/top/SqlTopQueryGroupBlock.kt index f14ead1a..d388aa1b 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/top/SqlTopQueryGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/top/SqlTopQueryGroupBlock.kt @@ -41,7 +41,7 @@ abstract class SqlTopQueryGroupBlock( SqlWithQuerySubGroupBlock::class, SqlElConditionLoopCommentBlock::class, ) - private val offset = 0 + private const val OFFSET = 0 } override fun setParentGroupBlock(lastGroup: SqlBlock?) { @@ -55,7 +55,7 @@ abstract class SqlTopQueryGroupBlock( override fun createBlockIndentLen(): Int { parentBlock?.let { parent -> - if (parent.indent.indentLevel == IndentType.FILE) return offset + if (parent.indent.indentLevel == IndentType.FILE) return OFFSET if (parent is SqlElConditionLoopCommentBlock) { return createIndentLenInConditionLoopDirective(parent) } @@ -84,7 +84,7 @@ abstract class SqlTopQueryGroupBlock( // align with the indent of that parent's parent prevGroupBlock?.let { prev -> if (prev.indent.indentLevel >= indent.indentLevel) { - p.indent.indentLen = prev.parentBlock?.indent?.indentLen ?: offset + p.indent.indentLen = prev.parentBlock?.indent?.indentLen ?: OFFSET } } } else { diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubGroupBlock.kt index faf0e30e..f924a33e 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubGroupBlock.kt @@ -21,14 +21,14 @@ import com.intellij.lang.ASTNode import com.intellij.psi.formatter.common.AbstractBlock import org.domaframework.doma.intellij.common.util.TypeUtil import org.domaframework.doma.intellij.formatter.block.SqlBlock -import org.domaframework.doma.intellij.formatter.block.SqlKeywordBlock import org.domaframework.doma.intellij.formatter.block.SqlRightPatternBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlCommentBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock import org.domaframework.doma.intellij.formatter.block.conflict.SqlDoGroupBlock import org.domaframework.doma.intellij.formatter.block.group.SqlNewGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlLateralGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateViewGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlInGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlLateralGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlFromGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.top.SqlJoinQueriesGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithColumnGroupBlock @@ -122,11 +122,14 @@ abstract class SqlSubGroupBlock( override fun isSaveSpace(lastGroup: SqlBlock?): Boolean { lastGroup?.let { lastBlock -> if (lastBlock is SqlJoinQueriesGroupBlock) return true - val prevBlock = prevChildren?.dropLast(1)?.lastOrNull() - if (prevBlock is SqlKeywordBlock) { - if (prevBlock.getNodeText() == "in") return false - } + if (lastGroup is SqlInGroupBlock) return false if (lastGroup is SqlElConditionLoopCommentBlock) return true + val grand = lastBlock.parentBlock + if (grand is SqlElConditionLoopCommentBlock) { + if (grand.conditionType.isElse()) { + return false + } + } return TypeUtil.isExpectedClassType(NEW_LINE_EXPECTED_TYPES, lastBlock.parentBlock) } return false diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlBlockRelationBuilder.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlBlockRelationBuilder.kt index d799e122..da38d0d9 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlBlockRelationBuilder.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlBlockRelationBuilder.kt @@ -25,10 +25,11 @@ import org.domaframework.doma.intellij.formatter.block.conflict.SqlDoGroupBlock import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnDefinitionRawGroupBlock import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnRawGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlLateralGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateViewGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.inline.SqlInlineGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.inline.SqlInlineSecondGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlInGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlLateralGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlReturningGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.top.SqlTopQueryGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.update.SqlUpdateQueryGroupBlock @@ -62,6 +63,7 @@ class SqlBlockRelationBuilder( SqlInlineSecondGroupBlock::class, SqlColumnDefinitionRawGroupBlock::class, SqlLateralGroupBlock::class, + SqlInGroupBlock::class, ) private val TOP_LEVEL_EXPECTED_TYPES = diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/NotQueryGroupHandler.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/NotQueryGroupHandler.kt index 5b7505fd..c04812d5 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/NotQueryGroupHandler.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/NotQueryGroupHandler.kt @@ -18,10 +18,12 @@ package org.domaframework.doma.intellij.formatter.handler import com.intellij.lang.ASTNode import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.formatter.block.SqlCommaBlock +import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock import org.domaframework.doma.intellij.formatter.block.conflict.SqlConflictClauseBlock import org.domaframework.doma.intellij.formatter.block.conflict.SqlConflictExpressionSubGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.condition.SqlConditionKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.condition.SqlConditionalExpressionGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlInGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlReturningGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlValuesGroupBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlFunctionParamBlock @@ -31,10 +33,8 @@ import org.domaframework.doma.intellij.formatter.block.word.SqlAliasBlock import org.domaframework.doma.intellij.formatter.block.word.SqlTableBlock import org.domaframework.doma.intellij.formatter.block.word.SqlWordBlock import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext -import org.domaframework.doma.intellij.formatter.util.SqlKeywordUtil object NotQueryGroupHandler { - private const val IN_KEYWORD = "in" private const val RETURNING_KEYWORD = "returning" /** @@ -75,13 +75,7 @@ object NotQueryGroupHandler { /** * Checks if the last group has an 'IN' keyword as its last option keyword. */ - private fun hasInKeyword(lastGroup: SqlBlock?): Boolean { - val lastKeyword = - lastGroup - ?.childBlocks - ?.lastOrNull { SqlKeywordUtil.isOptionSqlKeyword(it.getNodeText()) } - return lastKeyword?.getNodeText()?.lowercase() == IN_KEYWORD - } + private fun hasInKeyword(lastGroup: SqlBlock?): Boolean = lastGroup is SqlInGroupBlock /** * Creates a conditional expression group block. @@ -94,7 +88,20 @@ object NotQueryGroupHandler { /** * Checks if the last group has a word block context that requires function or alias handling. */ - private fun hasFunctionOrAliasContext(lastGroup: SqlBlock?): Boolean = lastGroup?.childBlocks?.lastOrNull() is SqlWordBlock + private fun hasFunctionOrAliasContext(lastGroup: SqlBlock?): Boolean { + val lastChild = lastGroup?.childBlocks?.lastOrNull() + if (lastChild != null) { + when (lastChild) { + is SqlElConditionLoopCommentBlock -> { + if (lastChild.isBeforeParentBlock()) { + return lastChild.parentBlock is SqlWordBlock + } + } + else -> return lastGroup.childBlocks.lastOrNull() is SqlWordBlock + } + } + return false + } /** * Creates either a function parameter block or values parameter block based on the previous child type. diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt index a6dbb10d..2cd06558 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt @@ -37,8 +37,6 @@ import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnBlo import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnDefinitionRawGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlJoinGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlLateralGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlSecondOptionKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.condition.SqlConditionKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateTableColumnDefinitionGroupBlock @@ -47,6 +45,9 @@ import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlC import org.domaframework.doma.intellij.formatter.block.group.keyword.inline.SqlInlineGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.inline.SqlInlineSecondGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.insert.SqlInsertQueryGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlInGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlLateralGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlSecondOptionKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlFromGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlSecondKeywordBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlValuesGroupBlock @@ -140,6 +141,12 @@ class SqlBlockGenerator( if (keywordText == "lateral") { return SqlLateralGroupBlock(child, sqlBlockFormattingCtx) } + if (keywordText == "in") { + return SqlInGroupBlock( + child, + sqlBlockFormattingCtx, + ) + } } IndentType.CONFLICT -> { diff --git a/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt index 81d46c9a..cc07e945 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt @@ -230,8 +230,24 @@ class SqlFormatterTest : BasePlatformTestCase() { formatSqlFile("WithOptional.sql", "WithOptional$formatDataPrefix.sql") } - fun testCalculationDirectivesFormatter() { - formatSqlFile("CalculationDirectives.sql", "CalculationDirectives$formatDataPrefix.sql") + fun testConditionalInClauseFormatter() { + formatSqlFile("ConditionalInClause.sql", "ConditionalInClause$formatDataPrefix.sql") + } + + fun testConditionalJoinClauseFormatter() { + formatSqlFile("ConditionalJoinClause.sql", "ConditionalJoinClause$formatDataPrefix.sql") + } + + fun testConditionalWhereClauseFormatter() { + formatSqlFile("ConditionalWhereClause.sql", "ConditionalWhereClause$formatDataPrefix.sql") + } + + fun testConditionalUnionFormatter() { + formatSqlFile("ConditionalUnion.sql", "ConditionalUnion$formatDataPrefix.sql") + } + + fun testConditionalSubqueryFormatter() { + formatSqlFile("ConditionalSubquery.sql", "ConditionalSubquery$formatDataPrefix.sql") } private fun formatSqlFile( From 63aa0c47ae2db4936f708595a116a543b13ab8f8 Mon Sep 17 00:00:00 2001 From: xterao Date: Wed, 20 Aug 2025 19:19:36 +0900 Subject: [PATCH 02/21] Add SQL files for conditional clauses and format them for improved readability --- .../intellij/formatter/SqlFormatterTest.kt | 4 -- .../sql/formatter/ConditionalInClause.sql | 12 ++++ .../formatter/ConditionalInClause_format.sql | 14 +++++ .../sql/formatter/ConditionalJoinClause.sql | 15 +++++ .../ConditionalJoinClause_format.sql | 23 ++++++++ .../sql/formatter/ConditionalUnion.sql | 31 ++++++++++ .../sql/formatter/ConditionalUnion_format.sql | 41 ++++++++++++++ .../sql/formatter/ConditionalWhereClause.sql | 24 ++++++++ .../ConditionalWhereClause_format.sql | 26 +++++++++ .../formatter/NestedConditionalDirective.sql | 54 ++++++++++++++++++ .../NestedConditionalDirective_format.sql | 56 +++++++++++++++++++ .../sql/formatter/UseDirectiveWithQuery.sql | 2 +- .../UseDirectiveWithQuery_format.sql | 2 +- 13 files changed, 298 insertions(+), 6 deletions(-) create mode 100644 src/test/testData/sql/formatter/ConditionalInClause.sql create mode 100644 src/test/testData/sql/formatter/ConditionalInClause_format.sql create mode 100644 src/test/testData/sql/formatter/ConditionalJoinClause.sql create mode 100644 src/test/testData/sql/formatter/ConditionalJoinClause_format.sql create mode 100644 src/test/testData/sql/formatter/ConditionalUnion.sql create mode 100644 src/test/testData/sql/formatter/ConditionalUnion_format.sql create mode 100644 src/test/testData/sql/formatter/ConditionalWhereClause.sql create mode 100644 src/test/testData/sql/formatter/ConditionalWhereClause_format.sql create mode 100644 src/test/testData/sql/formatter/NestedConditionalDirective.sql create mode 100644 src/test/testData/sql/formatter/NestedConditionalDirective_format.sql diff --git a/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt index cc07e945..32ca8e6c 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt @@ -246,10 +246,6 @@ class SqlFormatterTest : BasePlatformTestCase() { formatSqlFile("ConditionalUnion.sql", "ConditionalUnion$formatDataPrefix.sql") } - fun testConditionalSubqueryFormatter() { - formatSqlFile("ConditionalSubquery.sql", "ConditionalSubquery$formatDataPrefix.sql") - } - private fun formatSqlFile( beforeFile: String, afterFile: String, diff --git a/src/test/testData/sql/formatter/ConditionalInClause.sql b/src/test/testData/sql/formatter/ConditionalInClause.sql new file mode 100644 index 00000000..e5e58b09 --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalInClause.sql @@ -0,0 +1,12 @@ +SELECT id, name, department_id +FROM employees +WHERE +/*%if departmentIds != null */ +department_id IN /* departmentIds */(1, 2, 3) +/*%end*/ +/*%if statusList != null */ +AND status IN /* statusList */('active', 'pending', 'inactive') +/*%end*/ +/*%if categoryIds != null && categoryIds.size() > 0 */ +AND category_id IN /* categoryIds */(100, 200, 300, 400, 500) +/*%end*/ \ No newline at end of file diff --git a/src/test/testData/sql/formatter/ConditionalInClause_format.sql b/src/test/testData/sql/formatter/ConditionalInClause_format.sql new file mode 100644 index 00000000..aadb9e53 --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalInClause_format.sql @@ -0,0 +1,14 @@ +SELECT id + , name + , department_id + FROM employees + WHERE + /*%if departmentIds != null */ + department_id IN /* departmentIds */(1, 2, 3) + /*%end*/ + /*%if statusList != null */ + AND status IN /* statusList */('active', 'pending', 'inactive') + /*%end*/ + /*%if categoryIds != null && categoryIds.size() > 0 */ + AND category_id IN /* categoryIds */(100, 200, 300, 400, 500) + /*%end*/ diff --git a/src/test/testData/sql/formatter/ConditionalJoinClause.sql b/src/test/testData/sql/formatter/ConditionalJoinClause.sql new file mode 100644 index 00000000..427926c8 --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalJoinClause.sql @@ -0,0 +1,15 @@ +SELECT e.*, d.department_name, p.project_name +FROM employees e +/*%if includeDepartments */ +INNER JOIN departments d ON e.department_id = d.id +/*%end*/ +/*%if includeProjects */ +LEFT JOIN projects p ON e.project_id = p.id +/*%elseif includeActiveProjects */ +INNER JOIN projects p ON e.project_id = p.id AND p.status = 'active' +/*%end*/ +/*%if includeSkills */ +LEFT JOIN employee_skills es ON e.id = es.employee_id +INNER JOIN skills s ON es.skill_id = s.id +/*%end*/ +WHERE e.active = true \ No newline at end of file diff --git a/src/test/testData/sql/formatter/ConditionalJoinClause_format.sql b/src/test/testData/sql/formatter/ConditionalJoinClause_format.sql new file mode 100644 index 00000000..3dd1b211 --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalJoinClause_format.sql @@ -0,0 +1,23 @@ +SELECT e.* + , d.department_name + , p.project_name + FROM employees e + /*%if includeDepartments */ + INNER JOIN departments d + ON e.department_id = d.id + /*%end*/ + /*%if includeProjects */ + LEFT JOIN projects p + ON e.project_id = p.id + /*%elseif includeActiveProjects */ + INNER JOIN projects p + ON e.project_id = p.id + AND p.status = 'active' + /*%end*/ + /*%if includeSkills */ + LEFT JOIN employee_skills es + ON e.id = es.employee_id + INNER JOIN skills s + ON es.skill_id = s.id + /*%end*/ + WHERE e.active = true diff --git a/src/test/testData/sql/formatter/ConditionalUnion.sql b/src/test/testData/sql/formatter/ConditionalUnion.sql new file mode 100644 index 00000000..d2bf6252 --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalUnion.sql @@ -0,0 +1,31 @@ +/*%if includeEmployees */ +SELECT 'Employee' as type, e.id, e.name, e.email +FROM employees e +WHERE e.is_active = true +/*%if departmentId != null */ +AND e.department_id = /* departmentId */1 +/*%end*/ +/*%end*/ +/*%if includeEmployees && includeContractors */ +UNION ALL +/*%end*/ +/*%if includeContractors */ +SELECT 'Contractor' as type, c.id, c.name, c.email +FROM contractors c +WHERE c.contract_end_date >= CURRENT_DATE +/*%if projectId != null */ +AND c.project_id = /* projectId */10 +/*%end*/ +/*%end*/ +/*%if (includeEmployees || includeContractors) && includeVendors */ +UNION ALL +/*%end*/ +/*%if includeVendors */ +SELECT 'Vendor' as type, v.id, v.company_name as name, v.contact_email as email +FROM vendors v +WHERE v.status = 'active' +/*%if vendorType != null */ +AND v.vendor_type IN /* vendorType */('supplier', 'service') +/*%end*/ +/*%end*/ +ORDER BY type, name \ No newline at end of file diff --git a/src/test/testData/sql/formatter/ConditionalUnion_format.sql b/src/test/testData/sql/formatter/ConditionalUnion_format.sql new file mode 100644 index 00000000..8ebe0e71 --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalUnion_format.sql @@ -0,0 +1,41 @@ +/*%if includeEmployees */ +SELECT 'Employee' AS type + , e.id + , e.name + , e.email + FROM employees e + WHERE e.is_active = true + /*%if departmentId != null */ + AND e.department_id = /* departmentId */1 + /*%end*/ +/*%end*/ +/*%if includeEmployees && includeContractors */ +UNION ALL +/*%end*/ +/*%if includeContractors */ +SELECT 'Contractor' AS type + , c.id + , c.name + , c.email + FROM contractors c + WHERE c.contract_end_date >= CURRENT_DATE + /*%if projectId != null */ + AND c.project_id = /* projectId */10 + /*%end*/ +/*%end*/ +/*%if (includeEmployees || includeContractors) && includeVendors */ +UNION ALL +/*%end*/ +/*%if includeVendors */ +SELECT 'Vendor' AS type + , v.id + , v.company_name AS name + , v.contact_email AS email + FROM vendors v + WHERE v.status = 'active' + /*%if vendorType != null */ + AND v.vendor_type IN /* vendorType */('supplier', 'service') + /*%end*/ +/*%end*/ +ORDER BY type + , name diff --git a/src/test/testData/sql/formatter/ConditionalWhereClause.sql b/src/test/testData/sql/formatter/ConditionalWhereClause.sql new file mode 100644 index 00000000..9437942e --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalWhereClause.sql @@ -0,0 +1,24 @@ +SELECT * FROM products p +WHERE 1 = 1 +/*%if productName != null */ +AND p.name LIKE /* productName */'%laptop%' +/*%end*/ +/*%if minPrice != null */ +AND p.price >= /* minPrice */1000 +/*%end*/ +/*%if maxPrice != null */ +AND p.price <= /* maxPrice */5000 +/*%end*/ +/*%if categoryIds != null && categoryIds.size() > 0 */ +AND p.category_id IN /* categoryIds */(1, 2, 3) +/*%end*/ +/*%if status != null */ +/*%if status == "available" */ +AND p.stock_quantity > IN /* quantitys */(0, 1, 2) AND p.is_active = true +/*%elseif status == "outofstock" */ +AND p.stock_quantity = IN /* quantitys */(0, 1, 2) +/*%else*/ +AND p.is_active = false +/*%end*/ +/*%end*/ +ORDER BY p.created_at DESC \ No newline at end of file diff --git a/src/test/testData/sql/formatter/ConditionalWhereClause_format.sql b/src/test/testData/sql/formatter/ConditionalWhereClause_format.sql new file mode 100644 index 00000000..9c9a096d --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalWhereClause_format.sql @@ -0,0 +1,26 @@ +SELECT * + FROM products p + WHERE 1 = 1 + /*%if productName != null */ + AND p.name LIKE /* productName */'%laptop%' + /*%end*/ + /*%if minPrice != null */ + AND p.price >= /* minPrice */1000 + /*%end*/ + /*%if maxPrice != null */ + AND p.price <= /* maxPrice */5000 + /*%end*/ + /*%if categoryIds != null && categoryIds.size() > 0 */ + AND p.category_id IN /* categoryIds */(1, 2, 3) + /*%end*/ + /*%if status != null */ + /*%if status == "available" */ + AND p.stock_quantity > IN /* quantitys */(0, 1, 2) + AND p.is_active = true + /*%elseif status == "outofstock" */ + AND p.stock_quantitys IN /* quantitys */(0, 1, 2) + /*%else*/ + AND p.is_active = false + /*%end*/ + /*%end*/ + ORDER BY p.created_at DESC diff --git a/src/test/testData/sql/formatter/NestedConditionalDirective.sql b/src/test/testData/sql/formatter/NestedConditionalDirective.sql new file mode 100644 index 00000000..a0e9a26a --- /dev/null +++ b/src/test/testData/sql/formatter/NestedConditionalDirective.sql @@ -0,0 +1,54 @@ +SELECT e.*, d.department_name, s.salary_amount +FROM employees e +/*%if departmentFilter != null */ +INNER JOIN departments d ON e.department_id = d.id +/*%end*/ +/*%if includeSalary */ +LEFT JOIN salaries s ON e.id = s.employee_id +/*%if salaryFilter != null */ +AND s.effective_date = ( +SELECT MAX(s2.effective_date) +FROM salaries s2 +WHERE s2.employee_id = e.id +/*%if salaryFilter.maxDate != null */ +AND s2.effective_date <= /* salaryFilter.maxDate */'2024-01-01' +/*%end*/ +) +/*%end*/ +/*%end*/ +WHERE +/*%if departmentFilter != null */ +/*%if departmentFilter.includeActive */ +d.is_active = true +/*%if departmentFilter.locations != null && departmentFilter.locations.size() > 0 */ +AND d.location IN /* departmentFilter.locations */('Tokyo', 'Osaka') +/*%end*/ +/*%else*/ +d.is_active = false +/*%end*/ +/*%if employeeFilter != null */ +AND +/*%if employeeFilter.minExperience != null */ +e.experience_years >= /* employeeFilter.minExperience */5 +/*%if employeeFilter.maxExperience != null */ +AND e.experience_years <= /* employeeFilter.maxExperience */10 +/*%end*/ +/*%end*/ +/*%if employeeFilter.skills != null && employeeFilter.skills.size() > 0 */ +AND EXISTS ( +SELECT 1 FROM employee_skills es +WHERE es.employee_id = e.id +/*%if employeeFilter.skillMatchType == "all" */ +AND es.skill_id IN /* employeeFilter.skills */(1, 2, 3) +GROUP BY es.employee_id +HAVING COUNT(DISTINCT es.skill_id) = /* employeeFilter.skills.size() */3 +/*%else*/ +AND es.skill_id IN /* employeeFilter.skills */(1, 2, 3) +/*%end*/ +) +/*%end*/ +/*%end*/ +/*%else*/ +e.department_id IS NULL +/*%end*/ +ORDER BY e.created_at DESC \ No newline at end of file diff --git a/src/test/testData/sql/formatter/NestedConditionalDirective_format.sql b/src/test/testData/sql/formatter/NestedConditionalDirective_format.sql new file mode 100644 index 00000000..0d565f92 --- /dev/null +++ b/src/test/testData/sql/formatter/NestedConditionalDirective_format.sql @@ -0,0 +1,56 @@ +SELECT e.* + , d.department_name + , s.salary_amount + FROM employees e + /*%if departmentFilter != null */ + INNER JOIN departments d + ON e.department_id = d.id + /*%end*/ + /*%if includeSalary */ + LEFT JOIN salaries s + ON e.id = s.employee_id + /*%if salaryFilter != null */ + AND s.effective_date = ( SELECT MAX(s2.effective_date) + FROM salaries s2 + WHERE s2.employee_id = e.id + /*%if salaryFilter.maxDate != null */ + AND s2.effective_date <= /* salaryFilter.maxDate */'2024-01-01' + /*%end*/) + /*%end*/ + /*%end*/ + WHERE + /*%if departmentFilter != null */ + /*%if departmentFilter.includeActive */ + d.is_active = true + /*%if departmentFilter.locations != null && departmentFilter.locations.size() > 0 */ + AND d.location IN /* departmentFilter.locations */('Tokyo', 'Osaka') + /*%end*/ + /*%else*/ + d.is_active = false + /*%end*/ + /*%if employeeFilter != null */ + AND + /*%if employeeFilter.minExperience != null */ + e.experience_years >= /* employeeFilter.minExperience */5 + /*%if employeeFilter.maxExperience != null */ + AND e.experience_years <= /* employeeFilter.maxExperience */10 + /*%end*/ + /*%end*/ + /*%if employeeFilter.skills != null && employeeFilter.skills.size() > 0 */ + AND EXISTS + ( SELECT 1 + FROM employee_skills es + WHERE es.employee_id = e.id + /*%if employeeFilter.skillMatchType == "all" */ + AND es.skill_id IN /* employeeFilter.skills */(1, 2, 3) + GROUP BY es.employee_id + HAVING COUNT(DISTINCT es.skill_id) = /* employeeFilter.skills.size() */3 + /*%else*/ + AND es.skill_id IN /* employeeFilter.skills */(1, 2, 3) + /*%end*/) + /*%end*/ + /*%end*/ + /*%else*/ + e.department_id IS NULL + /*%end*/ + ORDER BY e.created_at DESC diff --git a/src/test/testData/sql/formatter/UseDirectiveWithQuery.sql b/src/test/testData/sql/formatter/UseDirectiveWithQuery.sql index 21c7e4a3..7adfd6db 100644 --- a/src/test/testData/sql/formatter/UseDirectiveWithQuery.sql +++ b/src/test/testData/sql/formatter/UseDirectiveWithQuery.sql @@ -2,7 +2,7 @@ WITH /*%for cte : cteDefinitions */ /* cte.name */user_stats AS ( -/* cte.query */SELECT user_id, COUNT(*) as order_count FROM orders GROUP BY user_id +/*# cte.query */SELECT user_id, COUNT(*) as order_count FROM orders GROUP BY user_id ) /*%if cte_has_next */,/*%end*/ /*%end*/ diff --git a/src/test/testData/sql/formatter/UseDirectiveWithQuery_format.sql b/src/test/testData/sql/formatter/UseDirectiveWithQuery_format.sql index fb6ce358..0c480d94 100644 --- a/src/test/testData/sql/formatter/UseDirectiveWithQuery_format.sql +++ b/src/test/testData/sql/formatter/UseDirectiveWithQuery_format.sql @@ -2,7 +2,7 @@ WITH /*%for cte : cteDefinitions */ /* cte.name */ user_stats AS ( - /* cte.query */ + /*# cte.query */ SELECT user_id , COUNT(*) AS order_count FROM orders From 6b18951cee8d74060211f75ba4de385d5f32684b Mon Sep 17 00:00:00 2001 From: xterao Date: Wed, 20 Aug 2025 19:21:06 +0900 Subject: [PATCH 03/21] Fix SQL formatting logic for condition loop comments and improve indentation handling --- .../doma/intellij/formatter/block/SqlBlock.kt | 1 - .../comment/SqlElConditionLoopCommentBlock.kt | 15 ++++++++++----- .../group/keyword/top/SqlJoinQueriesGroupBlock.kt | 5 +++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt index c8b40950..a4cdc696 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt @@ -28,7 +28,6 @@ import org.domaframework.doma.intellij.formatter.block.comment.SqlCommentBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlDefaultCommentBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock import org.domaframework.doma.intellij.formatter.block.group.SqlNewGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock import org.domaframework.doma.intellij.formatter.builder.SqlCustomSpacingBuilder import org.domaframework.doma.intellij.formatter.util.IndentType import org.domaframework.doma.intellij.formatter.util.SqlKeywordUtil diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt index f8693c27..c7758ae2 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt @@ -249,11 +249,16 @@ class SqlElConditionLoopCommentBlock( // At this point, it's not possible to determine whether the parent keyword group appears before or after this block based solely on the parent-child relationship. // Therefore, determine the position directly using the text offset. return if (!isBeforeParentBlock()) { - // The child branch applies in cases where a conditional directive is included as a child of this block. - val questOffset = if (parent is SqlWithQueryGroupBlock) 0 else 1 - parent.indent.groupIndentLen - .plus(openConditionLoopDirectiveCount * 2) - .plus(questOffset) + val lastBlockConditionLoopCommentBlock: SqlElConditionLoopCommentBlock? = getLastBlockHasConditionLoopDirective() + if (lastBlockConditionLoopCommentBlock != null && lastBlockConditionLoopCommentBlock.conditionEnd != null) { + lastBlockConditionLoopCommentBlock.indent.indentLen + } else { + // The child branch applies in cases where a conditional directive is included as a child of this block. + val questOffset = if (parent is SqlWithQueryGroupBlock) 0 else 1 + parent.indent.groupIndentLen + .plus(openConditionLoopDirectiveCount * 2) + .plus(questOffset) + } } else { parent.indent.indentLen.plus(openConditionLoopDirectiveCount * 2) } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/top/SqlJoinQueriesGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/top/SqlJoinQueriesGroupBlock.kt index 3fdf8018..1c5d0801 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/top/SqlJoinQueriesGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/top/SqlJoinQueriesGroupBlock.kt @@ -17,6 +17,7 @@ package org.domaframework.doma.intellij.formatter.block.group.keyword.top import com.intellij.lang.ASTNode import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.block.SqlFileBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithQuerySubGroupBlock import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext @@ -41,8 +42,8 @@ class SqlJoinQueriesGroupBlock( override fun createBlockIndentLen(): Int { parentBlock?.let { parent -> return when (parent) { - is SqlWithQuerySubGroupBlock, - -> parent.indent.groupIndentLen + is SqlFileBlock -> 0 + is SqlWithQuerySubGroupBlock -> parent.indent.groupIndentLen is SqlElConditionLoopCommentBlock -> createIndentLenInConditionLoopDirective(parent) else -> parent.indent.groupIndentLen.plus(1) } From 24874403475700419ecc4e8a45920476f0f0c3a9 Mon Sep 17 00:00:00 2001 From: xterao Date: Wed, 20 Aug 2025 20:00:58 +0900 Subject: [PATCH 04/21] Adjust keyword group indentation within conditional directives --- .../keyword/second/SqlWhereGroupBlock.kt | 26 +++++++++++++++++++ .../with/SqlWithCommonTableGroupBlock.kt | 2 +- .../builder/SqlBlockRelationBuilder.kt | 22 +++++++++++++--- .../formatter/util/SqlBlockGenerator.kt | 7 +++++ .../sql/formatter/ConditionalWhereClause.sql | 5 ++-- .../ConditionalWhereClause_format.sql | 5 ++-- 6 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/second/SqlWhereGroupBlock.kt diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/second/SqlWhereGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/second/SqlWhereGroupBlock.kt new file mode 100644 index 00000000..7e925044 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/second/SqlWhereGroupBlock.kt @@ -0,0 +1,26 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.domaframework.doma.intellij.formatter.block.group.keyword.second + +import com.intellij.lang.ASTNode +import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext + +class SqlWhereGroupBlock( + node: ASTNode, + context: SqlBlockFormattingContext, +) : SqlSecondKeywordBlock(node, context) { + override fun createGroupIndentLen(): Int = indent.indentLen.plus(getNodeText().length) +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/with/SqlWithCommonTableGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/with/SqlWithCommonTableGroupBlock.kt index a82a62e2..a89e35fc 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/with/SqlWithCommonTableGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/with/SqlWithCommonTableGroupBlock.kt @@ -77,7 +77,7 @@ class SqlWithCommonTableGroupBlock( parentBlock?.let { parent -> val prevBlock = parent - .getChildBlocksDropLast() + .getChildBlocksDropLast(skipConditionLoopCommentBlock = false) .lastOrNull() return if (prevBlock is SqlElConditionLoopCommentBlock) 4 else 0 } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlBlockRelationBuilder.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlBlockRelationBuilder.kt index da38d0d9..e4106390 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlBlockRelationBuilder.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlBlockRelationBuilder.kt @@ -138,11 +138,27 @@ class SqlBlockRelationBuilder( val context = SetParentContext(childBlock, blockBuilder) if (lastGroupBlock is SqlElConditionLoopCommentBlock) { handleConditionLoopParent(lastGroupBlock, context, childBlock) - } else if (childBlock.indent.indentLevel == IndentType.TOP) { + return + } + if (childBlock.indent.indentLevel == IndentType.TOP) { handleTopLevelKeyword(lastGroupBlock, childBlock, context) - } else { - handleNonTopLevelKeyword(lastGroupBlock, lastIndentLevel, childBlock, context) + return + } + if (lastGroupBlock !is SqlSubGroupBlock) { + if (lastIndentLevel > childBlock.indent.indentLevel) { + val findLastGroup = + blockBuilder.getGroupTopNodeIndexHistory().findLast { + it.indent.indentLevel < childBlock.indent.indentLevel || + it is + SqlElConditionLoopCommentBlock + } + if (findLastGroup is SqlElConditionLoopCommentBlock) { + handleConditionLoopParent(findLastGroup, context, childBlock) + return + } + } } + handleNonTopLevelKeyword(lastGroupBlock, lastIndentLevel, childBlock, context) } private fun handleConditionLoopParent( diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt index 2cd06558..3ea84ce3 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt @@ -51,6 +51,7 @@ import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlS import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlFromGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlSecondKeywordBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlValuesGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlWhereGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.top.SqlDeleteQueryGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.top.SqlJoinQueriesGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.top.SqlSelectQueryGroupBlock @@ -271,6 +272,12 @@ class SqlBlockGenerator( sqlBlockFormattingCtx, ) } + "where" -> { + SqlWhereGroupBlock( + child, + sqlBlockFormattingCtx, + ) + } "values" -> SqlValuesGroupBlock( diff --git a/src/test/testData/sql/formatter/ConditionalWhereClause.sql b/src/test/testData/sql/formatter/ConditionalWhereClause.sql index 9437942e..605265f2 100644 --- a/src/test/testData/sql/formatter/ConditionalWhereClause.sql +++ b/src/test/testData/sql/formatter/ConditionalWhereClause.sql @@ -14,9 +14,10 @@ AND p.category_id IN /* categoryIds */(1, 2, 3) /*%end*/ /*%if status != null */ /*%if status == "available" */ -AND p.stock_quantity > IN /* quantitys */(0, 1, 2) AND p.is_active = true +AND p.stock_quantitys IN /* quantitys */(0, 1, 2) or p.is_active = true /*%elseif status == "outofstock" */ -AND p.stock_quantity = IN /* quantitys */(0, 1, 2) +OR p.is_active = true +AND p.stock_quantitys IN /* quantitys */(0, 1, 2) /*%else*/ AND p.is_active = false /*%end*/ diff --git a/src/test/testData/sql/formatter/ConditionalWhereClause_format.sql b/src/test/testData/sql/formatter/ConditionalWhereClause_format.sql index 9c9a096d..53958211 100644 --- a/src/test/testData/sql/formatter/ConditionalWhereClause_format.sql +++ b/src/test/testData/sql/formatter/ConditionalWhereClause_format.sql @@ -15,9 +15,10 @@ SELECT * /*%end*/ /*%if status != null */ /*%if status == "available" */ - AND p.stock_quantity > IN /* quantitys */(0, 1, 2) - AND p.is_active = true + AND p.stock_quantitys IN /* quantitys */(0, 1, 2) + OR p.is_active = true /*%elseif status == "outofstock" */ + OR p.is_active = true AND p.stock_quantitys IN /* quantitys */(0, 1, 2) /*%else*/ AND p.is_active = false From 69eedd02c456054a1a662a3b29495172659bf58e Mon Sep 17 00:00:00 2001 From: xterao <72187180+xterao@users.noreply.github.com> Date: Thu, 21 Aug 2025 09:21:15 +0900 Subject: [PATCH 05/21] Remove duplicate return Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../formatter/block/group/keyword/option/SqlInGroupBlock.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlInGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlInGroupBlock.kt index 0ed05b2f..8af43525 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlInGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlInGroupBlock.kt @@ -69,7 +69,7 @@ class SqlInGroupBlock( override fun isSaveSpace(lastGroup: SqlBlock?): Boolean { if (lastGroup is SqlElConditionLoopCommentBlock) { return if (lastGroup.conditionType.isElse()) { - return false + false } else { !lastGroup.isBeforeParentBlock() } From 4de78cd39da51d64def01eb9f9676de36fd73399 Mon Sep 17 00:00:00 2001 From: xterao Date: Thu, 21 Aug 2025 10:00:42 +0900 Subject: [PATCH 06/21] ix subquery formatting under conditional --- .../formatter/block/SqlKeywordBlock.kt | 9 +- .../comment/SqlElConditionLoopCommentBlock.kt | 85 +++++++++++-------- .../block/group/subgroup/SqlSubGroupBlock.kt | 5 +- .../group/subgroup/SqlSubQueryGroupBlock.kt | 12 ++- .../intellij/formatter/SqlFormatterTest.kt | 4 + .../sql/formatter/ConditionalSubquery.sql | 41 +++++++++ .../formatter/ConditionalSubquery_format.sql | 44 ++++++++++ 7 files changed, 154 insertions(+), 46 deletions(-) create mode 100644 src/test/testData/sql/formatter/ConditionalSubquery.sql create mode 100644 src/test/testData/sql/formatter/ConditionalSubquery_format.sql diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlKeywordBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlKeywordBlock.kt index 4bb701da..2b0cd5bf 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlKeywordBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlKeywordBlock.kt @@ -17,7 +17,6 @@ package org.domaframework.doma.intellij.formatter.block import com.intellij.lang.ASTNode import com.intellij.psi.formatter.common.AbstractBlock -import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock import org.domaframework.doma.intellij.formatter.block.conflict.SqlDoGroupBlock import org.domaframework.doma.intellij.formatter.block.group.SqlNewGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock @@ -96,13 +95,7 @@ open class SqlKeywordBlock( } else -> { - parentBlock?.let { parent -> - if (parent is SqlElConditionLoopCommentBlock) { - parent.indent.groupIndentLen - } else { - 1 - } - } ?: 1 + parentBlock?.indent?.groupIndentLen ?: 1 } } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt index c7758ae2..4b194347 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt @@ -67,6 +67,9 @@ class SqlElConditionLoopCommentBlock( } companion object { + private const val DIRECTIVE_INDENT_STEP = 2 + private const val DEFAULT_INDENT_OFFSET = 1 + private val LINE_BREAK_PARENT_TYPES = listOf( SqlSubGroupBlock::class, @@ -121,7 +124,7 @@ class SqlElConditionLoopCommentBlock( childBlocks.forEach { child -> if (child is SqlElConditionLoopCommentBlock && child.conditionType.isStartDirective()) { // If the child is a condition loop directive, align its indentation with the parent directive - child.indent.indentLen = indent.indentLen.plus(2) + child.indent.indentLen = indent.indentLen.plus(DIRECTIVE_INDENT_STEP) } else if (child is SqlLineCommentBlock) { if (PsiTreeUtil.prevLeaf(child.node.psi, false)?.text?.contains(StringUtil.LINE_SEPARATE) == true) { child.indent.indentLen = indent.groupIndentLen @@ -201,37 +204,7 @@ class SqlElConditionLoopCommentBlock( } val openConditionLoopDirectiveCount = getOpenDirectiveCount(parent) when (parent) { - is SqlSubGroupBlock -> { - val parentGroupIndentLen = parent.indent.groupIndentLen - val grand = parent.parentBlock - grand?.let { grand -> - if (grand is SqlCreateKeywordGroupBlock) { - val grandIndentLen = grand.indent.groupIndentLen - return grandIndentLen.plus(parentGroupIndentLen).minus(1) - } - if (grand is SqlInsertQueryGroupBlock) { - return parentGroupIndentLen - } - if (grand is SqlColumnRawGroupBlock) { - val grandIndentLen = grand.indent.groupIndentLen - var prevTextLen = 1 - parent.prevChildren?.dropLast(1)?.forEach { prev -> - prevTextLen = prevTextLen.plus(prev.getNodeText().length) - } - return grandIndentLen.plus(prevTextLen) - } - } - return if (TypeUtil.isExpectedClassType( - SqlRightPatternBlock.NOT_INDENT_EXPECTED_TYPES, - parent, - ) || - parent is SqlWithCommonTableGroupBlock - ) { - parentGroupIndentLen.plus(openConditionLoopDirectiveCount * 2) - } else { - parentGroupIndentLen.plus(openConditionLoopDirectiveCount * 2).plus(1) - } - } + is SqlSubGroupBlock -> return calculateSubGroupBlockIndent(parent, openConditionLoopDirectiveCount) is SqlElConditionLoopCommentBlock -> { if (conditionType.isEnd()) { @@ -241,7 +214,7 @@ class SqlElConditionLoopCommentBlock( } else if (conditionType.isElse()) { return parent.indent.indentLen } else { - return parent.indent.indentLen.plus(2) + return parent.indent.indentLen.plus(DIRECTIVE_INDENT_STEP) } } @@ -256,14 +229,14 @@ class SqlElConditionLoopCommentBlock( // The child branch applies in cases where a conditional directive is included as a child of this block. val questOffset = if (parent is SqlWithQueryGroupBlock) 0 else 1 parent.indent.groupIndentLen - .plus(openConditionLoopDirectiveCount * 2) + .plus(openConditionLoopDirectiveCount * DIRECTIVE_INDENT_STEP) .plus(questOffset) } } else { - parent.indent.indentLen.plus(openConditionLoopDirectiveCount * 2) + parent.indent.indentLen.plus(openConditionLoopDirectiveCount * DIRECTIVE_INDENT_STEP) } } - else -> return parent.indent.indentLen.plus(openConditionLoopDirectiveCount * 2) + else -> return parent.indent.indentLen.plus(openConditionLoopDirectiveCount * DIRECTIVE_INDENT_STEP) } } return 0 @@ -298,4 +271,44 @@ class SqlElConditionLoopCommentBlock( } return false } + + fun checkConditionLoopDirectiveParentBlock(block: SqlBlock): Boolean = isBeforeParentBlock() && parentBlock == block + + private fun calculateSubGroupBlockIndent(parent: SqlSubGroupBlock, openDirectiveCount: Int): Int { + val parentGroupIndentLen = parent.indent.groupIndentLen + val grand = parent.parentBlock + + grand?.let { grandParent -> + when (grandParent) { + is SqlCreateKeywordGroupBlock -> { + val grandIndentLen = grandParent.indent.groupIndentLen + return grandIndentLen.plus(parentGroupIndentLen).minus(DEFAULT_INDENT_OFFSET) + } + is SqlInsertQueryGroupBlock -> return parentGroupIndentLen + is SqlColumnRawGroupBlock -> { + val grandIndentLen = grandParent.indent.groupIndentLen + val prevTextLen = calculatePreviousTextLength(parent) + return grandIndentLen.plus(prevTextLen) + } + } + } + + return if (shouldNotIndent(parent)) { + parentGroupIndentLen.plus(openDirectiveCount * DIRECTIVE_INDENT_STEP) + } else { + parentGroupIndentLen.plus(openDirectiveCount * DIRECTIVE_INDENT_STEP).plus(DEFAULT_INDENT_OFFSET) + } + } + + private fun calculatePreviousTextLength(parent: SqlSubGroupBlock): Int { + var prevTextLen = DEFAULT_INDENT_OFFSET + parent.prevChildren?.dropLast(1)?.forEach { prev -> + prevTextLen = prevTextLen.plus(prev.getNodeText().length) + } + return prevTextLen + } + + private fun shouldNotIndent(parent: SqlSubGroupBlock): Boolean = + TypeUtil.isExpectedClassType(SqlRightPatternBlock.NOT_INDENT_EXPECTED_TYPES, parent) || + parent is SqlWithCommonTableGroupBlock } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubGroupBlock.kt index f924a33e..4cac053e 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubGroupBlock.kt @@ -123,7 +123,10 @@ abstract class SqlSubGroupBlock( lastGroup?.let { lastBlock -> if (lastBlock is SqlJoinQueriesGroupBlock) return true if (lastGroup is SqlInGroupBlock) return false - if (lastGroup is SqlElConditionLoopCommentBlock) return true + if (lastGroup is SqlElConditionLoopCommentBlock) { + return lastGroup.checkConditionLoopDirectiveParentBlock(this) || + lastGroup.conditionType.isElse() + } val grand = lastBlock.parentBlock if (grand is SqlElConditionLoopCommentBlock) { if (grand.conditionType.isElse()) { diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubQueryGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubQueryGroupBlock.kt index 7f5b0793..4242c551 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubQueryGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubQueryGroupBlock.kt @@ -51,7 +51,17 @@ open class SqlSubQueryGroupBlock( override fun createBlockIndentLen(): Int = parentBlock?.let { parent -> return when (parent) { - is SqlElConditionLoopCommentBlock, is SqlWithQuerySubGroupBlock -> return parent.indent.groupIndentLen + is SqlElConditionLoopCommentBlock -> { + return if (parent.isBeforeParentBlock()) { + parent.parentBlock + ?.indent + ?.groupIndentLen + ?.plus(1) ?: 1 + } else { + parent.indent.indentLen + } + } + is SqlWithQuerySubGroupBlock -> return parent.indent.groupIndentLen is SqlJoinQueriesGroupBlock -> return parent.indent.indentLen is SqlJoinGroupBlock -> return parent.indent.groupIndentLen.plus(1) else -> { diff --git a/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt index 32ca8e6c..cc07e945 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt @@ -246,6 +246,10 @@ class SqlFormatterTest : BasePlatformTestCase() { formatSqlFile("ConditionalUnion.sql", "ConditionalUnion$formatDataPrefix.sql") } + fun testConditionalSubqueryFormatter() { + formatSqlFile("ConditionalSubquery.sql", "ConditionalSubquery$formatDataPrefix.sql") + } + private fun formatSqlFile( beforeFile: String, afterFile: String, diff --git a/src/test/testData/sql/formatter/ConditionalSubquery.sql b/src/test/testData/sql/formatter/ConditionalSubquery.sql new file mode 100644 index 00000000..d6a203d2 --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalSubquery.sql @@ -0,0 +1,41 @@ +SELECT e.id +, e.name +, +/*%if includeSkillCount */ +( SELECT COUNT(DISTINCT es.skill_id) +FROM employee_skills es +/*%if onlyActiveSkills */ +INNER JOIN skills sk ON es.skill_id = sk.id +WHERE es.employee_id = e.id +AND sk.is_active = true +/*%if skillCategories != null && skillCategories.size() > 0 */ +AND sk.category IN /* skillCategories */('technical', 'management') +/*%end*/ +/*%else*/ +WHERE es.employee_id = e.id +/*%end*/ ), +/*%end*/ +d.department_name +FROM employees e +INNER JOIN departments d ON e.department_id = d.id + WHERE/*%if filterByHighPerformers */ +ids IN ( SELECT id + FROM performance_reviews pr +WHERE pr.employee_id = e.id + /*%if reviewPeriod != null */ + AND pr.review_date BETWEEN /* reviewPeriod.startDate */'2023-01-01' AND /* reviewPeriod.endDate */'2023-12-31' + /*%end*/ + /*%if minScore != null */ + AND pr.performance_score >= /* minScore */4.0 + /*%end*/ ) +/*%else*/ +e.is_active = true +/*%end*/ +/*%if excludeDepartments != null && excludeDepartments.size() > 0 */ +AND e.department_id NOT IN ( SELECT d2.id +FROM departments d2 +WHERE d2.name IN /* excludeDepartments */('HR', 'Finance') +/*%if onlyActiveDepartments */ +AND d2.is_active = true +/*%end*/ ) +/*%end*/ diff --git a/src/test/testData/sql/formatter/ConditionalSubquery_format.sql b/src/test/testData/sql/formatter/ConditionalSubquery_format.sql new file mode 100644 index 00000000..d6f9e444 --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalSubquery_format.sql @@ -0,0 +1,44 @@ +SELECT e.id + , e.name + , /*%if includeSkillCount */ + ( SELECT COUNT(DISTINCT es.skill_id) + FROM employee_skills es + /*%if onlyActiveSkills */ + INNER JOIN skills sk + ON es.skill_id = sk.id + WHERE es.employee_id = e.id + AND sk.is_active = true + /*%if skillCategories != null && skillCategories.size() > 0 */ + AND sk.category IN /* skillCategories */('technical', 'management') + /*%end*/ + /*%else*/ + WHERE es.employee_id = e.id + /*%end*/ ) + , + /*%end*/ + d.department_name + FROM employees e + INNER JOIN departments d + ON e.department_id = d.id + WHERE + /*%if filterByHighPerformers */ + ids IN ( SELECT id + FROM performance_reviews pr + WHERE pr.employee_id = e.id + /*%if reviewPeriod != null */ + AND pr.review_date BETWEEN /* reviewPeriod.startDate */'2023-01-01' AND /* reviewPeriod.endDate */'2023-12-31' + /*%end*/ + /*%if minScore != null */ + AND pr.performance_score >= /* minScore */4.0 + /*%end*/ ) + /*%else*/ + e.is_active = true + /*%end*/ + /*%if excludeDepartments != null && excludeDepartments.size() > 0 */ + AND e.department_id NOT IN ( SELECT d2.id + FROM departments d2 + WHERE d2.name IN /* excludeDepartments */('HR', 'Finance') + /*%if onlyActiveDepartments */ + AND d2.is_active = true + /*%end*/ ) + /*%end*/ From cac0623ae68688494a12ac9a77f08ea12031024a Mon Sep 17 00:00:00 2001 From: xterao Date: Thu, 21 Aug 2025 10:12:25 +0900 Subject: [PATCH 07/21] Refactor isSaveSpace method for improved readability and conciseness --- .../block/group/keyword/option/SqlInGroupBlock.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlInGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlInGroupBlock.kt index 8af43525..197404bf 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlInGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlInGroupBlock.kt @@ -68,11 +68,8 @@ class SqlInGroupBlock( override fun isSaveSpace(lastGroup: SqlBlock?): Boolean { if (lastGroup is SqlElConditionLoopCommentBlock) { - return if (lastGroup.conditionType.isElse()) { - false - } else { - !lastGroup.isBeforeParentBlock() - } + if (lastGroup.conditionType.isElse()) return false + return !lastGroup.isBeforeParentBlock() } return false } From ab0a52265128e54bb4f84644863d5229bb5f7d86 Mon Sep 17 00:00:00 2001 From: xterao Date: Thu, 21 Aug 2025 10:13:44 +0900 Subject: [PATCH 08/21] Refactor SQL indentation logic for sub-group blocks and clean up formatting --- .../comment/SqlElConditionLoopCommentBlock.kt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt index 4b194347..0a2daf21 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt @@ -69,7 +69,7 @@ class SqlElConditionLoopCommentBlock( companion object { private const val DIRECTIVE_INDENT_STEP = 2 private const val DEFAULT_INDENT_OFFSET = 1 - + private val LINE_BREAK_PARENT_TYPES = listOf( SqlSubGroupBlock::class, @@ -273,11 +273,14 @@ class SqlElConditionLoopCommentBlock( } fun checkConditionLoopDirectiveParentBlock(block: SqlBlock): Boolean = isBeforeParentBlock() && parentBlock == block - - private fun calculateSubGroupBlockIndent(parent: SqlSubGroupBlock, openDirectiveCount: Int): Int { + + private fun calculateSubGroupBlockIndent( + parent: SqlSubGroupBlock, + openDirectiveCount: Int, + ): Int { val parentGroupIndentLen = parent.indent.groupIndentLen val grand = parent.parentBlock - + grand?.let { grandParent -> when (grandParent) { is SqlCreateKeywordGroupBlock -> { @@ -292,14 +295,14 @@ class SqlElConditionLoopCommentBlock( } } } - + return if (shouldNotIndent(parent)) { parentGroupIndentLen.plus(openDirectiveCount * DIRECTIVE_INDENT_STEP) } else { parentGroupIndentLen.plus(openDirectiveCount * DIRECTIVE_INDENT_STEP).plus(DEFAULT_INDENT_OFFSET) } } - + private fun calculatePreviousTextLength(parent: SqlSubGroupBlock): Int { var prevTextLen = DEFAULT_INDENT_OFFSET parent.prevChildren?.dropLast(1)?.forEach { prev -> @@ -307,8 +310,8 @@ class SqlElConditionLoopCommentBlock( } return prevTextLen } - + private fun shouldNotIndent(parent: SqlSubGroupBlock): Boolean = TypeUtil.isExpectedClassType(SqlRightPatternBlock.NOT_INDENT_EXPECTED_TYPES, parent) || - parent is SqlWithCommonTableGroupBlock + parent is SqlWithCommonTableGroupBlock } From 08ff1fe7da7c48a98c66fbf95db66074fcac91c9 Mon Sep 17 00:00:00 2001 From: xterao Date: Thu, 21 Aug 2025 13:18:55 +0900 Subject: [PATCH 09/21] Fix indent calculation logic for combined IN clause and subquery --- .../block/group/keyword/option/SqlInGroupBlock.kt | 9 ++++++++- .../block/group/subgroup/SqlSubQueryGroupBlock.kt | 7 ++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlInGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlInGroupBlock.kt index 8af43525..a02db661 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlInGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlInGroupBlock.kt @@ -40,7 +40,11 @@ class SqlInGroupBlock( override fun createBlockIndentLen(): Int { parentBlock?.let { parent -> - if (parent is SqlElConditionLoopCommentBlock) return parent.indent.groupIndentLen + if (parent is SqlElConditionLoopCommentBlock && + parent.checkConditionLoopDirectiveParentBlock(this) + ) { + return parent.indent.indentLen + } val prevChildren = this.prevBlocks val children = prevChildren.filter { it !is SqlDefaultCommentBlock } val firstChild = children.firstOrNull() @@ -52,6 +56,8 @@ class SqlInGroupBlock( } val dotCount = sumChildren.count { it.node.elementType == SqlTypes.DOT } + val parentText = (parent as? SqlElConditionLoopCommentBlock)?.parentBlock?.getNodeText()?.length ?: 0 + return sumChildren .sumOf { prev -> prev @@ -59,6 +65,7 @@ class SqlInGroupBlock( .plus(prev.getNodeText().length.plus(1)) }.minus(dotCount * 2) .plus(parent.indent.groupIndentLen) + .plus(parentText) .plus(1) } return 0 diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubQueryGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubQueryGroupBlock.kt index 4242c551..faea6ab8 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubQueryGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubQueryGroupBlock.kt @@ -21,6 +21,7 @@ import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlDefaultCommentBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlJoinGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.condition.SqlConditionalExpressionGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.top.SqlJoinQueriesGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithCommonTableGroupBlock @@ -65,7 +66,11 @@ open class SqlSubQueryGroupBlock( is SqlJoinQueriesGroupBlock -> return parent.indent.indentLen is SqlJoinGroupBlock -> return parent.indent.groupIndentLen.plus(1) else -> { - val children = prevChildren?.filter { it !is SqlDefaultCommentBlock } + val children = + prevChildren?.filter { + it !is SqlDefaultCommentBlock && + (parent as? SqlKeywordGroupBlock)?.topKeywordBlocks?.contains(it) == false + } // Retrieve the list of child blocks excluding the conditional directive that appears immediately before this block, // as it is already included as a child block. val sumChildren = From a661681ef20248e14f99aba0255dadb77bf5b270 Mon Sep 17 00:00:00 2001 From: xterao Date: Thu, 21 Aug 2025 13:28:29 +0900 Subject: [PATCH 10/21] Refactor SQL indentation logic for condition loop blocks to improve clarity and accuracy --- .../comment/SqlElConditionLoopCommentBlock.kt | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt index 0a2daf21..9a8bb731 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt @@ -221,20 +221,17 @@ class SqlElConditionLoopCommentBlock( is SqlKeywordGroupBlock -> { // At this point, it's not possible to determine whether the parent keyword group appears before or after this block based solely on the parent-child relationship. // Therefore, determine the position directly using the text offset. - return if (!isBeforeParentBlock()) { - val lastBlockConditionLoopCommentBlock: SqlElConditionLoopCommentBlock? = getLastBlockHasConditionLoopDirective() - if (lastBlockConditionLoopCommentBlock != null && lastBlockConditionLoopCommentBlock.conditionEnd != null) { - lastBlockConditionLoopCommentBlock.indent.indentLen - } else { - // The child branch applies in cases where a conditional directive is included as a child of this block. - val questOffset = if (parent is SqlWithQueryGroupBlock) 0 else 1 - parent.indent.groupIndentLen - .plus(openConditionLoopDirectiveCount * DIRECTIVE_INDENT_STEP) - .plus(questOffset) + if (isBeforeParentBlock()) { + return parent.indent.indentLen + openConditionLoopDirectiveCount * DIRECTIVE_INDENT_STEP + } + getLastBlockHasConditionLoopDirective()?.let { lastBlock -> + if (lastBlock.conditionEnd != null) { + return lastBlock.indent.indentLen } - } else { - parent.indent.indentLen.plus(openConditionLoopDirectiveCount * DIRECTIVE_INDENT_STEP) } + return parent.indent.groupIndentLen + + openConditionLoopDirectiveCount * DIRECTIVE_INDENT_STEP + + if (parent !is SqlWithQueryGroupBlock) 1 else 0 } else -> return parent.indent.indentLen.plus(openConditionLoopDirectiveCount * DIRECTIVE_INDENT_STEP) } From 60b42033531535a05651f939fbe8ea3212ca2a3d Mon Sep 17 00:00:00 2001 From: xterao Date: Thu, 21 Aug 2025 13:07:43 +0900 Subject: [PATCH 11/21] Support for EXISTS clause format --- .../keyword/option/SqlExistsGroupBlock.kt | 50 +++++++++++++++++++ .../builder/SqlBlockRelationBuilder.kt | 2 + .../formatter/handler/JoinClauseHandler.kt | 2 +- .../intellij/formatter/util/SqlKeywordUtil.kt | 10 ++++ .../intellij/formatter/SqlFormatterTest.kt | 4 ++ .../sql/formatter/ConditionalExists.sql | 46 +++++++++++++++++ .../formatter/ConditionalExists_format.sql | 44 ++++++++++++++++ 7 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlExistsGroupBlock.kt create mode 100644 src/test/testData/sql/formatter/ConditionalExists.sql create mode 100644 src/test/testData/sql/formatter/ConditionalExists_format.sql diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlExistsGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlExistsGroupBlock.kt new file mode 100644 index 00000000..2b0f8c39 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlExistsGroupBlock.kt @@ -0,0 +1,50 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.domaframework.doma.intellij.formatter.block.group.keyword.option + +import com.intellij.lang.ASTNode +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateTableColumnDefinitionRawGroupBlock +import org.domaframework.doma.intellij.formatter.util.IndentType +import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext + +class SqlExistsGroupBlock( + node: ASTNode, + context: SqlBlockFormattingContext, +) : SqlKeywordGroupBlock(node, IndentType.OPTIONS, context) { + override fun setParentGroupBlock(lastGroup: SqlBlock?) { + super.setParentGroupBlock(lastGroup) + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = createGroupIndentLen() + } + + override fun createBlockIndentLen(): Int = parentBlock?.indent?.groupIndentLen?.plus(1) ?: 1 + + override fun createGroupIndentLen(): Int { + parentBlock?.let { parent -> + return parent.indent.groupIndentLen.plus(topKeywordBlocks.sumOf { it.getNodeText().length.plus(1) }) + } + return topKeywordBlocks.sumOf { it.getNodeText().length.plus(1) }.minus(1) + } + + override fun isSaveSpace(lastGroup: SqlBlock?): Boolean { + if (lastGroup is SqlCreateTableColumnDefinitionRawGroupBlock) { + return false + } + return super.isSaveSpace(lastGroup) + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlBlockRelationBuilder.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlBlockRelationBuilder.kt index e4106390..6ff1f11d 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlBlockRelationBuilder.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlBlockRelationBuilder.kt @@ -28,6 +28,7 @@ import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordG import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateViewGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.inline.SqlInlineGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.inline.SqlInlineSecondGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlExistsGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlInGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlLateralGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlReturningGroupBlock @@ -64,6 +65,7 @@ class SqlBlockRelationBuilder( SqlColumnDefinitionRawGroupBlock::class, SqlLateralGroupBlock::class, SqlInGroupBlock::class, + SqlExistsGroupBlock::class, ) private val TOP_LEVEL_EXPECTED_TYPES = diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/JoinClauseHandler.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/JoinClauseHandler.kt index 28aa2dc5..99a987a4 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/JoinClauseHandler.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/JoinClauseHandler.kt @@ -30,7 +30,7 @@ object JoinClauseHandler { child: ASTNode, sqlBlockFormattingCtx: SqlBlockFormattingContext, ): SqlBlock = - if (SqlKeywordUtil.Companion.isJoinKeyword(keywordText)) { + if (SqlKeywordUtil.isJoinKeyword(keywordText)) { SqlJoinGroupBlock( child, sqlBlockFormattingCtx, diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlKeywordUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlKeywordUtil.kt index 7aef2483..5fdc0a3e 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlKeywordUtil.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlKeywordUtil.kt @@ -292,6 +292,15 @@ class SqlKeywordUtil { fun isWithOptionKeyword(keyword: String): Boolean = WITH_OPTION_KEYWORDS.contains(keyword.lowercase()) + private val EXISTS_KEYWORDS = + setOf( + "if", + "exists", + "not", + ) + + fun isExistsKeyword(keyword: String): Boolean = EXISTS_KEYWORDS.contains(keyword.lowercase()) + private val SET_LINE_KEYWORDS = mapOf( "into" to setOf("insert"), @@ -307,6 +316,7 @@ class SqlKeywordUtil { "by" to setOf("group", "order", "first"), "and" to setOf("between"), "if" to setOf("table", "create"), + "exists" to setOf("if", "where"), "conflict" to setOf("on"), "nothing" to setOf("do"), "constraint" to setOf("on"), diff --git a/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt index cc07e945..ca0f6030 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt @@ -250,6 +250,10 @@ class SqlFormatterTest : BasePlatformTestCase() { formatSqlFile("ConditionalSubquery.sql", "ConditionalSubquery$formatDataPrefix.sql") } + fun testConditionalExistsFormatter() { + formatSqlFile("ConditionalExists.sql", "ConditionalExists$formatDataPrefix.sql") + } + private fun formatSqlFile( beforeFile: String, afterFile: String, diff --git a/src/test/testData/sql/formatter/ConditionalExists.sql b/src/test/testData/sql/formatter/ConditionalExists.sql new file mode 100644 index 00000000..487d5c96 --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalExists.sql @@ -0,0 +1,46 @@ +SELECT e.id + , e.name + , d.department_name + FROM employees e + WHERE +/*%if isNot*/ +NOT +/*%end*/ +/*%if filterByHighPerformers */ +EXISTS ( SELECT 1 + FROM performance_reviews pr + WHERE pr.employee_id = e.id + /*%if reviewPeriod != null */ + AND pr.review_date BETWEEN /* reviewPeriod.startDate */'2023-01-01' AND /* reviewPeriod.endDate */'2023-12-31' + /*%end*/ + /*%if minScore != null */ + AND pr.performance_score >= /* minScore */4.0 + /*%end*/ ) +/*%elseif filterByLowPerformers */ + /*%if isNot*/ + NOT +/*%end*/ +EXISTS ( SELECT 1 + FROM performance_reviews pr + WHERE pr.employee_id = e.id + /*%if reviewPeriod != null */ + AND pr.review_date BETWEEN /* reviewPeriod.startDate */'2023-01-01' AND /* reviewPeriod.endDate */'2023-12-31' + /*%end*/ + /*%if minScore != null */ + AND pr.performance_score >= /* minScore */4.0 + /*%end*/ ) +/*%elseif filterByMediumPerformers */ +NOT +EXISTS + ( SELECT 1 + FROM performance_reviews pr + WHERE pr.employee_id = e.id + /*%if reviewPeriod != null */ + AND pr.review_date BETWEEN /* reviewPeriod.startDate */'2023-01-01' AND /* reviewPeriod.endDate */'2023-12-31' + /*%end*/ + /*%if minScore != null */ + AND pr.performance_score >= /* minScore */4.0 + /*%end*/ ) + /*%else*/ + e.is_active = true + /*%end*/ diff --git a/src/test/testData/sql/formatter/ConditionalExists_format.sql b/src/test/testData/sql/formatter/ConditionalExists_format.sql new file mode 100644 index 00000000..315112fd --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalExists_format.sql @@ -0,0 +1,44 @@ +SELECT e.id + , e.name + , d.department_name + FROM employees e + WHERE + /*%if isNot*/ + NOT + /*%end*/ + /*%if filterByHighPerformers */ + EXISTS ( SELECT 1 + FROM performance_reviews pr + WHERE pr.employee_id = e.id + /*%if reviewPeriod != null */ + AND pr.review_date BETWEEN /* reviewPeriod.startDate */'2023-01-01' AND /* reviewPeriod.endDate */'2023-12-31' + /*%end*/ + /*%if minScore != null */ + AND pr.performance_score >= /* minScore */4.0 + /*%end*/ ) + /*%elseif filterByLowPerformers */ + /*%if isNot*/ + NOT + /*%end*/ + EXISTS ( SELECT 1 + FROM performance_reviews pr + WHERE pr.employee_id = e.id + /*%if reviewPeriod != null */ + AND pr.review_date BETWEEN /* reviewPeriod.startDate */'2023-01-01' AND /* reviewPeriod.endDate */'2023-12-31' + /*%end*/ + /*%if minScore != null */ + AND pr.performance_score >= /* minScore */4.0 + /*%end*/ ) + /*%elseif filterByMediumPerformers */ + NOT EXISTS ( SELECT 1 + FROM performance_reviews pr + WHERE pr.employee_id = e.id + /*%if reviewPeriod != null */ + AND pr.review_date BETWEEN /* reviewPeriod.startDate */'2023-01-01' AND /* reviewPeriod.endDate */'2023-12-31' + /*%end*/ + /*%if minScore != null */ + AND pr.performance_score >= /* minScore */4.0 + /*%end*/ ) + /*%else*/ + e.is_active = true + /*%end*/ From 9ba4ebe4fc75e6d9bcf0d19093e1b2d18cf64ea6 Mon Sep 17 00:00:00 2001 From: xterao Date: Thu, 21 Aug 2025 14:48:01 +0900 Subject: [PATCH 12/21] Fix detection and indentation logic for EXISTS clauses within conditional directives --- .../formatter/block/SqlKeywordBlock.kt | 7 ++- .../keyword/option/SqlExistsGroupBlock.kt | 16 ++++--- .../group/subgroup/SqlSubQueryGroupBlock.kt | 13 +++--- .../formatter/util/SqlBlockGenerator.kt | 19 ++++++++ .../sql/formatter/ConditionalExists.sql | 5 ++- .../formatter/ConditionalExists_format.sql | 43 +++++++++++-------- 6 files changed, 72 insertions(+), 31 deletions(-) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlKeywordBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlKeywordBlock.kt index 2b0cd5bf..fd2ef97c 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlKeywordBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlKeywordBlock.kt @@ -17,6 +17,7 @@ package org.domaframework.doma.intellij.formatter.block import com.intellij.lang.ASTNode import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock import org.domaframework.doma.intellij.formatter.block.conflict.SqlDoGroupBlock import org.domaframework.doma.intellij.formatter.block.group.SqlNewGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock @@ -95,7 +96,11 @@ open class SqlKeywordBlock( } else -> { - parentBlock?.indent?.groupIndentLen ?: 1 + if (parentBlock is SqlElConditionLoopCommentBlock) { + parentBlock?.indent?.groupIndentLen ?: 1 + } else { + parentBlock?.indent?.groupIndentLen?.plus(1) ?: 1 + } } } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlExistsGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlExistsGroupBlock.kt index 2b0f8c39..03f7013a 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlExistsGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlExistsGroupBlock.kt @@ -17,6 +17,7 @@ package org.domaframework.doma.intellij.formatter.block.group.keyword.option import com.intellij.lang.ASTNode import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateTableColumnDefinitionRawGroupBlock import org.domaframework.doma.intellij.formatter.util.IndentType @@ -32,13 +33,18 @@ class SqlExistsGroupBlock( indent.groupIndentLen = createGroupIndentLen() } - override fun createBlockIndentLen(): Int = parentBlock?.indent?.groupIndentLen?.plus(1) ?: 1 - - override fun createGroupIndentLen(): Int { + override fun createBlockIndentLen(): Int { parentBlock?.let { parent -> - return parent.indent.groupIndentLen.plus(topKeywordBlocks.sumOf { it.getNodeText().length.plus(1) }) + if (parent.parentBlock is SqlElConditionLoopCommentBlock) { + return parent.indent.groupIndentLen + } } - return topKeywordBlocks.sumOf { it.getNodeText().length.plus(1) }.minus(1) + return parentBlock?.indent?.groupIndentLen?.plus(1) ?: 1 + } + + override fun createGroupIndentLen(): Int { + val parentGroupIndent = parentBlock?.indent?.groupIndentLen ?: 0 + return topKeywordBlocks.sumOf { it.getNodeText().length.plus(1) }.plus(parentGroupIndent).minus(1) } override fun isSaveSpace(lastGroup: SqlBlock?): Boolean { diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubQueryGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubQueryGroupBlock.kt index faea6ab8..19ec3387 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubQueryGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubQueryGroupBlock.kt @@ -66,11 +66,7 @@ open class SqlSubQueryGroupBlock( is SqlJoinQueriesGroupBlock -> return parent.indent.indentLen is SqlJoinGroupBlock -> return parent.indent.groupIndentLen.plus(1) else -> { - val children = - prevChildren?.filter { - it !is SqlDefaultCommentBlock && - (parent as? SqlKeywordGroupBlock)?.topKeywordBlocks?.contains(it) == false - } + val children = prevChildren?.filter { shouldIncludeChildBlock(it, parent) } // Retrieve the list of child blocks excluding the conditional directive that appears immediately before this block, // as it is already included as a child block. val sumChildren = @@ -90,6 +86,13 @@ open class SqlSubQueryGroupBlock( } } ?: offset + private fun shouldIncludeChildBlock( + block: SqlBlock, + parent: SqlBlock, + ): Boolean = + block !is SqlDefaultCommentBlock && + (parent as? SqlKeywordGroupBlock)?.topKeywordBlocks?.contains(block) == false + override fun createGroupIndentLen(): Int { parentBlock?.let { parent -> if (parent is SqlJoinQueriesGroupBlock) { diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt index 3ea84ce3..01af7d4d 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt @@ -45,6 +45,7 @@ import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlC import org.domaframework.doma.intellij.formatter.block.group.keyword.inline.SqlInlineGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.inline.SqlInlineSecondGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.insert.SqlInsertQueryGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlExistsGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlInGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlLateralGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlSecondOptionKeywordGroupBlock @@ -148,6 +149,24 @@ class SqlBlockGenerator( sqlBlockFormattingCtx, ) } + + if (SqlKeywordUtil.isExistsKeyword(keywordText)) { + if (lastGroupBlock is SqlExistsGroupBlock) { + return SqlKeywordBlock( + child, + indentLevel, + sqlBlockFormattingCtx, + ) + } + if (lastGroupBlock is SqlElConditionLoopCommentBlock && lastGroupBlock.conditionType.isElse() || + keywordText == "not" && (lastGroupBlock !is SqlConditionKeywordGroupBlock && lastGroupBlock !is SqlWhereGroupBlock) + ) { + return SqlExistsGroupBlock( + child, + sqlBlockFormattingCtx, + ) + } + } } IndentType.CONFLICT -> { diff --git a/src/test/testData/sql/formatter/ConditionalExists.sql b/src/test/testData/sql/formatter/ConditionalExists.sql index 487d5c96..b864503b 100644 --- a/src/test/testData/sql/formatter/ConditionalExists.sql +++ b/src/test/testData/sql/formatter/ConditionalExists.sql @@ -1,7 +1,10 @@ SELECT e.id , e.name , d.department_name - FROM employees e + FROM employees e /*%if join */ +INNER JOIN separators spr +ON EXISTS ( SELECT id FROM spr ) + /*%end */ WHERE /*%if isNot*/ NOT diff --git a/src/test/testData/sql/formatter/ConditionalExists_format.sql b/src/test/testData/sql/formatter/ConditionalExists_format.sql index 315112fd..b850a000 100644 --- a/src/test/testData/sql/formatter/ConditionalExists_format.sql +++ b/src/test/testData/sql/formatter/ConditionalExists_format.sql @@ -2,6 +2,11 @@ SELECT e.id , e.name , d.department_name FROM employees e + /*%if join */ + INNER JOIN separators spr + ON EXISTS ( SELECT id + FROM spr ) + /*%end */ WHERE /*%if isNot*/ NOT @@ -18,27 +23,27 @@ SELECT e.id /*%end*/ ) /*%elseif filterByLowPerformers */ /*%if isNot*/ - NOT + NOT /*%end*/ - EXISTS ( SELECT 1 - FROM performance_reviews pr - WHERE pr.employee_id = e.id - /*%if reviewPeriod != null */ - AND pr.review_date BETWEEN /* reviewPeriod.startDate */'2023-01-01' AND /* reviewPeriod.endDate */'2023-12-31' - /*%end*/ - /*%if minScore != null */ - AND pr.performance_score >= /* minScore */4.0 - /*%end*/ ) + EXISTS ( SELECT 1 + FROM performance_reviews pr + WHERE pr.employee_id = e.id + /*%if reviewPeriod != null */ + AND pr.review_date BETWEEN /* reviewPeriod.startDate */'2023-01-01' AND /* reviewPeriod.endDate */'2023-12-31' + /*%end*/ + /*%if minScore != null */ + AND pr.performance_score >= /* minScore */4.0 + /*%end*/ ) /*%elseif filterByMediumPerformers */ - NOT EXISTS ( SELECT 1 - FROM performance_reviews pr - WHERE pr.employee_id = e.id - /*%if reviewPeriod != null */ - AND pr.review_date BETWEEN /* reviewPeriod.startDate */'2023-01-01' AND /* reviewPeriod.endDate */'2023-12-31' - /*%end*/ - /*%if minScore != null */ - AND pr.performance_score >= /* minScore */4.0 - /*%end*/ ) + NOT EXISTS ( SELECT 1 + FROM performance_reviews pr + WHERE pr.employee_id = e.id + /*%if reviewPeriod != null */ + AND pr.review_date BETWEEN /* reviewPeriod.startDate */'2023-01-01' AND /* reviewPeriod.endDate */'2023-12-31' + /*%end*/ + /*%if minScore != null */ + AND pr.performance_score >= /* minScore */4.0 + /*%end*/ ) /*%else*/ e.is_active = true /*%end*/ From b25b329c160bf3e74af0d8a483a5a0eed2376bdb Mon Sep 17 00:00:00 2001 From: xterao Date: Thu, 21 Aug 2025 14:56:16 +0900 Subject: [PATCH 13/21] Add SqlKeywordBlockFactory for creating SQL keyword blocks based on context --- .../formatter/util/SqlBlockGenerator.kt | 83 +---------- .../formatter/util/SqlKeywordBlockFactory.kt | 138 ++++++++++++++++++ 2 files changed, 146 insertions(+), 75 deletions(-) create mode 100644 src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlKeywordBlockFactory.kt diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt index 01af7d4d..419793df 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt @@ -30,7 +30,6 @@ import org.domaframework.doma.intellij.formatter.block.comment.SqlBlockCommentBl import org.domaframework.doma.intellij.formatter.block.comment.SqlCommentBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElBlockCommentBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock -import org.domaframework.doma.intellij.formatter.block.conflict.OnConflictKeywordType import org.domaframework.doma.intellij.formatter.block.conflict.SqlConflictClauseBlock import org.domaframework.doma.intellij.formatter.block.conflict.SqlDoGroupBlock import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnBlock @@ -41,13 +40,8 @@ import org.domaframework.doma.intellij.formatter.block.group.keyword.condition.S import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateTableColumnDefinitionGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateTableColumnDefinitionRawGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateViewGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.inline.SqlInlineGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.inline.SqlInlineSecondGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.insert.SqlInsertQueryGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlExistsGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlInGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlLateralGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlSecondOptionKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlFromGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlSecondKeywordBlock @@ -100,12 +94,12 @@ class SqlBlockGenerator( formatMode, ) + private val keywordBlockFactory = SqlKeywordBlockFactory(sqlBlockFormattingCtx) + fun getKeywordBlock( child: ASTNode, lastGroupBlock: SqlBlock?, ): SqlBlock { - // Because we haven't yet set the parent-child relationship of the block, - // the parent group references groupTopNodeIndexHistory. val keywordText = child.text.lowercase() val indentLevel = SqlKeywordUtil.getIndentType(keywordText) @@ -113,74 +107,13 @@ class SqlBlockGenerator( return getKeywordGroupBlock(indentLevel, keywordText, child, lastGroupBlock) } - when (indentLevel) { - IndentType.INLINE -> { - if (!SqlKeywordUtil.isSetLineKeyword( - child.text, - lastGroupBlock?.getNodeText() ?: "", - ) - ) { - return SqlInlineGroupBlock(child, sqlBlockFormattingCtx) - } - } - - IndentType.ATTACHED -> { - if (lastGroupBlock is SqlCreateKeywordGroupBlock) { - lastGroupBlock.setCreateQueryType(child.text) - return SqlKeywordBlock(child, indentLevel, sqlBlockFormattingCtx) - } - } - - IndentType.OPTIONS -> { - if (keywordText == "as") { - val parentCreateBlock = - lastGroupBlock as? SqlCreateKeywordGroupBlock - ?: lastGroupBlock?.parentBlock as? SqlCreateKeywordGroupBlock - if (parentCreateBlock != null && parentCreateBlock.createType == CreateQueryType.VIEW) { - return SqlCreateViewGroupBlock(child, sqlBlockFormattingCtx) - } - } - if (keywordText == "lateral") { - return SqlLateralGroupBlock(child, sqlBlockFormattingCtx) - } - if (keywordText == "in") { - return SqlInGroupBlock( - child, - sqlBlockFormattingCtx, - ) - } - - if (SqlKeywordUtil.isExistsKeyword(keywordText)) { - if (lastGroupBlock is SqlExistsGroupBlock) { - return SqlKeywordBlock( - child, - indentLevel, - sqlBlockFormattingCtx, - ) - } - if (lastGroupBlock is SqlElConditionLoopCommentBlock && lastGroupBlock.conditionType.isElse() || - keywordText == "not" && (lastGroupBlock !is SqlConditionKeywordGroupBlock && lastGroupBlock !is SqlWhereGroupBlock) - ) { - return SqlExistsGroupBlock( - child, - sqlBlockFormattingCtx, - ) - } - } - } - - IndentType.CONFLICT -> { - if (lastGroupBlock is SqlConflictClauseBlock) { - lastGroupBlock.conflictType = OnConflictKeywordType.of(keywordText) - return SqlKeywordBlock(child, indentLevel, sqlBlockFormattingCtx) - } else { - return SqlConflictClauseBlock(child, sqlBlockFormattingCtx) - } - } - - else -> return SqlKeywordBlock(child, indentLevel, sqlBlockFormattingCtx) + return when (indentLevel) { + IndentType.INLINE -> keywordBlockFactory.createInlineBlock(child, lastGroupBlock) + IndentType.ATTACHED -> keywordBlockFactory.createAttachedBlock(child, lastGroupBlock) + IndentType.OPTIONS -> keywordBlockFactory.createOptionsBlock(keywordText, child, lastGroupBlock) + IndentType.CONFLICT -> keywordBlockFactory.createConflictBlock(keywordText, child, lastGroupBlock) + else -> SqlKeywordBlock(child, indentLevel, sqlBlockFormattingCtx) } - return SqlKeywordBlock(child, indentLevel, sqlBlockFormattingCtx) } private fun getKeywordGroupBlock( diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlKeywordBlockFactory.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlKeywordBlockFactory.kt new file mode 100644 index 00000000..926db5d4 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlKeywordBlockFactory.kt @@ -0,0 +1,138 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.domaframework.doma.intellij.formatter.util + +import com.intellij.lang.ASTNode +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.block.SqlKeywordBlock +import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock +import org.domaframework.doma.intellij.formatter.block.conflict.OnConflictKeywordType +import org.domaframework.doma.intellij.formatter.block.conflict.SqlConflictClauseBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.condition.SqlConditionKeywordGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateKeywordGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateViewGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.inline.SqlInlineGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlExistsGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlInGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlLateralGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlWhereGroupBlock + +/** + * Factory class for creating SQL keyword blocks based on indent type and context + */ +class SqlKeywordBlockFactory( + private val sqlBlockFormattingCtx: SqlBlockFormattingContext, +) { + fun createInlineBlock( + child: ASTNode, + lastGroupBlock: SqlBlock?, + ): SqlBlock = + if (!SqlKeywordUtil.isSetLineKeyword( + child.text, + lastGroupBlock?.getNodeText() ?: "", + ) + ) { + SqlInlineGroupBlock(child, sqlBlockFormattingCtx) + } else { + SqlKeywordBlock(child, IndentType.INLINE, sqlBlockFormattingCtx) + } + + fun createAttachedBlock( + child: ASTNode, + lastGroupBlock: SqlBlock?, + ): SqlBlock { + if (lastGroupBlock is SqlCreateKeywordGroupBlock) { + lastGroupBlock.setCreateQueryType(child.text) + } + return SqlKeywordBlock(child, IndentType.ATTACHED, sqlBlockFormattingCtx) + } + + fun createOptionsBlock( + keywordText: String, + child: ASTNode, + lastGroupBlock: SqlBlock?, + ): SqlBlock { + // Handle AS keyword for CREATE VIEW + if (keywordText == "as") { + val parentCreateBlock = findParentCreateBlock(lastGroupBlock) + if (parentCreateBlock?.createType == CreateQueryType.VIEW) { + return SqlCreateViewGroupBlock(child, sqlBlockFormattingCtx) + } + } + + // Handle LATERAL keyword + if (keywordText == "lateral") { + return SqlLateralGroupBlock(child, sqlBlockFormattingCtx) + } + + // Handle IN keyword + if (keywordText == "in") { + return SqlInGroupBlock(child, sqlBlockFormattingCtx) + } + + // Handle EXISTS/NOT EXISTS keywords + if (SqlKeywordUtil.isExistsKeyword(keywordText)) { + return createExistsBlock(keywordText, child, lastGroupBlock) + } + + return SqlKeywordBlock(child, IndentType.OPTIONS, sqlBlockFormattingCtx) + } + + fun createConflictBlock( + keywordText: String, + child: ASTNode, + lastGroupBlock: SqlBlock?, + ): SqlBlock = + if (lastGroupBlock is SqlConflictClauseBlock) { + lastGroupBlock.conflictType = OnConflictKeywordType.of(keywordText) + SqlKeywordBlock(child, IndentType.CONFLICT, sqlBlockFormattingCtx) + } else { + SqlConflictClauseBlock(child, sqlBlockFormattingCtx) + } + + private fun findParentCreateBlock(block: SqlBlock?): SqlCreateKeywordGroupBlock? = + when (block) { + is SqlCreateKeywordGroupBlock -> block + else -> block?.parentBlock as? SqlCreateKeywordGroupBlock + } + + private fun createExistsBlock( + keywordText: String, + child: ASTNode, + lastGroupBlock: SqlBlock?, + ): SqlBlock { + // If already in EXISTS group, just return a keyword block + if (lastGroupBlock is SqlExistsGroupBlock) { + return SqlKeywordBlock(child, IndentType.OPTIONS, sqlBlockFormattingCtx) + } + + // Check for ELSE condition or NOT keyword outside WHERE/condition context + val shouldCreateExistsGroup = + when { + lastGroupBlock is SqlElConditionLoopCommentBlock && lastGroupBlock.conditionType.isElse() -> true + keywordText == "not" && !isInConditionContext(lastGroupBlock) -> true + else -> false + } + + return if (shouldCreateExistsGroup) { + SqlExistsGroupBlock(child, sqlBlockFormattingCtx) + } else { + SqlKeywordBlock(child, IndentType.OPTIONS, sqlBlockFormattingCtx) + } + } + + private fun isInConditionContext(block: SqlBlock?): Boolean = block is SqlConditionKeywordGroupBlock || block is SqlWhereGroupBlock +} From f8ba20a3a578f141d75db520bf4fae3e09b0dc11 Mon Sep 17 00:00:00 2001 From: xterao Date: Thu, 21 Aug 2025 17:17:56 +0900 Subject: [PATCH 14/21] Fix to prevent spaces from being inserted in comparison operator symbols --- .../doma/intellij/formatter/block/SqlFileBlock.kt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt index 935fa905..376a4196 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt @@ -32,6 +32,7 @@ import org.domaframework.doma.intellij.formatter.block.comment.SqlDefaultComment import org.domaframework.doma.intellij.formatter.block.comment.SqlElBlockCommentBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlLineCommentBlock +import org.domaframework.doma.intellij.formatter.block.expr.SqlElAtSignBlock import org.domaframework.doma.intellij.formatter.block.expr.SqlElSymbolBlock import org.domaframework.doma.intellij.formatter.block.group.SqlNewGroupBlock import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnBlock @@ -519,7 +520,7 @@ open class SqlFileBlock( return SqlCustomSpacingBuilder().getSpacing(childBlock2) } - if (childBlock2 is SqlOtherBlock) { + if (childBlock1 !is SqlElSymbolBlock && childBlock1 !is SqlOtherBlock && childBlock2 is SqlOtherBlock) { return SqlCustomSpacingBuilder().getSpacing(childBlock2) } @@ -578,6 +579,16 @@ open class SqlFileBlock( return SqlCustomSpacingBuilder.normalSpacing } + if (childBlock1 is SqlElSymbolBlock && childBlock2 is SqlElSymbolBlock || + childBlock1 is SqlElAtSignBlock && childBlock2 is SqlElSymbolBlock || + childBlock1 is SqlOtherBlock && childBlock2 is SqlElSymbolBlock || + childBlock1 is SqlElSymbolBlock && childBlock2 is SqlElAtSignBlock || + childBlock1 is SqlOtherBlock && childBlock2 is SqlOtherBlock || + childBlock1 is SqlElSymbolBlock && childBlock2 is SqlOtherBlock + ) { + return SqlCustomSpacingBuilder.nonSpacing + } + val spacing: Spacing? = customSpacingBuilder?.getCustomSpacing(childBlock1, childBlock2) return spacing ?: spacingBuilder.getSpacing(this, childBlock1, childBlock2) } From 2c55788f7ce03468fd8ac9b97efcb345d44fe01c Mon Sep 17 00:00:00 2001 From: xterao Date: Thu, 21 Aug 2025 20:09:09 +0900 Subject: [PATCH 15/21] Implement formatting rules for syntax handling array-type parameters --- .../doma/intellij/formatter/block/SqlBlock.kt | 18 +++-- .../intellij/formatter/block/SqlFileBlock.kt | 55 +++++++++++++--- .../block/comma/SqlArrayCommaBlock.kt | 54 +++++++++++++++ .../block/comment/SqlBlockCommentBlock.kt | 19 +----- .../block/comment/SqlCommentBlock.kt | 35 ++++++++-- .../block/comment/SqlDefaultCommentBlock.kt | 32 +++++---- .../block/comment/SqlElBlockCommentBlock.kt | 18 ++--- .../comment/SqlElConditionLoopCommentBlock.kt | 46 ++++++------- .../group/subgroup/SqlArrayListGroupBlock.kt | 44 +++++++++++++ .../block/group/subgroup/SqlSubGroupBlock.kt | 17 ++--- .../formatter/block/other/SqlEscapeBlock.kt | 14 ++-- .../formatter/block/other/SqlOtherBlock.kt | 8 ++- .../formatter/block/word/SqlArrayWordBlock.kt | 64 ++++++++++++++++++ .../handler/CommaRawClauseHandler.kt | 3 + .../formatter/util/SqlBlockGenerator.kt | 7 ++ .../intellij/formatter/SqlFormatterTest.kt | 4 ++ .../sql/formatter/ComparisonOperators.sql | 64 ++++++++++++++++++ .../formatter/ComparisonOperators_format.sql | 65 +++++++++++++++++++ 18 files changed, 460 insertions(+), 107 deletions(-) create mode 100644 src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comma/SqlArrayCommaBlock.kt create mode 100644 src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlArrayListGroupBlock.kt create mode 100644 src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlArrayWordBlock.kt create mode 100644 src/test/testData/sql/formatter/ComparisonOperators.sql create mode 100644 src/test/testData/sql/formatter/ComparisonOperators_format.sql diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt index a4cdc696..3b196056 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt @@ -123,13 +123,19 @@ open class SqlBlock( (parent.parentBlock is SqlNewGroupBlock || parent.parentBlock is SqlElConditionLoopCommentBlock) } == true - protected fun isElementAfterConditionLoopEnd(): Boolean = - ( + protected fun isElementAfterConditionLoopEnd(): Boolean { + val prevChildren = prevBlocks - .lastOrNull() + .firstOrNull() ?.childBlocks - ?.firstOrNull() as? SqlElConditionLoopCommentBlock - )?.conditionEnd != null + + val firstConditionBlock = (prevChildren?.firstOrNull() as? SqlElConditionLoopCommentBlock) + val endBlock = firstConditionBlock?.conditionEnd + if (endBlock == null) return false + val lastBlock = prevChildren.lastOrNull() + + return endBlock.node.startOffset > (lastBlock?.node?.startOffset ?: 0) + } protected fun isFirstChildConditionLoopDirective(): Boolean = childBlocks.firstOrNull() is SqlElConditionLoopCommentBlock @@ -265,7 +271,7 @@ open class SqlBlock( /** * Creates a spacing builder specifically for directive block comments. */ - protected fun createBlockDirectiveCommentSpacingBuilder(): SqlCustomSpacingBuilder = + protected open fun createBlockDirectiveCommentSpacingBuilder(): SqlCustomSpacingBuilder = SqlCustomSpacingBuilder() .withSpacing( SqlTypes.BLOCK_COMMENT_START, diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt index 376a4196..a0bc7155 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt @@ -48,6 +48,7 @@ import org.domaframework.doma.intellij.formatter.block.group.keyword.update.SqlU import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithColumnGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithCommonTableGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithQueryGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlArrayListGroupBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlDataTypeParamBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlFunctionParamBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock @@ -55,6 +56,7 @@ import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubQuer import org.domaframework.doma.intellij.formatter.block.other.SqlEscapeBlock import org.domaframework.doma.intellij.formatter.block.other.SqlOtherBlock import org.domaframework.doma.intellij.formatter.block.word.SqlAliasBlock +import org.domaframework.doma.intellij.formatter.block.word.SqlArrayWordBlock import org.domaframework.doma.intellij.formatter.block.word.SqlFunctionGroupBlock import org.domaframework.doma.intellij.formatter.block.word.SqlTableBlock import org.domaframework.doma.intellij.formatter.block.word.SqlWordBlock @@ -106,14 +108,14 @@ open class SqlFileBlock( if (isLeaf) return mutableListOf() var child = node.firstChildNode - var prevNonWhiteSpaceNode: ASTNode? = null + var prevNonWhiteSpaceNode: SqlBlock? = null blockBuilder.addGroupTopNodeIndexHistory(this) while (child != null) { val lastBlock = blocks.lastOrNull() val lastGroup = blockBuilder.getLastGroupTopNodeIndexHistory() if (child !is PsiWhiteSpace) { - val childBlock = getBlock(child) - prevNonWhiteSpaceNode = child + val childBlock = getBlock(child, prevNonWhiteSpaceNode) + prevNonWhiteSpaceNode = childBlock updateCommentParentAndIdent(childBlock) updateBlockParentAndLAddGroup(childBlock) updateWhiteSpaceInclude(lastBlock, childBlock, lastGroup) @@ -141,7 +143,10 @@ open class SqlFileBlock( /** * Creates a block for the given child AST node. */ - override fun getBlock(child: ASTNode): SqlBlock { + private fun getBlock( + child: ASTNode, + prevBlock: SqlBlock?, + ): SqlBlock { val defaultFormatCtx = SqlBlockFormattingContext( wrap, @@ -182,10 +187,17 @@ open class SqlFileBlock( } else { val escapeStrings = listOf("\"", "`", "[", "]") if (escapeStrings.contains(child.text)) { - SqlEscapeBlock( - child, - defaultFormatCtx, - ) + if (child.text == "[" && prevBlock is SqlArrayWordBlock) { + SqlArrayListGroupBlock( + child, + defaultFormatCtx, + ) + } else { + SqlEscapeBlock( + child, + defaultFormatCtx, + ) + } } else { SqlOtherBlock( child, @@ -379,6 +391,25 @@ open class SqlFileBlock( ) } + is SqlEscapeBlock -> { + val index = + if (lastGroupBlock is SqlArrayListGroupBlock) { + blockBuilder.getGroupTopNodeIndex { + it is SqlArrayListGroupBlock + } + } else { + -1 + } + blockRelationBuilder.updateGroupBlockParentAndAddGroup( + childBlock, + ) + if (lastGroupBlock is SqlArrayListGroupBlock) { + if (index >= 0) { + blockBuilder.clearSubListGroupTopNodeIndexHistory(index) + } + } + } + is SqlWordBlock, is SqlOtherBlock -> { blockRelationBuilder.updateGroupBlockParentAndAddGroup( childBlock, @@ -462,6 +493,10 @@ open class SqlFileBlock( ) } + if (childBlock1 is SqlArrayWordBlock && childBlock2 is SqlArrayListGroupBlock) { + return SqlCustomSpacingBuilder.nonSpacing + } + if (childBlock2 is SqlWithColumnGroupBlock) { return SqlCustomSpacingBuilder.normalSpacing } @@ -495,6 +530,10 @@ open class SqlFileBlock( SqlCustomSpacingBuilder().getSpacing(childBlock2) } + is SqlArrayListGroupBlock -> { + SqlCustomSpacingBuilder.nonSpacing + } + else -> SqlCustomSpacingBuilder.normalSpacing } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comma/SqlArrayCommaBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comma/SqlArrayCommaBlock.kt new file mode 100644 index 00000000..83300f63 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comma/SqlArrayCommaBlock.kt @@ -0,0 +1,54 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.domaframework.doma.intellij.formatter.block.comma + +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.block.SqlCommaBlock +import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock +import org.domaframework.doma.intellij.formatter.util.IndentType +import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext + +class SqlArrayCommaBlock( + node: ASTNode, + context: SqlBlockFormattingContext, +) : SqlCommaBlock( + node, + context, + ) { + override val indent = + ElementIndent( + IndentType.NONE, + 0, + 0, + ) + + override fun setParentGroupBlock(lastGroup: SqlBlock?) { + super.setParentGroupBlock(lastGroup) + indent.indentLevel = IndentType.NONE + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = createGroupIndentLen() + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun createBlockIndentLen(): Int = 0 + + override fun createGroupIndentLen(): Int = indent.indentLen.plus(1) + + override fun isSaveSpace(lastGroup: SqlBlock?): Boolean = parentBlock is SqlElConditionLoopCommentBlock +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlBlockCommentBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlBlockCommentBlock.kt index 680360c7..d788efc2 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlBlockCommentBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlBlockCommentBlock.kt @@ -18,10 +18,7 @@ package org.domaframework.doma.intellij.formatter.block.comment import com.intellij.formatting.Block import com.intellij.formatting.Spacing import com.intellij.lang.ASTNode -import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.formatter.common.AbstractBlock -import com.intellij.psi.util.PsiTreeUtil -import org.domaframework.doma.intellij.common.util.StringUtil import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.formatter.block.SqlUnknownBlock import org.domaframework.doma.intellij.formatter.builder.SqlCustomSpacingBuilder @@ -36,18 +33,7 @@ open class SqlBlockCommentBlock( node, context, ) { - override fun buildChildren(): MutableList { - val blocks = mutableListOf() - var child = node.firstChildNode - while (child != null) { - if (child !is PsiWhiteSpace) { - val block = getBlock(child) - blocks.add(block) - } - child = child.treeNext - } - return blocks - } + override fun buildChildren(): MutableList = buildChildBlocks { getBlock(it) } override fun getBlock(child: ASTNode): SqlBlock { val elementType = child.elementType @@ -59,8 +45,7 @@ open class SqlBlockCommentBlock( } } - override fun isSaveSpace(lastGroup: SqlBlock?): Boolean = - PsiTreeUtil.prevLeaf(node.psi)?.text?.contains(StringUtil.LINE_SEPARATE) == true + override fun isSaveSpace(lastGroup: SqlBlock?): Boolean = hasLineBreakBefore() override fun getSpacing( child1: Block?, diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlCommentBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlCommentBlock.kt index 2f6660e2..3ddc76fd 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlCommentBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlCommentBlock.kt @@ -16,7 +16,10 @@ package org.domaframework.doma.intellij.formatter.block.comment import com.intellij.lang.ASTNode +import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.formatter.common.AbstractBlock +import com.intellij.psi.util.PsiTreeUtil +import org.domaframework.doma.intellij.common.util.StringUtil import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock import org.domaframework.doma.intellij.formatter.util.IndentType @@ -33,11 +36,16 @@ abstract class SqlCommentBlock( context.enableFormat, context.formatMode, ) { + companion object { + const val DEFAULT_INDENT = 0 + const val SINGLE_INDENT = 1 + } + override val indent = ElementIndent( IndentType.NONE, - 0, - 0, + DEFAULT_INDENT, + DEFAULT_INDENT, ) override fun setParentGroupBlock(lastGroup: SqlBlock?) { @@ -53,14 +61,29 @@ abstract class SqlCommentBlock( override fun createBlockIndentLen(): Int { parentBlock?.let { parent -> - if (parent.parentBlock !is SqlSubGroupBlock || - parent.parentBlock?.childBlocks?.size != 1 - ) { + if (shouldInheritParentIndent(parent)) { return parent.indent.indentLen } } - return 0 + return DEFAULT_INDENT } override fun createGroupIndentLen(): Int = indent.indentLen + + protected open fun shouldInheritParentIndent(parent: SqlBlock): Boolean = + parent.parentBlock !is SqlSubGroupBlock || parent.parentBlock?.childBlocks?.size != 1 + + fun hasLineBreakBefore(): Boolean = PsiTreeUtil.prevLeaf(node.psi)?.text?.contains(StringUtil.LINE_SEPARATE) == true + + protected fun buildChildBlocks(blockProvider: (ASTNode) -> SqlBlock): MutableList { + val blocks = mutableListOf() + var child = node.firstChildNode + while (child != null) { + if (child !is PsiWhiteSpace) { + blocks.add(blockProvider(child)) + } + child = child.treeNext + } + return blocks + } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlDefaultCommentBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlDefaultCommentBlock.kt index 1c9047ca..a07c30aa 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlDefaultCommentBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlDefaultCommentBlock.kt @@ -17,8 +17,6 @@ package org.domaframework.doma.intellij.formatter.block.comment import com.intellij.lang.ASTNode import com.intellij.psi.formatter.common.AbstractBlock -import com.intellij.psi.util.PsiTreeUtil -import org.domaframework.doma.intellij.common.util.StringUtil import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.formatter.util.IndentType import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext @@ -27,6 +25,10 @@ abstract class SqlDefaultCommentBlock( node: ASTNode, context: SqlBlockFormattingContext, ) : SqlCommentBlock(node, context) { + companion object { + private const val FILE_TOP_THRESHOLD = 2 + } + /** * If this block is the last element, the indentation update process is not called, * so set the default indentation to 1. @@ -34,8 +36,8 @@ abstract class SqlDefaultCommentBlock( override val indent = ElementIndent( IndentType.NONE, - 1, - 1, + SINGLE_INDENT, + SINGLE_INDENT, ) override fun buildChildren(): MutableList = mutableListOf() @@ -50,16 +52,18 @@ abstract class SqlDefaultCommentBlock( baseBlock: SqlBlock, groupBlockCount: Int, ) { - indent.indentLen = - if (isSaveSpace(parentBlock)) { - baseBlock.indent.indentLen - } else if (groupBlockCount <= 2) { - // If it is the top of the file, the indent number should be 0. - 0 - } else { - 1 - } + indent.indentLen = calculateIndentForUpdate(baseBlock, groupBlockCount) } - override fun isSaveSpace(lastGroup: SqlBlock?) = PsiTreeUtil.prevLeaf(node.psi)?.text?.contains(StringUtil.LINE_SEPARATE) == true + private fun calculateIndentForUpdate( + baseBlock: SqlBlock, + groupBlockCount: Int, + ): Int = + when { + isSaveSpace(parentBlock) -> baseBlock.indent.indentLen + groupBlockCount <= FILE_TOP_THRESHOLD -> DEFAULT_INDENT + else -> SINGLE_INDENT + } + + override fun isSaveSpace(lastGroup: SqlBlock?) = hasLineBreakBefore() } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElBlockCommentBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElBlockCommentBlock.kt index 44b65849..7cf1ddde 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElBlockCommentBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElBlockCommentBlock.kt @@ -18,7 +18,6 @@ package org.domaframework.doma.intellij.formatter.block.comment import com.intellij.formatting.Block import com.intellij.formatting.Spacing import com.intellij.lang.ASTNode -import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.formatter.common.AbstractBlock import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.elementType @@ -40,7 +39,7 @@ import org.domaframework.doma.intellij.psi.SqlTypes open class SqlElBlockCommentBlock( node: ASTNode, - private val context: SqlBlockFormattingContext, + protected open val context: SqlBlockFormattingContext, open val customSpacingBuilder: SqlCustomSpacingBuilder?, ) : SqlCommentBlock(node, context) { enum class SqlElCommentDirectiveType { @@ -82,18 +81,7 @@ open class SqlElBlockCommentBlock( return SqlElCommentDirectiveType.NORMAL } - override fun buildChildren(): MutableList { - val blocks = mutableListOf() - var child = node.firstChildNode - while (child != null) { - if (child !is PsiWhiteSpace) { - val block = getBlock(child) - blocks.add(block) - } - child = child.treeNext - } - return blocks - } + override fun buildChildren(): MutableList = buildChildBlocks { getBlock(it) } override fun getBlock(child: ASTNode): SqlBlock = when (child.elementType) { @@ -148,6 +136,8 @@ open class SqlElBlockCommentBlock( Spacing.createSpacing(1, 1, 0, false, 0), ) + override fun createBlockDirectiveCommentSpacingBuilder(): SqlCustomSpacingBuilder = createBlockCommentSpacingBuilder() + override fun getSpacing( child1: Block?, child2: Block, diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt index 9a8bb731..66f081f6 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt @@ -18,11 +18,9 @@ package org.domaframework.doma.intellij.formatter.block.comment import com.intellij.formatting.Block import com.intellij.formatting.Spacing import com.intellij.lang.ASTNode -import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.formatter.common.AbstractBlock import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.elementType -import org.domaframework.doma.intellij.common.util.StringUtil import org.domaframework.doma.intellij.common.util.TypeUtil import org.domaframework.doma.intellij.extension.expr.isConditionOrLoopDirective import org.domaframework.doma.intellij.formatter.block.SqlBlock @@ -34,6 +32,7 @@ import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlC import org.domaframework.doma.intellij.formatter.block.group.keyword.insert.SqlInsertQueryGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithCommonTableGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithQueryGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlArrayListGroupBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock import org.domaframework.doma.intellij.formatter.builder.SqlCustomSpacingBuilder import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext @@ -44,7 +43,7 @@ import org.domaframework.doma.intellij.psi.SqlTypes class SqlElConditionLoopCommentBlock( node: ASTNode, - private val context: SqlBlockFormattingContext, + override val context: SqlBlockFormattingContext, override val customSpacingBuilder: SqlCustomSpacingBuilder?, ) : SqlElBlockCommentBlock( node, @@ -67,7 +66,7 @@ class SqlElConditionLoopCommentBlock( } companion object { - private const val DIRECTIVE_INDENT_STEP = 2 + const val DIRECTIVE_INDENT_STEP = 2 private const val DEFAULT_INDENT_OFFSET = 1 private val LINE_BREAK_PARENT_TYPES = @@ -124,9 +123,9 @@ class SqlElConditionLoopCommentBlock( childBlocks.forEach { child -> if (child is SqlElConditionLoopCommentBlock && child.conditionType.isStartDirective()) { // If the child is a condition loop directive, align its indentation with the parent directive - child.indent.indentLen = indent.indentLen.plus(DIRECTIVE_INDENT_STEP) + child.indent.indentLen = indent.indentLen + DIRECTIVE_INDENT_STEP } else if (child is SqlLineCommentBlock) { - if (PsiTreeUtil.prevLeaf(child.node.psi, false)?.text?.contains(StringUtil.LINE_SEPARATE) == true) { + if (child.hasLineBreakBefore()) { child.indent.indentLen = indent.groupIndentLen } else { child.indent.indentLen = 1 @@ -143,18 +142,7 @@ class SqlElConditionLoopCommentBlock( } } - override fun buildChildren(): MutableList { - val blocks = mutableListOf() - var child = node.firstChildNode - while (child != null) { - if (child !is PsiWhiteSpace) { - val block = getBlock(child) - blocks.add(block) - } - child = child.treeNext - } - return blocks - } + override fun buildChildren(): MutableList = buildChildBlocks { getBlock(it) } override fun getBlock(child: ASTNode): SqlBlock = when (child.elementType) { @@ -214,7 +202,7 @@ class SqlElConditionLoopCommentBlock( } else if (conditionType.isElse()) { return parent.indent.indentLen } else { - return parent.indent.indentLen.plus(DIRECTIVE_INDENT_STEP) + return parent.indent.indentLen + DIRECTIVE_INDENT_STEP } } @@ -233,7 +221,7 @@ class SqlElConditionLoopCommentBlock( openConditionLoopDirectiveCount * DIRECTIVE_INDENT_STEP + if (parent !is SqlWithQueryGroupBlock) 1 else 0 } - else -> return parent.indent.indentLen.plus(openConditionLoopDirectiveCount * DIRECTIVE_INDENT_STEP) + else -> return parent.indent.indentLen + openConditionLoopDirectiveCount * DIRECTIVE_INDENT_STEP } } return 0 @@ -255,8 +243,8 @@ class SqlElConditionLoopCommentBlock( val startDirectives = conditionLoopDirectives.count { it.conditionType.isStartDirective() } val endDirectives = conditionLoopDirectives.count { it.conditionType.isEnd() } - val diffCount = startDirectives.minus(endDirectives) - return if (diffCount > 0) diffCount.minus(1) else 0 + val diffCount = startDirectives - endDirectives + return if (diffCount > 0) diffCount - 1 else 0 } /** @@ -278,32 +266,36 @@ class SqlElConditionLoopCommentBlock( val parentGroupIndentLen = parent.indent.groupIndentLen val grand = parent.parentBlock + if (parent is SqlArrayListGroupBlock) { + return parent.indent.groupIndentLen + } + grand?.let { grandParent -> when (grandParent) { is SqlCreateKeywordGroupBlock -> { val grandIndentLen = grandParent.indent.groupIndentLen - return grandIndentLen.plus(parentGroupIndentLen).minus(DEFAULT_INDENT_OFFSET) + return grandIndentLen + parentGroupIndentLen - DEFAULT_INDENT_OFFSET } is SqlInsertQueryGroupBlock -> return parentGroupIndentLen is SqlColumnRawGroupBlock -> { val grandIndentLen = grandParent.indent.groupIndentLen val prevTextLen = calculatePreviousTextLength(parent) - return grandIndentLen.plus(prevTextLen) + return grandIndentLen + prevTextLen } } } return if (shouldNotIndent(parent)) { - parentGroupIndentLen.plus(openDirectiveCount * DIRECTIVE_INDENT_STEP) + parentGroupIndentLen + openDirectiveCount * DIRECTIVE_INDENT_STEP } else { - parentGroupIndentLen.plus(openDirectiveCount * DIRECTIVE_INDENT_STEP).plus(DEFAULT_INDENT_OFFSET) + parentGroupIndentLen + openDirectiveCount * DIRECTIVE_INDENT_STEP + DEFAULT_INDENT_OFFSET } } private fun calculatePreviousTextLength(parent: SqlSubGroupBlock): Int { var prevTextLen = DEFAULT_INDENT_OFFSET parent.prevChildren?.dropLast(1)?.forEach { prev -> - prevTextLen = prevTextLen.plus(prev.getNodeText().length) + prevTextLen += prev.getNodeText().length } return prevTextLen } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlArrayListGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlArrayListGroupBlock.kt new file mode 100644 index 00000000..ecf3df6f --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlArrayListGroupBlock.kt @@ -0,0 +1,44 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.domaframework.doma.intellij.formatter.block.group.subgroup + +import com.intellij.lang.ASTNode +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.block.other.SqlEscapeBlock +import org.domaframework.doma.intellij.formatter.block.word.SqlArrayWordBlock +import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext + +class SqlArrayListGroupBlock( + node: ASTNode, + context: SqlBlockFormattingContext, +) : SqlSubGroupBlock( + node, + context, + ) { + var endSymbol: SqlEscapeBlock? = null + var arrayBlock: SqlArrayWordBlock? = null + + override fun setParentPropertyBlock(lastGroup: SqlBlock?) { + arrayBlock = parentBlock?.getChildBlocksDropLast()?.findLast { it is SqlArrayWordBlock } as SqlArrayWordBlock? + arrayBlock?.arrayParams = this + } + + override fun createBlockIndentLen(): Int = arrayBlock?.indent?.groupIndentLen ?: 1 + + override fun createGroupIndentLen(): Int = indent.indentLen.plus(1) + + override fun isSaveSpace(lastGroup: SqlBlock?): Boolean = false +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubGroupBlock.kt index 4cac053e..bc63fbec 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubGroupBlock.kt @@ -75,9 +75,7 @@ abstract class SqlSubGroupBlock( prevChildren = parentBlock?.childBlocks?.toList() indent.indentLevel = indent.indentLevel indent.indentLen = createBlockIndentLen() - indent.groupIndentLen = parentBlock?.let { parent -> - parent.indent.indentLen.plus(parent.getNodeText().length.plus(1)) - } ?: indent.indentLen.plus(getNodeText().length) + indent.groupIndentLen = createGroupIndentLen() } override fun setParentPropertyBlock(lastGroup: SqlBlock?) { @@ -112,11 +110,14 @@ abstract class SqlSubGroupBlock( } override fun createGroupIndentLen(): Int { - parentBlock?.let { parent -> - // The parent groupIndent includes the number of characters in the group itself. - val baseGroupLen = parent.indent.groupIndentLen - return if (parent is SqlSubGroupBlock) baseGroupLen.plus(2) else baseGroupLen - } ?: return 1 + return parentBlock?.let { parent -> + parent.indent.indentLen.plus(parent.getNodeText().length.plus(1)) + } ?: indent.indentLen.plus(getNodeText().length) +// parentBlock?.let { parent -> +// // The parent groupIndent includes the number of characters in the group itself. +// val baseGroupLen = parent.indent.groupIndentLen +// return if (parent is SqlSubGroupBlock) baseGroupLen.plus(2) else baseGroupLen +// } ?: return 1 } override fun isSaveSpace(lastGroup: SqlBlock?): Boolean { diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/other/SqlEscapeBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/other/SqlEscapeBlock.kt index 1aba59ec..363e0662 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/other/SqlEscapeBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/other/SqlEscapeBlock.kt @@ -17,24 +17,28 @@ package org.domaframework.doma.intellij.formatter.block.other import com.intellij.lang.ASTNode import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlArrayListGroupBlock import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext class SqlEscapeBlock( node: ASTNode, context: SqlBlockFormattingContext, ) : SqlOtherBlock(node, context) { + // If the number of escape characters, including itself, is even var isEndEscape = false override fun setParentPropertyBlock(lastGroup: SqlBlock?) { - super.setParentPropertyBlock(lastGroup) - // If the number of escape characters, including itself, is even - isEndEscape = lastGroup?.childBlocks?.count { it is SqlEscapeBlock }?.let { it % 2 == 0 } == true + if (parentBlock is SqlArrayListGroupBlock) { + (parentBlock as SqlArrayListGroupBlock).endSymbol = this + } } - override fun createBlockIndentLen(): Int = - if (isEndEscape) { + override fun createBlockIndentLen(): Int { + isEndEscape = parentBlock?.childBlocks?.count { it is SqlEscapeBlock }?.let { it % 2 == 0 } == true || getNodeText() == "]" + return if (isEndEscape) { 0 } else { 1 } + } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/other/SqlOtherBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/other/SqlOtherBlock.kt index aad9a994..cc8eff48 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/other/SqlOtherBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/other/SqlOtherBlock.kt @@ -42,11 +42,15 @@ open class SqlOtherBlock( override fun setParentGroupBlock(lastGroup: SqlBlock?) { super.setParentGroupBlock(lastGroup) indent.indentLevel = IndentType.NONE - indent.indentLen = 1 - indent.groupIndentLen = 0 + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = createGroupIndentLen() } override fun buildChildren(): MutableList = mutableListOf() override fun isLeaf(): Boolean = true + + override fun createBlockIndentLen(): Int = 1 + + override fun createGroupIndentLen(): Int = 0 } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlArrayWordBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlArrayWordBlock.kt new file mode 100644 index 00000000..5151af3d --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlArrayWordBlock.kt @@ -0,0 +1,64 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.domaframework.doma.intellij.formatter.block.word + +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock +import org.domaframework.doma.intellij.formatter.block.expr.SqlElDotBlock +import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlArrayListGroupBlock +import org.domaframework.doma.intellij.formatter.block.other.SqlOtherBlock +import org.domaframework.doma.intellij.formatter.util.IndentType +import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext + +open class SqlArrayWordBlock( + node: ASTNode, + context: SqlBlockFormattingContext, +) : SqlWordBlock( + node, + context, + ) { + var arrayParams: SqlArrayListGroupBlock? = null + + override val indent = + ElementIndent( + IndentType.NONE, + 0, + 0, + ) + + override fun setParentGroupBlock(lastGroup: SqlBlock?) { + super.setParentGroupBlock(lastGroup) + indent.groupIndentLen = createGroupIndentLen() + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun createBlockIndentLen(): Int = (parentBlock as? SqlElConditionLoopCommentBlock)?.indent?.groupIndentLen ?: 1 + + override fun createGroupIndentLen(): Int = + parentBlock + ?.getChildBlocksDropLast() + ?.sumOf { + when (it) { + is SqlOtherBlock, is SqlElDotBlock -> it.getNodeText().length + else -> it.getNodeText().length.plus(1) + } + }?.plus(parentBlock?.indent?.groupIndentLen?.plus(1) ?: 1) + ?.plus(getNodeText().length.plus(1)) + ?: getNodeText().length.plus(1) +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/CommaRawClauseHandler.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/CommaRawClauseHandler.kt index 5d4a028c..13382339 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/CommaRawClauseHandler.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/CommaRawClauseHandler.kt @@ -18,10 +18,12 @@ package org.domaframework.doma.intellij.formatter.handler import com.intellij.lang.ASTNode import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.formatter.block.SqlCommaBlock +import org.domaframework.doma.intellij.formatter.block.comma.SqlArrayCommaBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnRawGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithCommonTableGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlArrayListGroupBlock import org.domaframework.doma.intellij.formatter.util.IndentType import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext @@ -68,6 +70,7 @@ object CommaRawClauseHandler { sqlBlockFormattingCtx: SqlBlockFormattingContext, ): SqlBlock = when { + lastGroup is SqlArrayListGroupBlock -> SqlArrayCommaBlock(child, sqlBlockFormattingCtx) shouldCreateColumnRawBlock(lastGroup) -> SqlColumnRawGroupBlock( child, diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt index 419793df..a8bd6e0b 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt @@ -58,6 +58,7 @@ import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlDataTyp import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubQueryGroupBlock import org.domaframework.doma.intellij.formatter.block.word.SqlAliasBlock +import org.domaframework.doma.intellij.formatter.block.word.SqlArrayWordBlock import org.domaframework.doma.intellij.formatter.block.word.SqlFunctionGroupBlock import org.domaframework.doma.intellij.formatter.block.word.SqlTableBlock import org.domaframework.doma.intellij.formatter.block.word.SqlWordBlock @@ -379,6 +380,12 @@ class SqlBlockGenerator( lastGroup: SqlBlock?, child: ASTNode, ): SqlBlock { + if (child.text.lowercase() == "array") { + return SqlArrayWordBlock( + child, + sqlBlockFormattingCtx, + ) + } when (lastGroup) { is SqlKeywordGroupBlock -> { when { diff --git a/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt index ca0f6030..913d3d05 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt @@ -254,6 +254,10 @@ class SqlFormatterTest : BasePlatformTestCase() { formatSqlFile("ConditionalExists.sql", "ConditionalExists$formatDataPrefix.sql") } + fun testComparisonOperatorsFormatter() { + formatSqlFile("ComparisonOperators.sql", "ComparisonOperators$formatDataPrefix.sql") + } + private fun formatSqlFile( beforeFile: String, afterFile: String, diff --git a/src/test/testData/sql/formatter/ComparisonOperators.sql b/src/test/testData/sql/formatter/ComparisonOperators.sql new file mode 100644 index 00000000..b3030575 --- /dev/null +++ b/src/test/testData/sql/formatter/ComparisonOperators.sql @@ -0,0 +1,64 @@ +-- JSON operators and comparison operators test +SELECT + id, + -- JSON/JSONB operators + json_data - > > 'name' AS name, + json_data - >'address'->>'city' AS city, + jsonb_data @ > '{"active": true}' AS is_active, + jsonb_data < @ '{"role": "admin"}' AS has_admin_role, + jsonb_data ? 'email' AS has_email, +jsonb_data ?& array [ /* property1 */'name', /* property2 */'age'] AS has_required_fields , + jsonb_data ?| array['phone' + , 'mobile'] AS has_contact, + jsonb_data #> '{contact,phone}' AS phone_path, + jsonb_data #>> '{contact,phone}' AS phone_text, + -- Comparison operators + price <> 0 AS non_zero_price, + quantity != 0 AS non_zero_quantity, + start_date <= end_date AS valid_period, + priority >= 5 AS high_priority + , -- Array operators + tags && ARRAY[/*%for property : properties */ +/* property */'property' + /*%if property_has_next*/ + , + /*%end*/ + /*%end*/] AS is_urgent + ,ARRAY['tag1' + , 'tag2'] <@ tags AS has_all_tags, + tags && ARRAY['urgent', 'critical'] AS is_urgent, + -- Pattern matching + name !~ '^test' AS not_test_name, + description ~* 'important' AS has_important_desc, + code !~* 'temp' AS not_temp_code, + -- Range operators + date_range @> current_date AS in_range, + int_range && int4range(1, 10) AS overlaps_range, + -- Geometric operators + point <-> point '(0,0)' AS distance_from_origin, + box @> point '(1,1)' AS contains_point, + -- Additional comparison combinations + quantity << 100 AS much_less, + quantity >> 10 AS much_greater, + -- Complex conditions with multiple operators + CASE + WHEN json_data->>'status' <> 'active' THEN 'inactive' + WHEN json_data->>'priority' >= '5' THEN 'high' + ELSE 'normal' + END AS status_priority +FROM + test_table +WHERE + -- Multiple consecutive operators in conditions + json_data->>'enabled' = 'true' + AND jsonb_data @> '{"verified": true}' + AND price <> 0 + AND quantity >= 10 + AND tags && ARRAY['featured' + , 'promoted'] + AND date_range @> current_date + AND name !~* 'test|demo|sample' +ORDER BY + json_data->>'priority' DESC, + price <> 0 DESC, + id \ No newline at end of file diff --git a/src/test/testData/sql/formatter/ComparisonOperators_format.sql b/src/test/testData/sql/formatter/ComparisonOperators_format.sql new file mode 100644 index 00000000..14d3306d --- /dev/null +++ b/src/test/testData/sql/formatter/ComparisonOperators_format.sql @@ -0,0 +1,65 @@ +-- JSON operators and comparison operators test +SELECT id + , + -- JSON/JSONB operators + json_data ->> 'name' AS name + , json_data -> 'address' ->> 'city' AS city + , jsonb_data @> '{"active": true}' AS is_active + , jsonb_data <@ '{"role": "admin"}' AS has_admin_role + , jsonb_data ? 'email' AS has_email + , jsonb_data ?& array[/* property1 */'name', /* property2 */'age'] AS has_required_fields + , jsonb_data ?| array['phone', 'mobile'] AS has_contact + , jsonb_data #> '{contact,phone}' AS phone_path + , jsonb_data #>> '{contact,phone}' AS phone_text + , + -- Comparison operators + price <> 0 AS non_zero_price + , quantity != 0 AS non_zero_quantity + , start_date <= end_date AS valid_period + , priority >= 5 AS high_priority + , -- Array operators + tags && ARRAY[/*%for property : properties */ + /* property */'property' + /*%if property_has_next*/ + , + /*%end*/ + /*%end*/] AS is_urgent + , ARRAY['tag1', 'tag2'] <@ tags AS has_all_tags + , tags && ARRAY['urgent', 'critical'] AS is_urgent + , + -- Pattern matching + name !~ '^test' AS not_test_name + , description ~* 'important' AS has_important_desc + , code !~* 'temp' AS not_temp_code + , + -- Range operators + date_range @> current_date AS in_range + , int_range && int4range(1 + , 10) AS overlaps_range + , + -- Geometric operators + point <-> point '(0,0)' AS distance_from_origin + , box @> point '(1,1)' AS contains_point + , + -- Additional comparison combinations + quantity << 100 AS much_less + , quantity >> 10 AS much_greater + , + -- Complex conditions with multiple operators + CASE WHEN json_data ->> 'status' <> 'active' THEN 'inactive' + WHEN json_data ->> 'priority' >= '5' THEN 'high' + ELSE 'normal' + END AS status_priority + FROM test_table + WHERE + -- Multiple consecutive operators in conditions + json_data ->> 'enabled' = 'true' + AND jsonb_data @> '{"verified": true}' + AND price <> 0 + AND quantity >= 10 + AND tags && ARRAY['featured', 'promoted'] + AND date_range @> current_date + AND name !~* 'test|demo|sample' + ORDER BY json_data ->> 'priority' DESC + , price <> 0 DESC + , id From b58e9bdb0a18bed0f359fe084b6e01f05204183c Mon Sep 17 00:00:00 2001 From: xterao Date: Thu, 21 Aug 2025 20:18:53 +0900 Subject: [PATCH 16/21] Refactor indentation logic in SqlSubGroupBlock and SqlEscapeBlock, SqlArrayWordBlock for clarity --- .../doma/intellij/formatter/block/SqlBlock.kt | 223 ++++++------------ .../intellij/formatter/block/SqlFileBlock.kt | 19 +- .../block/group/subgroup/SqlSubGroupBlock.kt | 10 +- .../formatter/block/other/SqlEscapeBlock.kt | 3 +- .../formatter/block/word/SqlArrayWordBlock.kt | 6 +- 5 files changed, 91 insertions(+), 170 deletions(-) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt index 3b196056..87f9f0d7 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt @@ -67,21 +67,28 @@ open class SqlBlock( open val childBlocks = mutableListOf() open var prevBlocks = emptyList() + companion object { + private const val DEFAULT_INDENT_SIZE = 4 + private const val DEFAULT_TEXT_LENGTH_INCREMENT = 1 + private val EXCLUDED_FROM_TEXT_LENGTH = setOf(SqlTypes.DOT, SqlTypes.RIGHT_PAREN) + private val SPACING_ONE = Spacing.createSpacing(1, 1, 0, true, 0) + private val SPACING_ZERO = Spacing.createSpacing(0, 0, 0, true, 0) + private val SPACING_ONE_NO_KEEP = Spacing.createSpacing(1, 1, 0, false, 0) + } + fun getChildrenTextLen(): Int = childBlocks.sumOf { child -> calculateChildTextLength(child) } private fun calculateChildTextLength(child: SqlBlock): Int { val nonCommentChildren = child.childBlocks.filterNot { it is SqlDefaultCommentBlock } - if (nonCommentChildren.isNotEmpty()) { - return child.getChildrenTextLen() + child.getNodeText().length - } - if (isExcludedFromTextLength(child)) { - return 0 + return when { + nonCommentChildren.isNotEmpty() -> child.getChildrenTextLen() + child.getNodeText().length + isExcludedFromTextLength(child) -> 0 + else -> child.getNodeText().length + DEFAULT_TEXT_LENGTH_INCREMENT } - return child.getNodeText().length + 1 } - private fun isExcludedFromTextLength(block: SqlBlock): Boolean = block.node.elementType in setOf(SqlTypes.DOT, SqlTypes.RIGHT_PAREN) + private fun isExcludedFromTextLength(block: SqlBlock): Boolean = block.node.elementType in EXCLUDED_FROM_TEXT_LENGTH /** * Checks if a conditional loop directive is registered before the parent block. @@ -185,10 +192,8 @@ open class SqlBlock( open fun isSaveSpace(lastGroup: SqlBlock?): Boolean = when (lastGroup) { is SqlNewGroupBlock -> shouldSaveSpaceForNewGroup(lastGroup) - else -> { - shouldSaveSpaceForConditionLoop() - } - } == true + else -> shouldSaveSpaceForConditionLoop() + } private fun shouldSaveSpaceForConditionLoop(): Boolean = isConditionLoopDirectiveRegisteredBeforeParent() || @@ -203,15 +208,22 @@ open class SqlBlock( return false } - return isFollowedByConditionLoop() || isPrecededByConditionLoop(parent) + return hasConditionLoopAround(parent) } private fun isNonBreakingKeywordCombination( parent: SqlNewGroupBlock, prevWord: SqlBlock?, - ): Boolean = - SqlKeywordUtil.isSetLineKeyword(getNodeText(), parent.getNodeText()) || - SqlKeywordUtil.isSetLineKeyword(getNodeText(), prevWord?.getNodeText() ?: "") + ): Boolean { + val currentText = getNodeText() + val parentText = parent.getNodeText() + val prevText = prevWord?.getNodeText() ?: "" + + return SqlKeywordUtil.isSetLineKeyword(currentText, parentText) || + SqlKeywordUtil.isSetLineKeyword(currentText, prevText) + } + + private fun hasConditionLoopAround(parent: SqlNewGroupBlock): Boolean = isFollowedByConditionLoop() || isPrecededByConditionLoop(parent) private fun isFollowedByConditionLoop(): Boolean = childBlocks.lastOrNull() is SqlElConditionLoopCommentBlock @@ -271,158 +283,67 @@ open class SqlBlock( /** * Creates a spacing builder specifically for directive block comments. */ - protected open fun createBlockDirectiveCommentSpacingBuilder(): SqlCustomSpacingBuilder = - SqlCustomSpacingBuilder() - .withSpacing( - SqlTypes.BLOCK_COMMENT_START, - SqlTypes.EL_ID_EXPR, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.BLOCK_COMMENT_START, - SqlTypes.EL_PRIMARY_EXPR, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.BLOCK_COMMENT_START, - SqlTypes.EL_STRING, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.BLOCK_COMMENT_START, - SqlTypes.EL_NUMBER, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.BLOCK_COMMENT_START, - SqlTypes.BOOLEAN, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.BLOCK_COMMENT_START, - SqlTypes.EL_NULL, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.BLOCK_COMMENT_START, - SqlTypes.EL_FIELD_ACCESS_EXPR, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.BLOCK_COMMENT_START, - SqlTypes.EL_STATIC_FIELD_ACCESS_EXPR, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.BLOCK_COMMENT_START, - SqlTypes.HASH, - Spacing.createSpacing(0, 0, 0, true, 0), - ).withSpacing( - SqlTypes.HASH, - SqlTypes.EL_ID_EXPR, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.HASH, - SqlTypes.EL_PRIMARY_EXPR, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.HASH, - SqlTypes.EL_STRING, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.HASH, - SqlTypes.EL_NUMBER, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.HASH, - SqlTypes.BOOLEAN, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.HASH, - SqlTypes.EL_NULL, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.HASH, - SqlTypes.EL_FIELD_ACCESS_EXPR, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.HASH, - SqlTypes.EL_STATIC_FIELD_ACCESS_EXPR, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.BLOCK_COMMENT_START, - SqlTypes.CARET, - Spacing.createSpacing(0, 0, 0, true, 0), - ).withSpacing( - SqlTypes.CARET, + protected open fun createBlockDirectiveCommentSpacingBuilder(): SqlCustomSpacingBuilder { + val builder = SqlCustomSpacingBuilder() + + // Types that need spacing after BLOCK_COMMENT_START + val typesNeedingSpaceAfterStart = + listOf( SqlTypes.EL_ID_EXPR, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.CARET, SqlTypes.EL_PRIMARY_EXPR, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.CARET, SqlTypes.EL_STRING, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.CARET, SqlTypes.EL_NUMBER, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.CARET, SqlTypes.BOOLEAN, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.CARET, SqlTypes.EL_NULL, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.CARET, SqlTypes.EL_FIELD_ACCESS_EXPR, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.CARET, SqlTypes.EL_STATIC_FIELD_ACCESS_EXPR, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.BLOCK_COMMENT_CONTENT, - SqlTypes.BLOCK_COMMENT_END, - Spacing.createSpacing(0, 0, 0, true, 0), - ).withSpacing( - SqlTypes.EL_FIELD_ACCESS_EXPR, - SqlTypes.OTHER, - Spacing.createSpacing(1, 1, 0, false, 0), - ).withSpacing( - SqlTypes.EL_STATIC_FIELD_ACCESS_EXPR, - SqlTypes.OTHER, - Spacing.createSpacing(1, 1, 0, false, 0), - ).withSpacing( + ) + + // Types that need spacing before BLOCK_COMMENT_END + val typesNeedingSpaceBeforeEnd = + listOf( SqlTypes.EL_ID_EXPR, - SqlTypes.BLOCK_COMMENT_END, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( SqlTypes.EL_PRIMARY_EXPR, - SqlTypes.BLOCK_COMMENT_END, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( SqlTypes.STRING, - SqlTypes.BLOCK_COMMENT_END, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( SqlTypes.EL_NUMBER, - SqlTypes.BLOCK_COMMENT_END, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( SqlTypes.EL_NULL, - SqlTypes.BLOCK_COMMENT_END, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( SqlTypes.BOOLEAN, - SqlTypes.BLOCK_COMMENT_END, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( SqlTypes.EL_FIELD_ACCESS_EXPR, - SqlTypes.BLOCK_COMMENT_END, - Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( SqlTypes.EL_STATIC_FIELD_ACCESS_EXPR, - SqlTypes.BLOCK_COMMENT_END, - Spacing.createSpacing(1, 1, 0, true, 0), ) + // Add spacing rules for BLOCK_COMMENT_START + typesNeedingSpaceAfterStart.forEach { type -> + builder.withSpacing(SqlTypes.BLOCK_COMMENT_START, type, SPACING_ONE) + } + + // Special cases for BLOCK_COMMENT_START + builder.withSpacing(SqlTypes.BLOCK_COMMENT_START, SqlTypes.HASH, SPACING_ZERO) + builder.withSpacing(SqlTypes.BLOCK_COMMENT_START, SqlTypes.CARET, SPACING_ZERO) + + // Add spacing rules for HASH + typesNeedingSpaceAfterStart.forEach { type -> + builder.withSpacing(SqlTypes.HASH, type, SPACING_ONE) + } + + // Add spacing rules for CARET + typesNeedingSpaceAfterStart.forEach { type -> + builder.withSpacing(SqlTypes.CARET, type, SPACING_ONE) + } + + // Special spacing rules + builder.withSpacing(SqlTypes.BLOCK_COMMENT_CONTENT, SqlTypes.BLOCK_COMMENT_END, SPACING_ZERO) + builder.withSpacing(SqlTypes.EL_FIELD_ACCESS_EXPR, SqlTypes.OTHER, SPACING_ONE_NO_KEEP) + builder.withSpacing(SqlTypes.EL_STATIC_FIELD_ACCESS_EXPR, SqlTypes.OTHER, SPACING_ONE_NO_KEEP) + + // Add spacing rules before BLOCK_COMMENT_END + typesNeedingSpaceBeforeEnd.forEach { type -> + builder.withSpacing(type, SqlTypes.BLOCK_COMMENT_END, SPACING_ONE) + } + + return builder + } + /** * Returns the child indentation for the block. * @@ -435,10 +356,6 @@ open class SqlBlock( Indent.getSpaceIndent(0) } - companion object { - private const val DEFAULT_INDENT_SIZE = 4 - } - /** * Determines whether the block is a leaf node. * diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt index a0bc7155..cc091aa7 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt @@ -618,13 +618,7 @@ open class SqlFileBlock( return SqlCustomSpacingBuilder.normalSpacing } - if (childBlock1 is SqlElSymbolBlock && childBlock2 is SqlElSymbolBlock || - childBlock1 is SqlElAtSignBlock && childBlock2 is SqlElSymbolBlock || - childBlock1 is SqlOtherBlock && childBlock2 is SqlElSymbolBlock || - childBlock1 is SqlElSymbolBlock && childBlock2 is SqlElAtSignBlock || - childBlock1 is SqlOtherBlock && childBlock2 is SqlOtherBlock || - childBlock1 is SqlElSymbolBlock && childBlock2 is SqlOtherBlock - ) { + if (isNonSpacingPair(childBlock1, childBlock2)) { return SqlCustomSpacingBuilder.nonSpacing } @@ -632,6 +626,17 @@ open class SqlFileBlock( return spacing ?: spacingBuilder.getSpacing(this, childBlock1, childBlock2) } + private fun isNonSpacingPair( + childBlock1: SqlBlock?, + childBlock2: SqlBlock, + ): Boolean = + childBlock1 is SqlElSymbolBlock && childBlock2 is SqlElSymbolBlock || + childBlock1 is SqlElAtSignBlock && childBlock2 is SqlElSymbolBlock || + childBlock1 is SqlOtherBlock && childBlock2 is SqlElSymbolBlock || + childBlock1 is SqlElSymbolBlock && childBlock2 is SqlElAtSignBlock || + childBlock1 is SqlOtherBlock && childBlock2 is SqlOtherBlock || + childBlock1 is SqlElSymbolBlock && childBlock2 is SqlOtherBlock + override fun isLeaf(): Boolean = false /** diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubGroupBlock.kt index bc63fbec..69497384 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubGroupBlock.kt @@ -109,16 +109,10 @@ abstract class SqlSubGroupBlock( return offset } - override fun createGroupIndentLen(): Int { - return parentBlock?.let { parent -> + override fun createGroupIndentLen(): Int = + parentBlock?.let { parent -> parent.indent.indentLen.plus(parent.getNodeText().length.plus(1)) } ?: indent.indentLen.plus(getNodeText().length) -// parentBlock?.let { parent -> -// // The parent groupIndent includes the number of characters in the group itself. -// val baseGroupLen = parent.indent.groupIndentLen -// return if (parent is SqlSubGroupBlock) baseGroupLen.plus(2) else baseGroupLen -// } ?: return 1 - } override fun isSaveSpace(lastGroup: SqlBlock?): Boolean { lastGroup?.let { lastBlock -> diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/other/SqlEscapeBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/other/SqlEscapeBlock.kt index 363e0662..4a3666e4 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/other/SqlEscapeBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/other/SqlEscapeBlock.kt @@ -34,7 +34,8 @@ class SqlEscapeBlock( } override fun createBlockIndentLen(): Int { - isEndEscape = parentBlock?.childBlocks?.count { it is SqlEscapeBlock }?.let { it % 2 == 0 } == true || getNodeText() == "]" + val hasEvenEscapeBlocks = parentBlock?.childBlocks?.count { it is SqlEscapeBlock }?.let { it % 2 == 0 } == true + isEndEscape = hasEvenEscapeBlocks || getNodeText() == "]" return if (isEndEscape) { 0 } else { diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlArrayWordBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlArrayWordBlock.kt index 5151af3d..f90ed64b 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlArrayWordBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlArrayWordBlock.kt @@ -48,7 +48,11 @@ open class SqlArrayWordBlock( override fun buildChildren(): MutableList = mutableListOf() - override fun createBlockIndentLen(): Int = (parentBlock as? SqlElConditionLoopCommentBlock)?.indent?.groupIndentLen ?: 1 + override fun createBlockIndentLen(): Int = + when (val parent = parentBlock) { + is SqlElConditionLoopCommentBlock -> parent.indent.groupIndentLen + else -> 1 + } override fun createGroupIndentLen(): Int = parentBlock From 6fc5fe2b0b0feca8db98119962abc3c32b96d7c2 Mon Sep 17 00:00:00 2001 From: xterao Date: Fri, 22 Aug 2025 10:57:18 +0900 Subject: [PATCH 17/21] Move SqlCommaBlock --- .../doma/intellij/formatter/block/SqlFileBlock.kt | 1 + .../formatter/block/comma/SqlArrayCommaBlock.kt | 1 - .../formatter/block/{ => comma}/SqlCommaBlock.kt | 15 ++++++++++++--- .../formatter/handler/CommaRawClauseHandler.kt | 2 +- .../formatter/handler/NotQueryGroupHandler.kt | 2 +- 5 files changed, 15 insertions(+), 6 deletions(-) rename src/main/kotlin/org/domaframework/doma/intellij/formatter/block/{ => comma}/SqlCommaBlock.kt (89%) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt index cc091aa7..5ea60593 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt @@ -27,6 +27,7 @@ import com.intellij.lang.ASTNode import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.formatter.common.AbstractBlock import org.domaframework.doma.intellij.common.util.TypeUtil +import org.domaframework.doma.intellij.formatter.block.comma.SqlCommaBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlCommentBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlDefaultCommentBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElBlockCommentBlock diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comma/SqlArrayCommaBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comma/SqlArrayCommaBlock.kt index 83300f63..69987798 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comma/SqlArrayCommaBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comma/SqlArrayCommaBlock.kt @@ -18,7 +18,6 @@ package org.domaframework.doma.intellij.formatter.block.comma import com.intellij.lang.ASTNode import com.intellij.psi.formatter.common.AbstractBlock import org.domaframework.doma.intellij.formatter.block.SqlBlock -import org.domaframework.doma.intellij.formatter.block.SqlCommaBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock import org.domaframework.doma.intellij.formatter.util.IndentType import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlCommaBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comma/SqlCommaBlock.kt similarity index 89% rename from src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlCommaBlock.kt rename to src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comma/SqlCommaBlock.kt index b27cd671..d79b85d4 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlCommaBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comma/SqlCommaBlock.kt @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.domaframework.doma.intellij.formatter.block +package org.domaframework.doma.intellij.formatter.block.comma import com.intellij.lang.ASTNode import com.intellij.psi.formatter.common.AbstractBlock import org.domaframework.doma.intellij.common.util.TypeUtil +import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnRawGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock @@ -59,6 +60,7 @@ open class SqlCommaBlock( SqlFunctionParamBlock::class, SqlWithColumnGroupBlock::class, SqlKeywordGroupBlock::class, + SqlFunctionParamBlock::class, SqlElConditionLoopCommentBlock::class, ) @@ -135,8 +137,15 @@ open class SqlCommaBlock( } return parentIndentLen.plus(1) } else { - if (parent is SqlValuesGroupBlock) return parent.indent.indentLen - return parent.indent.groupIndentLen.plus(1) + return when (parent) { + is SqlValuesGroupBlock -> parent.indent.indentLen + is SqlElConditionLoopCommentBlock -> { + val firstChild = parent.childBlocks.findLast { it is SqlFunctionParamBlock && it.endPatternBlock == null } + val parentIndent = firstChild?.indent ?: parent.indent + parentIndent.groupIndentLen.plus(1) + } + else -> parent.indent.groupIndentLen.plus(1) + } } } return 1 diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/CommaRawClauseHandler.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/CommaRawClauseHandler.kt index 13382339..dfad48c4 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/CommaRawClauseHandler.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/CommaRawClauseHandler.kt @@ -17,8 +17,8 @@ package org.domaframework.doma.intellij.formatter.handler import com.intellij.lang.ASTNode import org.domaframework.doma.intellij.formatter.block.SqlBlock -import org.domaframework.doma.intellij.formatter.block.SqlCommaBlock import org.domaframework.doma.intellij.formatter.block.comma.SqlArrayCommaBlock +import org.domaframework.doma.intellij.formatter.block.comma.SqlCommaBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnRawGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/NotQueryGroupHandler.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/NotQueryGroupHandler.kt index c04812d5..967adc9d 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/NotQueryGroupHandler.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/NotQueryGroupHandler.kt @@ -17,7 +17,7 @@ package org.domaframework.doma.intellij.formatter.handler import com.intellij.lang.ASTNode import org.domaframework.doma.intellij.formatter.block.SqlBlock -import org.domaframework.doma.intellij.formatter.block.SqlCommaBlock +import org.domaframework.doma.intellij.formatter.block.comma.SqlCommaBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock import org.domaframework.doma.intellij.formatter.block.conflict.SqlConflictClauseBlock import org.domaframework.doma.intellij.formatter.block.conflict.SqlConflictExpressionSubGroupBlock From 624d8064ec15a9b7b174cf21663d475b0c74629b Mon Sep 17 00:00:00 2001 From: xterao Date: Fri, 22 Aug 2025 10:58:18 +0900 Subject: [PATCH 18/21] Enhance SQL formatting by refining indentation logic and preventing unnecessary spaces in function parameters --- .../doma/intellij/formatter/block/SqlBlock.kt | 4 +- .../intellij/formatter/block/SqlFileBlock.kt | 23 ++++++++- .../group/subgroup/SqlFunctionParamBlock.kt | 50 +++++++++++++++---- .../block/word/SqlFunctionGroupBlock.kt | 22 ++++---- .../processor/SqlFormatPreProcessor.kt | 18 ++++++- .../formatter/util/SqlBlockGenerator.kt | 16 ++++-- .../intellij/formatter/util/SqlKeywordUtil.kt | 2 +- 7 files changed, 106 insertions(+), 29 deletions(-) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt index 87f9f0d7..e6aa4ee1 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt @@ -76,7 +76,7 @@ open class SqlBlock( private val SPACING_ONE_NO_KEEP = Spacing.createSpacing(1, 1, 0, false, 0) } - fun getChildrenTextLen(): Int = childBlocks.sumOf { child -> calculateChildTextLength(child) } + fun getChildrenTextLen(): Int = childBlocks.sumOf { child -> calculateChildTextLength(child).plus(1) } private fun calculateChildTextLength(child: SqlBlock): Int { val nonCommentChildren = child.childBlocks.filterNot { it is SqlDefaultCommentBlock } @@ -139,7 +139,7 @@ open class SqlBlock( val firstConditionBlock = (prevChildren?.firstOrNull() as? SqlElConditionLoopCommentBlock) val endBlock = firstConditionBlock?.conditionEnd if (endBlock == null) return false - val lastBlock = prevChildren.lastOrNull() + val lastBlock = prevBlocks.lastOrNull() return endBlock.node.startOffset > (lastBlock?.node?.startOffset ?: 0) } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt index 5ea60593..ad4ac09e 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt @@ -26,6 +26,10 @@ import com.intellij.formatting.Wrap import com.intellij.lang.ASTNode import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.formatter.common.AbstractBlock +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.util.elementType +import com.intellij.psi.util.nextLeaf +import com.intellij.psi.util.nextLeafs import org.domaframework.doma.intellij.common.util.TypeUtil import org.domaframework.doma.intellij.formatter.block.comma.SqlCommaBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlCommentBlock @@ -221,8 +225,23 @@ open class SqlFileBlock( } } - SqlTypes.FUNCTION_NAME -> - return SqlFunctionGroupBlock(child, defaultFormatCtx) + SqlTypes.FUNCTION_NAME -> { + val notWhiteSpaceElement = + child.psi.nextLeafs + .takeWhile { it is PsiWhiteSpace } + .lastOrNull() + ?.nextLeaf(true) + if (notWhiteSpaceElement?.elementType == SqlTypes.LEFT_PAREN || + PsiTreeUtil.nextLeaf(child.psi)?.elementType == SqlTypes.LEFT_PAREN + ) { + return SqlFunctionGroupBlock(child, defaultFormatCtx) + } + return SqlKeywordBlock( + child, + IndentType.ATTACHED, + defaultFormatCtx, + ) + } SqlTypes.WORD -> { return if (lastGroup is SqlWithQueryGroupBlock) { diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlFunctionParamBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlFunctionParamBlock.kt index e9adf8a2..72f123bb 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlFunctionParamBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlFunctionParamBlock.kt @@ -19,6 +19,9 @@ import com.intellij.lang.ASTNode import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlDefaultCommentBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock +import org.domaframework.doma.intellij.formatter.block.expr.SqlElAtSignBlock +import org.domaframework.doma.intellij.formatter.block.expr.SqlElSymbolBlock +import org.domaframework.doma.intellij.formatter.block.other.SqlOtherBlock import org.domaframework.doma.intellij.formatter.block.word.SqlFunctionGroupBlock import org.domaframework.doma.intellij.formatter.util.IndentType import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext @@ -79,22 +82,51 @@ class SqlFunctionParamBlock( } override fun createGroupIndentLen(): Int { - val parentFunctionName = parentBlock as? SqlFunctionGroupBlock - parentFunctionName?.let { parent -> - return parent.indent.groupIndentLen - .plus(getNodeText().length) - } - + val parentGroupIndent = parentBlock?.indent?.groupIndentLen ?: 0 val prevChildrenDropLast = prevChildren?.dropLast(1)?.filter { it !is SqlDefaultCommentBlock && it.node.elementType != SqlTypes.DOT } ?: emptyList() + val parentText = + if (parentBlock is SqlElConditionLoopCommentBlock) { + val grand = parentBlock?.parentBlock + grand?.getNodeText() ?: "" + } else { + "" + } val prevLength = prevChildrenDropLast - .sumOf { it.getNodeText().length } - .plus(getNodeText().length) - return prevLength.plus(prevChildrenDropLast.count()).plus(1) + .sumOf { + it.getChildrenTextLen().plus(it.getNodeText().length) + }.plus(getNodeText().length) + + // Avoid unnecessary spaces when operators are consecutive + val consecutiveSymbolCount = + calculateConsecutiveSymbolCount(prevChildrenDropLast) + val spaces = prevChildrenDropLast.count().minus(consecutiveSymbolCount) + +// parentFunctionName?.let { parent -> +// return parentGroupIndent +// .plus(prevLength) +// } + + return prevLength.plus(spaces).plus(parentText.length).plus(parentGroupIndent) + } + + private fun calculateConsecutiveSymbolCount(prevChildrenDropLast: List): Int { + var count = 0 + var total = 0 + for (block in prevChildrenDropLast) { + if (block is SqlOtherBlock || block is SqlElAtSignBlock || block is SqlElSymbolBlock) { + count++ + } else { + if (count > 1) total++ + count = 0 + } + } + if (count > 1) total += count + return total } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlFunctionGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlFunctionGroupBlock.kt index 2e047a9e..cb549380 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlFunctionGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlFunctionGroupBlock.kt @@ -37,7 +37,10 @@ class SqlFunctionGroupBlock( indent.groupIndentLen = createGroupIndentLen() } - override fun createBlockIndentLen(): Int { + override fun createBlockIndentLen(): Int = parentBlock?.indent?.groupIndentLen ?: 0 + + override fun createGroupIndentLen(): Int { + var baseIndent = 0 parentBlock?.let { parent -> val children = prevChildren.dropLast(1).filter { it !is SqlDefaultCommentBlock } val prevBlocksLength = @@ -55,15 +58,14 @@ class SqlFunctionGroupBlock( }, ) }.plus(parent.indent.groupIndentLen) - return if (parent is SqlSubGroupBlock) { - // parent.indent.groupIndentLen - prevBlocksLength - } else { - prevBlocksLength.plus(1) - } + baseIndent = + if (parent is SqlSubGroupBlock) { + // parent.indent.groupIndentLen + prevBlocksLength + } else { + prevBlocksLength.plus(1) + } } - return 0 + return baseIndent.plus(getNodeText().length) } - - override fun createGroupIndentLen(): Int = indent.indentLen.plus(getNodeText().length) } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPreProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPreProcessor.kt index 3263ce6c..215a7366 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPreProcessor.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPreProcessor.kt @@ -111,7 +111,7 @@ class SqlFormatPreProcessor : PreFormatProcessor { } SqlTypes.LEFT_PAREN -> { - newKeyword = getNewLineString(it.prevSibling, getUpperText(it)) + newKeyword = getNewLineLeftParenString(it.prevSibling, getUpperText(it)) } SqlTypes.RIGHT_PAREN -> { @@ -233,6 +233,22 @@ class SqlFormatPreProcessor : PreFormatProcessor { } } + private fun getNewLineLeftParenString( + prevElement: PsiElement?, + text: String, + ): String = + if (prevElement?.elementType == SqlTypes.BLOCK_COMMENT || + ( + prevElement?.text?.contains(LINE_SEPARATE) == false && + prevElement.prevSibling != null && + prevElement.elementType != SqlTypes.FUNCTION_NAME + ) + ) { + "$LINE_SEPARATE$text" + } else { + text + } + private fun getNewLineString( prevElement: PsiElement?, text: String, diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt index a8bd6e0b..f12ef23b 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt @@ -220,10 +220,18 @@ class SqlBlockGenerator( } } "from" -> { - SqlFromGroupBlock( - child, - sqlBlockFormattingCtx, - ) + if (lastGroupBlock is SqlSubGroupBlock) { + SqlKeywordBlock( + child, + IndentType.ATTACHED, + sqlBlockFormattingCtx, + ) + } else { + SqlFromGroupBlock( + child, + sqlBlockFormattingCtx, + ) + } } "where" -> { SqlWhereGroupBlock( diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlKeywordUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlKeywordUtil.kt index 5fdc0a3e..a6814e14 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlKeywordUtil.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlKeywordUtil.kt @@ -304,7 +304,7 @@ class SqlKeywordUtil { private val SET_LINE_KEYWORDS = mapOf( "into" to setOf("insert"), - "from" to setOf("delete", "distinct"), + "from" to setOf("delete", "distinct", "year"), "distinct" to setOf("select"), "table" to setOf("create", "alter", "rename", "truncate", "drop"), "index" to setOf("create", "alter", "rename", "truncate", "drop"), From 1aa62ee300661e7f0273c3266da1a54f04098001 Mon Sep 17 00:00:00 2001 From: xterao Date: Fri, 22 Aug 2025 10:58:30 +0900 Subject: [PATCH 19/21] Add tests and formatting for function keyword in condition directive --- .../intellij/formatter/SqlFormatterTest.kt | 4 ++ .../formatter/ComparisonOperators_format.sql | 2 +- .../FunctionKeywordInConditionDirective.sql | 21 +++++++++ ...tionKeywordInConditionDirective_format.sql | 45 +++++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 src/test/testData/sql/formatter/FunctionKeywordInConditionDirective.sql create mode 100644 src/test/testData/sql/formatter/FunctionKeywordInConditionDirective_format.sql diff --git a/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt index 913d3d05..d068f59e 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt @@ -258,6 +258,10 @@ class SqlFormatterTest : BasePlatformTestCase() { formatSqlFile("ComparisonOperators.sql", "ComparisonOperators$formatDataPrefix.sql") } + fun testFunctionKeywordInConditionDirectiveFormatter() { + formatSqlFile("FunctionKeywordInConditionDirective.sql", "FunctionKeywordInConditionDirective$formatDataPrefix.sql") + } + private fun formatSqlFile( beforeFile: String, afterFile: String, diff --git a/src/test/testData/sql/formatter/ComparisonOperators_format.sql b/src/test/testData/sql/formatter/ComparisonOperators_format.sql index 14d3306d..e95fff91 100644 --- a/src/test/testData/sql/formatter/ComparisonOperators_format.sql +++ b/src/test/testData/sql/formatter/ComparisonOperators_format.sql @@ -35,7 +35,7 @@ SELECT id -- Range operators date_range @> current_date AS in_range , int_range && int4range(1 - , 10) AS overlaps_range + , 10) AS overlaps_range , -- Geometric operators point <-> point '(0,0)' AS distance_from_origin diff --git a/src/test/testData/sql/formatter/FunctionKeywordInConditionDirective.sql b/src/test/testData/sql/formatter/FunctionKeywordInConditionDirective.sql new file mode 100644 index 00000000..32c1ca22 --- /dev/null +++ b/src/test/testData/sql/formatter/FunctionKeywordInConditionDirective.sql @@ -0,0 +1,21 @@ +-- PostgreSQL +SELECT extract(YEAR FROM e.hire_date) AS hire_year FROM employees e +SELECT extract(MONTH FROM e.hire_date) AS hire_month FROM employees e +-- Oracle +SELECT TO_DATE('2024-01-01', 'YYYY-MM-DD') AS new_year FROM dual +SELECT TO_CHAR(e.hire_date, 'YYYY-MM') AS hire_month FROM employees e +SELECT CURRENT_DATE FROM dual +--MySQL +SELECT YEAR(e.hire_date) AS hire_year FROM employees e +SELECT MONTH(e.hire_date) AS hire_month FROM employees e +SELECT DATE_FORMAT(e.hire_date, '%Y-%m') AS hire_month FROM employees e +-- SQL Server + SELECT DATEPART(YEAR, e.hire_date) AS hire_year FROM employees e +SELECT DATEPART(MONTH, e.hire_date) AS hire_month FROM employees e +SELECT CONVERT(VARCHAR, e.hire_date, 23) FROM employees e +-- SQLite + SELECT strftime('%Y', e.hire_date) AS hire_year FROM employees e +SELECT strftime('%m', e.hire_date) AS hire_month FROM employees e +-- Standard SQL +SELECT CURRENT_TIMESTAMP FROM employees +SELECT CAST(e.salary AS INTEGER) FROM employees e \ No newline at end of file diff --git a/src/test/testData/sql/formatter/FunctionKeywordInConditionDirective_format.sql b/src/test/testData/sql/formatter/FunctionKeywordInConditionDirective_format.sql new file mode 100644 index 00000000..57d1f530 --- /dev/null +++ b/src/test/testData/sql/formatter/FunctionKeywordInConditionDirective_format.sql @@ -0,0 +1,45 @@ +-- PostgreSQL +SELECT extract(YEAR FROM e.hire_date) AS hire_year + FROM employees e +SELECT extract(MONTH FROM e.hire_date) AS hire_month + FROM employees e +-- Oracle +SELECT TO_DATE('2024-01-01' + , 'YYYY-MM-DD') AS new_year + FROM dual +SELECT TO_CHAR(e.hire_date + , 'YYYY-MM') AS hire_month + FROM employees e +SELECT CURRENT_DATE + FROM dual +--MySQL +SELECT YEAR(e.hire_date) AS hire_year + FROM employees e +SELECT MONTH(e.hire_date) AS hire_month + FROM employees e +SELECT DATE_FORMAT(e.hire_date + , '%Y-%m') AS hire_month + FROM employees e +-- SQL Server +SELECT DATEPART(YEAR + , e.hire_date) AS hire_year + FROM employees e +SELECT DATEPART(MONTH + , e.hire_date) AS hire_month + FROM employees e +SELECT CONVERT(VARCHAR + , e.hire_date + , 23) + FROM employees e +-- SQLite +SELECT strftime('%Y' + , e.hire_date) AS hire_year + FROM employees e +SELECT strftime('%m' + , e.hire_date) AS hire_month + FROM employees e +-- Standard SQL +SELECT CURRENT_TIMESTAMP + FROM employees +SELECT CAST(e.salary AS INTEGER) + FROM employees e From 10ea2838d2460417633cadf29a0c0958fa32ca6f Mon Sep 17 00:00:00 2001 From: xterao Date: Fri, 22 Aug 2025 11:47:22 +0900 Subject: [PATCH 20/21] Remove commented-out code and simplify child text length calculation in SQL formatting --- .../domaframework/doma/intellij/formatter/block/SqlBlock.kt | 2 +- .../formatter/block/group/subgroup/SqlFunctionParamBlock.kt | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt index e6aa4ee1..50e1312d 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt @@ -76,7 +76,7 @@ open class SqlBlock( private val SPACING_ONE_NO_KEEP = Spacing.createSpacing(1, 1, 0, false, 0) } - fun getChildrenTextLen(): Int = childBlocks.sumOf { child -> calculateChildTextLength(child).plus(1) } + fun getChildrenTextLen(): Int = childBlocks.sumOf { child -> calculateChildTextLength(child) } private fun calculateChildTextLength(child: SqlBlock): Int { val nonCommentChildren = child.childBlocks.filterNot { it is SqlDefaultCommentBlock } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlFunctionParamBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlFunctionParamBlock.kt index 72f123bb..4843d7a1 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlFunctionParamBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlFunctionParamBlock.kt @@ -106,12 +106,6 @@ class SqlFunctionParamBlock( val consecutiveSymbolCount = calculateConsecutiveSymbolCount(prevChildrenDropLast) val spaces = prevChildrenDropLast.count().minus(consecutiveSymbolCount) - -// parentFunctionName?.let { parent -> -// return parentGroupIndent -// .plus(prevLength) -// } - return prevLength.plus(spaces).plus(parentText.length).plus(parentGroupIndent) } From 638527b8e9c7421eedc63ee14c01223deaca1c37 Mon Sep 17 00:00:00 2001 From: xterao Date: Fri, 22 Aug 2025 11:58:36 +0900 Subject: [PATCH 21/21] Remove duplicate SqlFunctionParamBlock from block class list in SqlCommaBlock --- .../doma/intellij/formatter/block/comma/SqlCommaBlock.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comma/SqlCommaBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comma/SqlCommaBlock.kt index d79b85d4..79129f91 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comma/SqlCommaBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comma/SqlCommaBlock.kt @@ -60,7 +60,6 @@ open class SqlCommaBlock( SqlFunctionParamBlock::class, SqlWithColumnGroupBlock::class, SqlKeywordGroupBlock::class, - SqlFunctionParamBlock::class, SqlElConditionLoopCommentBlock::class, )