From f9babf040d15e2c1f287985ba6ec342f893f3fa2 Mon Sep 17 00:00:00 2001 From: bigguy345 Date: Sun, 4 Jan 2026 21:41:39 +0200 Subject: [PATCH] temp JS/java merger --- .../script/interpreter/ScriptDocument.java | 735 ++++++++++++++++-- .../js_parser/JSScriptAnalyzer.java | 77 ++ .../interpreter/js_parser/JSTypeRegistry.java | 10 + .../script/interpreter/type/TypeResolver.java | 116 ++- 4 files changed, 872 insertions(+), 66 deletions(-) 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 af9d60ae1..e004fb2e4 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 @@ -8,7 +8,11 @@ import noppes.npcs.client.gui.util.script.interpreter.field.AssignmentInfo; import noppes.npcs.client.gui.util.script.interpreter.field.EnumConstantInfo; import noppes.npcs.client.gui.util.script.interpreter.field.FieldAccessInfo; 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.JSScriptAnalyzer; +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.method.MethodCallInfo; import noppes.npcs.client.gui.util.script.interpreter.method.MethodInfo; import noppes.npcs.client.gui.util.script.interpreter.method.MethodSignature; @@ -20,6 +24,7 @@ import noppes.npcs.client.gui.util.script.interpreter.type.ScriptTypeInfo; import noppes.npcs.client.gui.util.script.interpreter.type.TypeChecker; import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo; import noppes.npcs.client.gui.util.script.interpreter.type.TypeResolver; +import scala.annotation.meta.field; import java.util.*; import java.util.regex.Matcher; @@ -233,13 +238,20 @@ public class ScriptDocument { } // ==================== TOKENIZATION ==================== + + // JS-specific: Function parameter types (funcName -> paramName -> typeName) + private final Map> jsFunctionParams = new HashMap<>(); + + // JS-specific: Inferred variable types (varName -> typeName) + private final Map jsVariableTypes = new HashMap<>(); /** * Main tokenization entry point. * Performs complete analysis and builds tokens for all lines. + * Uses a UNIFIED pipeline for both Java and JavaScript. */ public void formatCodeText() { - // Clear previous state + // Clear previous state (common for both languages) imports.clear(); methods.clear(); globalFields.clear(); @@ -250,15 +262,11 @@ public class ScriptDocument { methodCalls.clear(); externalFieldAssignments.clear(); declarationErrors.clear(); + jsFunctionParams.clear(); + jsVariableTypes.clear(); - List marks; - - // Use different analysis paths for JavaScript vs Java - if (isJavaScript()) { - marks = formatJavaScript(); - } else { - marks = formatJava(); - } + // Unified pipeline for both languages + List marks = formatUnified(); // Phase 5: Resolve conflicts and sort marks = resolveConflicts(marks); @@ -272,41 +280,67 @@ public class ScriptDocument { computeIndentGuides(marks); } - // Store the last JS analyzer for autocomplete + // Store the last JS analyzer for autocomplete (deprecated - use getJSVariableTypes instead) + @Deprecated private JSScriptAnalyzer currentJSAnalyzer; /** - * Format JavaScript/ECMAScript code using the JS type inference system. - */ - private List formatJavaScript() { - // Use JSScriptAnalyzer for JS-specific analysis - currentJSAnalyzer = new JSScriptAnalyzer(this); - return currentJSAnalyzer.analyze(); - } - - /** - * Get the last JS analyzer (for autocomplete to access variable types). + * @deprecated Use the unified pipeline. Variable types are now in jsVariableTypes. */ + @Deprecated public JSScriptAnalyzer getJSAnalyzer() { return currentJSAnalyzer; } /** - * Format Java/Groovy code using the full Java analysis system. + * Get inferred JS variable types (for autocomplete). */ - private List formatJava() { - // Phase 1: Find excluded regions (strings/comments) + public Map getJSVariableTypes() { + return new HashMap<>(jsVariableTypes); + } + + /** + * Get JS function parameter types. + */ + public Map> getJSFunctionParams() { + return new HashMap<>(jsFunctionParams); + } + + /** + * Unified format method - single pipeline for both Java and JavaScript. + * The methods called here are language-aware and handle both cases. + */ + private List formatUnified() { + // Phase 1: Find excluded regions (strings/comments) - same for both findExcludedRanges(); - // Phase 2: Parse imports + // Phase 2: Parse imports (Java only, JS skips this) parseImports(); - // Phase 3: Parse structure (script types, methods, fields, locals) + // Phase 3: Parse structure (methods, fields, locals) - language aware parseStructure(); - // Phase 4: Build marks and assign to lines + // Phase 4: Build marks - language aware return buildMarks(); } + + /** + * @deprecated Use formatUnified() instead. Kept for compatibility. + */ + @Deprecated + private List formatJavaScript() { + // Delegate to unified pipeline + currentJSAnalyzer = new JSScriptAnalyzer(this); + return currentJSAnalyzer.analyze(); + } + + /** + * @deprecated Use formatUnified() instead. Kept for compatibility. + */ + @Deprecated + private List formatJava() { + return formatUnified(); + } // ==================== PHASE 1: EXCLUDED RANGES ==================== @@ -377,6 +411,11 @@ public class ScriptDocument { // ==================== PHASE 2: IMPORTS ==================== private void parseImports() { + // JavaScript doesn't use Java-style imports + if (isJavaScript()) { + return; + } + Matcher m = IMPORT_PATTERN.matcher(text); while (m.find()) { if (isExcluded(m.start())) @@ -418,28 +457,37 @@ public class ScriptDocument { imp.clearReferences(); } - // Parse script-defined types (classes, interfaces, enums) - parseScriptTypes(); + // Parse script-defined types (classes, interfaces, enums) - Java only + if (!isJavaScript()) { + parseScriptTypes(); + } - // Parse methods + // Parse methods/functions - language aware parseMethodDeclarations(); - // Parse local variables inside methods + // Parse local variables inside methods/functions - language aware parseLocalVariables(); - // Parse global fields (outside methods) - parseGlobalFields(); + // Parse global fields (outside methods) - Java only + if (!isJavaScript()) { + parseGlobalFields(); + } - // Parse and validate assignments (reassignments) - stores in FieldInfo - parseAssignments(); + // Parse and validate assignments (reassignments) - Java only + if (!isJavaScript()) { + parseAssignments(); + } - // Detect method overrides and interface implementations for script types - detectMethodInheritance(); + // Detect method overrides and interface implementations for script types - Java only + if (!isJavaScript()) { + detectMethodInheritance(); + } } /** * Parse class, interface, and enum declarations defined in the script. * Creates ScriptTypeInfo instances and stores them for later resolution. + * Java only - JavaScript doesn't use class declarations in the same way. */ private void parseScriptTypes() { // Pattern: [modifiers] (class|interface|enum) ClassName [optional ()] [extends Parent] [implements I1, I2...] { ... } @@ -626,6 +674,97 @@ public class ScriptDocument { } private void parseMethodDeclarations() { + if (isJavaScript()) { + parseJSFunctions(); + } else { + parseJavaMethods(); + } + } + + /** + * Parse JavaScript function declarations and infer parameter types from hooks. + */ + private void parseJSFunctions() { + // Pattern: function funcName(params) { ... } + Pattern funcPattern = Pattern.compile("function\\s+(\\w+)\\s*\\(([^)]*)\\)\\s*\\{"); + Matcher m = funcPattern.matcher(text); + + while (m.find()) { + if (isExcluded(m.start())) continue; + + String funcName = m.group(1); + String paramList = m.group(2); + + int nameStart = m.start(1); + int nameEnd = m.end(1); + int bodyStart = text.indexOf('{', m.end() - 1); + int bodyEnd = findMatchingBrace(bodyStart); + if (bodyEnd < 0) bodyEnd = text.length(); + + // Check if this is a known hook function + List params = new ArrayList<>(); + TypeInfo returnType = TypeInfo.fromPrimitive("void"); + String documentation = null; + + if (typeResolver.isJSHook(funcName)) { + List sigs = typeResolver.getJSHookSignatures(funcName); + if (!sigs.isEmpty()) { + JSTypeRegistry.HookSignature sig = sigs.get(0); + documentation = sig.doc; + + // Infer parameter types from hook signature + if (paramList != null && !paramList.trim().isEmpty()) { + String[] paramNames = paramList.split(","); + if (paramNames.length > 0) { + String paramName = paramNames[0].trim(); + String paramTypeName = sig.paramType; + TypeInfo paramType = typeResolver.resolveJSType(paramTypeName); + + // Store in tracking maps for later use + Map funcParams = new HashMap<>(); + funcParams.put(paramName, paramTypeName); + jsFunctionParams.put(funcName, funcParams); + jsVariableTypes.put(paramName, paramTypeName); + + // Create parameter with proper type + int paramStart = m.start(2) + paramList.indexOf(paramName); + params.add(FieldInfo.parameter(paramName, paramType, paramStart, null)); + } + } + } + } else { + // Non-hook function - parameters have no type + if (paramList != null && !paramList.trim().isEmpty()) { + int paramOffset = m.start(2); + for (String p : paramList.split(",")) { + String pn = p.trim(); + if (!pn.isEmpty()) { + int paramStart = paramOffset + paramList.indexOf(pn); + params.add(FieldInfo.parameter(pn, TypeInfo.fromPrimitive("any"), paramStart, null)); + + // Track as untyped parameter + Map funcParams = jsFunctionParams.computeIfAbsent(funcName, k -> new HashMap<>()); + funcParams.put(pn, "any"); + } + } + } + } + + // Create MethodInfo for the function + MethodInfo methodInfo = MethodInfo.declaration( + funcName, null, returnType, params, + m.start(), nameStart, nameStart, + bodyStart, bodyEnd, 0, documentation + ); + + methods.add(methodInfo); + } + } + + /** + * Parse Java method declarations. + */ + private void parseJavaMethods() { Pattern methodWithBody = Pattern.compile( "\\b([a-zA-Z_][a-zA-Z0-9_<>\\[\\]]*)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\(([^)]*)\\)\\s*(\\{|;)"); @@ -699,25 +838,6 @@ public class ScriptDocument { // Check for duplicate method declarations checkDuplicateMethods(); - - // Also parse JS-style functions - Matcher funcM = FUNC_PARAMS_PATTERN.matcher(text); - while (funcM.find()) { - if (isExcluded(funcM.start())) - continue; - // JS functions don't have explicit return types - String paramList = funcM.group(1); - List params = new ArrayList<>(); - if (paramList != null && !paramList.trim().isEmpty()) { - for (String p : paramList.split(",")) { - String pn = p.trim(); - if (!pn.isEmpty()) { - params.add(FieldInfo.parameter(pn, null, funcM.start(), null)); - } - } - } - // We don't need to store JS functions in methods list - just extract params - } } /** @@ -876,6 +996,144 @@ public class ScriptDocument { } private void parseLocalVariables() { + if (isJavaScript()) { + parseJSVariables(); + } else { + parseJavaLocalVariables(); + } + } + + /** + * Parse JavaScript variable declarations (var/let/const). + */ + private void parseJSVariables() { + // Pattern: var/let/const varName = expression; or var/let/const varName; + Pattern varPattern = Pattern.compile("(?:var|let|const)\\s+(\\w+)(?:\\s*=\\s*([^;]+))?"); + Matcher m = varPattern.matcher(text); + + while (m.find()) { + if (isExcluded(m.start())) continue; + + String varName = m.group(1); + String initializer = m.group(2); + + int varStart = m.start(1); + int varEnd = m.end(1); + + // Infer type from initializer + String inferredType = "any"; + if (initializer != null) { + inferredType = inferJSTypeFromExpression(initializer.trim()); + } + + // Store in tracking maps + jsVariableTypes.put(varName, inferredType); + + TypeInfo typeInfo = typeResolver.resolveJSType(inferredType); + + // Store as a global field (JS doesn't have method-scoped locals in the same way) + FieldInfo fieldInfo = FieldInfo.localField(varName, typeInfo, varStart, null); + globalFields.put(varName, fieldInfo); + } + } + + /** + * Infer type from a JavaScript expression. + */ + private String inferJSTypeFromExpression(String expr) { + if (expr == null || expr.isEmpty()) return "any"; + + // String literal + if (expr.startsWith("\"") || expr.startsWith("'")) { + return "string"; + } + + // Number literal + if (expr.matches("-?\\d+\\.?\\d*")) { + return "number"; + } + + // Boolean literal + if (expr.equals("true") || expr.equals("false")) { + return "boolean"; + } + + // Null/undefined + if (expr.equals("null")) return "null"; + if (expr.equals("undefined")) return "undefined"; + + // Array literal + if (expr.startsWith("[")) { + return "any[]"; + } + + // Object literal + if (expr.startsWith("{")) { + return "object"; + } + + // Method call chain: something.method() - infer from method return type + if (expr.contains(".") && expr.contains("(")) { + return inferJSTypeFromMethodCall(expr); + } + + // Variable reference + if (jsVariableTypes.containsKey(expr)) { + return jsVariableTypes.get(expr); + } + + return "any"; + } + + /** + * Infer type from a JavaScript method call chain. + */ + private String inferJSTypeFromMethodCall(String expr) { + // Remove trailing parentheses and args for analysis + int parenIndex = expr.indexOf('('); + if (parenIndex > 0) { + expr = expr.substring(0, parenIndex); + } + + String[] parts = expr.split("\\."); + if (parts.length < 2) return "any"; + + // Start with the receiver type + String currentType = jsVariableTypes.get(parts[0]); + if (currentType == null) return "any"; + + // Walk the chain + JSTypeRegistry registry = typeResolver.getJSTypeRegistry(); + for (int i = 1; i < parts.length; i++) { + JSTypeInfo typeInfo = registry.getType(currentType); + if (typeInfo == null) return "any"; + + String member = parts[i]; + + // Check if it's a method + JSMethodInfo method = typeInfo.getMethod(member); + if (method != null) { + currentType = method.getReturnType(); + continue; + } + + // Check if it's a field + JSFieldInfo field = typeInfo.getField(member); + if (field != null) { + currentType = field.getType(); + continue; + } + + return "any"; // Unknown member + } + + return currentType; + } + + /** + * Parse Java local variables inside methods. + */ + private void parseJavaLocalVariables() { for (MethodInfo method : getAllMethods()) { Map locals = new HashMap<>(); methodLocals.put(method.getDeclarationOffset(), locals); @@ -1450,12 +1708,28 @@ public class ScriptDocument { } public TypeInfo resolveType(String typeName) { + // Use language-appropriate resolution + if (isJavaScript()) { + return resolveJSTypeUnified(typeName); + } return resolveTypeAndTrackUsage(typeName); } + /** + * Resolve a JS type name to unified TypeInfo. + * Handles JS primitives, .d.ts defined types, and falls back to Java types. + */ + private TypeInfo resolveJSTypeUnified(String typeName) { + if (typeName == null || typeName.isEmpty()) { + return TypeInfo.fromPrimitive("void"); + } + return typeResolver.resolveJSType(typeName); + } + /** * Resolve a type and track the import usage for unused import detection. * Checks script-defined types first, then falls back to imported types. + * Used for Java/Groovy scripts. */ private TypeInfo resolveTypeAndTrackUsage(String typeName) { if (typeName == null || typeName.isEmpty()) @@ -1514,10 +1788,311 @@ public class ScriptDocument { private List buildMarks() { List marks = new ArrayList<>(); - // Comments and strings first (highest priority) + // Comments and strings first (highest priority) - same for both languages addPatternMarks(marks, COMMENT_PATTERN, TokenType.COMMENT); addPatternMarks(marks, STRING_PATTERN, TokenType.STRING); + + // Keywords - same for both languages (KEYWORD_PATTERN includes JS keywords) + addPatternMarks(marks, KEYWORD_PATTERN, TokenType.KEYWORD); + + // Numbers - same for both languages + addPatternMarks(marks, NUMBER_PATTERN, TokenType.LITERAL); + + // Language-specific marking + if (isJavaScript()) { + buildJSMarks(marks); + } else { + buildJavaMarks(marks); + } + return marks; + } + + /** + * Build marks specific to JavaScript/ECMAScript. + */ + private void buildJSMarks(List marks) { + // Mark function declarations + markJSFunctionDeclarations(marks); + + // Mark variable declarations + markJSVariableDeclarations(marks); + + // Mark member accesses with type validation + markJSMemberAccesses(marks); + + // Mark method calls + markJSMethodCalls(marks); + + // Mark standalone identifiers (parameters and variables in function bodies) + markJSIdentifiers(marks); + } + + /** + * Mark JavaScript function declarations. + */ + private void markJSFunctionDeclarations(List marks) { + Pattern funcPattern = Pattern.compile("function\\s+(\\w+)\\s*\\(([^)]*)\\)"); + Matcher m = funcPattern.matcher(text); + + while (m.find()) { + if (isExcluded(m.start())) continue; + + String funcName = m.group(1); + String params = m.group(2); + int nameStart = m.start(1); + int nameEnd = m.end(1); + + // Check if this is a known hook + if (typeResolver.isJSHook(funcName)) { + List sigs = typeResolver.getJSHookSignatures(funcName); + if (!sigs.isEmpty()) { + JSTypeRegistry.HookSignature sig = sigs.get(0); + + // Create unified MethodInfo for hover info + TypeInfo paramTypeInfo = typeResolver.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)); + + // Mark parameters with inferred types + if (params != null && !params.isEmpty()) { + String[] paramNames = params.split(","); + if (paramNames.length > 0) { + String paramName = paramNames[0].trim(); + 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)); + } + } + continue; + } + } + + // Non-hook function + marks.add(new ScriptLine.Mark(nameStart, nameEnd, TokenType.METHOD_DECL, funcName)); + + // Mark parameters without type info + if (params != null && !params.isEmpty()) { + int paramOffset = m.start(2); + for (String p : params.split(",")) { + String pn = p.trim(); + if (!pn.isEmpty()) { + int paramStart = paramOffset + params.indexOf(pn); + int paramEnd = paramStart + pn.length(); + marks.add(new ScriptLine.Mark(paramStart, paramEnd, TokenType.PARAMETER)); + } + } + } + } + } + + /** + * Mark JavaScript variable declarations. + */ + private void markJSVariableDeclarations(List marks) { + Pattern varPattern = Pattern.compile("(?:var|let|const)\\s+(\\w+)"); + Matcher m = varPattern.matcher(text); + + while (m.find()) { + if (isExcluded(m.start())) continue; + + String varName = m.group(1); + int varStart = m.start(1); + int varEnd = m.end(1); + + String inferredType = jsVariableTypes.getOrDefault(varName, "any"); + TypeInfo typeInfo = typeResolver.resolveJSType(inferredType); + FieldInfo varFieldInfo = FieldInfo.localField(varName, typeInfo, varStart, null); + + marks.add(new ScriptLine.Mark(varStart, varEnd, TokenType.LOCAL_FIELD, varFieldInfo)); + } + } + + /** + * Mark JavaScript member accesses (x.y.z) with type validation. + */ + private void markJSMemberAccesses(List marks) { + Pattern memberPattern = Pattern.compile("(\\w+)(?:\\.(\\w+))+"); + Matcher m = memberPattern.matcher(text); + + while (m.find()) { + if (isExcluded(m.start())) continue; + + String fullAccess = m.group(0); + String[] parts = fullAccess.split("\\."); + + if (parts.length < 2) continue; + + // Get receiver type + String receiverName = parts[0]; + String currentType = jsVariableTypes.get(receiverName); + + int pos = m.start(); + + // Mark receiver with unified FieldInfo + if (currentType != null) { + TypeInfo unifiedType = typeResolver.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 + + // Walk the chain and mark each member + JSTypeRegistry registry = typeResolver.getJSTypeRegistry(); + for (int i = 1; i < parts.length; i++) { + String member = parts[i]; + int memberStart = pos; + int memberEnd = pos + member.length(); + + if (currentType != null) { + JSTypeInfo jsTypeInfo = registry.getType(currentType); + if (jsTypeInfo != null) { + TypeInfo unifiedContainingType = TypeInfo.fromJSTypeInfo(jsTypeInfo); + + // Check method first + JSMethodInfo jsMethod = jsTypeInfo.getMethod(member); + if (jsMethod != null) { + MethodInfo unifiedMethod = MethodInfo.fromJSMethod(jsMethod, unifiedContainingType); + marks.add(new ScriptLine.Mark(memberStart, memberEnd, TokenType.METHOD_CALL, unifiedMethod)); + currentType = jsMethod.getReturnType(); + } else { + // Check field + JSFieldInfo jsField = jsTypeInfo.getField(member); + if (jsField != null) { + 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 " + jsTypeInfo.getFullName())); + currentType = null; + } + } + } else { + currentType = null; + } + } + + pos = memberEnd + 1; // +1 for the next dot + } + } + } + + /** + * Mark JavaScript method calls. + */ + private void markJSMethodCalls(List marks) { + Pattern callPattern = Pattern.compile("(\\w+)\\s*\\("); + Matcher m = callPattern.matcher(text); + + while (m.find()) { + if (isExcluded(m.start())) continue; + + String callExpr = m.group(1); + int nameStart = m.start(1); + int nameEnd = m.end(1); + + // Skip if it's preceded by a dot (handled by markJSMemberAccesses) + if (nameStart > 0 && text.charAt(nameStart - 1) == '.') continue; + + // Skip keywords that look like function calls + if (callExpr.equals("if") || callExpr.equals("while") || callExpr.equals("for") || + callExpr.equals("switch") || callExpr.equals("catch") || callExpr.equals("function")) { + continue; + } + + if (typeResolver.isJSHook(callExpr)) { + List sigs = typeResolver.getJSHookSignatures(callExpr); + if (!sigs.isEmpty()) { + JSTypeRegistry.HookSignature sig = sigs.get(0); + TypeInfo paramTypeInfo = typeResolver.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)); + } + } + } + } + + /** + * Mark standalone JavaScript identifiers (parameters and variables in function bodies). + */ + private void markJSIdentifiers(List marks) { + // Build set of positions already marked + Set markedPositions = new HashSet<>(); + for (ScriptLine.Mark mark : marks) { + for (int i = mark.start; i < mark.end; i++) { + markedPositions.add(i); + } + } + + // Find all identifiers + Pattern identPattern = Pattern.compile("\\b([a-zA-Z_][a-zA-Z0-9_]*)\\b"); + Matcher matcher = identPattern.matcher(text); + + while (matcher.find()) { + if (isExcluded(matcher.start())) continue; + + int start = matcher.start(); + int end = matcher.end(); + + // Skip if already marked + boolean alreadyMarked = false; + for (int i = start; i < end; i++) { + if (markedPositions.contains(i)) { + alreadyMarked = true; + break; + } + } + if (alreadyMarked) continue; + + String identifier = matcher.group(); + + // Check if it's a parameter + boolean isParameter = false; + for (Map params : jsFunctionParams.values()) { + if (params.containsKey(identifier)) { + isParameter = true; + break; + } + } + + if (isParameter) { + marks.add(new ScriptLine.Mark(start, end, TokenType.PARAMETER)); + for (int i = start; i < end; i++) { + markedPositions.add(i); + } + } else if (jsVariableTypes.containsKey(identifier)) { + marks.add(new ScriptLine.Mark(start, end, TokenType.LOCAL_FIELD)); + for (int i = start; i < end; i++) { + markedPositions.add(i); + } + } + } + } + + /** + * Build marks specific to Java/Groovy. + */ + private void buildJavaMarks(List marks) { // Import statements markImports(marks); @@ -1527,8 +2102,7 @@ public class ScriptDocument { // Enum constants (must be after class declarations so enums are known) markEnumConstants(marks); - // Keywords and modifiers - addPatternMarks(marks, KEYWORD_PATTERN, TokenType.KEYWORD); + // Modifiers addPatternMarks(marks, MODIFIER_PATTERN, TokenType.MODIFIER); // Type declarations and usages @@ -1537,9 +2111,6 @@ public class ScriptDocument { // Methods markMethodDeclarations(marks); - // Numbers and othe - addPatternMarks(marks, NUMBER_PATTERN, TokenType.LITERAL); - // Method calls (parse before variables so we can attach context) markMethodCalls(marks); @@ -1560,8 +2131,6 @@ public class ScriptDocument { // Final pass: Mark any remaining unmarked identifiers as undefined markUndefinedIdentifiers(marks); - - return marks; } /** @@ -4745,6 +5314,46 @@ public class ScriptDocument { } FieldInfo resolveVariable(String name, int position) { + // For JavaScript, use the JS variable tracking + if (isJavaScript()) { + return resolveJSVariable(name, position); + } + + return resolveJavaVariable(name, position); + } + + /** + * Resolve a JavaScript variable by name. + */ + private FieldInfo resolveJSVariable(String name, int position) { + // Check if it's a parameter + for (Map.Entry> entry : jsFunctionParams.entrySet()) { + if (entry.getValue().containsKey(name)) { + String typeName = entry.getValue().get(name); + TypeInfo typeInfo = typeResolver.resolveJSType(typeName); + return FieldInfo.parameter(name, typeInfo, position, null); + } + } + + // Check if it's a tracked variable + if (jsVariableTypes.containsKey(name)) { + String typeName = jsVariableTypes.get(name); + TypeInfo typeInfo = typeResolver.resolveJSType(typeName); + return FieldInfo.localField(name, typeInfo, position, null); + } + + // Check global fields (variables declared with var/let/const) + if (globalFields.containsKey(name)) { + return globalFields.get(name); + } + + return null; + } + + /** + * Resolve a Java variable by name. + */ + private FieldInfo resolveJavaVariable(String name, int position) { // Find containing method MethodInfo containingMethod = findMethodAtPosition(position); 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 0f117c4d4..2a730efda 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 @@ -14,7 +14,18 @@ import java.util.regex.*; /** * Analyzes JavaScript scripts to produce syntax highlighting marks and type information. * Uses the JSTypeRegistry to resolve types and validate member access. + * + * @deprecated This class is deprecated. All JavaScript analysis is now handled by the + * unified pipeline in {@link ScriptDocument}. The functionality has been merged into: + *
    + *
  • {@link ScriptDocument#parseJSFunctions()} - for function parsing
  • + *
  • {@link ScriptDocument#parseJSVariables()} - for variable parsing
  • + *
  • {@link ScriptDocument#buildJSMarks(List)} - for mark building
  • + *
+ * Use {@link ScriptDocument#getJSVariableTypes()} and {@link ScriptDocument#getJSFunctionParams()} + * to access inferred type information. */ +@Deprecated public class JSScriptAnalyzer { private final ScriptDocument document; @@ -88,6 +99,9 @@ public class JSScriptAnalyzer { // Mark method calls markMethodCalls(marks); + // Mark standalone identifiers (parameters and variables in function bodies) + markIdentifiers(marks); + return marks; } @@ -472,4 +486,67 @@ public class JSScriptAnalyzer { // Fallback: unresolved type return TypeInfo.unresolved(jsTypeName, jsTypeName); } + + /** + * Mark all identifiers in the script - variables and parameters. + * This ensures consistent coloring for parameters throughout function bodies. + */ + private void markIdentifiers(List marks) { + // Build set of positions already marked + Set markedPositions = new HashSet<>(); + for (ScriptLine.Mark mark : marks) { + for (int i = mark.start; i < mark.end; i++) { + markedPositions.add(i); + } + } + + // Find all identifiers + Matcher matcher = IDENTIFIER_PATTERN.matcher(text); + while (matcher.find()) { + if (isExcluded(matcher.start())) { + continue; + } + + int start = matcher.start(); + int end = matcher.end(); + + // Skip if already marked + boolean alreadyMarked = false; + for (int i = start; i < end; i++) { + if (markedPositions.contains(i)) { + alreadyMarked = true; + break; + } + } + if (alreadyMarked) { + continue; + } + + String identifier = matcher.group(); + + // Check if it's a parameter + boolean isParameter = false; + for (Map params : functionParams.values()) { + if (params.containsKey(identifier)) { + isParameter = true; + break; + } + } + + if (isParameter) { + marks.add(new ScriptLine.Mark(start, end, TokenType.PARAMETER)); + // Mark this position as used + for (int i = start; i < end; i++) { + markedPositions.add(i); + } + } else if (variableTypes.containsKey(identifier)) { + // It's a local variable + marks.add(new ScriptLine.Mark(start, end, TokenType.LOCAL_FIELD)); + // Mark this position as used + for (int i = start; i < end; i++) { + markedPositions.add(i); + } + } + } + } } 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 741543fa2..a724b5d2e 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 @@ -324,15 +324,25 @@ public class JSTypeRegistry { * Resolve inheritance relationships between types. */ private void resolveInheritance() { + int totalInheritance = 0; + int resolvedInheritance = 0; + for (JSTypeInfo type : types.values()) { String extendsType = type.getExtendsType(); if (extendsType != null) { + totalInheritance++; JSTypeInfo parent = getType(extendsType); if (parent != null) { type.setResolvedParent(parent); + resolvedInheritance++; + System.out.println("[JSTypeRegistry] Resolved " + type.getSimpleName() + " extends " + parent.getSimpleName()); + } else { + System.err.println("[JSTypeRegistry] FAILED to resolve parent type '" + extendsType + "' for " + type.getFullName()); } } } + + System.out.println("[JSTypeRegistry] Inheritance resolution: " + resolvedInheritance + "/" + totalInheritance + " successful"); } /** 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 1be0617d7..297256f97 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 @@ -1,15 +1,22 @@ package noppes.npcs.client.gui.util.script.interpreter.type; import noppes.npcs.client.gui.util.script.PackageFinder; +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.token.TokenType; import java.util.*; /** - * Resolves class/interface/enum types from import paths and simple names. - * Maintains caches for performance and handles inner class resolution. + * Unified type resolver for both Java and JavaScript/TypeScript types. + * Serves as the single front-end for all type resolution needs. * - * This is a clean reimplementation of ClassPathFinder with better OOP structure. + * For Java: Uses reflection and import resolution + * For JavaScript: Delegates to JSTypeRegistry for .d.ts defined types + * + * This is the ONLY class that should be used for type resolution. */ public class TypeResolver { @@ -18,6 +25,9 @@ public class TypeResolver { // Cache: validated package paths private final Set validPackages = new HashSet<>(); + + // JavaScript type registry (lazily initialized) + private JSTypeRegistry jsTypeRegistry; // Auto-imported java.lang classes public static final Set JAVA_LANG_CLASSES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( @@ -30,6 +40,19 @@ public class TypeResolver { "IndexOutOfBoundsException", "NullPointerException", "NumberFormatException", "UnsupportedOperationException", "AssertionError", "OutOfMemoryError", "StackOverflowError" ))); + + // JavaScript primitive type mappings + private static final Map JS_PRIMITIVE_TO_JAVA = new HashMap<>(); + static { + JS_PRIMITIVE_TO_JAVA.put("string", "String"); + JS_PRIMITIVE_TO_JAVA.put("number", "double"); + JS_PRIMITIVE_TO_JAVA.put("boolean", "boolean"); + JS_PRIMITIVE_TO_JAVA.put("any", "Object"); + JS_PRIMITIVE_TO_JAVA.put("void", "void"); + JS_PRIMITIVE_TO_JAVA.put("null", "null"); + JS_PRIMITIVE_TO_JAVA.put("undefined", "void"); + JS_PRIMITIVE_TO_JAVA.put("object", "Object"); + } // Singleton instance for global caching (optional - can also use per-document instances) private static TypeResolver instance; @@ -48,6 +71,42 @@ public class TypeResolver { validPackages.add("java.util"); validPackages.add("java.io"); } + + // ==================== JS TYPE REGISTRY ACCESS ==================== + + /** + * Get the JS type registry, initializing it if needed. + */ + public JSTypeRegistry getJSTypeRegistry() { + if (jsTypeRegistry == null) { + jsTypeRegistry = JSTypeRegistry.getInstance(); + if (!jsTypeRegistry.isInitialized()) { + jsTypeRegistry.initializeFromResources(); + } + } + return jsTypeRegistry; + } + + /** + * Check if a function name is a known JS hook. + */ + public boolean isJSHook(String functionName) { + return getJSTypeRegistry().isHook(functionName); + } + + /** + * Get the parameter type for a JS hook function. + */ + public String getJSHookParameterType(String functionName) { + return getJSTypeRegistry().getHookParameterType(functionName); + } + + /** + * Get hook signatures for a JS function. + */ + public List getJSHookSignatures(String functionName) { + return getJSTypeRegistry().getHookSignatures(functionName); + } // ==================== CACHE MANAGEMENT ==================== @@ -63,6 +122,57 @@ public class TypeResolver { validPackages.add("java.util"); validPackages.add("java.io"); } + + // ==================== UNIFIED TYPE RESOLUTION ==================== + + /** + * Resolve a type name for JavaScript context. + * Handles JS primitives, .d.ts types, and falls back to Java types. + * + * @param jsTypeName The JS type name (e.g., "IPlayer", "string", "number") + * @return TypeInfo for the resolved type, or unresolved TypeInfo if not found + */ + public TypeInfo resolveJSType(String jsTypeName) { + if (jsTypeName == null || jsTypeName.isEmpty()) { + return TypeInfo.fromPrimitive("void"); + } + + // Strip array brackets for base type resolution + boolean isArray = jsTypeName.endsWith("[]"); + String baseName = isArray ? jsTypeName.substring(0, jsTypeName.length() - 2) : jsTypeName; + + // Check JS primitives first + if (JS_PRIMITIVE_TO_JAVA.containsKey(baseName)) { + String javaType = JS_PRIMITIVE_TO_JAVA.get(baseName); + TypeInfo baseType = TypeResolver.isPrimitiveType(javaType) + ? TypeInfo.fromPrimitive(javaType) + : resolveFullName("java.lang." + javaType); + return isArray ? TypeInfo.arrayOf(baseType) : baseType; + } + + // Check JS type registry + JSTypeInfo jsTypeInfo = getJSTypeRegistry().getType(baseName); + if (jsTypeInfo != null) { + TypeInfo baseType = TypeInfo.fromJSTypeInfo(jsTypeInfo); + return isArray ? TypeInfo.arrayOf(baseType) : baseType; + } + + // Fall back to Java type resolution + TypeInfo javaType = resolveFullName(baseName); + if (javaType != null && javaType.isResolved()) { + return isArray ? TypeInfo.arrayOf(javaType) : javaType; + } + + // Unresolved + return TypeInfo.unresolved(jsTypeName, jsTypeName); + } + + /** + * Check if a JS type name is a primitive. + */ + public boolean isJSPrimitive(String typeName) { + return JS_PRIMITIVE_TO_JAVA.containsKey(typeName); + } // ==================== TYPE RESOLUTION ====================