From 61a363fdbac0ac3ae111cedf72ab831f9d987ddf Mon Sep 17 00:00:00 2001 From: bigguy345 Date: Sun, 4 Jan 2026 20:56:11 +0200 Subject: [PATCH] A bunch of JS parser fixes and improvements --- .../script/interpreter/field/FieldInfo.java | 72 +++++++ .../interpreter/js_parser/JSHoverInfo.java | 117 ------------ .../js_parser/JSScriptAnalyzer.java | 178 +++++++++++++----- .../interpreter/js_parser/JSTypeRegistry.java | 2 +- .../script/interpreter/method/MethodInfo.java | 81 ++++++++ .../script/interpreter/type/TypeInfo.java | 143 +++++++++++++- 6 files changed, 420 insertions(+), 173 deletions(-) delete mode 100644 src/main/java/noppes/npcs/client/gui/util/script/interpreter/js_parser/JSHoverInfo.java 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 fd6890fb0..d10218d83 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 @@ -1,5 +1,8 @@ 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.token.TokenType; import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo; import noppes.npcs.client.gui.util.script.interpreter.method.MethodCallInfo; @@ -142,6 +145,75 @@ public final class FieldInfo { return new FieldInfo(name, Scope.GLOBAL, type, -1, true, null, null, -1, -1, field.getModifiers(), field); } + + /** + * Create a FieldInfo from a JSFieldInfo (parsed from .d.ts files). + * Used when resolving field access on JavaScript types. + * + * @param jsField The JavaScript field info from the type registry + * @param containingType The TypeInfo that owns this field + * @return A FieldInfo representing the JavaScript field + */ + public static FieldInfo fromJSField(JSFieldInfo jsField, TypeInfo containingType) { + String name = jsField.getName(); + + // Resolve the type from the JS type registry + TypeInfo type = resolveJSType(jsField.getType()); + + // JS fields are public by default, readonly maps to final + int modifiers = Modifier.PUBLIC; + if (jsField.isReadonly()) { + modifiers |= Modifier.FINAL; + } + + // Use documentation if available + String documentation = jsField.getDocumentation(); + + return new FieldInfo(name, Scope.GLOBAL, type, -1, true, null, documentation, -1, -1, modifiers, null); + } + + /** + * Resolves a JavaScript type name to a TypeInfo. + * Handles primitives, mapped types, and custom types from the registry. + */ + private static TypeInfo resolveJSType(String jsTypeName) { + if (jsTypeName == null || jsTypeName.isEmpty() || "void".equals(jsTypeName)) { + return TypeInfo.fromPrimitive("void"); + } + + // Handle JS primitives + switch (jsTypeName) { + case "string": + return TypeInfo.fromClass(String.class); + case "number": + return TypeInfo.fromClass(double.class); + case "boolean": + return TypeInfo.fromClass(boolean.class); + case "any": + return TypeInfo.fromClass(Object.class); + case "void": + return TypeInfo.fromPrimitive("void"); + } + + // Handle array types + if (jsTypeName.endsWith("[]")) { + String elementType = jsTypeName.substring(0, jsTypeName.length() - 2); + TypeInfo elementTypeInfo = resolveJSType(elementType); + return TypeInfo.arrayOf(elementTypeInfo); + } + + // Try to resolve from the JS type registry + JSTypeRegistry registry = JSTypeRegistry.getInstance(); + if (registry != null) { + JSTypeInfo jsTypeInfo = registry.getType(jsTypeName); + if (jsTypeInfo != null) { + return TypeInfo.fromJSTypeInfo(jsTypeInfo); + } + } + + // Fallback: unresolved type + return TypeInfo.unresolved(jsTypeName, jsTypeName); + } /** * Create a FieldInfo for an enum constant. diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/js_parser/JSHoverInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/js_parser/JSHoverInfo.java deleted file mode 100644 index edc2879ca..000000000 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/js_parser/JSHoverInfo.java +++ /dev/null @@ -1,117 +0,0 @@ -package noppes.npcs.client.gui.util.script.interpreter.js_parser; - -import java.util.List; - -/** - * Container classes for hover information in JS scripts. - */ -public class JSHoverInfo { - - /** - * Hover info for a variable. - */ - public static class VariableInfo { - public final String name; - public final String typeName; - public final JSTypeInfo resolvedType; - - public VariableInfo(String name, String typeName, JSTypeInfo resolvedType) { - this.name = name; - this.typeName = typeName; - this.resolvedType = resolvedType; - } - - public String buildHoverHtml() { - StringBuilder sb = new StringBuilder(); - sb.append("").append(name).append(": ").append(typeName).append(""); - - if (resolvedType != null && resolvedType.getDocumentation() != null) { - sb.append("

").append(resolvedType.getDocumentation()); - } - - return sb.toString(); - } - } - - /** - * Hover info for a method. - */ - public static class MethodInfo { - public final JSMethodInfo method; - public final JSTypeInfo containingType; - - public MethodInfo(JSMethodInfo method, JSTypeInfo containingType) { - this.method = method; - this.containingType = containingType; - } - - public String buildHoverHtml() { - StringBuilder sb = new StringBuilder(); - - // Show containing type - if (containingType != null) { - sb.append("").append(containingType.getFullName()).append("
"); - } - - // Show method signature - sb.append(method.buildHoverInfo()); - - return sb.toString(); - } - } - - /** - * Hover info for a field. - */ - public static class FieldInfo { - public final JSFieldInfo field; - public final JSTypeInfo containingType; - - public FieldInfo(JSFieldInfo field, JSTypeInfo containingType) { - this.field = field; - this.containingType = containingType; - } - - public String buildHoverHtml() { - StringBuilder sb = new StringBuilder(); - - // Show containing type - if (containingType != null) { - sb.append("").append(containingType.getFullName()).append("
"); - } - - // Show field info - sb.append(field.buildHoverInfo()); - - return sb.toString(); - } - } - - /** - * Hover info for a hook function. - */ - public static class FunctionInfo { - public final String name; - public final List signatures; - - public FunctionInfo(String name, List signatures) { - this.name = name; - this.signatures = signatures; - } - - public String buildHoverHtml() { - StringBuilder sb = new StringBuilder(); - sb.append("Hook: ").append(name).append(""); - - if (!signatures.isEmpty()) { - sb.append("

Overloads:"); - for (JSTypeRegistry.HookSignature sig : signatures) { - sb.append("
• function ").append(name).append("(") - .append(sig.paramName).append(": ").append(sig.paramType).append(")"); - } - } - - return sb.toString(); - } - } -} diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/js_parser/JSScriptAnalyzer.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/js_parser/JSScriptAnalyzer.java index 103ee677c..0f117c4d4 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/js_parser/JSScriptAnalyzer.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/js_parser/JSScriptAnalyzer.java @@ -2,7 +2,10 @@ package noppes.npcs.client.gui.util.script.interpreter.js_parser; import noppes.npcs.client.gui.util.script.interpreter.ScriptDocument; import noppes.npcs.client.gui.util.script.interpreter.ScriptLine; +import noppes.npcs.client.gui.util.script.interpreter.field.FieldInfo; +import noppes.npcs.client.gui.util.script.interpreter.method.MethodInfo; import noppes.npcs.client.gui.util.script.interpreter.token.TokenType; +import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo; import scala.annotation.meta.field; import java.util.*; @@ -127,32 +130,48 @@ public class JSScriptAnalyzer { // Check if this is a known hook if (registry.isHook(funcName)) { - marks.add(new ScriptLine.Mark(nameStart, nameEnd, TokenType.METHOD_DECL, - new JSHoverInfo.FunctionInfo(funcName, registry.getHookSignatures(funcName)))); - - // Infer parameter types from hook + // Create a unified MethodInfo for the hook List sigs = registry.getHookSignatures(funcName); - if (!sigs.isEmpty() && !params.isEmpty()) { - // Parse parameter names from function - String[] paramNames = params.split(","); - if (paramNames.length > 0) { - String paramName = paramNames[0].trim(); - // Use first hook signature's type - String paramType = sigs.get(0).paramType; - - // Store in function params and variable types - Map funcParamMap = new HashMap<>(); - funcParamMap.put(paramName, paramType); - functionParams.put(funcName, funcParamMap); - variableTypes.put(paramName, paramType); - - // Mark the parameter - int paramStart = m.start(2) + params.indexOf(paramName); - int paramEnd = paramStart + paramName.length(); - JSTypeInfo typeInfo = registry.getType(paramType); - marks.add(new ScriptLine.Mark(paramStart, paramEnd, TokenType.PARAMETER, - new JSHoverInfo.VariableInfo(paramName, paramType, typeInfo))); + if (!sigs.isEmpty()) { + JSTypeRegistry.HookSignature sig = sigs.get(0); + + // Create unified MethodInfo for hover info + TypeInfo paramTypeInfo = resolveJSType(sig.paramType); + List methodParams = new ArrayList<>(); + methodParams.add(FieldInfo.reflectionParam(sig.paramName, paramTypeInfo)); + + MethodInfo hookMethod = MethodInfo.declaration( + funcName, null, TypeInfo.fromPrimitive("void"), methodParams, + nameStart, nameStart, nameStart, -1, -1, 0, sig.doc + ); + + marks.add(new ScriptLine.Mark(nameStart, nameEnd, TokenType.METHOD_DECL, hookMethod)); + + // Infer parameter types from hook + if (!params.isEmpty()) { + // Parse parameter names from function + String[] paramNames = params.split(","); + if (paramNames.length > 0) { + String paramName = paramNames[0].trim(); + String paramType = sig.paramType; + + // Store in function params and variable types + Map funcParamMap = new HashMap<>(); + funcParamMap.put(paramName, paramType); + functionParams.put(funcName, funcParamMap); + variableTypes.put(paramName, paramType); + + // Mark the parameter with unified FieldInfo + int paramStart = m.start(2) + params.indexOf(paramName); + int paramEnd = paramStart + paramName.length(); + FieldInfo paramFieldInfo = FieldInfo.parameter( + paramName, paramTypeInfo, paramStart, hookMethod + ); + marks.add(new ScriptLine.Mark(paramStart, paramEnd, TokenType.PARAMETER, paramFieldInfo)); + } } + } else { + marks.add(new ScriptLine.Mark(nameStart, nameEnd, TokenType.METHOD_DECL, funcName)); } } else { marks.add(new ScriptLine.Mark(nameStart, nameEnd, TokenType.METHOD_DECL, funcName)); @@ -181,12 +200,12 @@ public class JSScriptAnalyzer { variableTypes.put(varName, inferredType); } - // Mark variable declaration + // Mark variable declaration with unified FieldInfo int varStart = m.start(1); int varEnd = m.end(1); - JSTypeInfo typeInfo = inferredType != null ? registry.getType(inferredType) : null; - marks.add(new ScriptLine.Mark(varStart, varEnd, TokenType.LOCAL_FIELD, - new JSHoverInfo.VariableInfo(varName, inferredType != null ? inferredType : "any", typeInfo))); + TypeInfo typeInfo = resolveJSType(inferredType != null ? inferredType : "any"); + FieldInfo varFieldInfo = FieldInfo.localField(varName, typeInfo, varStart, null); + marks.add(new ScriptLine.Mark(varStart, varEnd, TokenType.LOCAL_FIELD, varFieldInfo)); } } @@ -270,9 +289,9 @@ public class JSScriptAnalyzer { } // Check if it's a field - JSFieldInfo field = typeInfo.getField(member); - if (field != null) { - currentType = field.getType(); + JSFieldInfo f = typeInfo.getField(member); + if (f != null) { + currentType = f.getType(); continue; } @@ -301,11 +320,11 @@ public class JSScriptAnalyzer { int pos = m.start(); - // Mark receiver + // Mark receiver with unified FieldInfo if (currentType != null) { - JSTypeInfo typeInfo = registry.getType(currentType); - marks.add(new ScriptLine.Mark(pos, pos + receiverName.length(), TokenType.LOCAL_FIELD, - new JSHoverInfo.VariableInfo(receiverName, currentType, typeInfo))); + TypeInfo unifiedType = resolveJSType(currentType); + FieldInfo receiverField = FieldInfo.localField(receiverName, unifiedType, pos, null); + marks.add(new ScriptLine.Mark(pos, pos + receiverName.length(), TokenType.LOCAL_FIELD, receiverField)); } pos += receiverName.length() + 1; // +1 for the dot @@ -317,25 +336,29 @@ public class JSScriptAnalyzer { int memberEnd = pos + member.length(); if (currentType != null) { - JSTypeInfo typeInfo = registry.getType(currentType); - if (typeInfo != null) { + JSTypeInfo jsTypeInfo = registry.getType(currentType); + if (jsTypeInfo != null) { + TypeInfo unifiedContainingType = TypeInfo.fromJSTypeInfo(jsTypeInfo); + // Check method first - JSMethodInfo method = typeInfo.getMethod(member); - if (method != null) { - marks.add(new ScriptLine.Mark(memberStart, memberEnd, TokenType.METHOD_CALL, - new JSHoverInfo.MethodInfo(method, typeInfo))); - currentType = method.getReturnType(); + JSMethodInfo jsMethod = jsTypeInfo.getMethod(member); + if (jsMethod != null) { + // Convert to unified MethodInfo + MethodInfo unifiedMethod = MethodInfo.fromJSMethod(jsMethod, unifiedContainingType); + marks.add(new ScriptLine.Mark(memberStart, memberEnd, TokenType.METHOD_CALL, unifiedMethod)); + currentType = jsMethod.getReturnType(); } else { // Check field - JSFieldInfo field = typeInfo.getField(member); - if (field != null) { - marks.add(new ScriptLine.Mark(memberStart, memberEnd, TokenType.GLOBAL_FIELD, - new JSHoverInfo.FieldInfo(field, typeInfo))); - currentType = field.getType(); + JSFieldInfo jsField = jsTypeInfo.getField(member); + if (jsField != null) { + // Convert to unified FieldInfo + FieldInfo unifiedField = FieldInfo.fromJSField(jsField, unifiedContainingType); + marks.add(new ScriptLine.Mark(memberStart, memberEnd, TokenType.GLOBAL_FIELD, unifiedField)); + currentType = jsField.getType(); } else { // Unknown member - mark as undefined marks.add(new ScriptLine.Mark(memberStart, memberEnd, TokenType.UNDEFINED_VAR, - "Unknown member '" + member + "' on type " + typeInfo.getFullName())); + "Unknown member '" + member + "' on type " + jsTypeInfo.getFullName())); currentType = null; } } @@ -364,9 +387,20 @@ public class JSScriptAnalyzer { int nameEnd = m.end(1); if (registry.isHook(callExpr)) { - // It's a known hook being called - marks.add(new ScriptLine.Mark(nameStart, nameEnd, TokenType.METHOD_CALL, - new JSHoverInfo.FunctionInfo(callExpr, registry.getHookSignatures(callExpr)))); + // It's a known hook being called - create unified MethodInfo + List sigs = registry.getHookSignatures(callExpr); + if (!sigs.isEmpty()) { + JSTypeRegistry.HookSignature sig = sigs.get(0); + TypeInfo paramTypeInfo = resolveJSType(sig.paramType); + List methodParams = new ArrayList<>(); + methodParams.add(FieldInfo.reflectionParam(sig.paramName, paramTypeInfo)); + + MethodInfo hookMethod = MethodInfo.declaration( + callExpr, null, TypeInfo.fromPrimitive("void"), methodParams, + nameStart, nameStart, nameStart, -1, -1, 0, sig.doc + ); + marks.add(new ScriptLine.Mark(nameStart, nameEnd, TokenType.METHOD_CALL, hookMethod)); + } } } // Chained calls are handled in markMemberAccesses @@ -396,4 +430,46 @@ public class JSScriptAnalyzer { public Map getVariableTypes() { return new HashMap<>(variableTypes); } + + /** + * Resolves a JavaScript type name to a unified TypeInfo. + * Handles primitives, mapped types, and custom types from the registry. + */ + private TypeInfo resolveJSType(String jsTypeName) { + if (jsTypeName == null || jsTypeName.isEmpty() || "void".equals(jsTypeName)) { + return TypeInfo.fromPrimitive("void"); + } + + // Handle JS primitives + switch (jsTypeName) { + case "string": + return TypeInfo.fromClass(String.class); + case "number": + return TypeInfo.fromClass(double.class); + case "boolean": + return TypeInfo.fromClass(boolean.class); + case "any": + return TypeInfo.fromClass(Object.class); + case "void": + return TypeInfo.fromPrimitive("void"); + } + + // Handle array types + if (jsTypeName.endsWith("[]")) { + String elementType = jsTypeName.substring(0, jsTypeName.length() - 2); + TypeInfo elementTypeInfo = resolveJSType(elementType); + return TypeInfo.arrayOf(elementTypeInfo); + } + + // Try to resolve from the JS type registry + if (registry != null) { + JSTypeInfo jsTypeInfo = registry.getType(jsTypeName); + if (jsTypeInfo != null) { + return TypeInfo.fromJSTypeInfo(jsTypeInfo); + } + } + + // Fallback: unresolved type + return TypeInfo.unresolved(jsTypeName, jsTypeName); + } } diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/js_parser/JSTypeRegistry.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/js_parser/JSTypeRegistry.java index e9bdf053f..741543fa2 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/js_parser/JSTypeRegistry.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/js_parser/JSTypeRegistry.java @@ -94,7 +94,7 @@ public class JSTypeRegistry { sb.append(line).append("\n"); } parser.parseDefinitionFile(sb.toString(), fileName); - } + } } } catch (Exception e) { // File might not exist, that's ok 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 f39875b26..b5b1d9b7a 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,9 @@ 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.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.token.TokenType; import noppes.npcs.client.gui.util.script.interpreter.type.TypeChecker; import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo; @@ -160,6 +163,84 @@ public final class MethodInfo { return new MethodInfo(name, returnType, containingType, params, -1, -1, -1, -1, -1, true, true, modifiers, null, null); } + /** + * Create a MethodInfo from a JSMethodInfo (parsed from .d.ts files). + * Used when resolving method calls on JavaScript types. + * + * @param jsMethod The JavaScript method info from the type registry + * @param containingType The TypeInfo that owns this method + * @return A MethodInfo representing the JavaScript method + */ + public static MethodInfo fromJSMethod(JSMethodInfo jsMethod, TypeInfo containingType) { + String name = jsMethod.getName(); + + // Resolve the return type from the JS type registry + TypeInfo returnType = resolveJSType(jsMethod.getReturnType()); + + // Convert JS parameters to FieldInfo + List params = new ArrayList<>(); + List jsParams = jsMethod.getParameters(); + + for (JSMethodInfo.JSParameterInfo param : jsParams) { + String paramName = param.getName(); + String paramTypeName = param.getType(); + TypeInfo paramType = resolveJSType(paramTypeName); + params.add(FieldInfo.reflectionParam(paramName, paramType)); + } + + // JS methods are always public (no access modifiers in .d.ts) + int modifiers = Modifier.PUBLIC; + + // Use the documentation from the method if available + String documentation = jsMethod.getDocumentation(); + + return new MethodInfo(name, returnType, containingType, params, -1, -1, -1, -1, -1, true, false, modifiers, documentation, null); + } + + /** + * Resolves a JavaScript type name to a TypeInfo. + * Handles primitives, mapped types, and custom types from the registry. + */ + private static TypeInfo resolveJSType(String jsTypeName) { + if (jsTypeName == null || jsTypeName.isEmpty() || "void".equals(jsTypeName)) { + return TypeInfo.fromPrimitive("void"); + } + + // Handle JS primitives + switch (jsTypeName) { + case "string": + return TypeInfo.fromClass(String.class); + case "number": + return TypeInfo.fromClass(double.class); + case "boolean": + return TypeInfo.fromClass(boolean.class); + case "any": + return TypeInfo.fromClass(Object.class); + case "void": + return TypeInfo.fromPrimitive("void"); + } + + // Handle array types + if (jsTypeName.endsWith("[]")) { + String elementType = jsTypeName.substring(0, jsTypeName.length() - 2); + TypeInfo elementTypeInfo = resolveJSType(elementType); + // For arrays, we create an array type representation + return TypeInfo.arrayOf(elementTypeInfo); + } + + // Try to resolve from the JS type registry + JSTypeRegistry registry = JSTypeRegistry.getInstance(); + if (registry != null) { + JSTypeInfo jsTypeInfo = registry.getType(jsTypeName); + if (jsTypeInfo != null) { + return TypeInfo.fromJSTypeInfo(jsTypeInfo); + } + } + + // Fallback: unresolved type + return TypeInfo.unresolved(jsTypeName, jsTypeName); + } + // Getters public String getName() { return name; } public TypeInfo getReturnType() { return returnType; } diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java index c49a095bf..cf4dbfd11 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java @@ -2,6 +2,9 @@ package noppes.npcs.client.gui.util.script.interpreter.type; import noppes.npcs.client.gui.util.script.interpreter.field.EnumConstantInfo; import noppes.npcs.client.gui.util.script.interpreter.field.FieldInfo; +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.method.MethodInfo; import noppes.npcs.client.gui.util.script.interpreter.token.TokenType; @@ -11,7 +14,14 @@ import java.util.List; /** * Represents resolved type information for a class/interface/enum. - * Base class for type metadata. Extended by ScriptTypeInfo for script-defined types. + * + * This is the unified type system that supports: + * - Java types (via Class reflection) + * - Script-defined types (via ScriptTypeInfo subclass) + * - JavaScript/TypeScript types (via JSTypeInfo bridge) + * + * For JavaScript types, the jsTypeInfo field holds the parsed .d.ts data, + * and methods like hasMethod/getMethodInfo delegate to it. */ public class TypeInfo { @@ -21,17 +31,31 @@ public class TypeInfo { ENUM, UNKNOWN } + + /** + * Singleton constant for the void type. + */ + public static final TypeInfo VOID = fromPrimitive("void"); private final String simpleName; // e.g., "List", "ColorType" private final String fullName; // e.g., "java.util.List", "kamkeel...IOverlay$ColorType" private final String packageName; // e.g., "java.util", "kamkeel.npcdbc.api.client.overlay" private final Kind kind; // CLASS, INTERFACE, ENUM - private final Class javaClass; // The actual resolved Java class (null if unresolved) + private final Class javaClass; // The actual resolved Java class (null if unresolved or JS type) private final boolean resolved; // Whether this type was successfully resolved private final TypeInfo enclosingType; // For inner classes, the outer type (null if top-level) + + // JavaScript/TypeScript type info (for types from .d.ts files) + private final JSTypeInfo jsTypeInfo; // The JS type info (null if Java type) private TypeInfo(String simpleName, String fullName, String packageName, Kind kind, Class javaClass, boolean resolved, TypeInfo enclosingType) { + this(simpleName, fullName, packageName, kind, javaClass, resolved, enclosingType, null); + } + + private TypeInfo(String simpleName, String fullName, String packageName, + Kind kind, Class javaClass, boolean resolved, TypeInfo enclosingType, + JSTypeInfo jsTypeInfo) { this.simpleName = simpleName; this.fullName = fullName; this.packageName = packageName; @@ -39,6 +63,7 @@ public class TypeInfo { this.javaClass = javaClass; this.resolved = resolved; this.enclosingType = enclosingType; + this.jsTypeInfo = jsTypeInfo; } // Protected constructor for subclasses (like ScriptTypeInfo) @@ -52,6 +77,7 @@ public class TypeInfo { this.javaClass = javaClass; this.resolved = resolved; this.enclosingType = enclosingType; + this.jsTypeInfo = null; } // Factory methods @@ -122,6 +148,67 @@ public class TypeInfo { } return new TypeInfo(typeName, typeName, "", Kind.CLASS, primitiveClass, true, null); } + + /** + * Create a TypeInfo from a JavaScript/TypeScript type. + * This bridges JS types parsed from .d.ts files into the unified type system. + * + * @param jsType The parsed JS type info + * @return A TypeInfo wrapping the JS type + */ + public static TypeInfo fromJSTypeInfo(JSTypeInfo jsType) { + if (jsType == null) return null; + + String simpleName = jsType.getSimpleName(); + String fullName = jsType.getFullName(); + String namespace = jsType.getNamespace(); + + // JS interfaces are always Kind.INTERFACE + return new TypeInfo(simpleName, fullName, namespace != null ? namespace : "", + Kind.INTERFACE, null, true, null, jsType); + } + + /** + * Create an array type wrapping the given element type. + * + * @param elementType The type of elements in the array + * @return A TypeInfo representing the array type + */ + public static TypeInfo arrayOf(TypeInfo elementType) { + if (elementType == null) { + return fromClass(Object[].class); + } + + String simpleName = elementType.getSimpleName() + "[]"; + String fullName = elementType.getFullName() + "[]"; + String pkg = elementType.getPackageName(); + + // Try to get the actual array class if we have a Java class + Class arrayClass = null; + if (elementType.getJavaClass() != null) { + try { + arrayClass = java.lang.reflect.Array.newInstance(elementType.getJavaClass(), 0).getClass(); + } catch (Exception e) { + // Fallback to Object array if we can't create the specific array type + } + } + + return new TypeInfo(simpleName, fullName, pkg, Kind.CLASS, arrayClass, true, null); + } + + /** + * Check if this is a JavaScript type (backed by JSTypeInfo). + */ + public boolean isJSType() { + return jsTypeInfo != null; + } + + /** + * Get the underlying JSTypeInfo if this is a JS type. + */ + public JSTypeInfo getJSTypeInfo() { + return jsTypeInfo; + } // Getters public String getSimpleName() { return simpleName; } @@ -160,6 +247,11 @@ public class TypeInfo { * Check if this type has a method with the given name. */ public boolean hasMethod(String methodName) { + // Check JS type first + if (jsTypeInfo != null) { + return jsTypeInfo.hasMethod(methodName); + } + if (javaClass == null) return false; try { for (java.lang.reflect.Method m : javaClass.getMethods()) { @@ -177,6 +269,17 @@ public class TypeInfo { * Check if this type has a method with the given name and parameter count. */ public boolean hasMethod(String methodName, int paramCount) { + // Check JS type first + if (jsTypeInfo != null) { + List overloads = jsTypeInfo.getMethodOverloads(methodName); + for (JSMethodInfo m : overloads) { + if (m.getParameterCount() == paramCount) { + return true; + } + } + return false; + } + if (javaClass == null) return false; try { for (java.lang.reflect.Method m : javaClass.getMethods()) { @@ -274,6 +377,11 @@ public class TypeInfo { * Check if this type has a field with the given name. */ public boolean hasField(String fieldName) { + // Check JS type first + if (jsTypeInfo != null) { + return jsTypeInfo.hasField(fieldName); + } + if (javaClass == null) return false; try { for (java.lang.reflect.Field f : javaClass.getFields()) { @@ -320,9 +428,18 @@ public class TypeInfo { /** * Get MethodInfo for a method by name. Returns null if not found. - * Creates a synthetic MethodInfo based on reflection data. + * Creates a synthetic MethodInfo based on reflection data or JS type data. */ public MethodInfo getMethodInfo(String methodName) { + // Check JS type first + if (jsTypeInfo != null) { + JSMethodInfo jsMethod = jsTypeInfo.getMethod(methodName); + if (jsMethod != null) { + return MethodInfo.fromJSMethod(jsMethod, this); + } + return null; + } + if (javaClass == null) return null; try { for (java.lang.reflect.Method m : javaClass.getMethods()) { @@ -343,6 +460,15 @@ public class TypeInfo { */ public java.util.List getAllMethodOverloads(String methodName) { java.util.List overloads = new java.util.ArrayList<>(); + + // Check JS type first + if (jsTypeInfo != null) { + for (JSMethodInfo jsMethod : jsTypeInfo.getMethodOverloads(methodName)) { + overloads.add(MethodInfo.fromJSMethod(jsMethod, this)); + } + return overloads; + } + if (javaClass == null) return overloads; try { for (java.lang.reflect.Method m : javaClass.getMethods()) { @@ -507,9 +633,18 @@ public class TypeInfo { /** * Get FieldInfo for a field by name. Returns null if not found. - * Creates a synthetic FieldInfo based on reflection data. + * Creates a synthetic FieldInfo based on reflection data or JS type data. */ public FieldInfo getFieldInfo(String fieldName) { + // Check JS type first + if (jsTypeInfo != null) { + JSFieldInfo jsField = jsTypeInfo.getField(fieldName); + if (jsField != null) { + return FieldInfo.fromJSField(jsField, this); + } + return null; + } + if (javaClass == null) return null; try { for (java.lang.reflect.Field f : javaClass.getFields()) {