diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/psi/PsiPatternUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/psi/PsiPatternUtil.kt index 97c1fa9a..80f233aa 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/psi/PsiPatternUtil.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/psi/PsiPatternUtil.kt @@ -27,6 +27,7 @@ import com.intellij.psi.util.elementType import com.intellij.psi.util.prevLeafs import com.intellij.util.ProcessingContext import org.domaframework.doma.intellij.common.sql.directive.DirectiveCompletion +import org.domaframework.doma.intellij.common.util.StringUtil.SINGLE_SPACE import org.domaframework.doma.intellij.psi.SqlCustomElCommentExpr import org.domaframework.doma.intellij.psi.SqlElClass import org.domaframework.doma.intellij.psi.SqlElIdExpr @@ -135,7 +136,7 @@ object PsiPatternUtil { element: PsiElement, symbol: String, ): String { - val text = originalFile.containingFile?.text ?: " " + val text = originalFile.containingFile?.text ?: SINGLE_SPACE val offset = element.textOffset val builder = StringBuilder() for (i in offset - 1 downTo 0) { diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/CleanElementText.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/CleanElementText.kt index 6a26bae1..8898121f 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/CleanElementText.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/CleanElementText.kt @@ -16,6 +16,7 @@ package org.domaframework.doma.intellij.common.sql import org.domaframework.doma.intellij.common.util.StringUtil +import org.domaframework.doma.intellij.common.util.StringUtil.SINGLE_SPACE /** * Exclude extra strings and block symbols added by IntelliJ operations a @@ -29,5 +30,5 @@ fun cleanString(str: String): String { // TODO: Temporary support when using operators. // Remove the "== a" element because it is attached to the end. // Make it possible to obtain the equilateral elements of the left side individually. - .substringBefore(" ") + .substringBefore(SINGLE_SPACE) } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/StaticDirectiveHandler.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/StaticDirectiveHandler.kt index a0f0c9a0..ec5eb7a9 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/StaticDirectiveHandler.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/StaticDirectiveHandler.kt @@ -25,6 +25,7 @@ import org.domaframework.doma.intellij.common.psi.PsiPatternUtil import org.domaframework.doma.intellij.common.sql.directive.collector.FunctionCallCollector import org.domaframework.doma.intellij.common.sql.directive.collector.StaticClassPackageCollector import org.domaframework.doma.intellij.common.sql.directive.collector.StaticPropertyCollector +import org.domaframework.doma.intellij.common.util.StringUtil.SINGLE_SPACE import org.domaframework.doma.intellij.psi.SqlElClass import org.domaframework.doma.intellij.psi.SqlElStaticFieldAccessExpr import org.domaframework.doma.intellij.psi.SqlTypes @@ -117,7 +118,7 @@ class StaticDirectiveHandler( val prev = PsiTreeUtil.prevLeaf(element, true) val staticFieldAccess = PsiTreeUtil.getParentOfType(prev, SqlElStaticFieldAccessExpr::class.java) - val sqlElClassWords = PsiPatternUtil.getBindSearchWord(element.containingFile, element, " ") + val sqlElClassWords = PsiPatternUtil.getBindSearchWord(element.containingFile, element, SINGLE_SPACE) return ( staticFieldAccess != null && staticFieldAccess.elIdExprList.isEmpty() ) || @@ -141,7 +142,7 @@ class StaticDirectiveHandler( .getChildOfType(prev, SqlElClass::class.java) ?: PsiTreeUtil.getChildOfType(PsiTreeUtil.prevLeaf(element)?.parent, SqlElClass::class.java) - val sqlElClassWords = PsiPatternUtil.getBindSearchWord(element.containingFile, element, " ") + val sqlElClassWords = PsiPatternUtil.getBindSearchWord(element.containingFile, element, SINGLE_SPACE) val sqlElClassName = PsiTreeUtil.getChildrenOfTypeAsList(clazzRef, PsiElement::class.java).joinToString("") { it.text } val fqdn = if (sqlElClassName.isNotEmpty()) sqlElClassName else sqlElClassWords.replace("@", "") diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/util/StringUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/util/StringUtil.kt index bd4499d1..4c82b9ca 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/util/StringUtil.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/util/StringUtil.kt @@ -17,6 +17,7 @@ package org.domaframework.doma.intellij.common.util object StringUtil { const val LINE_SEPARATE: String = "\n" + const val SINGLE_SPACE: String = " " fun getSqlElClassText(text: String): String = text diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/util/TypeUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/util/TypeUtil.kt index 57867f0c..98482bfe 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/util/TypeUtil.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/util/TypeUtil.kt @@ -20,6 +20,7 @@ import com.intellij.openapi.project.Project import com.intellij.psi.PsiClassType import com.intellij.psi.PsiType import org.domaframework.doma.intellij.common.psi.PsiTypeChecker +import org.domaframework.doma.intellij.common.util.StringUtil.SINGLE_SPACE import org.domaframework.doma.intellij.extension.getJavaClazz import org.domaframework.doma.intellij.extension.psi.getClassAnnotation import org.domaframework.doma.intellij.extension.psi.isDomain @@ -79,13 +80,13 @@ object TypeUtil { * Checks if the given type is a valid Map. */ fun isValidMapType(type: PsiType?): Boolean { - val canonical = type?.canonicalText?.replace(" ", "") ?: return false + val canonical = type?.canonicalText?.replace(SINGLE_SPACE, "") ?: return false val expected = DomaClassName.MAP .getGenericParamCanonicalText( DomaClassName.STRING.className, DomaClassName.OBJECT.className, - ).replace(" ", "") + ).replace(SINGLE_SPACE, "") return canonical == expected } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt b/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt index c8b9a1e4..89e26105 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt @@ -43,6 +43,7 @@ import org.domaframework.doma.intellij.common.util.ForDirectiveUtil import org.domaframework.doma.intellij.common.util.PluginLoggerUtil import org.domaframework.doma.intellij.common.util.SqlCompletionUtil.createMethodLookupElement import org.domaframework.doma.intellij.common.util.StringUtil +import org.domaframework.doma.intellij.common.util.StringUtil.SINGLE_SPACE import org.domaframework.doma.intellij.common.validation.result.ValidationCompleteResult import org.domaframework.doma.intellij.contributor.sql.processor.SqlCompletionDirectiveBlockProcessor import org.domaframework.doma.intellij.contributor.sql.processor.SqlCompletionOtherBlockProcessor @@ -207,7 +208,7 @@ class SqlParameterCompletionProvider : CompletionProvider( val fqdn = StringUtil.getSqlElClassText( PsiPatternUtil - .getBindSearchWord(originalFile, top, " "), + .getBindSearchWord(originalFile, top, SINGLE_SPACE), ) topElementType = getElementTypeByPrevSqlElClassWords(project, fqdn, topText) } 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 4307e22b..ee5a00a2 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 @@ -66,7 +66,7 @@ import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext import org.domaframework.doma.intellij.formatter.util.SqlBlockGenerator import org.domaframework.doma.intellij.psi.SqlTypes -class SqlFileBlock( +open class SqlFileBlock( node: ASTNode, wrap: Wrap?, alignment: Alignment?, @@ -221,7 +221,7 @@ class SqlFileBlock( return if (lastGroup is SqlWithCommonTableGroupBlock) { SqlWithCommonTableGroupBlock(child, defaultFormatCtx) } else { - blockUtil.getBlockCommentBlock(child, createBlockCommentSpacingBuilder()) + blockUtil.getBlockCommentBlock(child, createBlockDirectiveCommentSpacingBuilder()) } } @@ -416,15 +416,11 @@ class SqlFileBlock( } /** - * Creates a spacing builder specifically for block comments. + * Creates a spacing builder specifically for directive block comments. */ - private fun createBlockCommentSpacingBuilder(): SqlCustomSpacingBuilder = + protected fun createBlockDirectiveCommentSpacingBuilder(): SqlCustomSpacingBuilder = SqlCustomSpacingBuilder() .withSpacing( - SqlTypes.BLOCK_COMMENT_START, - SqlTypes.BLOCK_COMMENT_CONTENT, - Spacing.createSpacing(0, 0, 0, true, 0), - ).withSpacing( SqlTypes.BLOCK_COMMENT_START, SqlTypes.EL_ID_EXPR, Spacing.createSpacing(1, 1, 0, true, 0), @@ -572,10 +568,6 @@ class SqlFileBlock( SqlTypes.EL_STATIC_FIELD_ACCESS_EXPR, SqlTypes.BLOCK_COMMENT_END, Spacing.createSpacing(1, 1, 0, true, 0), - ).withSpacing( - SqlTypes.BLOCK_COMMENT_CONTENT, - SqlTypes.BLOCK_COMMENT_END, - Spacing.createSpacing(0, 0, 0, true, 0), ) /** 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 baac03ab..680360c7 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 @@ -15,19 +15,57 @@ */ 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 import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext +import org.domaframework.doma.intellij.psi.SqlTypes open class SqlBlockCommentBlock( node: ASTNode, - context: SqlBlockFormattingContext, + private val customSpacingBuilder: SqlCustomSpacingBuilder, + private val context: SqlBlockFormattingContext, ) : SqlDefaultCommentBlock( 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 getBlock(child: ASTNode): SqlBlock { + val elementType = child.elementType + return when (elementType) { + SqlTypes.BLOCK_COMMENT_START -> SqlCommentStartBlock(child, context) + SqlTypes.BLOCK_COMMENT_END -> SqlCommentEndBlock(child, context) + SqlTypes.BLOCK_COMMENT_CONTENT -> SqlCommentContentBlock(child, context) + else -> SqlUnknownBlock(child, context) + } + } + override fun isSaveSpace(lastGroup: SqlBlock?): Boolean = PsiTreeUtil.prevLeaf(node.psi)?.text?.contains(StringUtil.LINE_SEPARATE) == true + + override fun getSpacing( + child1: Block?, + child2: Block, + ): Spacing? = + customSpacingBuilder.getCustomSpacing(child1, child2) + ?: SqlCustomSpacingBuilder.normalSpacing } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlCommentContentBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlCommentContentBlock.kt new file mode 100644 index 00000000..05c73539 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlCommentContentBlock.kt @@ -0,0 +1,31 @@ +/* + * 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.comment + +import com.intellij.lang.ASTNode +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext + +class SqlCommentContentBlock( + node: ASTNode, + context: SqlBlockFormattingContext, +) : SqlCommentBlock(node, context) { + override fun isSaveSpace(lastGroup: SqlBlock?): Boolean = false + + override fun createBlockIndentLen(): Int = parentBlock?.indent?.indentLen ?: 1 + + override fun createGroupIndentLen(): Int = indent.indentLen +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlCommentEndBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlCommentEndBlock.kt new file mode 100644 index 00000000..7e639b43 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlCommentEndBlock.kt @@ -0,0 +1,36 @@ +/* + * 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.comment + +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiComment +import com.intellij.psi.util.PsiTreeUtil +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext + +class SqlCommentEndBlock( + node: ASTNode, + context: SqlBlockFormattingContext, +) : SqlCommentSeparateBlock(node, context) { + override fun isSaveSpace(lastGroup: SqlBlock?): Boolean { + parentBlock?.let { parent -> + val contents = + PsiTreeUtil.getChildOfType(parent.node.psi, PsiComment::class.java) + return contents != null + } + return false + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlCommentSeparateBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlCommentSeparateBlock.kt new file mode 100644 index 00000000..94741c0f --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlCommentSeparateBlock.kt @@ -0,0 +1,38 @@ +/* + * 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.comment + +import com.intellij.lang.ASTNode +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext + +abstract class SqlCommentSeparateBlock( + node: ASTNode, + context: SqlBlockFormattingContext, +) : SqlBlock( + node, + context.wrap, + context.alignment, + context.spacingBuilder, + context.enableFormat, + context.formatMode, + ) { + override fun isSaveSpace(lastGroup: SqlBlock?): Boolean = false + + override fun createBlockIndentLen(): Int = parentBlock?.indent?.indentLen ?: 0 + + override fun createGroupIndentLen(): Int = indent.indentLen +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlCommentStartBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlCommentStartBlock.kt new file mode 100644 index 00000000..8a1fd820 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlCommentStartBlock.kt @@ -0,0 +1,27 @@ +/* + * 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.comment + +import com.intellij.lang.ASTNode +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext + +class SqlCommentStartBlock( + node: ASTNode, + context: SqlBlockFormattingContext, +) : SqlCommentSeparateBlock(node, context) { + override fun isSaveSpace(lastGroup: SqlBlock?): Boolean = false +} 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 43ecc6e4..c95e34f7 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 @@ -108,6 +108,10 @@ open class SqlElBlockCommentBlock( createFieldAccessSpacingBuilder(), ) + SqlTypes.BLOCK_COMMENT_START -> SqlCommentStartBlock(child, context) + + SqlTypes.BLOCK_COMMENT_END -> SqlCommentEndBlock(child, context) + SqlTypes.EL_STATIC_FIELD_ACCESS_EXPR -> SqlElStaticFieldAccessBlock( child, @@ -121,7 +125,7 @@ open class SqlElBlockCommentBlock( ) SqlTypes.BLOCK_COMMENT_CONTENT -> - SqlBlockCommentBlock(child, context) + SqlBlockCommentBlock(child, createBlockCommentSpacingBuilder(), context) else -> SqlUnknownBlock(child, context) } @@ -146,6 +150,14 @@ open class SqlElBlockCommentBlock( Spacing.createSpacing(0, 0, 0, false, 0), ) + protected fun createBlockCommentSpacingBuilder(): SqlCustomSpacingBuilder = + SqlCustomSpacingBuilder() + .withSpacing( + SqlTypes.BLOCK_COMMENT_START, + SqlTypes.BLOCK_COMMENT_CONTENT, + Spacing.createSpacing(1, 1, 0, false, 0), + ) + 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 301fd1d1..31d993ff 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 @@ -159,9 +159,6 @@ class SqlElConditionLoopCommentBlock( context, ) - SqlTypes.BLOCK_COMMENT_CONTENT -> - SqlBlockCommentBlock(child, context) - else -> SqlUnknownBlock(child, context) } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPostProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPostProcessor.kt index 66a40f13..90b43a33 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPostProcessor.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPostProcessor.kt @@ -43,7 +43,7 @@ class SqlFormatPostProcessor : SqlPostProcessor() { } val document = getDocument(source) ?: return rangeToReformat - val processedText = processDocumentText(document.text, true) + val processedText = processDocumentText(document.text) if (document.text == processedText) { return rangeToReformat 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 7c6ae9a4..a96d2f70 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 @@ -28,7 +28,8 @@ import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.elementType import org.domaframework.doma.intellij.common.util.InjectionSqlUtil.isInjectedSqlFile import org.domaframework.doma.intellij.common.util.PluginLoggerUtil -import org.domaframework.doma.intellij.common.util.StringUtil +import org.domaframework.doma.intellij.common.util.StringUtil.LINE_SEPARATE +import org.domaframework.doma.intellij.common.util.StringUtil.SINGLE_SPACE import org.domaframework.doma.intellij.formatter.util.CreateQueryType import org.domaframework.doma.intellij.formatter.util.SqlKeywordUtil import org.domaframework.doma.intellij.formatter.visitor.SqlFormatVisitor @@ -139,7 +140,6 @@ class SqlFormatPreProcessor : PreFormatProcessor { document: Document, element: PsiWhiteSpace, ) { - val singleSpace = " " val range = element.textRange val originalText = document.getText(range) val nextElement = element.nextSibling @@ -147,7 +147,7 @@ class SqlFormatPreProcessor : PreFormatProcessor { var newText = "" if (!targetElementTypes.contains(nextElement?.elementType)) { - newText = originalText.replace(originalText, singleSpace) + newText = originalText.replace(originalText, SINGLE_SPACE) } else { newText = if (element.prevSibling == null) { @@ -155,22 +155,22 @@ class SqlFormatPreProcessor : PreFormatProcessor { } else { when (nextElement.elementType) { SqlTypes.LINE_COMMENT -> { - if (nextElementText.startsWith(StringUtil.LINE_SEPARATE)) { - originalText.replace(originalText, singleSpace) - } else if (originalText.contains(StringUtil.LINE_SEPARATE)) { - originalText.replace(Regex("\\s*\\n\\s*"), StringUtil.LINE_SEPARATE) + if (nextElementText.startsWith(LINE_SEPARATE)) { + originalText.replace(originalText, SINGLE_SPACE) + } else if (originalText.contains(LINE_SEPARATE)) { + originalText.replace(Regex("\\s*\\n\\s*"), LINE_SEPARATE) } else { - originalText.replace(originalText, singleSpace) + originalText.replace(originalText, SINGLE_SPACE) } } else -> { - if (nextElementText.contains(StringUtil.LINE_SEPARATE) == true) { - originalText.replace(originalText, singleSpace) - } else if (originalText.contains(StringUtil.LINE_SEPARATE)) { - originalText.replace(Regex("\\s*\\n\\s*"), StringUtil.LINE_SEPARATE) + if (nextElementText.contains(LINE_SEPARATE) == true) { + originalText.replace(originalText, SINGLE_SPACE) + } else if (originalText.contains(LINE_SEPARATE)) { + originalText.replace(Regex("\\s*\\n\\s*"), LINE_SEPARATE) } else { - originalText.replace(originalText, StringUtil.LINE_SEPARATE) + originalText.replace(originalText, LINE_SEPARATE) } } } @@ -256,10 +256,10 @@ class SqlFormatPreProcessor : PreFormatProcessor { prevElement: PsiElement?, text: String, ): String = - if (prevElement?.text?.contains(StringUtil.LINE_SEPARATE) == false && + if (prevElement?.text?.contains(LINE_SEPARATE) == false && PsiTreeUtil.prevLeaf(prevElement) != null ) { - "${StringUtil.LINE_SEPARATE}$text" + "$LINE_SEPARATE$text" } else { text } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlInjectionPostProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlInjectionPostProcessor.kt index 328e7ba1..cae5ded1 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlInjectionPostProcessor.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlInjectionPostProcessor.kt @@ -46,7 +46,7 @@ class SqlInjectionPostProcessor : SqlPostProcessor() { val visitor = DaoInjectionSqlVisitor(element, project) element.accept(visitor) visitor.processAll { text, skipFinalLineBreak -> - processDocumentText(text, skipFinalLineBreak) + processDocumentText(text) } } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlPostProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlPostProcessor.kt index 618b11f2..96ee044e 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlPostProcessor.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlPostProcessor.kt @@ -36,20 +36,12 @@ abstract class SqlPostProcessor : PostFormatProcessor { settings: CodeStyleSettings, ): TextRange = rangeToReformat - protected fun processDocumentText( - originalText: String, - existsOriginalDocument: Boolean, - ): String { + protected fun processDocumentText(originalText: String): String { val withoutTrailingSpaces = removeTrailingSpaces(originalText) - return ensureProperFileEnding(withoutTrailingSpaces, existsOriginalDocument) + return ensureProperFileEnding(withoutTrailingSpaces) } private fun removeTrailingSpaces(text: String): String = text.replace(trailingSpacesRegex, "$1") - private fun ensureProperFileEnding( - text: String, - isEndSpace: Boolean, - ): String = - text.trimEnd() + - if (isEndSpace) StringUtil.LINE_SEPARATE else "" + private fun ensureProperFileEnding(text: String): String = text.trimEnd() + StringUtil.LINE_SEPARATE } 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 d1f70de6..cdd19875 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 @@ -17,6 +17,7 @@ package org.domaframework.doma.intellij.formatter.util import com.intellij.formatting.Alignment import com.intellij.formatting.FormattingMode +import com.intellij.formatting.Spacing import com.intellij.formatting.SpacingBuilder import com.intellij.formatting.Wrap import com.intellij.lang.ASTNode @@ -72,6 +73,7 @@ import org.domaframework.doma.intellij.formatter.handler.NotQueryGroupHandler import org.domaframework.doma.intellij.formatter.handler.UpdateClauseHandler import org.domaframework.doma.intellij.formatter.handler.WithClauseHandler import org.domaframework.doma.intellij.psi.SqlCustomElCommentExpr +import org.domaframework.doma.intellij.psi.SqlTypes data class SqlBlockFormattingContext( val wrap: Wrap?, @@ -451,7 +453,7 @@ class SqlBlockGenerator( blockCommentSpacingBuilder: SqlCustomSpacingBuilder?, ): SqlCommentBlock { if (PsiTreeUtil.getChildOfType(child.psi, PsiComment::class.java) != null) { - return SqlBlockCommentBlock(child, sqlBlockFormattingCtx) + return SqlBlockCommentBlock(child, createBlockCommentSpacingBuilder(), sqlBlockFormattingCtx) } if (child.psi is SqlCustomElCommentExpr && (child.psi as SqlCustomElCommentExpr).isConditionOrLoopDirective() @@ -468,4 +470,16 @@ class SqlBlockGenerator( blockCommentSpacingBuilder, ) } + + private fun createBlockCommentSpacingBuilder(): SqlCustomSpacingBuilder = + SqlCustomSpacingBuilder() + .withSpacing( + SqlTypes.BLOCK_COMMENT_START, + SqlTypes.BLOCK_COMMENT_CONTENT, + Spacing.createSpacing(0, 0, 0, true, 0), + ).withSpacing( + SqlTypes.BLOCK_COMMENT_CONTENT, + SqlTypes.BLOCK_COMMENT_END, + Spacing.createSpacing(0, 0, 0, true, 0), + ) } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/visitor/DaoInjectionSqlVisitor.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/visitor/DaoInjectionSqlVisitor.kt index 2b1651e8..175e7287 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/visitor/DaoInjectionSqlVisitor.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/visitor/DaoInjectionSqlVisitor.kt @@ -27,8 +27,6 @@ import com.intellij.psi.codeStyle.CodeStyleManager import org.domaframework.doma.intellij.common.util.InjectionSqlUtil import org.domaframework.doma.intellij.common.util.StringUtil import kotlin.text.isBlank -import kotlin.text.isNotBlank -import kotlin.text.takeWhile /** * Visitor for processing and formatting SQL injections in DAO files. @@ -41,15 +39,15 @@ class DaoInjectionSqlVisitor( private data class FormattingTask( val expression: PsiLiteralExpression, val formattedText: String, - val baseIndent: String, ) companion object { private const val TEMP_FILE_PREFIX = "temp_format" private const val SQL_FILE_EXTENSION = ".sql" - private const val SQL_COMMENT_PATTERN = "^[ \\t]*/\\*" private const val TRIPLE_QUOTE = "\"\"\"" private const val WRITE_COMMAND_NAME = "Format Injected SQL" + private const val BASE_INDENT = "\t\t\t" + private val COMMENT_START_REGEX = Regex("^[ \t]*/[*][ \t]*\\*") } private val formattingTasks = mutableListOf() @@ -59,36 +57,31 @@ class DaoInjectionSqlVisitor( val injected: PsiFile? = InjectionSqlUtil.initInjectionElement(element, project, expression) if (injected != null) { // Format SQL and store the task - val originalSqlText = injected.text - val formattedSql = formatAsTemporarySqlFile(originalSqlText) - // Keep the current top line indent - val baseIndent = getBaseIndent(formattedSql) val originalText = expression.value?.toString() ?: return - - if (formattedSql != originalText) { - formattingTasks.add(FormattingTask(expression, formattedSql, baseIndent)) - } + val removeIndent = removeIndentLines(originalText) + formattingTasks.add(FormattingTask(expression, removeIndent)) } } - /** - * Extracts the base indentation from the first non-blank, non-comment line. - */ - private fun getBaseIndent(string: String): String { - val lines = string.lines() - val commentRegex = Regex(SQL_COMMENT_PATTERN) - - // Skip blank lines and comment lines - val firstContentLineIndex = - lines.indexOfFirst { line -> - line.isNotBlank() && !commentRegex.matches(line) + private fun removeIndentLines(sqlText: String): String { + val lines = sqlText.lines() + + var blockComment = false + val removeIndentLines = + lines.map { line -> + val baseLine = + if (COMMENT_START_REGEX.containsMatchIn(line)) { + blockComment = true + // Exclude spaces between `/*` and the comment content element, + // as IntelliJ IDEA's Java formatter may insert a space there during formatting. + line.replace(COMMENT_START_REGEX, "/**") + } else { + line + } + baseLine.dropWhile { it.isWhitespace() } } - return if (firstContentLineIndex >= 0) { - lines[firstContentLineIndex].takeWhile { it.isWhitespace() } - } else { - "" - } + return removeIndentLines.joinToString(StringUtil.LINE_SEPARATE) } /** @@ -139,10 +132,11 @@ class DaoInjectionSqlVisitor( */ private fun replaceHostStringLiteral( task: FormattingTask, - removeSpace: (String, Boolean) -> String, + sqlPostProcessorProcess: (String, Boolean) -> String, ) { try { - val formattedLiteral = createFormattedLiteral(task, removeSpace) + // Keep the current top line indent + val formattedLiteral = createFormattedLiteral(task, sqlPostProcessorProcess) replaceInDocument(task.expression, formattedLiteral) } catch (_: Exception) { // Silently ignore formatting failures @@ -151,16 +145,19 @@ class DaoInjectionSqlVisitor( private fun createFormattedLiteral( task: FormattingTask, - removeSpace: (String, Boolean) -> String, + sqlPostProcessorProcess: (String, Boolean) -> String, ): String { - val newLiteralText = createFormattedLiteralText(task.formattedText) - val normalizedText = normalizeIndentation(newLiteralText, task.baseIndent) - val cleanedText = removeSpace(normalizedText, false) + // Retrieve the same formatted string as when formatting a regular SQL file. + val formattedSql = formatAsTemporarySqlFile(task.formattedText) + val cleanedText = sqlPostProcessorProcess(formattedSql, false) + // Generate text aligned with the literal element using the formatted string. + val newLiteralText = createFormattedLiteralText(cleanedText) + val normalizedText = normalizeIndentation(newLiteralText) val elementFactory = com.intellij.psi.JavaPsiFacade .getElementFactory(project) - val newLiteral = elementFactory.createExpressionFromText(cleanedText, task.expression) + val newLiteral = elementFactory.createExpressionFromText(normalizedText, task.expression) return newLiteral.text } @@ -193,10 +190,7 @@ class DaoInjectionSqlVisitor( /** * Normalizes indentation by removing base indent and reapplying it consistently. */ - private fun normalizeIndentation( - sqlText: String, - baseIndent: String, - ): String { + private fun normalizeIndentation(sqlText: String): String { val lines = sqlText.lines() if (lines.isEmpty()) return sqlText @@ -207,8 +201,8 @@ class DaoInjectionSqlVisitor( contentLines.map { line -> when { line.isBlank() -> line - line.startsWith(baseIndent) -> baseIndent + line.removePrefix(baseIndent) - else -> baseIndent + line + line.startsWith(BASE_INDENT) -> BASE_INDENT + line.removePrefix(BASE_INDENT) + else -> BASE_INDENT + line } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/processor/TypeCheckerProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/processor/TypeCheckerProcessor.kt index 07a40daa..3fd62d5b 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/processor/TypeCheckerProcessor.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/inspection/dao/processor/TypeCheckerProcessor.kt @@ -24,6 +24,7 @@ import com.intellij.psi.PsiType import org.domaframework.doma.intellij.common.psi.PsiDaoMethod import org.domaframework.doma.intellij.common.psi.PsiTypeChecker import org.domaframework.doma.intellij.common.util.DomaClassName +import org.domaframework.doma.intellij.common.util.StringUtil.SINGLE_SPACE import org.domaframework.doma.intellij.extension.getJavaClazz import org.domaframework.doma.intellij.extension.psi.getSuperType import org.domaframework.doma.intellij.extension.psi.isDomain @@ -99,13 +100,13 @@ abstract class TypeCheckerProcessor( } protected fun checkMapType(paramTypeCanonicalText: String): Boolean { - val mapClassName = paramTypeCanonicalText.replace(" ", "") + val mapClassName = paramTypeCanonicalText.replace(SINGLE_SPACE, "") val mapExpectedType = DomaClassName.MAP .getGenericParamCanonicalText( DomaClassName.STRING.className, DomaClassName.OBJECT.className, - ).replace(" ", "") + ).replace(SINGLE_SPACE, "") return mapClassName == mapExpectedType } }