Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API for reacting to collected replacements #40

Merged
merged 5 commits into from
Sep 4, 2024
Merged
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
12 changes: 10 additions & 2 deletions api/src/main/java/net/neoforged/jst/api/Replacements.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Replacement> replacements = new ArrayList<>();
private final List<Replacement> replacements;

public Replacements(List<Replacement> replacements) {
this.replacements = replacements;
}

public Replacements() {
this(new ArrayList<>());
}

public boolean isEmpty() {
return replacements.isEmpty();
Expand Down Expand Up @@ -81,5 +90,4 @@ public String apply(CharSequence originalContent) {
writer.append(originalContent, replacements.get(replacements.size() - 1).range().getEndOffset(), originalContent.length());
return writer.toString();
}

}
16 changes: 15 additions & 1 deletion api/src/main/java/net/neoforged/jst/api/SourceTransformer.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.intellij.psi.PsiFile;

import java.util.List;

/**
* Transformers are created through {@link SourceTransformerPlugin plugins}, and handle source replacements.
* <p>
Expand All @@ -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.
* <p>
* 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<Replacement> replacements) {
return true;
}

/**
* Invoked after all source transformations are finished.
* <p>
* 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;
Expand Down
31 changes: 0 additions & 31 deletions cli/src/main/java/net/neoforged/jst/cli/Replacement.java

This file was deleted.

39 changes: 32 additions & 7 deletions cli/src/main/java/net/neoforged/jst/cli/SourceFileProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -63,16 +66,22 @@ public boolean process(FileSource source, FileSink sink, List<SourceTransformer>
});
}
} 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;
Expand All @@ -83,25 +92,31 @@ public boolean process(FileSource source, FileSink sink, List<SourceTransformer>
return isOk;
}

private void processEntry(FileEntry entry, VirtualFile sourceRoot, List<SourceTransformer> transformers, FileSink sink) throws IOException {
private boolean processEntry(FileEntry entry, VirtualFile sourceRoot, List<SourceTransformer> 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();
var lastModified = entry.lastModified();

if (!isIgnored(entry.relativePath()) && !transformers.isEmpty() && entry.hasExtension("java")) {
var orgContent = content;
content = transformSource(sourceRoot, entry.relativePath(), transformers, content);
content = transformSource(sourceRoot, entry, transformers, content, success);
if (!success[0]) {
return false;
}
if (orgContent != content) {
lastModified = FileTime.from(Instant.now());
}
}
sink.putFile(entry.relativePath(), lastModified, content);
}
return true;
}

private boolean isIgnored(String relativePath) {
Expand All @@ -113,11 +128,12 @@ private boolean isIgnored(String relativePath) {
return false;
}

byte[] transformSource(VirtualFile contentRoot, String path, List<SourceTransformer> transformers, byte[] originalContentBytes) {
private byte[] transformSource(VirtualFile contentRoot, FileEntry entry, List<SourceTransformer> 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.");
Expand All @@ -130,14 +146,23 @@ byte[] transformSource(VirtualFile contentRoot, String path, List<SourceTransfor
}

// Gather replaced ranges in the source-file with their replacement
var replacements = new Replacements();
List<Replacement> 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;
}

Expand Down