temp JS/java merger

This commit is contained in:
bigguy345 committed 2026-01-04 21:41:39 +02:00
1 parent b3d49945f7
commit f9babf040d
4 files changed
+872 -66

No files matched your search

@@ -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<String, Map<String, String>> jsFunctionParams = new HashMap<>();
// JS-specific: Inferred variable types (varName -> typeName)
private final Map<String, String> 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<ScriptLine.Mark> marks;
// Use different analysis paths for JavaScript vs Java
if (isJavaScript()) {
marks = formatJavaScript();
} else {
marks = formatJava();
}
// Unified pipeline for both languages
List<ScriptLine.Mark> 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<ScriptLine.Mark> 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<ScriptLine.Mark> formatJava() {
// Phase 1: Find excluded regions (strings/comments)
public Map<String, String> getJSVariableTypes() {
return new HashMap<>(jsVariableTypes);
}
/**
* Get JS function parameter types.
*/
public Map<String, Map<String, String>> 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<ScriptLine.Mark> 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<ScriptLine.Mark> formatJavaScript() {
// Delegate to unified pipeline
currentJSAnalyzer = new JSScriptAnalyzer(this);
return currentJSAnalyzer.analyze();
}
/**
* @deprecated Use formatUnified() instead. Kept for compatibility.
*/
@Deprecated
private List<ScriptLine.Mark> 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<FieldInfo> params = new ArrayList<>();
TypeInfo returnType = TypeInfo.fromPrimitive("void");
String documentation = null;
if (typeResolver.isJSHook(funcName)) {
List<JSTypeRegistry.HookSignature> 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<String, String> 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<String, String> 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<FieldInfo> 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<String, FieldInfo> 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<ScriptLine.Mark> buildMarks() {
List<ScriptLine.Mark> 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<ScriptLine.Mark> 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<ScriptLine.Mark> 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<JSTypeRegistry.HookSignature> 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<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));
// 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<ScriptLine.Mark> 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<ScriptLine.Mark> 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<ScriptLine.Mark> 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<JSTypeRegistry.HookSignature> sigs = typeResolver.getJSHookSignatures(callExpr);
if (!sigs.isEmpty()) {
JSTypeRegistry.HookSignature sig = sigs.get(0);
TypeInfo paramTypeInfo = typeResolver.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));
}
}
}
}
/**
* Mark standalone JavaScript identifiers (parameters and variables in function bodies).
*/
private void markJSIdentifiers(List<ScriptLine.Mark> marks) {
// Build set of positions already marked
Set<Integer> 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<String, String> 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<ScriptLine.Mark> 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<String, Map<String, String>> 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);
@@ -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:
* <ul>
* <li>{@link ScriptDocument#parseJSFunctions()} - for function parsing</li>
* <li>{@link ScriptDocument#parseJSVariables()} - for variable parsing</li>
* <li>{@link ScriptDocument#buildJSMarks(List)} - for mark building</li>
* </ul>
* 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<ScriptLine.Mark> marks) {
// Build set of positions already marked
Set<Integer> 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<String, String> 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);
}
}
}
}
}
@@ -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");
}
/**
@@ -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<String> validPackages = new HashSet<>();
// JavaScript type registry (lazily initialized)
private JSTypeRegistry jsTypeRegistry;
// Auto-imported java.lang classes
public static final Set<String> 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<String, String> 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<JSTypeRegistry.HookSignature> 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 ====================