plugin and formatter improvements
This commit is contained in:
parent
f848d79d39
commit
0759346e4b
@ -122,6 +122,24 @@ class Compiler(
|
|||||||
return doc
|
return doc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun nextNonWhitespace(): Token {
|
||||||
|
while (true) {
|
||||||
|
val t = cc.next()
|
||||||
|
when (t.type) {
|
||||||
|
Token.Type.SINGLE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> {
|
||||||
|
pushPendingDocToken(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
Token.Type.NEWLINE -> {
|
||||||
|
if (!prevWasComment) clearPendingDoc() else prevWasComment = false
|
||||||
|
}
|
||||||
|
|
||||||
|
Token.Type.EOF -> return t
|
||||||
|
else -> return t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set just before entering a declaration parse, taken from keyword token position
|
// Set just before entering a declaration parse, taken from keyword token position
|
||||||
private var pendingDeclStart: Pos? = null
|
private var pendingDeclStart: Pos? = null
|
||||||
private var pendingDeclDoc: MiniDoc? = null
|
private var pendingDeclDoc: MiniDoc? = null
|
||||||
@ -320,9 +338,15 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.LABEL -> continue
|
Token.Type.LABEL -> continue
|
||||||
Token.Type.SINGLE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> continue
|
Token.Type.SINGLE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> {
|
||||||
|
pushPendingDocToken(t)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
Token.Type.NEWLINE -> continue
|
Token.Type.NEWLINE -> {
|
||||||
|
if (!prevWasComment) clearPendingDoc() else prevWasComment = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
Token.Type.SEMICOLON -> continue
|
Token.Type.SEMICOLON -> continue
|
||||||
|
|
||||||
@ -1345,7 +1369,7 @@ class Compiler(
|
|||||||
modifiers.add(currentToken.value)
|
modifiers.add(currentToken.value)
|
||||||
val next = cc.peekNextNonWhitespace()
|
val next = cc.peekNextNonWhitespace()
|
||||||
if (next.type == Token.Type.ID || next.type == Token.Type.OBJECT) {
|
if (next.type == Token.Type.ID || next.type == Token.Type.OBJECT) {
|
||||||
currentToken = cc.next()
|
currentToken = nextNonWhitespace()
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -1373,6 +1397,8 @@ class Compiler(
|
|||||||
throw ScriptError(currentToken.pos, "abstract members cannot be private")
|
throw ScriptError(currentToken.pos, "abstract members cannot be private")
|
||||||
|
|
||||||
pendingDeclStart = firstId.pos
|
pendingDeclStart = firstId.pos
|
||||||
|
// pendingDeclDoc might be already set by an annotation
|
||||||
|
if (pendingDeclDoc == null)
|
||||||
pendingDeclDoc = consumePendingDoc()
|
pendingDeclDoc = consumePendingDoc()
|
||||||
|
|
||||||
val isMember = (codeContexts.lastOrNull() is CodeContext.ClassBody)
|
val isMember = (codeContexts.lastOrNull() is CodeContext.ClassBody)
|
||||||
@ -1900,6 +1926,7 @@ class Compiler(
|
|||||||
// skip '{'
|
// skip '{'
|
||||||
cc.skipTokenOfType(Token.Type.LBRACE)
|
cc.skipTokenOfType(Token.Type.LBRACE)
|
||||||
|
|
||||||
|
if (cc.peekNextNonWhitespace().type != Token.Type.RBRACE) {
|
||||||
do {
|
do {
|
||||||
val t = cc.nextNonWhitespace()
|
val t = cc.nextNonWhitespace()
|
||||||
when (t.type) {
|
when (t.type) {
|
||||||
@ -1921,6 +1948,9 @@ class Compiler(
|
|||||||
else -> t.raiseSyntax("expected enum entry name")
|
else -> t.raiseSyntax("expected enum entry name")
|
||||||
}
|
}
|
||||||
} while (true)
|
} while (true)
|
||||||
|
} else {
|
||||||
|
cc.nextNonWhitespace()
|
||||||
|
}
|
||||||
|
|
||||||
miniSink?.onEnumDecl(
|
miniSink?.onEnumDecl(
|
||||||
MiniEnumDecl(
|
MiniEnumDecl(
|
||||||
|
|||||||
@ -108,8 +108,9 @@ object LyngFormatter {
|
|||||||
if (t == "finally") return true
|
if (t == "finally") return true
|
||||||
|
|
||||||
// Short definition form: fun x() = or val x =
|
// Short definition form: fun x() = or val x =
|
||||||
if (Regex("^(override\\s+)?(fun|fn)\\b.*=\\s*$").matches(t)) return true
|
// We allow optional 'static' as well
|
||||||
if (Regex("^(private\\s+|protected\\s+|public\\s+|override\\s+)?(val|var)\\b.*=\\s*$").matches(t)) return true
|
if (Regex("^(static\\s+)?(override\\s+)?(fun|fn)\\b.*=\\s*$").matches(t)) return true
|
||||||
|
if (Regex("^(static\\s+)?(private\\s+|protected\\s+|public\\s+|override\\s+)?(val|var)\\b.*=\\s*$").matches(t)) return true
|
||||||
|
|
||||||
// property accessors ending with ) or =
|
// property accessors ending with ) or =
|
||||||
if (isAccessorRelated(t)) {
|
if (isAccessorRelated(t)) {
|
||||||
@ -122,7 +123,8 @@ object LyngFormatter {
|
|||||||
val t = s.trim()
|
val t = s.trim()
|
||||||
if (!t.endsWith("=")) return false
|
if (!t.endsWith("=")) return false
|
||||||
// Is it a function or property definition?
|
// Is it a function or property definition?
|
||||||
if (Regex("\\b(fun|fn|val|var)\\b").containsMatchIn(t)) return true
|
// (Note: we exclude 'static' here to avoid double indent if it's already handled)
|
||||||
|
if (Regex("^(override\\s+)?(fun|fn|val|var)\\b").containsMatchIn(t)) return true
|
||||||
// Is it an accessor?
|
// Is it an accessor?
|
||||||
if (isPropertyAccessor(t)) return true
|
if (isPropertyAccessor(t)) return true
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -44,13 +44,22 @@ data class MiniDoc(
|
|||||||
fun parse(range: MiniRange, lines: Iterable<String>, extraTags: Map<String, List<String>> = emptyMap()): MiniDoc {
|
fun parse(range: MiniRange, lines: Iterable<String>, extraTags: Map<String, List<String>> = emptyMap()): MiniDoc {
|
||||||
val parsedTags = mutableMapOf<String, MutableList<String>>()
|
val parsedTags = mutableMapOf<String, MutableList<String>>()
|
||||||
var currentTag: String? = null
|
var currentTag: String? = null
|
||||||
val currentContent = StringBuilder()
|
val currentContent = mutableListOf<String>()
|
||||||
|
|
||||||
fun flush() {
|
fun flush() {
|
||||||
currentTag?.let { tag ->
|
currentTag?.let { tag ->
|
||||||
parsedTags.getOrPut(tag) { mutableListOf() }.add(currentContent.toString().trim())
|
val trimmedContent = if (currentContent.size > 1) {
|
||||||
|
// First line is common content, we want to trim indent from subsequent lines
|
||||||
|
// based on THEIR common indent.
|
||||||
|
val firstLine = currentContent[0]
|
||||||
|
val remaining = currentContent.drop(1).joinToString("\n").trimIndent()
|
||||||
|
if (remaining.isEmpty()) firstLine else firstLine + "\n" + remaining
|
||||||
|
} else {
|
||||||
|
currentContent.joinToString("\n")
|
||||||
}
|
}
|
||||||
currentContent.setLength(0)
|
parsedTags.getOrPut(tag) { mutableListOf() }.add(trimmedContent.trim())
|
||||||
|
}
|
||||||
|
currentContent.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
val descriptionLines = mutableListOf<String>()
|
val descriptionLines = mutableListOf<String>()
|
||||||
@ -58,26 +67,38 @@ data class MiniDoc(
|
|||||||
|
|
||||||
for (rawLine in lines) {
|
for (rawLine in lines) {
|
||||||
for (line in rawLine.lines()) {
|
for (line in rawLine.lines()) {
|
||||||
val trimmed = line.trim()
|
// Strip leading '*' and space if present (standard doc comment style)
|
||||||
|
val cleanedLine = line.trim().let {
|
||||||
|
if (it.startsWith("*")) it.substring(1).removePrefix(" ") else line
|
||||||
|
}
|
||||||
|
val trimmed = cleanedLine.trim()
|
||||||
if (trimmed.startsWith("@")) {
|
if (trimmed.startsWith("@")) {
|
||||||
inTags = true
|
inTags = true
|
||||||
flush()
|
flush()
|
||||||
val parts = trimmed.substring(1).split(Regex("\\s+"), 2)
|
val parts = trimmed.substring(1).split(Regex("\\s+"), 2)
|
||||||
currentTag = parts[0]
|
currentTag = parts[0]
|
||||||
currentContent.append(parts.getOrNull(1) ?: "")
|
val tagFirstLine = parts.getOrNull(1) ?: ""
|
||||||
|
if (tagFirstLine.isNotEmpty()) {
|
||||||
|
// Find where the content starts in the cleaned line to preserve relative indentation
|
||||||
|
val contentIndex = cleanedLine.indexOf(tagFirstLine)
|
||||||
|
if (contentIndex >= 0) {
|
||||||
|
currentContent.add(cleanedLine.substring(contentIndex))
|
||||||
|
} else {
|
||||||
|
currentContent.add(tagFirstLine)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (inTags) {
|
if (inTags) {
|
||||||
if (currentContent.isNotEmpty()) currentContent.append("\n")
|
currentContent.add(cleanedLine)
|
||||||
currentContent.append(line)
|
|
||||||
} else {
|
} else {
|
||||||
descriptionLines.add(line)
|
descriptionLines.add(cleanedLine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flush()
|
flush()
|
||||||
|
|
||||||
val raw = descriptionLines.joinToString("\n").trimEnd()
|
val raw = descriptionLines.joinToString("\n").trimIndent().trim()
|
||||||
val summary = raw.lines().firstOrNull { it.isNotBlank() }?.trim()
|
val summary = raw.lines().firstOrNull { it.isNotBlank() }?.trim()
|
||||||
|
|
||||||
val finalTags = parsedTags.toMutableMap()
|
val finalTags = parsedTags.toMutableMap()
|
||||||
|
|||||||
@ -528,4 +528,52 @@ class MiniAstTest {
|
|||||||
assertTrue(xParam.contains("first line of x"), "should contain first line")
|
assertTrue(xParam.contains("first line of x"), "should contain first line")
|
||||||
assertTrue(xParam.contains("second line of x"), "should contain second line")
|
assertTrue(xParam.contains("second line of x"), "should contain second line")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun enum_minidocs_and_semicolon_robustness() = runTest {
|
||||||
|
val code = """
|
||||||
|
/** Enum doc */
|
||||||
|
enum E { A };
|
||||||
|
|
||||||
|
/** Next doc */
|
||||||
|
class C {}
|
||||||
|
""".trimIndent()
|
||||||
|
val (_, sink) = compileWithMini(code)
|
||||||
|
val mini = sink.build()
|
||||||
|
assertNotNull(mini)
|
||||||
|
|
||||||
|
val en = mini.declarations.filterIsInstance<MiniEnumDecl>().firstOrNull { it.name == "E" }
|
||||||
|
assertNotNull(en)
|
||||||
|
assertNotNull(en.doc)
|
||||||
|
assertEquals("Enum doc", en.doc.summary)
|
||||||
|
|
||||||
|
val cl = mini.declarations.filterIsInstance<MiniClassDecl>().firstOrNull { it.name == "C" }
|
||||||
|
assertNotNull(cl)
|
||||||
|
assertNotNull(cl.doc)
|
||||||
|
assertEquals("Next doc", cl.doc.summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun empty_enum_support() = runTest {
|
||||||
|
val code = "enum E {}"
|
||||||
|
val (_, sink) = compileWithMini(code)
|
||||||
|
val mini = sink.build()
|
||||||
|
assertNotNull(mini)
|
||||||
|
assertTrue(mini.declarations.any { it.name == "E" && it is MiniEnumDecl })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun modifiers_with_comment_robustness() = runTest {
|
||||||
|
val code = """
|
||||||
|
class X {
|
||||||
|
static /** doc */ fun f() {}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
val (_, sink) = compileWithMini(code)
|
||||||
|
val mini = sink.build()
|
||||||
|
assertNotNull(mini)
|
||||||
|
val cls = mini.declarations.filterIsInstance<MiniClassDecl>().firstOrNull { it.name == "X" }
|
||||||
|
assertNotNull(cls)
|
||||||
|
assertTrue(cls.members.any { it.name == "f" })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -882,6 +882,22 @@ class LyngFormatterTest {
|
|||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
|
val cfg = LyngFormatConfig(indentSize = 4, continuationIndentSize = 4)
|
||||||
|
val out = LyngFormatter.reindent(src, cfg)
|
||||||
|
assertEquals(expected, out)
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun multlinedShortFunIndentation() {
|
||||||
|
val src = """
|
||||||
|
static fun create(walletId, trId, amount) =
|
||||||
|
Cells.createNew(Commission(amount) => ["commission", "w:" + walletId, "tr:" + trId, createdTag(), "pending"])
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val expected = """
|
||||||
|
static fun create(walletId, trId, amount) =
|
||||||
|
Cells.createNew(Commission(amount) => ["commission", "w:" + walletId, "tr:" + trId, createdTag(), "pending"])
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
val cfg = LyngFormatConfig(indentSize = 4, continuationIndentSize = 4)
|
val cfg = LyngFormatConfig(indentSize = 4, continuationIndentSize = 4)
|
||||||
val out = LyngFormatter.reindent(src, cfg)
|
val out = LyngFormatter.reindent(src, cfg)
|
||||||
assertEquals(expected, out)
|
assertEquals(expected, out)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user