Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -859,50 +859,55 @@ internal class ToolShellCommand : ProjectAwareSubcommand<ToolState, CommandConte
}

// Given an error location (in interactive mode), fetch the source, plus `contextLines` on each side of the error.
private fun errorContextLines(exc: PolyglotException, contextLines: Int = 1): Pair<Int, List<String>> {
val startLine = maxOf(0, (exc.sourceLocation?.startLine ?: 0) - contextLines)
val endLine = exc.sourceLocation?.endLine ?: 0
val errorBase = minOf(0, startLine)
private fun errorContextLines(section: org.graalvm.polyglot.SourceSection?, contextLines: Int = 1): Pair<Int, List<String>> {
val start = (section?.startLine ?: 0)
val end = (section?.endLine ?: 0)
if (start == 0 && end == 0) return 0 to emptyList()

// bail: no lines to show
if (startLine == 0 && endLine == 0) return errorBase to emptyList()
// Convert to 0-based indices and expand with context
val startZero = maxOf(0, start - 1)
val base = maxOf(0, startZero - contextLines)
val lengthInclusive = maxOf(1, end - start + 1)
val totalLines = lengthInclusive + (contextLines * 2)

// otherwise, calculate lines
val totalLines = endLine - startLine + (contextLines * 2)
val ctxLines = ArrayList<String>(totalLines)
val baseOnHand = minOf(0, statementCounter.value)
val errorTail = errorBase + (exc.sourceLocation.endLine - exc.sourceLocation.startLine)
val baseOnHand = maxOf(0, statementCounter.value)
val errorTail = base + maxOf(0, (section?.endLine ?: start) - (section?.startLine ?: start))
val topLine = maxOf(statementCounter.value, errorTail)

return when {
// cannot resolve: we don't have those lines (they are too early for our buffer)
errorBase < baseOnHand -> -1 to emptyList()
base < baseOnHand -> -1 to emptyList()

// otherwise, resolve from seen statements
else -> {
if (allSeenStatements.isNotEmpty()) {
ctxLines.addAll(allSeenStatements.subList(errorBase, minOf(topLine, allSeenStatements.size)))
(errorBase + 1) to ctxLines
val upper = minOf(topLine, allSeenStatements.size)
if (base < upper) ctxLines.addAll(allSeenStatements.subList(base, upper))
(base + 1) to ctxLines
} else when (val target = runnable?.ifBlank { null }) {
// with no runnable, we can't resolve lines by hand
null -> (errorBase + 1) to emptyList()
null -> (base + 1) to emptyList()

// if we can resolve it, pluck the lines from there
// if we can resolve it, pluck the lines from the file
else -> totalLines.takeIf { it > 0 }?.let {
val asPath = runCatching { Paths.get(target) }.getOrNull()
if (asPath != null && asPath.isRegularFile() && asPath.isReadable()) {
val linesFromRange = asPath.bufferedReader(StandardCharsets.UTF_8).use { reader ->
reader.lineSequence().drop(errorBase).take(totalLines).toList()
reader.lineSequence().drop(base).take(totalLines).toList()
}
ctxLines.addAll(linesFromRange)
(errorBase + 1) to ctxLines
(base + 1) to ctxLines
} else null // fallback to below
} ?: ((-1) to emptyList())
}
}
}
}

private fun errorContextLines(exc: PolyglotException, contextLines: Int = 1): Pair<Int, List<String>> =
errorContextLines(exc.sourceLocation, contextLines)

// Determine error printer settings for this run.
private fun errPrinterSettings(): ErrPrinter.ErrPrinterSettings = ErrPrinter.ErrPrinterSettings(
enableColor = pretty,
Expand Down Expand Up @@ -983,9 +988,10 @@ internal class ToolShellCommand : ProjectAwareSubcommand<ToolState, CommandConte
val errContextPrefix = "$errPrefix%lineNum┊ %lineText"
val stopContextPrefix = "$stopPrefix%lineNum┊ %lineText"

val (startingLineNumber, errorContext) = errorContextLines(exc)
val startLine = startingLineNumber + max((exc.sourceLocation?.startLine ?: 0) - 1, 0)
val endLine = startingLineNumber + max((exc.sourceLocation?.endLine ?: 0) - 1, 0)
val bestSection = topGuestFrame?.sourceLocation ?: exc.sourceLocation
val (startingLineNumber, errorContext) = errorContextLines(bestSection)
val startLine = startingLineNumber + max((bestSection?.startLine ?: 0) - 1, 0)
val endLine = startingLineNumber + max((bestSection?.endLine ?: 0) - 1, 0)
val lineDigits = endLine.toString().length

val errRange = startLine..endLine
Expand Down Expand Up @@ -1187,7 +1193,7 @@ internal class ToolShellCommand : ProjectAwareSubcommand<ToolState, CommandConte
)

else -> displayFormattedError(
exc.asHostException(),
exc,
msg ?: exc.asHostException().message ?: "A runtime error was thrown",
advice = "This is an error in Elide. Please report this to the Elide Team with `elide bug`",
stacktrace = true,
Expand Down
10 changes: 8 additions & 2 deletions packages/cli/src/main/kotlin/elide/tool/engine/NativeEngine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -399,9 +399,15 @@ object NativeEngine {
this::class.java.classLoader,
)

// in jvm mode, force-load sqlite
// in jvm mode, force-load sqlite unless explicitly disabled
if (!ImageInfo.inImageCode()) {
nativeLibraryGroups["sqlite"] = SQLiteJDBCLoader.initialize()
val disableSqlite = (System.getProperty("elide.disable.sqlite") == "true") ||
(System.getProperty("elide.disableNatives") == "true") ||
(System.getenv("ELIDE_DISABLE_SQLITE")?.lowercase() == "true") ||
(System.getenv("ELIDE_DISABLE_NATIVES")?.lowercase() == "true")
if (!disableSqlite) {
nativeLibraryGroups["sqlite"] = SQLiteJDBCLoader.initialize()
}
}

// fix: account for static jni
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package elide.tool.cli

import org.apache.commons.io.output.ByteArrayOutputStream
import java.io.PrintStream
import java.nio.file.Files
import java.nio.file.StandardOpenOption
import elide.testing.annotations.Test
import elide.testing.annotations.TestCase
import kotlin.test.assertTrue

@TestCase class ElideToolErrorLocationTest : AbstractEntryTest() {
@Test fun testGuestErrorHighlightsCorrectLine() {
val temp = Files.createTempFile("elide-js-error-test", ".js").toFile()
temp.writeText(
"""
const a = 1;
const b = 2;
throw new Error("boom");
""".trimIndent(),
Charsets.UTF_8
)

// capture stdout and stderr
val stubbedOut = ByteArrayOutputStream()
val stubbedErr = ByteArrayOutputStream()
val originalOut = System.out
val originalErr = System.err
System.setOut(PrintStream(stubbedOut))
System.setErr(PrintStream(stubbedErr))

val code = try {
// Disable native lib loading for JVM test run to avoid requiring sqlite natives
System.setProperty("elide.disable.sqlite", "true")
Elide.exec(arrayOf("run", temp.absolutePath))
} finally {
System.clearProperty("elide.disable.sqlite")
System.setOut(originalOut)
System.setErr(originalErr)
}

val stderr = stubbedErr.toString("UTF-8")
// Should have failed with a non-zero code
assertTrue(code != 0, "expected non-zero exit code, got $code; stderr=\n$stderr")
// Should point to line 3 where the error occurs
assertTrue(
stderr.contains("→ 3┊") || stderr.contains("\u2192 3┊") || stderr.contains("✗ 3┊"),
"expected error marker on line 3; stderr=\n$stderr"
)
}
}

2 changes: 2 additions & 0 deletions packages/exec/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ dependencies {
api(libs.kotlinx.coroutines.jdk8)
api(libs.kotlinx.coroutines.guava)
implementation(libs.guava)
// Needed for ImageInfo.inImageCode() reference in Tracing.kt during JVM builds
compileOnly(libs.graalvm.svm)
testImplementation(libs.kotlin.test.junit5)
testImplementation(libs.kotlinx.coroutines.test)
testRuntimeOnly(libs.logback.core)
Expand Down
Loading