A bunch of JS parser fixes and improvements

This commit is contained in:
bigguy345 committed 2026-01-04 20:56:11 +02:00
1 parent 50bcbb5872
commit 61a363fdba
6 files changed
+420 -173

No files matched your search

@@ -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.
@@ -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("<b>").append(name).append("</b>: <i>").append(typeName).append("</i>");
if (resolvedType != null && resolvedType.getDocumentation() != null) {
sb.append("<br><br>").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("<i>").append(containingType.getFullName()).append("</i><br>");
}
// 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("<i>").append(containingType.getFullName()).append("</i><br>");
}
// 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<JSTypeRegistry.HookSignature> signatures;
public FunctionInfo(String name, List<JSTypeRegistry.HookSignature> signatures) {
this.name = name;
this.signatures = signatures;
}
public String buildHoverHtml() {
StringBuilder sb = new StringBuilder();
sb.append("<b>Hook: ").append(name).append("</b>");
if (!signatures.isEmpty()) {
sb.append("<br><br>Overloads:");
for (JSTypeRegistry.HookSignature sig : signatures) {
sb.append("<br>• function ").append(name).append("(")
.append(sig.paramName).append(": <i>").append(sig.paramType).append("</i>)");
}
}
return sb.toString();
}
}
}
@@ -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<JSTypeRegistry.HookSignature> 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<String, String> 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<FieldInfo> 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<String, String> 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<JSTypeRegistry.HookSignature> sigs = registry.getHookSignatures(callExpr);
if (!sigs.isEmpty()) {
JSTypeRegistry.HookSignature sig = sigs.get(0);
TypeInfo paramTypeInfo = resolveJSType(sig.paramType);
List<FieldInfo> 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<String, String> 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);
}
}
@@ -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
@@ -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<FieldInfo> params = new ArrayList<>();
List<JSMethodInfo.JSParameterInfo> 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; }
@@ -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<JSMethodInfo> 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<MethodInfo> getAllMethodOverloads(String methodName) {
java.util.List<MethodInfo> 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()) {