diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt index 0b3f2c824eb..f262aafc4ae 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt @@ -106,7 +106,7 @@ sealed class ExportedType { val returnType: ExportedType ) : ExportedType() - class ClassType(val name: String, val arguments: List) : ExportedType() + class ClassType(val name: String, val arguments: List, val ir: IrClass) : ExportedType() class TypeParameter(val name: String) : ExportedType() class Nullable(val baseType: ExportedType) : ExportedType() class ErrorType(val comment: String) : ExportedType() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt index 5c44d5d5eac..930e6378a2a 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt @@ -483,7 +483,8 @@ class ExportModelGenerator( ClassKind.ENUM_CLASS, ClassKind.INTERFACE -> ExportedType.ClassType( name, - type.arguments.map { exportTypeArgument(it) } + type.arguments.map { exportTypeArgument(it) }, + klass ) }.withImplicitlyExported(isImplicitlyExported) } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt index f09442ecaef..7acb73c7d62 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt @@ -19,7 +19,15 @@ fun ExportedModule.toTypeScript(): String { } fun wrapTypeScript(name: String, moduleKind: ModuleKind, dts: String): String { - val types = "${moduleKind.indent}type Nullable = T | null | undefined\n" + val declareKeyword = when (moduleKind) { + ModuleKind.PLAIN -> "" + else -> "declare " + } + val types = """ + type Nullable = T | null | undefined + ${declareKeyword}const __doNotImplementIt: unique symbol + type __doNotImplementIt = typeof __doNotImplementIt + """.trimIndent().prependIndent(moduleKind.indent) + "\n" val declarationsDts = types + dts @@ -117,14 +125,16 @@ fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): Strin val superClassClause = superClass?.let { it.toExtendsClause(indent) } ?: "" val superInterfacesClause = superInterfaces.toImplementsClause(superInterfacesKeyword, indent) - val members = members.map { - if (!ir.isInner || it !is ExportedFunction || !it.isStatic) { - it - } else { - // Remove $outer argument from secondary constructors of inner classes - it.copy(parameters = it.parameters.drop(1)) + val members = members + .let { if (shouldNotBeImplemented()) it.withMagicProperty() else it } + .map { + if (!ir.isInner || it !is ExportedFunction || !it.isStatic) { + it + } else { + // Remove $outer argument from secondary constructors of inner classes + it.copy(parameters = it.parameters.drop(1)) + } } - } val (innerClasses, nonInnerClasses) = nestedClasses.partition { it.ir.isInner } val innerClassesProperties = innerClasses.map { it.toReadonlyProperty() } @@ -179,6 +189,26 @@ fun List.toImplementsClause(superInterfacesKeyword: String, indent } } +fun ExportedClass.shouldNotBeImplemented(): Boolean { + return (isInterface && !ir.isExternal) || superInterfaces.any { it is ExportedType.ClassType && !it.ir.isExternal } +} + +fun List.withMagicProperty(): List { + return plus( + ExportedProperty( + "__doNotUseIt", + ExportedType.TypeParameter("__doNotImplementIt"), + mutable = false, + isMember = true, + isStatic = false, + isAbstract = false, + isProtected = false, + irGetter = null, + irSetter = null + ) + ) +} + fun IrClass.asNestedClassAccess(): String { val name = getJsNameOrKotlinName().identifier if (parent !is IrClass) return name diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt index e109a14044b..1e4ac20a111 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.descriptors.DescriptorVisibility import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.ir.backend.js.export.isEnumFakeOverriddenDeclaration import org.jetbrains.kotlin.ir.backend.js.export.isExported @@ -125,24 +126,21 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo ) } - fun IrSimpleFunction.accessorRef(): JsNameRef? = - when (visibility) { - DescriptorVisibilities.PRIVATE -> null - else -> JsNameRef( - context.getNameForMemberFunction(this), - classPrototypeRef - ) - } + val overriddenSymbols = property.getter?.overriddenSymbols.orEmpty() // Don't generate `defineProperty` if the property overrides a property from an exported class, // because we've already generated `defineProperty` for the base class property. // In other words, we only want to generate `defineProperty` once for each property. // The exception is case when we override val with var, // so we need regenerate `defineProperty` with setter. - val noOverriddenGetter = property.getter?.overriddenSymbols?.isEmpty() == true + // P.S. If the overridden property is owned by an interface - we should generate defineProperty + // for overridden property in the first class which override those properties + val hasOverriddenExportedInterfaceProperties = overriddenSymbols.any { it.owner.parentClassOrNull.isExportedInterface() } + && !overriddenSymbols.any { it.owner.parentClassOrNull.isExportedClass() } - val overriddenExportedGetter = (property.getter?.overriddenSymbols?.isNotEmpty() == true && - property.getter?.isOverriddenExported(context.staticContext.backendContext) == true) + val getterOverridesExternal = property.getter?.overridesExternal() == true + val overriddenExportedGetter = !property.getter?.overriddenSymbols.isNullOrEmpty() && + property.getter?.isOverriddenExported(context.staticContext.backendContext) == true val noOverriddenExportedSetter = property.setter?.isOverriddenExported(context.staticContext.backendContext) == false @@ -150,8 +148,9 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo property.isEnumFakeOverriddenDeclaration(context.staticContext.backendContext) if (irClass.isExported(context.staticContext.backendContext) && - (noOverriddenGetter || needsOverride) || - property.getter?.overridesExternal() == true || + (overriddenSymbols.isEmpty() || needsOverride) || + hasOverriddenExportedInterfaceProperties || + getterOverridesExternal || property.getJsName() != null ) { @@ -168,31 +167,24 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo // set: Foo.prototype._set_prop__a4enbm_k$ // }); - val getterForwarder = if (property.getter?.modality == Modality.FINAL) property.getter?.accessorRef() - else { - property.getter?.propertyAccessorForwarder("getter forwarder") { getterRef -> - JsReturn( - JsInvocation( - getterRef - ) - ) + val getterForwarder = property.getter + .takeIf { it.shouldExportAccessor() } + .getOrGenerateIfFinal { + propertyAccessorForwarder("getter forwarder") { + JsReturn(JsInvocation(it)) + } } - } - val setterForwarder = if (property.setter?.modality == Modality.FINAL) property.setter?.accessorRef() - else { - property.setter?.let { + val setterForwarder = property.setter + .takeIf { it.shouldExportAccessor() } + .getOrGenerateIfFinal { val setterArgName = JsName("value", false) - it.propertyAccessorForwarder("setter forwarder") { setterRef -> - JsInvocation( - setterRef, - JsNameRef(setterArgName) - ).makeStmt() + propertyAccessorForwarder("setter forwarder") { + JsInvocation(it, JsNameRef(setterArgName)).makeStmt() }?.apply { parameters.add(JsParameter(setterArgName)) } } - } classBlock.statements += JsExpressionStatement( defineProperty( @@ -209,6 +201,34 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo return classBlock } + private inline fun IrSimpleFunction?.getOrGenerateIfFinal(generateFunc: IrSimpleFunction.() -> JsFunction?): JsExpression? { + if (this == null) return null + return if (modality == Modality.FINAL) accessorRef() else generateFunc() + } + + private fun IrSimpleFunction?.shouldExportAccessor(): Boolean { + if (this == null) return false + + if (parentAsClass.isExported(context.staticContext.backendContext)) return true + + val property = correspondingPropertySymbol!!.owner + + if (property.isOverriddenExported(context.staticContext.backendContext)) { + return isOverriddenExported(context.staticContext.backendContext) + } + + return overridesExternal() || property.getJsName() != null + } + + private fun IrSimpleFunction.accessorRef(): JsNameRef? = + when (visibility) { + DescriptorVisibilities.PRIVATE -> null + else -> JsNameRef( + context.getNameForMemberFunction(this), + classPrototypeRef + ) + } + private fun IrSimpleFunction.generateAssignmentIfMangled(memberRef: JsExpression) { if ( irClass.isExported(context.staticContext.backendContext) && diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt index a172e6c7886..df88d29f069 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt @@ -143,11 +143,14 @@ fun translateCall( val jsExtensionReceiver = expression.extensionReceiver?.accept(transformer, context) val arguments = translateCallArguments(expression, context, transformer) - // Transform external property accessor call - // @JsName-annotated external property accessors are translated as function calls + // Transform external and interface's property accessor call + // @JsName-annotated external and interface's property accessors are translated as function calls if (function.getJsName() == null) { val property = function.correspondingPropertySymbol?.owner - if (property != null && property.isEffectivelyExternal()) { + if ( + property != null && + (property.isEffectivelyExternal() || property.isExportedInterfaceMember()) + ) { val propertyName = context.getNameForProperty(property) val nameRef = when (jsDispatchReceiver) { null -> jsGlobalVarRef(JsNameRef(propertyName)) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/IrJsUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/IrJsUtils.kt new file mode 100644 index 00000000000..27eda221734 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/IrJsUtils.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.ir.backend.js.utils + +import org.jetbrains.kotlin.descriptors.isInterface +import org.jetbrains.kotlin.descriptors.isClass +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrDeclaration +import org.jetbrains.kotlin.ir.util.parentClassOrNull + +fun IrDeclaration?.isExportedClass() = + this is IrClass && kind.isClass && isJsExport() + +fun IrDeclaration?.isExportedInterface() = + this is IrClass && kind.isInterface && isJsExport() + +fun IrDeclaration.isExportedInterfaceMember() = + parentClassOrNull.isExportedInterface() \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/extendingNonExportedType.kt b/compiler/testData/diagnostics/testsWithJsStdLib/export/extendingNonExportedType.kt index aec72fe12c8..985971cf5ac 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/export/extendingNonExportedType.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/extendingNonExportedType.kt @@ -20,7 +20,7 @@ open class ExportedGenericClass class ")!>ExportedClass3 : ExportedGenericClass() @JsExport -interface ExportedGenericInterface +interface ExportedGenericInterface @JsExport class ")!>ExportedClass4 : ExportedGenericInterface diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInTypeParameters.kt b/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInTypeParameters.kt index d15c491623d..173c3eb1535 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInTypeParameters.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/unexportableTypesInTypeParameters.kt @@ -14,4 +14,4 @@ fun <T : C>foo() { } class A<T : C, S: I> @JsExport -interface I2<T> where T : C, T : I +interface I2<T> where T : C, T : I diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.kt b/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.kt index bc29bab0d23..dcb4984bd86 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.kt @@ -18,11 +18,23 @@ val String.extensionProperty annotation class AnnotationClass @JsExport -interface SomeInterface +interface SomeInterface @JsExport external interface GoodInterface +@JsExport +interface InterfaceWithCompanion { + companion object { + fun foo() = 42 + } +} + +@JsExport +interface OuterInterface { + class Nested +} + @JsExport value class A(val a: Int) diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.txt b/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.txt index e3eb2a532c1..052f63134e4 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.txt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.txt @@ -50,6 +50,33 @@ package foo { public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } + @kotlin.js.JsExport public interface InterfaceWithCompanion { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public companion object Companion { + private constructor Companion() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun foo(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + } + + @kotlin.js.JsExport public interface OuterInterface { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public final class Nested { + public constructor Nested() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + } + @kotlin.js.JsExport public interface SomeInterface { public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int diff --git a/core/compiler.common/src/org/jetbrains/kotlin/descriptors/ClassKind.kt b/core/compiler.common/src/org/jetbrains/kotlin/descriptors/ClassKind.kt index 69a5769676e..aaeb8bd69af 100644 --- a/core/compiler.common/src/org/jetbrains/kotlin/descriptors/ClassKind.kt +++ b/core/compiler.common/src/org/jetbrains/kotlin/descriptors/ClassKind.kt @@ -32,3 +32,6 @@ inline val ClassKind.isInterface: Boolean inline val ClassKind.isEnumClass: Boolean get() = this == ClassKind.ENUM_CLASS + +inline val ClassKind.isClass: Boolean + get() = this == ClassKind.CLASS \ No newline at end of file diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt b/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt index c8765d07714..705b208b0fb 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt @@ -136,6 +136,11 @@ val DeclarationDescriptor.isInsidePrivateClass: Boolean return parent != null && DescriptorVisibilities.isPrivate(parent.visibility) } +val DeclarationDescriptor.isInsideInterface: Boolean + get() { + val parent = containingDeclaration as? ClassDescriptor + return parent != null && parent.kind.isInterface + } fun ClassDescriptor.getSuperClassNotAny(): ClassDescriptor? { for (supertype in defaultType.constructor.supertypes) { diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt index 23cfb8c2275..c418b008785 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt @@ -17,6 +17,7 @@ import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal import org.jetbrains.kotlin.resolve.descriptorUtil.isExtensionProperty +import org.jetbrains.kotlin.resolve.descriptorUtil.isInsideInterface import org.jetbrains.kotlin.resolve.inline.isInlineWithReified import org.jetbrains.kotlin.resolve.isInlineClass import org.jetbrains.kotlin.types.KotlinType @@ -109,12 +110,15 @@ object JsExportDeclarationChecker : DeclarationChecker { } val wrongDeclaration: String? = when (descriptor.kind) { - INTERFACE -> if (descriptor.isExternal) null else "interface" ANNOTATION_CLASS -> "annotation class" - CLASS -> if (descriptor.isInlineClass()) { - "${if (descriptor.isInline) "inline " else ""}${if (descriptor.isValue) "value " else ""}class" + CLASS -> when { + descriptor.isInsideInterface -> "nested class inside exported interface" + descriptor.isInlineClass() -> "${if (descriptor.isInline) "inline " else ""}${if (descriptor.isValue) "value " else ""}class" + else -> null + } + else -> if (descriptor.isInsideInterface) { + "${if (descriptor.isCompanionObject) "companion object" else "nested/inner declaration"} inside exported interface" } else null - else -> null } if (wrongDeclaration != null) { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java index 4e7c08ed9df..c88a47eebac 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java @@ -2070,6 +2070,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { runTest("js/js.translator/testData/box/export/exportEnumClass.kt"); } + @Test + @TestMetadata("exportInterface.kt") + public void testExportInterface() throws Exception { + runTest("js/js.translator/testData/box/export/exportInterface.kt"); + } + @Test @TestMetadata("exportNestedClass.kt") public void testExportNestedClass() throws Exception { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java index 46003f15682..16573c85976 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java @@ -2460,6 +2460,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { runTest("js/js.translator/testData/box/export/exportEnumClass.kt"); } + @Test + @TestMetadata("exportInterface.kt") + public void testExportInterface() throws Exception { + runTest("js/js.translator/testData/box/export/exportInterface.kt"); + } + @Test @TestMetadata("exportNestedClass.kt") public void testExportNestedClass() throws Exception { diff --git a/js/js.translator/testData/box/coercion/defaultAccessors.kt b/js/js.translator/testData/box/coercion/defaultAccessors.kt index 85c5c7871c7..077af238d95 100644 --- a/js/js.translator/testData/box/coercion/defaultAccessors.kt +++ b/js/js.translator/testData/box/coercion/defaultAccessors.kt @@ -1,6 +1,7 @@ // EXPECTED_REACHABLE_NODES: 1288 // IGNORE_BACKEND: JS_IR // IGNORE_BACKEND: JS_IR_ES6 +@JsExport interface I { val a: Char } diff --git a/js/js.translator/testData/box/export/exportInterface.kt b/js/js.translator/testData/box/export/exportInterface.kt new file mode 100644 index 00000000000..da204b4e0a7 --- /dev/null +++ b/js/js.translator/testData/box/export/exportInterface.kt @@ -0,0 +1,79 @@ +// IGNORE_BACKEND: JS +// RUN_PLAIN_BOX_FUNCTION +// INFER_MAIN_MODULE + +// MODULE: export_interface +// FILE: lib.kt + +@JsExport +interface I { + val value: Int + var variable: Int + fun foo(): String +} + +open class NotExportedClass(override var value: Int) : I { + override var variable: Int = value + override open fun foo(): String = "Not Exported" +} + +@JsExport +class ExportedClass(override val value: Int) : I { + override var variable: Int = value + override fun foo(): String = "Exported" +} + +@JsExport +class AnotherOne : NotExportedClass(42) { + override fun foo(): String = "Another One Exported" +} + +@JsExport +fun generateNotExported(value: Int): NotExportedClass { + return NotExportedClass(value) +} + +@JsExport +fun consume(i: I): String { + return "Value is ${i.value}, variable is ${i.variable} and result is '${i.foo()}'" +} + +// FILE: test.js +function box() { + const { I, ExportedClass, AnotherOne, generateNotExported, consume } = this["export_interface"] + + if (I !== undefined) return "Fail: module should not export interface in runtime" + + const exported = new ExportedClass(1) + const another = new AnotherOne() + const notExported = generateNotExported (3) + + if (exported.foo() !== "Exported") return "Fail: foo function was not generated for ExportedClass" + if (another.foo() !== "Another One Exported") return "Fail: foo function was not generated for AnotherOne" + if (notExported.foo() !== "Not Exported") return "Fail: foo function was not generated for NotExportedClass" + + if (exported.value !== 1) return "Fail: value getter was not generated for ExportedClass" + if (another.value !== 42) return "Fail: value getter was not generated for AnotherOne" + if (notExported.value !== 3) return "Fail: value getter was not generated for NotExportedClass" + + if (exported.variable !== 1) return "Fail: variable getter was not generated for ExportedClass" + if (another.variable !== 42) return "Fail: variable getter was not generated for AnotherOne" + if (notExported.variable !== 3) return "Fail: variable getter was not generated for NotExportedClass" + + exported.variable = 101 + another.variable = 102 + notExported.variable = 103 + + if (exported.variable !== 101) return "Fail: variable setter was not generated for ExportedClass" + if (another.variable !== 102) return "Fail: variable setter was not generated for AnotherOne" + if (notExported.variable !== 103) return "Fail: variable setter was not generated for NotExportedClass" + + notExported.value = 42 + if (notExported.value !== 3) return "Fail: value setter was generated for NotExportedClass, but it shouldn't" + + if (consume(exported) !== "Value is 1, variable is 101 and result is 'Exported'") return "Fail: methods or fields of ExportedClass was mangled" + if (consume(another) !== "Value is 42, variable is 102 and result is 'Another One Exported'") return "Fail: methods or fields of AnotherOne was mangled" + if (consume(notExported) !== "Value is 3, variable is 103 and result is 'Not Exported'") return "Fail: methods or fields of NotExported was mangled" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/classes/inner-class.d.ts b/js/js.translator/testData/typescript-export/classes/inner-class.d.ts index 39f5d2b14df..6fa28923ddc 100644 --- a/js/js.translator/testData/typescript-export/classes/inner-class.d.ts +++ b/js/js.translator/testData/typescript-export/classes/inner-class.d.ts @@ -1,5 +1,7 @@ declare namespace JS_TESTS { type Nullable = T | null | undefined + const __doNotImplementIt: unique symbol + type __doNotImplementIt = typeof __doNotImplementIt namespace foo { class TestInner { constructor(a: string); diff --git a/js/js.translator/testData/typescript-export/constructors/constructors.d.ts b/js/js.translator/testData/typescript-export/constructors/constructors.d.ts index 5c712041e5e..b19e0d9d046 100644 --- a/js/js.translator/testData/typescript-export/constructors/constructors.d.ts +++ b/js/js.translator/testData/typescript-export/constructors/constructors.d.ts @@ -1,5 +1,7 @@ declare namespace JS_TESTS { type Nullable = T | null | undefined + const __doNotImplementIt: unique symbol + type __doNotImplementIt = typeof __doNotImplementIt class ClassWithDefaultCtor { constructor(); readonly x: string; diff --git a/js/js.translator/testData/typescript-export/declarations/declarations.d.ts b/js/js.translator/testData/typescript-export/declarations/declarations.d.ts index 18d33836100..3ccd1d33c13 100644 --- a/js/js.translator/testData/typescript-export/declarations/declarations.d.ts +++ b/js/js.translator/testData/typescript-export/declarations/declarations.d.ts @@ -1,5 +1,7 @@ declare namespace JS_TESTS { type Nullable = T | null | undefined + const __doNotImplementIt: unique symbol + type __doNotImplementIt = typeof __doNotImplementIt namespace foo { const _val: number; let _var: number; @@ -123,5 +125,17 @@ declare namespace JS_TESTS { readonly prop: string; } } + interface TestInterface { + readonly value: string; + getOwnerName(): string; + readonly __doNotUseIt: __doNotImplementIt; + } + class TestInterfaceImpl implements foo.TestInterface { + constructor(value: string); + readonly value: string; + getOwnerName(): string; + readonly __doNotUseIt: __doNotImplementIt; + } + function processInterface(test: foo.TestInterface): string; } } diff --git a/js/js.translator/testData/typescript-export/declarations/declarations.kt b/js/js.translator/testData/typescript-export/declarations/declarations.kt index 12ec2eda338..66ab80da281 100644 --- a/js/js.translator/testData/typescript-export/declarations/declarations.kt +++ b/js/js.translator/testData/typescript-export/declarations/declarations.kt @@ -179,4 +179,21 @@ enum class TestEnumClass(val constructorParameter: String) { class Nested { val prop: String = "hello2" } +} + + +@JsExport +interface TestInterface { + val value: String + fun getOwnerName(): String +} + +@JsExport +class TestInterfaceImpl(override val value: String) : TestInterface { + override fun getOwnerName() = "TestInterfaceImpl" +} + +@JsExport +fun processInterface(test: TestInterface): String { + return "Owner ${test.getOwnerName()} has value '${test.value}'" } \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/declarations/declarations__main.js b/js/js.translator/testData/typescript-export/declarations/declarations__main.js index 31ac4271d34..e0495942d5e 100644 --- a/js/js.translator/testData/typescript-export/declarations/declarations__main.js +++ b/js/js.translator/testData/typescript-export/declarations/declarations__main.js @@ -27,6 +27,8 @@ var TestSealed = JS_TESTS.foo.TestSealed; var TestAbstract = JS_TESTS.foo.TestAbstract; var TestDataClass = JS_TESTS.foo.TestDataClass; var TestEnumClass = JS_TESTS.foo.TestEnumClass; +var TestInterfaceImpl = JS_TESTS.foo.TestInterfaceImpl; +var processInterface = JS_TESTS.foo.processInterface; function assert(condition) { if (!condition) { throw "Assertion failed"; @@ -112,5 +114,8 @@ function box() { assert(TestEnumClass.A.ordinal === 0); assert(TestEnumClass.B.ordinal === 1); assert(new TestEnumClass.Nested().prop == "hello2"); + assert(processInterface(new TestInterfaceImpl("bar")) == "Owner TestInterfaceImpl has value 'bar'"); + // @ts-expect-error "Just test that this code will throw compilation error for a user" + assert(processInterface({ value: "bar", getOwnerName: function () { return "RandomObject"; } }) == "Owner RandomObject has value 'bar'"); return "OK"; } diff --git a/js/js.translator/testData/typescript-export/declarations/declarations__main.ts b/js/js.translator/testData/typescript-export/declarations/declarations__main.ts index 36fc9ecdb32..f3a7e35f790 100644 --- a/js/js.translator/testData/typescript-export/declarations/declarations__main.ts +++ b/js/js.translator/testData/typescript-export/declarations/declarations__main.ts @@ -27,6 +27,8 @@ import TestSealed = JS_TESTS.foo.TestSealed; import TestAbstract = JS_TESTS.foo.TestAbstract; import TestDataClass = JS_TESTS.foo.TestDataClass; import TestEnumClass = JS_TESTS.foo.TestEnumClass; +import TestInterfaceImpl = JS_TESTS.foo.TestInterfaceImpl; +import processInterface = JS_TESTS.foo.processInterface; function assert(condition: boolean) { if (!condition) { @@ -133,5 +135,10 @@ function box(): string { assert(new TestEnumClass.Nested().prop == "hello2") + assert(processInterface(new TestInterfaceImpl("bar")) == "Owner TestInterfaceImpl has value 'bar'") + + // @ts-expect-error "Just test that this code will throw compilation error for a user" + assert(processInterface({ value: "bar", getOwnerName: () => "RandomObject" }) == "Owner RandomObject has value 'bar'") + return "OK"; } \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/escapedDeclarations/escapedDeclarations.d.ts b/js/js.translator/testData/typescript-export/escapedDeclarations/escapedDeclarations.d.ts index 690ddbf28e8..fd9608a47fc 100644 --- a/js/js.translator/testData/typescript-export/escapedDeclarations/escapedDeclarations.d.ts +++ b/js/js.translator/testData/typescript-export/escapedDeclarations/escapedDeclarations.d.ts @@ -1,5 +1,7 @@ declare namespace JS_TESTS { type Nullable = T | null | undefined + const __doNotImplementIt: unique symbol + type __doNotImplementIt = typeof __doNotImplementIt namespace foo { diff --git a/js/js.translator/testData/typescript-export/implicitExport/declarations.d.ts b/js/js.translator/testData/typescript-export/implicitExport/declarations.d.ts index 462a886db11..ae8f719471f 100644 --- a/js/js.translator/testData/typescript-export/implicitExport/declarations.d.ts +++ b/js/js.translator/testData/typescript-export/implicitExport/declarations.d.ts @@ -1,6 +1,11 @@ declare namespace JS_TESTS { type Nullable = T | null | undefined + const __doNotImplementIt: unique symbol + type __doNotImplementIt = typeof __doNotImplementIt namespace foo { + interface ExportedInterface { + readonly __doNotUseIt: __doNotImplementIt; + } function producer(value: number): any/* foo.NonExportedType */; function consumer(value: any/* foo.NonExportedType */): number; class A { @@ -14,5 +19,16 @@ declare namespace JS_TESTS { class C /* implements foo.NonExportedInterface */ { constructor(); } + class D implements foo.ExportedInterface/*, foo.NonExportedInterface */ { + constructor(); + readonly __doNotUseIt: __doNotImplementIt; + } + class E /* extends foo.NonExportedType */ implements foo.ExportedInterface { + constructor(); + readonly __doNotUseIt: __doNotImplementIt; + } + class F extends foo.A /* implements foo.NonExportedInterface */ { + constructor(); + } } } \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/implicitExport/declarations.kt b/js/js.translator/testData/typescript-export/implicitExport/declarations.kt index 0850c4f91dd..cade038d655 100644 --- a/js/js.translator/testData/typescript-export/implicitExport/declarations.kt +++ b/js/js.translator/testData/typescript-export/implicitExport/declarations.kt @@ -11,6 +11,9 @@ package foo interface NonExportedInterface open class NonExportedType(val value: Int) +@JsExport +interface ExportedInterface + @JsExport fun producer(value: Int): NonExportedType { return NonExportedType(value) @@ -22,7 +25,7 @@ fun consumer(value: NonExportedType): Int { } @JsExport -class A(var value: NonExportedType) { +open class A(var value: NonExportedType) { fun increment(t: T): NonExportedType { return NonExportedType(value = t.value + 1) } @@ -32,4 +35,13 @@ class A(var value: NonExportedType) { class B(v: Int) : NonExportedType(v) @JsExport -class C : NonExportedInterface \ No newline at end of file +class C : NonExportedInterface + +@JsExport +class D : NonExportedInterface, ExportedInterface + +@JsExport +class E : NonExportedType(42), ExportedInterface + +@JsExport +class F : A(NonExportedType(42)), NonExportedInterface \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/inheritance/inheritance.d.ts b/js/js.translator/testData/typescript-export/inheritance/inheritance.d.ts index 1c92ff5378c..07241a5220e 100644 --- a/js/js.translator/testData/typescript-export/inheritance/inheritance.d.ts +++ b/js/js.translator/testData/typescript-export/inheritance/inheritance.d.ts @@ -1,5 +1,7 @@ declare namespace JS_TESTS { type Nullable = T | null | undefined + const __doNotImplementIt: unique symbol + type __doNotImplementIt = typeof __doNotImplementIt namespace foo { interface I { x: T; diff --git a/js/js.translator/testData/typescript-export/inheritance/inheritance__main.js b/js/js.translator/testData/typescript-export/inheritance/inheritance__main.js index 4f59b96438a..9540ab956b0 100644 --- a/js/js.translator/testData/typescript-export/inheritance/inheritance__main.js +++ b/js/js.translator/testData/typescript-export/inheritance/inheritance__main.js @@ -26,12 +26,12 @@ var Impl = /** @class */ (function (_super) { }; Object.defineProperty(Impl.prototype, "acAbstractProp", { get: function () { return "Impl"; }, - enumerable: true, + enumerable: false, configurable: true }); Object.defineProperty(Impl.prototype, "y", { get: function () { return true; }, - enumerable: true, + enumerable: false, configurable: true }); return Impl; diff --git a/js/js.translator/testData/typescript-export/moduleSystems/commonjs.d.ts b/js/js.translator/testData/typescript-export/moduleSystems/commonjs.d.ts index c25552a5bbd..61f3f4212cd 100644 --- a/js/js.translator/testData/typescript-export/moduleSystems/commonjs.d.ts +++ b/js/js.translator/testData/typescript-export/moduleSystems/commonjs.d.ts @@ -1,4 +1,6 @@ type Nullable = T | null | undefined +declare const __doNotImplementIt: unique symbol +type __doNotImplementIt = typeof __doNotImplementIt export namespace foo { const prop: number; class C { diff --git a/js/js.translator/testData/typescript-export/moduleSystems/plain.d.ts b/js/js.translator/testData/typescript-export/moduleSystems/plain.d.ts index a4ae18f2ad8..5b23509e69d 100644 --- a/js/js.translator/testData/typescript-export/moduleSystems/plain.d.ts +++ b/js/js.translator/testData/typescript-export/moduleSystems/plain.d.ts @@ -1,5 +1,7 @@ declare namespace JS_TESTS { type Nullable = T | null | undefined + const __doNotImplementIt: unique symbol + type __doNotImplementIt = typeof __doNotImplementIt namespace foo { const prop: number; class C { diff --git a/js/js.translator/testData/typescript-export/moduleSystems/umd.d.ts b/js/js.translator/testData/typescript-export/moduleSystems/umd.d.ts index 001b152254b..6e9a3e175c0 100644 --- a/js/js.translator/testData/typescript-export/moduleSystems/umd.d.ts +++ b/js/js.translator/testData/typescript-export/moduleSystems/umd.d.ts @@ -1,4 +1,6 @@ type Nullable = T | null | undefined +declare const __doNotImplementIt: unique symbol +type __doNotImplementIt = typeof __doNotImplementIt export namespace foo { const prop: number; class C { diff --git a/js/js.translator/testData/typescript-export/namespaces/namespaces.d.ts b/js/js.translator/testData/typescript-export/namespaces/namespaces.d.ts index 5981f3195b2..c24c4ed8191 100644 --- a/js/js.translator/testData/typescript-export/namespaces/namespaces.d.ts +++ b/js/js.translator/testData/typescript-export/namespaces/namespaces.d.ts @@ -1,5 +1,7 @@ declare namespace JS_TESTS { type Nullable = T | null | undefined + const __doNotImplementIt: unique symbol + type __doNotImplementIt = typeof __doNotImplementIt namespace foo.bar.baz { class C1 { constructor(value: string); diff --git a/js/js.translator/testData/typescript-export/primitives/primitives.d.ts b/js/js.translator/testData/typescript-export/primitives/primitives.d.ts index b55d9e8c4b1..b5a9efab3a7 100644 --- a/js/js.translator/testData/typescript-export/primitives/primitives.d.ts +++ b/js/js.translator/testData/typescript-export/primitives/primitives.d.ts @@ -1,5 +1,7 @@ declare namespace JS_TESTS { type Nullable = T | null | undefined + const __doNotImplementIt: unique symbol + type __doNotImplementIt = typeof __doNotImplementIt namespace foo { const _any: any; const _throwable: Error; diff --git a/js/js.translator/testData/typescript-export/selectiveExport/selectiveExport.d.ts b/js/js.translator/testData/typescript-export/selectiveExport/selectiveExport.d.ts index e3fc42c0853..1080cdfff6d 100644 --- a/js/js.translator/testData/typescript-export/selectiveExport/selectiveExport.d.ts +++ b/js/js.translator/testData/typescript-export/selectiveExport/selectiveExport.d.ts @@ -1,5 +1,7 @@ declare namespace JS_TESTS { type Nullable = T | null | undefined + const __doNotImplementIt: unique symbol + type __doNotImplementIt = typeof __doNotImplementIt namespace foo { interface ExportedInternalInterface { } diff --git a/js/js.translator/testData/typescript-export/visibility/visibility.d.ts b/js/js.translator/testData/typescript-export/visibility/visibility.d.ts index 0d643071956..ab35f3c44ed 100644 --- a/js/js.translator/testData/typescript-export/visibility/visibility.d.ts +++ b/js/js.translator/testData/typescript-export/visibility/visibility.d.ts @@ -1,5 +1,7 @@ declare namespace JS_TESTS { type Nullable = T | null | undefined + const __doNotImplementIt: unique symbol + type __doNotImplementIt = typeof __doNotImplementIt interface publicInterface { } const publicVal: number;