From 9594e9b3b1bb599207a720e9cec773315349a02d Mon Sep 17 00:00:00 2001 From: Svyatoslav Kuzmich Date: Mon, 5 Aug 2019 17:38:18 +0300 Subject: [PATCH] [JS IR BE] Initial export generation --- .../jetbrains/kotlin/cli/js/K2JsIrCompiler.kt | 2 +- .../ir/backend/js/JsIrBackendContext.kt | 7 +- .../kotlin/ir/backend/js/JsLoweringPhases.kt | 12 +- .../kotlin/ir/backend/js/compiler.kt | 15 +- .../ir/backend/js/export/ExportModel.kt | 97 +++++ .../backend/js/export/ExportModelGenerator.kt | 408 ++++++++++++++++++ .../js/export/ExportModelToJsStatements.kt | 93 ++++ .../js/export/ExportModelToTsDeclarations.kt | 101 +++++ ...MoveBodilessDeclarationsToSeparatePlace.kt | 27 +- .../irToJs/IrModuleToJsTransformer.kt | 120 +----- .../kotlin/ir/backend/js/utils/NameTables.kt | 8 +- .../generators/tests/GenerateJsTests.kt | 5 + .../test/AbstractIrJsTypeScriptExportTest.kt | 35 ++ .../jetbrains/kotlin/js/test/BasicBoxTest.kt | 13 +- .../kotlin/js/test/BasicIrBoxTest.kt | 21 +- .../kotlin/js/test/BasicWasmBoxTest.kt | 3 +- .../IrJsTypeScriptExportTestGenerated.java | 139 ++++++ .../ir/semantics/IrBoxJsTestGenerated.java | 10 - .../box/propertyAccess/accessorsWithJsName.kt | 1 + .../defaultAccessorsWithJsName.kt | 1 + .../staticAccessorsWithJsName.kt | 1 + js/js.translator/testData/package.json | 10 +- .../typescript-export/common.tsconfig.json | 6 + .../declarations/declarations.d.ts | 46 ++ .../declarations/declarations.kt | 84 ++++ .../declarations/declarations__main.js | 79 ++++ .../declarations/declarations__main.ts | 90 ++++ .../declarations/tsconfig.json | 4 + .../inheritance/inheritance.d.ts | 34 ++ .../inheritance/inheritance.kt | 41 ++ .../inheritance/inheritance__main.js | 60 +++ .../inheritance/inheritance__main.ts | 33 ++ .../inheritance/tsconfig.json | 4 + .../namespaces/namespaces.d.ts | 37 ++ .../namespaces/namespaces.kt | 47 ++ .../namespaces/namespaces__main.js | 19 + .../namespaces/namespaces__main.ts | 24 ++ .../namespaces/tsconfig.json | 4 + .../primitives/primitives.d.ts | 50 +++ .../primitives/primitives.kt | 93 ++++ .../primitives/primitives__main.js | 58 +++ .../primitives/primitives__main.ts | 69 +++ .../primitives/tsconfig.json | 4 + .../selectiveExport/selectiveExport.d.ts | 27 ++ .../selectiveExport/selectiveExport.kt | 43 ++ .../selectiveExport/selectiveExport__main.js | 15 + .../selectiveExport/selectiveExport__main.ts | 17 + .../selectiveExport/tsconfig.json | 4 + .../visibility/tsconfig.json | 4 + .../visibility/visibility.d.ts | 20 + .../visibility/visibility.kt | 38 ++ .../visibility/visibility__main.js | 17 + .../visibility/visibility__main.ts | 18 + 53 files changed, 2063 insertions(+), 155 deletions(-) create mode 100644 compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt create mode 100644 compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt create mode 100644 compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt create mode 100644 compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt create mode 100644 js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractIrJsTypeScriptExportTest.kt create mode 100644 js/js.tests/test/org/jetbrains/kotlin/js/test/IrJsTypeScriptExportTestGenerated.java create mode 100644 js/js.translator/testData/typescript-export/common.tsconfig.json create mode 100644 js/js.translator/testData/typescript-export/declarations/declarations.d.ts create mode 100644 js/js.translator/testData/typescript-export/declarations/declarations.kt create mode 100644 js/js.translator/testData/typescript-export/declarations/declarations__main.js create mode 100644 js/js.translator/testData/typescript-export/declarations/declarations__main.ts create mode 100644 js/js.translator/testData/typescript-export/declarations/tsconfig.json create mode 100644 js/js.translator/testData/typescript-export/inheritance/inheritance.d.ts create mode 100644 js/js.translator/testData/typescript-export/inheritance/inheritance.kt create mode 100644 js/js.translator/testData/typescript-export/inheritance/inheritance__main.js create mode 100644 js/js.translator/testData/typescript-export/inheritance/inheritance__main.ts create mode 100644 js/js.translator/testData/typescript-export/inheritance/tsconfig.json create mode 100644 js/js.translator/testData/typescript-export/namespaces/namespaces.d.ts create mode 100644 js/js.translator/testData/typescript-export/namespaces/namespaces.kt create mode 100644 js/js.translator/testData/typescript-export/namespaces/namespaces__main.js create mode 100644 js/js.translator/testData/typescript-export/namespaces/namespaces__main.ts create mode 100644 js/js.translator/testData/typescript-export/namespaces/tsconfig.json create mode 100644 js/js.translator/testData/typescript-export/primitives/primitives.d.ts create mode 100644 js/js.translator/testData/typescript-export/primitives/primitives.kt create mode 100644 js/js.translator/testData/typescript-export/primitives/primitives__main.js create mode 100644 js/js.translator/testData/typescript-export/primitives/primitives__main.ts create mode 100644 js/js.translator/testData/typescript-export/primitives/tsconfig.json create mode 100644 js/js.translator/testData/typescript-export/selectiveExport/selectiveExport.d.ts create mode 100644 js/js.translator/testData/typescript-export/selectiveExport/selectiveExport.kt create mode 100644 js/js.translator/testData/typescript-export/selectiveExport/selectiveExport__main.js create mode 100644 js/js.translator/testData/typescript-export/selectiveExport/selectiveExport__main.ts create mode 100644 js/js.translator/testData/typescript-export/selectiveExport/tsconfig.json create mode 100644 js/js.translator/testData/typescript-export/visibility/tsconfig.json create mode 100644 js/js.translator/testData/typescript-export/visibility/visibility.d.ts create mode 100644 js/js.translator/testData/typescript-export/visibility/visibility.kt create mode 100644 js/js.translator/testData/typescript-export/visibility/visibility__main.js create mode 100644 js/js.translator/testData/typescript-export/visibility/visibility__main.ts diff --git a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt index 9b67e662c18..6b64454bf2b 100644 --- a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt +++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt @@ -189,7 +189,7 @@ class K2JsIrCompiler : CLICompiler() { mainArguments = mainCallArguments ) - outputFile.writeText(compiledModule) + outputFile.writeText(compiledModule.jsCode) } return OK diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt index 255e19e84a5..b018332ea88 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt @@ -29,10 +29,7 @@ import org.jetbrains.kotlin.ir.backend.js.utils.OperatorNames import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns -import org.jetbrains.kotlin.ir.symbols.IrClassSymbol -import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol -import org.jetbrains.kotlin.ir.symbols.IrEnumEntrySymbol -import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol +import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.impl.IrDynamicTypeImpl import org.jetbrains.kotlin.ir.util.* @@ -54,7 +51,7 @@ class JsIrBackendContext( override var inVerbosePhase: Boolean = false - var externalPackageFragment = mutableMapOf() + var externalPackageFragment = mutableMapOf() lateinit var bodilessBuiltInsPackageFragment: IrPackageFragment val externalNestedClasses = mutableListOf() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt index 38f073be178..b92cc7bcd16 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt @@ -78,14 +78,6 @@ private val validateIrAfterLowering = makeCustomJsModulePhase( description = "Validate IR after lowering" ) -private val moveBodilessDeclarationsToSeparatePlacePhase = makeCustomJsModulePhase( - { context, module -> - moveBodilessDeclarationsToSeparatePlace(context, module) - }, - name = "MoveBodilessDeclarationsToSeparatePlace", - description = "Move `external` and `built-in` declarations into separate place to make the following lowerings do not care about them" -) - private val expectDeclarationsRemovingPhase = makeJsModulePhase( ::ExpectDeclarationsRemoveLowering, name = "ExpectDeclarationsRemoving", @@ -419,7 +411,6 @@ val jsPhases = namedIrModulePhase( primaryConstructorLoweringPhase then initializersLoweringPhase then // Common prefix ends - moveBodilessDeclarationsToSeparatePlacePhase then enumClassLoweringPhase then enumUsageLoweringPhase then suspendFunctionsLoweringPhase then @@ -447,6 +438,5 @@ val jsPhases = namedIrModulePhase( objectDeclarationLoweringPhase then objectUsageLoweringPhase then callsLoweringPhase then - staticMembersLoweringPhase then validateIrAfterLowering -) \ No newline at end of file +) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt index 250632d5bd2..e32b440749a 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt @@ -9,6 +9,7 @@ import com.intellij.openapi.project.Project import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.ir.backend.js.lower.moveBodilessDeclarationsToSeparatePlace import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer import org.jetbrains.kotlin.ir.backend.js.utils.JsMainFunctionDetector import org.jetbrains.kotlin.ir.declarations.IrModuleFragment @@ -28,6 +29,11 @@ fun sortDependencies(dependencies: Collection): Collection, @@ -37,7 +43,7 @@ fun compile( friendDependencies: List, mainArguments: List?, exportedDeclarations: Set = emptySet() -): String { +): CompilerResult { val (moduleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer) = loadIr(project, files, configuration, allDependencies, friendDependencies) @@ -71,9 +77,10 @@ fun compile( ).generateUnboundSymbolsAsDependencies() moduleFragment.patchDeclarationParents() + moveBodilessDeclarationsToSeparatePlace(context, moduleFragment) + jsPhases.invokeToplevel(phaseConfig, context, moduleFragment) - val jsProgram = - moduleFragment.accept(IrModuleToJsTransformer(context, mainFunction, mainArguments), null) - return jsProgram.toString() + val transformer = IrModuleToJsTransformer(context, mainFunction, mainArguments) + return transformer.generateModule(moduleFragment) } \ No newline at end of file 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 new file mode 100644 index 00000000000..16bc5223f81 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt @@ -0,0 +1,97 @@ +/* + * Copyright 2010-2019 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.export + +import org.jetbrains.kotlin.ir.declarations.* + +sealed class ExportedDeclaration + +data class ExportedModule( + val name: String, + val declarations: List +) + +class ExportedNamespace( + val name: String, + val declarations: List +) : ExportedDeclaration() + +class ExportedFunction( + val name: String, + val returnType: ExportedType, + val parameters: List, + val typeParameters: List = emptyList(), + val isMember: Boolean = false, + val isStatic: Boolean = false, + val isAbstract: Boolean = false, + val ir: IrSimpleFunction +) : ExportedDeclaration() + +class ExportedConstructor( + val parameters: List +) : ExportedDeclaration() + +class ExportedProperty( + val name: String, + val type: ExportedType, + val mutable: Boolean, + val isMember: Boolean = false, + val isStatic: Boolean = false, + val isAbstract: Boolean, + val ir: IrProperty +) : ExportedDeclaration() + + +// TODO: Cover all cases with frontend and disable error declarations +class ErrorDeclaration(val message: String) : ExportedDeclaration() + +class ExportedClass( + val name: String, + val isInterface: Boolean = false, + val isAbstract: Boolean = false, + val superClass: ExportedType? = null, + val superInterfaces: List = emptyList(), + val typeParameters: List, + val members: List, + val statics: List, + val ir: IrClass +) : ExportedDeclaration() + +class ExportedParameter( + val name: String, + val type: ExportedType +) + +sealed class ExportedType { + sealed class Primitive(val typescript: kotlin.String) : ExportedType() { + object Boolean : Primitive("boolean") + object Number : Primitive("number") + object ByteArray : Primitive("Int8Array") + object ShortArray : Primitive("Int16Array") + object IntArray : Primitive("Int32Array") + object FloatArray : Primitive("Float32Array") + object DoubleArray : Primitive("Float64Array") + object String : Primitive("string") + object Throwable : Primitive("Error") + object Any : Primitive("any") + object Unit : Primitive("void") + object Nothing : Primitive("never") + } + + class Array(val elementType: ExportedType) : ExportedType() + class Function( + val parameterTypes: List, + val returnType: ExportedType + ) : ExportedType() + + class ClassType(val name: String, val arguments: List) : ExportedType() + class TypeParameter(val name: String) : ExportedType() + class Nullable(val baseType: ExportedType) : ExportedType() + class ErrorType(val comment: String) : ExportedType() + + fun withNullability(nullable: Boolean) = + if (nullable) Nullable(this) else this +} 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 new file mode 100644 index 00000000000..11235b061f5 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt @@ -0,0 +1,408 @@ +/* + * Copyright 2010-2019 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.export + +import org.jetbrains.kotlin.backend.common.ir.isExpect +import org.jetbrains.kotlin.backend.common.ir.isMethodOfAny +import org.jetbrains.kotlin.config.CommonConfigurationKeys +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.ir.backend.js.* +import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName +import org.jetbrains.kotlin.ir.backend.js.utils.isJsExport +import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.symbols.IrClassSymbol +import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol +import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.FqNameUnsafe +import org.jetbrains.kotlin.utils.addIfNotNull + +class ExportModelGenerator(val context: JsIrBackendContext) { + + private fun generateExport(file: IrPackageFragment): List { + val namespaceFqName = file.fqName + val exports = file.declarations.flatMap { declaration -> listOfNotNull(exportDeclaration(declaration)) } + return when { + exports.isEmpty() -> emptyList() + namespaceFqName.isRoot -> exports + else -> listOf(ExportedNamespace(namespaceFqName.toString(), exports)) + } + } + + fun generateExport(module: IrModuleFragment): ExportedModule = + ExportedModule( + sanitizeName(context.configuration[CommonConfigurationKeys.MODULE_NAME]!!), + (context.externalPackageFragment.values + module.files).flatMap { + generateExport(it) + } + ) + + private fun getExportCandidate(declaration: IrDeclaration): IrDeclarationWithName? { + // Only actual public declarations with name can be exported + if (declaration !is IrDeclarationWithVisibility || + declaration !is IrDeclarationWithName || + declaration.visibility != Visibilities.PUBLIC || + declaration.isExpect + ) { + return null + } + + // Workaround to get property declarations instead of its lowered accessors. + if (declaration is IrSimpleFunction) { + val property = declaration.correspondingPropertySymbol?.owner + if (property != null) { + // Return property for getter accessors only to prevent + // returning it twice (for getter and setter) in the same scope + return if (property.getter == declaration) + property + else + null + } + } + + return declaration + } + + private fun exportDeclaration(declaration: IrDeclaration): ExportedDeclaration? { + val candidate = getExportCandidate(declaration) ?: return null + if (!shouldDeclarationBeExported(candidate)) return null + + return when (candidate) { + is IrSimpleFunction -> exportFunction(candidate) + is IrProperty -> exportProperty(candidate) + is IrClass -> exportClass(candidate) + is IrField -> null + else -> error("Can't export declaration $candidate") + } + } + + private fun exportFunction(function: IrSimpleFunction): ExportedDeclaration? = + when (val exportability = functionExportability(function)) { + is Exportability.NotNeeded -> null + is Exportability.Prohibited -> ErrorDeclaration(exportability.reason) + is Exportability.Allowed -> { + val parent = function.parent + ExportedFunction( + function.getExportedIdentifier(), + returnType = exportType(function.returnType), + parameters = (listOfNotNull(function.extensionReceiverParameter) + function.valueParameters).map { exportParameter(it) }, + typeParameters = function.typeParameters.map { it.name.identifier }, + isMember = parent is IrClass, + isStatic = function.isStaticMethodOfClass, + isAbstract = parent is IrClass && !parent.isInterface && function.modality == Modality.ABSTRACT, + ir = function + ) + } + } + + private fun exportConstructor(constructor: IrConstructor): ExportedDeclaration? { + if (!constructor.isPrimary) return null + val allValueParameters = listOfNotNull(constructor.extensionReceiverParameter) + constructor.valueParameters + return ExportedConstructor(allValueParameters.map { exportParameter(it) }) + } + + private fun exportParameter(parameter: IrValueParameter): ExportedParameter { + // Parameter names do not matter in d.ts files. They can be renamed as we like + var parameterName = sanitizeName(parameter.name.asString()) + if (parameterName in allReservedWords) + parameterName = "_$parameterName" + + return ExportedParameter(parameterName, exportType(parameter.type)) + } + + private fun exportProperty(property: IrProperty): ExportedDeclaration? { + for (accessor in listOfNotNull(property.getter, property.setter)) { + // TODO: Report a frontend error + if (accessor.extensionReceiverParameter != null) + return null + if (accessor.isFakeOverride) { + return null + } + } + + val parentClass = property.parent as? IrClass + + return ExportedProperty( + property.getExportedIdentifier(), + exportType(property.getter!!.returnType), + mutable = property.isVar, + isMember = parentClass != null, + isStatic = false, + isAbstract = parentClass?.isInterface == false && property.modality == Modality.ABSTRACT, + ir = property + ) + } + + private fun classExportability(klass: IrClass): Exportability { + when (klass.kind) { + ClassKind.ANNOTATION_CLASS, + ClassKind.ENUM_CLASS, + ClassKind.ENUM_ENTRY, + ClassKind.OBJECT -> + return Exportability.Prohibited("Class ${klass.fqNameWhenAvailable} with kind: ${klass.kind}") + + ClassKind.CLASS, + ClassKind.INTERFACE -> { + } + } + + if (klass.isInline) + return Exportability.Prohibited("Inline class ${klass.fqNameWhenAvailable}") + + return Exportability.Allowed + } + + private fun exportClass( + klass: IrClass + ): ExportedDeclaration? { + when (val exportability = classExportability(klass)) { + is Exportability.Prohibited -> return ErrorDeclaration(exportability.reason) + is Exportability.NotNeeded -> return null + } + + val members = mutableListOf() + val statics = mutableListOf() + + for (declaration in klass.declarations) { + val candidate = getExportCandidate(declaration) ?: continue + if (!shouldDeclarationBeExported(candidate)) continue + + when (candidate) { + is IrSimpleFunction -> + members.addIfNotNull(exportFunction(candidate)) + + is IrConstructor -> + members.addIfNotNull(exportConstructor(candidate)) + + is IrProperty -> + members.addIfNotNull(exportProperty(candidate)) + + is IrClass -> + statics.addIfNotNull(exportClass(candidate)) + + is IrField -> { + assert(candidate.correspondingPropertySymbol != null) { + "Properties without fields are not supported ${candidate.fqNameWhenAvailable}" + } + } + + else -> error("Can't export member declaration $declaration") + } + } + + val typeParameters = klass.typeParameters.map { it.name.identifier } + + // TODO: Handle non-exported super types + + val superType = klass.superTypes + .firstOrNull { !it.classifierOrFail.isInterface && !it.isAny() } + ?.let { exportType(it).takeIf { it !is ExportedType.ErrorType } } + + val superInterfaces = klass.superTypes + .filter {it.classifierOrFail.isInterface } + .map { exportType(it) } + .filter { it !is ExportedType.ErrorType } + + val name = klass.getExportedIdentifier() + + return ExportedClass( + name = name, + isInterface = klass.isInterface, + isAbstract = klass.modality == Modality.ABSTRACT, + superClass = superType, + superInterfaces = superInterfaces, + typeParameters = typeParameters, + members = members, + statics = statics, + ir = klass + ) + } + + private fun exportTypeArgument(type: IrTypeArgument): ExportedType { + if (type is IrTypeProjection) + return exportType(type.type) + + if (type is IrType) + return exportType(type) + + return ExportedType.ErrorType("UnknownType ${type.render()}") + } + + private fun exportType(type: IrType): ExportedType { + if (type is IrDynamicType) + return ExportedType.Primitive.Any + + if (type !is IrSimpleType) + return ExportedType.ErrorType("NonSimpleType ${type.render()}") + + val classifier = type.classifier + val isNullable = type.hasQuestionMark + val nonNullType = type.makeNotNull() as IrSimpleType + + val exportedType = when { + nonNullType.isBoolean() -> ExportedType.Primitive.Boolean + nonNullType.isPrimitiveType() && (!nonNullType.isLong() && !nonNullType.isChar()) -> + ExportedType.Primitive.Number + + nonNullType.isByteArray() -> ExportedType.Primitive.ByteArray + nonNullType.isShortArray() -> ExportedType.Primitive.ShortArray + nonNullType.isIntArray() -> ExportedType.Primitive.IntArray + nonNullType.isFloatArray() -> ExportedType.Primitive.FloatArray + nonNullType.isDoubleArray() -> ExportedType.Primitive.DoubleArray + + // TODO: Cover these in frontend + nonNullType.isBooleanArray() -> ExportedType.ErrorType("BooleanArray") + nonNullType.isLongArray() -> ExportedType.ErrorType("LongArray") + nonNullType.isCharArray() -> ExportedType.ErrorType("CharArray") + + nonNullType.isString() -> ExportedType.Primitive.String + nonNullType.isThrowable() -> ExportedType.Primitive.Throwable + nonNullType.isAny() -> ExportedType.Primitive.Any // TODO: Should we wrap Any in a Nullable type? + nonNullType.isUnit() -> ExportedType.Primitive.Unit + nonNullType.isNothing() -> ExportedType.Primitive.Nothing + nonNullType.isArray() -> ExportedType.Array(exportTypeArgument(nonNullType.arguments[0])) + nonNullType.isSuspendFunction() -> ExportedType.ErrorType("Suspend functions are not supported") + nonNullType.isFunction() -> ExportedType.Function( + parameterTypes = nonNullType.arguments.dropLast(1).map { exportTypeArgument(it) }, + returnType = exportTypeArgument(nonNullType.arguments.last()) + ) + + classifier is IrTypeParameterSymbol -> ExportedType.TypeParameter(classifier.owner.name.identifier) + + classifier is IrClassSymbol -> { + val klass = classifier.owner + when (val exportability = classExportability(klass)) { + is Exportability.Prohibited -> ExportedType.ErrorType(exportability.reason) + is Exportability.NotNeeded -> error("Not needed classes types cannot be used") + else -> ExportedType.ClassType( + klass.fqNameWhenAvailable!!.asString(), + type.arguments.map { exportTypeArgument(it) } + ) + } + } + + else -> error("Unexpected classifier $classifier") + } + + return exportedType.withNullability(isNullable) + } + + private fun IrDeclarationWithName.getExportedIdentifier(): String = + with(getJsNameOrKotlinName()) { + if (isSpecial) + error("Cannot export special name: ${name.asString()} for declaration $fqNameWhenAvailable") + else identifier + } + + private fun shouldDeclarationBeExported(declaration: IrDeclarationWithName): Boolean { + if (declaration.fqNameWhenAvailable in context.additionalExportedDeclarations) + return true + + if (declaration.isJsExport()) + return true + + return when (val parent = declaration.parent) { + is IrDeclarationWithName -> shouldDeclarationBeExported(parent) + is IrAnnotationContainer -> parent.isJsExport() + else -> false + } + } + + private fun functionExportability(function: IrSimpleFunction): Exportability { + if (function.isInline && function.typeParameters.any { it.isReified }) + return Exportability.Prohibited("Inline reified function") + if (function.isSuspend) + return Exportability.Prohibited("Suspend function") + if (function.isFakeOverride) + return Exportability.NotNeeded + if (function.origin == IrDeclarationOrigin.BRIDGE || + function.origin == JsLoweredDeclarationOrigin.BRIDGE_TO_EXTERNAL_FUNCTION || + function.origin == IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER + ) { + return Exportability.NotNeeded + } + + if (function.isFakeOverriddenFromAny()) + return Exportability.NotNeeded + if (function.name.asString().endsWith("-impl")) + return Exportability.NotNeeded + + val name = function.getExportedIdentifier() + // TODO: Use [] syntax instead of prohibiting + if (name in allReservedWords) + return Exportability.Prohibited("Name is a reserved word") + + return Exportability.Allowed + } +} + +sealed class Exportability { + object Allowed : Exportability() + object NotNeeded : Exportability() + class Prohibited(val reason: String) : Exportability() +} + +private val IrClassifierSymbol.isInterface + get() = (owner as? IrClass)?.isInterface == true + +private val reservedWords = setOf( + "break", + "case", + "catch", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "else", + "enum", + "export", + "extends", + "false", + "finally", + "for", + "function", + "if", + "import", + "in", + "instanceof", + "new", + "null", + "return", + "super", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "var", + "void", + "while", + "with" +) + +val strictModeReservedWords = setOf( + "as", + "implements", + "interface", + "let", + "package", + "private", + "protected", + "public", + "static", + "yield" +) + +private val allReservedWords = reservedWords + strictModeReservedWords \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt new file mode 100644 index 00000000000..f1693a9ed8e --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2010-2019 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.export + +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.jsAssignment +import org.jetbrains.kotlin.ir.backend.js.utils.* +import org.jetbrains.kotlin.js.backend.ast.* + + +class ExportModelToJsStatements( + private val internalModuleName: JsName, + private val nameTables: NameTables +) { + private val namespaceToRefMap = mutableMapOf() + private val globalNames = NameTable(nameTables.globalNames) + + fun generateModuleExport(module: ExportedModule): List { + return module.declarations.flatMap { generateDeclarationExport(it, JsNameRef(internalModuleName)) } + } + + private fun generateDeclarationExport(declaration: ExportedDeclaration, namespace: JsNameRef): List { + return when (declaration) { + is ExportedNamespace -> { + val statements = mutableListOf() + val elements = declaration.name.split(".") + var currentNamespace = "" + var currentRef = namespace + for (element in elements) { + val newNamespace = "$currentNamespace$$element" + val newNameSpaceRef = namespaceToRefMap.getOrPut(newNamespace) { + val varName = globalNames.declareFreshName(newNamespace, newNamespace) + val varRef = JsNameRef(varName) + val namespaceRef = JsNameRef(element, currentRef) + statements += JsVars( + JsVars.JsVar(JsName(varName), + JsAstUtils.or( + namespaceRef, + jsAssignment( + namespaceRef, + JsObjectLiteral() + ) + ) + ) + ) + varRef + } + currentRef = newNameSpaceRef + currentNamespace = newNamespace + } + statements + declaration.declarations.flatMap { generateDeclarationExport(it, currentRef) } + } + + is ExportedFunction -> { + listOf( + jsAssignment( + JsNameRef(declaration.name, namespace), + JsNameRef(nameTables.getNameForStaticDeclaration(declaration.ir)) + ).makeStmt() + ) + } + + is ExportedConstructor -> emptyList() + + is ExportedProperty -> { + val getter = declaration.ir.getter?.let { JsNameRef(nameTables.getNameForStaticDeclaration(it)) } + val setter = declaration.ir.setter?.let { JsNameRef(nameTables.getNameForStaticDeclaration(it)) } + listOf(defineProperty(namespace, declaration.name, getter, setter).makeStmt()) + } + + is ErrorDeclaration -> emptyList() + + is ExportedClass -> { + if (declaration.isInterface) return emptyList() + val newNameSpace = JsNameRef(declaration.name, namespace) + val klassExport = jsAssignment( + newNameSpace, + JsNameRef( + nameTables.getNameForStaticDeclaration( + declaration.ir + ) + ) + ).makeStmt() + val staticsExport = declaration.statics.flatMap { generateDeclarationExport(it, newNameSpace) } + listOf(klassExport) + staticsExport + } + } + } +} 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 new file mode 100644 index 00000000000..12c8477e7d9 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2010-2019 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.export + +// TODO: Support module kinds other than plain + +fun ExportedModule.toTypeScript(): String { + val prefix = " type Nullable = T | null | undefined\n" + val body = declarations.joinToString("\n") { it.toTypeScript(" ") } + return "declare namespace $name {\n$prefix$body\n}\n" +} + +fun List.toTypeScript(indent: String): String = + joinToString("") { it.toTypeScript(indent) + "\n" } + +fun ExportedDeclaration.toTypeScript(indent: String): String = indent + when (this) { + is ErrorDeclaration -> "/* ErrorDeclaration: $message */" + + is ExportedNamespace -> + "namespace $name {\n" + declarations.toTypeScript("$indent ") + "$indent}" + + is ExportedFunction -> { + val keyword: String = when { + isMember -> when { + isStatic -> "static " + isAbstract -> "abstract " + else -> "" + } + else -> "function " + } + + val renderedParameters = parameters.joinToString(", ") { it.toTypeScript() } + + val renderedTypeParameters = + if (typeParameters.isNotEmpty()) + "<" + typeParameters.joinToString(", ") + ">" + else + "" + + val renderedReturnType = returnType.toTypeScript() + + "$keyword$name$renderedTypeParameters($renderedParameters): $renderedReturnType" + } + is ExportedConstructor -> + "constructor(${parameters.joinToString(", ") { it.toTypeScript() }})" + + is ExportedProperty -> { + val keyword = when { + isMember -> (if (isAbstract) "abstract " else "") + (if (!mutable) "readonly " else "") + else -> if (mutable) "let " else "const " + } + keyword + name + ": " + type.toTypeScript() + ";" + } + + is ExportedClass -> { + val keyword = if (isInterface) "interface" else "class" + val superInterfacesKeyword = if (isInterface) "extends" else "implements" + + val superClassClause = superClass?.let { " extends ${it.toTypeScript()}" } ?: "" + val superInterfacesClause = if (superInterfaces.isNotEmpty()) { + " $superInterfacesKeyword " + superInterfaces.joinToString(", ") { it.toTypeScript() } + } else "" + + val membersString = members.joinToString("") { it.toTypeScript("$indent ") + "\n" } + + val renderedTypeParameters = + if (typeParameters.isNotEmpty()) + "<" + typeParameters.joinToString(", ") + ">" + else + "" + + val modifiers = if (isAbstract && !isInterface) "abstract " else "" + + val klassExport = "$modifiers$keyword $name$renderedTypeParameters$superClassClause$superInterfacesClause {\n$membersString$indent}" + val staticsExport = if (statics.isNotEmpty()) "\n" + ExportedNamespace(name, statics).toTypeScript(indent) else "" + klassExport + staticsExport + } +} + +fun ExportedParameter.toTypeScript(): String = + "$name: ${type.toTypeScript()}" + +fun ExportedType.toTypeScript(): String = when (this) { + is ExportedType.Primitive -> typescript + is ExportedType.Array -> "Array<${elementType.toTypeScript()}>" + is ExportedType.Function -> "(" + parameterTypes + .withIndex() + .joinToString(", ") { (index, type) -> + "p$index: ${type.toTypeScript()}" + } + ") => " + returnType.toTypeScript() + + is ExportedType.ClassType -> + name + if (arguments.isNotEmpty()) "<${arguments.joinToString(", ") { it.toTypeScript() }}>" else "" + + is ExportedType.ErrorType -> "any /*$comment*/" + is ExportedType.TypeParameter -> name + is ExportedType.Nullable -> "Nullable<" + baseType.toTypeScript() + ">" +} \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MoveBodilessDeclarationsToSeparatePlace.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MoveBodilessDeclarationsToSeparatePlace.kt index 350dd96188d..3fc93c9bb79 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MoveBodilessDeclarationsToSeparatePlace.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MoveBodilessDeclarationsToSeparatePlace.kt @@ -12,7 +12,9 @@ import org.jetbrains.kotlin.ir.backend.js.utils.getJsModule import org.jetbrains.kotlin.ir.backend.js.utils.getJsQualifier import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl +import org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl import org.jetbrains.kotlin.ir.symbols.IrExternalPackageFragmentSymbol +import org.jetbrains.kotlin.ir.symbols.IrFileSymbol import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable import org.jetbrains.kotlin.ir.util.isEffectivelyExternal import org.jetbrains.kotlin.ir.util.transformFlat @@ -54,6 +56,22 @@ private class DescriptorlessExternalPackageFragmentSymbol : IrExternalPackageFra } } +private class DescriptorlessIrFileSymbol : IrFileSymbol { + override fun bind(owner: IrFile) { + _owner = owner + } + + override val descriptor: PackageFragmentDescriptor + get() = error("Operation is unsupported") + + private var _owner: IrFile? = null + override val owner get() = _owner!! + + override val isBound get() = _owner != null + +} + + fun moveBodilessDeclarationsToSeparatePlace(context: JsIrBackendContext, module: IrModuleFragment) { val bodilessBuiltInsPackageFragment = IrExternalPackageFragmentImpl( @@ -81,11 +99,10 @@ fun moveBodilessDeclarationsToSeparatePlace(context: JsIrBackendContext, module: fun lowerFile(irFile: IrFile): IrFile? { val externalPackageFragment by lazy { - context.externalPackageFragment.getOrPut(irFile.fqName) { - IrExternalPackageFragmentImpl( - DescriptorlessExternalPackageFragmentSymbol(), - irFile.fqName - ) + context.externalPackageFragment.getOrPut(irFile.symbol) { + IrFileImpl(fileEntry = irFile.fileEntry, fqName = irFile.fqName, symbol = DescriptorlessIrFileSymbol()).also { + it.annotations += irFile.annotations + } } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt index 583598ef4de..b4516591316 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt @@ -6,30 +6,32 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs import org.jetbrains.kotlin.config.CommonConfigurationKeys -import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.ir.backend.js.CompilerResult import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext +import org.jetbrains.kotlin.ir.backend.js.export.ExportModelGenerator +import org.jetbrains.kotlin.ir.backend.js.export.ExportModelToJsStatements +import org.jetbrains.kotlin.ir.backend.js.lower.StaticMembersLowering +import org.jetbrains.kotlin.ir.backend.js.export.toTypeScript import org.jetbrains.kotlin.ir.backend.js.utils.* -import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName +import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.declarations.path import org.jetbrains.kotlin.ir.symbols.IrClassSymbol -import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable -import org.jetbrains.kotlin.ir.util.isEffectivelyExternal -import org.jetbrains.kotlin.ir.util.isObject import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.utils.DFS -import org.jetbrains.kotlin.utils.addIfNotNull class IrModuleToJsTransformer( private val backendContext: JsIrBackendContext, private val mainFunction: IrSimpleFunction?, private val mainArguments: List? -) : BaseIrElementToJsNodeTransformer { - +) { val moduleName = backendContext.configuration[CommonConfigurationKeys.MODULE_NAME]!! private val moduleKind = backendContext.configuration[JSConfigurationKeys.MODULE_KIND]!! private fun generateModuleBody(module: IrModuleFragment, context: JsGenerationContext): List { - val statements = mutableListOf( + val statements = mutableListOf( JsStringLiteral("use strict").makeStmt() ) @@ -56,68 +58,7 @@ class IrModuleToJsTransformer( return statements } - private fun generateExportStatements( - module: IrModuleFragment, - context: JsGenerationContext, - internalModuleName: JsName - ): List { - val exports = mutableListOf() - - for (file in module.files) { - for (declaration in file.declarations) { - exports.addIfNotNull( - generateExportStatement(declaration, context, internalModuleName) - ) - } - } - - return exports - } - - private fun generateExportStatement( - declaration: IrDeclaration, - context: JsGenerationContext, - internalModuleName: JsName - ): JsExpressionStatement? { - if (declaration !is IrDeclarationWithVisibility || - declaration !is IrDeclarationWithName || - declaration.visibility != Visibilities.PUBLIC) { - return null - } - - if (!declaration.isExported()) - return null - - if (declaration.isEffectivelyExternal()) - return null - - if (declaration is IrClass && declaration.isCompanion) - return null - - val name: JsName = when (declaration) { - is IrSimpleFunction -> context.getNameForStaticFunction(declaration) - is IrClass -> context.getNameForClass(declaration) - // TODO: Fields must be exported as properties - is IrField -> context.getNameForField(declaration) - else -> return null - } - - val exportName = sanitizeName(declaration.getJsNameOrKotlinName().asString()) - - val expression = - if (declaration is IrClass && declaration.isObject) { - // TODO: Use export names for properties - val instanceGetter = backendContext.objectToGetInstanceFunction[declaration.symbol]!! - val instanceGetterName: JsName = context.getNameForStaticFunction(instanceGetter) - defineProperty(internalModuleName.makeRef(), name.ident, getter = JsNameRef(instanceGetterName)) - } else { - jsAssignment(JsNameRef(exportName, internalModuleName.makeRef()), name.makeRef()) - } - - return JsExpressionStatement(expression) - } - - private fun generateModule(module: IrModuleFragment): JsProgram { + fun generateModule(module: IrModuleFragment): CompilerResult { val additionalPackages = with(backendContext) { externalPackageFragment.values + listOf( bodilessBuiltInsPackageFragment, @@ -125,6 +66,11 @@ class IrModuleToJsTransformer( ) + packageLevelJsModules } + val exportedModule = ExportModelGenerator(backendContext).generateExport(module) + val dts = exportedModule.toTypeScript() + + module.files.forEach { StaticMembersLowering(backendContext).lower(it) } + val namer = NameTables(module.files + additionalPackages) val program = JsProgram() @@ -151,7 +97,8 @@ class IrModuleToJsTransformer( ) val moduleBody = generateModuleBody(module, rootContext) - val exportStatements = generateExportStatements(module, rootContext, internalModuleName) + val exportStatements = ExportModelToJsStatements(internalModuleName, namer) + .generateModuleExport(exportedModule) with(rootFunction) { parameters += JsParameter(internalModuleName) @@ -173,7 +120,7 @@ class IrModuleToJsTransformer( kind = moduleKind ) - return program + return CompilerResult(program.toString(), dts) } private fun generateMainArguments(mainFunction: IrSimpleFunction, rootContext: JsGenerationContext): List { @@ -247,9 +194,6 @@ class IrModuleToJsTransformer( return Pair(importStatements, importedJsModules) } - override fun visitModuleFragment(declaration: IrModuleFragment, data: Nothing?): JsNode = - generateModule(declaration) - private fun processClassModels( classModelMap: Map, preDeclarationBlock: JsBlock, @@ -271,28 +215,4 @@ class IrModuleToJsTransformer( declarationHandler ) } - - fun IrDeclarationWithName.isExported(): Boolean { - if (fqNameWhenAvailable in backendContext.additionalExportedDeclarations) - return true - - // Hack to support properties - val correspondingProperty = when { - this is IrField -> correspondingPropertySymbol - this is IrSimpleFunction -> correspondingPropertySymbol - else -> null - } - correspondingProperty?.let { - return it.owner.isExported() - } - - if (isJsExport()) - return true - - return when (val parent = parent) { - is IrDeclarationWithName -> parent.isExported() - is IrAnnotationContainer -> parent.isJsExport() - else -> false - } - } } \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/NameTables.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/NameTables.kt index 51a034a30d3..e40e23e8aeb 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/NameTables.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/NameTables.kt @@ -22,7 +22,7 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty class NameTable( - val parent: NameTable? = null, + val parent: NameTable<*>? = null, private val reserved: MutableSet = mutableSetOf(), val sanitizer: (String) -> String = ::sanitizeName ) { @@ -135,7 +135,7 @@ fun functionSignature(declaration: IrFunction): Signature { } class NameTables(packages: List) { - private val globalNames: NameTable + val globalNames: NameTable private val memberNames: NameTable private val localNames = mutableMapOf>() private val loopNames = mutableMapOf() @@ -297,7 +297,7 @@ class NameTables(packages: List) { } inner class LocalNameGenerator(parentDeclaration: IrDeclaration) : IrElementVisitorVoid { - val table = NameTable(globalNames) + val table = NameTable(globalNames) init { localNames[parentDeclaration] = table @@ -348,4 +348,4 @@ fun sanitizeName(name: String): String { val first = name.first().let { if (it.isES5IdentifierStart()) it else '_' } return first.toString() + name.drop(1).map { if (it.isES5IdentifierPart()) it else '_' }.joinToString("") -} \ No newline at end of file +} diff --git a/js/js.tests/test/org/jetbrains/kotlin/generators/tests/GenerateJsTests.kt b/js/js.tests/test/org/jetbrains/kotlin/generators/tests/GenerateJsTests.kt index f5ab018acdd..b6a1d1fb81d 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/generators/tests/GenerateJsTests.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/generators/tests/GenerateJsTests.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.generators.tests import org.jetbrains.kotlin.generators.tests.generator.testGroup import org.jetbrains.kotlin.js.test.AbstractDceTest +import org.jetbrains.kotlin.js.test.AbstractIrJsTypeScriptExportTest import org.jetbrains.kotlin.js.test.AbstractJsLineNumberTest import org.jetbrains.kotlin.js.test.ir.semantics.* import org.jetbrains.kotlin.js.test.semantics.* @@ -28,6 +29,10 @@ fun main(args: Array) { model("box/", pattern = "^([^_](.+))\\.kt$", targetBackend = TargetBackend.JS_IR) } + testClass { + model("typescript-export/", pattern = "^([^_](.+))\\.kt$", targetBackend = TargetBackend.JS_IR) + } + testClass { model("sourcemap/", pattern = "^([^_](.+))\\.kt$", targetBackend = TargetBackend.JS) } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractIrJsTypeScriptExportTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractIrJsTypeScriptExportTest.kt new file mode 100644 index 00000000000..ec8811057ac --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractIrJsTypeScriptExportTest.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2010-2019 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.js.test + +import org.jetbrains.kotlin.test.KotlinTestUtils +import org.jetbrains.kotlin.utils.fileUtils.withReplacedExtensionOrNull +import java.io.File +import java.lang.Boolean.getBoolean + +@Suppress("ConstantConditionIf") +abstract class AbstractIrJsTypeScriptExportTest : BasicIrBoxTest( + pathToTestDir = TEST_DATA_DIR_PATH + "typescript-export/", + testGroupOutputDirPrefix = "typescript-export/", + pathToRootOutputDir = TEST_DATA_DIR_PATH +) { + override val generateDts = true + private val updateReferenceDtsFiles = getBoolean("kotlin.js.updateReferenceDtsFiles") + + override fun performAdditionalChecks(inputFile: File, outputFile: File) { + val referenceDtsFile = inputFile.withReplacedExtensionOrNull(".kt", ".d.ts") + ?: error("Can't find reference .d.ts file") + val generatedDtsFile = outputFile.withReplacedExtensionOrNull("_v5", ".d.ts") + ?: error("Can't find generated .d.ts file") + + val generatedDts = generatedDtsFile.readText() + + if (updateReferenceDtsFiles) + referenceDtsFile.writeText(generatedDts) + else + KotlinTestUtils.assertEqualsToFile(referenceDtsFile, generatedDts) + } +} \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt index 676e8c50227..834f14c63c9 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt @@ -21,7 +21,6 @@ import org.jetbrains.kotlin.cli.common.output.writeAllTo import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.config.* -import org.jetbrains.kotlin.daemon.common.PackageMetadata import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.incremental.js.IncrementalDataProviderImpl import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumerImpl @@ -49,7 +48,6 @@ import org.jetbrains.kotlin.js.test.interop.ScriptEngineV8Lazy import org.jetbrains.kotlin.js.test.utils.* import org.jetbrains.kotlin.js.util.TextOutputImpl import org.jetbrains.kotlin.metadata.DebugProtoBuf -import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtPsiFactory @@ -200,8 +198,14 @@ abstract class BasicBoxTest( additionalFiles += additionalJsFile } + val additionalMainFiles = mutableListOf() + val additionalMainJsFile = filePath.removeSuffix("." + KotlinFileType.EXTENSION) + "__main.js" + if (File(additionalMainJsFile).exists()) { + additionalMainFiles += additionalMainJsFile + } + val allJsFiles = additionalFiles + inputJsFiles + generatedJsFiles.map { it.first } + globalCommonFiles + localCommonFiles + - additionalCommonFiles + additionalCommonFiles + additionalMainFiles val dontRunGeneratedCode = InTextDirectivesUtils.dontRunGeneratedCode(targetBackend, file) @@ -217,7 +221,7 @@ abstract class BasicBoxTest( } performAdditionalChecks(generatedJsFiles.map { it.first }, outputPrefixFile, outputPostfixFile) - + performAdditionalChecks(file, File(mainModule.outputFileName(outputDir))) val expectedReachableNodesMatcher = EXPECTED_REACHABLE_NODES.matcher(fileContent) val expectedReachableNodesFound = expectedReachableNodesMatcher.find() @@ -277,6 +281,7 @@ abstract class BasicBoxTest( } protected open fun performAdditionalChecks(generatedJsFiles: List, outputPrefixFile: File?, outputPostfixFile: File?) {} + protected open fun performAdditionalChecks(inputFile: File, outputFile: File) {} private fun generateNodeRunner( files: Collection, diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt index 84eb1d99e82..dc5e0f77e5b 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt @@ -17,7 +17,9 @@ import org.jetbrains.kotlin.js.facade.MainCallParameters import org.jetbrains.kotlin.js.facade.TranslationUnit import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.test.TargetBackend +import org.jetbrains.kotlin.utils.fileUtils.withReplacedExtensionOrNull import java.io.File +import java.lang.Boolean.getBoolean private val fullRuntimeKlib = loadKlib("compiler/ir/serialization.js/build/fullRuntime/klib") private val defaultRuntimeKlib = loadKlib("compiler/ir/serialization.js/build/reducedRuntime/klib") @@ -26,7 +28,7 @@ private val kotlinTestKLib = loadKlib("compiler/ir/serialization.js/build/kotlin abstract class BasicIrBoxTest( pathToTestDir: String, testGroupOutputDirPrefix: String, - pathToRootOutputDir: String = BasicBoxTest.TEST_DATA_DIR_PATH, + pathToRootOutputDir: String = TEST_DATA_DIR_PATH, generateSourceMap: Boolean = false, generateNodeJsRunner: Boolean = false ) : BasicBoxTest( @@ -38,6 +40,7 @@ abstract class BasicIrBoxTest( generateNodeJsRunner = generateNodeJsRunner, targetBackend = TargetBackend.JS_IR ) { + open val generateDts = false override val skipMinification = true @@ -53,6 +56,7 @@ abstract class BasicIrBoxTest( override val testChecker get() = if (runTestInNashorn) NashornIrJsTestChecker() else V8IrJsTestChecker + @Suppress("ConstantConditionIf") override fun translateFiles( units: List, outputFile: File, @@ -70,7 +74,7 @@ abstract class BasicIrBoxTest( val filesToCompile = units .map { (it as TranslationUnit.SourceFile).file } // TODO: split input files to some parts (global common, local common, test) - .filterNot { it.virtualFilePath.contains(BasicBoxTest.COMMON_FILES_DIR_PATH) } + .filterNot { it.virtualFilePath.contains(COMMON_FILES_DIR_PATH) } val runtimeKlibs = if (needsFullIrRuntime) listOf(fullRuntimeKlib, kotlinTestKLib) else listOf(defaultRuntimeKlib) @@ -85,7 +89,7 @@ abstract class BasicIrBoxTest( } if (isMainModule) { - val debugMode = false + val debugMode = getBoolean("kotlin.js.debugMode") val phaseConfig = if (debugMode) { val allPhasesSet = jsPhases.toPhaseMap().values.toSet() @@ -102,7 +106,7 @@ abstract class BasicIrBoxTest( PhaseConfig(jsPhases) } - val jsCode = compile( + val compiledModule = compile( project = config.project, files = filesToCompile, configuration = config.configuration, @@ -113,9 +117,14 @@ abstract class BasicIrBoxTest( exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))) ) - val wrappedCode = wrapWithModuleEmulationMarkers(jsCode, moduleId = config.moduleId, moduleKind = config.moduleKind) + val wrappedCode = wrapWithModuleEmulationMarkers(compiledModule.jsCode, moduleId = config.moduleId, moduleKind = config.moduleKind) outputFile.write(wrappedCode) + if (generateDts) { + val dtsFile = outputFile.withReplacedExtensionOrNull("_v5.js", ".d.ts") + dtsFile?.write(compiledModule.tsDefinitions ?: error("No ts definitions")) + } + } else { generateKLib( project = config.project, @@ -142,7 +151,7 @@ abstract class BasicIrBoxTest( // TODO: should we do anything special for module systems? // TODO: return list of js from translateFiles and provide then to this function with other js files - testChecker.check(jsFiles, testModuleName, null, testFunction, expectedResult, withModuleSystem) + testChecker.check(jsFiles, testModuleName, testPackage, testFunction, expectedResult, withModuleSystem) } } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicWasmBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicWasmBoxTest.kt index 24c89fcb0cd..88cdc687e0f 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicWasmBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicWasmBoxTest.kt @@ -29,6 +29,7 @@ import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.test.KotlinTestWithEnvironment import java.io.Closeable import java.io.File +import java.lang.Boolean.getBoolean private val wasmRuntimeKlib = loadKlib("compiler/ir/serialization.js/build/wasmRuntime/klib") @@ -88,7 +89,7 @@ abstract class BasicWasmBoxTest( testFunction: String ) { val filesToCompile = units.map { (it as TranslationUnit.SourceFile).file } - val debugMode = false + val debugMode = getBoolean("kotlin.js.debugMode") val phaseConfig = if (debugMode) { val allPhasesSet = wasmPhases.toPhaseMap().values.toSet() diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/IrJsTypeScriptExportTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/IrJsTypeScriptExportTestGenerated.java new file mode 100644 index 00000000000..71680a1db09 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/IrJsTypeScriptExportTestGenerated.java @@ -0,0 +1,139 @@ +/* + * Copyright 2010-2019 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.js.test; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TargetBackend; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("js/js.translator/testData/typescript-export") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class IrJsTypeScriptExportTestGenerated extends AbstractIrJsTypeScriptExportTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath); + } + + public void testAllFilesPresentInTypescript_export() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/typescript-export"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS_IR, true); + } + + @TestMetadata("js/js.translator/testData/typescript-export/declarations") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Declarations extends AbstractIrJsTypeScriptExportTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath); + } + + public void testAllFilesPresentInDeclarations() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/typescript-export/declarations"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS_IR, true); + } + + @TestMetadata("declarations.kt") + public void testDeclarations() throws Exception { + runTest("js/js.translator/testData/typescript-export/declarations/declarations.kt"); + } + } + + @TestMetadata("js/js.translator/testData/typescript-export/inheritance") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Inheritance extends AbstractIrJsTypeScriptExportTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath); + } + + public void testAllFilesPresentInInheritance() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/typescript-export/inheritance"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS_IR, true); + } + + @TestMetadata("inheritance.kt") + public void testInheritance() throws Exception { + runTest("js/js.translator/testData/typescript-export/inheritance/inheritance.kt"); + } + } + + @TestMetadata("js/js.translator/testData/typescript-export/namespaces") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Namespaces extends AbstractIrJsTypeScriptExportTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath); + } + + public void testAllFilesPresentInNamespaces() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/typescript-export/namespaces"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS_IR, true); + } + + @TestMetadata("namespaces.kt") + public void testNamespaces() throws Exception { + runTest("js/js.translator/testData/typescript-export/namespaces/namespaces.kt"); + } + } + + @TestMetadata("js/js.translator/testData/typescript-export/primitives") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Primitives extends AbstractIrJsTypeScriptExportTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath); + } + + public void testAllFilesPresentInPrimitives() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/typescript-export/primitives"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS_IR, true); + } + + @TestMetadata("primitives.kt") + public void testPrimitives() throws Exception { + runTest("js/js.translator/testData/typescript-export/primitives/primitives.kt"); + } + } + + @TestMetadata("js/js.translator/testData/typescript-export/selectiveExport") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class SelectiveExport extends AbstractIrJsTypeScriptExportTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath); + } + + public void testAllFilesPresentInSelectiveExport() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/typescript-export/selectiveExport"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS_IR, true); + } + + @TestMetadata("selectiveExport.kt") + public void testSelectiveExport() throws Exception { + runTest("js/js.translator/testData/typescript-export/selectiveExport/selectiveExport.kt"); + } + } + + @TestMetadata("js/js.translator/testData/typescript-export/visibility") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Visibility extends AbstractIrJsTypeScriptExportTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath); + } + + public void testAllFilesPresentInVisibility() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/typescript-export/visibility"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS_IR, true); + } + + @TestMetadata("visibility.kt") + public void testVisibility() throws Exception { + runTest("js/js.translator/testData/typescript-export/visibility/visibility.kt"); + } + } +} diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java index f9f0f583d38..1f24fbc9543 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java @@ -6425,11 +6425,6 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { runTest("js/js.translator/testData/box/propertyAccess/accessToInstanceProperty.kt"); } - @TestMetadata("accessorsWithJsName.kt") - public void testAccessorsWithJsName() throws Exception { - runTest("js/js.translator/testData/box/propertyAccess/accessorsWithJsName.kt"); - } - public void testAllFilesPresentInPropertyAccess() throws Exception { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/box/propertyAccess"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS_IR, true); } @@ -6449,11 +6444,6 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { runTest("js/js.translator/testData/box/propertyAccess/customSetter.kt"); } - @TestMetadata("defaultAccessorsWithJsName.kt") - public void testDefaultAccessorsWithJsName() throws Exception { - runTest("js/js.translator/testData/box/propertyAccess/defaultAccessorsWithJsName.kt"); - } - @TestMetadata("enumerable.kt") public void testEnumerable() throws Exception { runTest("js/js.translator/testData/box/propertyAccess/enumerable.kt"); diff --git a/js/js.translator/testData/box/propertyAccess/accessorsWithJsName.kt b/js/js.translator/testData/box/propertyAccess/accessorsWithJsName.kt index 65d2060fab9..ccd559bdbc4 100644 --- a/js/js.translator/testData/box/propertyAccess/accessorsWithJsName.kt +++ b/js/js.translator/testData/box/propertyAccess/accessorsWithJsName.kt @@ -1,3 +1,4 @@ +// DONT_TARGET_EXACT_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1291 class A { diff --git a/js/js.translator/testData/box/propertyAccess/defaultAccessorsWithJsName.kt b/js/js.translator/testData/box/propertyAccess/defaultAccessorsWithJsName.kt index 00799288a71..4a028abe2c7 100644 --- a/js/js.translator/testData/box/propertyAccess/defaultAccessorsWithJsName.kt +++ b/js/js.translator/testData/box/propertyAccess/defaultAccessorsWithJsName.kt @@ -1,3 +1,4 @@ +// DONT_TARGET_EXACT_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1288 package foo diff --git a/js/js.translator/testData/box/propertyAccess/staticAccessorsWithJsName.kt b/js/js.translator/testData/box/propertyAccess/staticAccessorsWithJsName.kt index f6888c9faf2..9cce2e4c262 100644 --- a/js/js.translator/testData/box/propertyAccess/staticAccessorsWithJsName.kt +++ b/js/js.translator/testData/box/propertyAccess/staticAccessorsWithJsName.kt @@ -1,3 +1,4 @@ +// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1288 @JsExport diff --git a/js/js.translator/testData/package.json b/js/js.translator/testData/package.json index 6642c074345..bc1cd39c895 100644 --- a/js/js.translator/testData/package.json +++ b/js/js.translator/testData/package.json @@ -1,14 +1,16 @@ { - "dependencies" : { + "dependencies": { "require-from-string": "1.2.0" }, - "devDependencies" : { + "devDependencies": { "mocha": "3.2.0", - "mocha-teamcity-reporter": "1.1.1" + "mocha-teamcity-reporter": "1.1.1", + "typescript": "^3.5.3" }, "scripts": { "runOnTeamcity": "mocha --reporter mocha-teamcity-reporter", "test": "mocha", - "runIrTestInNode" : "node $NODE_DEBUG_OPTION runIrTestInNode.js" + "runIrTestInNode": "node $NODE_DEBUG_OPTION runIrTestInNode.js", + "generateTypeScriptTests": "tsc --build typescript-export/*/" } } diff --git a/js/js.translator/testData/typescript-export/common.tsconfig.json b/js/js.translator/testData/typescript-export/common.tsconfig.json new file mode 100644 index 00000000000..90eb35259a1 --- /dev/null +++ b/js/js.translator/testData/typescript-export/common.tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "target": "es5", + "strict": true + } +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/declarations/declarations.d.ts b/js/js.translator/testData/typescript-export/declarations/declarations.d.ts new file mode 100644 index 00000000000..5e6db47f597 --- /dev/null +++ b/js/js.translator/testData/typescript-export/declarations/declarations.d.ts @@ -0,0 +1,46 @@ +declare namespace JS_TESTS { + type Nullable = T | null | undefined + namespace foo { + function sum(x: number, y: number): number + function varargInt(x: Int32Array): number + function varargNullableInt(x: Array>): number + function varargWithOtherParameters(x: string, y: Array, z: string): number + function varargWithComplexType(x: Array<(p0: Array) => Array>): number + function sumNullable(x: Nullable, y: Nullable): number + function defaultParameters(x: number, y: string): string + function generic1(x: T): T + function generic2(x: Nullable): boolean + function generic3(a: A, b: B, c: C, d: D): Nullable + function inlineFun(x: number, callback: (p0: number) => void): void + const _const_val: number; + const _val: number; + let _var: number; + const _valCustom: number; + const _valCustomWithField: number; + let _varCustom: number; + let _varCustomWithField: number; + class A { + constructor() + } + class A1 { + constructor(x: number) + readonly x: number; + } + class A2 { + constructor(x: string, y: boolean) + readonly x: string; + y: boolean; + } + class A3 { + constructor() + readonly x: number; + } + class A4 { + constructor() + readonly _valCustom: number; + readonly _valCustomWithField: number; + _varCustom: number; + _varCustomWithField: number; + } + } +} diff --git a/js/js.translator/testData/typescript-export/declarations/declarations.kt b/js/js.translator/testData/typescript-export/declarations/declarations.kt new file mode 100644 index 00000000000..d503a98e7b8 --- /dev/null +++ b/js/js.translator/testData/typescript-export/declarations/declarations.kt @@ -0,0 +1,84 @@ +// TARGET_BACKEND: JS_IR +// CHECK_TYPESCRIPT_DECLARATIONS +// RUN_PLAIN_BOX_FUNCTION + +@file:JsExport + +package foo + +// TODO: Test the same for member functions: + +fun sum(x: Int, y: Int): Int = + x + y + +fun varargInt(vararg x: Int): Int = + x.size + +fun varargNullableInt(vararg x: Int?): Int = + x.size + +fun varargWithOtherParameters(x: String, vararg y: String, z: String): Int = + x.length + y.size + z.length + +fun varargWithComplexType(vararg x: (Array) -> Array): Int = + x.size + +fun sumNullable(x: Int?, y: Int?): Int = + (x ?: 0) + (y ?: 0) + +fun defaultParameters(x: Int = 10, y: String = "OK"): String = + x.toString() + y + +fun generic1(x: T): T = x + +fun generic2(x: T?): Boolean = (x == null) + +fun generic3(a: A, b: B, c: C, d: D): E? = null + +inline fun inlineFun(x: Int, callback: (Int) -> Unit) { + callback(x) +} + +// Properties + +const val _const_val: Int = 1 +val _val: Int = 1 +var _var: Int = 1 + +val _valCustom: Int + get() = 1 + +val _valCustomWithField: Int = 1 + get() = field + 1 + +var _varCustom: Int + get() = 1 + set(value) {} + +var _varCustomWithField: Int = 1 + get() = field * 10 + set(value) { field = value * 10 } + +// Classes + +class A +class A1(val x: Int) +class A2(val x: String, var y: Boolean) +class A3 { + val x: Int = 100 +} +class A4 { + val _valCustom: Int + get() = 1 + + val _valCustomWithField: Int = 1 + get() = field + 1 + + var _varCustom: Int + get() = 1 + set(value) {} + + var _varCustomWithField: Int = 1 + get() = field * 10 + set(value) { field = value * 10 } +} \ 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 new file mode 100644 index 00000000000..20f4866b0ba --- /dev/null +++ b/js/js.translator/testData/typescript-export/declarations/declarations__main.js @@ -0,0 +1,79 @@ +"use strict"; +var foo = JS_TESTS.foo; +var varargInt = JS_TESTS.foo.varargInt; +var varargNullableInt = JS_TESTS.foo.varargNullableInt; +var varargWithOtherParameters = JS_TESTS.foo.varargWithOtherParameters; +var varargWithComplexType = JS_TESTS.foo.varargWithComplexType; +var sumNullable = JS_TESTS.foo.sumNullable; +var defaultParameters = JS_TESTS.foo.defaultParameters; +var generic1 = JS_TESTS.foo.generic1; +var generic2 = JS_TESTS.foo.generic2; +var generic3 = JS_TESTS.foo.generic3; +var inlineFun = JS_TESTS.foo.inlineFun; +var _const_val = JS_TESTS.foo._const_val; +var _val = JS_TESTS.foo._val; +var _var = JS_TESTS.foo._var; +var A = JS_TESTS.foo.A; +var A1 = JS_TESTS.foo.A1; +var A2 = JS_TESTS.foo.A2; +var A3 = JS_TESTS.foo.A3; +var _valCustom = JS_TESTS.foo._valCustom; +var _valCustomWithField = JS_TESTS.foo._valCustomWithField; +var A4 = JS_TESTS.foo.A4; +function assert(condition) { + if (!condition) { + throw "Assertion failed"; + } +} +function box() { + assert(foo.sum(10, 20) === 30); + assert(varargInt(new Int32Array([1, 2, 3])) === 3); + assert(varargNullableInt([10, 20, 30, null, undefined, 40]) === 6); + assert(varargWithOtherParameters("1234", ["1", "2", "3"], "12") === 9); + assert(varargWithComplexType([]) === 0); + assert(varargWithComplexType([ + function (x) { return x; }, + function (x) { return [new Int32Array([1, 2, 3])]; }, + function (x) { return []; }, + ]) === 3); + assert(sumNullable(10, null) === 10); + assert(sumNullable(undefined, 20) === 20); + assert(sumNullable(1, 2) === 3); + assert(defaultParameters(20, "OK") === "20OK"); + assert(generic1("FOO") === "FOO"); + assert(generic1({ x: 10 }).x === 10); + assert(generic2(null) === true); + assert(generic2(undefined) === true); + assert(generic2(10) === false); + assert(generic3(10, true, "__", {}) === null); + var result = 0; + inlineFun(10, function (x) { result = x; }); + assert(result === 10); + assert(_const_val === 1); + assert(_val === 1); + assert(_var === 1); + foo._var = 1000; + assert(foo._var === 1000); + assert(foo._valCustom === 1); + assert(foo._valCustomWithField === 2); + assert(foo._varCustom === 1); + foo._varCustom = 20; + assert(foo._varCustom === 1); + assert(foo._varCustomWithField === 10); + foo._varCustomWithField = 10; + assert(foo._varCustomWithField === 1000); + new A(); + assert(new A1(10).x === 10); + assert(new A2("10", true).x === "10"); + assert(new A3().x === 100); + var a4 = new A4(); + assert(a4._valCustom === 1); + assert(a4._valCustomWithField === 2); + assert(a4._varCustom === 1); + a4._varCustom = 20; + assert(a4._varCustom === 1); + assert(a4._varCustomWithField === 10); + a4._varCustomWithField = 10; + assert(a4._varCustomWithField === 1000); + 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 new file mode 100644 index 00000000000..c19af7202e1 --- /dev/null +++ b/js/js.translator/testData/typescript-export/declarations/declarations__main.ts @@ -0,0 +1,90 @@ + +import foo = JS_TESTS.foo; +import varargInt = JS_TESTS.foo.varargInt; +import varargNullableInt = JS_TESTS.foo.varargNullableInt; +import varargWithOtherParameters = JS_TESTS.foo.varargWithOtherParameters; +import varargWithComplexType = JS_TESTS.foo.varargWithComplexType; +import sumNullable = JS_TESTS.foo.sumNullable; +import defaultParameters = JS_TESTS.foo.defaultParameters; +import generic1 = JS_TESTS.foo.generic1; +import generic2 = JS_TESTS.foo.generic2; +import generic3 = JS_TESTS.foo.generic3; +import inlineFun = JS_TESTS.foo.inlineFun; +import _const_val = JS_TESTS.foo._const_val; +import _val = JS_TESTS.foo._val; +import _var = JS_TESTS.foo._var; +import A = JS_TESTS.foo.A; +import A1 = JS_TESTS.foo.A1; +import A2 = JS_TESTS.foo.A2; +import A3 = JS_TESTS.foo.A3; +import _valCustom = JS_TESTS.foo._valCustom; +import _valCustomWithField = JS_TESTS.foo._valCustomWithField; +import A4 = JS_TESTS.foo.A4; + +function assert(condition: boolean) { + if (!condition) { + throw "Assertion failed"; + } +} + +function box(): string { + assert(foo.sum(10, 20) === 30); + assert(varargInt(new Int32Array([1, 2, 3])) === 3); + assert(varargNullableInt([10, 20, 30, null, undefined, 40]) === 6); + assert(varargWithOtherParameters("1234", ["1", "2", "3"], "12") === 9); + assert(varargWithComplexType([]) === 0); + assert(varargWithComplexType([ + x => x, + x => [new Int32Array([1, 2, 3])], + x => [], + ]) === 3); + + assert(sumNullable(10, null) === 10); + assert(sumNullable(undefined, 20) === 20); + assert(sumNullable(1, 2) === 3); + assert(defaultParameters(20, "OK") === "20OK"); + assert(generic1("FOO") === "FOO"); + assert(generic1({x: 10}).x === 10); + assert(generic2(null) === true); + assert(generic2(undefined) === true); + assert(generic2(10) === false); + assert(generic3(10, true, "__", {}) === null); + + let result: number = 0; + inlineFun(10, x => { result = x; }); + assert(result === 10); + + assert(_const_val === 1); + assert(_val === 1); + assert(_var === 1); + foo._var = 1000; + assert(foo._var === 1000); + + assert(foo._valCustom === 1); + assert(foo._valCustomWithField === 2); + assert(foo._varCustom === 1); + foo._varCustom = 20; + assert(foo._varCustom === 1); + assert(foo._varCustomWithField === 10); + foo._varCustomWithField = 10; + assert(foo._varCustomWithField === 1000); + + new A(); + assert(new A1(10).x === 10); + assert(new A2("10", true).x === "10"); + assert(new A3().x === 100); + + + const a4 = new A4(); + + assert(a4._valCustom === 1); + assert(a4._valCustomWithField === 2); + assert(a4._varCustom === 1); + a4._varCustom = 20; + assert(a4._varCustom === 1); + assert(a4._varCustomWithField === 10); + a4._varCustomWithField = 10; + assert(a4._varCustomWithField === 1000); + + return "OK"; +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/declarations/tsconfig.json b/js/js.translator/testData/typescript-export/declarations/tsconfig.json new file mode 100644 index 00000000000..17612c56c9d --- /dev/null +++ b/js/js.translator/testData/typescript-export/declarations/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [ "./*" ], + "extends": "../common.tsconfig.json" +} \ 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 new file mode 100644 index 00000000000..6a04989b078 --- /dev/null +++ b/js/js.translator/testData/typescript-export/inheritance/inheritance.d.ts @@ -0,0 +1,34 @@ +declare namespace JS_TESTS { + type Nullable = T | null | undefined + namespace foo { + interface I { + x: T; + readonly y: S; + z(u: U): void + } + interface I2 { + x: string; + readonly y: boolean; + z(z: number): void + } + } + namespace foo { + abstract class AC implements foo.I2 { + constructor() + x: string; + abstract readonly y: boolean; + abstract z(z: number): void + readonly acProp: string; + abstract readonly acAbstractProp: string; + } + class OC extends foo.AC implements foo.I { + constructor(y: boolean, acAbstractProp: string) + readonly y: boolean; + readonly acAbstractProp: string; + z(z: number): void + } + class FC extends foo.OC { + constructor() + } + } +} diff --git a/js/js.translator/testData/typescript-export/inheritance/inheritance.kt b/js/js.translator/testData/typescript-export/inheritance/inheritance.kt new file mode 100644 index 00000000000..0745f74d3e4 --- /dev/null +++ b/js/js.translator/testData/typescript-export/inheritance/inheritance.kt @@ -0,0 +1,41 @@ +// TARGET_BACKEND: JS_IR +// CHECK_TYPESCRIPT_DECLARATIONS +// RUN_PLAIN_BOX_FUNCTION + +@file:JsExport + +package foo + +external interface I { + var x: T + val y: S + fun z(u: U) +} + +external interface I2 { + var x: String + val y: Boolean + fun z(z: Int) +} + +abstract class AC : I2 { + override var x = "AC" + override abstract val y: Boolean + override abstract fun z(z: Int) + + val acProp: String = "acProp" + abstract val acAbstractProp: String +} + +open class OC( + override val y: Boolean, + override val acAbstractProp: String +) : AC(), I { + override fun z(z: Int) { + } + + private val privateX: String = "privateX" + private fun privateFun(): String = "privateFun" +} + +final class FC : OC(true, "FC") \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/inheritance/inheritance__main.js b/js/js.translator/testData/typescript-export/inheritance/inheritance__main.js new file mode 100644 index 00000000000..96bf2488242 --- /dev/null +++ b/js/js.translator/testData/typescript-export/inheritance/inheritance__main.js @@ -0,0 +1,60 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var OC = JS_TESTS.foo.OC; +var AC = JS_TESTS.foo.AC; +var FC = JS_TESTS.foo.FC; +var Impl = /** @class */ (function (_super) { + __extends(Impl, _super); + function Impl() { + return _super !== null && _super.apply(this, arguments) || this; + } + Impl.prototype.z = function (z) { + }; + Object.defineProperty(Impl.prototype, "acAbstractProp", { + get: function () { return "Impl"; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Impl.prototype, "y", { + get: function () { return true; }, + enumerable: true, + configurable: true + }); + return Impl; +}(AC)); +function box() { + var impl = new Impl(); + if (impl.acProp !== "acProp") + return "Fail 1"; + if (impl.x !== "AC") + return "Fail 2"; + if (impl.acAbstractProp !== "Impl") + return "Fail 2.1"; + if (impl.y !== true) + return "Fail 2.2"; + var oc = new OC(false, "OC"); + if (oc.y !== false) + return "Fail 3"; + if (oc.acAbstractProp !== "OC") + return "Fail 4"; + oc.z(10); + var fc = new FC(); + if (fc.y !== true) + return "Fail 5"; + if (fc.acAbstractProp !== "FC") + return "Fail 6"; + fc.z(10); + return "OK"; +} diff --git a/js/js.translator/testData/typescript-export/inheritance/inheritance__main.ts b/js/js.translator/testData/typescript-export/inheritance/inheritance__main.ts new file mode 100644 index 00000000000..6186eed0eba --- /dev/null +++ b/js/js.translator/testData/typescript-export/inheritance/inheritance__main.ts @@ -0,0 +1,33 @@ +import OC = JS_TESTS.foo.OC; +import AC = JS_TESTS.foo.AC; +import FC = JS_TESTS.foo.FC; + +class Impl extends AC { + z(z: number): void { + } + + get acAbstractProp(): string { return "Impl"; } + get y(): boolean { return true; } +} + +function box(): string { + const impl = new Impl(); + if (impl.acProp !== "acProp") return "Fail 1"; + if (impl.x !== "AC") return "Fail 2"; + if (impl.acAbstractProp !== "Impl") return "Fail 2.1"; + if (impl.y !== true) return "Fail 2.2"; + + + const oc = new OC(false, "OC"); + if (oc.y !== false) return "Fail 3"; + if (oc.acAbstractProp !== "OC") return "Fail 4"; + oc.z(10); + + + const fc = new FC(); + if (fc.y !== true) return "Fail 5"; + if (fc.acAbstractProp !== "FC") return "Fail 6"; + fc.z(10); + + return "OK"; +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/inheritance/tsconfig.json b/js/js.translator/testData/typescript-export/inheritance/tsconfig.json new file mode 100644 index 00000000000..17612c56c9d --- /dev/null +++ b/js/js.translator/testData/typescript-export/inheritance/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [ "./*" ], + "extends": "../common.tsconfig.json" +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/namespaces/namespaces.d.ts b/js/js.translator/testData/typescript-export/namespaces/namespaces.d.ts new file mode 100644 index 00000000000..3c285bfac6f --- /dev/null +++ b/js/js.translator/testData/typescript-export/namespaces/namespaces.d.ts @@ -0,0 +1,37 @@ +declare namespace JS_TESTS { + type Nullable = T | null | undefined + namespace foo.bar.baz { + class C1 { + constructor(value: string) + readonly value: string; + component1(): string + copy(value: string): foo.bar.baz.C1 + toString(): string + hashCode(): number + equals(other: Nullable): boolean + } + function f(x1: foo.bar.baz.C1, x2: a.b.C2, x3: C3): string + } + namespace a.b { + class C2 { + constructor(value: string) + readonly value: string; + component1(): string + copy(value: string): a.b.C2 + toString(): string + hashCode(): number + equals(other: Nullable): boolean + } + function f(x1: foo.bar.baz.C1, x2: a.b.C2, x3: C3): string + } + class C3 { + constructor(value: string) + readonly value: string; + component1(): string + copy(value: string): C3 + toString(): string + hashCode(): number + equals(other: Nullable): boolean + } + function f(x1: foo.bar.baz.C1, x2: a.b.C2, x3: C3): string +} diff --git a/js/js.translator/testData/typescript-export/namespaces/namespaces.kt b/js/js.translator/testData/typescript-export/namespaces/namespaces.kt new file mode 100644 index 00000000000..3567e2e5bcd --- /dev/null +++ b/js/js.translator/testData/typescript-export/namespaces/namespaces.kt @@ -0,0 +1,47 @@ +// TARGET_BACKEND: JS_IR +// CHECK_TYPESCRIPT_DECLARATIONS +// RUN_PLAIN_BOX_FUNCTION + +// FILE: file1.kt + +package foo.bar.baz + +import a.b.* +import C3 + +@JsExport +data class C1(val value: String) + +@JsExport +fun f(x1: C1, x2: C2, x3: C3): String { + return "foo.bar.baz.f($x1, $x2, $x3)" +} + +// FILE: file2.kt + +@file:JsExport + +package a.b + +import foo.bar.baz.* +import C3 + +data class C2(val value: String) +fun f(x1: C1, x2: C2, x3: C3): String { + return "a.b.f($x1, $x2, $x3)" +} + +// FILE: file3.kt + +@file:JsExport + +import a.b.* +import foo.bar.baz.* + +@JsExport +data class C3(val value: String) + +@JsExport +fun f(x1: C1, x2: C2, x3: C3): String { + return "f($x1, $x2, $x3)" +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/namespaces/namespaces__main.js b/js/js.translator/testData/typescript-export/namespaces/namespaces__main.js new file mode 100644 index 00000000000..599108f9f09 --- /dev/null +++ b/js/js.translator/testData/typescript-export/namespaces/namespaces__main.js @@ -0,0 +1,19 @@ +"use strict"; +var C1 = JS_TESTS.foo.bar.baz.C1; +var C2 = JS_TESTS.a.b.C2; +var C3 = JS_TESTS.C3; +function box() { + var c1 = new C1("1"); + var c2 = new C2("2"); + var c3 = new C3("3"); + var res1 = JS_TESTS.foo.bar.baz.f(c1, c2, c3); + var res2 = JS_TESTS.a.b.f(c1, c2, c3); + var res3 = JS_TESTS.f(c1, c2, c3); + if (res1 !== "foo.bar.baz.f(C1(value=1), C2(value=2), C3(value=3))") + return "Fail 1: " + res1; + if (res2 !== "a.b.f(C1(value=1), C2(value=2), C3(value=3))") + return "Fail 2: " + res2; + if (res3 !== "f(C1(value=1), C2(value=2), C3(value=3))") + return "Fail 3: " + res3; + return "OK"; +} diff --git a/js/js.translator/testData/typescript-export/namespaces/namespaces__main.ts b/js/js.translator/testData/typescript-export/namespaces/namespaces__main.ts new file mode 100644 index 00000000000..f5168a7478f --- /dev/null +++ b/js/js.translator/testData/typescript-export/namespaces/namespaces__main.ts @@ -0,0 +1,24 @@ +import C1 = JS_TESTS.foo.bar.baz.C1; +import C2 = JS_TESTS.a.b.C2; +import C3 = JS_TESTS.C3; + +function box(): string { + const c1 = new C1("1"); + const c2 = new C2("2"); + const c3 = new C3("3"); + + const res1 = JS_TESTS.foo.bar.baz.f(c1, c2, c3); + const res2 = JS_TESTS.a.b.f(c1, c2, c3); + const res3 = JS_TESTS.f(c1, c2, c3); + + if (res1 !== "foo.bar.baz.f(C1(value=1), C2(value=2), C3(value=3))") + return "Fail 1: " + res1; + + if (res2 !== "a.b.f(C1(value=1), C2(value=2), C3(value=3))") + return "Fail 2: " + res2; + + if (res3 !== "f(C1(value=1), C2(value=2), C3(value=3))") + return "Fail 3: " + res3; + + return "OK"; +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/namespaces/tsconfig.json b/js/js.translator/testData/typescript-export/namespaces/tsconfig.json new file mode 100644 index 00000000000..17612c56c9d --- /dev/null +++ b/js/js.translator/testData/typescript-export/namespaces/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [ "./*" ], + "extends": "../common.tsconfig.json" +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/primitives/primitives.d.ts b/js/js.translator/testData/typescript-export/primitives/primitives.d.ts new file mode 100644 index 00000000000..0de767fb004 --- /dev/null +++ b/js/js.translator/testData/typescript-export/primitives/primitives.d.ts @@ -0,0 +1,50 @@ +declare namespace JS_TESTS { + type Nullable = T | null | undefined + namespace foo { + const _any: any; + const _unit: void; + function _nothing(): never + const _throwable: Error; + const _string: string; + const _boolean: boolean; + const _byte: number; + const _short: number; + const _int: number; + const _float: number; + const _double: number; + const _byte_array: Int8Array; + const _short_array: Int16Array; + const _int_array: Int32Array; + const _float_array: Float32Array; + const _double_array: Float64Array; + const _array_byte: Array; + const _array_short: Array; + const _array_int: Array; + const _array_float: Array; + const _array_double: Array; + const _array_string: Array; + const _array_boolean: Array; + const _array_array_string: Array>; + const _array_array_int_array: Array>; + const _fun_unit: () => void; + const _fun_int_unit: (p0: number) => void; + const _fun_boolean_int_string_intarray: (p0: boolean, p1: number, p2: string) => Int32Array; + const _curried_fun: (p0: number) => (p0: number) => (p0: number) => (p0: number) => (p0: number) => number; + const _higher_order_fun: (p0: (p0: number) => string, p1: (p0: string) => number) => (p0: number) => number; + const _n_any: Nullable; + const _n_nothing: Nullable; + const _n_throwable: Nullable; + const _n_string: Nullable; + const _n_boolean: Nullable; + const _n_byte: Nullable; + const _n_short_array: Nullable; + const _n_array_int: Nullable>; + const _array_n_int: Array>; + const _n_array_n_int: Nullable>>; + const _array_n_array_string: Array>>; + const _fun_n_int_unit: (p0: Nullable) => void; + const _fun_n_boolean_n_int_n_string_n_intarray: (p0: Nullable, p1: Nullable, p2: Nullable) => Nullable; + const _n_curried_fun: (p0: Nullable) => (p0: Nullable) => (p0: Nullable) => Nullable; + const _n_higher_order_fun: (p0: (p0: Nullable) => Nullable, p1: (p0: Nullable) => Nullable) => (p0: Nullable) => Nullable; + } +} diff --git a/js/js.translator/testData/typescript-export/primitives/primitives.kt b/js/js.translator/testData/typescript-export/primitives/primitives.kt new file mode 100644 index 00000000000..db7b0faabce --- /dev/null +++ b/js/js.translator/testData/typescript-export/primitives/primitives.kt @@ -0,0 +1,93 @@ +// TARGET_BACKEND: JS_IR +// CHECK_TYPESCRIPT_DECLARATIONS +// RUN_PLAIN_BOX_FUNCTION + +@file:JsExport + +package foo + +val _any: Any = Any() + +val _unit: Unit = Unit + +fun _nothing(): Nothing { throw Throwable() } + +val _throwable: Throwable = Throwable() + +val _string: String = "ZZZ" + +val _boolean: Boolean = true + +val _byte: Byte = 1.toByte() +val _short: Short = 1.toShort() +val _int: Int = 1 +val _float: Float = 1.0f +val _double: Double = 1.0 +// TODO: Char and Long + +val _byte_array: ByteArray = byteArrayOf() +val _short_array: ShortArray = shortArrayOf() +val _int_array: IntArray = intArrayOf() +val _float_array: FloatArray = floatArrayOf() +val _double_array: DoubleArray = doubleArrayOf() + +val _array_byte: Array = emptyArray() +val _array_short: Array = emptyArray() +val _array_int: Array = emptyArray() +val _array_float: Array = emptyArray() +val _array_double: Array = emptyArray() +val _array_string: Array = emptyArray() +val _array_boolean: Array = emptyArray() +val _array_array_string: Array> = arrayOf(emptyArray()) +val _array_array_int_array: Array> = arrayOf(arrayOf(intArrayOf())) + +val _fun_unit: () -> Unit = { } +val _fun_int_unit: (Int) -> Unit = { x -> } + +val _fun_boolean_int_string_intarray: (Boolean, Int, String) -> IntArray = + { b, i, s -> intArrayOf(b.toString().length, i, s.length) } + +val _curried_fun: (Int) -> (Int) -> (Int) -> (Int) -> (Int) -> Int = + { x1 -> { x2 -> { x3 -> { x4 -> { x5 -> x1 + x2 + x3 + x4 + x5 } } } } } + +val _higher_order_fun: ((Int) -> String, (String) -> Int) -> ((Int) -> Int) = + { f1, f2 -> { x -> f2(f1(x)) } } + + +// Nullable types + +val _n_any: Any? = Any() + +// TODO: +// val _n_unit: Unit? = Unit + +val _n_nothing: Nothing? = null + +val _n_throwable: Throwable? = Throwable() + +val _n_string: String? = "ZZZ" + +val _n_boolean: Boolean? = true + +val _n_byte: Byte? = 1.toByte() + +// TODO: Char and Long + +val _n_short_array: ShortArray? = shortArrayOf() + +val _n_array_int: Array? = emptyArray() +val _array_n_int: Array = emptyArray() +val _n_array_n_int: Array? = emptyArray() + +val _array_n_array_string: Array?> = arrayOf(arrayOf(":)")) + +val _fun_n_int_unit: (Int?) -> Unit = { x -> } + +val _fun_n_boolean_n_int_n_string_n_intarray: (Boolean?, Int?, String?) -> IntArray? = + { b, i, s -> null } + +val _n_curried_fun: (Int?) -> (Int?) -> (Int?) -> Int? = + { x1 -> { x2 -> { x3 -> (x1 ?: 0) + (x2 ?: 0) + (x3 ?: 0) } } } + +val _n_higher_order_fun: ((Int?) -> String?, (String?) -> Int?) -> ((Int?) -> Int?) = + { f1, f2 -> { x -> f2(f1(x)) } } diff --git a/js/js.translator/testData/typescript-export/primitives/primitives__main.js b/js/js.translator/testData/typescript-export/primitives/primitives__main.js new file mode 100644 index 00000000000..cff2e278c5b --- /dev/null +++ b/js/js.translator/testData/typescript-export/primitives/primitives__main.js @@ -0,0 +1,58 @@ +"use strict"; +var foo = JS_TESTS.foo; +function assert(condition) { + if (!condition) { + throw "Assertion failed"; + } +} +assert(typeof foo._any === "object"); +assert(foo._string === "ZZZ"); +assert(foo._boolean === true); +assert(foo._byte === 1); +assert(foo._short === 1); +assert(foo._float === 1); +assert(foo._double === 1); +assert(foo._byte_array instanceof Int8Array); +assert(foo._short_array instanceof Int16Array); +assert(foo._int_array instanceof Int32Array); +assert(foo._float_array instanceof Float32Array); +assert(foo._double_array instanceof Float64Array); +assert(foo._array_byte instanceof Array); +assert(foo._array_short instanceof Array); +assert(foo._array_int instanceof Array); +assert(foo._array_float instanceof Array); +assert(foo._array_double instanceof Array); +assert(foo._array_string instanceof Array); +assert(foo._array_boolean instanceof Array); +assert(foo._array_array_string instanceof Array); +assert(foo._array_array_string[0] instanceof Array); +assert(foo._array_array_int_array instanceof Array); +assert(foo._array_array_int_array[0] instanceof Array); +assert(foo._array_array_int_array[0][0] instanceof Int32Array); +foo._fun_unit(); +foo._fun_int_unit(10); +assert(foo._fun_boolean_int_string_intarray(true, 20, "A") instanceof Int32Array); +assert(foo._curried_fun(1)(2)(3)(4)(5) == 15); +assert(foo._higher_order_fun(function (n) { return String(n); }, function (s) { return s.length; })(1000) == 4); +assert(foo._n_any != null); +assert(foo._n_nothing == null); +assert(foo._n_throwable instanceof Error); +assert(foo._n_string === "ZZZ"); +assert(foo._n_boolean === true); +assert(foo._n_byte === 1); +assert(foo._n_short_array instanceof Int16Array); +assert(foo._n_array_int instanceof Array); +assert(foo._array_n_int instanceof Array); +assert(foo._n_array_n_int instanceof Array); +assert(foo._array_n_array_string instanceof Array); +var x = foo._array_n_array_string[0]; +assert(x != null); +assert(x instanceof Array); +assert(x == null ? false : (x[0] === ":)")); +foo._fun_n_int_unit(null); +assert(foo._fun_n_boolean_n_int_n_string_n_intarray(false, undefined, "ZZZ") == null); +assert(foo._n_curried_fun(10)(null)(30) === 40); +assert(foo._n_higher_order_fun(function (n) { return String(n); }, function (s) { return (s == null ? 10 : s.length); })(1000) === 4); +function box() { + return "OK"; +} diff --git a/js/js.translator/testData/typescript-export/primitives/primitives__main.ts b/js/js.translator/testData/typescript-export/primitives/primitives__main.ts new file mode 100644 index 00000000000..e3035eb5f3d --- /dev/null +++ b/js/js.translator/testData/typescript-export/primitives/primitives__main.ts @@ -0,0 +1,69 @@ +import foo = JS_TESTS.foo; + +function assert(condition: boolean) { + if (!condition) { + throw "Assertion failed"; + } +} + +assert(typeof foo._any === "object"); +assert(foo._string === "ZZZ"); +assert(foo._boolean === true); +assert(foo._byte === 1); +assert(foo._short === 1); +assert(foo._float === 1); +assert(foo._double === 1); +assert(foo._byte_array instanceof Int8Array); +assert(foo._short_array instanceof Int16Array); +assert(foo._int_array instanceof Int32Array); +assert(foo._float_array instanceof Float32Array); +assert(foo._double_array instanceof Float64Array); +assert(foo._array_byte instanceof Array); +assert(foo._array_short instanceof Array); +assert(foo._array_int instanceof Array); +assert(foo._array_float instanceof Array); +assert(foo._array_double instanceof Array); +assert(foo._array_string instanceof Array); +assert(foo._array_boolean instanceof Array); + +assert(foo._array_array_string instanceof Array); +assert(foo._array_array_string[0] instanceof Array); + +assert(foo._array_array_int_array instanceof Array); +assert(foo._array_array_int_array[0] instanceof Array); +assert(foo._array_array_int_array[0][0] instanceof Int32Array); + +foo._fun_unit(); +foo._fun_int_unit(10); +assert(foo._fun_boolean_int_string_intarray(true, 20, "A") instanceof Int32Array); +assert(foo._curried_fun(1)(2)(3)(4)(5) == 15); +assert(foo._higher_order_fun((n) => String(n), (s) => s.length)(1000) == 4); +assert(foo._n_any != null); +assert(foo._n_nothing == null); +assert(foo._n_throwable instanceof Error); +assert(foo._n_string === "ZZZ"); +assert(foo._n_boolean === true); +assert(foo._n_byte === 1); +assert(foo._n_short_array instanceof Int16Array); +assert(foo._n_array_int instanceof Array); +assert(foo._array_n_int instanceof Array); +assert(foo._n_array_n_int instanceof Array); + +assert(foo._array_n_array_string instanceof Array); +let x = foo._array_n_array_string[0]; +assert(x != null); +assert(x instanceof Array); +assert(x == null ? false : (x[0] === ":)")); + +foo._fun_n_int_unit(null); + +assert(foo._fun_n_boolean_n_int_n_string_n_intarray(false, undefined, "ZZZ") == null); +assert(foo._n_curried_fun(10)(null)(30) === 40); +assert(foo._n_higher_order_fun( + n => String(n), + s => (s == null ? 10 : s.length) +)(1000) === 4); + +function box(): string { + return "OK"; +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/primitives/tsconfig.json b/js/js.translator/testData/typescript-export/primitives/tsconfig.json new file mode 100644 index 00000000000..17612c56c9d --- /dev/null +++ b/js/js.translator/testData/typescript-export/primitives/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [ "./*" ], + "extends": "../common.tsconfig.json" +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/selectiveExport/selectiveExport.d.ts b/js/js.translator/testData/typescript-export/selectiveExport/selectiveExport.d.ts new file mode 100644 index 00000000000..646eaeb6235 --- /dev/null +++ b/js/js.translator/testData/typescript-export/selectiveExport/selectiveExport.d.ts @@ -0,0 +1,27 @@ +declare namespace JS_TESTS { + type Nullable = T | null | undefined + namespace foo { + interface ExportedInternalInterface { + } + } + namespace foo { + interface FileLevelExportedExternalInterface { + } + } + namespace foo { + const exportedVal: number; + function exportedFun(): number + class ExportedClass { + constructor() + readonly value: number; + } + } + namespace foo { + const fileLevelExportedVal: number; + function fileLevelExportedFun(): number + class FileLevelExportedClass { + constructor() + readonly value: number; + } + } +} diff --git a/js/js.translator/testData/typescript-export/selectiveExport/selectiveExport.kt b/js/js.translator/testData/typescript-export/selectiveExport/selectiveExport.kt new file mode 100644 index 00000000000..4ede8cb5196 --- /dev/null +++ b/js/js.translator/testData/typescript-export/selectiveExport/selectiveExport.kt @@ -0,0 +1,43 @@ +// TARGET_BACKEND: JS_IR +// CHECK_TYPESCRIPT_DECLARATIONS +// RUN_PLAIN_BOX_FUNCTION + +// FILE: file1.kt +package foo + +@JsExport +val exportedVal = 10 + +@JsExport +fun exportedFun() = 10 + +@JsExport +class ExportedClass { + val value = 10 +} + +@JsExport +external interface ExportedInternalInterface + +val _val = 10 + +fun _fun() = 10 + +class Class + +external interface ExternalInterface + +// FILE: file2.kt + +@file:JsExport + +package foo + +val fileLevelExportedVal = 10 +fun fileLevelExportedFun() = 10 +class FileLevelExportedClass { + val value = 10 +} +external interface FileLevelExportedExternalInterface + + diff --git a/js/js.translator/testData/typescript-export/selectiveExport/selectiveExport__main.js b/js/js.translator/testData/typescript-export/selectiveExport/selectiveExport__main.js new file mode 100644 index 00000000000..9f09deaa899 --- /dev/null +++ b/js/js.translator/testData/typescript-export/selectiveExport/selectiveExport__main.js @@ -0,0 +1,15 @@ +"use strict"; +var foo = JS_TESTS.foo; +function box() { + var tens = [ + foo.exportedVal, + foo.exportedFun(), + new foo.ExportedClass().value, + foo.fileLevelExportedVal, + foo.fileLevelExportedFun(), + new foo.FileLevelExportedClass().value + ]; + if (tens.every(function (value) { return value === 10; })) + return "OK"; + return "FAIL"; +} diff --git a/js/js.translator/testData/typescript-export/selectiveExport/selectiveExport__main.ts b/js/js.translator/testData/typescript-export/selectiveExport/selectiveExport__main.ts new file mode 100644 index 00000000000..a2a00d2e731 --- /dev/null +++ b/js/js.translator/testData/typescript-export/selectiveExport/selectiveExport__main.ts @@ -0,0 +1,17 @@ +import foo = JS_TESTS.foo; + +function box(): string { + const tens: number[] = [ + foo.exportedVal, + foo.exportedFun(), + new foo.ExportedClass().value, + foo.fileLevelExportedVal, + foo.fileLevelExportedFun(), + new foo.FileLevelExportedClass().value + ]; + + if (tens.every((value) => value === 10)) + return "OK"; + + return "FAIL"; +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/selectiveExport/tsconfig.json b/js/js.translator/testData/typescript-export/selectiveExport/tsconfig.json new file mode 100644 index 00000000000..17612c56c9d --- /dev/null +++ b/js/js.translator/testData/typescript-export/selectiveExport/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [ "./*" ], + "extends": "../common.tsconfig.json" +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/visibility/tsconfig.json b/js/js.translator/testData/typescript-export/visibility/tsconfig.json new file mode 100644 index 00000000000..17612c56c9d --- /dev/null +++ b/js/js.translator/testData/typescript-export/visibility/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [ "./*" ], + "extends": "../common.tsconfig.json" +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/visibility/visibility.d.ts b/js/js.translator/testData/typescript-export/visibility/visibility.d.ts new file mode 100644 index 00000000000..57b6edc6071 --- /dev/null +++ b/js/js.translator/testData/typescript-export/visibility/visibility.d.ts @@ -0,0 +1,20 @@ +declare namespace JS_TESTS { + type Nullable = T | null | undefined + interface publicInterface { + } + const publicVal: number; + function publicFun(): number + class publicClass { + constructor() + } + class Class { + constructor() + readonly publicVal: number; + publicFun(): number + } + namespace Class { + class publicClass { + constructor() + } + } +} diff --git a/js/js.translator/testData/typescript-export/visibility/visibility.kt b/js/js.translator/testData/typescript-export/visibility/visibility.kt new file mode 100644 index 00000000000..d03856351e3 --- /dev/null +++ b/js/js.translator/testData/typescript-export/visibility/visibility.kt @@ -0,0 +1,38 @@ +// TARGET_BACKEND: JS_IR +// CHECK_TYPESCRIPT_DECLARATIONS +// RUN_PLAIN_BOX_FUNCTION + +@file:JsExport + +internal val internalVal = 10 +internal fun internalFun() = 10 +internal class internalClass +internal external interface internalInterface + +private val privateVal = 10 +private fun privateFun() = 10 +private class privateClass +private external interface privateInterface + +public val publicVal = 10 +public fun publicFun() = 10 +public class publicClass +public external interface publicInterface + +open class Class { + internal val internalVal = 10 + internal fun internalFun() = 10 + internal class internalClass + + private val privateVal = 10 + private fun privateFun() = 10 + private class privateClass + + protected val protectedVal = 10 + protected fun protectedFun() = 10 + protected class protectedClass + + public val publicVal = 10 + public fun publicFun() = 10 + public class publicClass +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/visibility/visibility__main.js b/js/js.translator/testData/typescript-export/visibility/visibility__main.js new file mode 100644 index 00000000000..162373ce09d --- /dev/null +++ b/js/js.translator/testData/typescript-export/visibility/visibility__main.js @@ -0,0 +1,17 @@ +"use strict"; +var Class = JS_TESTS.Class; +function box() { + var tens = [ + JS_TESTS.publicVal, + JS_TESTS.publicFun(), + new JS_TESTS.Class().publicVal, + new JS_TESTS.Class().publicFun() + ]; + if (!tens.every(function (value) { return value === 10; })) + return "Fail 1"; + if (!(new Class() instanceof Class)) + return "Fail 2"; + if (!(new Class.publicClass() instanceof Class.publicClass)) + return "Fail 3"; + return "OK"; +} diff --git a/js/js.translator/testData/typescript-export/visibility/visibility__main.ts b/js/js.translator/testData/typescript-export/visibility/visibility__main.ts new file mode 100644 index 00000000000..3d7897071df --- /dev/null +++ b/js/js.translator/testData/typescript-export/visibility/visibility__main.ts @@ -0,0 +1,18 @@ +import Class = JS_TESTS.Class; + +function box(): string { + const tens: number[] = [ + JS_TESTS.publicVal, + JS_TESTS.publicFun(), + new JS_TESTS.Class().publicVal, + new JS_TESTS.Class().publicFun() + ]; + + if (!tens.every(value => value === 10)) + return "Fail 1"; + if (!(new Class() instanceof Class)) + return "Fail 2"; + if (!(new Class.publicClass() instanceof Class.publicClass)) + return "Fail 3"; + return "OK"; +}