From 6a09f181c7afa0844a6dbcf408c41dce6e974821 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Thu, 11 Apr 2024 20:29:57 +0200 Subject: [PATCH] js: add JSTopLevel annotation that allows to import top-level declarations --- .../org/teavm/backend/javascript/runtime.js | 4 +- .../java/org/teavm/jso/browser/Window.java | 46 ++++++++-------- .../src/main/java/org/teavm/jso/JSModule.java | 2 +- .../main/java/org/teavm/jso/JSTopLevel.java | 26 +++++++++ .../org/teavm/jso/impl/JSClassProcessor.java | 17 +++++- .../org/teavm/jso/impl/JSNativeInjector.java | 55 +++++++++++++++---- .../org/teavm/jso/impl/JSValueMarshaller.java | 52 ++++++++++++++++-- .../java/org/teavm/jso/test/CallTest.java | 30 ++++++++++ .../teavm/jso/test/ClassWithConstructor.java | 12 ++++ .../test/ClassWithConstructorInModule.java | 8 +++ .../org/teavm/jso/test/ImportClassTest.java | 16 ++++++ .../org/teavm/jso/test/ImportModuleTest.java | 8 +++ .../teavm/jso/test/TopLevelDeclarations.java | 36 ++++++++++++ .../teavm/jso/test/classWithConstructor.js | 8 ++- .../jso/test/classWithConstructorInModule.js | 8 ++- .../resources/org/teavm/jso/test/vararg.js | 8 +++ 16 files changed, 290 insertions(+), 46 deletions(-) create mode 100644 jso/core/src/main/java/org/teavm/jso/JSTopLevel.java create mode 100644 tests/src/test/java/org/teavm/jso/test/TopLevelDeclarations.java diff --git a/core/src/main/resources/org/teavm/backend/javascript/runtime.js b/core/src/main/resources/org/teavm/backend/javascript/runtime.js index d21bd7691..180c84bd1 100644 --- a/core/src/main/resources/org/teavm/backend/javascript/runtime.js +++ b/core/src/main/resources/org/teavm/backend/javascript/runtime.js @@ -108,4 +108,6 @@ let $rt_setThread = t => { } } -let $rt_apply = (instance, method, args) => instance[method].apply(instance, args); \ No newline at end of file +let $rt_apply = (instance, method, args) => instance[method].apply(instance, args); + +let $rt_apply_topLevel = (method, args) => method.apply(null, args); \ No newline at end of file diff --git a/jso/apis/src/main/java/org/teavm/jso/browser/Window.java b/jso/apis/src/main/java/org/teavm/jso/browser/Window.java index d87327c6e..814877102 100644 --- a/jso/apis/src/main/java/org/teavm/jso/browser/Window.java +++ b/jso/apis/src/main/java/org/teavm/jso/browser/Window.java @@ -15,9 +15,9 @@ */ package org.teavm.jso.browser; -import org.teavm.jso.JSBody; import org.teavm.jso.JSObject; import org.teavm.jso.JSProperty; +import org.teavm.jso.JSTopLevel; import org.teavm.jso.core.JSArray; import org.teavm.jso.core.JSArrayReader; import org.teavm.jso.dom.html.HTMLDocument; @@ -81,47 +81,47 @@ public abstract class Window implements JSObject, WindowEventTarget, StorageProv @JSProperty public abstract Window getTop(); - @JSBody(params = "message", script = "alert(message);") + @JSTopLevel public static native void alert(JSObject message); - @JSBody(params = "message", script = "alert(message);") + @JSTopLevel public static native void alert(String message); - @JSBody(params = "message", script = "return confirm(message);") + @JSTopLevel public static native boolean confirm(JSObject message); - @JSBody(params = "message", script = "return confirm(message);") + @JSTopLevel public static native boolean confirm(String message); public static String prompt(String message) { return prompt(message, ""); } - @JSBody(params = { "message", "defaultValue" }, script = "return prompt(message, defaultValue);") + @JSTopLevel public static native String prompt(String message, String defaultValue); - @JSBody(params = { "handler", "delay" }, script = "return setTimeout(handler, delay);") + @JSTopLevel public static native int setTimeout(TimerHandler handler, int delay); - @JSBody(params = { "handler", "delay" }, script = "return setTimeout(handler, delay);") + @JSTopLevel public static native int setTimeout(TimerHandler handler, double delay); - @JSBody(params = "timeoutId", script = "clearTimeout(timeoutId);") + @JSTopLevel public static native void clearTimeout(int timeoutId); - @JSBody(params = { "handler", "delay" }, script = "return setInterval(handler, delay);") + @JSTopLevel public static native int setInterval(TimerHandler handler, int delay); - @JSBody(params = { "handler", "delay" }, script = "return setInterval(handler, delay);") + @JSTopLevel public static native int setInterval(TimerHandler handler, double delay); - @JSBody(params = "timeoutId", script = "clearInterval(timeoutId);") + @JSTopLevel public static native void clearInterval(int timeoutId); - @JSBody(params = "callback", script = "return requestAnimationFrame(callback);") + @JSTopLevel public static native int requestAnimationFrame(AnimationFrameCallback callback); - @JSBody(params = "requestId", script = "cancelAnimationFrame(requestId);") + @JSTopLevel public static native void cancelAnimationFrame(int requestId); public abstract void blur(); @@ -170,30 +170,32 @@ public abstract class Window implements JSObject, WindowEventTarget, StorageProv postMessage(message, JSArray.of(transfer)); } - @JSBody(script = "return window;") + @JSTopLevel + @JSProperty("window") public static native Window current(); - @JSBody(script = "return self;") + @JSTopLevel + @JSProperty("self") public static native Window worker(); - @JSBody(params = "uri", script = "return encodeURI(uri);") + @JSTopLevel public static native String encodeURI(String uri); - @JSBody(params = "uri", script = "return encodeURIComponent(uri);") + @JSTopLevel public static native String encodeURIComponent(String uri); - @JSBody(params = "uri", script = "return decodeURI(uri);") + @JSTopLevel public static native String decodeURI(String uri); - @JSBody(params = "uri", script = "return decodeURIComponent(uri);") + @JSTopLevel public static native String decodeURIComponent(String uri); @JSProperty public abstract double getDevicePixelRatio(); - @JSBody(params = "s", script = "return atob(s);") + @JSTopLevel public static native String atob(String s); - @JSBody(params = "s", script = "return btoa(s);") + @JSTopLevel public static native String btoa(String s); } diff --git a/jso/core/src/main/java/org/teavm/jso/JSModule.java b/jso/core/src/main/java/org/teavm/jso/JSModule.java index 6715bd4cd..f648a3c03 100644 --- a/jso/core/src/main/java/org/teavm/jso/JSModule.java +++ b/jso/core/src/main/java/org/teavm/jso/JSModule.java @@ -21,7 +21,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) +@Target({ ElementType.TYPE, ElementType.METHOD }) public @interface JSModule { String value(); } diff --git a/jso/core/src/main/java/org/teavm/jso/JSTopLevel.java b/jso/core/src/main/java/org/teavm/jso/JSTopLevel.java new file mode 100644 index 000000000..08e235f34 --- /dev/null +++ b/jso/core/src/main/java/org/teavm/jso/JSTopLevel.java @@ -0,0 +1,26 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface JSTopLevel { +} diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java index 90ec61bf5..df0d8fb3a 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java @@ -39,6 +39,7 @@ import org.teavm.jso.JSClass; import org.teavm.jso.JSFunctor; import org.teavm.jso.JSObject; import org.teavm.jso.JSPrimitiveType; +import org.teavm.jso.JSTopLevel; import org.teavm.model.AnnotationContainerReader; import org.teavm.model.AnnotationHolder; import org.teavm.model.AnnotationReader; @@ -991,9 +992,19 @@ class JSClassProcessor { } private Variable getCallTarget(InvokeInstruction invoke) { - return invoke.getInstance() != null - ? invoke.getInstance() - : marshaller.classRef(invoke.getMethod().getClassName(), invoke.getLocation()); + if (invoke.getInstance() != null) { + return invoke.getInstance(); + } + var cls = classSource.get(invoke.getMethod().getClassName()); + var method = cls != null ? cls.getMethod(invoke.getMethod().getDescriptor()) : null; + var isTopLevel = (cls != null && cls.getAnnotations().get(JSTopLevel.class.getName()) != null) + || (method != null && method.getAnnotations().get(JSTopLevel.class.getName()) != null); + if (isTopLevel) { + var methodAnnotations = method != null ? method.getAnnotations() : null; + return marshaller.moduleRef(invoke.getMethod().getClassName(), methodAnnotations, invoke.getLocation()); + } else { + return marshaller.classRef(invoke.getMethod().getClassName(), invoke.getLocation()); + } } private boolean processConstructor(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) { diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeInjector.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeInjector.java index 9aa5faee9..4bf1cc02a 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeInjector.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeInjector.java @@ -63,19 +63,31 @@ public class JSNativeInjector implements Injector, DependencyPlugin { break; case "get": case "getPure": - context.writeExpr(context.getArgument(0), Precedence.MEMBER_ACCESS); - renderProperty(context.getArgument(1), context); + if (isNull(context.getArgument(0))) { + writer.append(extractPropertyName(context.getArgument(1))); + } else { + context.writeExpr(context.getArgument(0), Precedence.MEMBER_ACCESS); + renderProperty(context.getArgument(1), context); + } break; case "set": case "setPure": - context.writeExpr(context.getArgument(0), Precedence.ASSIGNMENT.next()); - renderProperty(context.getArgument(1), context); + if (isNull(context.getArgument(0))) { + writer.append(extractPropertyName(context.getArgument(1))); + } else { + context.writeExpr(context.getArgument(0), Precedence.MEMBER_ACCESS.next()); + renderProperty(context.getArgument(1), context); + } writer.ws().append('=').ws(); context.writeExpr(context.getArgument(2), Precedence.ASSIGNMENT.next()); break; case "invoke": - context.writeExpr(context.getArgument(0), Precedence.GROUPING); - renderProperty(context.getArgument(1), context); + if (isNull(context.getArgument(0))) { + writer.append(extractPropertyName(context.getArgument(1))); + } else { + context.writeExpr(context.getArgument(0), Precedence.GROUPING); + renderProperty(context.getArgument(1), context); + } writer.append('('); for (int i = 2; i < context.argumentCount(); ++i) { if (i > 2) { @@ -234,15 +246,30 @@ public class JSNativeInjector implements Injector, DependencyPlugin { } } + private static boolean isNull(Expr expr) { + if (expr instanceof ConstantExpr) { + var constantExpr = (ConstantExpr) expr; + if (constantExpr.getValue() == null) { + return true; + } + } + return false; + } + private void applyFunction(InjectorContext context) { if (tryApplyFunctionOptimized(context)) { return; } var writer = context.getWriter(); - writer.appendFunction("$rt_apply").append("("); - context.writeExpr(context.getArgument(0), Precedence.ASSIGNMENT); - writer.append(",").ws(); - context.writeExpr(context.getArgument(1), Precedence.ASSIGNMENT); + if (isNull(context.getArgument(0))) { + writer.appendFunction("$rt_apply_topLevel").append("("); + writer.append(extractPropertyName(context.getArgument(1))); + } else { + writer.appendFunction("$rt_apply").append("("); + context.writeExpr(context.getArgument(0), Precedence.ASSIGNMENT); + writer.append(",").ws(); + context.writeExpr(context.getArgument(1), Precedence.ASSIGNMENT); + } writer.append(",").ws(); context.writeExpr(context.getArgument(2), Precedence.ASSIGNMENT); writer.append(")"); @@ -323,8 +350,12 @@ public class JSNativeInjector implements Injector, DependencyPlugin { private void applyFunctionOptimized(InjectorContext context, List paramList) { var writer = context.getWriter(); - context.writeExpr(context.getArgument(0), Precedence.GROUPING); - renderProperty(context.getArgument(1), context); + if (isNull(context.getArgument(0))) { + writer.append(extractPropertyName(context.getArgument(1))); + } else { + context.writeExpr(context.getArgument(0), Precedence.GROUPING); + renderProperty(context.getArgument(1), context); + } writer.append('('); for (int i = 0; i < paramList.size(); ++i) { if (i > 0) { diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java index 6842b66e0..7fac833b0 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java @@ -22,6 +22,7 @@ import org.teavm.jso.JSClass; import org.teavm.jso.JSFunctor; import org.teavm.jso.JSModule; import org.teavm.jso.JSObject; +import org.teavm.model.AnnotationContainerReader; import org.teavm.model.CallLocation; import org.teavm.model.ClassReader; import org.teavm.model.ClassReaderSource; @@ -36,6 +37,7 @@ import org.teavm.model.Variable; import org.teavm.model.instructions.ClassConstantInstruction; import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.model.instructions.NullConstantInstruction; import org.teavm.model.instructions.StringConstantInstruction; class JSValueMarshaller { @@ -563,6 +565,10 @@ class JSValueMarshaller { } Variable classRef(String className, TextLocation location) { + return classRef(className, null, location); + } + + Variable classRef(String className, AnnotationContainerReader annotations, TextLocation location) { String name = null; String module = null; var cls = classSource.get(className); @@ -578,17 +584,37 @@ class JSValueMarshaller { } } } - var jsModule = cls.getAnnotations().get(JSModule.class.getName()); - if (jsModule != null) { - module = jsModule.getValue("value").getString(); - } + module = moduleName(cls.getAnnotations()); } if (name == null) { name = cls.getName().substring(cls.getName().lastIndexOf('.') + 1); } + if (module == null && annotations != null) { + module = moduleName(annotations); + } return module != null ? moduleRef(module, name, location) : globalRef(name, location); } + Variable moduleRef(String className, AnnotationContainerReader annotations, TextLocation location) { + String module = null; + var cls = classSource.get(className); + if (cls != null) { + module = moduleName(cls.getAnnotations()); + } + if (module == null && annotations != null) { + module = moduleName(annotations); + } + return module != null ? moduleRef(module, location) : nullInstance(location); + } + + private String moduleName(AnnotationContainerReader annotations) { + var jsModule = annotations.get(JSModule.class.getName()); + if (jsModule != null) { + return jsModule.getValue("value").getString(); + } + return null; + } + Variable globalRef(String name, TextLocation location) { var invoke = new InvokeInstruction(); invoke.setType(InvocationType.SPECIAL); @@ -602,6 +628,10 @@ class JSValueMarshaller { } Variable moduleRef(String module, String name, TextLocation location) { + return dot(moduleRef(module, location), name, location); + } + + Variable moduleRef(String module, TextLocation location) { var moduleNameInsn = new StringConstantInstruction(); moduleNameInsn.setReceiver(program.createVariable()); moduleNameInsn.setConstant(module); @@ -616,14 +646,26 @@ class JSValueMarshaller { invoke.setLocation(location); replacement.add(invoke); + return invoke.getReceiver(); + } + + Variable dot(Variable instance, String name, TextLocation location) { var get = new InvokeInstruction(); get.setType(InvocationType.SPECIAL); get.setMethod(JSMethods.GET_PURE); get.setReceiver(program.createVariable()); - get.setArguments(invoke.getReceiver(), addJsString(name, location)); + get.setArguments(instance, addJsString(name, location)); get.setLocation(location); replacement.add(get); return get.getReceiver(); } + + Variable nullInstance(TextLocation location) { + var nullConstant = new NullConstantInstruction(); + nullConstant.setReceiver(program.createVariable()); + nullConstant.setLocation(location); + replacement.add(nullConstant); + return nullConstant.getReceiver(); + } } diff --git a/tests/src/test/java/org/teavm/jso/test/CallTest.java b/tests/src/test/java/org/teavm/jso/test/CallTest.java index 5c6d07eb0..40f0419fc 100644 --- a/tests/src/test/java/org/teavm/jso/test/CallTest.java +++ b/tests/src/test/java/org/teavm/jso/test/CallTest.java @@ -20,6 +20,7 @@ import org.junit.runner.RunWith; import org.teavm.jso.JSClass; import org.teavm.jso.JSMethod; import org.teavm.jso.JSObject; +import org.teavm.jso.JSTopLevel; import org.teavm.junit.AttachJavaScript; import org.teavm.junit.EachTestCompiledSeparately; import org.teavm.junit.OnlyPlatform; @@ -77,6 +78,28 @@ public class CallTest { assertEquals("a:23,b:q,va:6:7:8", TestClass.restVararg(23, "q", intArray)); } + @Test + @AttachJavaScript("org/teavm/jso/test/vararg.js") + public void topLevelVararg() { + assertEquals("tva:q:w", TestClass.topLevelVararg("q", "w")); + assertEquals("tva:23:42", TestClass.topLevelVarargInt(23, 42)); + + var array = new String[3]; + for (var i = 0; i < array.length; ++i) { + array[i] = String.valueOf((char) ('A' + i)); + } + assertEquals("tva:A:B:C", TestClass.topLevelVararg(array)); + + var intArray = new int[3]; + for (var i = 0; i < array.length; ++i) { + intArray[i] = 6 + i; + } + assertEquals("tva:6:7:8", TestClass.topLevelVarargInt(intArray)); + + assertEquals("tva", TestClass.topLevelVararg()); + assertEquals("tva", TestClass.topLevelVarargInt()); + } + @JSClass public static class TestClass implements JSObject { public static native String allVararg(String... args); @@ -87,5 +110,12 @@ public class CallTest { public static native String restVararg(String a, int b, String... args); public static native String restVararg(int a, String b, int... args); + + @JSTopLevel + public static native String topLevelVararg(String... args); + + @JSTopLevel + @JSMethod("topLevelVararg") + public static native String topLevelVarargInt(int... args); } } diff --git a/tests/src/test/java/org/teavm/jso/test/ClassWithConstructor.java b/tests/src/test/java/org/teavm/jso/test/ClassWithConstructor.java index e2741717a..1539986e3 100644 --- a/tests/src/test/java/org/teavm/jso/test/ClassWithConstructor.java +++ b/tests/src/test/java/org/teavm/jso/test/ClassWithConstructor.java @@ -18,6 +18,7 @@ package org.teavm.jso.test; import org.teavm.jso.JSClass; import org.teavm.jso.JSObject; import org.teavm.jso.JSProperty; +import org.teavm.jso.JSTopLevel; @JSClass public class ClassWithConstructor implements JSObject { @@ -33,4 +34,15 @@ public class ClassWithConstructor implements JSObject { public native String bar(); public static native String staticMethod(); + + @JSTopLevel + public static native String topLevelFunction(); + + @JSTopLevel + @JSProperty + public static native String getTopLevelProperty(); + + @JSTopLevel + @JSProperty + public static native void setTopLevelProperty(String value); } diff --git a/tests/src/test/java/org/teavm/jso/test/ClassWithConstructorInModule.java b/tests/src/test/java/org/teavm/jso/test/ClassWithConstructorInModule.java index 529b7f406..e28a2f879 100644 --- a/tests/src/test/java/org/teavm/jso/test/ClassWithConstructorInModule.java +++ b/tests/src/test/java/org/teavm/jso/test/ClassWithConstructorInModule.java @@ -19,6 +19,7 @@ import org.teavm.jso.JSClass; import org.teavm.jso.JSModule; import org.teavm.jso.JSObject; import org.teavm.jso.JSProperty; +import org.teavm.jso.JSTopLevel; @JSClass(name = "ClassWithConstructor") @JSModule("./testModule.js") @@ -33,4 +34,11 @@ public class ClassWithConstructorInModule implements JSObject { public native int getFoo(); public native String bar(); + + @JSTopLevel + public static native String topLevelFunction(); + + @JSTopLevel + @JSProperty + public static native String getTopLevelProperty(); } diff --git a/tests/src/test/java/org/teavm/jso/test/ImportClassTest.java b/tests/src/test/java/org/teavm/jso/test/ImportClassTest.java index 5c2adfdf5..23a1c020f 100644 --- a/tests/src/test/java/org/teavm/jso/test/ImportClassTest.java +++ b/tests/src/test/java/org/teavm/jso/test/ImportClassTest.java @@ -59,6 +59,22 @@ public class ImportClassTest { assertEquals("static method called", ClassWithConstructor.staticMethod()); } + @Test + @AttachJavaScript("org/teavm/jso/test/classWithConstructor.js") + public void topLevel() { + assertEquals("top level", ClassWithConstructor.topLevelFunction()); + assertEquals("top level prop", ClassWithConstructor.getTopLevelProperty()); + + ClassWithConstructor.setTopLevelProperty("update"); + assertEquals("update", ClassWithConstructor.getTopLevelProperty()); + + assertEquals("top level", TopLevelDeclarations.topLevelFunction()); + assertEquals("update", TopLevelDeclarations.getTopLevelProperty()); + + TopLevelDeclarations.setTopLevelProperty("update2"); + assertEquals("update2", ClassWithConstructor.getTopLevelProperty()); + } + @JSBody(script = "return {};") private static native O create(); diff --git a/tests/src/test/java/org/teavm/jso/test/ImportModuleTest.java b/tests/src/test/java/org/teavm/jso/test/ImportModuleTest.java index a8a47576c..285f04911 100644 --- a/tests/src/test/java/org/teavm/jso/test/ImportModuleTest.java +++ b/tests/src/test/java/org/teavm/jso/test/ImportModuleTest.java @@ -68,6 +68,14 @@ public class ImportModuleTest { assertEquals(23, o.getFoo()); } + @Test + @JsModuleTest + @ServeJS(from = "org/teavm/jso/test/classWithConstructorInModule.js", as = "testModule.js") + public void topLevel() { + assertEquals("top level", ClassWithConstructorInModule.topLevelFunction()); + assertEquals("top level prop", ClassWithConstructorInModule.getTopLevelProperty()); + } + @JSBody( script = "return testModule.foo();", imports = @JSBodyImport(alias = "testModule", fromModule = "./testModule.js") diff --git a/tests/src/test/java/org/teavm/jso/test/TopLevelDeclarations.java b/tests/src/test/java/org/teavm/jso/test/TopLevelDeclarations.java new file mode 100644 index 000000000..a4698e360 --- /dev/null +++ b/tests/src/test/java/org/teavm/jso/test/TopLevelDeclarations.java @@ -0,0 +1,36 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.test; + +import org.teavm.jso.JSClass; +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; +import org.teavm.jso.JSTopLevel; + +@JSClass +@JSTopLevel +public class TopLevelDeclarations implements JSObject { + private TopLevelDeclarations() { + } + + public static native String topLevelFunction(); + + @JSProperty + public static native String getTopLevelProperty(); + + @JSProperty + public static native void setTopLevelProperty(String value); +} diff --git a/tests/src/test/resources/org/teavm/jso/test/classWithConstructor.js b/tests/src/test/resources/org/teavm/jso/test/classWithConstructor.js index 4558db502..fd6181879 100644 --- a/tests/src/test/resources/org/teavm/jso/test/classWithConstructor.js +++ b/tests/src/test/resources/org/teavm/jso/test/classWithConstructor.js @@ -30,4 +30,10 @@ class ClassWithConstructor { static staticMethod() { return "static method called"; } -} \ No newline at end of file +} + +function topLevelFunction() { + return "top level"; +} + +let topLevelProperty = "top level prop"; \ No newline at end of file diff --git a/tests/src/test/resources/org/teavm/jso/test/classWithConstructorInModule.js b/tests/src/test/resources/org/teavm/jso/test/classWithConstructorInModule.js index e88e51471..ff4cf61b3 100644 --- a/tests/src/test/resources/org/teavm/jso/test/classWithConstructorInModule.js +++ b/tests/src/test/resources/org/teavm/jso/test/classWithConstructorInModule.js @@ -26,4 +26,10 @@ export class ClassWithConstructor { bar() { return "bar called"; } -} \ No newline at end of file +} + +export function topLevelFunction() { + return "top level"; +} + +export let topLevelProperty = "top level prop"; \ No newline at end of file diff --git a/tests/src/test/resources/org/teavm/jso/test/vararg.js b/tests/src/test/resources/org/teavm/jso/test/vararg.js index aa97209e8..02ae25580 100644 --- a/tests/src/test/resources/org/teavm/jso/test/vararg.js +++ b/tests/src/test/resources/org/teavm/jso/test/vararg.js @@ -30,4 +30,12 @@ class TestClass { } return result; } +} + +function topLevelVararg(...args) { + let result = "tva"; + for (const arg of args) { + result += ":" + arg; + } + return result; } \ No newline at end of file