better formatting for multi-line comments
This commit is contained in:
parent
6d8b60fcfa
commit
22f6c149db
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -34,8 +34,20 @@ object LyngFormatter {
|
|||||||
var parenBalance = 0
|
var parenBalance = 0
|
||||||
var bracketBalance = 0
|
var bracketBalance = 0
|
||||||
var prevBracketContinuation = false
|
var prevBracketContinuation = false
|
||||||
|
var inBlockComment = false
|
||||||
// We don't keep per-"[" base alignment; continuation rules define alignment.
|
// We don't keep per-"[" base alignment; continuation rules define alignment.
|
||||||
|
|
||||||
|
fun updateBalances(ch: Char) {
|
||||||
|
when (ch) {
|
||||||
|
'{' -> blockLevel++
|
||||||
|
'}' -> if (blockLevel > 0) blockLevel--
|
||||||
|
'(' -> parenBalance++
|
||||||
|
')' -> if (parenBalance > 0) parenBalance--
|
||||||
|
'[' -> bracketBalance++
|
||||||
|
']' -> if (bracketBalance > 0) bracketBalance--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun codePart(s: String): String {
|
fun codePart(s: String): String {
|
||||||
val idx = s.indexOf("//")
|
val idx = s.indexOf("//")
|
||||||
return if (idx >= 0) s.substring(0, idx) else s
|
return if (idx >= 0) s.substring(0, idx) else s
|
||||||
@ -58,12 +70,19 @@ object LyngFormatter {
|
|||||||
|
|
||||||
for ((i, rawLine) in lines.withIndex()) {
|
for ((i, rawLine) in lines.withIndex()) {
|
||||||
val line = rawLine
|
val line = rawLine
|
||||||
|
val trimmedLine = line.trim()
|
||||||
val code = codePart(line)
|
val code = codePart(line)
|
||||||
val trimmedStart = code.dropWhile { it == ' ' || it == '\t' }
|
val trimmedStart = code.dropWhile { it == ' ' || it == '\t' }
|
||||||
|
|
||||||
// Compute effective indent level for this line
|
// Compute effective indent level for this line
|
||||||
var effectiveLevel = blockLevel
|
var effectiveLevel = blockLevel
|
||||||
if (trimmedStart.startsWith("}")) effectiveLevel = (effectiveLevel - 1).coerceAtLeast(0)
|
if (inBlockComment) {
|
||||||
|
if (!trimmedLine.startsWith("*/")) {
|
||||||
|
effectiveLevel += 1
|
||||||
|
}
|
||||||
|
} else if (trimmedStart.startsWith("}")) {
|
||||||
|
effectiveLevel = (effectiveLevel - 1).coerceAtLeast(0)
|
||||||
|
}
|
||||||
// else/catch/finally should align with the parent block level; no extra dedent here,
|
// else/catch/finally should align with the parent block level; no extra dedent here,
|
||||||
// because the preceding '}' has already reduced [blockLevel] appropriately.
|
// because the preceding '}' has already reduced [blockLevel] appropriately.
|
||||||
|
|
||||||
@ -91,6 +110,7 @@ object LyngFormatter {
|
|||||||
bracketBalance > 0 && firstChar != ']' -> config.continuationIndentSize
|
bracketBalance > 0 && firstChar != ']' -> config.continuationIndentSize
|
||||||
// While inside parentheses, continuation applies scaled by nesting level
|
// While inside parentheses, continuation applies scaled by nesting level
|
||||||
parenContLevels > 0 -> config.continuationIndentSize * parenContLevels
|
parenContLevels > 0 -> config.continuationIndentSize * parenContLevels
|
||||||
|
trimmedStart.startsWith(".") -> config.indentSize
|
||||||
else -> 0
|
else -> 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,13 +147,33 @@ object LyngFormatter {
|
|||||||
if (i < lines.lastIndex) sb.append('\n')
|
if (i < lines.lastIndex) sb.append('\n')
|
||||||
|
|
||||||
// Update balances using this line's code content
|
// Update balances using this line's code content
|
||||||
for (ch in code) when (ch) {
|
if (!inBlockComment) {
|
||||||
'{' -> blockLevel++
|
val startIdx = code.indexOf("/*")
|
||||||
'}' -> if (blockLevel > 0) blockLevel--
|
if (startIdx >= 0) {
|
||||||
'(' -> parenBalance++
|
val endIdx = code.indexOf("*/", startIdx + 2)
|
||||||
')' -> if (parenBalance > 0) parenBalance--
|
if (endIdx < 0) {
|
||||||
'[' -> bracketBalance++
|
inBlockComment = true
|
||||||
']' -> if (bracketBalance > 0) bracketBalance--
|
// Process code before /*
|
||||||
|
val before = code.substring(0, startIdx)
|
||||||
|
for (ch in before) updateBalances(ch)
|
||||||
|
} else {
|
||||||
|
// Block comment starts and ends on the same line
|
||||||
|
val before = code.substring(0, startIdx)
|
||||||
|
val after = code.substring(endIdx + 2)
|
||||||
|
for (ch in before) updateBalances(ch)
|
||||||
|
for (ch in after) updateBalances(ch)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (ch in code) updateBalances(ch)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val endIdx = line.indexOf("*/")
|
||||||
|
if (endIdx >= 0) {
|
||||||
|
inBlockComment = false
|
||||||
|
val after = line.substring(endIdx + 2)
|
||||||
|
val codeAfter = codePart(after)
|
||||||
|
for (ch in codeAfter) updateBalances(ch)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update awaitingSingleIndent based on current line
|
// Update awaitingSingleIndent based on current line
|
||||||
@ -408,15 +448,16 @@ private fun applyMinimalSpacing(code: String): String {
|
|||||||
s = s.replace(Regex(", \\)"), ",)")
|
s = s.replace(Regex(", \\)"), ",)")
|
||||||
s = s.replace(Regex(", \\]"), ",]")
|
s = s.replace(Regex(", \\]"), ",]")
|
||||||
// Equality/boolean operators: ensure spaces around
|
// Equality/boolean operators: ensure spaces around
|
||||||
s = s.replace(Regex("\\s*(==|!=|<=|>=|&&|\\|\\|)\\s*"), " $1 ")
|
s = s.replace(Regex("\\s*(==|!=|<=|>=|&&|\\|\\||\\+=|-=|\\*=|/=|=>|->)\\s*"), " $1 ")
|
||||||
// Assignment '=' (not part of '==', '!=', '<=', '>=', '=>'): collapse whitespace to single spaces around '='
|
// Assignment '=' (not part of '==', '!=', '<=', '>=', '=>', '+=', '-=', '*=', '/=', '->'): collapse whitespace to single spaces around '='
|
||||||
s = s.replace(Regex("(?<![=!<>])\\s*=\\s*(?![=>])"), " = ")
|
s = s.replace(Regex("(?<![=!<>+\\-*/])\\s*=\\s*(?![=>])"), " = ")
|
||||||
// Multiply/Divide/Mod as binary: spaces around
|
// Multiply/Divide/Mod as binary: spaces around. Avoid '*=', '/='
|
||||||
s = s.replace(Regex("\\s*([*/%])\\s*"), " $1 ")
|
s = s.replace(Regex("(?<![*])\\s*([*/%])\\s*(?![=])"), " $1 ")
|
||||||
// Addition as binary: spaces around '+'. We try not to break '++' and '+=' here.
|
// Addition as binary: spaces around '+'. We try not to break '++' and '+=' here.
|
||||||
s = s.replace(Regex("(?<![+])\\s*\\+\\s*(?![+=])"), " + ")
|
s = s.replace(Regex("(?<![+])\\s*\\+\\s*(?![+=])"), " + ")
|
||||||
// Subtraction as binary: add spaces when it looks binary. Keep unary '-' tight (after start or ( [ { = : ,)
|
// Subtraction as binary: add spaces when it looks binary. Keep unary '-' tight (after start or ( [ { = : ,)
|
||||||
s = s.replace(Regex("(?<=[^\\s(\\[\\{=:,])-(?=[^=])"), " - ")
|
// and avoid splitting '-=', '--', '->'
|
||||||
|
s = s.replace(Regex("(?<=[^\\s(\\[\\{=:,])\\s*-\\s*(?=[^=\\->])"), " - ")
|
||||||
// Colon in types/extends: remove spaces before, ensure one space after (keep '::' intact)
|
// Colon in types/extends: remove spaces before, ensure one space after (keep '::' intact)
|
||||||
s = s.replace(Regex("(?<!:)\\s*:(?!:)\\s*"), ": ")
|
s = s.replace(Regex("(?<!:)\\s*:(?!:)\\s*"), ": ")
|
||||||
return s
|
return s
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -331,6 +331,25 @@ class LyngFormatterTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun multiLineChainCalls() {
|
||||||
|
val src = """
|
||||||
|
somevar.where
|
||||||
|
.isGreen()
|
||||||
|
.isWarm()
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val expected = """
|
||||||
|
somevar.where
|
||||||
|
.isGreen()
|
||||||
|
.isWarm()
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val cfg = LyngFormatConfig(indentSize = 4, continuationIndentSize = 8)
|
||||||
|
val out = LyngFormatter.reindent(src, cfg)
|
||||||
|
assertEquals(expected, out)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun chainedCalls_and_MemberAccess() {
|
fun chainedCalls_and_MemberAccess() {
|
||||||
val src = """
|
val src = """
|
||||||
@ -413,6 +432,55 @@ class LyngFormatterTest {
|
|||||||
assertEquals(expected, LyngFormatter.format(out, cfg))
|
assertEquals(expected, LyngFormatter.format(out, cfg))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun multilineCommentIndentation() {
|
||||||
|
val src = """
|
||||||
|
/*
|
||||||
|
This is
|
||||||
|
a comment
|
||||||
|
*/
|
||||||
|
fun f() {
|
||||||
|
/*
|
||||||
|
Inner
|
||||||
|
comment
|
||||||
|
*/
|
||||||
|
val x = 1 /* end of line
|
||||||
|
comment */
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val expected = """
|
||||||
|
/*
|
||||||
|
This is
|
||||||
|
a comment
|
||||||
|
*/
|
||||||
|
fun f() {
|
||||||
|
/*
|
||||||
|
Inner
|
||||||
|
comment
|
||||||
|
*/
|
||||||
|
val x = 1 /* end of line
|
||||||
|
comment */
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val cfg = LyngFormatConfig(indentSize = 4)
|
||||||
|
val out = LyngFormatter.reindent(src, cfg)
|
||||||
|
assertEquals(expected, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun multicharacterOperators_spacing() {
|
||||||
|
val operators = listOf("+=", "-=", "*=", "/=", "=>", "==", "!=", "<=", ">=", "&&", "||", "->")
|
||||||
|
for (op in operators) {
|
||||||
|
val src = "a${op}b"
|
||||||
|
val expected = "a $op b"
|
||||||
|
val cfg = LyngFormatConfig(applySpacing = true)
|
||||||
|
val out = LyngFormatter.format(src, cfg)
|
||||||
|
assertEquals(expected, out, "Failed for operator $op")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun propertyBasedIdempotencyMedium() {
|
fun propertyBasedIdempotencyMedium() {
|
||||||
// Compose slightly longer random snippets out of templates
|
// Compose slightly longer random snippets out of templates
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user