[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:
+21
-1
@@ -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 */"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
|
||||
Generated
+16
@@ -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")
|
||||
|
||||
+25
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -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()
|
||||
+20
@@ -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";
|
||||
}
|
||||
+24
@@ -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";
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"include": [ "./*" ],
|
||||
"extends": "../common.tsconfig.json"
|
||||
}
|
||||
Reference in New Issue
Block a user