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 new file mode 100644 index 000000000..40e97531a --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/bridge/DtsJavaBridge.java @@ -0,0 +1,167 @@ +package noppes.npcs.client.gui.util.script.interpreter.bridge; + +import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSFieldInfo; +import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSMethodInfo; +import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSTypeInfo; +import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSTypeRegistry; +import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo; +import noppes.npcs.client.gui.util.script.interpreter.type.TypeResolver; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; + +public final class DtsJavaBridge { + + private DtsJavaBridge() {} + + public static JSMethodInfo findMatchingMethod(Method method, TypeInfo containingType) { + JSTypeInfo jsType = findJSTypeInfo(method, containingType); + if (jsType == null) return null; + + List overloads = jsType.getMethodOverloads(method.getName()); + if (overloads.isEmpty()) return null; + + Class[] paramTypes = method.getParameterTypes(); + JSMethodInfo best = null; + int bestScore = -1; + + for (JSMethodInfo candidate : overloads) { + if (candidate.getParameterCount() != paramTypes.length) continue; + int score = scoreOverload(candidate, paramTypes, containingType); + if (score > bestScore) { + bestScore = score; + best = candidate; + } + } + + return bestScore >= 0 ? best : null; + } + + public static JSFieldInfo findMatchingField(Field field, TypeInfo containingType) { + JSTypeInfo jsType = findJSTypeInfo(field, containingType); + if (jsType == null) return null; + return jsType.getField(field.getName()); + } + + private static int scoreOverload(JSMethodInfo method, Class[] paramTypes, TypeInfo containingType) { + int score = 0; + List jsParams = method.getParameters(); + for (int i = 0; i < paramTypes.length; i++) { + int paramScore = scoreParam(paramTypes[i], jsParams.get(i), containingType); + if (paramScore < 0) return -1; + score += paramScore; + } + return score; + } + + private static int scoreParam(Class javaParam, JSMethodInfo.JSParameterInfo jsParam, TypeInfo containingType) { + if (javaParam == null || jsParam == null) return -1; + + TypeInfo resolved = jsParam.getResolvedType(containingType); + if (resolved != null && resolved.getJavaClass() != null) { + Class jsClass = resolved.getJavaClass(); + if (javaParam.equals(jsClass)) return 4; + if (jsClass.isAssignableFrom(javaParam)) return 3; + if (javaParam.isAssignableFrom(jsClass)) return 2; + } + + String jsTypeName = jsParam.getType(); + if (jsTypeName == null || jsTypeName.isEmpty()) return -1; + + if (matchesPrimitive(jsTypeName, javaParam)) { + return 3; + } + + if (javaParam.isArray() && jsTypeName.endsWith("[]")) { + String elementType = jsTypeName.substring(0, jsTypeName.length() - 2); + return matchesArrayElement(javaParam.getComponentType(), elementType, containingType) ? 2 : -1; + } + + JSTypeInfo jsType = resolveJSTypeInfo(jsTypeName); + if (jsType != null && jsType.getJavaFqn() != null) { + String javaFqn = normalizeJavaFqn(jsType.getJavaFqn()); + String paramFqn = normalizeJavaFqn(javaParam.getName()); + if (javaFqn.equals(paramFqn)) return 3; + } + + if (jsTypeName.equals(javaParam.getSimpleName())) return 1; + if (jsTypeName.equals(javaParam.getName())) return 1; + if ("any".equals(jsTypeName)) return 1; + return -1; + } + + private static boolean matchesArrayElement(Class elementClass, String jsElementType, TypeInfo containingType) { + if (elementClass == null) return false; + if (matchesPrimitive(jsElementType, elementClass)) return true; + + JSTypeInfo jsType = resolveJSTypeInfo(jsElementType); + if (jsType != null && jsType.getJavaFqn() != null) { + String javaFqn = normalizeJavaFqn(jsType.getJavaFqn()); + String elementFqn = normalizeJavaFqn(elementClass.getName()); + return javaFqn.equals(elementFqn); + } + + return jsElementType.equals(elementClass.getSimpleName()) || jsElementType.equals(elementClass.getName()); + } + + private static boolean matchesPrimitive(String jsTypeName, Class javaParam) { + switch (jsTypeName) { + case "string": + return javaParam == String.class || javaParam == char.class || javaParam == Character.class; + case "boolean": + return javaParam == boolean.class || javaParam == Boolean.class; + case "number": + return isNumberType(javaParam); + case "void": + return javaParam == void.class || javaParam == Void.class; + default: + return false; + } + } + + private static boolean isNumberType(Class type) { + if (type == null) return false; + return type == byte.class || type == short.class || type == int.class || type == long.class + || type == float.class || type == double.class + || Number.class.isAssignableFrom(type); + } + + private static JSTypeInfo findJSTypeInfo(Method method, TypeInfo containingType) { + if (containingType != null && containingType.isJSType()) { + return containingType.getJSTypeInfo(); + } + + Class javaClass = containingType != null ? containingType.getJavaClass() : method.getDeclaringClass(); + return resolveJSTypeInfo(javaClass != null ? javaClass.getName() : null); + } + + private static JSTypeInfo findJSTypeInfo(Field field, TypeInfo containingType) { + if (containingType != null && containingType.isJSType()) { + return containingType.getJSTypeInfo(); + } + + Class javaClass = containingType != null ? containingType.getJavaClass() : field.getDeclaringClass(); + return resolveJSTypeInfo(javaClass != null ? javaClass.getName() : null); + } + + private static JSTypeInfo resolveJSTypeInfo(String javaFqnOrType) { + if (javaFqnOrType == null || javaFqnOrType.isEmpty()) return null; + JSTypeRegistry registry = TypeResolver.getInstance().getJSTypeRegistry(); + JSTypeInfo direct = registry.getTypeByJavaFqn(javaFqnOrType); + if (direct != null) return direct; + + String normalized = normalizeJavaFqn(javaFqnOrType); + if (!normalized.equals(javaFqnOrType)) { + JSTypeInfo normalizedType = registry.getTypeByJavaFqn(normalized); + if (normalizedType != null) return normalizedType; + } + + return registry.getType(javaFqnOrType); + } + + private static String normalizeJavaFqn(String javaFqn) { + if (javaFqn == null) return null; + return javaFqn.replace('$', '.'); + } +} diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/field/FieldInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/field/FieldInfo.java index 55834d311..bbf74f644 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/field/FieldInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/field/FieldInfo.java @@ -3,6 +3,7 @@ package noppes.npcs.client.gui.util.script.interpreter.field; import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSFieldInfo; import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSTypeInfo; import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSTypeRegistry; +import noppes.npcs.client.gui.util.script.interpreter.bridge.DtsJavaBridge; import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocInfo; import noppes.npcs.client.gui.util.script.interpreter.token.TokenType; import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo; @@ -158,7 +159,21 @@ public final class FieldInfo { return constantInfo.getFieldInfo(); } - return new FieldInfo(name, Scope.GLOBAL, type, -1, true, null, null, -1, -1, field.getModifiers(), field); + // Try to find matching JSFieldInfo to bridge over documentation + JSFieldInfo jsField = DtsJavaBridge.findMatchingField(field, containingType); + String documentation = null; + JSDocInfo jsDocInfo = null; + if (jsField != null) { + jsDocInfo = jsField.getJsDocInfo(); + String jsDocDesc = jsDocInfo != null ? jsDocInfo.getDescription() : null; + documentation = jsDocDesc != null ? jsDocDesc : jsField.getDocumentation(); + } + + FieldInfo fieldInfo = new FieldInfo(name, Scope.GLOBAL, type, -1, true, null, documentation, -1, -1, field.getModifiers(), field); + if (jsDocInfo != null) { + fieldInfo.setJSDocInfo(jsDocInfo); + } + return fieldInfo; } /** 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 40963b165..381b817d0 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 @@ -736,6 +736,10 @@ public class TokenHoverInfo { } // Try to get actual Java method for more details + if (methodInfo != null && shouldPreferMethodInfo(methodInfo)) { + buildBasicMethodDeclaration(methodInfo, containingType); + return; + } if (methodInfo != null && methodInfo.getJavaMethod() != null) { buildMethodDeclaration(methodInfo.getJavaMethod(), containingType); extractJavadoc(methodInfo.getJavaMethod()); @@ -1110,6 +1114,20 @@ public class TokenHoverInfo { } + private boolean shouldPreferMethodInfo(MethodInfo methodInfo) { + if (methodInfo.getJSDocInfo() != null) return true; + String doc = methodInfo.getDocumentation(); + if (doc != null && !doc.isEmpty()) return true; + + for (FieldInfo param : methodInfo.getParameters()) { + String name = param.getName(); + if (name != null && !name.matches("arg\\d+")) { + return true; + } + } + return false; + } + /** * Format JSDoc information in IntelliJ-style with "Params:" and "Returns:" sections. * Creates colored documentation lines with parameter names highlighted. 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 859d3c0e9..60987e8cc 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 @@ -2,6 +2,7 @@ package noppes.npcs.client.gui.util.script.interpreter.method; import noppes.npcs.client.gui.util.script.interpreter.*; import noppes.npcs.client.gui.util.script.interpreter.field.FieldInfo; +import noppes.npcs.client.gui.util.script.interpreter.bridge.DtsJavaBridge; import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocInfo; import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSMethodInfo; import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSTypeInfo; @@ -144,15 +145,37 @@ public final class MethodInfo { String name = method.getName(); TypeInfo returnType = TypeInfo.fromClass(method.getReturnType()); int modifiers = method.getModifiers(); - + + // Try to find matching JSFieldInfo to bridge parameter names and docs + JSMethodInfo jsMethod = DtsJavaBridge.findMatchingMethod(method, containingType); List params = new ArrayList<>(); Class[] paramTypes = method.getParameterTypes(); + List jsParams = jsMethod != null ? jsMethod.getParameters() : null; for (int i = 0; i < paramTypes.length; i++) { TypeInfo paramType = TypeInfo.fromClass(paramTypes[i]); - params.add(FieldInfo.reflectionParam("arg" + i, paramType)); + String paramName = "arg" + i; + if (jsParams != null && i < jsParams.size()) { + String jsName = jsParams.get(i).getName(); + if (jsName != null && !jsName.isEmpty()) { + paramName = jsName; + } + } + params.add(FieldInfo.reflectionParam(paramName, paramType)); } - - return new MethodInfo(name, returnType, containingType, params, -1, -1, -1, -1, -1, true, false, modifiers, null, method); + + String documentation = null; + JSDocInfo jsDocInfo = null; + if (jsMethod != null) { + jsDocInfo = jsMethod.getJsDocInfo(); + String jsDocDesc = jsDocInfo != null ? jsDocInfo.getDescription() : null; + documentation = jsDocDesc != null ? jsDocDesc : jsMethod.getDocumentation(); + } + + MethodInfo methodInfo = new MethodInfo(name, returnType, containingType, params, -1, -1, -1, -1, -1, true, false, modifiers, documentation, method); + if (jsDocInfo != null) { + methodInfo.setJSDocInfo(jsDocInfo); + } + return methodInfo; } /**