diff --git a/dts-patches/ecmascript/Object.d.ts b/dts-patches/ecmascript/Object.d.ts index 0fa2e0b06..21fe3f824 100644 --- a/dts-patches/ecmascript/Object.d.ts +++ b/dts-patches/ecmascript/Object.d.ts @@ -102,9 +102,4 @@ export interface Object { /** Returns a localized string representation of the object (format depends on locale) */ toLocaleString(): string; - /** Returns a string representation of the object, usually "[object Object]" unless overridden */ - toString(): string; - - /** Returns the primitive value of the object (usually the object itself unless overridden) */ - valueOf(): any; } diff --git a/dts-patches/ecmascript/String.d.ts b/dts-patches/ecmascript/String.d.ts index 2d15c0b9b..8a48033e2 100644 --- a/dts-patches/ecmascript/String.d.ts +++ b/dts-patches/ecmascript/String.d.ts @@ -58,66 +58,25 @@ * ``` */ export interface String { - /** Creates a string from one or more Unicode character codes (e.g., String.fromCharCode(65, 66, 67) returns "ABC") */ - fromCharCode(...codes: number[]): string; - /** The number of characters in the string */ length: number; - /** Returns the character at the specified index, or an empty string if the index is out of range */ - charAt(pos: number): string; + /** Creates a string from one or more Unicode character codes (e.g., String.fromCharCode(65, 66, 67) returns "ABC") */ + static fromCharCode(...codes: number[]): string; - /** Returns the Unicode code point (0-65535) of the character at the specified index */ + /** Returns the Unicode code point (0–65535) of the character at the specified index. Unlike Java's charAt() + cast, this returns the numeric code directly. */ charCodeAt(pos: number): number; - /** Combines this string with other strings and returns a new combined string */ - concat(...strings: string[]): string; - - /** Returns the index of the first occurrence of the search string, or -1 if not found */ - indexOf(searchString: string, position: number): number; - - /** Returns the index of the last occurrence of the search string, or -1 if not found */ - lastIndexOf(searchString: string, position: number): number; - - /** Compares this string to another string and returns a number indicating sort order (-1, 0, or 1) */ - localeCompare(that: string): number; - - /** Tests the string against a regular expression and returns an array of matches or null if no match */ + /** Tests the string against a regular expression and returns an array of matches, or null if no match found */ match(regexp: any): any[]; - /** Replaces the first occurrence of a pattern (string or regex) with a replacement string or the result of a function */ + /** + * Replaces a pattern (string or RegExp object) with a replacement string. + * Unlike Java's replace/replaceAll which only accept String/CharSequence, + * this accepts a RegExp object as the first argument. + */ replace(searchValue: any, replaceValue: any): string; /** Returns the index of the first character that matches a regular expression, or -1 if not found */ search(regexp: any): number; - - /** Returns a new string containing the characters from the start index up to (but not including) the end index */ - slice(start: number, end: number): string; - - /** Splits the string into an array of substrings using the separator, optionally limiting the number of splits */ - split(separator: any, limit: number): string[]; - - /** Returns a new string containing characters from the start index up to (but not including) the end index */ - substring(start: number, end: number): string; - - /** Returns a new string with all uppercase characters converted to lowercase */ - toLowerCase(): string; - - /** Returns a new string with all uppercase characters converted to lowercase according to locale rules */ - toLocaleLowerCase(): string; - - /** Returns a new string with all lowercase characters converted to uppercase */ - toUpperCase(): string; - - /** Returns a new string with all lowercase characters converted to uppercase according to locale rules */ - toLocaleUpperCase(): string; - - /** Returns a new string with whitespace (spaces, tabs, newlines, etc.) removed from both ends */ - trim(): string; - - /** Returns the string itself */ - toString(): string; - - /** Returns the primitive value of the string object */ - valueOf(): string; } 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 b74652a0f..907b740d4 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 @@ -4887,7 +4887,7 @@ public class ScriptDocument { // String literals if (expr.startsWith("\"") && expr.endsWith("\"")) { - return resolveType("String"); + return TypeInfo.string(); } // Character literals @@ -5728,7 +5728,7 @@ public class ScriptDocument { private TypeInfo resolveSimpleExpression(String expr, int position) { // String literals if (expr.startsWith("\"") && expr.endsWith("\"")) { - return resolveType("String"); + return TypeInfo.string(); } // Character literals diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/expression/ExpressionNode.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/expression/ExpressionNode.java index f3d588f4d..2d03773a8 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/expression/ExpressionNode.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/expression/ExpressionNode.java @@ -54,7 +54,7 @@ public abstract class ExpressionNode { private final String value; public StringLiteralNode(String value, int start, int end) { super(start, end); this.value = value; } public String getValue() { return value; } - public TypeInfo resolveType(TypeResolverContext resolver) { return TypeInfo.fromClass(String.class); } + public TypeInfo resolveType(TypeResolverContext resolver) { return TypeInfo.string(); } } public static class NullLiteralNode extends ExpressionNode { diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/expression/ExpressionTypeResolver.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/expression/ExpressionTypeResolver.java index eee81cd2c..35db70ea7 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/expression/ExpressionTypeResolver.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/expression/ExpressionTypeResolver.java @@ -88,7 +88,7 @@ public class ExpressionTypeResolver { if (node instanceof ExpressionNode.DoubleLiteralNode) return TypeInfo.fromPrimitive("double"); if (node instanceof ExpressionNode.BooleanLiteralNode) return TypeInfo.fromPrimitive("boolean"); if (node instanceof ExpressionNode.CharLiteralNode) return TypeInfo.fromPrimitive("char"); - if (node instanceof ExpressionNode.StringLiteralNode) return TypeInfo.fromClass(String.class); + if (node instanceof ExpressionNode.StringLiteralNode) return TypeInfo.string(); if (node instanceof ExpressionNode.NullLiteralNode) return TypeInfo.unresolved("null", ""); if (node instanceof ExpressionNode.IdentifierNode) { diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/expression/TypeRules.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/expression/TypeRules.java index bbbd8a86b..d20f8709a 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/expression/TypeRules.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/expression/TypeRules.java @@ -94,7 +94,7 @@ public class TypeRules { switch (op.getCategory()) { case ARITHMETIC: if (op == OperatorType.ADD && (isString(left) || isString(right))) { - return validateAgainstExpectedType(TypeInfo.fromClass(String.class)); + return validateAgainstExpectedType(TypeInfo.string()); } if (isAny(left) || isAny(right)) { return validateAgainstExpectedType(TypeInfo.ANY); diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeChecker.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeChecker.java index ce0d24922..8d6c91a2b 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeChecker.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeChecker.java @@ -19,7 +19,7 @@ public final class TypeChecker { CURRENT_JAVASCRIPT_MODE = javaScriptMode; } - private static boolean isJavaScriptMode() { + static boolean isJavaScriptMode() { return CURRENT_JAVASCRIPT_MODE; } diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java index 790b1f35b..f290473b7 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java @@ -43,6 +43,18 @@ public class TypeInfo { public static final TypeInfo BOOLEAN = fromPrimitive("boolean"); public static final TypeInfo STRING = TypeInfo.fromClass(String.class); + static TypeInfo jsString = null; + + public static final TypeInfo OBJECT = TypeInfo.fromClass(Object.class); + static TypeInfo jsObject = null; + + public static TypeInfo string() { + return TypeChecker.isJavaScriptMode() && jsString != null ? jsString : STRING; + } + + public static TypeInfo object() { + return TypeChecker.isJavaScriptMode() && jsObject != null ? jsObject : OBJECT; + } public static final TypeInfo NULL = TypeInfo.unresolved("null", ""); @@ -317,6 +329,13 @@ public class TypeInfo { Kind.INTERFACE, null, true, null, jsType); } + public static void injectJSMembers(TypeInfo target, JSTypeInfo jsExtensions) { + for (JSFieldInfo jsField : jsExtensions.getFields().values()) + target.syntheticFields.add(FieldInfo.fromJSField(jsField, target)); + for (JSMethodInfo jsMethod : jsExtensions.getMethods().values()) + target.syntheticMethods.add(MethodInfo.fromJSMethod(jsMethod, target)); + } + /** * Create an array type wrapping the given element type. * 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 dcab4c4a6..4c0e845e9 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 @@ -24,6 +24,9 @@ public class TypeResolver { // Cache: fully-qualified class name -> TypeInfo private final Map typeCache = new HashMap<>(); + // Cache: Java types that have been enriched with JS members from .d.ts types (e.g., String, Object) + private final Map jsMergeCache = new HashMap<>(); + // Cache: validated package paths private final Set validPackages = new HashSet<>(); @@ -418,6 +421,8 @@ public class TypeResolver { // Before primitive mapping, to allow .d.ts types to override primitive names for ECMAScript 5.1 globals (e.g., "String" in JS is not the same as "String" in Java)) JSTypeInfo jsTypeInfo = getJSTypeRegistry().getType(baseName); if (jsTypeInfo != null) { + TypeInfo merged = tryMergeJSWithJava(baseName, jsTypeInfo); + if (merged != null) return merged; return TypeInfo.fromJSTypeInfo(jsTypeInfo); } @@ -449,7 +454,38 @@ public class TypeResolver { // Unresolved return TypeInfo.unresolved(baseName, baseName); } - + + private static TypeInfo getJSJavaMergeBase(String baseName) { + switch (baseName) { + case "String": + return TypeInfo.STRING; + case "Object": + return TypeInfo.OBJECT; + default: + return null; + } + } + + /** + * For certain core types (e.g., String, Object), we want to merge JS type info from .d.ts with the Java TypeInfo to create an enriched type that has both Java and JS members. + */ + private TypeInfo tryMergeJSWithJava(String baseName, JSTypeInfo jsTypeInfo) { + TypeInfo javaBase = getJSJavaMergeBase(baseName); + if (javaBase == null) return null; + + return jsMergeCache.computeIfAbsent(baseName, k -> { + TypeInfo result = TypeInfo.fromClass(javaBase.getJavaClass()); + if (result == null) result = javaBase; + TypeInfo.injectJSMembers(result, jsTypeInfo); + if (javaBase == TypeInfo.STRING) { + TypeInfo.jsString = result; + } else if (javaBase == TypeInfo.OBJECT) { + TypeInfo.jsObject = result; + } + return result; + }); + } + /** * Check if a JS type name is a primitive. */