diff --git a/src/main/java/noppes/npcs/client/gui/util/script/autocomplete/JavaAutocompleteProvider.java b/src/main/java/noppes/npcs/client/gui/util/script/autocomplete/JavaAutocompleteProvider.java index e60e874eb..9a871afbb 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/autocomplete/JavaAutocompleteProvider.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/autocomplete/JavaAutocompleteProvider.java @@ -343,12 +343,11 @@ public class JavaAutocompleteProvider implements AutocompleteProvider { /** * Check if the receiver expression represents static access (class type). - * Similar logic to FieldChainMarker.isStaticContext(). + * Returns true if the expression is a TYPE NAME (not a variable). */ protected boolean isStaticAccess(String receiverExpr, int position) { - // Try to resolve the receiver expression as a type - TypeInfo typeCheck = document.resolveType(receiverExpr); - return typeCheck != null && typeCheck.isResolved(); + // Use unified static access checker + return TypeResolver.isStaticAccessExpression(receiverExpr, position, document); } protected UsageTracker getUsageTracker() { diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/FieldChainMarker.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/FieldChainMarker.java index 9a3e2d181..a94d81bc8 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/FieldChainMarker.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/FieldChainMarker.java @@ -1,6 +1,7 @@ package noppes.npcs.client.gui.util.script.interpreter; import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo; +import noppes.npcs.client.gui.util.script.interpreter.type.TypeResolver; import noppes.npcs.client.gui.util.script.interpreter.type.ScriptTypeInfo; import noppes.npcs.client.gui.util.script.interpreter.field.FieldInfo; import noppes.npcs.client.gui.util.script.interpreter.field.FieldAccessInfo; @@ -477,9 +478,9 @@ public class FieldChainMarker { return false; String previousSegment = ctx.chain.segments.get(ctx.currentIndex - 1); - - // Try to resolve the previous segment as a type - TypeInfo typeCheck = document.resolveType(previousSegment); - return typeCheck != null && typeCheck.isResolved(); + int prevPos = ctx.chain.positions.get(ctx.currentIndex - 1)[0]; + + // Use unified static access checker + return TypeResolver.isStaticAccessExpression(previousSegment, prevPos, document); } } diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java index 6a1682f6b..41378ea25 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java @@ -2789,6 +2789,10 @@ public class ScriptDocument { * Check if a method call is a static access (Class.method() style). * Returns true if the immediate receiver before the dot is a class name (uppercase). */ + /** + * Check if a method call at the given position is static access. + * Walks backward from the method name to analyze the receiver. + */ private boolean isStaticAccessCall(int methodNameStart) { // Walk backward to find the dot int pos = methodNameStart - 1; @@ -2813,7 +2817,8 @@ public class ScriptDocument { char c = text.charAt(pos); if (c == ')' || c == ']') { - // Method call or array - this is instance access + // Method call or array - would need complex expression resolution + // For now, conservatively treat as instance access return false; } @@ -2828,24 +2833,20 @@ public class ScriptDocument { String ident = text.substring(identStart, identEnd); - // Check if this is a class name (starts with uppercase) - // AND check if there's no dot before it (making it a direct class reference) - // If there's a chain like "obj.SomeClass.method()", we need to check further - // Skip whitespace before the identifier while (pos >= 0 && Character.isWhitespace(text.charAt(pos))) pos--; - - // If preceded by a dot, this might be part of a longer chain - // In that case, it's not a direct static access + + // If preceded by a dot, this is part of a chain - treat as instance for now if (pos >= 0 && text.charAt(pos) == '.') { return false; } - - // It's static access if the identifier resolves to a type + + // Direct identifier - check if it resolves to a type (static) or variable (instance) if (ident.isEmpty()) return false; - TypeInfo typeCheck = resolveType(ident); - return typeCheck != null && typeCheck.isResolved(); + + // Use unified static access checker + return TypeResolver.isStaticAccessExpression(ident, identStart, this); } /** @@ -3486,6 +3487,14 @@ public class ScriptDocument { if (currentType == null || !currentType.isResolved()) { return null; } + + // Check if current type is a synthetic type + if (isJavaScript()) { + SyntheticType syntheticType = typeResolver.getSyntheticType(currentType.getSimpleName()); + if (syntheticType != null) { + return resolveSyntheticChainSegment(syntheticType, segment); + } + } if (segment.isMethodCall) { // Method call - get return type with argument-based overload resolution @@ -5323,7 +5332,7 @@ public class ScriptDocument { * Resolve a variable by name at a given position. * Works for BOTH Java and JavaScript using unified data structures. */ - FieldInfo resolveVariable(String name, int position) { + public FieldInfo resolveVariable(String name, int position) { // Find containing method/function MethodInfo containingMethod = findMethodAtPosition(position); diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeResolver.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeResolver.java index 4b930c934..7b2673a05 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeResolver.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeResolver.java @@ -668,4 +668,117 @@ public class TypeResolver { return typeInfo.getTokenType(); } } + + /** + * Check if an expression represents static access (accessing a Class, not an instance). + * This checks if the expression STRING resolves to a TYPE name. + * + * Examples: + * - "String" -> true (it's a class name) + * - "myVar" -> false (it's a variable, even if myVar holds a ClassTypeInfo) + * - "Java" -> true (it's a synthetic type) + * + * Note: This is different from checking if a variable's TYPE is ClassTypeInfo. + * For variables holding ClassTypeInfo (like `var File = Java.type("File")`), + * the receiver type itself should be checked separately. + * + * @param typeInfo The resolved TypeInfo (can be null) + * @param wasResolvedAsType true if this was resolved as a type name (not a variable) + * @return true if this represents static access (direct class reference) + */ + public static boolean isStaticAccess(TypeInfo typeInfo, boolean wasResolvedAsType) { + // If explicitly resolved as a type name, it's static access + if (wasResolvedAsType) + return true; + + if (typeInfo == null) + return false; + + // If the typeInfo itself is a ClassTypeInfo (e.g., result of Java.type()), + // then accessing members on it should be static + if (typeInfo.isClassReference()) + return true; + + return false; + } + + /** + * Unified static access checker for expressions. + * Determines if an identifier represents static access (type name) or instance access (variable). + * + * This method is used by ScriptDocument, JavaAutocompleteProvider, and FieldChainMarker. + * + * @param identifier The identifier to check (e.g., "String", "myVar", "Java") + * @param position The position in the document for context + * @param document The script document for resolution + * @return true if this represents static access + */ + public static boolean isStaticAccessExpression(String identifier, int position, ScriptDocument document) { + if (identifier == null || identifier.isEmpty() || document == null) { + return false; + } + + // First check if it's a variable/field (instance access) + FieldInfo fieldInfo = document.resolveVariable(identifier, position); + if (fieldInfo != null && fieldInfo.isResolved()) { + // It's a variable - check if its type is a ClassTypeInfo + return isStaticAccess(fieldInfo.getTypeInfo(), false); + } + + // Not a variable - check if it's a type name + TypeInfo typeCheck = document.resolveExpressionType(identifier,position); + boolean isType = typeCheck != null && typeCheck.isResolved(); + return isStaticAccess(typeCheck, isType); + } + + /** + * Parse string literal arguments from a method call. + * Used for resolving dynamic return types like Java.type("className"). + * + * @param argumentsStr The arguments string (e.g., "\"java.io.File\", 123") + * @return Array of parsed arguments + */ + public static String[] parseStringArguments(String argumentsStr) { + if (argumentsStr == null || argumentsStr.isEmpty()) { + return new String[0]; + } + + List args = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + int depth = 0; + boolean inString = false; + char stringChar = 0; + + for (int i = 0; i < argumentsStr.length(); i++) { + char c = argumentsStr.charAt(i); + + if (inString) { + current.append(c); + if (c == stringChar && (i == 0 || argumentsStr.charAt(i - 1) != '\\')) { + inString = false; + } + } else if (c == '"' || c == '\'') { + current.append(c); + inString = true; + stringChar = c; + } else if (c == '(' || c == '[' || c == '{') { + depth++; + current.append(c); + } else if (c == ')' || c == ']' || c == '}') { + depth--; + current.append(c); + } else if (c == ',' && depth == 0) { + args.add(current.toString().trim()); + current = new StringBuilder(); + } else { + current.append(c); + } + } + + if (current.length() > 0) { + args.add(current.toString().trim()); + } + + return args.toArray(new String[0]); + } }