plugin fixes: incorrect reformatting on }, incorrect parsing of class minidocs

This commit is contained in:
Sergey Chernov 2026-01-04 11:58:55 +01:00
parent 1d9befe101
commit 9e138367ef
4 changed files with 73 additions and 6 deletions

View File

@ -1773,10 +1773,11 @@ class Compiler(
}
private fun parseEnumDeclaration(): Statement {
val startPos = pendingDeclStart ?: cc.currentPos()
val nameToken = cc.requireToken(Token.Type.ID)
val startPos = pendingDeclStart ?: nameToken.pos
val doc = pendingDeclDoc ?: consumePendingDoc()
pendingDeclDoc = null
val nameToken = cc.requireToken(Token.Type.ID)
pendingDeclStart = null
// so far only simplest enums:
val names = mutableListOf<String>()
// skip '{'
@ -1822,6 +1823,10 @@ class Compiler(
private suspend fun parseClassDeclaration(): Statement {
val nameToken = cc.requireToken(Token.Type.ID)
val startPos = pendingDeclStart ?: nameToken.pos
val doc = pendingDeclDoc ?: consumePendingDoc()
pendingDeclDoc = null
pendingDeclStart = null
return inCodeContext(CodeContext.ClassBody(nameToken.value)) {
val constructorArgsDeclaration =
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
@ -1879,7 +1884,7 @@ class Compiler(
// Emit MiniClassDecl with collected base names; bodyRange is omitted for now
run {
val declRange = MiniRange(pendingDeclStart ?: nameToken.pos, cc.currentPos())
val declRange = MiniRange(startPos, cc.currentPos())
val bases = baseSpecs.map { it.name }
// Collect constructor fields declared as val/var in primary constructor
val ctorFields = mutableListOf<MiniCtorField>()
@ -1903,11 +1908,10 @@ class Compiler(
bases = bases,
bodyRange = classBodyRange,
ctorFields = ctorFields,
doc = pendingDeclDoc,
doc = doc,
nameStart = nameToken.pos
)
miniSink?.onClassDecl(node)
pendingDeclDoc = null
}
val initScope = popInitScope()

View File

@ -288,7 +288,20 @@ object LyngFormatter {
i++
}
// Normalize collected base indent: replace tabs with spaces
var baseIndent = if (onlyWs) base.toString().replace("\t", " ".repeat(config.indentSize)) else ""
var baseIndent = if (onlyWs) {
if (start == lineStart) {
// Range starts at line start, pick up this line's indentation as base
var k = start
val lineIndent = StringBuilder()
while (k < text.length && text[k] != '\n' && (text[k] == ' ' || text[k] == '\t')) {
lineIndent.append(text[k])
k++
}
lineIndent.toString().replace("\t", " ".repeat(config.indentSize))
} else {
base.toString().replace("\t", " ".repeat(config.indentSize))
}
} else ""
var parentBaseIndent: String? = baseIndent
if (baseIndent.isEmpty()) {
// Fallback: use the indent of the nearest previous non-empty line as base.

View File

@ -115,6 +115,23 @@ class MiniAstTest {
assertNotNull(vd.initRange)
}
@Test
fun miniAst_captures_class_doc_with_members() = runTest {
val code = """
/** Class C docs */
class C {
fun foo() {}
}
"""
val (_, sink) = compileWithMini(code)
val mini = sink.build()
assertNotNull(mini)
val cd = mini!!.declarations.filterIsInstance<MiniClassDecl>().firstOrNull { it.name == "C" }
assertNotNull(cd)
assertNotNull(cd.doc, "Class doc should be preserved even with members")
assertTrue(cd.doc!!.raw.contains("Class C docs"))
}
@Test
fun miniAst_captures_class_bases_and_doc() = runTest {
val code = """

View File

@ -434,4 +434,37 @@ class BlockReindentTest {
kotlin.test.assertTrue(lines.getOrNull(innerStart + 1)?.trimStart()?.startsWith("1") == true)
kotlin.test.assertEquals("}", lines.getOrNull(innerStart + 2)?.trim())
}
@Test
fun reindentRange_doubledIndentationFix() {
val src = """
class X {
fun funA(): String {
"a"
}
fun runB() {
}
}
""".trimIndent()
// The range for runB should be from the start of 'fun runB' line to the end of '}' line.
val startOfRunB = src.indexOf(" fun runB")
val endOfRunB = src.indexOf(" }") + " }".length
val range = startOfRunB until endOfRunB
val cfg = LyngFormatConfig(indentSize = 4)
val updated = LyngFormatter.reindentRange(src, range, cfg, preserveBaseIndent = true)
val expected = """
class X {
fun funA(): String {
"a"
}
fun runB() {
}
}
""".trimIndent()
assertEquals(expected, updated)
}
}