diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java index 032ca0aab..121015830 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java @@ -7,12 +7,14 @@ import org.quiltmc.enigma.api.EnigmaProject; import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.analysis.index.jar.EntryIndex; +import org.quiltmc.enigma.api.analysis.index.jar.InheritanceIndex; import org.quiltmc.enigma.api.analysis.tree.ClassImplementationsTreeNode; import org.quiltmc.enigma.api.analysis.tree.ClassInheritanceTreeNode; import org.quiltmc.enigma.api.analysis.tree.ClassReferenceTreeNode; import org.quiltmc.enigma.api.analysis.EntryReference; import org.quiltmc.enigma.api.analysis.tree.FieldReferenceTreeNode; import org.quiltmc.enigma.api.service.ReadWriteService; +import org.quiltmc.enigma.api.translation.representation.entry.LocalVariableEntry; import org.quiltmc.enigma.gui.dialog.CrashDialog; import org.quiltmc.enigma.gui.network.IntegratedEnigmaClient; import org.quiltmc.enigma.impl.analysis.IndexTreeBuilder; @@ -553,6 +555,16 @@ private void applyChange0(ValidationContext vc, EntryChange change, boolean u if (!Objects.equals(prev.targetName(), mapping.targetName()) || !Objects.equals(prev.tokenType(), mapping.tokenType())) { this.chp.invalidateMapped(); + + // local variable entries need to be propagated up the tree to update param names in javadoc + if (target instanceof LocalVariableEntry) { + this.chp.invalidateJavadoc(target.getTopLevelClass()); + + var children = this.project.getJarIndex().getIndex(InheritanceIndex.class).getChildren(target.getContainingClass()); + for (ClassEntry child : children) { + this.chp.invalidateJavadoc(child.getTopLevelClass()); + } + } } if (!Objects.equals(prev.javadoc(), mapping.javadoc())) { diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java index 71c89b28d..5afbdd9d9 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java @@ -209,13 +209,13 @@ private void writeParameter(PrintWriter writer, EntryTreeNode node writer.print(node.getEntry().getName()); writer.print("\t"); EntryMapping mapping = node.getValue(); - if (mapping == null || mapping.targetName() == null) { - writer.println(); // todo ??? - } else { + if (mapping.targetName() != null) { writer.println(mapping.targetName()); - - this.writeComment(writer, mapping, 3); + } else { + writer.println(); } + + this.writeComment(writer, mapping, 3); } private void writeComment(PrintWriter writer, EntryMapping mapping, int indent) { diff --git a/enigma/src/main/java/org/quiltmc/enigma/impl/source/vineflower/EnigmaJavadocProvider.java b/enigma/src/main/java/org/quiltmc/enigma/impl/source/vineflower/EnigmaJavadocProvider.java index 749ff18cf..27dd1f9f8 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/impl/source/vineflower/EnigmaJavadocProvider.java +++ b/enigma/src/main/java/org/quiltmc/enigma/impl/source/vineflower/EnigmaJavadocProvider.java @@ -2,6 +2,7 @@ import org.quiltmc.enigma.api.translation.mapping.EntryMapping; import org.quiltmc.enigma.api.translation.mapping.EntryRemapper; +import org.quiltmc.enigma.api.translation.mapping.ResolutionStrategy; import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; import org.quiltmc.enigma.api.translation.representation.entry.Entry; import org.quiltmc.enigma.api.translation.representation.entry.FieldEntry; @@ -102,7 +103,15 @@ public String getMethodDoc(StructClass structClass, StructMethod structMethod) { EntryMapping paramMapping = this.remapper.getMapping(child); if (paramMapping.javadoc() != null) { - builder.append("\n@param ").append(paramMapping.targetName()).append(' ').append(paramMapping.javadoc()); + // for overridden methods, it's possible that we have no name and need to search for the root + String name = paramMapping.targetName(); + if (name == null) { + var root = this.remapper.getObfResolver().resolveFirstEntry(child, ResolutionStrategy.RESOLVE_ROOT); + EntryMapping rootMapping = this.remapper.getMapping(root); // root entry will never be null + name = rootMapping.targetName(); + } + + builder.append("\n@param ").append(name).append(' ').append(paramMapping.javadoc()); } } } diff --git a/enigma/src/test/java/org/quiltmc/enigma/input/interfaces/Inheritor.java b/enigma/src/test/java/org/quiltmc/enigma/input/interfaces/Inheritor.java new file mode 100644 index 000000000..9199c2d4a --- /dev/null +++ b/enigma/src/test/java/org/quiltmc/enigma/input/interfaces/Inheritor.java @@ -0,0 +1,13 @@ +package org.quiltmc.enigma.input.interfaces; + +public class Inheritor implements Root { + @Override + public int a() { + return 23; + } + + @Override + public double b(double c) { + return c + 100d; + } +} diff --git a/enigma/src/test/java/org/quiltmc/enigma/input/interfaces/Root.java b/enigma/src/test/java/org/quiltmc/enigma/input/interfaces/Root.java new file mode 100644 index 000000000..a23a4cf84 --- /dev/null +++ b/enigma/src/test/java/org/quiltmc/enigma/input/interfaces/Root.java @@ -0,0 +1,7 @@ +package org.quiltmc.enigma.input.interfaces; + +public interface Root { + int a(); + + double b(double c); +} diff --git a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestMethodOverrideParamJavadoc.java b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestMethodOverrideParamJavadoc.java new file mode 100644 index 000000000..d8cee7ef5 --- /dev/null +++ b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestMethodOverrideParamJavadoc.java @@ -0,0 +1,80 @@ +package org.quiltmc.enigma.translation.mapping; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.quiltmc.enigma.TestEntryFactory; +import org.quiltmc.enigma.TestUtil; +import org.quiltmc.enigma.api.Enigma; +import org.quiltmc.enigma.api.EnigmaProject; +import org.quiltmc.enigma.api.ProgressListener; +import org.quiltmc.enigma.api.class_provider.ClasspathClassProvider; +import org.quiltmc.enigma.api.service.ReadWriteService; +import org.quiltmc.enigma.api.translation.mapping.EntryMapping; +import org.quiltmc.enigma.api.translation.mapping.serde.FileType; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingFileNameFormat; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingParseException; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingSaveParameters; +import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; +import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; +import org.quiltmc.enigma.api.translation.representation.entry.LocalVariableEntry; +import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.function.Predicate; + +public class TestMethodOverrideParamJavadoc { + private static final MappingSaveParameters PARAMETERS = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false, null, null); + private static final Path JAR = TestUtil.obfJar("interfaces"); + private static Enigma enigma; + private static EnigmaProject project; + + @BeforeEach + void setupEnigma() throws IOException { + enigma = Enigma.create(); + project = enigma.openJar(JAR, new ClasspathClassProvider(), ProgressListener.createEmpty()); + } + + void test(ReadWriteService readWriteService, String tmpNameSuffix) throws IOException, MappingParseException { + ClassEntry inheritor = TestEntryFactory.newClass("a"); + MethodEntry method = TestEntryFactory.newMethod(inheritor, "a", "(D)D"); + LocalVariableEntry param = TestEntryFactory.newParameter(method, 1); + + EntryMapping mapping = project.getRemapper().getMapping(param); + Assertions.assertNull(mapping.javadoc()); + + project.getRemapper().putMapping(TestUtil.newVC(), param, mapping.withJavadoc("gaming")); + + EntryMapping withJavadoc = project.getRemapper().getMapping(param); + Assertions.assertEquals("gaming", withJavadoc.javadoc()); + + File tempFile = File.createTempFile("testMethodOverrideParamJavadoc", tmpNameSuffix); + tempFile.delete(); //remove the auto created file + + readWriteService.write(project.getRemapper().getMappings(), tempFile.toPath(), ProgressListener.createEmpty(), PARAMETERS); + Assertions.assertTrue(tempFile.exists(), "Written file not created"); + EntryTree loadedMappings = readWriteService.read(tempFile.toPath(), ProgressListener.createEmpty()); + + project.setMappings(loadedMappings, ProgressListener.createEmpty()); + + EntryMapping newMapping = project.getRemapper().getMapping(param); + Assertions.assertEquals("gaming", newMapping.javadoc()); + } + + @Test + public void testEnigmaFile() throws IOException, MappingParseException { + this.test(this.getService(file -> file.getExtensions().contains("mapping") && !file.isDirectory()), ".mapping"); + } + + @Test + public void testTinyFile() throws IOException, MappingParseException { + this.test(this.getService(file -> file.getExtensions().contains("tiny") && !file.isDirectory()), ".tiny"); + } + + @SuppressWarnings("all") + private ReadWriteService getService(Predicate predicate) { + return this.enigma.getReadWriteService(this.enigma.getSupportedFileTypes().stream().filter(predicate).findFirst().get()).get(); + } +}