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..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 @@ -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 cc091aa7..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,7 +26,12 @@ 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 import org.domaframework.doma.intellij.formatter.block.comment.SqlDefaultCommentBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElBlockCommentBlock @@ -220,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/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 90% 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..79129f91 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 @@ -135,8 +136,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/block/group/subgroup/SqlFunctionParamBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlFunctionParamBlock.kt index e9adf8a2..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 @@ -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,45 @@ 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) + 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/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 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"), 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