Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/main/kotlin/org/pkl/lsp/analyzers/MemberAnalyzer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,16 @@ import org.eclipse.lsp4j.DiagnosticSeverity
import org.pkl.lsp.ErrorMessages
import org.pkl.lsp.PklBaseModule
import org.pkl.lsp.Project
import org.pkl.lsp.ast.PklClassMember
import org.pkl.lsp.ast.PklClassMethod
import org.pkl.lsp.ast.PklClassProperty
import org.pkl.lsp.ast.PklExpr
import org.pkl.lsp.ast.PklNode
import org.pkl.lsp.ast.PklObjectProperty
import org.pkl.lsp.ast.PklProperty
import org.pkl.lsp.ast.Span
import org.pkl.lsp.ast.Terminal
import org.pkl.lsp.ast.TokenType
import org.pkl.lsp.packages.dto.PklProject
import org.pkl.lsp.resolvers.ResolveVisitors
import org.pkl.lsp.resolvers.Resolvers
Expand Down Expand Up @@ -56,6 +62,10 @@ class MemberAnalyzer(project: Project) : Analyzer(project) {
}
is PklClassProperty -> {
checkUnresolvedProperty(node, memberType, base, diagnosticsHolder, context)
checkAbstractMemberWithBody(node, diagnosticsHolder)
}
is PklClassMethod -> {
checkAbstractMemberWithBody(node, diagnosticsHolder)
}
}
return true
Expand Down Expand Up @@ -100,4 +110,33 @@ class MemberAnalyzer(project: Project) : Analyzer(project) {
}
}
}

private fun checkAbstractMemberWithBody(
node: PklClassMember,
diagnosticsHolder: DiagnosticsHolder,
) {
if (!node.isAbstract) return
val bodySpan =
if (node is PklClassProperty) {
node.expr?.spanWithAssign ?: node.objectBody?.span
} else {
node as PklClassMethod
node.body?.spanWithAssign
}
if (bodySpan != null) {
diagnosticsHolder.addDiagnostic(
node,
"Abstract member cannot have a body",
bodySpan,
DiagnosticSeverity.Error,
)
}
}

private val PklExpr.spanWithAssign: Span?
get() {
val assignNode =
this.prevSiblingMatching { it is Terminal && it.type == TokenType.ASSIGN } ?: return null
return assignNode.span.endAt(span)
}
}
8 changes: 8 additions & 0 deletions src/main/kotlin/org/pkl/lsp/ast/PklNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ interface PklNode {
val parentNode = parent ?: return null
return parentNode.children[index - 1]
}

fun prevSiblingMatching(filter: (PklNode) -> Boolean): PklNode? {
var node = prevSibling()
while (node != null && !filter(node)) {
node = node.prevSibling()
}
return node
}
}

sealed interface PklSuppressWarningsTarget : PklNode {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
abstract module MyMod

abstract foo = 1

abstract bar {}

abstract function foo() = 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
abstract module MyMod

abstract foo = 1
^^^
| Error: Abstract member cannot have a body

abstract bar {}
^^
| Error: Abstract member cannot have a body

abstract function foo() = 1
^^^
| Error: Abstract member cannot have a body

7 changes: 3 additions & 4 deletions src/test/kotlin/org/pkl/lsp/ModifierQuickfixTest.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2025-2026 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -53,7 +53,7 @@ class ModifierQuickfixTest : LspTestBase() {
"""
amends "pkl:test"

abstract external foo: String = "Hello"
const foo: String = "Hello"
"""
.trimIndent()
)
Expand All @@ -62,13 +62,12 @@ class ModifierQuickfixTest : LspTestBase() {
val action = diagnostic.actions.find { it.title == "Add modifier 'local'" }
assertThat(action).isNotNull
runAction(action!!.toMessage(diagnostic))
// modifier gets inserted in between 'abstract' and 'external'
assertThat(file.contents)
.isEqualTo(
"""
amends "pkl:test"

abstract local external foo: String = "Hello"
local const foo: String = "Hello"
"""
.trimIndent()
)
Expand Down
Loading