Skip to content

Commit f6a3b85

Browse files
committed
Treat escaped keyword elements as regular word blocks
1 parent 9bbfd16 commit f6a3b85

File tree

8 files changed

+154
-74
lines changed

8 files changed

+154
-74
lines changed

src/main/kotlin/org/domaframework/doma/intellij/common/util/TypeUtil.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,21 @@ import org.domaframework.doma.intellij.extension.psi.isDataType
2727
import org.domaframework.doma.intellij.extension.psi.isDomain
2828
import org.domaframework.doma.intellij.extension.psi.isEntity
2929
import org.domaframework.doma.intellij.formatter.block.SqlBlock
30+
import org.domaframework.doma.intellij.formatter.block.comma.SqlCommaBlock
31+
import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateViewGroupBlock
32+
import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithQuerySubGroupBlock
33+
import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock
3034
import kotlin.reflect.KClass
3135

3236
object TypeUtil {
37+
private val TOP_LEVEL_EXPECTED_TYPES =
38+
listOf(
39+
SqlSubGroupBlock::class,
40+
SqlCommaBlock::class,
41+
SqlWithQuerySubGroupBlock::class,
42+
SqlCreateViewGroupBlock::class,
43+
)
44+
3345
/**
3446
* Unwraps the type parameter from Optional if present, otherwise returns the original type.
3547
*/
@@ -118,6 +130,8 @@ object TypeUtil {
118130
return PsiTypeChecker.isBaseClassType(type) || DomaClassName.isOptionalWrapperType(type.canonicalText)
119131
}
120132

133+
fun isTopLevelExpectedType(childBlock: SqlBlock?): Boolean = isExpectedClassType(TOP_LEVEL_EXPECTED_TYPES, childBlock)
134+
121135
/**
122136
* Determines whether the specified class instance matches.
123137
*/

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

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,6 @@ import com.intellij.formatting.Wrap
2626
import com.intellij.lang.ASTNode
2727
import com.intellij.psi.PsiWhiteSpace
2828
import com.intellij.psi.formatter.common.AbstractBlock
29-
import com.intellij.psi.util.PsiTreeUtil
30-
import com.intellij.psi.util.elementType
31-
import com.intellij.psi.util.nextLeaf
32-
import com.intellij.psi.util.nextLeafs
3329
import org.domaframework.doma.intellij.common.util.TypeUtil
3430
import org.domaframework.doma.intellij.formatter.block.comma.SqlCommaBlock
3531
import org.domaframework.doma.intellij.formatter.block.comment.SqlCommentBlock
@@ -62,7 +58,6 @@ import org.domaframework.doma.intellij.formatter.block.other.SqlEscapeBlock
6258
import org.domaframework.doma.intellij.formatter.block.other.SqlOtherBlock
6359
import org.domaframework.doma.intellij.formatter.block.word.SqlAliasBlock
6460
import org.domaframework.doma.intellij.formatter.block.word.SqlArrayWordBlock
65-
import org.domaframework.doma.intellij.formatter.block.word.SqlFunctionGroupBlock
6661
import org.domaframework.doma.intellij.formatter.block.word.SqlTableBlock
6762
import org.domaframework.doma.intellij.formatter.block.word.SqlWordBlock
6863
import org.domaframework.doma.intellij.formatter.builder.SqlBlockBuilder
@@ -161,9 +156,16 @@ open class SqlFileBlock(
161156
formatMode,
162157
)
163158
val lastGroup = blockBuilder.getLastGroupTopNodeIndexHistory()
159+
164160
val lastGroupFilteredDirective = blockBuilder.getLastGroupFilterDirective()
165161
return when (child.elementType) {
166162
SqlTypes.KEYWORD -> {
163+
if (blockUtil.hasEscapeBeforeWhiteSpace(blocks.lastOrNull() as? SqlBlock?, child)) {
164+
return SqlWordBlock(
165+
child,
166+
defaultFormatCtx,
167+
)
168+
}
167169
return blockUtil.getKeywordBlock(
168170
child,
169171
blockBuilder.getLastGroupTopNodeIndexHistory(),
@@ -226,15 +228,9 @@ open class SqlFileBlock(
226228
}
227229

228230
SqlTypes.FUNCTION_NAME -> {
229-
val notWhiteSpaceElement =
230-
child.psi.nextLeafs
231-
.takeWhile { it is PsiWhiteSpace }
232-
.lastOrNull()
233-
?.nextLeaf(true)
234-
if (notWhiteSpaceElement?.elementType == SqlTypes.LEFT_PAREN ||
235-
PsiTreeUtil.nextLeaf(child.psi)?.elementType == SqlTypes.LEFT_PAREN
236-
) {
237-
return SqlFunctionGroupBlock(child, defaultFormatCtx)
231+
val block = blockUtil.getFunctionName(child, defaultFormatCtx)
232+
if (block != null) {
233+
return block
238234
}
239235
// If it is not followed by a left parenthesis, treat it as a word block
240236
return if (lastGroup is SqlWithQueryGroupBlock) {

src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/top/SqlTopQueryGroupBlock.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,14 @@ abstract class SqlTopQueryGroupBlock(
9999

100100
return parent.indent.indentLen
101101
}
102+
103+
override fun isSaveSpace(lastGroup: SqlBlock?): Boolean {
104+
if (TypeUtil.isTopLevelExpectedType(lastGroup) &&
105+
lastGroup !is SqlWithQuerySubGroupBlock &&
106+
lastGroup !is SqlCreateViewGroupBlock
107+
) {
108+
return false
109+
}
110+
return super.isSaveSpace(lastGroup)
111+
}
102112
}

src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlBlockRelationBuilder.kt

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,6 @@ class SqlBlockRelationBuilder(
6868
SqlExistsGroupBlock::class,
6969
)
7070

71-
private val TOP_LEVEL_EXPECTED_TYPES =
72-
listOf(
73-
SqlSubGroupBlock::class,
74-
SqlCreateViewGroupBlock::class,
75-
)
76-
7771
private val COLUMN_RAW_EXPECTED_TYPES =
7872
listOf(
7973
SqlColumnRawGroupBlock::class,
@@ -179,7 +173,7 @@ class SqlBlockRelationBuilder(
179173
context: SetParentContext,
180174
) {
181175
val parentBlock =
182-
if (TypeUtil.isExpectedClassType(TOP_LEVEL_EXPECTED_TYPES, lastGroupBlock)) {
176+
if (TypeUtil.isTopLevelExpectedType(lastGroupBlock)) {
183177
lastGroupBlock
184178
} else if (childBlock is SqlUpdateQueryGroupBlock) {
185179
UpdateClauseHandler.getParentGroupBlock(blockBuilder, childBlock)

src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPreProcessor.kt

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import com.intellij.psi.TokenType
2828
import com.intellij.psi.impl.source.codeStyle.PreFormatProcessor
2929
import com.intellij.psi.util.PsiTreeUtil
3030
import com.intellij.psi.util.elementType
31+
import com.intellij.psi.util.prevLeafs
3132
import org.domaframework.doma.intellij.common.util.InjectionSqlUtil.isInjectedSqlFile
3233
import org.domaframework.doma.intellij.common.util.PluginLoggerUtil
3334
import org.domaframework.doma.intellij.common.util.StringUtil.LINE_SEPARATE
@@ -97,45 +98,51 @@ class SqlFormatPreProcessor : PreFormatProcessor {
9798
var index = keywordList.size
9899
var keywordIndex = replaceKeywordList.size
99100

100-
visitor.replaces.asReversed().forEach {
101-
val textRangeStart = it.startOffset
102-
val textRangeEnd = textRangeStart + it.text.length
103-
if (it.elementType != TokenType.WHITE_SPACE) {
101+
visitor.replaces.asReversed().forEach { current ->
102+
val textRangeStart = current.startOffset
103+
val textRangeEnd = textRangeStart + current.text.length
104+
if (current.elementType != TokenType.WHITE_SPACE) {
104105
// Add a newline before any element that needs a newline+indent, without overlapping if there is already a newline
105106
index--
106-
var newKeyword = getUpperText(it)
107-
when (it.elementType) {
107+
var newKeyword = getUpperText(current)
108+
when (current.elementType) {
108109
SqlTypes.KEYWORD -> {
109110
keywordIndex--
110-
newKeyword = getKeywordNewText(it)
111+
// Escape-enclosed keywords are treated as regular words and are not converted to uppercase.
112+
val escapes = current.prevLeafs.filter { it.elementType == SqlTypes.OTHER }.toList()
113+
if (hasEscapeBeforeWhiteSpace(escapes, current.node)) {
114+
newKeyword = current.text
115+
} else {
116+
newKeyword = getKeywordNewText(current)
117+
}
111118
}
112119

113120
SqlTypes.LEFT_PAREN -> {
114-
newKeyword = getNewLineLeftParenString(it.prevSibling, getUpperText(it))
121+
newKeyword = getNewLineLeftParenString(current.prevSibling, getUpperText(current))
115122
}
116123

117124
SqlTypes.RIGHT_PAREN -> {
118125
newKeyword =
119-
getRightPatternNewText(it)
126+
getRightPatternNewText(current)
120127
}
121128

122129
SqlTypes.WORD, SqlTypes.FUNCTION_NAME -> {
123-
newKeyword = getWordNewText(it, newKeyword)
130+
newKeyword = getWordNewText(current, newKeyword)
124131
}
125132

126133
SqlTypes.COMMA, SqlTypes.OTHER -> {
127-
newKeyword = getNewLineString(it.prevSibling, getUpperText(it))
134+
newKeyword = getNewLineString(current.prevSibling, getUpperText(current))
128135
}
129136

130137
SqlTypes.BLOCK_COMMENT_START -> {
131138
newKeyword =
132-
getNewLineString(PsiTreeUtil.prevLeaf(it), getUpperText(it))
139+
getNewLineString(PsiTreeUtil.prevLeaf(current), getUpperText(current))
133140
}
134141
}
135142
document.deleteString(textRangeStart, textRangeEnd)
136143
document.insertString(textRangeStart, newKeyword)
137144
} else {
138-
removeSpacesAroundNewline(document, it as PsiWhiteSpace)
145+
removeSpacesAroundNewline(document, current as PsiWhiteSpace)
139146
}
140147
}
141148

@@ -188,6 +195,29 @@ class SqlFormatPreProcessor : PreFormatProcessor {
188195
document.replaceString(range.startOffset, range.endOffset, newText)
189196
}
190197

198+
private fun hasEscapeBeforeWhiteSpace(
199+
prevBlocks: List<PsiElement>,
200+
start: ASTNode,
201+
): Boolean {
202+
val countEscape = prevBlocks.filter { it.elementType == SqlTypes.OTHER && it.text in listOf("\"", "[", "`", "]") }
203+
if (countEscape.count() % 2 == 0) {
204+
return false
205+
}
206+
var node = start.treeNext
207+
while (node != null) {
208+
if (node.elementType == SqlTypes.OTHER &&
209+
listOf("\"", "`", "]").contains(node.text)
210+
) {
211+
return true
212+
}
213+
if (node.psi is PsiWhiteSpace) {
214+
return false
215+
}
216+
node = node.treeNext
217+
}
218+
return false
219+
}
220+
191221
/**
192222
* Checks for special case keyword elements and specific combinations of keywords with line breaks and capitalization only
193223
*/

src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ import com.intellij.formatting.SpacingBuilder
2222
import com.intellij.formatting.Wrap
2323
import com.intellij.lang.ASTNode
2424
import com.intellij.psi.PsiComment
25+
import com.intellij.psi.PsiWhiteSpace
2526
import com.intellij.psi.util.PsiTreeUtil
27+
import com.intellij.psi.util.elementType
28+
import com.intellij.psi.util.nextLeaf
29+
import com.intellij.psi.util.nextLeafs
2630
import org.domaframework.doma.intellij.extension.expr.isConditionOrLoopDirective
2731
import org.domaframework.doma.intellij.formatter.block.SqlBlock
2832
import org.domaframework.doma.intellij.formatter.block.SqlKeywordBlock
@@ -57,6 +61,7 @@ import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWit
5761
import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlDataTypeParamBlock
5862
import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock
5963
import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubQueryGroupBlock
64+
import org.domaframework.doma.intellij.formatter.block.other.SqlEscapeBlock
6065
import org.domaframework.doma.intellij.formatter.block.word.SqlAliasBlock
6166
import org.domaframework.doma.intellij.formatter.block.word.SqlArrayWordBlock
6267
import org.domaframework.doma.intellij.formatter.block.word.SqlFunctionGroupBlock
@@ -102,6 +107,7 @@ class SqlBlockGenerator(
102107
lastGroupBlock: SqlBlock?,
103108
): SqlBlock {
104109
val keywordText = child.text.lowercase()
110+
105111
val indentLevel = SqlKeywordUtil.getIndentType(keywordText)
106112

107113
if (indentLevel.isNewLineGroup()) {
@@ -384,6 +390,47 @@ class SqlBlockGenerator(
384390
return CommaRawClauseHandler.getCommaBlock(lastGroup, child, sqlBlockFormattingCtx)
385391
}
386392

393+
fun hasEscapeBeforeWhiteSpace(
394+
lastEscapeBlock: SqlBlock?,
395+
start: ASTNode,
396+
): Boolean {
397+
if (lastEscapeBlock == null ||
398+
(lastEscapeBlock as? SqlEscapeBlock)?.isEndEscape == true
399+
) {
400+
return false
401+
}
402+
var node = start.treeNext
403+
while (node != null) {
404+
if (node.elementType == SqlTypes.OTHER &&
405+
listOf("\"", "`", "]").contains(node.text)
406+
) {
407+
return true
408+
}
409+
if (node.psi is PsiWhiteSpace) {
410+
return false
411+
}
412+
node = node.treeNext
413+
}
414+
return false
415+
}
416+
417+
fun getFunctionName(
418+
child: ASTNode,
419+
defaultFormatCtx: SqlBlockFormattingContext,
420+
): SqlBlock? {
421+
val notWhiteSpaceElement =
422+
child.psi.nextLeafs
423+
.takeWhile { it is PsiWhiteSpace }
424+
.lastOrNull()
425+
?.nextLeaf(true)
426+
if (notWhiteSpaceElement?.elementType == SqlTypes.LEFT_PAREN ||
427+
PsiTreeUtil.nextLeaf(child.psi)?.elementType == SqlTypes.LEFT_PAREN
428+
) {
429+
return SqlFunctionGroupBlock(child, defaultFormatCtx)
430+
}
431+
return null
432+
}
433+
387434
fun getWordBlock(
388435
lastGroup: SqlBlock?,
389436
child: ASTNode,
Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,15 @@
1-
SELECT id
2-
, [age]
3-
, /*%if maxAge*/
4-
number + age(/* timestamp1 */'2099-12-31'
5-
, /* timestamp2 */'2099-12-31')
6-
/*%else*/
7-
name
8-
,
9-
/*%end*/" age"
10-
FROM employee
1+
WITH [abs] AS (
2+
insert into employee(id, name, "left")
3+
values (1, 'name', select "age" from user where id = 1))
4+
5+
SELECT id, [age]
6+
, "age"
7+
FROM "order"o, `age`
118
WHERE (/*%for age : ages */
12-
" age"
13-
= /* age */30
14-
/*%if age_has_next *//*# "and" *//*%else */
15-
/*%end */
16-
/*%end */)
17-
AND (/*%for age : ages */
18-
abs = /* abs */30
19-
AND age >= 20
20-
/*%if age_has_next */
21-
/*# "or" */
22-
/*%else */
23-
/*%end */
9+
"age" = /* age */30
10+
AND o."Left" = /* left */30
11+
/*%if age_has_next */
12+
/*# "and" */
13+
/*%else */
14+
/*%end */
2415
/*%end */)
Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,24 @@
1+
WITH [abs] AS (
2+
INSERT INTO employee
3+
(id
4+
, name
5+
, "left")
6+
VALUES ( 1
7+
, 'name'
8+
, SELECT "age"
9+
FROM user
10+
WHERE id = 1 )
11+
)
112
SELECT id
213
, [age]
3-
, /*%if maxAge*/
4-
number + age(/* timestamp1 */'2099-12-31'
5-
, /* timestamp2 */'2099-12-31')
6-
/*%else*/
7-
name
8-
,
9-
/*%end*/
10-
"age"
11-
FROM employee
14+
, "age"
15+
FROM "order" o
16+
, `age`
1217
WHERE (/*%for age : ages */
1318
"age" = /* age */30
14-
/*%if age_has_next */
15-
/*# "and" */
16-
/*%else */
17-
/*%end */
18-
/*%end */)
19-
AND (/*%for age : ages */
20-
abs = /* abs */30
21-
AND age >= 20
19+
AND o."Left" = /* left */30
2220
/*%if age_has_next */
23-
/*# "or" */
21+
/*# "and" */
2422
/*%else */
2523
/*%end */
2624
/*%end */)

0 commit comments

Comments
 (0)