diff --git a/README.md b/README.md index 381a1c4d..948c174b 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ A data dumper and typing generator for the KubeJS functions, constants and classes. -A recent migration to pure java makes the mod can function without any extra dependencies, enjoy the scripting! - Great thanks to @DAmNRelentless, @LatvianModder and @yesterday17 for invaluable suggestions during the development! ## 1. Showcase @@ -35,6 +33,9 @@ Auto-completion, type-hinting for most of the functions and classes: 3. Reload your IDE if your IDE doesn't know about the changes of typings, you will see the `onEvent` and `captureEvent` with correct typings now. 4. If you want to remove the mod, don't forget to replace all `captureEvent` back to `onEvent`. +5. v1.4 allows dumped events to be persisted between dumps, no matter actually they're fired or not in current dump, if + an event is missing (mostly from the removal of mods), cached events will be automatically removed too. If you want + to clear the cache manually, use `/probejs clear_cache`. ## 4. Beaning diff --git a/build.gradle b/build.gradle index 906bdb9e..9e5493bb 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ apply from: 'https://files.latmod.com/public/markdown-git-changelog.gradle' sourceCompatibility = targetCompatibility = JavaVersion.VERSION_17 def ENV = System.getenv() -version = "${mod_version}-build.18" +version = "${mod_version}-build.22" archivesBaseName = project.archives_base_name group = project.maven_group diff --git a/gradle.properties b/gradle.properties index df8f1997..de37d545 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.daemon=false loom.platform=forge mod_id=probejs archives_base_name=probejs -mod_version=1.3.0 +mod_version=1.4.0 maven_group=com.prunoideae mod_author=Prunoideae minecraft_version=1.18.1 diff --git a/src/main/java/com/prunoideae/probejs/ProbeCommands.java b/src/main/java/com/prunoideae/probejs/ProbeCommands.java index 8563ee24..d81d8b3e 100644 --- a/src/main/java/com/prunoideae/probejs/ProbeCommands.java +++ b/src/main/java/com/prunoideae/probejs/ProbeCommands.java @@ -7,6 +7,7 @@ import com.prunoideae.probejs.typings.KubeCompiler; import com.prunoideae.probejs.typings.ProbeCompiler; import com.prunoideae.probejs.typings.SpecialFormatters; +import dev.latvian.mods.kubejs.KubeJSPaths; import dev.latvian.mods.kubejs.server.ServerSettings; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; @@ -16,6 +17,8 @@ import net.minecraft.server.packs.repository.PackRepository; import net.minecraft.world.level.storage.WorldData; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Collection; public class ProbeCommands { @@ -28,20 +31,37 @@ public static void register(CommandDispatcher dispatcher) { try { export(context.getSource()); KubeCompiler.fromKubeDump(); + context.getSource().sendSuccess(new TextComponent("KubeJS registry snippets generated."), false); SpecialFormatters.init(); ProbeCompiler.compileDeclarations(); } catch (Exception e) { e.printStackTrace(); + context.getSource().sendSuccess(new TextComponent("Uncaught exception happened in wrapper, please report to the Github issue with complete latest.log."), false); } - context.getSource().sendSuccess(new TextComponent("Typing generation finished."), false); + context.getSource().sendSuccess(new TextComponent("ProbeJS typing generation finished."), false); return Command.SINGLE_SUCCESS; })) + .then(Commands.literal("clear_cache")) + .requires(source -> source.getServer().isSingleplayer() || source.hasPermission(2)) + .executes(context -> { + Path path = KubeJSPaths.EXPORTED.resolve("cachedEvents.json"); + if (Files.exists(path)) { + if (path.toFile().delete()) { + context.getSource().sendSuccess(new TextComponent("Cache files removed."), false); + } else { + context.getSource().sendSuccess(new TextComponent("Failed to remove cache files."), false); + } + } else { + context.getSource().sendSuccess(new TextComponent("No cached files to be cleared."), false); + } + return Command.SINGLE_SUCCESS; + }) ); } - private static int export(CommandSourceStack source) { + private static void export(CommandSourceStack source) { if (ServerSettings.dataExport != null) { - return 0; + return; } ServerSettings.source = source; @@ -63,6 +83,5 @@ private static int export(CommandSourceStack source) { } ReloadCommand.reloadPacks(collection2, source); - return 1; } } diff --git a/src/main/java/com/prunoideae/probejs/toucher/ClassInfo.java b/src/main/java/com/prunoideae/probejs/toucher/ClassInfo.java index 4a5a6608..4269de33 100644 --- a/src/main/java/com/prunoideae/probejs/toucher/ClassInfo.java +++ b/src/main/java/com/prunoideae/probejs/toucher/ClassInfo.java @@ -119,10 +119,6 @@ public ConstructorInfo(Constructor constructor) { this.constructor = constructor; } - public String getName() { - return this.constructor.getName(); - } - public List getParamsInfo() { return Arrays.stream(this.constructor.getParameters()).map(ParamInfo::new).collect(Collectors.toList()); } diff --git a/src/main/java/com/prunoideae/probejs/toucher/ClassToucher.java b/src/main/java/com/prunoideae/probejs/toucher/ClassToucher.java index 9c2a2818..5ca53bc1 100644 --- a/src/main/java/com/prunoideae/probejs/toucher/ClassToucher.java +++ b/src/main/java/com/prunoideae/probejs/toucher/ClassToucher.java @@ -2,10 +2,8 @@ import com.google.common.primitives.Primitives; -import javax.lang.model.SourceVersion; import java.lang.reflect.*; import java.util.*; -import java.util.function.Function; import java.util.stream.Collectors; public class ClassToucher { @@ -15,10 +13,6 @@ public class ClassToucher { private boolean dumpFields; private boolean dumpConstructors; - public static boolean isClassName(String name) { - return name.contains(".") && SourceVersion.isName(name) && !SourceVersion.isKeyword(name); - } - private static Class getClassOrComponent(Class clazz) { if (clazz == null) return null; diff --git a/src/main/java/com/prunoideae/probejs/typings/DummyBindingEvent.java b/src/main/java/com/prunoideae/probejs/typings/DummyBindingEvent.java index f258a99f..4afbe5f2 100644 --- a/src/main/java/com/prunoideae/probejs/typings/DummyBindingEvent.java +++ b/src/main/java/com/prunoideae/probejs/typings/DummyBindingEvent.java @@ -3,7 +3,6 @@ import dev.latvian.mods.kubejs.script.BindingsEvent; import dev.latvian.mods.kubejs.script.ScriptManager; import dev.latvian.mods.rhino.BaseFunction; -import dev.latvian.mods.rhino.util.DynamicFunction; import java.util.HashMap; diff --git a/src/main/java/com/prunoideae/probejs/typings/KubeCompiler.java b/src/main/java/com/prunoideae/probejs/typings/KubeCompiler.java index 6c8388b5..162ef6e5 100644 --- a/src/main/java/com/prunoideae/probejs/typings/KubeCompiler.java +++ b/src/main/java/com/prunoideae/probejs/typings/KubeCompiler.java @@ -46,13 +46,13 @@ public JsonObject toSnippet() { byModMembers.put(rl[0], new ArrayList<>()); byModMembers.get(rl[0]).add(rl[1]); }); - byModMembers.forEach((mod, mems) -> { - JsonObject modMembers = new JsonObject(); + byModMembers.forEach((mod, modMembers) -> { + JsonObject modMembersJson = new JsonObject(); JsonArray prefixes = new JsonArray(); prefixes.add("@%s.%s".formatted(mod, type)); - modMembers.add("prefix", prefixes); - modMembers.addProperty("body", "\"%s:${1|%s|}\"".formatted(mod, String.join(",", mems))); - resultJson.add("%s_%s".formatted(type, mod), modMembers); + modMembersJson.add("prefix", prefixes); + modMembersJson.addProperty("body", "\"%s:${1|%s|}\"".formatted(mod, String.join(",", modMembers))); + resultJson.add("%s_%s".formatted(type, mod), modMembersJson); }); } @@ -66,13 +66,13 @@ public JsonObject toSnippet() { byModMembers.put(rl[0], new ArrayList<>()); byModMembers.get(rl[0]).add(rl[1]); }); - byModMembers.forEach((mod, mems) -> { - JsonObject modMembers = new JsonObject(); + byModMembers.forEach((mod, modMembers) -> { + JsonObject modMembersJson = new JsonObject(); JsonArray prefixes = new JsonArray(); prefixes.add("@%s.tags.%s".formatted(mod, type)); - modMembers.add("prefix", prefixes); - modMembers.addProperty("body", "\"#%s:${1|%s|}\"".formatted(mod, String.join(",", mems))); - resultJson.add("%s_tag_%s".formatted(type, mod), modMembers); + modMembersJson.add("prefix", prefixes); + modMembersJson.addProperty("body", "\"#%s:${1|%s|}\"".formatted(mod, String.join(",", modMembers))); + resultJson.add("%s_tag_%s".formatted(type, mod), modMembersJson); }); } diff --git a/src/main/java/com/prunoideae/probejs/typings/ProbeCompiler.java b/src/main/java/com/prunoideae/probejs/typings/ProbeCompiler.java index 7a6dcda2..988625e9 100644 --- a/src/main/java/com/prunoideae/probejs/typings/ProbeCompiler.java +++ b/src/main/java/com/prunoideae/probejs/typings/ProbeCompiler.java @@ -1,12 +1,14 @@ package com.prunoideae.probejs.typings; import com.google.common.primitives.Primitives; +import com.google.gson.Gson; import com.mojang.datafixers.util.Pair; import com.prunoideae.probejs.ProbeJS; import com.prunoideae.probejs.plugin.WrappedEventHandler; import com.prunoideae.probejs.toucher.ClassInfo; import com.prunoideae.probejs.toucher.ClassToucher; import dev.latvian.mods.kubejs.KubeJSPaths; +import dev.latvian.mods.kubejs.event.EventJS; import dev.latvian.mods.kubejs.recipe.RecipeTypeJS; import dev.latvian.mods.kubejs.recipe.RegisterRecipeHandlersEvent; import dev.latvian.mods.kubejs.server.ServerScriptManager; @@ -36,12 +38,16 @@ private static void resolveClassname(Set> globalClasses, Set> } } - public static Set> compileGlobal(Path outFile, Map typeMap, DummyBindingEvent bindingEvent) { + public static Set> compileGlobal(Path outFile, Map typeMap, DummyBindingEvent bindingEvent, Set> cachedClasses) { Set> touchableClasses = new HashSet<>(bindingEvent.getClassDumpMap().values()); + + bindingEvent.getClassDumpMap().forEach((k, v) -> { TSGlobalClassFormatter.resolvedClassName.put(v.getName(), k); touchableClasses.add(v); }); + + touchableClasses.addAll(cachedClasses); touchableClasses.addAll(typeMap.values().stream().map(recipeTypeJS -> recipeTypeJS.factory.get().getClass()).collect(Collectors.toList())); touchableClasses.addAll(bindingEvent.getConstantDumpMap().values().stream().map(Object::getClass).collect(Collectors.toList())); touchableClasses.addAll(WrappedEventHandler.capturedEvents.values()); @@ -88,9 +94,7 @@ public static Set> compileGlobal(Path outFile, Map dummyRecipeClasses = new ArrayList<>(); - recipeMethodClassPlaceholder.forEach((k, v) -> { - dummyRecipeClasses.add(new TSDummyClassFormatter.DummyMethodClassFormatter(k, v)); - }); + recipeMethodClassPlaceholder.forEach((k, v) -> dummyRecipeClasses.add(new TSDummyClassFormatter.DummyMethodClassFormatter(k, v))); namespacedClasses.put("stub.probejs.recipes", dummyRecipeClasses); namespacedClasses.put("stub.probejs", List.of(new TSDummyClassFormatter.DummyFieldClassFormatter("RecipeHolder", recipeHolderFields.stream().toList()))); @@ -112,10 +116,11 @@ public static Set> compileGlobal(Path outFile, Map> cachedEvents) { ProbeJS.LOGGER.info("Compiling captured events..."); try (BufferedWriter writer = Files.newBufferedWriter(outFile)) { writer.write("/// \n"); + WrappedEventHandler.capturedEvents.putAll(cachedEvents); WrappedEventHandler.capturedEvents.forEach( (capture, event) -> { try { @@ -139,9 +144,11 @@ public static void compileConstants(Path outFile, DummyBindingEvent bindingEvent (name, value) -> { try { if (TSGlobalClassFormatter.staticValueTransformer.containsKey(value.getClass())) - writer.write("declare const %s: %s;\n".formatted(name, TSGlobalClassFormatter.staticValueTransformer.get(value.getClass()).apply(value))); - else - writer.write("declare const %s: %s;\n".formatted(name, TSGlobalClassFormatter.resolvedClassName.get(value.getClass().getName()))); + if (TSGlobalClassFormatter.staticValueTransformer.get(value.getClass()).apply(value) != null) { + writer.write("declare const %s: %s;\n".formatted(name, TSGlobalClassFormatter.staticValueTransformer.get(value.getClass()).apply(value))); + return; + } + writer.write("declare const %s: %s;\n".formatted(name, TSGlobalClassFormatter.resolvedClassName.get(value.getClass().getName()))); } catch (IOException e) { e.printStackTrace(); } @@ -161,7 +168,7 @@ public static void compileJava(Path outFile, Set> classes) { .filter(clazz -> ServerScriptManager.instance.scriptManager.isClassAllowed(clazz.getName())) .forEach(clazz -> { try { - writer.write("declare function java(name: \"%s\"): %s;\n".formatted(clazz.getName(), TSGlobalClassFormatter.resolvedClassName.get(clazz.getName()))); + writer.write("declare function java(name: \"%s\"): typeof %s;\n".formatted(clazz.getName(), TSGlobalClassFormatter.resolvedClassName.get(clazz.getName()))); } catch (IOException e) { e.printStackTrace(); } @@ -207,11 +214,37 @@ public static void compileDeclarations() throws IOException { KubeJSPlugins.forEachPlugin(plugin -> plugin.addRecipes(recipeEvent)); KubeJSPlugins.forEachPlugin(plugin -> plugin.addBindings(bindingEvent)); - Set> touchedClasses = compileGlobal(typingDir.resolve("globals.d.ts"), typeMap, bindingEvent); - compileEvent(typingDir.resolve("events.d.ts")); + Map> cachedEvents = new HashMap<>(); + Path cachedEventPath = KubeJSPaths.EXPORTED.resolve("cachedEvents.json"); + if (Files.exists(cachedEventPath)) { + Map cachedMap = new Gson().fromJson(Files.newBufferedReader(cachedEventPath), Map.class); + cachedMap.forEach((k, v) -> { + if (k instanceof String && v instanceof String) { + try { + Class clazz = Class.forName((String) v); + if (EventJS.class.isAssignableFrom(clazz)) + cachedEvents.put((String) k, (Class) clazz); + } catch (ClassNotFoundException e) { + ProbeJS.LOGGER.warn("Class %s was in the cache, but disappeared in packages now.".formatted(v)); + } + } + }); + } + + Set> cachedClasses = new HashSet<>(cachedEvents.values()); + Set> touchedClasses = compileGlobal(typingDir.resolve("globals.d.ts"), typeMap, bindingEvent, cachedClasses); + compileEvent(typingDir.resolve("events.d.ts"), cachedEvents); compileConstants(typingDir.resolve("constants.d.ts"), bindingEvent); compileJava(typingDir.resolve("java.d.ts"), touchedClasses); compileIndex(typingDir.resolve("index.d.ts")); + try (BufferedWriter writer = Files.newBufferedWriter(cachedEventPath)) { + Map eventsCache = new HashMap<>(); + WrappedEventHandler.capturedEvents.forEach((k, v) -> eventsCache.put(k, v.getName())); + new Gson().toJson(eventsCache, writer); + writer.flush(); + } catch (IOException e) { + e.printStackTrace(); + } if (Files.notExists(KubeJSPaths.DIRECTORY.resolve("jsconfig.json"))) compileJSConfig(KubeJSPaths.DIRECTORY.resolve("jsconfig.json")); ProbeJS.LOGGER.info("All done!"); diff --git a/src/main/java/com/prunoideae/probejs/typings/SpecialFormatters.java b/src/main/java/com/prunoideae/probejs/typings/SpecialFormatters.java index 8a89cb49..8c5a1ffb 100644 --- a/src/main/java/com/prunoideae/probejs/typings/SpecialFormatters.java +++ b/src/main/java/com/prunoideae/probejs/typings/SpecialFormatters.java @@ -1,13 +1,15 @@ package com.prunoideae.probejs.typings; +import com.google.common.primitives.Primitives; import com.google.gson.Gson; import com.prunoideae.probejs.toucher.ClassInfo; -import com.prunoideae.probejs.toucher.ClassToucher; import dev.latvian.mods.kubejs.recipe.RecipeEventJS; -import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; import java.lang.reflect.Type; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.*; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -54,6 +56,27 @@ private static Function generateParamFunction(int pa }; } + private static String formatMapKV(Object obj) { + //Only Map is allowed + //Others are discarded, if there are others + Map map = (Map) obj; + if (map.keySet().stream().anyMatch(o -> o instanceof String)) { + return "{%s}".formatted(map.entrySet() + .stream() + .filter(entry -> entry.getKey() != null) + .filter(entry -> entry.getKey() instanceof String) + .map(entry -> { + if (TSGlobalClassFormatter.FieldFormatter.formatValue(entry.getValue()) != null) { + return "%s: %s".formatted(new Gson().toJson(entry.getKey()), TSGlobalClassFormatter.FieldFormatter.formatValue(entry.getValue())); + } + return "%s: %s".formatted(new Gson().toJson(entry.getKey()), new TSGlobalClassFormatter.TypeFormatter(new ClassInfo.TypeInfo(entry.getValue().getClass(), entry.getValue().getClass())).format()); + }) + .collect(Collectors.joining(", "))); + } else { + return null; + } + } + private static void putStaticValueTransformer(Function transformer, Class... types) { for (Class type : types) TSGlobalClassFormatter.staticValueTransformer.put(type, transformer); @@ -91,6 +114,8 @@ public static void init() { Long.TYPE, Long.class, Short.TYPE, Short.class, Void.TYPE, Void.class); + putStaticValueTransformer(o -> new Gson().toJson(((ResourceLocation) o).toString()), ResourceLocation.class); + putStaticValueTransformer(SpecialFormatters::formatMapKV, HashMap.class, Map.class); putStaticValueTransformer(o -> new Gson().toJson(o), String.class, Character.TYPE, Character.class); } } diff --git a/src/main/java/com/prunoideae/probejs/typings/TSDummyClassFormatter.java b/src/main/java/com/prunoideae/probejs/typings/TSDummyClassFormatter.java index f8486e43..138cd3e0 100644 --- a/src/main/java/com/prunoideae/probejs/typings/TSDummyClassFormatter.java +++ b/src/main/java/com/prunoideae/probejs/typings/TSDummyClassFormatter.java @@ -22,7 +22,7 @@ public DummyMethodClassFormatter(String name, List innerLines = new ArrayList<>(); - innerLines.add("%sclass %s {".formatted(" ".repeat(this.indentation), this.name)); + innerLines.add(" ".repeat(this.indentation) + "class %s {".formatted(this.name)); pairs.forEach(pair -> innerLines.add("%s%s(...args: object): %s".formatted(" ".repeat(this.indentation + this.stepIndentation), pair.getFirst(), pair.getSecond().format()))); innerLines.add("}\n"); return String.join("\n", innerLines); @@ -42,7 +42,7 @@ public DummyFieldClassFormatter(String name, List> pairs) { @Override public String format() { List innerLines = new ArrayList<>(); - innerLines.add("%sclass %s {".formatted(" ".repeat(this.indentation), this.name)); + innerLines.add(" ".repeat(this.indentation) + "class %s {".formatted(this.name)); pairs.forEach(pair -> innerLines.add("%s%s: %s".formatted(" ".repeat(this.indentation + this.stepIndentation), pair.getFirst(), pair.getSecond()))); innerLines.add("}\n"); return String.join("\n", innerLines); diff --git a/src/main/java/com/prunoideae/probejs/typings/TSGlobalClassFormatter.java b/src/main/java/com/prunoideae/probejs/typings/TSGlobalClassFormatter.java index 95c0d468..9a9de080 100644 --- a/src/main/java/com/prunoideae/probejs/typings/TSGlobalClassFormatter.java +++ b/src/main/java/com/prunoideae/probejs/typings/TSGlobalClassFormatter.java @@ -55,7 +55,11 @@ public static String serializeType(Type type) { type = pair.getFirst(); } } - return resolvedClassName.get(type.getTypeName()) + "[]".repeat(depth); + String[] clzPath = type.getTypeName().split("\\."); + String name = clzPath[clzPath.length - 1]; + if (name.contains("/")) + name = name.split("/")[0]; + return resolvedClassName.getOrDefault(type.getTypeName(), "Unknown." + name) + "[]".repeat(depth); } public static class TypeFormatter implements ITSFormatter { @@ -68,12 +72,6 @@ public TypeFormatter(ClassInfo.TypeInfo typeInfo) { @Override public String format() { Class clazz = this.typeInfo.getTypeClass(); - int arrayDepth = -1; - if (clazz != null && clazz.isArray()) { - Pair, Integer> pair = getClassOrComponent(clazz); - clazz = pair.getFirst(); - arrayDepth = pair.getSecond(); - } String resolvedType = specialTypeFormatter.containsKey(clazz) ? specialTypeFormatter.get(clazz).apply(this.typeInfo) : serializeType(this.typeInfo.getType()); @@ -82,10 +80,7 @@ public String format() { if (type.getTypeParameters().length != 0) resolvedType += "<%s>".formatted(String.join(",", Collections.nCopies(type.getTypeParameters().length, "unknown"))); } - if (arrayDepth != -1) - resolvedType += "[]".repeat(arrayDepth); return resolvedType; - } } @@ -127,19 +122,27 @@ public String format() { public static class FieldFormatter implements ITSFormatter { private final ClassInfo.FieldInfo fieldInfo; + public static String formatValue(Object obj) { + if (obj == null) + return "any"; + if (staticValueTransformer.containsKey(obj.getClass())) + if (staticValueTransformer.get(obj.getClass()).apply(obj) != null) + return staticValueTransformer.get(obj.getClass()).apply(obj); + return null; + } + public FieldFormatter(ClassInfo.FieldInfo fieldInfo) { this.fieldInfo = fieldInfo; } @Override public String format() { - String resolvedAnnotation; - if (this.fieldInfo.isStatic() - && this.fieldInfo.getStaticValue() != null - && staticValueTransformer.containsKey(this.fieldInfo.getStaticValue().getClass())) { + String resolvedAnnotation = null; + if (this.fieldInfo.isStatic() && this.fieldInfo.getStaticValue() != null) { Object value = this.fieldInfo.getStaticValue(); - resolvedAnnotation = staticValueTransformer.get(value.getClass()).apply(value); - } else { + resolvedAnnotation = formatValue(value); + } + if (resolvedAnnotation == null) { resolvedAnnotation = new TypeFormatter(this.fieldInfo.getTypeInfo()).format(); }