summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2026-07-30 21:29:52 +0100
committerMichael Williamson <mike@zwobble.org>2026-07-30 21:29:52 +0100
commit2ff551c30f62283bd9faca4ad8e105b9a951abab (patch)
tree057f06a0b1e720e90aaace48e88e8f881d297889
parent00f2fff352e22fb83c0de38e6e5f70f4d1a0fea1 (diff)
Do not write to code file if header is missing
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/output/CodeWriter.java58
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/output/generators/java/JavaGenerator.java8
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/output/generators/rust/RustGenerator.java4
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriter.java24
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/output/lang/rust/RustWriter.java24
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/output/lang/CodeWriterTests.java99
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriterTests.java20
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/output/lang/rust/RustWriterTests.java16
8 files changed, 167 insertions, 86 deletions
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/CodeWriter.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/CodeWriter.java
index a0b9005..81fd091 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/output/CodeWriter.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/CodeWriter.java
@@ -18,6 +18,7 @@ public class CodeWriter implements AutoCloseable {
public static class InMemoryCodeWriterFile implements CodeWriterFile {
private final Optional<String> originalContents;
private final StringWriter stringWriter;
+ private boolean writerOpened;
public InMemoryCodeWriterFile(
Optional<String> originalContents,
@@ -25,6 +26,7 @@ public class CodeWriter implements AutoCloseable {
) {
this.originalContents = originalContents;
this.stringWriter = stringWriter;
+ this.writerOpened = false;
}
@Override
@@ -38,8 +40,13 @@ public class CodeWriter implements AutoCloseable {
@Override
public Writer openWriter() throws IOException {
+ this.writerOpened = true;
return stringWriter;
}
+
+ public boolean isWriterOpened() {
+ return writerOpened;
+ }
}
public record CustomAreas(
@@ -90,11 +97,16 @@ public class CodeWriter implements AutoCloseable {
}
}
- public static CodeWriter file(
+ public interface Write {
+ void write(CodeWriter writer) throws IOException;
+ }
+
+ public static void writeTo(
Path path,
- String lineCommentStart
+ String lineCommentStart,
+ Write write
) throws IOException {
- return file(
+ writeTo(
new CodeWriterFile() {
@Override
public String readString() throws IOException {
@@ -107,20 +119,31 @@ public class CodeWriter implements AutoCloseable {
return new FileWriter(path.toFile());
}
},
- lineCommentStart
+ lineCommentStart,
+ write
);
}
- public static CodeWriter file(
+ public static void writeTo(
CodeWriterFile file,
- String lineCommentStart
+ String lineCommentStart,
+ Write write
) throws IOException {
+ var originalContents = readFile(file);
+
+ var header = lineCommentStart + " Generated by hobgoblin.\n\n";
+ if (originalContents.isPresent() && !originalContents.get().startsWith(header)) {
+ return;
+ }
+
var customAreaStart = lineCommentStart + " Custom area start: ";
var customAreaEnd = lineCommentStart + " Custom area end: ";
+
var customAreas = readCustomAreas(file, customAreaStart, customAreaEnd);
- var writer = file.openWriter();
- // TODO: warn if custom areas are unused?
- return new CodeWriter(writer, lineCommentStart, customAreas);
+ try (var writer = file.openWriter()) {
+ // TODO: warn if custom areas are unused?
+ write.write(new CodeWriter(writer, header, customAreas));
+ }
}
private static CustomAreas readCustomAreas(
@@ -136,23 +159,30 @@ public class CodeWriter implements AutoCloseable {
}
}
+ private static Optional<String> readFile(CodeWriterFile file) throws IOException {
+ try {
+ return Optional.of(file.readString());
+ } catch (NoSuchFileException exception) {
+ return Optional.empty();
+ }
+ }
+
private final Writer writer;
- private final String lineCommentStart;
+ private final String header;
private final CustomAreas customAreas;
private int indent;
private boolean pendingIndent;
- public CodeWriter(Writer writer, String lineCommentStart, CustomAreas customAreas) {
+ public CodeWriter(Writer writer, String header, CustomAreas customAreas) {
this.writer = writer;
- this.lineCommentStart = lineCommentStart;
+ this.header = header;
this.customAreas = customAreas;
this.indent = 0;
this.pendingIndent = false;
}
public void writeHeader() throws IOException {
- writer.write(lineCommentStart);
- writer.write(" Generated by hobgoblin.\n\n");
+ writer.write(header);
}
public void write(String value) throws IOException {
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/java/JavaGenerator.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/java/JavaGenerator.java
index 1325149..d7f4bb0 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/java/JavaGenerator.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/java/JavaGenerator.java
@@ -29,9 +29,11 @@ public class JavaGenerator {
var javaCompilationUnitPath = sourceRootDirectory
.resolve(javaCompilationUnit.packageName().parts().stream().map(JavaIdentifier::value).collect(Collectors.joining(File.separator)))
.resolve(javaCompilationUnit.typeDeclaration().name().value() + ".java");
- try (var writer = JavaWriter.file(javaCompilationUnitPath)) {
- writer.writeCompilationUnit(javaCompilationUnit);
- }
+
+ JavaWriter.writeTo(
+ javaCompilationUnitPath,
+ writer -> writer.writeCompilationUnit(javaCompilationUnit)
+ );
}
}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/rust/RustGenerator.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/rust/RustGenerator.java
index 0a0290d..22b6d7d 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/rust/RustGenerator.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/rust/RustGenerator.java
@@ -27,9 +27,9 @@ public class RustGenerator {
.map(name -> name.value())
.collect(Collectors.joining(File.separator)) + ".rs"
);
- try (var writer = RustWriter.file(path)) {
+ RustWriter.writeTo(path, writer -> {
writer.writeModule(rustModule);
- }
+ });
}
public RustPath generateRustTypeExpression(Type type) {
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriter.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriter.java
index b593364..7af7e01 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriter.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriter.java
@@ -15,18 +15,24 @@ import static org.zwobble.hobgoblin.compiler.output.CodeWriter.writeWithSeparato
public class JavaWriter implements AutoCloseable {
private static final String LINE_COMMENT_START = "//";
- public static JavaWriter file(CodeWriter.CodeWriterFile file) throws IOException {
- return new JavaWriter(CodeWriter.file(
- file,
- LINE_COMMENT_START
- ));
+ public interface Write {
+ void write(JavaWriter writer) throws IOException;
}
- public static JavaWriter file(Path path) throws IOException {
- return new JavaWriter(CodeWriter.file(
+ public static void writeTo(Path path, Write write) throws IOException {
+ CodeWriter.writeTo(
path,
- LINE_COMMENT_START
- ));
+ LINE_COMMENT_START,
+ writer -> write.write(new JavaWriter(writer))
+ );
+ }
+
+ public static void writeTo(CodeWriter.CodeWriterFile file, Write write) throws IOException {
+ CodeWriter.writeTo(
+ file,
+ LINE_COMMENT_START,
+ writer -> write.write(new JavaWriter(writer))
+ );
}
private final CodeWriter writer;
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/rust/RustWriter.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/rust/RustWriter.java
index 2d7e9ed..d0552ad 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/rust/RustWriter.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/rust/RustWriter.java
@@ -15,18 +15,24 @@ import static org.zwobble.hobgoblin.compiler.output.CodeWriter.writeWithSeparato
public class RustWriter implements AutoCloseable {
private static final String LINE_COMMENT_START = "//";
- public static RustWriter file(CodeWriter.CodeWriterFile file) throws IOException {
- return new RustWriter(CodeWriter.file(
- file,
- LINE_COMMENT_START
- ));
+ public interface Write {
+ void write(RustWriter writer) throws IOException;
}
- public static RustWriter file(Path path) throws IOException {
- return new RustWriter(CodeWriter.file(
+ public static void writeTo(Path path, Write write) throws IOException {
+ CodeWriter.writeTo(
path,
- LINE_COMMENT_START
- ));
+ LINE_COMMENT_START,
+ writer -> write.write(new RustWriter(writer))
+ );
+ }
+
+ public static void writeTo(CodeWriter.CodeWriterFile file, Write write) throws IOException {
+ CodeWriter.writeTo(
+ file,
+ LINE_COMMENT_START,
+ writer -> write.write(new RustWriter(writer))
+ );
}
private final CodeWriter writer;
diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/CodeWriterTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/CodeWriterTests.java
index b0ff40c..2b3ad6d 100644
--- a/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/CodeWriterTests.java
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/CodeWriterTests.java
@@ -14,17 +14,21 @@ public class CodeWriterTests {
@Test
public void whenFileDoesNotExistThenCustomAreasAreEmpty() throws IOException {
var stringWriter = new StringWriter();
- var codeWriter = CodeWriter.file(
- new CodeWriter.InMemoryCodeWriterFile(Optional.empty(), stringWriter),
- "//"
+ var file = new CodeWriter.InMemoryCodeWriterFile(Optional.empty(), stringWriter);
+
+ CodeWriter.writeTo(
+ file,
+ "//",
+ codeWriter -> {
+ codeWriter.writeHeader();
+ codeWriter.write("Before");
+ codeWriter.newLine();
+ codeWriter.writeCustomArea("body");
+ codeWriter.write("After");
+ codeWriter.newLine();
+ }
);
- codeWriter.writeHeader();
- codeWriter.write("Before");
- codeWriter.newLine();
- codeWriter.writeCustomArea("body");
- codeWriter.write("After");
- codeWriter.newLine();
assertThat(stringWriter.toString(), equalTo("""
// Generated by hobgoblin.
@@ -39,21 +43,28 @@ public class CodeWriterTests {
@Test
public void whenExistingFileIsMissingCustomAreaThenCustomAreaIsEmpty() throws IOException {
var stringWriter = new StringWriter();
- var codeWriter = CodeWriter.file(
- new CodeWriter.InMemoryCodeWriterFile(Optional.of(""), stringWriter),
- "//"
- );
+ var originalContents = """
+ // Generated by hobgoblin.
- codeWriter.writeHeader();
- codeWriter.write("Before");
- codeWriter.newLine();
- codeWriter.writeCustomArea("body");
- codeWriter.write("After");
- codeWriter.newLine();
+ """;
+ var file = new CodeWriter.InMemoryCodeWriterFile(Optional.of(originalContents), stringWriter);
+
+ CodeWriter.writeTo(
+ file,
+ "//",
+ codeWriter -> {
+ codeWriter.writeHeader();
+ codeWriter.write("Before");
+ codeWriter.newLine();
+ codeWriter.writeCustomArea("body");
+ codeWriter.write("After");
+ codeWriter.newLine();
+ }
+ );
assertThat(stringWriter.toString(), equalTo("""
// Generated by hobgoblin.
-
+
Before
// Custom area start: body
// Custom area end: body
@@ -65,6 +76,8 @@ public class CodeWriterTests {
public void whenExistingFileHasCustomAreasThenCustomAreaContentsArePreserved() throws IOException {
var stringWriter = new StringWriter();
var originalContents = """
+ // Generated by hobgoblin.
+
// Custom area start: head
Custom head.
// Custom area end: head
@@ -72,18 +85,21 @@ public class CodeWriterTests {
Custom body.
// Custom area end: body
""";
- var codeWriter = CodeWriter.file(
- new CodeWriter.InMemoryCodeWriterFile(Optional.of(originalContents), stringWriter),
- "//"
- );
+ var file = new CodeWriter.InMemoryCodeWriterFile(Optional.of(originalContents), stringWriter);
- codeWriter.writeHeader();
- codeWriter.writeCustomArea("head");
- codeWriter.writeCustomArea("body");
+ CodeWriter.writeTo(
+ file,
+ "//",
+ codeWriter -> {
+ codeWriter.writeHeader();
+ codeWriter.writeCustomArea("head");
+ codeWriter.writeCustomArea("body");
+ }
+ );
assertThat(stringWriter.toString(), equalTo("""
// Generated by hobgoblin.
-
+
// Custom area start: head
Custom head.
// Custom area end: head
@@ -92,4 +108,31 @@ public class CodeWriterTests {
// Custom area end: body
"""));
}
+
+ @Test
+ public void whenExistingFileIsMissingHeaderThenNothingIsWritten() throws IOException {
+ var stringWriter = new StringWriter();
+ var originalContents = """
+ // Custom area start: head
+ Custom head.
+ // Custom area end: head
+ // Custom area start: body
+ Custom body.
+ // Custom area end: body
+ """;
+ var file = new CodeWriter.InMemoryCodeWriterFile(Optional.of(originalContents), stringWriter);
+
+ CodeWriter.writeTo(
+ file,
+ "//",
+ codeWriter -> {
+ codeWriter.writeHeader();
+ codeWriter.writeCustomArea("head");
+ codeWriter.writeCustomArea("body");
+ }
+ );
+
+ assertThat(stringWriter.toString(), equalTo(""));
+ assertThat(file.isWriterOpened(), equalTo(false));
+ }
}
diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriterTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriterTests.java
index 2889032..9b16328 100644
--- a/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriterTests.java
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriterTests.java
@@ -1698,7 +1698,9 @@ public class JavaWriterTests {
var string = write(
writer -> writer.writeTypeDeclaration(java),
"""
- public class Point {
+ // Generated by hobgoblin.
+
+ public class Point {
// Custom area start: Point body
// <custom area>
// Custom area end: Point body
@@ -1717,24 +1719,20 @@ public class JavaWriterTests {
// == Doc comments ==
- private String write(Write write) throws IOException {
+ private String write(JavaWriter.Write write) throws IOException {
return write(write, Optional.empty());
}
- private String write(Write write, String originalContents) throws IOException {
+ private String write(JavaWriter.Write write, String originalContents) throws IOException {
return write(write, Optional.of(originalContents));
}
- private String write(Write write, Optional<String> originalContents) throws IOException {
+ private String write(JavaWriter.Write write, Optional<String> originalContents) throws IOException {
var stringWriter = new StringWriter();
- var javaWriter = JavaWriter.file(
- new CodeWriter.InMemoryCodeWriterFile(originalContents, stringWriter)
+ JavaWriter.writeTo(
+ new CodeWriter.InMemoryCodeWriterFile(originalContents, stringWriter),
+ write
);
- write.write(javaWriter);
return stringWriter.toString();
}
-
- private interface Write {
- void write(JavaWriter writer) throws IOException;
- }
}
diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/rust/RustWriterTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/rust/RustWriterTests.java
index e9edb72..4a81031 100644
--- a/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/rust/RustWriterTests.java
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/rust/RustWriterTests.java
@@ -1204,24 +1204,20 @@ public class RustWriterTests {
assertThat(string, equalTo("r#const"));
}
- private String write(Write write) throws IOException {
+ private String write(RustWriter.Write write) throws IOException {
return write(write, Optional.empty());
}
- private String write(Write write, String originalContents) throws IOException {
+ private String write(RustWriter.Write write, String originalContents) throws IOException {
return write(write, Optional.of(originalContents));
}
- private String write(Write write, Optional<String> originalContents) throws IOException {
+ private String write(RustWriter.Write write, Optional<String> originalContents) throws IOException {
var stringWriter = new StringWriter();
- var rustWriter = RustWriter.file(
- new CodeWriter.InMemoryCodeWriterFile(originalContents, stringWriter)
+ RustWriter.writeTo(
+ new CodeWriter.InMemoryCodeWriterFile(originalContents, stringWriter),
+ write
);
- write.write(rustWriter);
return stringWriter.toString();
}
-
- private interface Write {
- void write(RustWriter writer) throws IOException;
- }
}