diff --git a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSNumber.java b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSNumber.java
index f4983570a4f1..1248f93235cb 100644
--- a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSNumber.java
+++ b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSNumber.java
@@ -119,4 +119,136 @@ public boolean equals(Object that) {
public int hashCode() {
return javaDouble().hashCode();
}
+
+ @JS.Coerce
+ @JS(value = "return isFinite(number);")
+ public native static boolean isFinite(JSNumber number);
+
+ @JS.Coerce
+ @JS(value = "return isFinite(number);")
+ public native static boolean isFinite(double number);
+
+ @JS.Coerce
+ @JS(value = "return Number.isInteger(number);")
+ public native static boolean isInteger(JSNumber number);
+
+ @JS.Coerce
+ @JS(value = "return Number.isInteger(number);")
+ public native static boolean isInteger(double number);
+
+ @JS.Coerce
+ @JS(value = "return isNaN(number);")
+ public native static boolean isNaN(JSValue number);
+
+ @JS.Coerce
+ @JS(value = "return isNaN(number);")
+ public native static boolean isNaN(double number);
+
+ @JS.Coerce
+ @JS(value = "return Number.isSafeInteger(number);")
+ public native static boolean isSafeInteger(JSValue number);
+
+ @JS.Coerce
+ @JS(value = "return Number.isSafeInteger(number);")
+ public native static boolean isSafeInteger(double number);
+
+ @JS.Coerce
+ @JS(value = "return parseFloat(number);")
+ public native static float parseFloat(double number);
+
+ @JS.Coerce
+ @JS(value = "return parseFloat(number);")
+ public native static float parseFloat(String number);
+
+ @JS.Coerce
+ @JS(value = "return parseInt(number);")
+ public native static int parseInt(double number);
+
+ @JS.Coerce
+ @JS(value = "return parseInt(number);")
+ public native static int parseInt(String number);
+
+ @JS.Coerce
+ @JS(value = "return parseInt(number, radix);")
+ public native static int parseInt(String number, int radix);
+
+ @JS.Coerce
+ @JS(value = "return Number.EPSILON;")
+ public native static double EPSILON();
+
+ @JS.Coerce
+ @JS(value = "return Number.MAX_SAFE_INTEGER;")
+ public native static double MAX_SAFE_INTEGER();
+
+ @JS.Coerce
+ @JS(value = "return Number.MAX_VALUE;")
+ public native static double MAX_VALUE();
+
+ @JS.Coerce
+ @JS(value = "return Number.MIN_SAFE_INTEGER;")
+ public native static double MIN_SAFE_INTEGER();
+
+ @JS.Coerce
+ @JS(value = "return Number.MIN_VALUE;")
+ public native static double MIN_VALUE();
+
+ @JS.Coerce
+ @JS(value = "return Number.NaN;")
+ public native static double NaN();
+
+ @JS.Coerce
+ @JS(value = "return Number.NEGATIVE_INFINITY;")
+ public native static double NEGATIVE_INFINITY();
+
+ @JS.Coerce
+ @JS(value = "return Number.POSITIVE_INFINITY;")
+ public native static double POSITIVE_INFINITY();
+
+ @JS.Coerce
+ @JS(value = "return this.toExponential();")
+ public native String toExponential();
+
+ @JS.Coerce
+ @JS(value = "return this.toExponential(fractionDigits);")
+ public native String toExponential(int fractionDigits);
+
+ @JS.Coerce
+ @JS(value = "return this.toFixed();")
+ public native String toFixed();
+
+ @JS.Coerce
+ @JS(value = "return this.toFixed(digits);")
+ public native String toFixed(int digits);
+
+ @JS.Coerce
+ @JS(value = "return this.toLocaleString();")
+ public native String toLocaleString();
+
+ @JS.Coerce
+ @JS(value = "return this.toLocaleString(locales);")
+ public native String toLocaleString(String locales);
+
+ @JS.Coerce
+ @JS(value = "return this.toLocaleString(locales, options);")
+ public native String toLocaleString(String locales, JSObject options);
+
+ @JS.Coerce
+ @JS(value = "return this.toPrecision();")
+ public native String toPrecision();
+
+ @JS.Coerce
+ @JS(value = "return this.toPrecision(precision);")
+ public native String toPrecision(int precision);
+
+ @JS.Coerce
+ @JS(value = "return this.toString(radix);")
+ private native String toJSString(int radix);
+
+ public String toString(int radix) {
+ return "JavaScript<" + typeof() + "; " + toJSString(radix) + ">";
+ }
+
+ @JS.Coerce
+ @JS(value = "return this.valueOf();")
+ public native double valueOf();
}
diff --git a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSObject.java b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSObject.java
index cbae872ab70e..ed64c5152d56 100644
--- a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSObject.java
+++ b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSObject.java
@@ -49,14 +49,14 @@
* wrapped into a JSObject
instance. The JSObject
allows the Java code to
* access the fields of the underlying JavaScript object using the get
and
* set
methods.
- *
+ *
* The Java {@link JSObject} instance that corresponds to the JavaScript object is called a Java * mirror. Vice versa, the JavaScript instance is a JavaScript mirror for that * {@link JSObject} instance. * * *
* Here are a few examples of creating and modifying anonymous JavaScript objects: * *
@@ -68,7 +68,7 @@ * System.out.println(pair.get("x")); * System.out.println(pair.get("y")); *- * + *
* In this example, using the {@code JSObject} methods, the user can access the x
and
* y
fields.
*
@@ -76,32 +76,32 @@
* pair.set("x", 5.4);
* System.out.println(pair.get("x"));
*
- *
+ *
* The code above sets a new value for the x
field, and then prints the new value.
*
*
*
* A {@code JSObject} can be a Java-side wrapper for a JavaScript {@code Function} object. The * JavaScript {@code Function} value can be returned by the JavaScript code of the method annotated * with the {@link JS} annotation. - * + *
* The Java program can then call the underlying function by calling the * {@link JSObject#call(Object, Object...)} method. If the underlying JavaScript object is not * callable, then calling {@code call} will result in an exception. - * + *
* The {@code call} method has the following signature: * *
* public Object call(Object thisArgument, Object... arguments); *- * + *
* The {@code invoke} method has the following signature: * *
* public Object invoke(Object... arguments); *- * + *
* The difference is that the method {@code call} takes an object for specifying {@code this} of the * JavaScript function, while {@code invoke} uses the underlying {@code JSObject} as the value of * {@code this}. @@ -130,13 +130,13 @@ * * *
* The second purpose of {@link JSObject} is to declare JavaScript classes as classes in Java code, * in a way that makes the Java objects look-and-feel like native JavaScript objects. Users should * subclass the {@link JSObject} class when they need to define a JavaScript class whose fields and * methods can be accessed conveniently from Java, without the {@link JSObject#get(Object)} and * {@link JSObject#set(Object, Object)} methods. - * + *
* Directly exposing a Java object to JavaScript code means that the JavaScript code is able to * manipulate the data within the object (e.g. mutate fields, add new fields, or redefine existing * fields), which is not allowed by default for regular Java classes. Extending {@link JSObject} @@ -144,7 +144,7 @@ * One of the use-cases for these functionalities are JavaScript frameworks that redefine properties * of JavaScript objects with custom getters and setters, with the goal of enabling data-binding or * reactive updates. - * + *
* In a subclass of {@link JSObject}, every JavaScript property directly corresponds to the Java * field of the same name. Consequently, all these properties point to native JavaScript values * rather than Java values, so bridge methods are generated that are called for each property access @@ -154,7 +154,7 @@ * corresponding Java field. For this reason, the bridge methods also generate check-casts on every * access: if the JavaScript property that corresponds to the Java field does not contain a * compatible value, a {@link ClassCastException} is thrown. - * + *
* There are several restrictions imposed on {@link JSObject} subclasses: *
* The preceding Java class is effectively translated to the corresponding JavaScript class: * *
@@ -194,7 +194,7 @@ * } * } *- * + *
* The {@code Point} class can be used from Java as if it were a normal Java class: * *
@@ -203,7 +203,7 @@ * System.out.println(p.y); * System.out.println(p.absolute()); *- * + *
* All accesses to the fields {@code x} and {@code y} are rewritten to accesses on the corresponding * JavaScript properties. JavaScript code may assign values to these properties that violate the * type of the corresponding Java fields, but a subsequent Java read of such a field will result in @@ -219,7 +219,7 @@ * * *
* When an object of the {@link JSObject} subclass is passed from Java to JavaScript code using the * {@link JS} annotation, the object is converted from its Java representation to its JavaScript * representation. Changes in the JavaScript representation are reflected in the Java representation @@ -236,7 +236,7 @@ * reset(p0, 0.0, 0.0); * System.out.println(p0.x + ", " + p0.y); * - * + *
* A {@link Class} object that represents {@link JSObject} can also be passed to JavaScript code. * The {@link Class} object is wrapped in a proxy, which can be used inside a {@code new} expression * to instantiate the object of the corresponding class from JavaScript. @@ -250,14 +250,14 @@ * Point p1 = create(Point.class, 1.25, 0.5); * System.out.println(p1.x + ", " + p1.y); * - * + *
* Note that creating an object in JavaScript and passing it to Java several times does not * guarantee that the same mirror instance is returned each time -- each time a JavaScript object * becomes a Java mirror, a different instance of the mirror may be returned. * * *
* The {@link JSObject} class allows access to properties of any JavaScript object using the * {@link JSObject#get(Object)} and {@link JSObject#set(Object, Object)} methods. In situations * where the programmer knows the relevant properties of a JavaScript object (for example, when @@ -265,14 +265,14 @@ * "imported" to Java. To do this, the user declares a {@link JSObject} subclass that serves as a * facade to the JavaScript object. This subclass must be annotated with the {@link JS.Import} * annotation. - * + *
* The name of the declared class Java must match the name of the JavaScript class that is being * imported. The package name of the Java class is not taken into account -- the same JavaScript * class can be imported multiple times from within separate packages. - * + *
* The exposed JavaScript fields must be declared as {@code protected} or {@code public}. The * constructor parameters must match those of the JavaScript class, and its body must be empty. - * + *
* Here is an example of a class declared in JavaScript: * *
@@ -283,7 +283,7 @@ * } * } *- * + *
* To import this class in Java code, we need the following declaration in Java: * *
@@ -296,19 +296,19 @@ * } * } *- * + *
* The fields declared in the {@code Rectangle} class are directly mapped to the properties of the * underlying JavaScript object. If the type of the property of the underlying JavaScript object * does not match the type of the field declared in Java, then a field-read in Java will throw a * {@link ClassCastException}. - * + *
* The {@code Rectangle} class can be instantiated from Java as follows: * *
* Rectangle r = new Rectangle(640, 480); * System.out.println(r.width + "x" + r.height); *- * + *
* A JavaScript object whose {@code constructor} property matches the imported JavaScript class can * be converted to the declared Java class when the JavaScript code passes a value to Java. Here is * a code example that creates the {@code Rectangle} object in JavaScript, and passes it to Java: @@ -317,13 +317,13 @@ * @JS("return new Rectangle(width, height);") * Rectangle createRectangle(int width, int height); * - * + *
* Another way to convert a JavaScript object to a Java facade is to call the * {@link JSObject#as(Class)} method to cast the {@link JSObject} instance to the proper subtype. * * *
* The users can annotate the exported classes with the {@link JS.Export} annotation to denote that * the {@link JSObject} subclass should be made available to JavaScript code. Exported classes can * be accessed using the JavaScript VM-instance API, using the `exports` property. @@ -344,7 +344,7 @@ * } * } * - * + *
* The exported class can then be used from JavaScript code as follows: * *
@@ -395,7 +395,7 @@ public String typeof() { /** * Sets the value of the key passed as the argument in the JavaScript object. * - * @param key the object under which the value should be placed in the JavaScript object + * @param key the object under which the value should be placed in the JavaScript object * @param newValue the value that should be placed under the given key in the JavaScript object */ @JS("this[key] = newValue;") @@ -413,7 +413,7 @@ public String typeof() { * Invoke the underlying JavaScript function, if this object is callable. * * @param args The array of Java arguments, which is converted to JavaScript and passed to the - * underlying JavaScript function + * underlying JavaScript function * @return The result of the JavaScript function, converted to the corresponding Java value */ @JS("return this.apply(this, conversion.extractJavaScriptArray(args[runtime.symbol.javaNative]));") @@ -424,8 +424,8 @@ public String typeof() { * in the function, if this object is callable. * * @param thisArg The value for the binding of {@code this} inside the JavaScript function - * @param args The array of Java arguments, which is converted to JavaScript and passed to the - * underlying JavaScript function + * @param args The array of Java arguments, which is converted to JavaScript and passed to the + * underlying JavaScript function * @return The result of the JavaScript function, converted to the corresponding Java value */ @JS("return this.apply(thisArg, conversion.extractJavaScriptArray(args[runtime.symbol.javaNative]));") @@ -547,4 +547,104 @@ publicT as(Class cls) { public boolean equalsJavaScript(JSObject that) { return referenceEquals(this, that).asBoolean(); } + + @JS.Coerce + @JS(value = "return Object.create(proto);") + public static native JSObject create(JSObject proto); + + @JS.Coerce + @JS(value = "return Object.create(proto, properties);") + public static native JSObject create(JSObject proto, JSObject properties); + + @JS.Coerce + @JS(value = "return Object.defineProperties(obj, prop);") + public static native JSObject defineProperties(JSObject obj, JSObject prop); + + @JS.Coerce + @JS(value = "return Object.defineProperty(obj, prop, descriptor);") + public static native JSObject defineProperty(JSObject obj, JSString prop, JSObject descriptor); + + @JS.Coerce + @JS(value = "return Object.defineProperty(obj, prop, descriptor);") + public static native JSObject defineProperty(JSObject obj, String prop, JSObject descriptor); + + @JS.Coerce + @JS(value = "return Object.entries(obj);") + public static native JSObject entries(JSObject obj); + + @JS.Coerce + @JS(value = "Object.freeze(obj);") + public static native void freeze(JSObject obj); + + @JS.Coerce + @JS(value = "return Object.fromEntries(iterable);") + public static native JSObject fromEntries(JSObject iterable); + + @JS.Coerce + @JS(value = "return Object.getOwnPropertyDescriptor(obj, prop);") + public static native JSObject getOwnPropertyDescriptor(JSObject obj, String prop); + + @JS.Coerce + @JS(value = "return Object.getOwnPropertyNames(obj);") + public static native JSObject getOwnPropertyNames(JSObject obj); + + @JS.Coerce + @JS(value = "return Object.groupBy(items, callback);") + public native static JSObject groupBy(JSObject items, JSValue callback); + + @JS.Coerce + @JS(value = "return Object.hasOwn(obj, prop);") + public static native boolean hasOwn(JSObject obj, String prop); + + @JS.Coerce + @JS(value = "return Object.is(value1, value2);") + public static native boolean is(JSValue value1, JSValue value2); + + @JS.Coerce + @JS(value = "return Object.isExtensible(obj);") + public static native boolean isExtensible(JSObject obj); + + @JS.Coerce + @JS(value = "return Object.isFrozen(obj);") + public static native boolean isFrozen(JSObject obj); + + @JS.Coerce + @JS(value = "return Object.isSealed(obj);") + public static native boolean isSealed(JSObject obj); + + @JS.Coerce + @JS(value = "return Object.keys(obj);") + public static native JSObject keys(JSObject obj); + + @JS.Coerce + @JS(value = "return Object.preventExtensions(obj);") + public static native JSObject preventExtensions(JSObject obj); + + @JS.Coerce + @JS(value = "return Object.seal(obj);") + public static native JSObject seal(JSObject obj); + + @JS.Coerce + @JS(value = "return Object.setPrototypeOf(obj, proto);") + public static native JSObject setPrototypeOf(JSObject obj, JSObject proto); + + @JS.Coerce + @JS(value = "return Object.values(obj);") + public static native JSObject values(JSObject obj); + + @JS.Coerce + @JS(value = "return this.isPrototypeOf(object);") + public native boolean isPrototypeOf(JSObject object); + + @JS.Coerce + @JS(value = "return this.propertyIsEnumerable(prop);") + public native boolean propertyIsEnumerable(String prop); + + @JS.Coerce + @JS(value = "return this.toLocaleString();") + public native String toLocaleString(); + + @JS.Coerce + @JS(value = "return this.valueOf();") + public native JSValue valueOf(); } diff --git a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSString.java b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSString.java index e7ba442568c2..6a09a2eeec73 100644 --- a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSString.java +++ b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSString.java @@ -82,4 +82,310 @@ public boolean equals(Object that) { public int hashCode() { return javaString().hashCode(); } + + @JS.Coerce + @JS(value = "return String.fromCharCode(codeUnit);") + public static native JSString fromCharCode(int codeUnit); + + @JS.Coerce + @JS(value = "return String.fromCharCode(...codeUnits);") + public static native JSString fromCharCode(int... codeUnits); + + @JS.Coerce + @JS(value = "return String.fromCodePoint(codeUnit);") + public static native JSString fromCodePoint(int codeUnit); + + @JS.Coerce + @JS(value = "return String.fromCodePoint(...codeUnits);") + public static native JSString fromCodePoint(int... codeUnits); + + @JS(value = "return String.raw(template);") + public static native JSString raw(JSObject template); + + @JS(value = """ + const args = []; + for (let i = 0; i < substitutions.length; i++) { + args.push(substitutions[i]); + } + return String.raw(template, ...args); + """) + public static native JSString raw(JSObject template, Object... substitutions); + + @JS.Coerce + @JS(value = "return this.at(index);") + public native JSValue at(int index); + + @JS.Coerce + @JS(value = "return this.charAt(index);") + public native JSString charAt(int index); + + @JS.Coerce + @JS(value = """ + const code = this.charCodeAt(index); + return Number.isNaN(code) ? -1 : code; + """) + public native int charCodeAt(int index); + + @JS.Coerce + @JS(value = """ + const code = this.codePointAt(index); + return code === undefined ? -1 : code; + """) + public native int codePointAt(int index); + + @JS.Coerce + @JS(value = "return this.concat.apply(this, strings);") + public native JSString concat(JSString... strings); + + @JS.Coerce + @JS(value = "return this.endsWith(searchString);") + public native boolean endsWith(String searchString); + + @JS.Coerce + @JS(value = "return this.endsWith(searchString, endPosition);") + public native boolean endsWith(String searchString, int endPosition); + + @JS.Coerce + @JS(value = "return this.endsWith(searchString);") + public native boolean endsWith(JSString searchString); + + @JS.Coerce + @JS(value = "return this.endsWith(searchString, endPosition);") + public native boolean endsWith(JSString searchString, int endPosition); + + @JS.Coerce + @JS(value = "return this.includes(searchString);") + public native boolean includes(String searchString); + + @JS.Coerce + @JS(value = "return this.includes(searchString, position);") + public native boolean includes(String searchString, int position); + + @JS.Coerce + @JS(value = "return this.includes(searchString);") + public native boolean includes(JSString searchString); + + @JS.Coerce + @JS(value = "return this.includes(searchString, position);") + public native boolean includes(JSString searchString, int position); + + @JS.Coerce + @JS(value = "return this.indexOf(searchString);") + public native int indexOf(String searchString); + + @JS.Coerce + @JS(value = "return this.indexOf(searchString, position);") + public native int indexOf(String searchString, int position); + + @JS.Coerce + @JS(value = "return this.indexOf(searchString);") + public native int indexOf(JSString searchString); + + @JS.Coerce + @JS(value = "return this.indexOf(searchString, position);") + public native int indexOf(JSString searchString, int position); + + @JS.Coerce + @JS(value = "return this.isWellFormed();") + public native boolean isWellFormed(); + + @JS.Coerce + @JS(value = "return this.lastIndexOf(searchString);") + public native int lastIndexOf(String searchString); + + @JS.Coerce + @JS(value = "return this.lastIndexOf(searchString, position);") + public native int lastIndexOf(String searchString, int position); + + @JS.Coerce + @JS(value = "return this.lastIndexOf(searchString);") + public native int lastIndexOf(JSString searchString); + + @JS.Coerce + @JS(value = "return this.lastIndexOf(searchString, position);") + public native int lastIndexOf(JSString searchString, int position); + + @JS.Coerce + @JS(value = "return this.localeCompare(compareString);") + public native int localeCompare(String compareString); + + @JS.Coerce + @JS(value = "return this.localeCompare(compareString, locales);") + public native int localeCompare(String compareString, String locales); + + @JS.Coerce + @JS(value = "return this.localeCompare(compareString, locales, options);") + public native int localeCompare(String compareString, String locales, Object options); + + @JS.Coerce + @JS(value = "return this.localeCompare(compareString);") + public native int localeCompare(JSString compareString); + + @JS.Coerce + @JS(value = "return this.localeCompare(compareString, locales);") + public native int localeCompare(JSString compareString, JSString locales); + + @JS.Coerce + @JS(value = "return this.localeCompare(compareString, locales, options);") + public native int localeCompare(JSString compareString, JSString locales, Object options); + + @JS.Coerce + @JS(value = "return this.match(regexp);") + public native JSObject match(Object regexp); + + @JS.Coerce + @JS(value = "return this.matchAll(regexp);") + public native JSObject matchAll(Object regexp); + + @JS.Coerce + @JS(value = "return this.normalize();") + public native JSString normalize(); + + @JS.Coerce + @JS(value = "return this.normalize(form);") + public native JSString normalize(String form); + + @JS.Coerce + @JS(value = "return this.padEnd(targetLength);") + public native JSString padEnd(int targetLength); + + @JS.Coerce + @JS(value = "return this.padEnd(targetLength, padString);") + public native JSString padEnd(int targetLength, String padString); + + @JS.Coerce + @JS(value = "return this.padEnd(targetLength, padString);") + public native JSString padEnd(int targetLength, JSString padString); + + @JS.Coerce + @JS(value = "return this.padStart(targetLength);") + public native JSString padStart(int targetLength); + + @JS.Coerce + @JS(value = "return this.padStart(targetLength, padString);") + public native JSString padStart(int targetLength, String padString); + + @JS.Coerce + @JS(value = "return this.padStart(targetLength, padString);") + public native JSString padStart(int targetLength, JSString padString); + + @JS.Coerce + @JS(value = "return this.repeat(count);") + public native JSString repeat(int count); + + @JS.Coerce + @JS(value = "return this.replace(pattern, replacement);") + public native JSString replace(Object pattern, Object replacement); + + @JS.Coerce + @JS(value = "return this.replaceAll(pattern, replacement);") + public native JSString replaceAll(Object pattern, Object replacement); + + @JS.Coerce + @JS(value = "return this.search(regexp);") + public native int search(Object regexp); + + @JS.Coerce + @JS(value = "return this.slice(indexStart);") + public native JSString slice(int indexStart); + + @JS.Coerce + @JS(value = "return this.slice(indexStart, indexEnd);") + public native JSString slice(int indexStart, int indexEnd); + + @JS.Coerce + @JS(value = "return this.split(separator);") + public native JSObject split(String separator); + + @JS.Coerce + @JS(value = "return this.split(separator);") + public native JSObject split(JSObject separator); + + @JS.Coerce + @JS(value = "return this.split(separator, limit);") + public native JSObject split(String separator, int limit); + + @JS.Coerce + @JS(value = "return this.split(separator, limit);") + public native JSObject split(JSObject separator, int limit); + + @JS.Coerce + @JS(value = "return this.startsWith(searchString);") + public native boolean startsWith(String searchString); + + @JS.Coerce + @JS(value = "return this.startsWith(searchString);") + public native boolean startsWith(JSString searchString); + + @JS.Coerce + @JS(value = "return this.startsWith(searchString, position);") + public native boolean startsWith(String searchString, int position); + + @JS.Coerce + @JS(value = "return this.startsWith(searchString, position);") + public native boolean startsWith(JSString searchString, int position); + + @JS.Coerce + @JS(value = "return this.toLocaleLowerCase();") + public native JSString toLocaleLowerCase(); + + @JS.Coerce + @JS(value = "return this.toLocaleLowerCase(locales);") + public native JSString toLocaleLowerCase(String locales); + + @JS.Coerce + @JS(value = "return this.toLocaleLowerCase(locales);") + public native JSString toLocaleLowerCase(JSString locales); + + @JS.Coerce + @JS(value = "return this.toLocaleUpperCase();") + public native JSString toLocaleUpperCase(); + + @JS.Coerce + @JS(value = "return this.toLocaleUpperCase(locales);") + public native JSString toLocaleUpperCase(String locales); + + @JS.Coerce + @JS(value = "return this.toLocaleUpperCase(locales);") + public native JSString toLocaleUpperCase(JSString locales); + + @JS.Coerce + @JS(value = "return this.toLowerCase();") + public native JSString toLowerCase(); + + @JS.Coerce + @JS(value = "return this.toUpperCase();") + public native JSString toUpperCase(); + + @JS.Coerce + @JS(value = "return this.toWellFormed();") + public native JSString toWellFormed(); + + @JS.Coerce + @JS(value = "return this.trim();") + public native JSString trim(); + + @JS.Coerce + @JS(value = "return this.trimEnd();") + public native JSString trimEnd(); + + @JS.Coerce + @JS(value = "return this.trimRight();") + public native JSString trimRight(); + + @JS.Coerce + @JS(value = "return this.trimStart();") + public native JSString trimStart(); + + @JS.Coerce + @JS(value = "return this.trimLeft();") + public native JSString trimLeft(); + + @JS.Coerce + @JS(value = "return this.valueOf();") + public native JSString valueOf(); + + @JS.Coerce + @JS(value = "return this.length;") + public native int length(); } diff --git a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSSymbol.java b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSSymbol.java index 135ad522c429..5400a218c44c 100644 --- a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSSymbol.java +++ b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSSymbol.java @@ -87,4 +87,83 @@ public boolean equals(Object that) { public int hashCode() { return javaString().hashCode(); } + + @JS(value = "return Symbol.for(key);") + public static native JSSymbol forKey(String key); + + @JS.Coerce + @JS(value = "return Symbol.keyFor(sym);") + public static native JSValue keyFor(JSSymbol sym); + + @JS.Coerce + @JS(value = "return Symbol.asyncDispose;") + public static native JSSymbol asyncDispose(); + + @JS.Coerce + @JS(value = "return Symbol.asyncIterator;") + public static native JSSymbol asyncIterator(); + + @JS.Coerce + @JS(value = "return Symbol.dispose;") + public static native JSSymbol dispose(); + + @JS.Coerce + @JS(value = "return Symbol.hasInstance;") + public static native JSSymbol hasInstance(); + + @JS.Coerce + @JS(value = "return Symbol.isConcatSpreadable;") + public static native JSSymbol isConcatSpreadable(); + + @JS.Coerce + @JS(value = "return Symbol.iterator;") + public static native JSSymbol iterator(); + + @JS.Coerce + @JS(value = "return Symbol.match;") + public static native JSSymbol match(); + + @JS.Coerce + @JS(value = "return Symbol.matchAll;") + public static native JSSymbol matchAll(); + + @JS.Coerce + @JS(value = "return Symbol.replace;") + public static native JSSymbol replace(); + + @JS.Coerce + @JS(value = "return Symbol.search;") + public static native JSSymbol search(); + + @JS.Coerce + @JS(value = "return Symbol.species;") + public static native JSSymbol species(); + + @JS.Coerce + @JS(value = "return Symbol.split;") + public static native JSSymbol split(); + + @JS.Coerce + @JS(value = "return Symbol.toPrimitive;") + public static native JSSymbol toPrimitive(); + + @JS.Coerce + @JS(value = "return Symbol.toStringTag;") + public static native JSSymbol toStringTag(); + + @JS.Coerce + @JS(value = "return Symbol.unscopables;") + public static native JSSymbol unscopables(); + + @JS.Coerce + @JS(value = "return sym.valueOf();") + public static native JSSymbol valueOf(Object sym); + + @JS.Coerce + @JS(value = "return a === b;") + public static native boolean isSameSymbol(JSSymbol a, JSSymbol b); + + @JS.Coerce + @JS(value = "return sym.description;") + public static native String description(Object sym); } diff --git a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSValue.java b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSValue.java index 5787460aa37c..8b0b49164d05 100644 --- a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSValue.java +++ b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSValue.java @@ -45,7 +45,7 @@ /** * Java representation of a JavaScript value. - * + * * The subclasses of this class represent JavaScript's six primitive data types and the object data * type. The JavaScript {@code Null} data type does not have a special representation -- the * JavaScript {@code null} value is directly mapped to the Java {@code null} value. @@ -55,6 +55,12 @@ public abstract class JSValue { JSValue() { } + @SuppressWarnings("unchecked") + public static
R checkedCoerce(Object value, Class cls) { + if (value instanceof JSValue jsResult) return jsResult.as(cls); + return (R) value; + } + public static JSUndefined undefined() { return JSUndefined.instance(); } diff --git a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSError.java b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/ThrownFromJavaScript.java similarity index 94% rename from sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSError.java rename to sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/ThrownFromJavaScript.java index 52d810bd5366..25d92dd8be8a 100644 --- a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSError.java +++ b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/ThrownFromJavaScript.java @@ -45,10 +45,10 @@ * Represents the error value thrown in JavaScript. * * Must not pass a {@link Throwable} instance, these should be thrown directly instead of being - * wrapped in a {@link JSError}. + * wrapped in a {@link ThrownFromJavaScript}. */ @SuppressWarnings("serial") -public final class JSError extends RuntimeException { +public final class ThrownFromJavaScript extends RuntimeException { private static final long serialVersionUID = -2343564169271174471L; @@ -57,7 +57,7 @@ public final class JSError extends RuntimeException { */ private final Object thrownObject; - public JSError(Object thrownObject) { + public ThrownFromJavaScript(Object thrownObject) { super(thrownObject.toString()); this.thrownObject = thrownObject; assert !(thrownObject instanceof Throwable) : "Tried creating JSError for a throwable: " + thrownObject; diff --git a/web-image/src/com.oracle.svm.hosted.webimage.test/src/com/oracle/svm/hosted/webimage/test/spec/JS_JTT_JSAnnotation.java b/web-image/src/com.oracle.svm.hosted.webimage.test/src/com/oracle/svm/hosted/webimage/test/spec/JS_JTT_JSAnnotation.java index a70c0532dffe..9afe627b9c71 100644 --- a/web-image/src/com.oracle.svm.hosted.webimage.test/src/com/oracle/svm/hosted/webimage/test/spec/JS_JTT_JSAnnotation.java +++ b/web-image/src/com.oracle.svm.hosted.webimage.test/src/com/oracle/svm/hosted/webimage/test/spec/JS_JTT_JSAnnotation.java @@ -27,6 +27,10 @@ import java.nio.file.Path; +import com.oracle.svm.webimage.jtt.api.JSNumberTest; +import com.oracle.svm.webimage.jtt.api.JSObjectTest; +import com.oracle.svm.webimage.jtt.api.JSStringTest; +import com.oracle.svm.webimage.jtt.api.JSSymbolTest; import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; @@ -131,4 +135,23 @@ public void htmlApiExamplesTest() { testFileAgainstNoBuild(HtmlApiExamplesTest.OUTPUT, HtmlApiExamplesTest.class.getName()); } + @Test + public void jsNumberTest() { + testFileAgainstNoBuild(JSNumberTest.class.getName()); + } + + @Test + public void jsStringTest() { + testFileAgainstNoBuild(JSStringTest.class.getName()); + } + + @Test + public void jsSymbolTest() { + testFileAgainstNoBuild(JSSymbolTest.class.getName()); + } + + @Test + public void jsObjectTest() { + testFileAgainstNoBuild(JSObjectTest.class.getName()); + } } diff --git a/web-image/src/com.oracle.svm.hosted.webimage.test/src/com/oracle/svm/hosted/webimage/test/util/JTTTestSuite.java b/web-image/src/com.oracle.svm.hosted.webimage.test/src/com/oracle/svm/hosted/webimage/test/util/JTTTestSuite.java index 33ce19029df0..60b1fc9bbb61 100644 --- a/web-image/src/com.oracle.svm.hosted.webimage.test/src/com/oracle/svm/hosted/webimage/test/util/JTTTestSuite.java +++ b/web-image/src/com.oracle.svm.hosted.webimage.test/src/com/oracle/svm/hosted/webimage/test/util/JTTTestSuite.java @@ -200,6 +200,13 @@ protected static void testFileAgainstNoBuild(String[] expected, String... args) testFileAgainstNoBuild(0, new WebImageTestUtil.RunResult(expected), args); } + /** + * Runs the currently compiled JS image. + */ + protected static void testFileAgainstNoBuild(String... args) { + runJS(args, 0); + } + /** * Runs the currently compiled JS image and, asserts the given {@code exitCode} and checks its * output using the {@code lineChecker} consumer. @@ -226,7 +233,7 @@ protected static void testClassNoBuild(Class> c, String... args) { /** * Runs the currently compiled JS image and compares its result against the given class run in a * Java runtime. - * + *
* It also checks the expected exit code. */ protected static void testClassNoBuildWithExitCode(int exitCode, Class> c, String... args) { @@ -244,7 +251,7 @@ protected static void testClassNoBuild(Class> c, String[] args, BiConsumer
* It also checks the expected exit code. */ protected static void testClassNoBuildWithExitCode(int exitCode, Class> c, String[] args, BiConsumer lineChecker) { diff --git a/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSErrorsTest.java b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSErrorsTest.java index cd035d3c4aee..ef9710519966 100644 --- a/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSErrorsTest.java +++ b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSErrorsTest.java @@ -26,7 +26,7 @@ package com.oracle.svm.webimage.jtt.api; import org.graalvm.webimage.api.JS; -import org.graalvm.webimage.api.JSError; +import org.graalvm.webimage.api.ThrownFromJavaScript; public class JSErrorsTest { /** @@ -43,7 +43,7 @@ public class JSErrorsTest { public static void main(String[] args) { try { typeError(); - } catch (JSError e) { + } catch (ThrownFromJavaScript e) { System.out.println(e.getMessage()); } diff --git a/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSNumberTest.java b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSNumberTest.java new file mode 100644 index 000000000000..fa85a7fe0f08 --- /dev/null +++ b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSNumberTest.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.webimage.jtt.api; + +import org.graalvm.webimage.api.JSNumber; +import org.graalvm.webimage.api.JSObject; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class JSNumberTest { + + static final double DOUBLE_SMALL = 3.14159; + static final double DOUBLE_BIG = 123.456789; + static final double SMALL = 0.00001234; + static final double SMALL_EXTENDED = 0.0000123456789; + static final double LARGE = 987654321.123; + static final double SAFE_INT = 9007199254740991L; + static final double UNSAFE_INT = 9007199254740992L; + static final double POS_INF = Double.POSITIVE_INFINITY; + static final double NEG_INF = Double.NEGATIVE_INFINITY; + static final double NAN = Double.NaN; + static final int INT = 42; + static final int NEG_INT = -42; + static final int HEX = 255; + static final double DELTA = 0.0; + static final double SMALL_DELTA = 0.001; + + public static void main(String[] args) { + testIsFinite(); + testIsInteger(); + testIsNaN(); + testIsSafeInteger(); + testConstants(); + testParseFloat(); + testParseInt(); + testToExponential(); + testToFixed(); + testToLocaleString(); + testToPrecision(); + testToStringRadix(); + testValueOf(); + } + + public static void testIsFinite() { + assertTrue(JSNumber.isFinite(JSNumber.of(INT))); + assertFalse(JSNumber.isFinite(JSNumber.of(POS_INF))); + assertFalse(JSNumber.isFinite(JSNumber.of(NAN))); + assertTrue(JSNumber.isFinite(JSNumber.of(DOUBLE_BIG))); + assertFalse(JSNumber.isFinite(JSNumber.of(NEG_INF))); + } + + public static void testIsInteger() { + assertTrue(JSNumber.isInteger(JSNumber.of(INT))); + assertFalse(JSNumber.isInteger(JSNumber.of(DOUBLE_BIG))); + assertFalse(JSNumber.isInteger(JSNumber.of(NAN))); + assertFalse(JSNumber.isInteger(JSNumber.of(POS_INF))); + assertFalse(JSNumber.isInteger(JSNumber.of(NEG_INF))); + } + + public static void testIsNaN() { + assertFalse(JSNumber.isNaN(JSNumber.of(INT))); + assertTrue(JSNumber.isNaN(JSNumber.of(NAN))); + assertFalse(JSNumber.isNaN(JSNumber.of(POS_INF))); + assertFalse(JSNumber.isNaN(JSNumber.of(DOUBLE_BIG))); + assertFalse(JSNumber.isNaN(JSNumber.of(NEG_INF))); + } + + public static void testIsSafeInteger() { + assertTrue(JSNumber.isSafeInteger(JSNumber.of(SAFE_INT))); + assertFalse(JSNumber.isSafeInteger(JSNumber.of(UNSAFE_INT))); + assertFalse(JSNumber.isSafeInteger(JSNumber.of(DOUBLE_BIG))); + assertFalse(JSNumber.isSafeInteger(JSNumber.of(NAN))); + assertFalse(JSNumber.isSafeInteger(JSNumber.of(1e100))); + } + + public static void testConstants() { + assertEquals(2.220446e-16, JSNumber.EPSILON(), 1e-16); + assertEquals(9.007199e+15, JSNumber.MAX_SAFE_INTEGER(), 0.000001e+15); + assertEquals(1.797693e+308, JSNumber.MAX_VALUE(), 0.000001e+308); + assertEquals(-9.007199e+15, JSNumber.MIN_SAFE_INTEGER(), 0.000001e+15); + assertEquals(4.900000e-324, JSNumber.MIN_VALUE(), 1e-16); + assertEquals(NAN, JSNumber.NaN(), DELTA); + assertEquals(NEG_INF, JSNumber.NEGATIVE_INFINITY(), DELTA); + assertEquals(POS_INF, JSNumber.POSITIVE_INFINITY(), DELTA); + } + + public static void testParseFloat() { + assertEquals(42.0, JSNumber.parseFloat(INT), DELTA); + assertEquals(DOUBLE_BIG, JSNumber.parseFloat(DOUBLE_BIG), SMALL_DELTA); + assertEquals(3.14, JSNumber.parseFloat("3.14abc"), SMALL_DELTA); + assertEquals(NAN, JSNumber.parseFloat("abc"), DELTA); + } + + public static void testParseInt() { + assertEquals(42, JSNumber.parseInt(42.9)); + assertEquals(-3, JSNumber.parseInt(-3.99)); + assertEquals(123, JSNumber.parseInt("123")); + assertEquals(123, JSNumber.parseInt("123.456")); + assertEquals(0, JSNumber.parseInt("abc")); + assertEquals(10, JSNumber.parseInt("1010", 2)); + assertEquals(255, JSNumber.parseInt("FF", 16)); + assertEquals(63, JSNumber.parseInt("77", 8)); + } + + public static void testToExponential() { + JSNumber small = JSNumber.of(SMALL); + JSNumber big = JSNumber.of(987654321); + JSNumber pi = JSNumber.of(DOUBLE_SMALL); + + assertEquals("1.234e-5", small.toExponential()); + assertEquals("9.87654321e+8", big.toExponential()); + assertEquals("3.14159e+0", pi.toExponential()); + + assertEquals("1.23e-5", small.toExponential(2)); + assertEquals("9.8765e+8", big.toExponential(4)); + assertEquals("3.141590e+0", pi.toExponential(6)); + } + + public static void testToFixed() { + JSNumber pi = JSNumber.of(DOUBLE_SMALL); + JSNumber big = JSNumber.of(DOUBLE_BIG); + JSNumber small = JSNumber.of(SMALL); + + assertEquals("3", pi.toFixed()); + assertEquals("123", big.toFixed()); + assertEquals("0", small.toFixed()); + + assertEquals("3.14", pi.toFixed(2)); + assertEquals("123.4568", big.toFixed(4)); + assertEquals("0.00001234", small.toFixed(8)); + } + + public static void testToLocaleString() { + JSNumber number = JSNumber.of(1234567.89); + + JSObject currencyOpts = JSObject.create(); + currencyOpts.set("style", "currency"); + currencyOpts.set("currency", "EUR"); + + JSObject fractionOpts = JSObject.create(); + fractionOpts.set("minimumFractionDigits", JSNumber.of(4)); + fractionOpts.set("maximumFractionDigits", JSNumber.of(4)); + + assertTrue(number.toLocaleString().matches("\\d[,.]\\d{3}[,.]\\d{3}[,.]\\d{2}")); + assertEquals("1\u00A0234\u00A0567,89", number.toLocaleString("de-AT")); + assertEquals("1,234,567.89", number.toLocaleString("en-US")); + assertEquals("\u20AC\u00A01.234.567,89", number.toLocaleString("de-AT", currencyOpts)); + assertEquals("1,234,567.8900", number.toLocaleString("en-US", fractionOpts)); + } + + public static void testToPrecision() { + JSNumber big = JSNumber.of(DOUBLE_BIG); + JSNumber extended = JSNumber.of(SMALL_EXTENDED); + JSNumber large = JSNumber.of(LARGE); + + assertEquals("123.456789", big.toPrecision()); + assertEquals("0.0000123456789", extended.toPrecision()); + assertEquals("987654321.123", large.toPrecision()); + + assertEquals("123.5", big.toPrecision(4)); + assertEquals("0.0000123", extended.toPrecision(3)); + assertEquals("9.87654e+8", large.toPrecision(6)); + } + + public static void testToStringRadix() { + JSNumber hex = JSNumber.of(HEX); + JSNumber pi = JSNumber.of(DOUBLE_SMALL); + JSNumber neg = JSNumber.of(NEG_INT); + + assertEquals("JavaScript ", hex.toString()); + assertEquals("JavaScript ", pi.toString()); + assertEquals("JavaScript ", neg.toString()); + + assertEquals("JavaScript ", hex.toString(2)); + assertEquals("JavaScript ", hex.toString(16)); + assertEquals("JavaScript ", hex.toString(8)); + assertEquals("JavaScript ", neg.toString(5)); + } + + public static void testValueOf() { + assertEquals(42.0, JSNumber.of(INT).valueOf(), DELTA); + assertEquals(DOUBLE_SMALL, JSNumber.of(DOUBLE_SMALL).valueOf(), 0.000001); + assertEquals(NAN, JSNumber.of(NAN).valueOf(), DELTA); + } +} diff --git a/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSObjectTest.java b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSObjectTest.java new file mode 100644 index 000000000000..a74396bc3d8e --- /dev/null +++ b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSObjectTest.java @@ -0,0 +1,482 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.webimage.jtt.api; + +import org.graalvm.webimage.api.JS; +import org.graalvm.webimage.api.JSBoolean; +import org.graalvm.webimage.api.JSNumber; +import org.graalvm.webimage.api.JSObject; +import org.graalvm.webimage.api.JSString; +import org.graalvm.webimage.api.JSValue; +import org.graalvm.webimage.api.ThrownFromJavaScript; + +import java.util.function.Function; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class JSObjectTest { + + public static void main(String[] args) { + testPrototypeInheritance(); + testCreateWithProperties(); + testDefineProperties(); + testDefinePropertyVariants(); + testEntries(); + testFreeze(); + testFromEntries(); + testGetOwnPropertyDescriptor(); + testGetOwnPropertyNames(); + testGroupBy(); + testHasOwn(); + testIsEquality(); + testIsExtensibleAndPreventExtensions(); + testIsFrozenAndFreeze(); + testPrototypeChain(); + testSealAndMutation(); + testKeysAndValues(); + testPreventExtensions(); + testPropertyIsEnumerable(); + testPrototypeMethodBinding(); + testToLocaleString(); + testValueOf(); + testValues(); + } + + public static void testPrototypeInheritance() { + JSObject proto = JSObject.create(); + proto.set("greet", fromJavaFunction((String name) -> "Hello, " + name)); + + JSObject obj = JSObject.create(proto); + JSValue fun = (JSValue) obj.get("greet"); + String greeting = apply(fun, null, "Alice"); + + assertEquals("Hello, Alice", greeting); + } + + public static void testCreateWithProperties() { + JSObject proto = JSObject.create(); + proto.set("greet", fromJavaFunction((String name) -> "Hello, " + name)); + JSObject nameDescriptor = JSObject.create(); + set(nameDescriptor, JSString.of("Bob"), false, true); + JSObject properties = JSObject.create(); + properties.set("name", nameDescriptor); + + JSObject obj = JSObject.create(proto, properties); + boolean failed = false; + try { + obj.set("name", "Alice"); + } catch (Exception e) { + failed = true; + } + String result = JSValue.checkedCoerce(obj.get("name"), String.class); + JSValue fun = (JSValue) obj.get("greet"); + String greeting = apply(fun, null, "World"); + + assertTrue(failed); + assertEquals("Bob", result); + assertEquals("Hello, World", greeting); + } + + public static void testDefineProperties() { + JSObject target = JSObject.create(); + JSObject descriptors = JSObject.create(); + JSObject nameDescriptor = JSObject.create(); + set(nameDescriptor, JSString.of("Alice"), true, true); + descriptors.set("name", nameDescriptor); + JSObject versionDescriptor = JSObject.create(); + set(versionDescriptor, JSNumber.of(26), false, false); + descriptors.set("age", versionDescriptor); + + JSObject result = JSObject.defineProperties(target, descriptors); + String name1 = JSValue.checkedCoerce(result.get("name"), String.class); + int age = JSValue.checkedCoerce(result.get("age"), Integer.class); + result.set("name", JSString.of("Bob")); + boolean failed = false; + try { + result.set("age", JSNumber.of(21)); + } catch (Exception e) { + failed = true; + } + String name2 = JSValue.checkedCoerce(result.get("name"), String.class); + + assertEquals("Alice", name1); + assertEquals(26, age); + assertEquals("Bob", name2); + assertTrue(failed); + } + + public static void testDefinePropertyVariants() { + JSObject obj1 = JSObject.create(); + JSObject obj2 = JSObject.create(); + JSObject descriptor = JSObject.create(); + set(descriptor, JSString.of("Alice"), false, true); + + JSObject.defineProperty(obj1, JSString.of("name"), descriptor); + JSObject.defineProperty(obj2, "name", descriptor); + boolean failed1 = false; + boolean failed2 = false; + try { + obj1.set("name", "NotAlice"); + } catch (Exception e) { + failed1 = true; + } + try { + obj2.set("name", "StillNotAlice"); + } catch (Exception e) { + failed2 = true; + } + + assertTrue(failed1); + assertTrue(failed2); + assertEquals("Alice", JSValue.checkedCoerce(obj1.get("name"), String.class)); + assertEquals("Alice", JSValue.checkedCoerce(obj2.get("name"), String.class)); + } + + public static void testEntries() { + JSObject obj = JSObject.create(); + obj.set("language", "JavaScript"); + obj.set("version", "ES2025"); + + String result = objToString(JSObject.entries(obj)); + + assertEquals("language,JavaScript,version,ES2025", result); + } + + public static void testFreeze() { + JSObject obj = JSObject.create(); + obj.set("name", "Alice"); + + JSObject.freeze(obj); + boolean failed = false; + try { + obj.set("name", "Changed"); + } catch (ThrownFromJavaScript e) { + failed = true; + } + + assertTrue(failed); + assertEquals("Alice", JSValue.checkedCoerce(obj.get("name"), String.class)); + } + + public static void testFromEntries() { + JSObject entries1 = createArray("framework", "GraalVM"); + JSObject entries2 = createArray("mode", "native"); + JSObject entries = createArray(entries1, entries2); + + JSObject result = JSObject.fromEntries(entries); + String keys = objToString(result.keys()); + + assertEquals("framework,mode", keys); + assertEquals("GraalVM", result.get("framework")); + assertEquals("native", result.get("mode")); + + } + + public static void testGetOwnPropertyDescriptor() { + JSObject obj = JSObject.create(); + obj.set("name", "Alice"); + + JSObject descriptor = JSObject.getOwnPropertyDescriptor(obj, "name"); + + assertEquals("Alice", JSValue.checkedCoerce(descriptor.get("value"), String.class)); + assertTrue(JSValue.checkedCoerce(descriptor.get("writable"), Boolean.class)); + assertTrue(JSValue.checkedCoerce(descriptor.get("enumerable"), Boolean.class)); + assertTrue(JSValue.checkedCoerce(descriptor.get("configurable"), Boolean.class)); + } + + public static void testGetOwnPropertyNames() { + JSObject obj = JSObject.create(); + obj.set("x", 1); + obj.set("y", 2); + + String result = objToString(JSObject.getOwnPropertyNames(obj)); + assertEquals("x,y", result); + } + + public static void testGroupBy() { + JSObject item1 = createItem("asparagus", "vegetable", 5); + JSObject item2 = createItem("bananas", "fruit", 0); + JSObject item3 = createItem("cherries", "fruit", 5); + JSObject item4 = createItem("goat", "meat", 23); + JSObject item5 = createItem("fish", "meat", 22); + JSObject items = createArray(item1, item2, item3, item4, item5); + + JSValue groupByType = fromJavaFunction((JSObject item) -> item.get("type")); + JSObject grouped = JSObject.groupBy(items, groupByType); + + assertEquals("asparagus", JSValue.checkedCoerce(JSValue.checkedCoerce(grouped.get("vegetable"), JSObject.class).get(0), JSObject.class).get("name")); + assertEquals("bananas", JSValue.checkedCoerce(JSValue.checkedCoerce(grouped.get("fruit"), JSObject.class).get(0), JSObject.class).get("name")); + assertEquals("cherries", JSValue.checkedCoerce(JSValue.checkedCoerce(grouped.get("fruit"), JSObject.class).get(1), JSObject.class).get("name")); + assertEquals("goat", JSValue.checkedCoerce(JSValue.checkedCoerce(grouped.get("meat"), JSObject.class).get(0), JSObject.class).get("name")); + assertEquals("fish", JSValue.checkedCoerce(JSValue.checkedCoerce(grouped.get("meat"), JSObject.class).get(1), JSObject.class).get("name")); + } + + public static void testHasOwn() { + JSObject obj = JSObject.create(); + obj.set("x", 10); + + assertTrue(JSObject.hasOwn(obj, "x")); + assertFalse(JSObject.hasOwn(obj, "y")); + } + + public static void testIsEquality() { + JSObject obj1 = JSObject.create(); + obj1.set("value", 1); + JSObject obj2 = JSObject.create(); + obj2.set("value", 1); + + assertTrue(JSObject.is(JSString.of("hello"), JSString.of("hello"))); + assertFalse(JSObject.is(JSString.of("5"), JSNumber.of(5))); + assertTrue(JSObject.is(JSNumber.of(Double.NaN), JSNumber.of(Double.NaN))); + assertFalse(JSObject.is(JSNumber.of(0.0), JSNumber.of(-0.0))); + assertTrue(JSObject.is(JSBoolean.of(true), JSBoolean.of(true))); + assertFalse(JSObject.is(obj1, obj2)); + assertTrue(JSObject.is(obj1, obj1)); + } + + public static void testIsExtensibleAndPreventExtensions() { + JSObject obj = createTestObject(); + + boolean result1 = JSObject.isExtensible(obj); + JSObject.preventExtensions(obj); + boolean result2 = JSObject.isExtensible(obj); + boolean failed = false; + try { + obj.set("newProp", "test"); + } catch (Exception e) { + failed = true; + } + + assertTrue(result1); + assertFalse(result2); + assertTrue(failed); + assertFalse(JSObject.hasOwn(obj, "newProp")); + } + + public static void testIsFrozenAndFreeze() { + JSObject obj = createTestObject(); + + boolean result1 = JSObject.isFrozen(obj); + JSObject.freeze(obj); + boolean result2 = JSObject.isFrozen(obj); + boolean failed1 = false; + try { + obj.set("name", "Bob"); + } catch (Exception e) { + failed1 = true; + } + boolean failed2 = false; + try { + obj.set("newProp", "test"); + } catch (Exception e) { + failed2 = true; + } + + assertFalse(result1); + assertTrue(result2); + assertTrue(failed1); + assertTrue(failed2); + assertEquals("Alice", obj.get("name")); + assertFalse(JSObject.hasOwn(obj, "newProp")); + } + + public static void testPrototypeChain() { + JSObject proto = JSObject.create(); + JSObject obj = JSObject.create(); + JSObject.setPrototypeOf(obj, proto); + + assertTrue(proto.isPrototypeOf(obj)); + assertFalse(obj.isPrototypeOf(proto)); + } + + public static void testSealAndMutation() { + JSObject obj = createTestObject(); + + boolean result1 = JSObject.isSealed(obj); + JSObject.seal(obj); + boolean result2 = JSObject.isSealed(obj); + boolean failed1 = false; + try { + obj.set("name", "Bob"); + } catch (Exception e) { + failed1 = true; + } + boolean failed2 = false; + try { + obj.set("newProp", "test"); + } catch (Exception e) { + failed2 = true; + } + String finalName = JSValue.checkedCoerce(obj.get("name"), String.class); + boolean exists = JSObject.hasOwn(obj, "newProp"); + + assertFalse(result1); + assertTrue(result2); + assertFalse(failed1); + assertTrue(failed2); + assertEquals("Bob", finalName); + assertFalse(exists); + } + + public static void testKeysAndValues() { + JSObject obj = createTestObject(); + obj.set("age", "27"); + obj.set("active", "true"); + + String keys = objToString(JSObject.keys(obj)); + String values = objToString(JSObject.values(obj)); + + assertEquals("name,age,active", keys); + assertEquals("Alice,27,true", values); + } + + public static void testPreventExtensions() { + JSObject obj = createTestObject(); + + boolean result1 = JSObject.isExtensible(obj); + JSObject.preventExtensions(obj); + boolean result2 = JSObject.isExtensible(obj); + boolean failed = false; + try { + obj.set("newProp", "test"); + } catch (Exception e) { + failed = true; + } + boolean exists = JSObject.hasOwn(obj, "newProp"); + + assertTrue(result1); + assertFalse(result2); + assertTrue(failed); + assertFalse(exists); + } + + public static void testPropertyIsEnumerable() { + JSObject obj = JSObject.create(); + obj.set("visible", "yes"); + JSObject descriptors = JSObject.create(); + JSObject hiddenDescriptor = JSObject.create(); + hiddenDescriptor.set("value", "no"); + hiddenDescriptor.set("enumerable", JSBoolean.of(false)); + descriptors.set("hidden", hiddenDescriptor); + JSObject.defineProperties(obj, descriptors); + + String keys = objToString(obj.keys()); + + assertTrue(obj.propertyIsEnumerable("visible")); + assertFalse(obj.propertyIsEnumerable("hidden")); + assertFalse(obj.propertyIsEnumerable("missing")); + assertEquals("visible", keys); + } + + public static void testPrototypeMethodBinding() { + JSObject obj = createTestObject(); + + JSObject proto = JSObject.create(); + proto.set("describe", fromJSArgs("return 'I am ' + String(this.name);")); + + JSObject result = JSObject.setPrototypeOf(obj, proto); + JSValue describeFn = (JSValue) result.get("describe"); + String description = JSValue.checkedCoerce(apply(describeFn, result), String.class); + + assertEquals("I am Alice", description); + } + + public static void testToLocaleString() { + JSObject obj = createTestObject(); + obj.set("region", "Austria"); + + assertEquals("[object Object]", obj.toLocaleString()); + } + + public static void testValueOf() { + JSObject obj = JSObject.create(); + obj.set("id", 42); + + JSObject result = JSValue.checkedCoerce(obj.valueOf(), JSObject.class); + int value = JSValue.checkedCoerce(result.get("id"), Integer.class); + + assertEquals(42, value); + } + + public static void testValues() { + JSObject obj = createTestObject(); + obj.set("age", "27"); + obj.set("active", "true"); + + String values = objToString(JSObject.values(obj)); + + assertEquals("Alice,27,true", values); + } + + private static void set(JSObject obj, JSValue value, boolean writable, boolean configurable) { + obj.set("value", value); + obj.set("writable", JSBoolean.of(writable)); + obj.set("enumerable", JSBoolean.of(true)); + obj.set("configurable", JSBoolean.of(configurable)); + } + + private static JSObject createItem(String name, String type, int quantity) { + JSObject obj = JSObject.create(); + obj.set("name", name); + obj.set("type", type); + obj.set("quantity", quantity); + return obj; + } + + private static JSObject createTestObject() { + JSObject obj = JSObject.create(); + obj.set("name", "Alice"); + return obj; + } + + @JS.Coerce + @JS(value = "return function(args) { return javaFunc.apply(args); }") + public static native JSValue fromJavaFunction(Function javaFunc); + + @JS.Coerce + @JS(value = "return Function.apply(null, args);") + public static native JSValue fromJSArgs(String... args); + + @SafeVarargs + @JS.Coerce + @JS(value = "return fun.apply(thisArg, args);") + public static native R apply(JSValue fun, Q thisArg, T... args); + + @JS.Coerce + @JS("return it.toString();") + private static native String objToString(Object it); + + @JS(value = """ + const arr = []; + for (let i = 0; i < values.length; i++) { + arr.push(values[i]); + } + return arr; + """) + public static native JSObject createArray(Object... values); +} diff --git a/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSStringTest.java b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSStringTest.java new file mode 100644 index 000000000000..6cf665ba6064 --- /dev/null +++ b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSStringTest.java @@ -0,0 +1,537 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.webimage.jtt.api; + +import org.graalvm.webimage.api.JS; +import org.graalvm.webimage.api.JSObject; +import org.graalvm.webimage.api.JSString; +import org.graalvm.webimage.api.JSUndefined; +import org.graalvm.webimage.api.JSValue; + +import java.util.Map; +import java.util.function.Function; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class JSStringTest { + + public static final String[] OUTPUT = new String[]{}; + + private static final String HELLO_STRING = "Hello"; + private static final String WORLD_STRING = "World"; + private static final String HELLO_WORLD_STRING = "Hello World"; + private static final String JAVASCRIPT_STRING = "JavaScript"; + private static final int[] ARROWS_CODE_POINTS = new int[]{0x2190, 0x2191, 0x2192, 0x2193}; + private static final int[] MATH_CODE_POINTS = new int[]{0x2211, 0x221A, 0x03C0, 0x221E}; + private static final int[] CURRENCY_CODE_POINTS = new int[]{0x20AC, 0x00A5, 0x20B9, 0x0024}; + private static final String LONG_TEXT = "The quick brown fox jumps over the lazy dog."; + + public static void main(String[] args) { + testAt(); + testCharAt(); + testCharCodeAt(); + testCodePointAt(); + testConcat(); + testEndsWith(); + testIncludes(); + testFromCharCode(); + testFromCodePoint(); + testIndexOf(); + testLastIndexOf(); + testIsWellFormed(); + testLength(); + testLocaleCompare(); + testMatchAll(); + testMatch(); + testNormalize(); + testPadEnd(); + testPadStart(); + testRaw(); + testRepeat(); + testReplace(); + testReplaceAll(); + testSearch(); + testSlice(); + testSplit(); + testStartsWith(); + testToLocaleLowerCase(); + testToLocaleUpperCase(); + testToLowerCase(); + testToUpperCase(); + testToWellFormed(); + testTrim(); + testValueOf(); + } + + public static void testAt() { + JSString helloString = JSString.of(HELLO_STRING); + JSString javaScriptString = JSString.of(JAVASCRIPT_STRING); + JSString arrowsString = JSString.fromCodePoint(ARROWS_CODE_POINTS); + JSString mathString = JSString.fromCodePoint(MATH_CODE_POINTS); + JSString currencyString = JSString.fromCodePoint(CURRENCY_CODE_POINTS); + + assertEquals("H", helloString.at(0).as(String.class)); + assertEquals("o", helloString.at(4).as(String.class)); + assertEquals(JSUndefined.undefined(), JSValue.checkedCoerce(helloString.at(10), JSUndefined.class)); + assertEquals("o", helloString.at(-1).as(String.class)); + assertEquals("S", javaScriptString.at(4).as(String.class)); + assertEquals("i", javaScriptString.at(-3).as(String.class)); + assertEquals("\u2191", arrowsString.at(1).as(String.class)); + assertEquals("\u2192", arrowsString.at(-2).as(String.class)); + assertEquals("\u2211", mathString.at(0).as(String.class)); + assertEquals("\u221E", mathString.at(3).as(String.class)); + assertEquals("\u20B9", currencyString.at(2).as(String.class)); + assertEquals("\u0024", currencyString.at(-1).as(String.class)); + } + + public static void testCharAt() { + JSString helloString = JSString.of(HELLO_STRING); + JSString javaScriptString = JSString.of(JAVASCRIPT_STRING); + JSString arrowsString = JSString.fromCodePoint(ARROWS_CODE_POINTS); + JSString mathString = JSString.fromCodePoint(MATH_CODE_POINTS); + JSString currencyString = JSString.fromCodePoint(CURRENCY_CODE_POINTS); + + assertEquals("H", helloString.charAt(0).as(String.class)); + assertEquals("o", helloString.charAt(4).as(String.class)); + assertEquals("", helloString.charAt(-1).as(String.class)); + assertEquals("S", javaScriptString.charAt(4).as(String.class)); + assertEquals("t", javaScriptString.charAt(9).as(String.class)); + assertEquals("", javaScriptString.charAt(10).as(String.class)); + assertEquals("\u2190", arrowsString.charAt(0).as(String.class)); + assertEquals("\u2192", arrowsString.charAt(2).as(String.class)); + assertEquals("\u221A", mathString.charAt(1).as(String.class)); + assertEquals("\u221E", mathString.charAt(3).as(String.class)); + assertEquals("\u20B9", currencyString.charAt(2).as(String.class)); + assertEquals("\u0024", currencyString.charAt(3).as(String.class)); + } + + public static void testCharCodeAt() { + JSString helloString = JSString.of(HELLO_STRING); + JSString arrowsString = JSString.fromCodePoint(ARROWS_CODE_POINTS); + JSString mathString = JSString.fromCodePoint(MATH_CODE_POINTS); + JSString currencyString = JSString.fromCodePoint(CURRENCY_CODE_POINTS); + + assertEquals(72, helloString.charCodeAt(0)); + assertEquals(111, helloString.charCodeAt(4)); + assertEquals(-1, helloString.charCodeAt(-1)); + assertEquals(-1, helloString.charCodeAt(10)); + assertEquals(8592, arrowsString.charCodeAt(0)); + assertEquals(8594, arrowsString.charCodeAt(2)); + assertEquals(8730, mathString.charCodeAt(1)); + assertEquals(8734, mathString.charCodeAt(3)); + assertEquals(8377, currencyString.charCodeAt(2)); + assertEquals(36, currencyString.charCodeAt(3)); + } + + public static void testCodePointAt() { + JSString helloString = JSString.of(HELLO_STRING); + JSString arrowsString = JSString.fromCodePoint(ARROWS_CODE_POINTS); + JSString mathString = JSString.fromCodePoint(MATH_CODE_POINTS); + JSString currencyString = JSString.fromCodePoint(CURRENCY_CODE_POINTS); + + assertEquals(72, helloString.codePointAt(0)); + assertEquals(111, helloString.codePointAt(4)); + assertEquals(-1, helloString.codePointAt(-1)); + assertEquals(-1, helloString.codePointAt(10)); + assertEquals(8592, arrowsString.codePointAt(0)); + assertEquals(8594, arrowsString.codePointAt(2)); + assertEquals(8730, mathString.codePointAt(1)); + assertEquals(8734, mathString.codePointAt(3)); + assertEquals(8377, currencyString.codePointAt(2)); + assertEquals(36, currencyString.codePointAt(3)); + } + + public static void testConcat() { + JSString comma = JSString.of(", "); + JSString exclaim = JSString.of("!"); + JSString label = JSString.of("Arrows: "); + JSString mathLabel = JSString.of("Math: "); + JSString helloString = JSString.of(HELLO_STRING); + JSString worldString = JSString.of(WORLD_STRING); + JSString arrowsString = JSString.fromCodePoint(ARROWS_CODE_POINTS); + JSString mathString = JSString.fromCodePoint(MATH_CODE_POINTS); + + assertEquals("Hello, World!", helloString.concat(comma, worldString, exclaim).as(String.class)); + assertEquals("Arrows: \u2190\u2191\u2192\u2193", label.concat(arrowsString).as(String.class)); + assertEquals("Math: \u2211\u221A\u03C0\u221E", mathLabel.concat(mathString).as(String.class)); + } + + public static void testEndsWith() { + JSString worldString = JSString.of(WORLD_STRING); + JSString helloWorldString = JSString.of(HELLO_WORLD_STRING); + JSString arrowsString = JSString.fromCodePoint(ARROWS_CODE_POINTS); + JSString mathString = JSString.fromCodePoint(MATH_CODE_POINTS); + + assertTrue(helloWorldString.endsWith(worldString)); + assertFalse(helloWorldString.endsWith("world")); + assertFalse(helloWorldString.endsWith(HELLO_STRING)); + assertTrue(helloWorldString.endsWith(HELLO_STRING, 5)); + assertTrue(arrowsString.endsWith(JSString.fromCodePoint(0x2192, 0x2193))); + assertTrue(arrowsString.endsWith(JSString.fromCodePoint(0x2191), 2)); + assertTrue(mathString.endsWith(JSString.fromCodePoint(0x03C0, 0x221E))); + assertTrue(mathString.endsWith(JSString.fromCodePoint(0x221A), 2)); + assertFalse(mathString.endsWith(JSString.fromCodePoint(0x221E), 3)); + } + + public static void testIncludes() { + JSString worldString = JSString.of(WORLD_STRING); + JSString helloWorldString = JSString.of(HELLO_WORLD_STRING); + JSString arrowsString = JSString.fromCodePoint(ARROWS_CODE_POINTS); + JSString mathString = JSString.fromCodePoint(MATH_CODE_POINTS); + + assertTrue(helloWorldString.includes(worldString)); + assertFalse(helloWorldString.includes("world")); + assertTrue(helloWorldString.includes("lo")); + assertFalse(helloWorldString.includes("lo", 5)); + assertTrue(arrowsString.includes(JSString.fromCodePoint(0x2191))); + assertTrue(arrowsString.includes(JSString.fromCodePoint(0x2191), 1)); + assertTrue(mathString.includes(JSString.fromCodePoint(0x221A))); + assertTrue(mathString.includes(JSString.fromCodePoint(0x03C0), 2)); + } + + public static void testFromCharCode() { + assertEquals("", JSString.fromCharCode().as(String.class)); + assertEquals("A", JSString.fromCharCode(65).as(String.class)); + assertEquals("Hello", JSString.fromCharCode(72, 101, 108, 108, 111).as(String.class)); + assertEquals("\u0024\u00A9\u00AE", JSString.fromCharCode(36, 169, 174).as(String.class)); + assertEquals("\uD83D\uDE00", JSString.fromCharCode(0xD83D, 0xDE00).as(String.class)); + } + + public static void testFromCodePoint() { + assertEquals("", JSString.fromCodePoint().as(String.class)); + assertEquals("A", JSString.fromCodePoint(65).as(String.class)); + assertEquals("Hello", JSString.fromCodePoint(72, 101, 108, 108, 111).as(String.class)); + assertEquals("\u0024\u00A9\u00AE", JSString.fromCodePoint(36, 169, 174).as(String.class)); + assertEquals("\uD83D\uDE00", JSString.fromCodePoint(0x1F600).as(String.class)); + assertEquals("\u03A9\uD83D\uDE80", JSString.fromCodePoint(0x03A9, 0x1F680).as(String.class)); + } + + public static void testIndexOf() { + JSString helloWorldString = JSString.of(HELLO_WORLD_STRING); + JSString arrowsString = JSString.fromCodePoint(ARROWS_CODE_POINTS); + JSString mathString = JSString.fromCodePoint(MATH_CODE_POINTS); + + assertEquals(6, helloWorldString.indexOf(WORLD_STRING)); + assertEquals(-1, helloWorldString.indexOf("world")); + assertEquals(2, helloWorldString.indexOf("l")); + assertEquals(9, helloWorldString.indexOf("l", 4)); + assertEquals(2, helloWorldString.indexOf("l"), -4); + assertEquals(1, arrowsString.indexOf(JSString.fromCodePoint(0x2191))); + assertEquals(2, arrowsString.indexOf(JSString.fromCodePoint(0x2192), 2)); + assertEquals(2, mathString.indexOf(JSString.fromCodePoint(0x03C0))); + assertEquals(1, mathString.indexOf(JSString.fromCodePoint(0x221A), 1)); + } + + public static void testLastIndexOf() { + JSString phrase1 = JSString.of(HELLO_STRING + " " + HELLO_STRING); + JSString phrase2 = JSString.fromCodePoint(0x2190, 0x2191, 0x2192, 0x2193, 0x2190, 0x2191, 0x2192, 0x2193); + + assertEquals(6, phrase1.lastIndexOf(HELLO_STRING)); + assertEquals(0, phrase1.lastIndexOf(HELLO_STRING, 5)); + assertEquals(0, phrase1.lastIndexOf(HELLO_STRING, -5)); + assertEquals(9, phrase1.lastIndexOf("lo")); + assertEquals(6, phrase2.lastIndexOf(JSString.fromCodePoint(0x2192))); + assertEquals(6, phrase2.lastIndexOf(JSString.fromCodePoint(0x2192), 6)); + } + + public static void testIsWellFormed() { + JSString helloWorldString = JSString.of(HELLO_WORLD_STRING); + JSString mathString = JSString.fromCodePoint(MATH_CODE_POINTS); + + JSString highPlusAscii = JSString.fromCharCode(0xD800, 0x0041); + JSString lowPlusAscii = JSString.fromCharCode(0xDC00, 0x0042); + JSString reversedPair = JSString.fromCharCode(0xDC00, 0xD800); + + assertTrue(helloWorldString.isWellFormed()); + assertTrue(mathString.isWellFormed()); + assertFalse(highPlusAscii.isWellFormed()); + assertFalse(lowPlusAscii.isWellFormed()); + assertFalse(reversedPair.isWellFormed()); + } + + public static void testLength() { + JSString text = JSString.of("Life, the universe and everything. Answer:"); + + assertEquals(42, text.length()); + } + + public static void testLocaleCompare() { + JSString a = JSString.of("a"); + JSString a2 = JSString.of("A"); + JSString a3 = JSString.of("\u00E4"); + JSString a4 = JSString.of("ae"); + Map caseSensitive = Map.of("sensitivity", "case"); + Map accentSensitive = Map.of("sensitivity", "accent"); + + assertEquals(1, a2.localeCompare("a")); + assertEquals(-1, a.localeCompare("A")); + assertEquals(0, a2.localeCompare("A")); + assertEquals(-1, a3.localeCompare("ae", "de")); + assertEquals(-1, a.localeCompare("A", "en", caseSensitive)); + assertEquals(1, a2.localeCompare("a", "en", caseSensitive)); + assertEquals(-1, a.localeCompare(a2)); + assertEquals(-1, a3.localeCompare(a4, JSString.of("sv"))); + assertEquals(-1, a3.localeCompare("a", "en", accentSensitive)); + assertEquals(1, a.localeCompare(a3, JSString.of("en"), accentSensitive)); + assertEquals(0, a3.localeCompare("\u00E4", "en", accentSensitive)); + } + + public static void testMatchAll() { + JSString phrase = JSString.of("Price: \u002412, Discount: \u00245, Tax: \u00242"); + JSObject iterator = phrase.matchAll(eval("/\\\u0024(\\d+)/g")); + String result = iteratorToString(iterator); + + assertEquals("\u002412,12,\u00245,5,\u00242,2", result); + } + + public static void testMatch() { + JSString phrase = JSString.of("Hello 123 World 456"); + JSString mixed = JSString.of("Hello 123 World ABC xyz"); + + String result1 = objToString(phrase.match("World")); + String result2 = objToString(mixed.match(eval("/[A-Z]/g"))); + String result3 = objToString(phrase.match("\\d+")); + String result4 = objToString(phrase.match(eval("/\\d+/g"))); + + assertEquals("World", result1); + assertEquals("H,W,A,B,C", result2); + assertEquals("123", result3); + assertEquals("123,456", result4); + assertNull(phrase.match("XYZ")); + } + + public static void testNormalize() { + JSString composed = JSString.fromCodePoint(0x00E9); + JSString decomposed = JSString.fromCodePoint(0x0065, 0x0301); + JSString fullWidth = JSString.fromCodePoint(0xFF21); + + assertEquals("\u00E9", composed.as(String.class)); + assertEquals("e\u0301", decomposed.as(String.class)); + assertEquals("\u00E9", decomposed.normalize().as(String.class)); + assertEquals("e\u0301", decomposed.normalize("NFD").as(String.class)); + assertEquals("e\u0301", composed.normalize("NFD").as(String.class)); + assertEquals("\u00E9", composed.normalize("NFC").as(String.class)); + assertEquals("\u00E9", decomposed.normalize("NFC").as(String.class)); + assertEquals("\uFF21", fullWidth.as(String.class)); + assertEquals("A", fullWidth.normalize("NFKC").as(String.class)); + } + + public static void testPadEnd() { + JSString base = JSString.of("Hi"); + + assertEquals("Hi ", base.padEnd(5).as(String.class)); + assertEquals("Hi***", base.padEnd(5, "*").as(String.class)); + assertEquals("Hi*****", base.padEnd(7, JSString.of("*")).as(String.class)); + } + + public static void testPadStart() { + JSString base = JSString.of("Hi"); + + assertEquals(" Hi", base.padStart(5).as(String.class)); + assertEquals("---Hi", base.padStart(5, "-").as(String.class)); + assertEquals("-----Hi", base.padStart(7, JSString.of("-")).as(String.class)); + } + + public static void testRaw() { + JSObject template = JSObject.create(); + template.set("raw", new String[]{"Line1\n", "Line2\t", "End"}); + + assertEquals("Line1\nLine2\tEnd", JSString.raw(template).as(String.class)); + assertEquals("Line1\nALine2\tBEnd", JSString.raw(template, "A", "B").as(String.class)); + } + + public static void testRepeat() { + JSString base = JSString.of("Echo"); + JSString helloString = JSString.of(HELLO_STRING); + + assertEquals("EchoEchoEcho", base.repeat(3).as(String.class)); + assertEquals("HelloHelloHelloHello", helloString.repeat(4).as(String.class)); + } + + public static void testReplace() { + JSString phrase = JSString.of("foo bar foo"); + JSString digits = JSString.of("Price: 42"); + JSString helloWorldString = JSString.of(HELLO_WORLD_STRING); + Object replacer1 = eval("(match) => '[' + match + ']'"); + JSValue replacer2 = fromJavaFunction((JSString match) -> JSString.of("(" + match.asString() + ")")); + + assertEquals("baz bar foo", phrase.replace("foo", "baz").as(String.class)); + assertEquals("baz bar foo", phrase.replace(eval("/foo/"), "baz").as(String.class)); + assertEquals("World, Hello", helloWorldString.replace(eval("/(\\w+) (\\w+)/"), "\u00242, \u00241").as(String.class)); + assertEquals("Price: [42]", digits.replace(eval("/\\d+/"), replacer1).as(String.class)); + assertEquals("Price: (42)", digits.replace(eval("/\\d+/"), replacer2).as(String.class)); + assertEquals("foo bar foo", phrase.replace("xyz", "baz").as(String.class)); + assertEquals("123 bar foo", phrase.replace("foo", 123).as(String.class)); + + JSString multi = JSString.of("foo bar foo"); + assertEquals("baz bar baz", multi.replace(eval("/foo/g"), "baz").as(String.class)); + } + + public static void testReplaceAll() { + JSString multi = JSString.of("foo bar foo"); + + assertEquals("baz bar baz", multi.replace(eval("/foo/g"), "baz").as(String.class)); + } + + public static void testSearch() { + JSString text = JSString.of("Find 42 here"); + + assertEquals(5, text.search(eval("/\\d+/"))); + assertEquals(0, text.search("Find")); + assertEquals(-1, text.search(eval("/find/"))); + assertEquals(0, text.search(eval("/find/i"))); + assertEquals(-1, text.search(eval("/^42/"))); + assertEquals(-1, text.search("XYZ")); + } + + public static void testSlice() { + JSString longText = JSString.of(LONG_TEXT); + + assertEquals("the lazy dog.", longText.slice(31).as(String.class)); + assertEquals("dog.", longText.slice(-4).as(String.class)); + assertEquals("quick brown fox", longText.slice(4, 19).as(String.class)); + assertEquals("lazy", longText.slice(-9, -5).as(String.class)); + } + + public static void testSplit() { + JSString csv = JSString.of("red,green,blue,yellow"); + JSObject regexObject = eval("/,/"); + + String result1 = objToString(csv.split(",")); + String result2 = objToString(csv.split(",", 2)); + String result3 = objToString(csv.split(regexObject)); + String result4 = objToString(csv.split(regexObject, 2)); + String result5 = objToString(csv.split("")); + + assertEquals("red,green,blue,yellow", result1); + assertEquals("red,green", result2); + assertEquals("red,green,blue,yellow", result3); + assertEquals("red,green", result4); + assertEquals("r,e,d,,,g,r,e,e,n,,,b,l,u,e,,,y,e,l,l,o,w", result5); + } + + public static void testStartsWith() { + JSString text = JSString.of("To be, or not to be, that is the question."); + + assertTrue(text.startsWith("To be")); + assertFalse(text.startsWith("to be")); + assertTrue(text.startsWith(JSString.of("To be"))); + assertFalse(text.startsWith(JSString.of("question"))); + assertTrue(text.startsWith("not", 10)); + assertFalse(text.startsWith("To", 3)); + assertTrue(text.startsWith(JSString.of("not"), 10)); + assertFalse(text.startsWith(JSString.of("To"), 3)); + assertFalse(text.startsWith("To", 100)); + assertFalse(text.startsWith(JSString.of("To"), 100)); + } + + public static void testToLocaleLowerCase() { + JSString turkish = JSString.fromCodePoint(0x0130).concat(JSString.of("stanbul")); + JSString english = JSString.of("HELLO WORLD"); + + assertEquals("i\u0307stanbul", turkish.toLocaleLowerCase().as(String.class)); + assertEquals("istanbul", turkish.toLocaleLowerCase("tr").as(String.class)); + assertEquals("istanbul", turkish.toLocaleLowerCase(JSString.of("tr")).as(String.class)); + assertEquals("hello world", english.toLocaleLowerCase().as(String.class)); + assertEquals("hello world", english.toLocaleLowerCase("en").as(String.class)); + assertEquals("hello world", english.toLocaleLowerCase(JSString.of("en")).as(String.class)); + } + + public static void testToLocaleUpperCase() { + JSString turkish = JSString.fromCodePoint(0x0069).concat(JSString.of("stanbul")); + JSString english = JSString.of("hello world"); + + assertEquals("ISTANBUL", turkish.toLocaleUpperCase().as(String.class)); + assertEquals("\u0130STANBUL", turkish.toLocaleUpperCase("tr").as(String.class)); + assertEquals("\u0130STANBUL", turkish.toLocaleUpperCase(JSString.of("tr")).as(String.class)); + assertEquals("HELLO WORLD", english.toLocaleUpperCase().as(String.class)); + assertEquals("HELLO WORLD", english.toLocaleUpperCase("en").as(String.class)); + assertEquals("HELLO WORLD", english.toLocaleUpperCase(JSString.of("en")).as(String.class)); + } + + public static void testToLowerCase() { + JSString longText = JSString.of(LONG_TEXT); + + assertEquals("the quick brown fox jumps over the lazy dog.", longText.toLowerCase().as(String.class)); + } + + public static void testToUpperCase() { + JSString longText = JSString.of(LONG_TEXT); + + assertEquals("THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.", longText.toUpperCase().as(String.class)); + } + + public static void testToWellFormed() { + JSString str1 = JSString.of("ab").concat(JSString.fromCodePoint(0xD800)); + JSString str2 = str1.concat(JSString.of("c")); + JSString str3 = JSString.fromCodePoint(0xDFFF).concat(JSString.of("ab")); + JSString str4 = JSString.of("c").concat(JSString.fromCodePoint(0xDFFF)).concat(JSString.of("ab")); + JSString str5 = JSString.of("abc"); + JSString str6 = JSString.of("ab").concat(JSString.fromCodePoint(0x1F604)).concat(JSString.of("c")); + + assertEquals("ab\uFFFD", str1.toWellFormed().as(String.class)); + assertEquals("ab\uFFFDc", str2.toWellFormed().as(String.class)); + assertEquals("\uFFFDab", str3.toWellFormed().as(String.class)); + assertEquals("c\uFFFDab", str4.toWellFormed().as(String.class)); + assertEquals("abc", str5.toWellFormed().as(String.class)); + assertEquals("ab\uD83D\uDE04c", str6.toWellFormed().as(String.class)); + } + + public static void testTrim() { + JSString padded = JSString.of(" To be, or not to be "); + + assertEquals("To be, or not to be", padded.trim().as(String.class)); + assertEquals("To be, or not to be ", padded.trimStart().as(String.class)); + assertEquals(" To be, or not to be", padded.trimEnd().as(String.class)); + assertEquals("To be, or not to be ", padded.trimLeft().as(String.class)); + assertEquals(" To be, or not to be", padded.trimRight().as(String.class)); + } + + public static void testValueOf() { + JSString text = JSString.of("To be, or not to be"); + + assertEquals("To be, or not to be", text.valueOf().as(String.class)); + } + + @JS.Coerce + @JS(value = "return eval(script);") + private static native JSObject eval(String script); + + @JS.Coerce + @JS("return Array.from(it).toString();") + private static native String iteratorToString(Object it); + + @JS.Coerce + @JS("return it.toString();") + private static native String objToString(Object it); + + @JS.Coerce + @JS(value = "return function(args) { return javaFunc.apply(args); }") + public static native JSValue fromJavaFunction(Function javaFunc); +} diff --git a/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSSymbolTest.java b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSSymbolTest.java new file mode 100644 index 000000000000..74e613b60bae --- /dev/null +++ b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSSymbolTest.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.webimage.jtt.api; + +import org.graalvm.webimage.api.JS; +import org.graalvm.webimage.api.JSSymbol; +import org.graalvm.webimage.api.JSValue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertTrue; + +public class JSSymbolTest { + + public static void main(String[] args) { + testAsyncDispose(); + testAsyncIterator(); + testForKey(); + testDispose(); + testEquality(); + testHasInstance(); + testIsSameSymbol(); + testIterator(); + testKeyFor(); + testMatchAll(); + testMatch(); + testReplace(); + testSearch(); + testSpecies(); + testSplit(); + testToPrimitive(); + testToStringTag(); + testUnscopables(); + testValueOf(); + } + + public static void testAsyncDispose() { + JSSymbol sym = JSSymbol.asyncDispose(); + + assertEquals("JavaScript ", sym.toString()); + assertEquals(JSSymbol.class, sym.getClass()); + assertEquals("nodejs.asyncDispose", JSSymbol.description(sym)); + } + + public static void testAsyncIterator() { + JSSymbol sym = JSSymbol.asyncIterator(); + + assertEquals("JavaScript ", sym.toString()); + assertEquals(JSSymbol.class, sym.getClass()); + assertEquals("Symbol.asyncIterator", JSSymbol.description(sym)); + } + + public static void testForKey() { + JSSymbol sym = JSSymbol.forKey("alpha"); + + assertEquals("JavaScript ", sym.toString()); + assertEquals("alpha", JSSymbol.description(sym)); + } + + public static void testDispose() { + JSSymbol sym = JSSymbol.dispose(); + + assertEquals("JavaScript ", sym.toString()); + assertEquals(JSSymbol.class, sym.getClass()); + assertEquals("nodejs.dispose", JSSymbol.description(sym)); + } + + public static void testEquality() { + JSSymbol sym1 = JSSymbol.forKey("shared"); + JSSymbol sym2 = JSSymbol.forKey("shared"); + JSSymbol sym3 = JSSymbol.forKey("unique"); + + assertEquals(sym1, sym2); + assertNotEquals(sym1, sym3); + } + + public static void testHasInstance() { + JSSymbol sym = JSSymbol.hasInstance(); + + assertEquals("JavaScript ", sym.toString()); + assertEquals(JSSymbol.class, sym.getClass()); + assertEquals("Symbol.hasInstance", JSSymbol.description(sym)); + } + + public static void testIsSameSymbol() { + JSSymbol sym1 = JSSymbol.forKey("shared"); + JSSymbol sym2 = JSSymbol.forKey("shared"); + JSSymbol sym3 = JSSymbol.forKey("unique"); + + assertTrue(JSSymbol.isSameSymbol(sym1, sym2)); + assertFalse(JSSymbol.isSameSymbol(sym1, sym3)); + } + + public static void testIterator() { + JSSymbol sym = JSSymbol.iterator(); + + assertEquals("JavaScript ", sym.toString()); + assertEquals(JSSymbol.class, sym.getClass()); + assertEquals("Symbol.iterator", JSSymbol.description(sym)); + } + + public static void testKeyFor() { + JSSymbol shared1 = JSSymbol.forKey("alpha"); + JSSymbol shared2 = JSSymbol.forKey("beta"); + String result1 = JSValue.checkedCoerce(JSSymbol.keyFor(shared1), String.class); + String result2 = JSValue.checkedCoerce(JSSymbol.keyFor(shared2), String.class); + JSSymbol local = createLocalSymbol("gamma"); + JSValue result3 = JSSymbol.keyFor(local); + + assertEquals("alpha", result1); + assertEquals("beta", result2); + assertEquals(JSValue.undefined(), result3); + } + + public static void testMatchAll() { + JSSymbol sym = JSSymbol.matchAll(); + + assertEquals("JavaScript ", sym.toString()); + assertEquals(JSSymbol.class, sym.getClass()); + assertEquals("Symbol.matchAll", JSSymbol.description(sym)); + } + + public static void testMatch() { + JSSymbol sym = JSSymbol.match(); + + assertEquals("JavaScript ", sym.toString()); + assertEquals(JSSymbol.class, sym.getClass()); + assertEquals("Symbol.match", JSSymbol.description(sym)); + } + + public static void testReplace() { + JSSymbol sym = JSSymbol.replace(); + + assertEquals("JavaScript ", sym.toString()); + assertEquals(JSSymbol.class, sym.getClass()); + assertEquals("Symbol.replace", JSSymbol.description(sym)); + } + + public static void testSearch() { + JSSymbol sym = JSSymbol.search(); + + assertEquals("JavaScript ", sym.toString()); + assertEquals(JSSymbol.class, sym.getClass()); + assertEquals("Symbol.search", JSSymbol.description(sym)); + } + + public static void testSpecies() { + JSSymbol sym = JSSymbol.species(); + + assertEquals("JavaScript ", sym.toString()); + assertEquals(JSSymbol.class, sym.getClass()); + assertEquals("Symbol.species", JSSymbol.description(sym)); + } + + public static void testSplit() { + JSSymbol sym = JSSymbol.split(); + + assertEquals("JavaScript ", sym.toString()); + assertEquals(JSSymbol.class, sym.getClass()); + assertEquals("Symbol.split", JSSymbol.description(sym)); + } + + public static void testToPrimitive() { + JSSymbol sym = JSSymbol.toPrimitive(); + + assertEquals("JavaScript ", sym.toString()); + assertEquals(JSSymbol.class, sym.getClass()); + assertEquals("Symbol.toPrimitive", JSSymbol.description(sym)); + } + + public static void testToStringTag() { + JSSymbol sym = JSSymbol.toStringTag(); + + assertEquals("JavaScript ", sym.toString()); + assertEquals(JSSymbol.class, sym.getClass()); + assertEquals("Symbol.toStringTag", JSSymbol.description(sym)); + } + + public static void testUnscopables() { + JSSymbol sym = JSSymbol.unscopables(); + + assertEquals("JavaScript ", sym.toString()); + assertEquals(JSSymbol.class, sym.getClass()); + assertEquals("Symbol.unscopables", JSSymbol.description(sym)); + } + + public static void testValueOf() { + JSSymbol original = JSSymbol.forKey("alpha"); + JSSymbol value = JSSymbol.valueOf(original); + + assertEquals("JavaScript ", original.toString()); + assertEquals("JavaScript ", value.toString()); + assertNotSame(original, value); + assertEquals("alpha", JSSymbol.description(value)); + } + + @JS.Coerce + @JS(value = "return Symbol(desc);") + private static native JSSymbol createLocalSymbol(String desc); +} diff --git a/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JavaProxyTest.java b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JavaProxyTest.java index 32978311ccdb..01e7dbd88878 100644 --- a/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JavaProxyTest.java +++ b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JavaProxyTest.java @@ -28,7 +28,7 @@ import java.util.function.Function; import org.graalvm.webimage.api.JS; -import org.graalvm.webimage.api.JSError; +import org.graalvm.webimage.api.ThrownFromJavaScript; import org.graalvm.webimage.api.JSValue; import com.oracle.svm.core.NeverInline; @@ -123,7 +123,7 @@ private static void expectJSError(Runnable r, String name) { try { r.run(); System.out.println("ERROR: Expected JS error for " + name); - } catch (JSError jsError) { + } catch (ThrownFromJavaScript thrownFromJavaScript) { System.out.println("Caught JS error for " + name); } } diff --git a/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/testdispatcher/JSAnnotationTests.java b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/testdispatcher/JSAnnotationTests.java index 627efa5958ea..9c69b2dda511 100644 --- a/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/testdispatcher/JSAnnotationTests.java +++ b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/testdispatcher/JSAnnotationTests.java @@ -29,10 +29,14 @@ import com.oracle.svm.webimage.jtt.api.CoercionConversionTest; import com.oracle.svm.webimage.jtt.api.HtmlApiExamplesTest; import com.oracle.svm.webimage.jtt.api.JSErrorsTest; +import com.oracle.svm.webimage.jtt.api.JSNumberTest; import com.oracle.svm.webimage.jtt.api.JSObjectConversionTest; import com.oracle.svm.webimage.jtt.api.JSObjectSubclassTest; +import com.oracle.svm.webimage.jtt.api.JSObjectTest; import com.oracle.svm.webimage.jtt.api.JSPrimitiveConversionTest; import com.oracle.svm.webimage.jtt.api.JSRawCallTest; +import com.oracle.svm.webimage.jtt.api.JSStringTest; +import com.oracle.svm.webimage.jtt.api.JSSymbolTest; import com.oracle.svm.webimage.jtt.api.JavaDocExamplesTest; import com.oracle.svm.webimage.jtt.api.JavaProxyConversionTest; import com.oracle.svm.webimage.jtt.api.JavaProxyTest; @@ -61,6 +65,14 @@ public static void main(String[] args) { JSErrorsTest.main(remainingArgs); } else if (checkClass(HtmlApiExamplesTest.class, className)) { HtmlApiExamplesTest.main(remainingArgs); + } else if (checkClass(JSNumberTest.class, className)) { + JSNumberTest.main(null); + } else if (checkClass(JSStringTest.class, className)) { + JSStringTest.main(null); + } else if (checkClass(JSSymbolTest.class, className)) { + JSSymbolTest.main(null); + } else if (checkClass(JSObjectTest.class, className)) { + JSObjectTest.main(null); } else { throw new IllegalArgumentException("unexpected class name"); } diff --git a/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/JSExceptionSupport.java b/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/JSExceptionSupport.java index 01de3eaf12be..c07015b4f35f 100644 --- a/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/JSExceptionSupport.java +++ b/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/JSExceptionSupport.java @@ -28,7 +28,7 @@ import static com.oracle.svm.webimage.substitute.system.Target_java_lang_Throwable_Web.CAUSE_CAPTION; import static com.oracle.svm.webimage.substitute.system.Target_java_lang_Throwable_Web.SUPPRESSED_CAPTION; -import org.graalvm.webimage.api.JSError; +import org.graalvm.webimage.api.ThrownFromJavaScript; import org.graalvm.webimage.api.JSObject; import org.graalvm.webimage.api.JSString; import org.graalvm.webimage.api.JSValue; @@ -121,8 +121,8 @@ public static void printStackTrace(Target_java_lang_Throwable_Web t, Printer s, * If the throwable is a JSError, the thrown JS object is implicitly a cause (if it is an * Error object). In that case, we also print the JS stack trace if available. */ - if (SubstrateUtil.cast(t, Throwable.class) instanceof JSError jsError) { - Object thrownObject = jsError.getThrownObject(); + if (SubstrateUtil.cast(t, Throwable.class) instanceof ThrownFromJavaScript thrownFromJavaScript) { + Object thrownObject = thrownFromJavaScript.getThrownObject(); if (thrownObject instanceof JSObject jsObject && jsObject.get("stack") instanceof JSValue stack) { s.println(prefix + "Caused by JS Error: " + t.getMessage()); if (stack instanceof JSString jsString) { diff --git a/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/functionintrinsics/JSConversion.java b/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/functionintrinsics/JSConversion.java index 647adab05891..5802a15e9f67 100644 --- a/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/functionintrinsics/JSConversion.java +++ b/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/functionintrinsics/JSConversion.java @@ -33,7 +33,7 @@ import org.graalvm.webimage.api.JS; import org.graalvm.webimage.api.JSBigInt; import org.graalvm.webimage.api.JSBoolean; -import org.graalvm.webimage.api.JSError; +import org.graalvm.webimage.api.ThrownFromJavaScript; import org.graalvm.webimage.api.JSNumber; import org.graalvm.webimage.api.JSObject; import org.graalvm.webimage.api.JSString; @@ -674,11 +674,11 @@ public static T coerceJavaScriptToJava(Object x, Class target) { * Ensures that objects that are thrown in {@link JS}-annotated methods are a subclass of * {@link Throwable}. If a {@link Throwable} exception is thrown, it gets simply rethrown. * Otherwise, the exception object is converted to Java according ot the conversion rules - * specified by {@link JS} and wrapped into a {@link JSError}. + * specified by {@link JS} and wrapped into a {@link ThrownFromJavaScript}. * * @param excp thrown object. Due to JavaScript semantics this can be an arbitrary type. - * @throws Throwable the original {@link Throwable} or {@link JSError} which warps the converted - * thrown JavaScript object + * @throws Throwable the original {@link Throwable} or {@link ThrownFromJavaScript} which warps + * the converted thrown JavaScript object */ public static void handleJSError(Object excp) throws Throwable { if (JSExceptionSupport.isThrowable(excp)) { @@ -692,7 +692,7 @@ public static void handleJSError(Object excp) throws Throwable { throw t; } - throw new JSError(obj); + throw new ThrownFromJavaScript(obj); } } } diff --git a/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/wasmgc/WasmGCJSConversion.java b/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/wasmgc/WasmGCJSConversion.java index a013c5aa3a21..2bde9cd8d584 100644 --- a/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/wasmgc/WasmGCJSConversion.java +++ b/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/wasmgc/WasmGCJSConversion.java @@ -26,7 +26,7 @@ package com.oracle.svm.webimage.wasmgc; import org.graalvm.nativeimage.Platforms; -import org.graalvm.webimage.api.JSError; +import org.graalvm.webimage.api.ThrownFromJavaScript; import org.graalvm.webimage.api.JSValue; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; @@ -175,7 +175,7 @@ public static void throwExceptionFromJS(Object thrownObject) throws Throwable { throw t; } - throw new JSError(thrownObject); + throw new ThrownFromJavaScript(thrownObject); } @Override