Skip to content

Commit

Permalink
Switch to adapter pattern (#453)
Browse files Browse the repository at this point in the history
  • Loading branch information
bioball committed Apr 26, 2024
1 parent b3df602 commit b3b2ac1
Show file tree
Hide file tree
Showing 28 changed files with 1,571 additions and 1,858 deletions.
57 changes: 39 additions & 18 deletions pkl-lsp/src/main/kotlin/org/pkl/lsp/Builder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,23 @@ import org.eclipse.lsp4j.PublishDiagnosticsParams
import org.pkl.core.parser.LexParseException
import org.pkl.core.parser.Parser
import org.pkl.core.util.IoUtils
import org.pkl.lsp.cst.CstBuilder
import org.pkl.lsp.cst.ParseError
import org.pkl.lsp.cst.PklModule
import org.pkl.lsp.cst.Span
import org.pkl.lsp.LSPUtil.toRange
import org.pkl.lsp.analyzers.Analyzer
import org.pkl.lsp.analyzers.ModifierAnalyzer
import org.pkl.lsp.analyzers.PklDiagnostic
import org.pkl.lsp.ast.Module
import org.pkl.lsp.ast.ModuleImpl
import org.pkl.lsp.ast.Node
import org.pkl.lsp.ast.Span

class Builder(private val server: PklLSPServer) {
private var runningBuild: CompletableFuture<PklModule?> = CompletableFuture.supplyAsync(::noop)
private var runningBuild: CompletableFuture<Module?> = CompletableFuture.supplyAsync(::noop)

private val parser = Parser()

fun runningBuild(): CompletableFuture<PklModule?> = runningBuild
private val analyzers: List<Analyzer> = listOf(ModifierAnalyzer(server))

fun runningBuild(): CompletableFuture<Module?> = runningBuild

fun requestBuild(file: URI) {
val change = IoUtils.readString(file.toURL())
Expand All @@ -46,42 +52,55 @@ class Builder(private val server: PklLSPServer) {
runningBuild = CompletableFuture.supplyAsync { build(file, change) }
}

private fun build(file: URI, change: String): PklModule? {
val cstBuilder = CstBuilder()
try {
private fun build(file: URI, change: String): Module? {
// val cstBuilder = CstBuilder()
return try {
server.logger().log("building $file")
val moduleCtx = parser.parseModule(change)
val module = cstBuilder.visitModule(moduleCtx)
makeDiagnostics(file, cstBuilder.errors())
val module = ModuleImpl(moduleCtx)
val diagnostics = annotate(module)
makeDiagnostics(file, diagnostics)
return module
} catch (e: LexParseException) {
server.logger().error("Error building $file: ${e.message}")
makeDiagnostics(file, cstBuilder.errors() + listOf(toParserError(e)))
return null
makeParserDiagnostics(file, listOf(toParserError(e)))
null
} catch (e: Exception) {
server.logger().error("Error building $file: ${e.message}")
return null
server.logger().error("Error building $file: ${e.message} ${e.stackTraceToString()}")
null
}
}

private fun makeDiagnostics(file: URI, errors: List<ParseError>) {
private fun annotate(node: Node): List<Diagnostic> {
return buildList<PklDiagnostic> {
for (annotator in analyzers) {
annotator.annotate(node, this)
}
}
}

private fun makeParserDiagnostics(file: URI, errors: List<ParseError>) {
val diags =
errors.map { err ->
val msg = resolveErrorMessage(err.errorType, *err.args)
val diag = Diagnostic(LSPUtil.spanToRange(err.span), "$msg\n\n")
val diag = Diagnostic(err.span.toRange(), "$msg\n\n")
diag.severity = DiagnosticSeverity.Error
diag.source = "Pkl Language Server"
server.logger().log("diagnostic: $msg at ${err.span}")
diag
}
makeDiagnostics(file, diags)
}

private fun makeDiagnostics(file: URI, diags: List<Diagnostic>) {
server.logger().log("Found ${diags.size} diagnostic errors for $file")
val params = PublishDiagnosticsParams(file.toString(), diags)
// Have to publish diagnostics even if there are no errors, so we clear previous problems
server.client().publishDiagnostics(params)
}

companion object {
private fun noop(): PklModule? {
private fun noop(): Module? {
return null
}

Expand All @@ -103,3 +122,5 @@ class Builder(private val server: PklLSPServer) {
}
}
}

class ParseError(val errorType: String, val span: Span, vararg val args: Any)
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.lsp.cst
package org.pkl.lsp

data class Ident(val value: String, val span: Span)
import java.text.MessageFormat
import java.util.*

object ErrorMessages {
fun create(messageName: String, vararg args: Any): String {

val locale = Locale.getDefault()
val errorMessage =
ResourceBundle.getBundle("org.pkl.lsp.errorMessages", locale).getString(messageName)

// only format if `errorMessage` is a format string
if (args.isEmpty()) return errorMessage

val formatter = MessageFormat(errorMessage, locale)
return formatter.format(args)
}
}
12 changes: 8 additions & 4 deletions pkl-lsp/src/main/kotlin/org/pkl/lsp/LSPUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@ package org.pkl.lsp

import org.eclipse.lsp4j.Position
import org.eclipse.lsp4j.Range
import org.pkl.lsp.cst.Span
import org.pkl.lsp.ast.Span

object LSPUtil {
fun spanToRange(s: Span): Range {
val start = Position(s.beginLine - 1, s.beginCol - 1)
val end = Position(s.endLine - 1, s.endCol - 1)
fun Span.toRange(): Range {
val start = Position(beginLine - 1, beginCol - 1)
val end = Position(endLine - 1, endCol - 1)
return Range(start, end)
}

inline fun <reified T> List<*>.firstInstanceOf(): T? {
return firstOrNull { it is T } as T?
}
}
6 changes: 3 additions & 3 deletions pkl-lsp/src/main/kotlin/org/pkl/lsp/PklLSPServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import org.eclipse.lsp4j.services.*

class PklLSPServer(private val verbose: Boolean) : LanguageServer, LanguageClientAware {

val workspaceService: PklWorkspaceService = PklWorkspaceService()
val textService: PklTextDocumentService = PklTextDocumentService(this)
private val workspaceService: PklWorkspaceService = PklWorkspaceService()
private val textService: PklTextDocumentService = PklTextDocumentService(this)

private lateinit var client: LanguageClient
private lateinit var logger: ClientLogger
Expand All @@ -38,7 +38,7 @@ class PklLSPServer(private val verbose: Boolean) : LanguageServer, LanguageClien
res.capabilities.textDocumentSync = Either.forLeft(TextDocumentSyncKind.Full)

// Hover capability
res.capabilities.setHoverProvider(true)
// res.capabilities.setHoverProvider(true)

return CompletableFuture.supplyAsync { res }
}
Expand Down
43 changes: 43 additions & 0 deletions pkl-lsp/src/main/kotlin/org/pkl/lsp/analyzers/Analyzer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Copyright © 2024 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.lsp.analyzers

import org.pkl.lsp.ast.Node

/**
* Scans the source tree, and builds [PklDiagnostic]s.
*
* Diagnostics then get reported back to the user.
*/
abstract class Analyzer {
fun annotate(node: Node, diagnosticsHolder: MutableList<PklDiagnostic>) {
if (doAnnotate(node, diagnosticsHolder)) {
return
}
node.children.forEach { annotate(it, diagnosticsHolder) }
}

/**
* Collect diagnostics, pushing them into [diagnosticsHolder] as they are captured.
*
* Return `false` if the annotator does not need to analyze any further. This skips calling
* [doAnnotate] on its children.
*/
protected abstract fun doAnnotate(
node: Node,
diagnosticsHolder: MutableList<PklDiagnostic>
): Boolean
}
121 changes: 121 additions & 0 deletions pkl-lsp/src/main/kotlin/org/pkl/lsp/analyzers/ModifierAnalyzer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/**
* Copyright © 2024 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.lsp.analyzers

import org.eclipse.lsp4j.DiagnosticSeverity
import org.pkl.lsp.ErrorMessages
import org.pkl.lsp.PklLSPServer
import org.pkl.lsp.ast.*
import org.pkl.lsp.ast.TokenType.*

class ModifierAnalyzer(private val server: PklLSPServer) : Analyzer() {
companion object {
private val MODULE_MODIFIERS = setOf(ABSTRACT, OPEN)
private val AMENDING_MODULE_MODIFIERS = emptySet<TokenType>()
private val CLASS_MODIFIERS = setOf(ABSTRACT, OPEN, EXTERNAL, LOCAL)
private val TYPE_ALIAS_MODIFIERS = setOf(EXTERNAL, LOCAL)
private val CLASS_METHOD_MODIFIERS = setOf(ABSTRACT, EXTERNAL, LOCAL, CONST)
private val CLASS_PROPERTY_MODIFIERS = setOf(ABSTRACT, EXTERNAL, HIDDEN, LOCAL, FIXED, CONST)
private val OBJECT_METHOD_MODIFIERS = setOf(LOCAL)
private val OBJECT_PROPERTY_MODIFIERS = setOf(LOCAL)
}

override fun doAnnotate(node: Node, diagnosticsHolder: MutableList<PklDiagnostic>): Boolean {
if (node !is ModifierListOwner || node.modifiers == null) {
return false
}

var localModifier: Node? = null
var abstractModifier: Node? = null
var openModifier: Node? = null
var hiddenModifier: Node? = null
var fixedModifier: Node? = null

for (modifier in node.modifiers!!) {
when (modifier.type) {
LOCAL -> localModifier = modifier
ABSTRACT -> abstractModifier = modifier
OPEN -> openModifier = modifier
HIDDEN -> hiddenModifier = modifier
FIXED -> fixedModifier = modifier
else -> {}
}
}
if (localModifier == null) {
when (node) {
is ClassProperty -> {
if (
node.parent is Module &&
(node.parent as Module).isAmend &&
(hiddenModifier != null || node.typeAnnotation != null)
) {
if (node.identifier != null) {
diagnosticsHolder.add(
PklDiagnostic(
node.identifier!!,
ErrorMessages.create("missingModifierLocal"),
DiagnosticSeverity.Error
)
)
return true
}
}
}
}
}

if (abstractModifier != null && openModifier != null) {
diagnosticsHolder.add(
PklDiagnostic(
abstractModifier,
ErrorMessages.create("modifierAbstractConflictsWithOpen"),
DiagnosticSeverity.Error
)
)
diagnosticsHolder.add(
PklDiagnostic(
openModifier,
ErrorMessages.create("modifierOpenConflictsWithAbstract"),
DiagnosticSeverity.Error
)
)
}

val (description, applicableModifiers) =
when (node) {
is ModuleDeclaration ->
if (node.isAmend) "amending modules" to AMENDING_MODULE_MODIFIERS
else "modules" to MODULE_MODIFIERS
is Class -> "classes" to CLASS_MODIFIERS
is TypeAlias -> "typealiases" to TYPE_ALIAS_MODIFIERS
is ClassMethod -> "class methods" to CLASS_METHOD_MODIFIERS
is ClassProperty -> "class properties" to CLASS_PROPERTY_MODIFIERS
else -> return false
}
for (modifier in node.modifiers!!) {
if (modifier.type !in applicableModifiers) {
diagnosticsHolder.add(
PklDiagnostic(
modifier,
ErrorMessages.create("modifierIsNotApplicable", modifier.text, description),
DiagnosticSeverity.Error
)
)
}
}
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.lsp.cst
package org.pkl.lsp.analyzers

sealed class Parameter(override val span: Span) : PklNode(span) {
import org.eclipse.lsp4j.Diagnostic
import org.eclipse.lsp4j.DiagnosticSeverity
import org.pkl.lsp.LSPUtil.toRange
import org.pkl.lsp.ast.Node
import org.pkl.lsp.ast.Span

data class Underscore(override val span: Span) : Parameter(span)

data class TypedIdent(val ident: Ident, val type: Type?, override val span: Span) :
Parameter(span) {
init {
type?.parent = this
}
}
class PklDiagnostic(span: Span, message: String, severity: DiagnosticSeverity) :
Diagnostic(span.toRange(), message, severity, "pkl") {
constructor(
node: Node,
message: String,
severity: DiagnosticSeverity
) : this(node.span, message, severity)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.lsp.cst
package org.pkl.lsp.analyzers

data class Modifier(val mod: ModifierValue, val span: Span)
import org.pkl.lsp.ast.Node

enum class ModifierValue {
EXTERNAL,
ABSTRACT,
OPEN,
LOCAL,
HIDDEN,
FIXED,
CONST
class StringLiteralAnalyzer : Analyzer() {
override fun doAnnotate(node: Node, diagnosticsHolder: MutableList<PklDiagnostic>): Boolean {
TODO("Not yet implemented")
}
}

0 comments on commit b3b2ac1

Please sign in to comment.