From e2a5d254a9cc38f7279a7efce1df90eb66103ace Mon Sep 17 00:00:00 2001 From: bigguy345 Date: Sun, 28 Dec 2025 19:41:28 +0200 Subject: [PATCH] Added error "method signature already defined in this scope" --- .../util/script/interpreter/MethodInfo.java | 27 ++++++ .../script/interpreter/MethodSignature.java | 79 +++++++++++++++ .../script/interpreter/ScriptDocument.java | 95 ++++++++++++++++++- .../util/script/interpreter/ScriptLine.java | 41 ++++++++ .../interpreter/hover/TokenHoverInfo.java | 10 ++ 5 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 src/main/java/noppes/npcs/client/gui/util/script/interpreter/MethodSignature.java diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/MethodInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/MethodInfo.java index 484155472..5aa7da676 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/MethodInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/MethodInfo.java @@ -30,6 +30,7 @@ public final class MethodInfo { MISSING_RETURN, // Non-void method missing return statement RETURN_TYPE_MISMATCH, // Return statement type doesn't match method return type VOID_METHOD_RETURNS_VALUE, // Void method returns a value + DUPLICATE_METHOD, // Method with same signature already defined in scope DUPLICATE_PARAMETER, // Two parameters have the same name PARAMETER_UNDEFINED // Parameter type cannot be resolved } @@ -191,6 +192,16 @@ public final class MethodInfo { return position >= bodyStart && position < bodyEnd; } + /** + * Get the end of the method declaration (closing paren position). + * This is used for error highlighting of duplicate methods. + */ + public int getDeclarationEnd() { + // The declaration ends just before the opening brace + // We find the closing paren by searching backwards from bodyStart + return bodyStart > 0 ? bodyStart - 1 : nameOffset + name.length(); + } + /** * Check if this method has a parameter with the given name. */ @@ -215,6 +226,22 @@ public final class MethodInfo { return null; } + /** + * Get method signature for duplicate detection. + * Signature = name + parameter types (ignoring parameter names). + */ + public MethodSignature cachedSignature; + + public MethodSignature getSignature() { + if (cachedSignature == null) { + List paramTypes = new ArrayList<>(); + for (FieldInfo param : parameters) + paramTypes.add(param.getDeclaredType()); + cachedSignature = new MethodSignature(name, paramTypes); + } + return cachedSignature; + } + /** * Get the appropriate TokenType for highlighting this method. */ diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/MethodSignature.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/MethodSignature.java new file mode 100644 index 000000000..ed63d2925 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/MethodSignature.java @@ -0,0 +1,79 @@ +package noppes.npcs.client.gui.util.script.interpreter; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a method signature for robust comparison. + * Compares method name and parameter types as structured data, not as strings. + */ +public final class MethodSignature { + private final String methodName; + private final List parameterTypes; + + public MethodSignature(String methodName, List parameterTypes) { + this.methodName = methodName; + this.parameterTypes = new ArrayList<>(parameterTypes); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof MethodSignature)) + return false; + + MethodSignature other = (MethodSignature) obj; + + // Compare method names + if (!this.methodName.equals(other.methodName)) + return false; + + // Compare parameter count + if (this.parameterTypes.size() != other.parameterTypes.size()) + return false; + + // Compare each parameter type + for (int i = 0; i < this.parameterTypes.size(); i++) { + TypeInfo thisType = this.parameterTypes.get(i); + TypeInfo otherType = other.parameterTypes.get(i); + + // Both null = equal + if (thisType == null && otherType == null) + continue; + + // One null, one not = not equal + if (thisType == null || otherType == null) + return false; + + // Compare full type names + if (!thisType.getFullName().equals(otherType.getFullName())) + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = methodName.hashCode(); + for (TypeInfo paramType : parameterTypes) { + result = 31 * result + (paramType != null ? paramType.getFullName().hashCode() : 0); + } + return result; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(methodName); + sb.append("("); + for (int i = 0; i < parameterTypes.size(); i++) { + if (i > 0) + sb.append(", "); + TypeInfo type = parameterTypes.get(i); + sb.append(type != null ? type.getFullName() : "?"); + } + sb.append(")"); + return sb.toString(); + } +} 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 73874410c..89de53502 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 @@ -1,6 +1,5 @@ package noppes.npcs.client.gui.util.script.interpreter; -import net.minecraft.client.Minecraft; import noppes.npcs.client.ClientProxy; import java.util.*; @@ -604,6 +603,9 @@ public class ScriptDocument { methods.add(methodInfo); } + // Check for duplicate method declarations + checkDuplicateMethods(); + // Also parse JS-style functions Matcher funcM = FUNC_PARAMS_PATTERN.matcher(text); while (funcM.find()) { @@ -666,6 +668,97 @@ public class ScriptDocument { return earliestPos; } + /** + * Check for duplicate method declarations with same signature in same scope. + */ + private void checkDuplicateMethods() { + // Group methods by scope depth + Map> methodsByScope = new HashMap<>(); + + for (MethodInfo method : methods) { + int scopeDepth = calculateScopeDepth(method.getFullDeclarationOffset()); + methodsByScope.computeIfAbsent(scopeDepth, k -> new ArrayList<>()).add(method); + } + + // Check each scope for duplicates + for (List scopeMethods : methodsByScope.values()) { + Map> signatureMap = new HashMap<>(); + + for (MethodInfo method : scopeMethods) { + MethodSignature signature = method.getSignature(); + signatureMap.computeIfAbsent(signature, k -> new ArrayList<>()).add(method); + } + + // Mark all methods with duplicate signatures + for (Map.Entry> entry : signatureMap.entrySet()) { + List duplicates = entry.getValue(); + if (duplicates.size() > 1) { + for (MethodInfo duplicate : duplicates) { + duplicate.setError( + MethodInfo.ErrorType.DUPLICATE_METHOD, + duplicate.getSignature() + " is already defined in this scope" + ); + } + } + } + } + } + + /** + * Calculate the scope depth of a position (0 = top level, 1 = in class, etc.). + */ + private int calculateScopeDepth(int position) { + int depth = 0; + int braceDepth = 0; + boolean inString = false; + boolean inComment = false; + boolean inLineComment = false; + + for (int i = 0; i < position && i < text.length(); i++) { + char c = text.charAt(i); + char next = (i + 1 < text.length()) ? text.charAt(i + 1) : 0; + + // Handle strings + if (c == '"' && !inComment && !inLineComment) { + inString = !inString; + continue; + } + if (inString) continue; + + // Handle comments + if (!inComment && !inLineComment && c == '/' && next == '/') { + inLineComment = true; + i++; + continue; + } + if (!inComment && !inLineComment && c == '/' && next == '*') { + inComment = true; + i++; + continue; + } + if (inComment && c == '*' && next == '/') { + inComment = false; + i++; + continue; + } + if (inLineComment && c == '\n') { + inLineComment = false; + continue; + } + if (inComment || inLineComment) continue; + + // Track braces + if (c == '{') { + braceDepth++; + depth = Math.max(depth, braceDepth); + } else if (c == '}') { + braceDepth--; + } + } + + return depth; + } + private List parseParametersWithPositions(String paramList, int paramListStart) { List params = new ArrayList<>(); if (paramList == null || paramList.trim().isEmpty()) { diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptLine.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptLine.java index 455465c2c..69dcc33e1 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptLine.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptLine.java @@ -522,6 +522,47 @@ public class ScriptLine { } } + // Handle duplicate method error (underline from full declaration start to closing paren) + if (method.getErrorType() == MethodInfo.ErrorType.DUPLICATE_METHOD) { + int declStart = method.getFullDeclarationOffset(); + int declEnd = method.getDeclarationEnd(); + + // Skip if declaration doesn't intersect this line + if (declEnd < lineStart || declStart > lineEnd) + continue; + + // Clip to line boundaries + int clipStart = Math.max(declStart, lineStart); + int clipEnd = Math.min(declEnd, lineEnd); + + if (clipStart >= clipEnd) + continue; + + // Convert to line-local coordinates + int lineLocalStart = clipStart - lineStart; + int lineLocalEnd = clipEnd - lineStart; + + // Bounds check + if (lineLocalStart < 0 || lineLocalStart >= lineText.length()) + continue; + + // Compute pixel position + String beforeDecl = lineText.substring(0, lineLocalStart); + int beforeWidth = ClientProxy.Font.width(beforeDecl); + + int declWidth; + if (lineLocalEnd > lineText.length()) { + declWidth = ClientProxy.Font.width(lineText.substring(lineLocalStart)); + } else { + String declTextOnLine = lineText.substring(lineLocalStart, lineLocalEnd); + declWidth = ClientProxy.Font.width(declTextOnLine); + } + + if (declWidth > 0) { + drawCurlyUnderline(lineStartX + beforeWidth, baselineY, declWidth, 0xFF5555); + } + } + // Handle parameter errors (underline specific parameters) if (method.hasParameterErrors()) { for (MethodInfo.ParameterError paramError : method.getParameterErrors()) { diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverInfo.java index 1559a3b04..9db05c80f 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverInfo.java @@ -221,6 +221,16 @@ public class TokenHoverInfo { } } + // If hovering over duplicate method declaration, show the error + if (methodDecl.getErrorType() == MethodInfo.ErrorType.DUPLICATE_METHOD) { + int declStart = methodDecl.getFullDeclarationOffset(); + int declEnd = methodDecl.getDeclarationEnd(); + + if (tokenStart >= declStart && tokenEnd <= declEnd) { + errors.add(methodDecl.getErrorMessage()); + } + } + // If hovering over a parameter with an error, show that error if (methodDecl.hasParameterErrors()) { for (MethodInfo.ParameterError paramError : methodDecl.getParameterErrors()) {