Skip to content

Commit 932a1fc

Browse files
committed
Make sure to end the file with a space and a newline
1 parent bf73cb1 commit 932a1fc

File tree

1 file changed

+162
-95
lines changed

1 file changed

+162
-95
lines changed

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

Lines changed: 162 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.domaframework.doma.intellij.formatter
1717

1818
import com.intellij.lang.ASTNode
19+
import com.intellij.openapi.editor.Document
1920
import com.intellij.openapi.util.TextRange
2021
import com.intellij.psi.PsiDocumentManager
2122
import com.intellij.psi.PsiElement
@@ -26,6 +27,7 @@ import com.intellij.psi.TokenType
2627
import com.intellij.psi.impl.source.codeStyle.PreFormatProcessor
2728
import com.intellij.psi.util.PsiTreeUtil
2829
import com.intellij.psi.util.elementType
30+
import com.intellij.psi.util.endOffset
2931
import com.intellij.psi.util.prevLeafs
3032
import org.domaframework.doma.intellij.psi.SqlBlockComment
3133
import org.domaframework.doma.intellij.psi.SqlTypes
@@ -55,11 +57,17 @@ class SqlFormatPreProcessor : PreFormatProcessor {
5557
var index = keywordList.size
5658
var keywordIndex = replaceKeywordList.size
5759

60+
// Add a newline to the end of the file
5861
val documentLastElement = visitor.lastElement
5962
val documentLastRange = visitor.lastElement?.textRange
60-
if (documentLastRange != null && documentLastRange.endOffset <= rangeToReformat.endOffset) {
61-
if (documentLastElement !is PsiWhiteSpace || documentLastElement.text?.contains("\n") == false) {
62-
document.insertString(documentLastRange.endOffset, "\n")
63+
if (documentLastElement != null && documentLastRange != null && documentLastRange.endOffset <= rangeToReformat.endOffset) {
64+
if (documentLastElement !is PsiWhiteSpace) {
65+
val textEnd = documentLastElement.endOffset
66+
document.insertString(textEnd, " \n")
67+
} else {
68+
val textStart = documentLastElement.startOffset
69+
val textEnd = documentLastElement.endOffset
70+
document.replaceString(textStart, textEnd, " \n")
6371
}
6472
}
6573

@@ -68,112 +76,71 @@ class SqlFormatPreProcessor : PreFormatProcessor {
6876
val textRangeStart = it.startOffset
6977
val textRangeEnd = textRangeStart + it.text.length
7078
if (it.elementType != TokenType.WHITE_SPACE) {
79+
// Add a newline before any element that needs a newline+indent, without overlapping if there is already a newline
7180
index--
7281
var newKeyword = getUpperText(it)
7382
when (it.elementType) {
7483
SqlTypes.KEYWORD -> {
7584
keywordIndex--
76-
newKeyword =
77-
if (checkKeywordPrevElement(index, it) &&
78-
SqlKeywordUtil.getIndentType(it.text).isNewLineGroup() ||
79-
it.text.lowercase() == "end" ||
80-
(
81-
it.text.lowercase() == "as" &&
82-
createQueryType == CreateQueryType.VIEW
83-
)
84-
) {
85-
if (SqlKeywordUtil.isSetLineKeyword(
86-
it.text,
87-
keywordList[index - 1].text,
88-
)
89-
) {
90-
getUpperText(it)
91-
} else {
92-
getNewLineString(it)
93-
}
94-
} else {
95-
getUpperText(it)
96-
}
85+
newKeyword = getKeywordNewText(index, it, createQueryType, keywordList)
9786
}
87+
9888
SqlTypes.LEFT_PAREN -> {
9989
newKeyword =
10090
if (createQueryType == CreateQueryType.TABLE) {
101-
getNewLineString(it)
91+
getNewLineString(it.prevSibling, getUpperText(it))
10292
} else {
10393
getUpperText(it)
10494
}
10595
}
96+
10697
SqlTypes.RIGHT_PAREN -> {
107-
val prefixElements =
108-
getElementsBeforeKeyword(it.prevLeafs.toList()) { it.elementType == SqlTypes.LEFT_PAREN }
109-
val containsColumnRaw =
110-
prefixElements.findLast { isColumnDefinedRawElementType(it) } != null
111-
newKeyword =
112-
if (createQueryType == CreateQueryType.TABLE) {
113-
if (containsColumnRaw) {
114-
getNewLineString(it)
115-
} else {
116-
getUpperText(it)
117-
}
118-
} else {
119-
getUpperText(it)
120-
}
98+
newKeyword = getRightPatternNewText(it, newKeyword, createQueryType)
12199
}
122100

123101
SqlTypes.WORD -> {
124-
var prev = it.prevSibling
125-
var isColumnName = true
126-
while (prev != null && prev.elementType != SqlTypes.LEFT_PAREN && prev.elementType != SqlTypes.COMMA) {
127-
if (prev !is PsiWhiteSpace &&
128-
prev.elementType != SqlTypes.LINE_COMMENT &&
129-
prev.elementType != SqlTypes.BLOCK_COMMENT
130-
) {
131-
isColumnName =
132-
prev.elementType == SqlTypes.COMMA ||
133-
prev.elementType == SqlTypes.LEFT_PAREN
134-
break
135-
}
136-
prev = prev.prevSibling
137-
}
138-
139-
newKeyword =
140-
if (createQueryType == CreateQueryType.TABLE && isColumnName) {
141-
getNewLineString(it)
142-
} else {
143-
getUpperText(it)
144-
}
102+
newKeyword = getWordNewText(it, newKeyword, createQueryType)
145103
}
104+
146105
SqlTypes.COMMA -> {
147-
newKeyword = getNewLineString(it)
106+
newKeyword = getNewLineString(it.prevSibling, getUpperText(it))
148107
}
149108
}
150109
document.deleteString(textRangeStart, textRangeEnd)
151110
document.insertString(textRangeStart, newKeyword)
152111
} else {
153-
if (keywordIndex < replaceKeywordList.size) {
112+
// Remove spaces after newlines to reset indentation
113+
val nextSibling = it.nextSibling
114+
if (nextSibling.elementType == SqlTypes.LINE_COMMENT) {
115+
document.replaceString(textRangeStart, textRangeEnd, "")
116+
} else if (nextSibling.elementType == SqlTypes.BLOCK_COMMENT) {
117+
removeSpacesAroundNewline(document, it.textRange)
118+
} else if (keywordIndex < replaceKeywordList.size) {
154119
val nextElement = replaceKeywordList[keywordIndex]
155-
if ((
156-
SqlKeywordUtil.getIndentType(nextElement.text ?: "").isNewLineGroup() &&
120+
if (isNewLineOnlyCreateTable(nextSibling) && createQueryType == CreateQueryType.TABLE) {
121+
removeSpacesAroundNewline(document, it.textRange)
122+
} else if (isSubGroupFirstElement(nextElement)) {
123+
document.deleteString(textRangeStart, textRangeEnd)
124+
} else {
125+
val isNewLineGroup = SqlKeywordUtil.getIndentType(nextElement.text ?: "").isNewLineGroup()
126+
val isSetLineKeyword =
127+
if (keywordIndex > 0) {
157128
SqlKeywordUtil.isSetLineKeyword(
158-
replaceKeywordList[keywordIndex].text,
129+
nextElement.text,
159130
replaceKeywordList[keywordIndex - 1].text,
160131
)
161-
) ||
162-
(
163-
isNewLineOnlyCreateTable(nextElement) && createQueryType == CreateQueryType.TABLE
164-
)
165-
) {
166-
document.deleteString(textRangeStart, textRangeEnd)
167-
document.insertString(textRangeStart, " ")
168-
} else {
169-
val currentIndent = it.text.substringAfter("\n", "").length
170-
val start = textRangeEnd - currentIndent
171-
document.deleteString(start, textRangeEnd)
132+
} else {
133+
false
134+
}
135+
136+
if (isNewLineGroup && !isSetLineKeyword || keywordList[index].elementType == SqlTypes.COMMA) {
137+
removeSpacesAroundNewline(document, it.textRange)
138+
} else {
139+
document.replaceString(textRangeStart, textRangeEnd, " ")
140+
}
172141
}
173142
} else {
174-
val currentIndent = it.text.substringAfter("\n", "").length
175-
val start = textRangeEnd - currentIndent
176-
document.deleteString(start, textRangeEnd)
143+
removeSpacesAroundNewline(document, it.textRange)
177144
}
178145
}
179146
}
@@ -183,6 +150,103 @@ class SqlFormatPreProcessor : PreFormatProcessor {
183150
return rangeToReformat.grown(visitor.replaces.size)
184151
}
185152

153+
private fun removeSpacesAroundNewline(
154+
document: Document,
155+
range: TextRange,
156+
) {
157+
val originalText = document.getText(range)
158+
val newText = originalText.replace(Regex("\\s*\\n\\s*"), "\n")
159+
document.replaceString(range.startOffset, range.endOffset, newText)
160+
}
161+
162+
/**
163+
* Checks for special case keyword elements and specific combinations of keywords with line breaks and capitalization only
164+
*/
165+
private fun getKeywordNewText(
166+
index: Int,
167+
element: PsiElement,
168+
createQueryType: CreateQueryType,
169+
keywordList: List<PsiElement>,
170+
): String =
171+
if (element.text.lowercase() == "end") {
172+
getNewLineString(element.prevSibling, getUpperText(element))
173+
} else if (isCreateViewAs(element, createQueryType)) {
174+
getNewLineString(element.prevSibling, getUpperText(element))
175+
} else if (isSubGroupFirstElement(element)) {
176+
getUpperText(element)
177+
} else if (SqlKeywordUtil.getIndentType(element.text).isNewLineGroup()) {
178+
if (index > 0 &&
179+
SqlKeywordUtil.isSetLineKeyword(
180+
element.text,
181+
keywordList[index - 1].text,
182+
)
183+
) {
184+
getUpperText(element)
185+
} else {
186+
getNewLineString(element.prevSibling, getUpperText(element))
187+
}
188+
} else {
189+
getUpperText(element)
190+
}
191+
192+
private fun getRightPatternNewText(
193+
element: PsiElement,
194+
newKeyword: String,
195+
createQueryType: CreateQueryType,
196+
): String {
197+
var newKeyword1 = newKeyword
198+
val prefixElements =
199+
getElementsBeforeKeyword(element.prevLeafs.toList()) { it.elementType == SqlTypes.LEFT_PAREN }
200+
val containsColumnRaw =
201+
prefixElements.findLast { isColumnDefinedRawElementType(it) } != null
202+
newKeyword1 =
203+
if (createQueryType == CreateQueryType.TABLE) {
204+
if (containsColumnRaw) {
205+
getNewLineString(element.prevSibling, getUpperText(element))
206+
} else {
207+
getUpperText(element)
208+
}
209+
} else {
210+
getUpperText(element)
211+
}
212+
return newKeyword1
213+
}
214+
215+
private fun getWordNewText(
216+
element: PsiElement,
217+
newKeyword: String,
218+
createQueryType: CreateQueryType,
219+
): String {
220+
newKeyword
221+
var prev = element.prevSibling
222+
var isColumnName = true
223+
while (prev != null && prev.elementType != SqlTypes.LEFT_PAREN && prev.elementType != SqlTypes.COMMA) {
224+
if (prev !is PsiWhiteSpace &&
225+
prev.elementType != SqlTypes.LINE_COMMENT &&
226+
prev.elementType != SqlTypes.BLOCK_COMMENT
227+
) {
228+
isColumnName =
229+
prev.elementType == SqlTypes.COMMA ||
230+
prev.elementType == SqlTypes.LEFT_PAREN
231+
break
232+
}
233+
prev = prev.prevSibling
234+
}
235+
236+
return if (createQueryType == CreateQueryType.TABLE && isColumnName) {
237+
getNewLineString(element.prevSibling, getUpperText(element))
238+
} else {
239+
getUpperText(element)
240+
}
241+
}
242+
243+
private fun isCreateViewAs(
244+
element: PsiElement,
245+
createQueryType: CreateQueryType,
246+
): Boolean =
247+
element.text.lowercase() == "as" &&
248+
createQueryType == CreateQueryType.VIEW
249+
186250
private fun isColumnDefinedRawElementType(element: PsiElement): Boolean =
187251
element.elementType == SqlTypes.WORD ||
188252
element.elementType == SqlTypes.KEYWORD ||
@@ -214,6 +278,9 @@ class SqlFormatPreProcessor : PreFormatProcessor {
214278
return attachmentKeywordType
215279
}
216280

281+
/**
282+
* The column definition elements of Create Table, "(", "WORD", and ")" must be line breaks
283+
*/
217284
private fun isNewLineOnlyCreateTable(nextElement: PsiElement): Boolean =
218285
nextElement.elementType == SqlTypes.LEFT_PAREN ||
219286
nextElement.elementType == SqlTypes.RIGHT_PAREN ||
@@ -224,11 +291,14 @@ class SqlFormatPreProcessor : PreFormatProcessor {
224291
isLeft: (T) -> Boolean,
225292
): List<T> = elements.takeWhile { element -> !isLeft(element) }
226293

227-
private fun getNewLineString(element: PsiElement): String =
228-
if (element.prevSibling?.text?.contains("\n") == false) {
229-
"\n${getUpperText(element)}"
294+
private fun getNewLineString(
295+
prevElement: PsiElement?,
296+
text: String,
297+
): String =
298+
if (prevElement?.text?.contains("\n") == false) {
299+
"\n$text"
230300
} else {
231-
getUpperText(element)
301+
text
232302
}
233303

234304
private fun getUpperText(element: PsiElement): String =
@@ -238,13 +308,9 @@ class SqlFormatPreProcessor : PreFormatProcessor {
238308
element.text
239309
}
240310

241-
private fun checkKeywordPrevElement(
242-
index: Int,
243-
element: PsiElement,
244-
): Boolean =
245-
index > 0 &&
246-
element.prevSibling != null &&
247-
element.prevSibling.elementType != SqlTypes.LEFT_PAREN
311+
private fun isSubGroupFirstElement(element: PsiElement): Boolean =
312+
getElementsBeforeKeyword(element.prevLeafs.toList()) { it.elementType == SqlTypes.LEFT_PAREN }
313+
.findLast { it !is PsiWhiteSpace } == null
248314
}
249315

250316
private class SqlFormatVisitor : PsiRecursiveElementVisitor() {
@@ -259,10 +325,7 @@ private class SqlFormatVisitor : PsiRecursiveElementVisitor() {
259325

260326
if (PsiTreeUtil.getParentOfType(element, SqlBlockComment::class.java) == null) {
261327
when (element.elementType) {
262-
SqlTypes.KEYWORD, SqlTypes.COMMA -> {
263-
replaces.add(element)
264-
}
265-
SqlTypes.LEFT_PAREN, SqlTypes.RIGHT_PAREN, SqlTypes.WORD -> {
328+
SqlTypes.KEYWORD, SqlTypes.COMMA, SqlTypes.LEFT_PAREN, SqlTypes.RIGHT_PAREN, SqlTypes.WORD -> {
266329
replaces.add(element)
267330
}
268331
}
@@ -273,7 +336,11 @@ private class SqlFormatVisitor : PsiRecursiveElementVisitor() {
273336
super.visitWhiteSpace(space)
274337
val nextElement = space.nextSibling
275338
if (nextElement != null &&
276-
space.text.contains("\n")
339+
(
340+
space.text.contains("\n") ||
341+
nextElement.elementType == SqlTypes.LINE_COMMENT ||
342+
nextElement.elementType == SqlTypes.BLOCK_COMMENT
343+
)
277344
) {
278345
replaces.add(space)
279346
}

0 commit comments

Comments
 (0)