Skip to content

Commit 979f22b

Browse files
authored
Merge pull request #90 from dmarcotte/comment-formatting
Fix comment formatting corner case
2 parents c49842e + 8e8b0de commit 979f22b

File tree

2 files changed

+190
-25
lines changed

2 files changed

+190
-25
lines changed

src/commonMain/kotlin/org/kson/tools/IndentFormatter.kt

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class IndentFormatter(
8585
// write out anything we've read before this embed block
8686
result.append(prefixWithIndent(lineContent.joinToString(""), nesting.size))
8787
// write out the lines of the embed content, indenting the whole block appropriately
88-
result.append(indentEmbedContent(token, embedContentIndent))
88+
result.append(prefixWithIndent(token.value, embedContentIndent, true))
8989
tokenIndex++
9090
// write the rest of the trailing content from this line
9191
while (tokenIndex < line.size) {
@@ -200,58 +200,84 @@ class IndentFormatter(
200200
/**
201201
* Prefixes the given [content] with an indent computed from [nestingLevel]
202202
*/
203-
private fun prefixWithIndent(content: String, nestingLevel: Int): String {
204-
return indentType.indentString.repeat(nestingLevel) + content
203+
private fun prefixWithIndent(content: String, nestingLevel: Int, keepTrailingIndent: Boolean = false): String {
204+
val indent = indentType.indentString.repeat(nestingLevel)
205+
val lines = content.split('\n')
206+
207+
return lines.mapIndexed { index, line ->
208+
/**
209+
* If [content] ends in a newline, the next line does not belong to it,
210+
* so don't indent it unless our caller demands it
211+
*/
212+
if (!keepTrailingIndent && index == lines.lastIndex && line.isEmpty() && content.endsWith('\n')) {
213+
line
214+
} else {
215+
indent + line
216+
}
217+
}.joinToString("\n")
205218
}
206219

207220
/**
208221
* Split the given [tokens] into lines of tokens corresponding to the lines of the original source that was tokenized.
209-
* Note: trims leading whitespace tokens/lines
222+
* Note: trims leading whitespace tokens/lines, and may gather multiple lines of underlying source in one logical
223+
* "token line" when appropriate
210224
*/
211225
private fun splitTokenLines(tokens: List<Token>): MutableList<List<Token>> {
212-
val lines = mutableListOf<List<Token>>()
226+
val tokenLines = mutableListOf<List<Token>>()
213227
var currentLine = mutableListOf<Token>()
214228

215229
// Skip any leading whitespace tokens
216230
var startIndex = 0
217231
while (startIndex < tokens.size && tokens[startIndex].tokenType == WHITESPACE) {
218232
startIndex++
219233
}
234+
235+
var index = startIndex
236+
while (index < tokens.size) {
237+
val token = tokens[index]
220238

221-
for (token in tokens.subList(startIndex, tokens.size)) {
222239
if (token.tokenType == WHITESPACE && token.lexeme.text.contains('\n')) {
223240
currentLine.add(token.copy(lexeme = token.lexeme.copy(text = "\n")))
224-
lines.add(currentLine)
241+
tokenLines.add(currentLine)
225242

226243
var numAdditionalNewLines = token.lexeme.text.count { it == '\n' } - 1
227244
while (numAdditionalNewLines > 0) {
228-
lines.add(mutableListOf(token.copy(lexeme = token.lexeme.copy(text = "\n"))))
245+
tokenLines.add(mutableListOf(token.copy(lexeme = token.lexeme.copy(text = "\n"))))
229246
numAdditionalNewLines--
230247
}
231248
currentLine = mutableListOf()
249+
250+
/**
251+
* If we see a comment starting a newline, we gather all its lines and prefix the next
252+
* "tokenLine" with it so it gets consistently indented with the content it precedes/comments
253+
*/
254+
val nextToken = tokens.getOrNull(index + 1)
255+
if (nextToken?.tokenType == COMMENT) {
256+
var commentToken: Token = nextToken
257+
while(commentToken.tokenType == COMMENT
258+
|| (commentToken.tokenType == WHITESPACE && commentToken.lexeme.text.contains('\n'))) {
259+
index++
260+
if (commentToken.tokenType == WHITESPACE) {
261+
val numNewlines = commentToken.lexeme.text.count { it == '\n' }
262+
repeat (numNewlines) {
263+
currentLine.add(commentToken.copy(lexeme = commentToken.lexeme.copy(text = "\n")))
264+
}
265+
} else {
266+
currentLine.add(commentToken)
267+
}
268+
commentToken = tokens.getOrNull(index + 1) ?: break
269+
}
270+
}
232271
} else {
233272
currentLine.add(token)
234273
}
274+
275+
index++
235276
}
236277

237-
lines.add(currentLine)
278+
tokenLines.add(currentLine)
238279

239-
return lines
240-
}
241-
242-
/**
243-
* Prepend an appropriate indent to the lines in an [EMBED_CONTENT] token
244-
*/
245-
private fun indentEmbedContent(
246-
token: Token,
247-
nestingLevel: Int
248-
): String {
249-
val indent = indentType.indentString.repeat(nestingLevel)
250-
val lines = token.value.split('\n')
251-
252-
return lines.joinToString("\n") { line ->
253-
indent + line
254-
}
280+
return tokenLines
255281
}
256282
}
257283

src/commonTest/kotlin/org/kson/tools/IndentFormatterTest.kt

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,145 @@ class IndentFormatterTest {
313313
)
314314
}
315315

316+
@Test
317+
fun testCommentAfterKeywordWithHangingValue() {
318+
assertFormatting(
319+
"""
320+
key:
321+
# a value
322+
value
323+
324+
# a property
325+
key2: value
326+
""".trimIndent(),
327+
"""
328+
key:
329+
# a value
330+
value
331+
332+
# a property
333+
key2: value
334+
""".trimIndent()
335+
)
336+
337+
assertFormatting(
338+
"""
339+
key:
340+
- 1 # a value
341+
- 2
342+
""".trimIndent(),
343+
"""
344+
key:
345+
- 1 # a value
346+
- 2
347+
""".trimIndent()
348+
)
349+
350+
assertFormatting(
351+
"""
352+
key:
353+
- 1
354+
# a value
355+
- 2
356+
""".trimIndent(),
357+
"""
358+
key:
359+
- 1
360+
# a value
361+
- 2
362+
""".trimIndent()
363+
)
364+
365+
assertFormatting(
366+
"""
367+
key:
368+
# a value
369+
# a value
370+
# a value
371+
value
372+
373+
# a property
374+
# a property
375+
# a property
376+
key2: value
377+
""".trimIndent(),
378+
"""
379+
key:
380+
# a value
381+
# a value
382+
# a value
383+
value
384+
385+
# a property
386+
# a property
387+
# a property
388+
key2: value
389+
""".trimIndent()
390+
)
391+
}
392+
393+
@Test
394+
fun testCommentsWithNewlines() {
395+
assertFormatting(
396+
"""
397+
x: {
398+
# comment
399+
400+
# comment
401+
y: 12
402+
}
403+
""".trimIndent(),
404+
"""
405+
x: {
406+
# comment
407+
408+
# comment
409+
y: 12
410+
}
411+
""".trimIndent())
412+
}
413+
414+
@Test
415+
fun testCommentsOnNestedObjects() {
416+
assertFormatting(
417+
"""
418+
key:
419+
# a value
420+
# a value
421+
value
422+
423+
key2: {
424+
nested:
425+
# a value
426+
value
427+
428+
# a property
429+
# a property
430+
nested2:
431+
value
432+
}
433+
""".trimIndent(),
434+
"""
435+
key:
436+
# a value
437+
# a value
438+
value
439+
440+
key2: {
441+
nested:
442+
# a value
443+
value
444+
445+
# a property
446+
# a property
447+
nested2:
448+
value
449+
}
450+
""".trimIndent()
451+
)
452+
453+
}
454+
316455
@Test
317456
fun testEmbedBlocksAtDifferentNestingLevels() {
318457
assertFormatting(

0 commit comments

Comments
 (0)