From f16e3e247ddea995595dc2a21eb7a297c4ef51cd Mon Sep 17 00:00:00 2001 From: Brian Dahlem Date: Mon, 8 Sep 2025 13:42:44 -0700 Subject: [PATCH 1/4] test: verify diagnostics for failed in-memory compilation --- .../compiler/CompileDiagnosticListener.java | 54 +++++++++++-------- .../run/myCode/compiler/JavaCodeCompiler.java | 31 +++++++---- .../compiler/CompileDiagnosticListener.java | 50 ++++++++++------- .../java/zss/compiler/MemoryCompiler.java | 32 ++++++----- .../myCode/compiler/JavaCodeCompilerTest.java | 35 ++++++++++++ .../java/zss/compiler/MemoryCompilerTest.java | 37 +++++++++++++ 6 files changed, 175 insertions(+), 64 deletions(-) create mode 100644 src/test/java/run/myCode/compiler/JavaCodeCompilerTest.java create mode 100644 src/test/java/zss/compiler/MemoryCompilerTest.java diff --git a/src/main/java/run/myCode/compiler/CompileDiagnosticListener.java b/src/main/java/run/myCode/compiler/CompileDiagnosticListener.java index b9dd0b5..2b069f4 100644 --- a/src/main/java/run/myCode/compiler/CompileDiagnosticListener.java +++ b/src/main/java/run/myCode/compiler/CompileDiagnosticListener.java @@ -1,23 +1,31 @@ -package run.myCode.compiler; - -import java.util.Locale; - -import javax.tools.Diagnostic; -import javax.tools.DiagnosticListener; -import javax.tools.JavaFileObject; - -@SuppressWarnings("unused") -public class CompileDiagnosticListener implements DiagnosticListener -{ - @Override - public void report(Diagnostic diagnostic) - { - /* - System.out.println("Line Number->" + diagnostic.getLineNumber()); - System.out.println("code->" + diagnostic.getCode()); - System.out.println("Message->" + diagnostic.getMessage(Locale.ENGLISH)); - System.out.println("Source->" + diagnostic.getSource()); - System.out.println(" "); - */ - } -} \ No newline at end of file +package run.myCode.compiler; + +import java.util.ArrayList; +import java.util.List; + +import javax.tools.Diagnostic; +import javax.tools.DiagnosticListener; +import javax.tools.JavaFileObject; + +/** + * Collects diagnostics reported during compilation so that callers can display + * meaningful error messages. + */ +public class CompileDiagnosticListener implements DiagnosticListener { + + private final List> diagnostics = new ArrayList<>(); + + @Override + public void report(Diagnostic diagnostic) { + diagnostics.add(diagnostic); + } + + /** + * Retrieve all diagnostics that were reported during compilation. + * + * @return a list of diagnostics + */ + public List> getDiagnostics() { + return diagnostics; + } +} diff --git a/src/main/java/run/myCode/compiler/JavaCodeCompiler.java b/src/main/java/run/myCode/compiler/JavaCodeCompiler.java index 09cdbb1..aa2bb5d 100644 --- a/src/main/java/run/myCode/compiler/JavaCodeCompiler.java +++ b/src/main/java/run/myCode/compiler/JavaCodeCompiler.java @@ -13,11 +13,12 @@ import java.util.logging.Level; import java.util.logging.Logger; -import javax.tools.JavaCompiler; -import javax.tools.JavaFileObject; -import javax.tools.StandardJavaFileManager; - -import javax.tools.ToolProvider; +import javax.tools.Diagnostic; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; + +import javax.tools.ToolProvider; public class JavaCodeCompiler { @@ -117,10 +118,20 @@ public static FromMemoryClassLoader compile(Iterable f if (DEBUG) { System.out.println("Starting compilation with mem URIs"); } - boolean result = task.call(); - - // Return the classloader containing the compiled classes - return classLoader; - } + boolean result = task.call(); + + if (!result) { + for (Diagnostic d : diag.getDiagnostics()) { + System.out.println("Line Number->" + d.getLineNumber()); + System.out.println("Message->" + d.getMessage(Locale.ENGLISH)); + System.out.println("Source->" + d.getSource()); + System.out.println(); + } + throw new ClassNotFoundException("Compilation failed"); + } + + // Return the classloader containing the compiled classes + return classLoader; + } } diff --git a/src/main/java/zss/compiler/CompileDiagnosticListener.java b/src/main/java/zss/compiler/CompileDiagnosticListener.java index f86b384..395d871 100644 --- a/src/main/java/zss/compiler/CompileDiagnosticListener.java +++ b/src/main/java/zss/compiler/CompileDiagnosticListener.java @@ -1,19 +1,31 @@ -package zss.compiler; - -import javax.tools.Diagnostic; -import javax.tools.DiagnosticListener; -import javax.tools.JavaFileObject; - -public class CompileDiagnosticListener implements DiagnosticListener { - - @Override - public void report(Diagnostic diagnostic) { - - // System.err.println("Line Number->" + diagnostic.getLineNumber()); - // System.err.println("code->" + diagnostic.getCode()); - // System.err.println("Message->" + diagnostic.getMessage(Locale.ENGLISH)); - // System.err.println("Source->" + diagnostic.getSource()); - // System.err.println(" "); - - } -} +package zss.compiler; + +import java.util.ArrayList; +import java.util.List; + +import javax.tools.Diagnostic; +import javax.tools.DiagnosticListener; +import javax.tools.JavaFileObject; + +/** + * Collects diagnostics reported during compilation so that callers can display + * meaningful error messages. + */ +public class CompileDiagnosticListener implements DiagnosticListener { + + private final List> diagnostics = new ArrayList<>(); + + @Override + public void report(Diagnostic diagnostic) { + diagnostics.add(diagnostic); + } + + /** + * Retrieve all diagnostics that were reported during compilation. + * + * @return a list of diagnostics + */ + public List> getDiagnostics() { + return diagnostics; + } +} diff --git a/src/main/java/zss/compiler/MemoryCompiler.java b/src/main/java/zss/compiler/MemoryCompiler.java index 6787a0f..7ca5c3e 100644 --- a/src/main/java/zss/compiler/MemoryCompiler.java +++ b/src/main/java/zss/compiler/MemoryCompiler.java @@ -8,11 +8,12 @@ import java.util.List; import java.util.Locale; -import javax.tools.JavaCompiler; -import javax.tools.JavaFileObject; -import javax.tools.StandardJavaFileManager; - -import javax.tools.ToolProvider; +import javax.tools.Diagnostic; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; + +import javax.tools.ToolProvider; /** * Compiles in-memory source files into byte code using the standard JDK @@ -60,10 +61,17 @@ public static FromMemoryClassLoader compile(Iterable f Writer out = new PrintWriter(System.out); JavaCompiler.CompilationTask task = compiler.getTask(out, fileManager, diag, options, null, files); - Boolean result = task.call(); - if (result == true) { - return classLoader; - } - return null; - } -} + Boolean result = task.call(); + if (Boolean.TRUE.equals(result)) { + return classLoader; + } + + for (Diagnostic d : diag.getDiagnostics()) { + System.out.println("Line Number->" + d.getLineNumber()); + System.out.println("Message->" + d.getMessage(Locale.ENGLISH)); + System.out.println("Source->" + d.getSource()); + System.out.println(); + } + return null; + } +} diff --git a/src/test/java/run/myCode/compiler/JavaCodeCompilerTest.java b/src/test/java/run/myCode/compiler/JavaCodeCompilerTest.java new file mode 100644 index 0000000..3370f33 --- /dev/null +++ b/src/test/java/run/myCode/compiler/JavaCodeCompilerTest.java @@ -0,0 +1,35 @@ +package run.myCode.compiler; + +import static org.junit.Assert.*; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.List; + +import org.junit.Test; + +import javax.tools.JavaFileObject; + +public class JavaCodeCompilerTest { + + @Test + public void compileInvalidSourcePrintsDiagnostics() { + String sourceCode = "public class Broken { public static void main(String[] args) { System.out.println(\"hi\") } }"; + JavaFileObject source = new InMemoryJavaFileObject("Broken.java", sourceCode); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream originalOut = System.out; + System.setOut(new PrintStream(baos)); + try { + ClassNotFoundException ex = assertThrows(ClassNotFoundException.class, + () -> JavaCodeCompiler.compile(List.of(source), null)); + assertEquals("Compilation failed", ex.getMessage()); + } finally { + System.setOut(originalOut); + } + + String output = baos.toString(); + assertTrue(output.contains("Line Number")); + assertTrue(output.contains("Message->")); + } +} diff --git a/src/test/java/zss/compiler/MemoryCompilerTest.java b/src/test/java/zss/compiler/MemoryCompilerTest.java new file mode 100644 index 0000000..33ff305 --- /dev/null +++ b/src/test/java/zss/compiler/MemoryCompilerTest.java @@ -0,0 +1,37 @@ +package zss.compiler; + +import static org.junit.Assert.*; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.List; + +import org.junit.Test; + +import javax.tools.JavaFileObject; + +public class MemoryCompilerTest { + + @Test + public void compileInvalidSourcePrintsDiagnostics() throws Exception { + String sourceCode = "public class Broken { public static void main(String[] args) { System.out.println(\"hi\") } }"; + JavaFileObject source = new InMemoryJavaFileObject("Broken.java", sourceCode); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream originalOut = System.out; + System.setOut(new PrintStream(baos)); + try { + URLClassLoader parent = new URLClassLoader(new URL[0], ClassLoader.getSystemClassLoader()); + FromMemoryClassLoader loader = MemoryCompiler.compile(List.of(source), parent); + assertNull(loader); + } finally { + System.setOut(originalOut); + } + + String output = baos.toString(); + assertTrue(output.contains("Line Number")); + assertTrue(output.contains("Message->")); + } +} From 2cd26bf3ea370aa455950598833715e3c44c77e1 Mon Sep 17 00:00:00 2001 From: Brian Dahlem Date: Mon, 8 Sep 2025 13:56:17 -0700 Subject: [PATCH 2/4] Move compiler failure tests to example integration --- src/test/java/example/HelloFailureTest.java | 61 +++++++++++++++++++ .../myCode/compiler/JavaCodeCompilerTest.java | 35 ----------- .../java/zss/compiler/MemoryCompilerTest.java | 37 ----------- .../resources/jsonDataFiles/brokenLocal.json | 21 +++++++ 4 files changed, 82 insertions(+), 72 deletions(-) create mode 100644 src/test/java/example/HelloFailureTest.java delete mode 100644 src/test/java/run/myCode/compiler/JavaCodeCompilerTest.java delete mode 100644 src/test/java/zss/compiler/MemoryCompilerTest.java create mode 100644 src/test/resources/jsonDataFiles/brokenLocal.json diff --git a/src/test/java/example/HelloFailureTest.java b/src/test/java/example/HelloFailureTest.java new file mode 100644 index 0000000..8fbef83 --- /dev/null +++ b/src/test/java/example/HelloFailureTest.java @@ -0,0 +1,61 @@ +package example; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ByteArrayOutputStream; + +import org.junit.Test; +import static org.junit.Assert.*; + +import com.amazonaws.services.lambda.runtime.Context; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +import run.myCode.CompileResponse; +import run.myCode.Hello; + +/** + * Tests compilation failures for the Hello lambda. + */ +public class HelloFailureTest { + private Context createContext() { + TestContext ctx = new TestContext(); + + ctx.setFunctionName("HelloFunction"); + + return ctx; + } + + @Test + public void testCompilationFailureShowsDiagnostics() { + CompileResponse resp = doTest("jsonDataFiles/brokenLocal.json"); + + assertFalse("Compiler returned no response", resp == null); + assertFalse("Compilation unexpectedly succeeded", resp.getSucceeded()); + assertTrue("Missing line number in diagnostics", resp.getResult().contains("Line Number")); + assertTrue("Missing message in diagnostics", resp.getResult().contains("Message->")); + + System.out.println("Result: " + resp.getResult()); + } + + private CompileResponse doTest(String resourceName) { + Hello handler = new Hello(); + + System.out.println("Working Directory: " + System.getProperty("java.io.tmpdir")); + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + CompileResponse resp = null; + + try (InputStream input = this.getClass().getClassLoader().getResourceAsStream(resourceName)) { + handler.handleRequest(input, outContent, createContext()); + resp = mapper.readValue(outContent.toString(), CompileResponse.class); + } + catch (IOException e) { + throw new AssertionError(e); + } + + return resp; + } +} diff --git a/src/test/java/run/myCode/compiler/JavaCodeCompilerTest.java b/src/test/java/run/myCode/compiler/JavaCodeCompilerTest.java deleted file mode 100644 index 3370f33..0000000 --- a/src/test/java/run/myCode/compiler/JavaCodeCompilerTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package run.myCode.compiler; - -import static org.junit.Assert.*; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.util.List; - -import org.junit.Test; - -import javax.tools.JavaFileObject; - -public class JavaCodeCompilerTest { - - @Test - public void compileInvalidSourcePrintsDiagnostics() { - String sourceCode = "public class Broken { public static void main(String[] args) { System.out.println(\"hi\") } }"; - JavaFileObject source = new InMemoryJavaFileObject("Broken.java", sourceCode); - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - PrintStream originalOut = System.out; - System.setOut(new PrintStream(baos)); - try { - ClassNotFoundException ex = assertThrows(ClassNotFoundException.class, - () -> JavaCodeCompiler.compile(List.of(source), null)); - assertEquals("Compilation failed", ex.getMessage()); - } finally { - System.setOut(originalOut); - } - - String output = baos.toString(); - assertTrue(output.contains("Line Number")); - assertTrue(output.contains("Message->")); - } -} diff --git a/src/test/java/zss/compiler/MemoryCompilerTest.java b/src/test/java/zss/compiler/MemoryCompilerTest.java deleted file mode 100644 index 33ff305..0000000 --- a/src/test/java/zss/compiler/MemoryCompilerTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package zss.compiler; - -import static org.junit.Assert.*; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.List; - -import org.junit.Test; - -import javax.tools.JavaFileObject; - -public class MemoryCompilerTest { - - @Test - public void compileInvalidSourcePrintsDiagnostics() throws Exception { - String sourceCode = "public class Broken { public static void main(String[] args) { System.out.println(\"hi\") } }"; - JavaFileObject source = new InMemoryJavaFileObject("Broken.java", sourceCode); - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - PrintStream originalOut = System.out; - System.setOut(new PrintStream(baos)); - try { - URLClassLoader parent = new URLClassLoader(new URL[0], ClassLoader.getSystemClassLoader()); - FromMemoryClassLoader loader = MemoryCompiler.compile(List.of(source), parent); - assertNull(loader); - } finally { - System.setOut(originalOut); - } - - String output = baos.toString(); - assertTrue(output.contains("Line Number")); - assertTrue(output.contains("Message->")); - } -} diff --git a/src/test/resources/jsonDataFiles/brokenLocal.json b/src/test/resources/jsonDataFiles/brokenLocal.json new file mode 100644 index 0000000..b23ffb9 --- /dev/null +++ b/src/test/resources/jsonDataFiles/brokenLocal.json @@ -0,0 +1,21 @@ +{ +"body": { + "compile": { + "mainClass": "Broken", + "sourceFiles": [{ + "name": "Broken.java", + "contents": [ + "public class Broken {", + " public static void main(String[] args) {", + " System.out.println(\"hi\")", + " }", + "}" + ] + }] + }, + "test-type": "run" +}, +"params": {}, +"context": {}, +"stage-variables": {} +} From e45eca6038e1a1dbf8dc7480600b66364470688c Mon Sep 17 00:00:00 2001 From: Brian Dahlem Date: Mon, 8 Sep 2025 14:03:29 -0700 Subject: [PATCH 3/4] Improve in-memory compiler diagnostics --- .../compiler/CompileDiagnosticListener.java | 50 +++++++++++++++++++ .../run/myCode/compiler/JavaCodeCompiler.java | 14 ++---- .../compiler/CompileDiagnosticListener.java | 50 +++++++++++++++++++ .../java/zss/compiler/MemoryCompiler.java | 11 ++-- src/test/java/example/HelloFailureTest.java | 5 +- 5 files changed, 112 insertions(+), 18 deletions(-) diff --git a/src/main/java/run/myCode/compiler/CompileDiagnosticListener.java b/src/main/java/run/myCode/compiler/CompileDiagnosticListener.java index 2b069f4..893705e 100644 --- a/src/main/java/run/myCode/compiler/CompileDiagnosticListener.java +++ b/src/main/java/run/myCode/compiler/CompileDiagnosticListener.java @@ -1,7 +1,10 @@ package run.myCode.compiler; +import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import javax.tools.Diagnostic; import javax.tools.DiagnosticListener; @@ -28,4 +31,51 @@ public void report(Diagnostic diagnostic) { public List> getDiagnostics() { return diagnostics; } + + /** + * Format collected diagnostics into human readable messages that include the + * source line and a caret pointing to the error column when available. + * + * @return list of formatted diagnostic strings + */ + public List getFormattedDiagnostics() { + List messages = new ArrayList<>(); + for (Diagnostic d : diagnostics) { + messages.add(formatDiagnostic(d)); + } + return messages; + } + + private String formatDiagnostic(Diagnostic d) { + StringBuilder sb = new StringBuilder(); + String sourceName = d.getSource() == null ? "Unknown Source" + : new File(d.getSource().getName()).getName(); + sb.append(sourceName).append(':').append(d.getLineNumber()).append(':') + .append(' ').append(d.getKind().toString().toLowerCase(Locale.ENGLISH)) + .append(':').append(' ').append(d.getMessage(Locale.ENGLISH)); + + try { + if (d.getSource() != null) { + CharSequence content = d.getSource().getCharContent(true); + String[] lines = content.toString().split("\r?\n"); + long lineNo = d.getLineNumber(); + if (lineNo > 0 && lineNo <= lines.length) { + String line = lines[(int) lineNo - 1]; + sb.append(System.lineSeparator()).append(line) + .append(System.lineSeparator()); + long col = d.getColumnNumber(); + if (col > 0) { + for (int i = 1; i < col; i++) { + sb.append(' '); + } + sb.append('^'); + } + } + } + } catch (IOException e) { + // ignore - if we can't read the source, just return the basic message + } + + return sb.toString(); + } } diff --git a/src/main/java/run/myCode/compiler/JavaCodeCompiler.java b/src/main/java/run/myCode/compiler/JavaCodeCompiler.java index aa2bb5d..771b83e 100644 --- a/src/main/java/run/myCode/compiler/JavaCodeCompiler.java +++ b/src/main/java/run/myCode/compiler/JavaCodeCompiler.java @@ -9,15 +9,13 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Locale; -import java.util.logging.Level; -import java.util.logging.Logger; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; -import javax.tools.Diagnostic; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; - import javax.tools.ToolProvider; public class JavaCodeCompiler { @@ -121,10 +119,8 @@ public static FromMemoryClassLoader compile(Iterable f boolean result = task.call(); if (!result) { - for (Diagnostic d : diag.getDiagnostics()) { - System.out.println("Line Number->" + d.getLineNumber()); - System.out.println("Message->" + d.getMessage(Locale.ENGLISH)); - System.out.println("Source->" + d.getSource()); + for (String msg : diag.getFormattedDiagnostics()) { + System.out.println(msg); System.out.println(); } throw new ClassNotFoundException("Compilation failed"); diff --git a/src/main/java/zss/compiler/CompileDiagnosticListener.java b/src/main/java/zss/compiler/CompileDiagnosticListener.java index 395d871..f344570 100644 --- a/src/main/java/zss/compiler/CompileDiagnosticListener.java +++ b/src/main/java/zss/compiler/CompileDiagnosticListener.java @@ -1,7 +1,10 @@ package zss.compiler; +import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import javax.tools.Diagnostic; import javax.tools.DiagnosticListener; @@ -28,4 +31,51 @@ public void report(Diagnostic diagnostic) { public List> getDiagnostics() { return diagnostics; } + + /** + * Format collected diagnostics into human readable messages that include the + * source line and a caret pointing to the error column when available. + * + * @return list of formatted diagnostic strings + */ + public List getFormattedDiagnostics() { + List messages = new ArrayList<>(); + for (Diagnostic d : diagnostics) { + messages.add(formatDiagnostic(d)); + } + return messages; + } + + private String formatDiagnostic(Diagnostic d) { + StringBuilder sb = new StringBuilder(); + String sourceName = d.getSource() == null ? "Unknown Source" + : new File(d.getSource().getName()).getName(); + sb.append(sourceName).append(':').append(d.getLineNumber()).append(':') + .append(' ').append(d.getKind().toString().toLowerCase(Locale.ENGLISH)) + .append(':').append(' ').append(d.getMessage(Locale.ENGLISH)); + + try { + if (d.getSource() != null) { + CharSequence content = d.getSource().getCharContent(true); + String[] lines = content.toString().split("\r?\n"); + long lineNo = d.getLineNumber(); + if (lineNo > 0 && lineNo <= lines.length) { + String line = lines[(int) lineNo - 1]; + sb.append(System.lineSeparator()).append(line) + .append(System.lineSeparator()); + long col = d.getColumnNumber(); + if (col > 0) { + for (int i = 1; i < col; i++) { + sb.append(' '); + } + sb.append('^'); + } + } + } + } catch (IOException e) { + // ignore - if we can't read the source, just return the basic message + } + + return sb.toString(); + } } diff --git a/src/main/java/zss/compiler/MemoryCompiler.java b/src/main/java/zss/compiler/MemoryCompiler.java index 7ca5c3e..ebd56fc 100644 --- a/src/main/java/zss/compiler/MemoryCompiler.java +++ b/src/main/java/zss/compiler/MemoryCompiler.java @@ -6,9 +6,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Locale; - -import javax.tools.Diagnostic; +import java.util.Locale; + import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; @@ -66,10 +65,8 @@ public static FromMemoryClassLoader compile(Iterable f return classLoader; } - for (Diagnostic d : diag.getDiagnostics()) { - System.out.println("Line Number->" + d.getLineNumber()); - System.out.println("Message->" + d.getMessage(Locale.ENGLISH)); - System.out.println("Source->" + d.getSource()); + for (String msg : diag.getFormattedDiagnostics()) { + System.out.println(msg); System.out.println(); } return null; diff --git a/src/test/java/example/HelloFailureTest.java b/src/test/java/example/HelloFailureTest.java index 8fbef83..d7b40b1 100644 --- a/src/test/java/example/HelloFailureTest.java +++ b/src/test/java/example/HelloFailureTest.java @@ -32,8 +32,9 @@ public void testCompilationFailureShowsDiagnostics() { assertFalse("Compiler returned no response", resp == null); assertFalse("Compilation unexpectedly succeeded", resp.getSucceeded()); - assertTrue("Missing line number in diagnostics", resp.getResult().contains("Line Number")); - assertTrue("Missing message in diagnostics", resp.getResult().contains("Message->")); + assertTrue("Missing file/line in diagnostics", resp.getResult().contains("Broken.java:3")); + assertTrue("Missing caret in diagnostics", resp.getResult().contains("^")); + assertTrue("Missing source line", resp.getResult().contains("System.out.println(\"hi\")")); System.out.println("Result: " + resp.getResult()); } From b09016ca92e59f01ca2e8a42b09c927317eed807 Mon Sep 17 00:00:00 2001 From: Brian Dahlem Date: Mon, 8 Sep 2025 14:15:57 -0700 Subject: [PATCH 4/4] Print file info after source excerpt in diagnostics --- .../compiler/CompileDiagnosticListener.java | 17 ++++++++++++----- .../zss/compiler/CompileDiagnosticListener.java | 17 ++++++++++++----- src/test/java/example/HelloFailureTest.java | 15 +++++++++++---- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/main/java/run/myCode/compiler/CompileDiagnosticListener.java b/src/main/java/run/myCode/compiler/CompileDiagnosticListener.java index 893705e..6064655 100644 --- a/src/main/java/run/myCode/compiler/CompileDiagnosticListener.java +++ b/src/main/java/run/myCode/compiler/CompileDiagnosticListener.java @@ -50,10 +50,8 @@ private String formatDiagnostic(Diagnostic d) { StringBuilder sb = new StringBuilder(); String sourceName = d.getSource() == null ? "Unknown Source" : new File(d.getSource().getName()).getName(); - sb.append(sourceName).append(':').append(d.getLineNumber()).append(':') - .append(' ').append(d.getKind().toString().toLowerCase(Locale.ENGLISH)) - .append(':').append(' ').append(d.getMessage(Locale.ENGLISH)); + boolean appendedSource = false; try { if (d.getSource() != null) { CharSequence content = d.getSource().getCharContent(true); @@ -61,8 +59,7 @@ private String formatDiagnostic(Diagnostic d) { long lineNo = d.getLineNumber(); if (lineNo > 0 && lineNo <= lines.length) { String line = lines[(int) lineNo - 1]; - sb.append(System.lineSeparator()).append(line) - .append(System.lineSeparator()); + sb.append(line).append(System.lineSeparator()); long col = d.getColumnNumber(); if (col > 0) { for (int i = 1; i < col; i++) { @@ -70,12 +67,22 @@ private String formatDiagnostic(Diagnostic d) { } sb.append('^'); } + sb.append(System.lineSeparator()); + appendedSource = true; } } } catch (IOException e) { // ignore - if we can't read the source, just return the basic message } + if (!appendedSource && sb.length() > 0) { + sb.append(System.lineSeparator()); + } + + sb.append(sourceName).append(':').append(d.getLineNumber()).append(':') + .append(' ').append(d.getKind().toString().toLowerCase(Locale.ENGLISH)) + .append(':').append(' ').append(d.getMessage(Locale.ENGLISH)); + return sb.toString(); } } diff --git a/src/main/java/zss/compiler/CompileDiagnosticListener.java b/src/main/java/zss/compiler/CompileDiagnosticListener.java index f344570..732a3b4 100644 --- a/src/main/java/zss/compiler/CompileDiagnosticListener.java +++ b/src/main/java/zss/compiler/CompileDiagnosticListener.java @@ -50,10 +50,8 @@ private String formatDiagnostic(Diagnostic d) { StringBuilder sb = new StringBuilder(); String sourceName = d.getSource() == null ? "Unknown Source" : new File(d.getSource().getName()).getName(); - sb.append(sourceName).append(':').append(d.getLineNumber()).append(':') - .append(' ').append(d.getKind().toString().toLowerCase(Locale.ENGLISH)) - .append(':').append(' ').append(d.getMessage(Locale.ENGLISH)); + boolean appendedSource = false; try { if (d.getSource() != null) { CharSequence content = d.getSource().getCharContent(true); @@ -61,8 +59,7 @@ private String formatDiagnostic(Diagnostic d) { long lineNo = d.getLineNumber(); if (lineNo > 0 && lineNo <= lines.length) { String line = lines[(int) lineNo - 1]; - sb.append(System.lineSeparator()).append(line) - .append(System.lineSeparator()); + sb.append(line).append(System.lineSeparator()); long col = d.getColumnNumber(); if (col > 0) { for (int i = 1; i < col; i++) { @@ -70,12 +67,22 @@ private String formatDiagnostic(Diagnostic d) { } sb.append('^'); } + sb.append(System.lineSeparator()); + appendedSource = true; } } } catch (IOException e) { // ignore - if we can't read the source, just return the basic message } + if (!appendedSource && sb.length() > 0) { + sb.append(System.lineSeparator()); + } + + sb.append(sourceName).append(':').append(d.getLineNumber()).append(':') + .append(' ').append(d.getKind().toString().toLowerCase(Locale.ENGLISH)) + .append(':').append(' ').append(d.getMessage(Locale.ENGLISH)); + return sb.toString(); } } diff --git a/src/test/java/example/HelloFailureTest.java b/src/test/java/example/HelloFailureTest.java index d7b40b1..e503afe 100644 --- a/src/test/java/example/HelloFailureTest.java +++ b/src/test/java/example/HelloFailureTest.java @@ -32,11 +32,18 @@ public void testCompilationFailureShowsDiagnostics() { assertFalse("Compiler returned no response", resp == null); assertFalse("Compilation unexpectedly succeeded", resp.getSucceeded()); - assertTrue("Missing file/line in diagnostics", resp.getResult().contains("Broken.java:3")); - assertTrue("Missing caret in diagnostics", resp.getResult().contains("^")); - assertTrue("Missing source line", resp.getResult().contains("System.out.println(\"hi\")")); + String diag = resp.getResult(); + assertTrue("Missing source line", diag.contains("System.out.println(\"hi\")")); + assertTrue("Missing caret in diagnostics", diag.contains("^")); + assertTrue("Missing file/line in diagnostics", diag.contains("Broken.java:3")); - System.out.println("Result: " + resp.getResult()); + int lineIdx = diag.indexOf("System.out.println(\"hi\")"); + int caretIdx = diag.indexOf("^"); + int fileIdx = diag.indexOf("Broken.java:3"); + assertTrue("Caret should follow source line", lineIdx >= 0 && caretIdx > lineIdx); + assertTrue("File info should follow caret", fileIdx > caretIdx); + + System.out.println("Result: " + diag); } private CompileResponse doTest(String resourceName) {