More fixes and improvements

This commit is contained in:
bigguy345 committed 2025-12-23 14:40:50 +02:00
1 parent b1799b5a22
commit 555fb481d3
6 files changed
+422 -49

No files matched your search

@@ -48,10 +48,27 @@ public final class FieldInfo {
return new FieldInfo(name, scope, null, -1, false, null);
}
/**
* Create a FieldInfo from reflection data for method parameters.
*/
public static FieldInfo reflectionParam(String name, TypeInfo type) {
return new FieldInfo(name, Scope.PARAMETER, type, -1, true, null);
}
/**
* Create a FieldInfo from reflection data for a class field.
*/
public static FieldInfo fromReflection(java.lang.reflect.Field field, TypeInfo containingType) {
String name = field.getName();
TypeInfo type = TypeInfo.fromClass(field.getType());
return new FieldInfo(name, Scope.GLOBAL, type, -1, true, null);
}
// Getters
public String getName() { return name; }
public Scope getScope() { return scope; }
public TypeInfo getDeclaredType() { return declaredType; }
public TypeInfo getTypeInfo() { return declaredType; } // Alias for getDeclaredType
public int getDeclarationOffset() { return declarationOffset; }
public boolean isResolved() { return resolved; }
public MethodInfo getContainingMethod() { return containingMethod; }
@@ -58,6 +58,24 @@ public final class MethodInfo {
return new MethodInfo(name, null, null, params, -1, -1, -1, false, false);
}
/**
* Create a MethodInfo from reflection data.
* Used when resolving method calls on known types.
*/
public static MethodInfo fromReflection(java.lang.reflect.Method method, TypeInfo containingType) {
String name = method.getName();
TypeInfo returnType = TypeInfo.fromClass(method.getReturnType());
List<FieldInfo> params = new ArrayList<>();
Class<?>[] paramTypes = method.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
TypeInfo paramType = TypeInfo.fromClass(paramTypes[i]);
params.add(FieldInfo.reflectionParam("arg" + i, paramType));
}
return new MethodInfo(name, returnType, containingType, params, -1, -1, -1, true, false);
}
// Getters
public String getName() { return name; }
public TypeInfo getReturnType() { return returnType; }
@@ -40,9 +40,9 @@ public class ScriptDocument {
private static final Pattern KEYWORD_PATTERN = Pattern.compile(
"\\b(null|boolean|int|float|double|long|char|byte|short|void|if|else|switch|case|for|while|do|try|catch|finally|return|throw|var|let|const|function|continue|break|this|new|typeof|instanceof|import)\\b");
// Declarations
// Declarations - Updated to capture method parameters
private static final Pattern IMPORT_PATTERN = Pattern.compile(
"(?m)\\bimport\\s+(?:static\\s+)?([A-Za-z_][A-Za-z0-9_]*(?:\\s*\\.\\s*[A-Za-z_][A-Za-z0-9_]*)*)(?:\\s*\\.\\s*\\*)?\\s*(?:;|$)");
"(?m)\\bimport\\s+(?:static\\s+)?([A-Za-z_][A-Za-z0-9_]*(?:\\s*\\.\\s*[A-Za-z_][A-Za-z0-9_]*)*)(?:\\s*\\.\\s*\\*?)?\\s*(?:;|$)");
private static final Pattern CLASS_DECL_PATTERN = Pattern.compile(
"\\b(class|interface|enum)\\s+([A-Za-z_][a-zA-Z0-9_]*)");
private static final Pattern METHOD_DECL_PATTERN = Pattern.compile(
@@ -66,6 +66,9 @@ public class ScriptDocument {
private final Map<String, FieldInfo> globalFields = new HashMap<>();
private final Set<String> wildcardPackages = new HashSet<>();
private Map<String, ImportData> importsBySimpleName = new HashMap<>();
// Local variables per method (methodStartOffset -> {varName -> FieldInfo})
private final Map<Integer, Map<String, FieldInfo>> methodLocals = new HashMap<>();
// Excluded regions (strings/comments) - positions where other patterns shouldn't match
private final List<int[]> excludedRanges = new ArrayList<>();
@@ -171,6 +174,7 @@ public class ScriptDocument {
globalFields.clear();
wildcardPackages.clear();
excludedRanges.clear();
methodLocals.clear();
// Phase 1: Find excluded regions (strings/comments)
findExcludedRanges();
@@ -178,7 +182,7 @@ public class ScriptDocument {
// Phase 2: Parse imports
parseImports();
// Phase 3: Parse structure (methods, fields)
// Phase 3: Parse structure (methods, fields, locals)
parseStructure();
// Phase 4: Build marks and assign to lines
@@ -189,7 +193,7 @@ public class ScriptDocument {
// Phase 6: Build tokens for each line
for (ScriptLine line : lines) {
line.buildTokensFromMarks(marks, text);
line.buildTokensFromMarks(marks, text, this);
}
// Phase 7: Compute indent guides
@@ -259,6 +263,11 @@ public class ScriptDocument {
String matchText = m.group(0);
boolean isWildcard = matchText.contains("*");
boolean isStatic = matchText.contains("static");
// Skip if path ends with dot (incomplete import)
if (fullPath.endsWith(".")) {
// continue;
}
int lastDot = fullPath.lastIndexOf('.');
String simpleName = isWildcard ? null : (lastDot >= 0 ? fullPath.substring(lastDot + 1) : fullPath);
@@ -284,6 +293,9 @@ public class ScriptDocument {
// Parse methods
parseMethodDeclarations();
// Parse local variables inside methods
parseLocalVariables();
// Parse global fields (outside methods)
parseGlobalFields();
}
@@ -306,8 +318,8 @@ public class ScriptDocument {
if (bodyEnd < 0)
bodyEnd = text.length();
// Parse parameters
List<FieldInfo> params = parseParameters(paramList, m.start());
// Parse parameters with their actual positions
List<FieldInfo> params = parseParametersWithPositions(paramList, m.start(3));
MethodInfo methodInfo = MethodInfo.declaration(
methodName,
@@ -340,12 +352,13 @@ public class ScriptDocument {
}
}
private List<FieldInfo> parseParameters(String paramList, int declOffset) {
private List<FieldInfo> parseParametersWithPositions(String paramList, int paramListStart) {
List<FieldInfo> params = new ArrayList<>();
if (paramList == null || paramList.trim().isEmpty()) {
return params;
}
// Pattern: Type varName (with optional spaces)
Pattern paramPattern = Pattern.compile(
"([a-zA-Z_][a-zA-Z0-9_<>\\[\\]]*(?:\\.{3})?)\\s+([a-zA-Z_][a-zA-Z0-9_]*)");
Matcher m = paramPattern.matcher(paramList);
@@ -353,11 +366,50 @@ public class ScriptDocument {
String typeName = m.group(1);
String paramName = m.group(2);
TypeInfo typeInfo = resolveType(typeName);
params.add(FieldInfo.parameter(paramName, typeInfo, declOffset, null));
// Store the absolute position of the parameter name
int paramNameStart = paramListStart + m.start(2);
params.add(FieldInfo.parameter(paramName, typeInfo, paramNameStart, null));
}
return params;
}
private void parseLocalVariables() {
for (MethodInfo method : methods) {
Map<String, FieldInfo> locals = new HashMap<>();
methodLocals.put(method.getDeclarationOffset(), locals);
int bodyStart = method.getBodyStart();
int bodyEnd = method.getBodyEnd();
if (bodyStart < 0 || bodyEnd <= bodyStart) continue;
String bodyText = text.substring(bodyStart, Math.min(bodyEnd, text.length()));
// Pattern for local variable declarations: Type varName = or Type varName;
Pattern localDecl = Pattern.compile(
"\\b([A-Za-z_][a-zA-Z0-9_<>\\[\\]]*)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*(=|;|,)");
Matcher m = localDecl.matcher(bodyText);
while (m.find()) {
int absPos = bodyStart + m.start();
if (isExcluded(absPos)) continue;
String typeName = m.group(1);
String varName = m.group(2);
// Skip if it looks like a method call or control flow
if (typeName.equals("return") || typeName.equals("if") || typeName.equals("while") ||
typeName.equals("for") || typeName.equals("switch") || typeName.equals("catch") ||
typeName.equals("new") || typeName.equals("throw")) {
continue;
}
TypeInfo typeInfo = resolveType(typeName);
int declPos = bodyStart + m.start(2);
FieldInfo fieldInfo = FieldInfo.localField(varName, typeInfo, declPos, method);
locals.put(varName, fieldInfo);
}
}
}
private void parseGlobalFields() {
Matcher m = FIELD_DECL_PATTERN.matcher(text);
while (m.find()) {
@@ -453,22 +505,125 @@ public class ScriptDocument {
private void markImports(List<ScriptLine.Mark> marks) {
for (ImportData imp : imports) {
// Mark 'import' keyword
marks.add(new ScriptLine.Mark(imp.getStartOffset(), imp.getStartOffset() + 6, TokenType.IMPORT_KEYWORD));
marks.add(new ScriptLine.Mark(imp.getStartOffset(), imp.getStartOffset() + 6, TokenType.IMPORT_KEYWORD, imp));
// Mark the path
// Parse path tokens
int pathStart = imp.getPathStartOffset();
int pathEnd = imp.getPathEndOffset();
if (imp.isResolved()) {
// Resolved import - mark as type declaration
if (imp.getResolvedType() != null) {
marks.add(new ScriptLine.Mark(pathStart, pathEnd, imp.getResolvedType().getTokenType()));
String pathText = text.substring(pathStart, Math.min(pathEnd, text.length()));
// Skip if path ends with dot (incomplete)
if (pathText.trim().endsWith(".")) {
continue;
}
// Tokenize the path
List<String> tokens = new ArrayList<>();
List<Integer> tokenStarts = new ArrayList<>();
List<Integer> tokenEnds = new ArrayList<>();
Pattern idPattern = Pattern.compile("[A-Za-z_][A-Za-z0-9_]*");
Matcher idm = idPattern.matcher(pathText);
while (idm.find()) {
tokens.add(idm.group());
tokenStarts.add(pathStart + idm.start());
tokenEnds.add(pathStart + idm.end());
}
if (tokens.isEmpty()) continue;
if (imp.isWildcard()) {
// Wildcard import: mark all tokens as package (blue) if valid
boolean pkgValid = typeResolver.isValidPackage(imp.getFullPath());
// Also check if it might be a class wildcard (OuterClass.*)
TypeInfo outerType = typeResolver.resolveFullName(imp.getFullPath());
if (pkgValid || outerType != null) {
// Mark package portion in blue
int pkgTokenCount = outerType != null ? tokens.size() - 1 : tokens.size();
for (int i = 0; i < Math.min(pkgTokenCount, tokens.size()); i++) {
marks.add(new ScriptLine.Mark(tokenStarts.get(i), tokenEnds.get(i), TokenType.TYPE_DECL, imp));
}
// If it's a class wildcard, mark the class with its type
if (outerType != null && tokens.size() > 0) {
int lastIdx = tokens.size() - 1;
marks.add(new ScriptLine.Mark(tokenStarts.get(lastIdx), tokenEnds.get(lastIdx),
outerType.getTokenType(), imp));
}
} else {
marks.add(new ScriptLine.Mark(pathStart, pathEnd, TokenType.TYPE_DECL));
// Unknown package/class - mark as undefined
marks.add(new ScriptLine.Mark(pathStart, pathEnd, TokenType.UNDEFINED_VAR, imp));
}
} else {
// Unresolved import
marks.add(new ScriptLine.Mark(pathStart, pathEnd, TokenType.UNDEFINED_VAR));
// Non-wildcard import: package is blue, class name(s) get type colors
TypeInfo resolvedType = imp.getResolvedType();
if (resolvedType != null && resolvedType.isResolved()) {
// Count package segments
String fullPath = imp.getFullPath();
String resolvedName = resolvedType.getFullName();
String pkgName = resolvedType.getPackageName();
int pkgSegments = pkgName != null && !pkgName.isEmpty()
? pkgName.split("\\.").length : 0;
// Mark package tokens in blue
for (int i = 0; i < Math.min(pkgSegments, tokens.size()); i++) {
marks.add(new ScriptLine.Mark(tokenStarts.get(i), tokenEnds.get(i), TokenType.TYPE_DECL, imp));
}
// Mark class tokens with appropriate type colors
// For inner classes, each segment might have a different type
for (int i = pkgSegments; i < tokens.size(); i++) {
String segmentName = tokens.get(i);
// Try to resolve this specific class/inner class
StringBuilder classPath = new StringBuilder();
if (pkgName != null && !pkgName.isEmpty()) {
classPath.append(pkgName).append(".");
}
for (int j = pkgSegments; j <= i; j++) {
if (j > pkgSegments) classPath.append("$");
classPath.append(tokens.get(j));
}
TypeInfo segmentType = typeResolver.resolveFullName(classPath.toString());
TokenType tokenType = segmentType != null
? segmentType.getTokenType()
: resolvedType.getTokenType();
marks.add(new ScriptLine.Mark(tokenStarts.get(i), tokenEnds.get(i), tokenType, imp));
}
} else {
// Try to figure out which part is invalid
// Mark valid package portion in blue, invalid portion in red
String fullPath = imp.getFullPath();
int lastValidPkg = -1;
StringBuilder pkgBuilder = new StringBuilder();
for (int i = 0; i < tokens.size(); i++) {
if (i > 0) pkgBuilder.append(".");
pkgBuilder.append(tokens.get(i));
// Check if this is a valid package
if (typeResolver.isValidPackage(pkgBuilder.toString())) {
lastValidPkg = i;
}
}
if (lastValidPkg >= 0) {
// Mark valid package in blue
for (int i = 0; i <= lastValidPkg; i++) {
marks.add(new ScriptLine.Mark(tokenStarts.get(i), tokenEnds.get(i), TokenType.TYPE_DECL, imp));
}
// Mark rest as undefined
for (int i = lastValidPkg + 1; i < tokens.size(); i++) {
marks.add(new ScriptLine.Mark(tokenStarts.get(i), tokenEnds.get(i), TokenType.UNDEFINED_VAR, imp));
}
} else {
// Everything is unresolved
marks.add(new ScriptLine.Mark(pathStart, pathEnd, TokenType.UNDEFINED_VAR, imp));
}
}
}
}
}
@@ -532,6 +687,7 @@ public class ScriptDocument {
while (m.find()) {
int start = m.start(1);
int end = m.end(1);
String methodName = m.group(1);
if (isExcluded(start))
continue;
@@ -558,21 +714,42 @@ public class ScriptDocument {
}
}
// Determine if we should highlight this as a method call
boolean shouldHighlight = true;
if (hasReceiver && receiverName != null && !receiverName.isEmpty()) {
// Check if receiver is an unresolved class reference
// Check if receiver is an uppercase class reference (static call)
if (Character.isUpperCase(receiverName.charAt(0))) {
TypeInfo recvType = resolveType(receiverName);
if (recvType == null || !recvType.isResolved()) {
// Don't highlight method calls on unresolved types
shouldHighlight = false;
// Method call on unresolved type - mark as undefined
marks.add(new ScriptLine.Mark(start, end, TokenType.UNDEFINED_VAR));
continue;
}
// Check if method exists on resolved type
if (!recvType.hasMethod(methodName) ) {
marks.add(new ScriptLine.Mark(start, end, TokenType.UNDEFINED_VAR));
continue;
}
// Valid method call on static type
MethodInfo methodInfo = recvType.getMethodInfo(methodName);
marks.add(new ScriptLine.Mark(start, end, TokenType.METHOD_CALL, methodInfo));
} else {
// Lower-case receiver - this is a method call on an instance
// Try to resolve the receiver's type
FieldInfo receiverField = resolveVariable(receiverName, start);
if (receiverField != null && receiverField.getTypeInfo() != null) {
TypeInfo recvType = receiverField.getTypeInfo();
if (recvType.hasMethod(methodName)) {
MethodInfo methodInfo = recvType.getMethodInfo(methodName);
marks.add(new ScriptLine.Mark(start, end, TokenType.METHOD_CALL, methodInfo));
} else {
marks.add(new ScriptLine.Mark(start, end, TokenType.UNDEFINED_VAR));
}
} else {
// Can't resolve receiver - mark method call anyway (could be valid at runtime)
marks.add(new ScriptLine.Mark(start, end, TokenType.METHOD_CALL));
}
}
}
if (shouldHighlight) {
} else {
// No receiver - standalone method call (local function or global)
marks.add(new ScriptLine.Mark(start, end, TokenType.METHOD_CALL));
}
}
@@ -589,6 +766,15 @@ public class ScriptDocument {
"throws", "super", "assert", "volatile", "transient"
));
// First pass: mark method parameters in their declaration positions
for (MethodInfo method : methods) {
for (FieldInfo param : method.getParameters()) {
int pos = param.getDeclarationOffset();
String name = param.getName();
marks.add(new ScriptLine.Mark(pos, pos + name.length(), TokenType.PARAMETER, param));
}
}
Pattern identifier = Pattern.compile("\\b([a-zA-Z_][a-zA-Z0-9_]*)\\b");
Matcher m = identifier.matcher(text);
@@ -601,7 +787,7 @@ public class ScriptDocument {
if (knownKeywords.contains(name))
continue;
// Skip if preceded by dot (field access)
// Skip if preceded by dot (field access - handled separately)
if (isPrecededByDot(position))
continue;
@@ -613,29 +799,53 @@ public class ScriptDocument {
if (Character.isUpperCase(name.charAt(0)))
continue;
// Skip method calls (followed by paren)
if (isFollowedByParen(m.end(1)))
continue;
// Find containing method
MethodInfo containingMethod = findMethodAtPosition(position);
if (containingMethod != null) {
// Check parameters
if (containingMethod.hasParameter(name)) {
marks.add(new ScriptLine.Mark(m.start(1), m.end(1), TokenType.PARAMETER));
FieldInfo paramInfo = containingMethod.getParameter(name);
marks.add(new ScriptLine.Mark(m.start(1), m.end(1), TokenType.PARAMETER, paramInfo));
continue;
}
// Check local variables
Map<String, FieldInfo> locals = methodLocals.get(containingMethod.getDeclarationOffset());
if (locals != null && locals.containsKey(name)) {
FieldInfo localInfo = locals.get(name);
// Only highlight if the position is after the declaration
if (localInfo.isVisibleAt(position)) {
marks.add(new ScriptLine.Mark(m.start(1), m.end(1), TokenType.LOCAL_FIELD, localInfo));
continue;
}
}
// Check global fields
if (globalFields.containsKey(name)) {
FieldInfo fieldInfo = globalFields.get(name);
marks.add(new ScriptLine.Mark(m.start(1), m.end(1), TokenType.GLOBAL_FIELD, fieldInfo));
continue;
}
// Unknown variable inside method - mark as undefined
marks.add(new ScriptLine.Mark(m.start(1), m.end(1), TokenType.UNDEFINED_VAR));
} else {
// Outside any method
// Check global fields
if (globalFields.containsKey(name)) {
FieldInfo fieldInfo = globalFields.get(name);
marks.add(new ScriptLine.Mark(m.start(1), m.end(1), TokenType.GLOBAL_FIELD, fieldInfo));
continue;
}
// Unknown variable outside method - mark as undefined
marks.add(new ScriptLine.Mark(m.start(1), m.end(1), TokenType.UNDEFINED_VAR));
}
// Check global fields
if (globalFields.containsKey(name)) {
marks.add(new ScriptLine.Mark(m.start(1), m.end(1), TokenType.GLOBAL_FIELD));
continue;
}
// Check if it's a method call (followed by paren)
if (isFollowedByParen(m.end(1)))
continue;
// Unknown identifier - could mark as undefined, but be conservative
// Only mark as undefined if it looks like a variable usage
}
}
@@ -652,13 +862,42 @@ public class ScriptDocument {
if (isExcluded(start))
continue;
// Skip in import/package statements
if (isInImportOrPackage(start))
continue;
// Try to resolve the class
TypeInfo info = resolveType(className);
if (info != null && info.isResolved()) {
marks.add(new ScriptLine.Mark(start, end, info.getTokenType()));
} else if (!importsBySimpleName.containsKey(className) &&
!TypeResolver.JAVA_LANG_CLASSES.contains(className) &&
wildcardPackages.isEmpty()) {
marks.add(new ScriptLine.Mark(start, end, info.getTokenType(), info));
} else {
// Unknown type - mark as undefined
marks.add(new ScriptLine.Mark(start, end, TokenType.UNDEFINED_VAR));
}
}
// Also mark uppercase identifiers in type positions (new X(), X variable, etc.)
Pattern typeUsage = Pattern.compile("\\b(new\\s+)?([A-Z][a-zA-Z0-9_]*)(?:\\s*<[^>]*>)?\\s*(?:\\(|\\[|\\b[a-z])");
Matcher tm = typeUsage.matcher(text);
while (tm.find()) {
String className = tm.group(2);
int start = tm.start(2);
int end = tm.end(2);
if (isExcluded(start))
continue;
// Skip in import/package statements
if (isInImportOrPackage(start))
continue;
// Try to resolve the class
TypeInfo info = resolveType(className);
if (info != null && info.isResolved()) {
marks.add(new ScriptLine.Mark(start, end, info.getTokenType(), info));
} else {
// Unknown type - mark as undefined
marks.add(new ScriptLine.Mark(start, end, TokenType.UNDEFINED_VAR));
}
}
@@ -771,6 +1010,34 @@ public class ScriptDocument {
// ==================== UTILITY METHODS ====================
private FieldInfo resolveVariable(String name, int position) {
// Find containing method
MethodInfo containingMethod = findMethodAtPosition(position);
if (containingMethod != null) {
// Check parameters
if (containingMethod.hasParameter(name)) {
return containingMethod.getParameter(name);
}
// Check local variables
Map<String, FieldInfo> locals = methodLocals.get(containingMethod.getDeclarationOffset());
if (locals != null && locals.containsKey(name)) {
FieldInfo localInfo = locals.get(name);
if (localInfo.isVisibleAt(position)) {
return localInfo;
}
}
}
// Check global fields
if (globalFields.containsKey(name)) {
return globalFields.get(name);
}
return null;
}
private boolean isPrecededByDot(int position) {
if (position <= 0)
return false;
@@ -102,11 +102,13 @@ public class ScriptLine {
/**
* Build tokens from a list of marks (highlight regions).
* Fills gaps between marks with DEFAULT tokens.
* Attaches metadata from marks to the created tokens.
*
* @param marks List of highlight marks that overlap this line
* @param fullText The complete document text
* @param document The parent ScriptDocument (for context lookups)
*/
public void buildTokensFromMarks(List<Mark> marks, String fullText) {
public void buildTokensFromMarks(List<Mark> marks, String fullText, ScriptDocument document) {
clearTokens();
int cursor = globalStart;
@@ -131,10 +133,24 @@ public class ScriptLine {
addToken(Token.defaultToken(gapText, cursor, gapEnd));
}
// Add the marked token
// Add the marked token with metadata
if (tokenStart < tokenEnd) {
String tokenText = fullText.substring(tokenStart, tokenEnd);
Token token = new Token(tokenText, tokenStart, tokenEnd, mark.type);
// Attach metadata based on type
if (mark.metadata != null) {
if (mark.metadata instanceof TypeInfo) {
token.setTypeInfo((TypeInfo) mark.metadata);
} else if (mark.metadata instanceof FieldInfo) {
token.setFieldInfo((FieldInfo) mark.metadata);
} else if (mark.metadata instanceof MethodInfo) {
token.setMethodInfo((MethodInfo) mark.metadata);
} else if (mark.metadata instanceof ImportData) {
token.setImportData((ImportData) mark.metadata);
}
}
addToken(token);
}
@@ -283,21 +299,31 @@ public class ScriptLine {
/**
* A simple mark representing a highlighted region.
* Used during token building phase.
* Can optionally carry metadata (TypeInfo, FieldInfo, MethodInfo, ImportData).
*/
public static class Mark {
public final int start;
public final int end;
public final TokenType type;
public final Object metadata;
public Mark(int start, int end, TokenType type) {
this.start = start;
this.end = end;
this.type = type;
this.metadata = null;
}
public Mark(int start, int end, TokenType type, Object metadata) {
this.start = start;
this.end = end;
this.type = type;
this.metadata = metadata;
}
@Override
public String toString() {
return "Mark{" + type + " [" + start + "-" + end + "]}";
return "Mark{" + type + " [" + start + "-" + end + "]" + (metadata != null ? " " + metadata.getClass().getSimpleName() : "") + "}";
}
}
}
@@ -153,6 +153,44 @@ public final class TypeInfo {
return false;
}
/**
* Get MethodInfo for a method by name. Returns null if not found.
* Creates a synthetic MethodInfo based on reflection data.
*/
public MethodInfo getMethodInfo(String methodName) {
if (javaClass == null) return null;
try {
for (java.lang.reflect.Method m : javaClass.getMethods()) {
if (m.getName().equals(methodName)) {
// Create a synthetic MethodInfo from reflection
return MethodInfo.fromReflection(m, this);
}
}
} catch (Exception e) {
// Security or linkage error
}
return null;
}
/**
* Get FieldInfo for a field by name. Returns null if not found.
* Creates a synthetic FieldInfo based on reflection data.
*/
public FieldInfo getFieldInfo(String fieldName) {
if (javaClass == null) return null;
try {
for (java.lang.reflect.Field f : javaClass.getFields()) {
if (f.getName().equals(fieldName)) {
// Create a synthetic FieldInfo from reflection
return FieldInfo.fromReflection(f, this);
}
}
} catch (Exception e) {
// Security or linkage error
}
return null;
}
@Override
public String toString() {
return "TypeInfo{" + fullName + ", " + kind + ", resolved=" + resolved + "}";
@@ -1,5 +1,7 @@
package noppes.npcs.client.gui.util.script.interpreter;
import noppes.npcs.client.gui.util.script.PackageFinder;
import java.util.*;
/**
@@ -213,6 +215,11 @@ public class TypeResolver {
return true;
}
if (PackageFinder.find(packagePath)) {
registerPackage(packagePath);
return true;
}
// Try to find a class in this package to validate it
String[] testClasses = getTestClassesForPackage(packagePath);
for (String testClass : testClasses) {