diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/translate/utils/AnnotationsUtils.java b/js/js.frontend/src/org/jetbrains/kotlin/js/translate/utils/AnnotationsUtils.java index e9828ecc5be..013b0cfae24 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/translate/utils/AnnotationsUtils.java +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/translate/utils/AnnotationsUtils.java @@ -42,6 +42,7 @@ public final class AnnotationsUtils { private static final String JS_NAME = "kotlin.js.JsName"; private static final FqName JS_MODULE_ANNOTATION = new FqName("kotlin.js.JsModule"); private static final FqName JS_NON_MODULE_ANNOTATION = new FqName("kotlin.js.JsNonModule"); + private static final FqName JS_QUALIFIER_ANNOTATION = new FqName("kotlin.js.JsQualifier"); private AnnotationsUtils() { } @@ -197,21 +198,34 @@ public final class AnnotationsUtils { @Nullable public static String getModuleName(@NotNull DeclarationDescriptor declaration) { AnnotationDescriptor annotation = declaration.getAnnotations().findAnnotation(JS_MODULE_ANNOTATION); - return annotation != null ? extractJsModuleName(annotation) : null; + return annotation != null ? extractSingleStringArgument(annotation) : null; } @Nullable public static String getFileModuleName(@NotNull BindingContext bindingContext, @NotNull DeclarationDescriptor declaration) { + return getSingleStringAnnotationArgument(bindingContext, declaration, JS_MODULE_ANNOTATION); + } + + @Nullable + public static String getFileQualifier(@NotNull BindingContext bindingContext, @NotNull DeclarationDescriptor declaration) { + return getSingleStringAnnotationArgument(bindingContext, declaration, JS_QUALIFIER_ANNOTATION); + } + + @Nullable + private static String getSingleStringAnnotationArgument( + @NotNull BindingContext bindingContext, + @NotNull DeclarationDescriptor declaration, + @NotNull FqName annotationFqName + ) { for (AnnotationDescriptor annotation : getContainingFileAnnotations(bindingContext, declaration)) { DeclarationDescriptor annotationType = annotation.getType().getConstructor().getDeclarationDescriptor(); if (annotationType == null) continue; FqNameUnsafe fqName = DescriptorUtils.getFqName(annotation.getType().getConstructor().getDeclarationDescriptor()); - if (fqName.equals(JS_MODULE_ANNOTATION.toUnsafe())) { - return extractJsModuleName(annotation); + if (fqName.equals(annotationFqName.toUnsafe())) { + return extractSingleStringArgument(annotation); } } - return null; } @@ -237,7 +251,7 @@ public final class AnnotationsUtils { } @Nullable - private static String extractJsModuleName(@NotNull AnnotationDescriptor annotation) { + private static String extractSingleStringArgument(@NotNull AnnotationDescriptor annotation) { if (annotation.getAllValueArguments().isEmpty()) return null; ConstantValue importValue = annotation.getAllValueArguments().values().iterator().next(); diff --git a/js/js.libraries/src/core/annotations.kt b/js/js.libraries/src/core/annotations.kt index e33b631fe35..890bcecd8b7 100644 --- a/js/js.libraries/src/core/annotations.kt +++ b/js/js.libraries/src/core/annotations.kt @@ -54,4 +54,8 @@ annotation class JsModule(val import: String) @Retention(AnnotationRetention.BINARY) @Target(CLASS, PROPERTY, FUNCTION, FILE) -annotation class JsNonModule \ No newline at end of file +annotation class JsNonModule + +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.FILE) +annotation class JsQualifier(val value: String) \ No newline at end of file diff --git a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinJavascriptSerializationUtil.kt b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinJavascriptSerializationUtil.kt index 40a5777c95e..6a0c680b4a5 100644 --- a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinJavascriptSerializationUtil.kt +++ b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinJavascriptSerializationUtil.kt @@ -144,9 +144,6 @@ object KotlinJavascriptSerializationUtil { } }, skip) - writeFun(KotlinJavascriptSerializedResourcePaths.getFileListFilePath(fqName), - serializeFiles(fileRegistry, bindingContext, AnnotationSerializer(serializer.stringTable))) - val packageStream = ByteArrayOutputStream() val fragments = packageView.fragments val members = fragments @@ -158,6 +155,9 @@ object KotlinJavascriptSerializationUtil { writeFun(KotlinJavascriptSerializedResourcePaths.getPackageFilePath(fqName), packageStream.toByteArray()) } + writeFun(KotlinJavascriptSerializedResourcePaths.getFileListFilePath(fqName), + serializeFiles(fileRegistry, bindingContext, AnnotationSerializer(serializer.stringTable))) + val strings = serializerExtension.stringTable serializeClassNamesInPackage(fqName, fragments, strings, skip, writeFun) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index 412bb5bf482..68375a364ee 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -5238,6 +5238,39 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { } } + @TestMetadata("js/js.translator/testData/box/jsQualifier") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class JsQualifier extends AbstractBoxJsTest { + public void testAllFilesPresentInJsQualifier() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/box/jsQualifier"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS, true); + } + + @TestMetadata("classes.kt") + public void testClasses() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/jsQualifier/classes.kt"); + doTest(fileName); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/jsQualifier/simple.kt"); + doTest(fileName); + } + + @TestMetadata("umdFallback.kt") + public void testUmdFallback() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/jsQualifier/umdFallback.kt"); + doTest(fileName); + } + + @TestMetadata("withModule.kt") + public void testWithModule() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/jsQualifier/withModule.kt"); + doTest(fileName); + } + } + @TestMetadata("js/js.translator/testData/box/labels") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java index 8d936db5e9a..10921ee5310 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.js.translate.context; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.intellij.openapi.util.Factory; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.hash.LinkedHashMap; import org.jetbrains.annotations.NotNull; @@ -287,7 +288,14 @@ public final class StaticContext { String fileModuleName = AnnotationsUtils.getFileModuleName(getBindingContext(), suggested.getDescriptor()); if (fileModuleName != null) { JsName moduleJsName = getImportedModule(fileModuleName, null).internalName; - expression = pureFqn(moduleJsName, null); + expression = pureFqn(moduleJsName, expression); + } + + String qualifier = AnnotationsUtils.getFileQualifier(getBindingContext(), suggested.getDescriptor()); + if (qualifier != null) { + for (String qualifierPart : StringUtil.split(qualifier, ".")) { + expression = pureFqn(qualifierPart, expression); + } } } diff --git a/js/js.translator/testData/box/jsQualifier/classes.js b/js/js.translator/testData/box/jsQualifier/classes.js new file mode 100644 index 00000000000..7664fb09ca4 --- /dev/null +++ b/js/js.translator/testData/box/jsQualifier/classes.js @@ -0,0 +1,16 @@ +var pkg = function() { + function C() { + } + C.prototype.o = function() { + return "O"; + }; + function D() { + } + D.prototype.k = function() { + return "K"; + }; + C.D = D; + return { + C: C + }; +}(); \ No newline at end of file diff --git a/js/js.translator/testData/box/jsQualifier/classes.kt b/js/js.translator/testData/box/jsQualifier/classes.kt new file mode 100644 index 00000000000..af845cfe1fe --- /dev/null +++ b/js/js.translator/testData/box/jsQualifier/classes.kt @@ -0,0 +1,15 @@ +// MODULE: lib +// FILE: lib.kt +@file:JsQualifier("pkg") +external class C { + fun o(): String + + class D { + fun k(): String + } +} + +// MODULE: main(lib) +// FILE: main.kt + +fun box() = C().o() + C.D().k() \ No newline at end of file diff --git a/js/js.translator/testData/box/jsQualifier/simple.js b/js/js.translator/testData/box/jsQualifier/simple.js new file mode 100644 index 00000000000..c95546d9aec --- /dev/null +++ b/js/js.translator/testData/box/jsQualifier/simple.js @@ -0,0 +1,8 @@ +var a = { + b: { + c: function() { + return "O"; + }, + d: "K" + } +}; \ No newline at end of file diff --git a/js/js.translator/testData/box/jsQualifier/simple.kt b/js/js.translator/testData/box/jsQualifier/simple.kt new file mode 100644 index 00000000000..c860f76c43a --- /dev/null +++ b/js/js.translator/testData/box/jsQualifier/simple.kt @@ -0,0 +1,15 @@ +// MODULE: lib +// FILE: lib.kt +@file:JsQualifier("a.b") +package ab + +external fun c(): String + +external val d: String + +// MODULE: main(lib) +// FILE: main.kt + +package main + +fun box() = ab.c() + ab.d \ No newline at end of file diff --git a/js/js.translator/testData/box/jsQualifier/umdFallback.js b/js/js.translator/testData/box/jsQualifier/umdFallback.js new file mode 100644 index 00000000000..6d89d395fa7 --- /dev/null +++ b/js/js.translator/testData/box/jsQualifier/umdFallback.js @@ -0,0 +1,5 @@ +var foo = { + bar: function() { + return "OK"; + } +}; \ No newline at end of file diff --git a/js/js.translator/testData/box/jsQualifier/umdFallback.kt b/js/js.translator/testData/box/jsQualifier/umdFallback.kt new file mode 100644 index 00000000000..8458286653e --- /dev/null +++ b/js/js.translator/testData/box/jsQualifier/umdFallback.kt @@ -0,0 +1,13 @@ +// MODULE: lib +// FILE: lib.kt +// MODULE_KIND: UMD +// NO_JS_MODULE_SYSTEM +@file:JsQualifier("foo") + +external fun bar(): String + +// MODULE: main(lib) +// FILE: main.kt +// MODULE_KIND: UMD +// NO_JS_MODULE_SYSTEM +fun box() = bar() \ No newline at end of file diff --git a/js/js.translator/testData/box/jsQualifier/withModule.js b/js/js.translator/testData/box/jsQualifier/withModule.js new file mode 100644 index 00000000000..86e0a735bcb --- /dev/null +++ b/js/js.translator/testData/box/jsQualifier/withModule.js @@ -0,0 +1,9 @@ +define("libjs", ["exports"], function(exports) { + exports.a = { + b: { + c: function () { + return "OK"; + } + } + }; +}); \ No newline at end of file diff --git a/js/js.translator/testData/box/jsQualifier/withModule.kt b/js/js.translator/testData/box/jsQualifier/withModule.kt new file mode 100644 index 00000000000..5e7c304283a --- /dev/null +++ b/js/js.translator/testData/box/jsQualifier/withModule.kt @@ -0,0 +1,15 @@ +// MODULE: lib +// FILE: lib.kt +// MODULE_KIND: AMD +@file:JsQualifier("a.b") +@file:JsModule("libjs") +package ab + +external fun c(): String + +// MODULE: main(lib) +// FILE: main.kt +// MODULE_KIND: AMD +package main + +fun box() = ab.c() \ No newline at end of file