From adf2e8698e0bd590fcc2921ca087b8189dac255f Mon Sep 17 00:00:00 2001 From: Luke Bemish Date: Wed, 4 Sep 2024 14:47:26 -0600 Subject: [PATCH] Add API for reacting to collected replacements (#40) --- .../net/neoforged/jst/api/Replacements.java | 12 +++++- .../neoforged/jst/api/SourceTransformer.java | 16 +++++++- .../net/neoforged/jst/cli/Replacement.java | 31 --------------- .../jst/cli/SourceFileProcessor.java | 39 +++++++++++++++---- 4 files changed, 57 insertions(+), 41 deletions(-) delete mode 100644 cli/src/main/java/net/neoforged/jst/cli/Replacement.java diff --git a/api/src/main/java/net/neoforged/jst/api/Replacements.java b/api/src/main/java/net/neoforged/jst/api/Replacements.java index 2885813..645e48b 100644 --- a/api/src/main/java/net/neoforged/jst/api/Replacements.java +++ b/api/src/main/java/net/neoforged/jst/api/Replacements.java @@ -4,10 +4,19 @@ import com.intellij.psi.PsiElement; import java.util.ArrayList; +import java.util.Collections; import java.util.List; public final class Replacements { - private final List replacements = new ArrayList<>(); + private final List replacements; + + public Replacements(List replacements) { + this.replacements = replacements; + } + + public Replacements() { + this(new ArrayList<>()); + } public boolean isEmpty() { return replacements.isEmpty(); @@ -81,5 +90,4 @@ public String apply(CharSequence originalContent) { writer.append(originalContent, replacements.get(replacements.size() - 1).range().getEndOffset(), originalContent.length()); return writer.toString(); } - } diff --git a/api/src/main/java/net/neoforged/jst/api/SourceTransformer.java b/api/src/main/java/net/neoforged/jst/api/SourceTransformer.java index c595219..6535422 100644 --- a/api/src/main/java/net/neoforged/jst/api/SourceTransformer.java +++ b/api/src/main/java/net/neoforged/jst/api/SourceTransformer.java @@ -2,6 +2,8 @@ import com.intellij.psi.PsiFile; +import java.util.List; + /** * Transformers are created through {@link SourceTransformerPlugin plugins}, and handle source replacements. *

@@ -19,13 +21,25 @@ public interface SourceTransformer { default void beforeRun(TransformContext context) { } + /** + * Invoke after replacements are collected for a given file, but before they are applied. + *

+ * Can be used to react to or verify the replacements that were collected. + * @param fileEntry the file entry being transformed + * @param replacements the replacements that were collected; read-only + * @return {@code true} if the transformation should continue, {@code false} if it should fail + */ + default boolean beforeReplacement(FileEntry fileEntry, List replacements) { + return true; + } + /** * Invoked after all source transformations are finished. *

* Can be used for post-transformation validation. * * @param context the transform context - * @return {@code true} if the transformation was successful, {@code false} otherwise + * @return {@code true} if the transformation was successful, {@code false} if it failed */ default boolean afterRun(TransformContext context) { return true; diff --git a/cli/src/main/java/net/neoforged/jst/cli/Replacement.java b/cli/src/main/java/net/neoforged/jst/cli/Replacement.java deleted file mode 100644 index 9fad609..0000000 --- a/cli/src/main/java/net/neoforged/jst/cli/Replacement.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.neoforged.jst.cli; - -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.PsiElement; - -import java.util.Comparator; - -public record Replacement(TextRange range, String newText) { - - public static final Comparator COMPARATOR = Comparator.comparingInt(replacement -> replacement.range.getStartOffset()); - - public static Replacement replace(PsiElement element, String newText) { - return new Replacement(element.getTextRange(), newText); - } - - public static Replacement insertBefore(PsiElement element, String newText) { - var startOffset = element.getTextRange().getStartOffset(); - return new Replacement(new TextRange( - startOffset, - startOffset - ), newText); - } - - public static Replacement insertAfter(PsiElement element, String newText) { - var endOffset = element.getTextRange().getEndOffset(); - return new Replacement(new TextRange( - endOffset, - endOffset - ), newText); - } -} diff --git a/cli/src/main/java/net/neoforged/jst/cli/SourceFileProcessor.java b/cli/src/main/java/net/neoforged/jst/cli/SourceFileProcessor.java index c97b113..5e7dd06 100644 --- a/cli/src/main/java/net/neoforged/jst/cli/SourceFileProcessor.java +++ b/cli/src/main/java/net/neoforged/jst/cli/SourceFileProcessor.java @@ -6,6 +6,7 @@ import net.neoforged.jst.api.FileSink; import net.neoforged.jst.api.FileSource; import net.neoforged.jst.api.Logger; +import net.neoforged.jst.api.Replacement; import net.neoforged.jst.api.Replacements; import net.neoforged.jst.api.SourceTransformer; import net.neoforged.jst.api.TransformContext; @@ -19,7 +20,9 @@ import java.nio.file.attribute.FileTime; import java.time.Instant; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; /** * Reference for out-of-IDE usage of the IntelliJ Java parser is from the Kotlin compiler @@ -63,16 +66,22 @@ public boolean process(FileSource source, FileSink sink, List }); } } else { + var success = new AtomicBoolean(true); try (var asyncOut = new OrderedParallelWorkQueue(sink, maxQueueDepth); var stream = source.streamEntries()) { stream.forEach(entry -> asyncOut.submitAsync(parallelSink -> { try { - processEntry(entry, sourceRoot, transformers, parallelSink); + if (!processEntry(entry, sourceRoot, transformers, parallelSink)) { + success.set(false); + } } catch (IOException e) { throw new UncheckedIOException(e); } })); } + if (!success.get()) { + return false; + } } boolean isOk = true; @@ -83,11 +92,13 @@ public boolean process(FileSource source, FileSink sink, List return isOk; } - private void processEntry(FileEntry entry, VirtualFile sourceRoot, List transformers, FileSink sink) throws IOException { + private boolean processEntry(FileEntry entry, VirtualFile sourceRoot, List transformers, FileSink sink) throws IOException { if (entry.directory()) { sink.putDirectory(entry.relativePath()); - return; + return true; } + + boolean[] success = {true}; try (var in = entry.openInputStream()) { byte[] content = in.readAllBytes(); @@ -95,13 +106,17 @@ private void processEntry(FileEntry entry, VirtualFile sourceRoot, List transformers, byte[] originalContentBytes) { + private byte[] transformSource(VirtualFile contentRoot, FileEntry entry, List transformers, byte[] originalContentBytes, boolean[] successOut) { // Instead of parsing the content we actually read from the file, we read the virtual file that is // visible to IntelliJ from adding the source jar. The reasoning is that IntelliJ will cache this internally // and reuse it when cross-referencing type-references. If we parsed from a String instead, it would parse // the same file twice. + var path = entry.relativePath(); var sourceFile = contentRoot.findFileByRelativePath(path); if (sourceFile == null) { System.err.println("Can't transform " + path + " since IntelliJ doesn't see it in the source jar."); @@ -130,14 +146,23 @@ byte[] transformSource(VirtualFile contentRoot, String path, List replacementsList = new ArrayList<>(); + var replacements = new Replacements(replacementsList); for (var transformer : transformers) { transformer.visitFile(psiFile, replacements); } + var readOnlyReplacements = Collections.unmodifiableList(replacementsList); + boolean success = true; + for (var transformer : transformers) { + success = success && transformer.beforeReplacement(entry, readOnlyReplacements); + } + + successOut[0] = success; + // If no replacements were made, just stream the original content into the destination file - if (replacements.isEmpty()) { + if (!success || replacements.isEmpty()) { return originalContentBytes; }