diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index cb265dc..f019770 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -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() // 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() @@ -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() diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/format/LyngFormatter.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/format/LyngFormatter.kt index bcb9b31..b55c544 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/format/LyngFormatter.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/format/LyngFormatter.kt @@ -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. diff --git a/lynglib/src/commonTest/kotlin/MiniAstTest.kt b/lynglib/src/commonTest/kotlin/MiniAstTest.kt index 294d226..bb34e60 100644 --- a/lynglib/src/commonTest/kotlin/MiniAstTest.kt +++ b/lynglib/src/commonTest/kotlin/MiniAstTest.kt @@ -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().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 = """ diff --git a/lynglib/src/commonTest/kotlin/net/sergeych/lyng/format/BlockReindentTest.kt b/lynglib/src/commonTest/kotlin/net/sergeych/lyng/format/BlockReindentTest.kt index 8b28041..11b3ced 100644 --- a/lynglib/src/commonTest/kotlin/net/sergeych/lyng/format/BlockReindentTest.kt +++ b/lynglib/src/commonTest/kotlin/net/sergeych/lyng/format/BlockReindentTest.kt @@ -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) + } + }