From 46b62eea567ddab212b3595f9d910fd0db5d52d2 Mon Sep 17 00:00:00 2001 From: bigguy345 Date: Sun, 25 Jan 2026 06:22:11 +0200 Subject: [PATCH] Bridged .d.ts patch overrides to java side to show in autocomplete/hoverinfo --- .../interpreter/bridge/DtsJavaBridge.java | 79 +++++++++++++++++++ .../interpreter/hover/TokenHoverInfo.java | 8 ++ .../script/interpreter/method/MethodInfo.java | 20 ++++- 3 files changed, 106 insertions(+), 1 deletion(-) diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/bridge/DtsJavaBridge.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/bridge/DtsJavaBridge.java index dea2fb882..cebd1bcfc 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/bridge/DtsJavaBridge.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/bridge/DtsJavaBridge.java @@ -11,18 +11,97 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public final class DtsJavaBridge { + /** Cache of reflection Method -> matching JS method info (for docs/param names). */ private static final Map METHOD_CACHE = new ConcurrentHashMap<>(); + /** Cache of reflection Field -> matching JS field info (for docs/typing). */ private static final Map FIELD_CACHE = new ConcurrentHashMap<>(); + /** Cache of reflection Method -> resolved Java TypeInfo override from .d.ts. */ + private static final Map RETURN_OVERRIDE_CACHE = new ConcurrentHashMap<>(); + /** Negative cache for methods with no usable override. */ + private static final Set RETURN_OVERRIDE_MISSES = ConcurrentHashMap.newKeySet(); private DtsJavaBridge() {} public static void clearCache() { METHOD_CACHE.clear(); FIELD_CACHE.clear(); + RETURN_OVERRIDE_CACHE.clear(); + RETURN_OVERRIDE_MISSES.clear(); + } + + /** + * Resolve a Java return type override for a reflected method using its .d.ts twin. + * + * The override is used only for editor typing (e.g., Janino hover/autocomplete) and + * never changes runtime reflection. This returns a Java-resolved TypeInfo when the + * .d.ts return type can be mapped to a concrete Java class. + */ + public static TypeInfo resolveReturnTypeOverride(Method method, TypeInfo containingType, JSMethodInfo jsMethod) { + if (method == null || jsMethod == null) return null; + + TypeInfo cached = RETURN_OVERRIDE_CACHE.get(method); + if (cached != null) return cached; + if (RETURN_OVERRIDE_MISSES.contains(method)) return null; + + String jsReturnType = jsMethod.getReturnType(); + if (jsReturnType == null || jsReturnType.isEmpty()) { + RETURN_OVERRIDE_MISSES.add(method); + return null; + } + + String normalizedJsReturn = jsReturnType; + if (normalizedJsReturn.contains("|")) { + normalizedJsReturn = normalizedJsReturn.split("\\|")[0].trim(); + } + normalizedJsReturn = normalizedJsReturn.replace("?", "").trim(); + + // Fast-path: skip resolver if the .d.ts return matches reflection (common case). + Class reflectedReturn = method.getReturnType(); + if (reflectedReturn != null) { + if (normalizedJsReturn.equals(reflectedReturn.getName()) + || normalizedJsReturn.equals(reflectedReturn.getSimpleName()) + || normalizedJsReturn.equals("Java." + reflectedReturn.getName())) { + RETURN_OVERRIDE_MISSES.add(method); + return null; + } + + String lower = normalizedJsReturn.toLowerCase(); + if (("void".equals(lower) && (reflectedReturn == void.class || reflectedReturn == Void.class)) + || ("boolean".equals(lower) && (reflectedReturn == boolean.class || reflectedReturn == Boolean.class)) + || ("string".equals(lower) && reflectedReturn == String.class) + || ("number".equals(lower) && (reflectedReturn == double.class || reflectedReturn == Double.class))) { + RETURN_OVERRIDE_MISSES.add(method); + return null; + } + } + + TypeResolver resolver = TypeResolver.getInstance(); + TypeInfo resolved = resolver.resolveJSType(normalizedJsReturn); + TypeInfo javaResolved = null; + + if (resolved != null) { + if (resolved.getJavaClass() != null) { + javaResolved = resolved; + } else if (resolved.isJSType() && resolved.getJSTypeInfo() != null) { + String javaFqn = resolved.getJSTypeInfo().getJavaFqn(); + if (javaFqn != null && !javaFqn.isEmpty()) { + javaResolved = resolver.resolveFullName(javaFqn); + } + } + } + + if (javaResolved == null || javaResolved.getJavaClass() == null) { + RETURN_OVERRIDE_MISSES.add(method); + return null; + } + + RETURN_OVERRIDE_CACHE.put(method, javaResolved); + return javaResolved; } public static JSMethodInfo findMatchingMethod(Method method, TypeInfo containingType) { diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverInfo.java index 381b817d0..8520992f8 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverInfo.java @@ -741,6 +741,14 @@ public class TokenHoverInfo { return; } if (methodInfo != null && methodInfo.getJavaMethod() != null) { + TypeInfo methodReturnType = methodInfo.getReturnType(); + Class reflectedReturnType = methodInfo.getJavaMethod().getReturnType(); + if (methodReturnType != null && methodReturnType.getJavaClass() != null + && methodReturnType.getJavaClass() != reflectedReturnType) { + // Use MethodInfo when its return type differs from reflection (e.g., .d.ts override like IDBCPlayer -> IDBCAddon in npcdbc). + buildBasicMethodDeclaration(methodInfo, containingType); + return; + } buildMethodDeclaration(methodInfo.getJavaMethod(), containingType); extractJavadoc(methodInfo.getJavaMethod()); return; diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/method/MethodInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/method/MethodInfo.java index 60987e8cc..325ca4b56 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/method/MethodInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/method/MethodInfo.java @@ -146,8 +146,26 @@ public final class MethodInfo { TypeInfo returnType = TypeInfo.fromClass(method.getReturnType()); int modifiers = method.getModifiers(); - // Try to find matching JSFieldInfo to bridge parameter names and docs + // Try to find matching JSMethodInfo to bridge parameter names/docs and allow return overrides JSMethodInfo jsMethod = DtsJavaBridge.findMatchingMethod(method, containingType); + if (jsMethod != null) { + // If .d.ts provides a more specific return type, use it for editor typing. + // (e.g., .d.ts override like IDBCPlayer -> IDBCAddon in npcdbc). + TypeInfo overrideReturnType = DtsJavaBridge.resolveReturnTypeOverride(method, containingType, jsMethod); + if (overrideReturnType != null && overrideReturnType.getJavaClass() != null) { + Class reflectedReturn = method.getReturnType(); + Class overrideClass = overrideReturnType.getJavaClass(); + if (reflectedReturn.isAssignableFrom(overrideClass)) { + if (reflectedReturn != overrideClass) { + returnType = overrideReturnType; + System.out.println("[DtsJavaBridge] Overrode return type for " + + method.getDeclaringClass().getName() + "." + name + + "(" + method.getParameterCount() + ") from " + + reflectedReturn.getName() + " to " + overrideClass.getName()); + } + } + } + } List params = new ArrayList<>(); Class[] paramTypes = method.getParameterTypes(); List jsParams = jsMethod != null ? jsMethod.getParameters() : null;