[K/JS] Add @ts-ignore for the case when there is a class with a private primary constructor (or excluded from export) which has an exported subclass ^Fixed KT-52563

This commit is contained in:
Artem Kobzar
2023-01-04 15:21:40 +00:00
committed by Space Team
parent 2a724787f0
commit 97be632c9a
8 changed files with 152 additions and 1 deletions
@@ -7,14 +7,18 @@ package org.jetbrains.kotlin.ir.backend.js.export
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.backend.js.utils.JsAnnotations
import org.jetbrains.kotlin.ir.backend.js.utils.getFqNameWithJsNameWhenAvailable
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.ir.util.isObject
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.primaryConstructor
import org.jetbrains.kotlin.js.common.isValidES5Identifier
import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.kotlin.utils.addToStdlib.runIf
private const val Nullable = "Nullable"
@@ -306,12 +310,24 @@ class ExportModelToTsDeclarations {
val bodyString = privateCtorString + membersString + indent
val nestedClasses = nonInnerClasses + innerClasses.map { it.withProtectedConstructors() }
val tsIgnoreForPrivateConstructorInheritance = if (hasSuperClassWithPrivateConstructor()) {
tsIgnore("extends class with private primary constructor") + "\n$indent"
} else ""
val klassExport =
"$prefix$modifiers$keyword $name$renderedTypeParameters$superClassClause$superInterfacesClause {\n$bodyString}"
val staticsExport =
if (nestedClasses.isNotEmpty()) "\n" + ExportedNamespace(name, nestedClasses).toTypeScript(indent, prefix) else ""
return if (name.isValidES5Identifier()) klassExport + staticsExport else ""
return if (name.isValidES5Identifier()) tsIgnoreForPrivateConstructorInheritance + klassExport + staticsExport else ""
}
private fun ExportedRegularClass.hasSuperClassWithPrivateConstructor(): Boolean {
return superClasses.firstIsInstanceOrNull<ExportedType.ClassType>()
?.ir
?.takeIf { !it.isObject }
?.primaryConstructor
?.let { it.visibility == DescriptorVisibilities.PRIVATE || it.hasAnnotation(JsAnnotations.jsExportIgnoreFqn) } ?: false
}
private fun List<ExportedType>.toExtendsClause(indent: String): String {
@@ -451,4 +467,8 @@ class ExportModelToTsDeclarations {
it.couldBeProperty() && it.ir.visibility != DescriptorVisibilities.PROTECTED
}
}
private fun tsIgnore(reason: String): String {
return "/* @ts-ignore: $reason */"
}
}
+1
View File
@@ -226,6 +226,7 @@ val generateTypeScriptJsExportOnFileTests = sequential(
!it.path.endsWith("implicit-export") &&
!it.path.endsWith("inheritance") &&
!it.path.endsWith("strict-implicit-export") &&
!it.path.endsWith("private-primary-constructor") &&
!it.path.endsWith(exportFileDirPostfix)
}
.map { generateJsExportOnFileTestFor(it.name) }
@@ -637,6 +637,22 @@ public class IrJsTypeScriptExportTestGenerated extends AbstractIrJsTypeScriptExp
}
}
@Nested
@TestMetadata("js/js.translator/testData/typescript-export/private-primary-constructor")
@TestDataPath("$PROJECT_ROOT")
public class Private_primary_constructor {
@Test
public void testAllFilesPresentInPrivate_primary_constructor() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/private-primary-constructor"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true);
}
@Test
@TestMetadata("private-primary-constructor.kt")
public void testPrivate_primary_constructor() throws Exception {
runTest("js/js.translator/testData/typescript-export/private-primary-constructor/private-primary-constructor.kt");
}
}
@Nested
@TestMetadata("js/js.translator/testData/typescript-export/properties")
@TestDataPath("$PROJECT_ROOT")
@@ -0,0 +1,25 @@
declare namespace JS_TESTS {
type Nullable<T> = T | null | undefined
namespace foo {
class ClassWithoutPrimary {
private constructor();
get value(): string;
static fromInt(value: number): foo.ClassWithoutPrimary;
static fromString(value: string): foo.ClassWithoutPrimary;
}
/* @ts-ignore: extends class with private primary constructor */
class SomeBaseClass extends foo.ClassWithoutPrimary {
private constructor();
get answer(): number;
static secondary(): foo.SomeBaseClass;
}
/* @ts-ignore: extends class with private primary constructor */
class SomeExtendingClass extends /* foo.IntermediateClass1 */ foo.SomeBaseClass {
private constructor();
}
/* @ts-ignore: extends class with private primary constructor */
class FinalClassInChain extends /* foo.IntermediateClass2 */ foo.SomeExtendingClass {
constructor();
}
}
}
@@ -0,0 +1,41 @@
// KT-52563
// CHECK_TYPESCRIPT_DECLARATIONS
// RUN_PLAIN_BOX_FUNCTION
// SKIP_MINIFICATION
// SKIP_NODE_JS
// INFER_MAIN_MODULE
// MODULE: JS_TESTS
// FILE: enum-classes.kt
package foo
@JsExport
open class ClassWithoutPrimary {
val value: String
@JsName("fromInt")
constructor(value: Int) {
this.value = value.toString()
}
@JsName("fromString")
constructor(value: String) {
this.value = value
}
}
@JsExport
open class SomeBaseClass private constructor(val answer: Int): ClassWithoutPrimary(answer) {
@JsName("secondary")
constructor() : this(42)
}
open class IntermediateClass1: SomeBaseClass()
@JsExport
open class SomeExtendingClass @JsExport.Ignore public constructor() : IntermediateClass1()
open class IntermediateClass2: SomeExtendingClass()
@JsExport
class FinalClassInChain: IntermediateClass2()
@@ -0,0 +1,20 @@
"use strict";
var SomeBaseClass = JS_TESTS.foo.SomeBaseClass;
var SomeExtendingClass = JS_TESTS.foo.SomeExtendingClass;
var FinalClassInChain = JS_TESTS.foo.FinalClassInChain;
function assert(condition) {
if (!condition) {
throw "Assertion failed";
}
}
function box() {
// @ts-expect-error "the constructor is private and can't be used from JS/TS code"
var baseClass = new SomeBaseClass(4);
assert(baseClass.answer === 4);
// @ts-expect-error "the constructor is private and can't be used from JS/TS code"
var extendingClass = new SomeExtendingClass();
assert(extendingClass.answer === 42);
var finalClassInChain = new FinalClassInChain();
assert(finalClassInChain.answer === 42);
return "OK";
}
@@ -0,0 +1,24 @@
import SomeBaseClass = JS_TESTS.foo.SomeBaseClass;
import SomeExtendingClass = JS_TESTS.foo.SomeExtendingClass;
import FinalClassInChain = JS_TESTS.foo.FinalClassInChain;
function assert(condition: boolean) {
if (!condition) {
throw "Assertion failed";
}
}
function box(): string {
// @ts-expect-error "the constructor is private and can't be used from JS/TS code"
const baseClass = new SomeBaseClass(4)
assert(baseClass.answer === 4)
// @ts-expect-error "the constructor is private and can't be used from JS/TS code"
const extendingClass = new SomeExtendingClass()
assert(extendingClass.answer === 42)
const finalClassInChain = new FinalClassInChain()
assert(finalClassInChain.answer === 42)
return "OK";
}
@@ -0,0 +1,4 @@
{
"include": [ "./*" ],
"extends": "../common.tsconfig.json"
}