feat(Inner Classes): generate typescript type definitions and JS for Inner Classes.
This commit is contained in:
@@ -23,7 +23,7 @@ class ExportedNamespace(
|
|||||||
val declarations: List<ExportedDeclaration>
|
val declarations: List<ExportedDeclaration>
|
||||||
) : ExportedDeclaration()
|
) : ExportedDeclaration()
|
||||||
|
|
||||||
class ExportedFunction(
|
data class ExportedFunction(
|
||||||
val name: String,
|
val name: String,
|
||||||
val returnType: ExportedType,
|
val returnType: ExportedType,
|
||||||
val parameters: List<ExportedParameter>,
|
val parameters: List<ExportedParameter>,
|
||||||
@@ -35,11 +35,16 @@ class ExportedFunction(
|
|||||||
val ir: IrSimpleFunction
|
val ir: IrSimpleFunction
|
||||||
) : ExportedDeclaration()
|
) : ExportedDeclaration()
|
||||||
|
|
||||||
class ExportedConstructor(
|
data class ExportedConstructor(
|
||||||
val parameters: List<ExportedParameter>,
|
val parameters: List<ExportedParameter>,
|
||||||
val isProtected: Boolean
|
val isProtected: Boolean
|
||||||
) : ExportedDeclaration()
|
) : ExportedDeclaration()
|
||||||
|
|
||||||
|
data class ExportedConstructSignature(
|
||||||
|
val parameters: List<ExportedParameter>,
|
||||||
|
val returnType: ExportedType,
|
||||||
|
) : ExportedDeclaration()
|
||||||
|
|
||||||
class ExportedProperty(
|
class ExportedProperty(
|
||||||
val name: String,
|
val name: String,
|
||||||
val type: ExportedType,
|
val type: ExportedType,
|
||||||
@@ -56,7 +61,7 @@ class ExportedProperty(
|
|||||||
// TODO: Cover all cases with frontend and disable error declarations
|
// TODO: Cover all cases with frontend and disable error declarations
|
||||||
class ErrorDeclaration(val message: String) : ExportedDeclaration()
|
class ErrorDeclaration(val message: String) : ExportedDeclaration()
|
||||||
|
|
||||||
class ExportedClass(
|
data class ExportedClass(
|
||||||
val name: String,
|
val name: String,
|
||||||
val isInterface: Boolean = false,
|
val isInterface: Boolean = false,
|
||||||
val isAbstract: Boolean = false,
|
val isAbstract: Boolean = false,
|
||||||
|
|||||||
+1
@@ -187,6 +187,7 @@ class ExportModelGenerator(
|
|||||||
is IrField -> {
|
is IrField -> {
|
||||||
assert(
|
assert(
|
||||||
candidate.origin == IrDeclarationOrigin.FIELD_FOR_OBJECT_INSTANCE
|
candidate.origin == IrDeclarationOrigin.FIELD_FOR_OBJECT_INSTANCE
|
||||||
|
|| candidate.origin == IrDeclarationOrigin.FIELD_FOR_OUTER_THIS
|
||||||
|| candidate.correspondingPropertySymbol != null
|
|| candidate.correspondingPropertySymbol != null
|
||||||
) {
|
) {
|
||||||
"Unexpected field without property ${candidate.fqNameWhenAvailable}"
|
"Unexpected field without property ${candidate.fqNameWhenAvailable}"
|
||||||
|
|||||||
+68
-6
@@ -8,8 +8,13 @@ package org.jetbrains.kotlin.ir.backend.js.export
|
|||||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsAstUtils
|
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsAstUtils
|
||||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.defineProperty
|
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.defineProperty
|
||||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.jsAssignment
|
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.jsAssignment
|
||||||
|
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.prototypeOf
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.IrNamer
|
import org.jetbrains.kotlin.ir.backend.js.utils.IrNamer
|
||||||
|
import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
|
||||||
|
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
|
||||||
|
import org.jetbrains.kotlin.ir.util.companionObject
|
||||||
import org.jetbrains.kotlin.js.backend.ast.*
|
import org.jetbrains.kotlin.js.backend.ast.*
|
||||||
|
import org.jetbrains.kotlin.util.collectionUtils.filterIsInstanceAnd
|
||||||
|
|
||||||
class ExportModelToJsStatements(
|
class ExportModelToJsStatements(
|
||||||
private val namer: IrNamer,
|
private val namer: IrNamer,
|
||||||
@@ -69,6 +74,7 @@ class ExportModelToJsStatements(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is ExportedConstructor -> emptyList()
|
is ExportedConstructor -> emptyList()
|
||||||
|
is ExportedConstructSignature -> emptyList()
|
||||||
|
|
||||||
is ExportedProperty -> {
|
is ExportedProperty -> {
|
||||||
require(namespace != null) { "Only namespaced properties are allowed" }
|
require(namespace != null) { "Only namespaced properties are allowed" }
|
||||||
@@ -87,25 +93,81 @@ class ExportModelToJsStatements(
|
|||||||
if (namespace == null) {
|
if (namespace == null) {
|
||||||
JsExport(name, alias = JsName(declaration.name, false))
|
JsExport(name, alias = JsName(declaration.name, false))
|
||||||
} else {
|
} else {
|
||||||
jsAssignment(
|
jsAssignment(newNameSpace, JsNameRef(name)).makeStmt()
|
||||||
newNameSpace,
|
|
||||||
JsNameRef(name)
|
|
||||||
).makeStmt()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// These are only used when exporting secondary constructors annotated with @JsName
|
// These are only used when exporting secondary constructors annotated with @JsName
|
||||||
val staticFunctions = declaration.members.filter { it is ExportedFunction && it.isStatic }
|
val staticFunctions = declaration.members
|
||||||
|
.filter { it is ExportedFunction && it.isStatic }
|
||||||
|
.takeIf { !declaration.ir.isInner }.orEmpty()
|
||||||
|
|
||||||
// Nested objects are exported as static properties
|
// Nested objects are exported as static properties
|
||||||
val staticProperties = declaration.members.mapNotNull {
|
val staticProperties = declaration.members.mapNotNull {
|
||||||
(it as? ExportedProperty)?.takeIf { it.isStatic }
|
(it as? ExportedProperty)?.takeIf { it.isStatic }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val innerClassesAssignments = declaration.nestedClasses
|
||||||
|
.filter { it.ir.isInner }
|
||||||
|
.map { it.generateInnerClassAssignment(declaration) }
|
||||||
|
|
||||||
val staticsExport = (staticFunctions + staticProperties + declaration.nestedClasses)
|
val staticsExport = (staticFunctions + staticProperties + declaration.nestedClasses)
|
||||||
.flatMap { generateDeclarationExport(it, newNameSpace) }
|
.flatMap { generateDeclarationExport(it, newNameSpace) }
|
||||||
|
|
||||||
listOf(klassExport) + staticsExport
|
listOf(klassExport) + staticsExport + innerClassesAssignments
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ExportedClass.generateInnerClassAssignment(outerClass: ExportedClass): JsStatement {
|
||||||
|
val innerClassRef = namer.getNameForStaticDeclaration(ir).makeRef()
|
||||||
|
val outerClassRef = namer.getNameForStaticDeclaration(outerClass.ir).makeRef()
|
||||||
|
val companionObject = ir.companionObject()
|
||||||
|
val secondaryConstructors = members.filterIsInstanceAnd<ExportedFunction> { it.isStatic }
|
||||||
|
val bindConstructor = JsName("__bind_constructor_", false)
|
||||||
|
|
||||||
|
val blockStatements = mutableListOf<JsStatement>(
|
||||||
|
JsVars(JsVars.JsVar(bindConstructor, innerClassRef.bindToThis()))
|
||||||
|
)
|
||||||
|
|
||||||
|
if (companionObject != null) {
|
||||||
|
val companionName = companionObject.getJsNameOrKotlinName().identifier
|
||||||
|
blockStatements.add(
|
||||||
|
jsAssignment(
|
||||||
|
JsNameRef(companionName, bindConstructor.makeRef()),
|
||||||
|
JsNameRef(companionName, innerClassRef),
|
||||||
|
).makeStmt()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
secondaryConstructors.forEach {
|
||||||
|
val currentFunRef = namer.getNameForStaticDeclaration(it.ir).makeRef()
|
||||||
|
val assignment = jsAssignment(
|
||||||
|
JsNameRef(it.name, bindConstructor.makeRef()),
|
||||||
|
currentFunRef.bindToThis()
|
||||||
|
).makeStmt()
|
||||||
|
|
||||||
|
blockStatements.add(assignment)
|
||||||
|
}
|
||||||
|
|
||||||
|
blockStatements.add(JsReturn(bindConstructor.makeRef()))
|
||||||
|
|
||||||
|
return defineProperty(
|
||||||
|
prototypeOf(outerClassRef),
|
||||||
|
name,
|
||||||
|
JsFunction(
|
||||||
|
emptyScope,
|
||||||
|
JsBlock(*blockStatements.toTypedArray()),
|
||||||
|
"inner class '$name' getter"
|
||||||
|
),
|
||||||
|
null
|
||||||
|
).makeStmt()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JsNameRef.bindToThis(): JsInvocation {
|
||||||
|
return JsInvocation(
|
||||||
|
JsNameRef("bind", this),
|
||||||
|
JsNullLiteral(),
|
||||||
|
JsThisRef()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+69
-1
@@ -5,7 +5,10 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.ir.backend.js.export
|
package org.jetbrains.kotlin.ir.backend.js.export
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName
|
import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||||
|
import org.jetbrains.kotlin.ir.util.parentAsClass
|
||||||
import org.jetbrains.kotlin.serialization.js.ModuleKind
|
import org.jetbrains.kotlin.serialization.js.ModuleKind
|
||||||
|
|
||||||
// TODO: Support module kinds other than plain
|
// TODO: Support module kinds other than plain
|
||||||
@@ -73,12 +76,18 @@ fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): Strin
|
|||||||
|
|
||||||
"${prefix}$visibility$keyword$name$renderedTypeParameters($renderedParameters): $renderedReturnType;"
|
"${prefix}$visibility$keyword$name$renderedTypeParameters($renderedParameters): $renderedReturnType;"
|
||||||
}
|
}
|
||||||
|
|
||||||
is ExportedConstructor -> {
|
is ExportedConstructor -> {
|
||||||
val visibility = if (isProtected) "protected " else ""
|
val visibility = if (isProtected) "protected " else ""
|
||||||
val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) }
|
val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) }
|
||||||
"${visibility}constructor($renderedParameters);"
|
"${visibility}constructor($renderedParameters);"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is ExportedConstructSignature -> {
|
||||||
|
val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) }
|
||||||
|
"new($renderedParameters): ${returnType.toTypeScript(indent)};"
|
||||||
|
}
|
||||||
|
|
||||||
is ExportedProperty -> {
|
is ExportedProperty -> {
|
||||||
val visibility = if (isProtected) "protected " else ""
|
val visibility = if (isProtected) "protected " else ""
|
||||||
val keyword = when {
|
val keyword = when {
|
||||||
@@ -98,7 +107,18 @@ fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): Strin
|
|||||||
" $superInterfacesKeyword " + superInterfaces.joinToString(", ") { it.toTypeScript(indent) }
|
" $superInterfacesKeyword " + superInterfaces.joinToString(", ") { it.toTypeScript(indent) }
|
||||||
} else ""
|
} else ""
|
||||||
|
|
||||||
val membersString = members.joinToString("") { it.toTypeScript("$indent ") + "\n" }
|
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 (innerClasses, nonInnerClasses) = nestedClasses.partition { it.ir.isInner }
|
||||||
|
val innerClassesProperties = innerClasses.map { it.toReadonlyProperty() }
|
||||||
|
val membersString = (members + innerClassesProperties).joinToString("") { it.toTypeScript("$indent ") + "\n" }
|
||||||
|
|
||||||
// If there are no exported constructors, add a private constructor to disable default one
|
// If there are no exported constructors, add a private constructor to disable default one
|
||||||
val privateCtorString =
|
val privateCtorString =
|
||||||
@@ -117,12 +137,60 @@ fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): Strin
|
|||||||
|
|
||||||
val bodyString = privateCtorString + membersString + indent
|
val bodyString = privateCtorString + membersString + indent
|
||||||
|
|
||||||
|
val nestedClasses = nonInnerClasses + innerClasses.map { it.withProtectedConstructors() }
|
||||||
val klassExport = "$prefix$modifiers$keyword $name$renderedTypeParameters$superClassClause$superInterfacesClause {\n$bodyString}"
|
val klassExport = "$prefix$modifiers$keyword $name$renderedTypeParameters$superClassClause$superInterfacesClause {\n$bodyString}"
|
||||||
val staticsExport = if (nestedClasses.isNotEmpty()) "\n" + ExportedNamespace(name, nestedClasses).toTypeScript(indent, prefix) else ""
|
val staticsExport = if (nestedClasses.isNotEmpty()) "\n" + ExportedNamespace(name, nestedClasses).toTypeScript(indent, prefix) else ""
|
||||||
klassExport + staticsExport
|
klassExport + staticsExport
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun IrClass.asNestedClassAccess(): String {
|
||||||
|
val name = getJsNameOrKotlinName().identifier
|
||||||
|
if (parent !is IrClass) return name
|
||||||
|
return "${parentAsClass.asNestedClassAccess()}.$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ExportedClass.withProtectedConstructors(): ExportedClass {
|
||||||
|
return copy(members = members.map {
|
||||||
|
if (it !is ExportedConstructor || it.isProtected) {
|
||||||
|
it
|
||||||
|
} else {
|
||||||
|
it.copy(isProtected = true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ExportedClass.toReadonlyProperty(): ExportedProperty {
|
||||||
|
val innerClassReference = ir.asNestedClassAccess()
|
||||||
|
val allPublicConstructors = members.asSequence()
|
||||||
|
.filterIsInstance<ExportedConstructor>()
|
||||||
|
.filterNot { it.isProtected }
|
||||||
|
.map {
|
||||||
|
ExportedConstructSignature(
|
||||||
|
parameters = it.parameters.drop(1),
|
||||||
|
returnType = ExportedType.TypeParameter(innerClassReference),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
val type = ExportedType.IntersectionType(
|
||||||
|
ExportedType.InlineInterfaceType(allPublicConstructors),
|
||||||
|
ExportedType.TypeOf(innerClassReference)
|
||||||
|
)
|
||||||
|
|
||||||
|
return ExportedProperty(
|
||||||
|
name = name,
|
||||||
|
type = type,
|
||||||
|
mutable = false,
|
||||||
|
isMember = true,
|
||||||
|
isStatic = false,
|
||||||
|
isAbstract = false,
|
||||||
|
isProtected = false,
|
||||||
|
irGetter = null,
|
||||||
|
irSetter = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun ExportedParameter.toTypeScript(indent: String): String =
|
fun ExportedParameter.toTypeScript(indent: String): String =
|
||||||
"$name: ${type.toTypeScript(indent)}"
|
"$name: ${type.toTypeScript(indent)}"
|
||||||
|
|
||||||
|
|||||||
+1
@@ -75,6 +75,7 @@ class JsInnerClassesSupport(mapping: JsMapping, private val irFactory: IrFactory
|
|||||||
returnType = oldConstructor.returnType
|
returnType = oldConstructor.returnType
|
||||||
}.also {
|
}.also {
|
||||||
it.parent = oldConstructor.parent
|
it.parent = oldConstructor.parent
|
||||||
|
it.annotations = oldConstructor.annotations
|
||||||
}
|
}
|
||||||
|
|
||||||
newConstructor.copyTypeParametersFrom(oldConstructor)
|
newConstructor.copyTypeParametersFrom(oldConstructor)
|
||||||
|
|||||||
@@ -170,6 +170,20 @@ val unzipV8 by task<Copy> {
|
|||||||
into(unpackedDir)
|
into(unpackedDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val testDataDir = project(":js:js.translator").projectDir.resolve("testData")
|
||||||
|
|
||||||
|
val installTsDependencies by task<NpmTask> {
|
||||||
|
workingDir.set(testDataDir)
|
||||||
|
args.set(listOf("install"))
|
||||||
|
}
|
||||||
|
|
||||||
|
val generateTypeScriptTests by task<NpmTask> {
|
||||||
|
dependsOn(installTsDependencies)
|
||||||
|
|
||||||
|
workingDir.set(testDataDir)
|
||||||
|
args.set(listOf("run", "generateTypeScriptTests"))
|
||||||
|
}
|
||||||
|
|
||||||
fun Test.setupV8() {
|
fun Test.setupV8() {
|
||||||
dependsOn(unzipV8)
|
dependsOn(unzipV8)
|
||||||
val v8Path = unzipV8.get().destinationDir
|
val v8Path = unzipV8.get().destinationDir
|
||||||
@@ -190,6 +204,11 @@ fun Test.setUpJsBoxTests(jsEnabled: Boolean, jsIrEnabled: Boolean) {
|
|||||||
inputs.files(rootDir.resolve("js/js.engines/src/org/jetbrains/kotlin/js/engine/repl.js"))
|
inputs.files(rootDir.resolve("js/js.engines/src/org/jetbrains/kotlin/js/engine/repl.js"))
|
||||||
|
|
||||||
dependsOn(":dist")
|
dependsOn(":dist")
|
||||||
|
|
||||||
|
if (!project.hasProperty("teamcity")) {
|
||||||
|
dependsOn(generateTypeScriptTests)
|
||||||
|
}
|
||||||
|
|
||||||
if (jsEnabled) {
|
if (jsEnabled) {
|
||||||
dependsOn(testJsRuntime)
|
dependsOn(testJsRuntime)
|
||||||
inputs.files(testJsRuntime)
|
inputs.files(testJsRuntime)
|
||||||
@@ -241,8 +260,6 @@ fun Test.setUpBoxTests() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val testDataDir = project(":js:js.translator").projectDir.resolve("testData")
|
|
||||||
|
|
||||||
projectTest(parallel = true, jUnitMode = JUnitMode.Mix) {
|
projectTest(parallel = true, jUnitMode = JUnitMode.Mix) {
|
||||||
setUpJsBoxTests(jsEnabled = true, jsIrEnabled = true)
|
setUpJsBoxTests(jsEnabled = true, jsIrEnabled = true)
|
||||||
systemProperty("kotlin.js.ir.pir", "false")
|
systemProperty("kotlin.js.ir.pir", "false")
|
||||||
|
|||||||
+18
@@ -30,6 +30,24 @@ public class IrJsTypeScriptExportES6TestGenerated extends AbstractIrJsTypeScript
|
|||||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR_ES6, true);
|
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR_ES6, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("js/js.translator/testData/typescript-export/classes")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class Classes extends AbstractIrJsTypeScriptExportES6Test {
|
||||||
|
private void runTest(String testDataFilePath) throws Exception {
|
||||||
|
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR_ES6, testDataFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAllFilesPresentInClasses() throws Exception {
|
||||||
|
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/classes"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR_ES6, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("inner-class.kt")
|
||||||
|
public void testInner_class() throws Exception {
|
||||||
|
runTest("js/js.translator/testData/typescript-export/classes/inner-class.kt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("js/js.translator/testData/typescript-export/constructors")
|
@TestMetadata("js/js.translator/testData/typescript-export/constructors")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
+18
@@ -30,6 +30,24 @@ public class IrJsTypeScriptExportTestGenerated extends AbstractIrJsTypeScriptExp
|
|||||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true);
|
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("js/js.translator/testData/typescript-export/classes")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class Classes extends AbstractIrJsTypeScriptExportTest {
|
||||||
|
private void runTest(String testDataFilePath) throws Exception {
|
||||||
|
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAllFilesPresentInClasses() throws Exception {
|
||||||
|
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/classes"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("inner-class.kt")
|
||||||
|
public void testInner_class() throws Exception {
|
||||||
|
runTest("js/js.translator/testData/typescript-export/classes/inner-class.kt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("js/js.translator/testData/typescript-export/constructors")
|
@TestMetadata("js/js.translator/testData/typescript-export/constructors")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
declare namespace JS_TESTS {
|
||||||
|
type Nullable<T> = T | null | undefined
|
||||||
|
namespace foo {
|
||||||
|
class TestInner {
|
||||||
|
constructor(a: string);
|
||||||
|
readonly a: string;
|
||||||
|
readonly Inner: {
|
||||||
|
new(a: string): TestInner.Inner;
|
||||||
|
} & typeof TestInner.Inner;
|
||||||
|
}
|
||||||
|
namespace TestInner {
|
||||||
|
class Inner {
|
||||||
|
protected constructor($outer: foo.TestInner, a: string);
|
||||||
|
readonly a: string;
|
||||||
|
readonly concat: string;
|
||||||
|
static fromNumber(a: number): foo.TestInner.Inner;
|
||||||
|
readonly SecondLayerInner: {
|
||||||
|
new(a: string): TestInner.Inner.SecondLayerInner;
|
||||||
|
} & typeof TestInner.Inner.SecondLayerInner;
|
||||||
|
}
|
||||||
|
namespace Inner {
|
||||||
|
class SecondLayerInner {
|
||||||
|
protected constructor($outer: foo.TestInner.Inner, a: string);
|
||||||
|
readonly a: string;
|
||||||
|
readonly concat: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// CHECK_TYPESCRIPT_DECLARATIONS
|
||||||
|
// RUN_PLAIN_BOX_FUNCTION
|
||||||
|
// SKIP_MINIFICATION
|
||||||
|
// SKIP_NODE_JS
|
||||||
|
// IGNORE_BACKEND: JS
|
||||||
|
// INFER_MAIN_MODULE
|
||||||
|
// MODULE: JS_TESTS
|
||||||
|
// FILE: inner-class.kt
|
||||||
|
|
||||||
|
package foo
|
||||||
|
|
||||||
|
@JsExport
|
||||||
|
class TestInner(val a: String) {
|
||||||
|
inner class Inner(val a: String) {
|
||||||
|
val concat: String = this@TestInner.a + this.a
|
||||||
|
|
||||||
|
@JsName("fromNumber")
|
||||||
|
constructor(a: Int): this(a.toString())
|
||||||
|
|
||||||
|
@JsName("SecondLayerInner")
|
||||||
|
inner class InnerInner(val a: String) {
|
||||||
|
val concat: String = this@TestInner.a + this@Inner.a + this.a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
"use strict";
|
||||||
|
var TestInner = JS_TESTS.foo.TestInner;
|
||||||
|
function assert(condition) {
|
||||||
|
if (!condition) {
|
||||||
|
throw "Assertion failed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function box() {
|
||||||
|
var outer = new TestInner("Hello ");
|
||||||
|
var inner = new outer.Inner("World");
|
||||||
|
var innerFromNumber = outer.Inner.fromNumber(1654);
|
||||||
|
var secondInner = new inner.SecondLayerInner("!");
|
||||||
|
assert(outer instanceof TestInner);
|
||||||
|
assert(inner instanceof TestInner.Inner);
|
||||||
|
assert(innerFromNumber instanceof TestInner.Inner);
|
||||||
|
assert(secondInner instanceof TestInner.Inner.SecondLayerInner);
|
||||||
|
assert(inner.a == "World");
|
||||||
|
assert(inner.concat == "Hello World");
|
||||||
|
assert(innerFromNumber.a == "1654");
|
||||||
|
assert(innerFromNumber.concat == "Hello 1654");
|
||||||
|
assert(secondInner.a == "!");
|
||||||
|
assert(secondInner.concat == "Hello World!");
|
||||||
|
return "OK";
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import TestInner = JS_TESTS.foo.TestInner;
|
||||||
|
|
||||||
|
function assert(condition: boolean) {
|
||||||
|
if (!condition) {
|
||||||
|
throw "Assertion failed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function box(): string {
|
||||||
|
const outer = new TestInner("Hello ")
|
||||||
|
const inner = new outer.Inner("World")
|
||||||
|
const innerFromNumber = outer.Inner.fromNumber(1654)
|
||||||
|
const secondInner = new inner.SecondLayerInner("!")
|
||||||
|
|
||||||
|
assert(outer instanceof TestInner)
|
||||||
|
assert(inner instanceof TestInner.Inner)
|
||||||
|
assert(innerFromNumber instanceof TestInner.Inner)
|
||||||
|
assert(secondInner instanceof TestInner.Inner.SecondLayerInner)
|
||||||
|
|
||||||
|
assert(inner.a == "World")
|
||||||
|
assert(inner.concat == "Hello World")
|
||||||
|
|
||||||
|
assert(innerFromNumber.a == "1654")
|
||||||
|
assert(innerFromNumber.concat == "Hello 1654")
|
||||||
|
|
||||||
|
assert(secondInner.a == "!")
|
||||||
|
assert(secondInner.concat == "Hello World!")
|
||||||
|
|
||||||
|
return "OK";
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"include": [ "./*" ],
|
||||||
|
"extends": "../common.tsconfig.json"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user