feat(KT-48814): represent all of the external declarations nullable properties as an optional fields inside d.ts.

This commit is contained in:
Artem Kobzar
2022-09-02 10:50:20 +00:00
committed by Space
parent a52d8f0364
commit 0bb0be8703
12 changed files with 41 additions and 8 deletions
@@ -59,6 +59,7 @@ class ExportedProperty(
val isField: Boolean = false, val isField: Boolean = false,
val irGetter: IrFunction? = null, val irGetter: IrFunction? = null,
val irSetter: IrFunction? = null, val irSetter: IrFunction? = null,
val isOptional: Boolean = false
) : ExportedDeclaration() ) : ExportedDeclaration()
// TODO: Cover all cases with frontend and disable error declarations // TODO: Cover all cases with frontend and disable error declarations
@@ -139,6 +139,9 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
specializeType: ExportedType? = null specializeType: ExportedType? = null
): ExportedDeclaration { ): ExportedDeclaration {
val parentClass = property.parent as? IrClass val parentClass = property.parent as? IrClass
val isOptional = property.isEffectivelyExternal() &&
property.parent is IrClass &&
property.getter?.returnType?.isNullable() == true
return ExportedProperty( return ExportedProperty(
name = property.getExportedIdentifier(), name = property.getExportedIdentifier(),
@@ -149,7 +152,8 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
isProtected = property.visibility == DescriptorVisibilities.PROTECTED, isProtected = property.visibility == DescriptorVisibilities.PROTECTED,
isField = parentClass?.isInterface == true, isField = parentClass?.isInterface == true,
irGetter = property.getter, irGetter = property.getter,
irSetter = property.setter irSetter = property.setter,
isOptional = isOptional
) )
} }
@@ -133,7 +133,8 @@ class ExportModelToTsDeclarations {
"" ""
} else { } else {
val readonly = if (isMember && !mutable) "readonly " else "" val readonly = if (isMember && !mutable) "readonly " else ""
"$prefix$visibility$possibleStatic$keyword$readonly$memberName: $typeToTypeScript;" val optional = if (isOptional) "?" else ""
"$prefix$visibility$possibleStatic$keyword$readonly$memberName$optional: $typeToTypeScript;"
} }
} }
} }
@@ -25,7 +25,7 @@ object JsPlatformConfigurator : PlatformConfiguratorBase(
JsRuntimeAnnotationChecker, JsRuntimeAnnotationChecker,
JsDynamicDeclarationChecker, JsDynamicDeclarationChecker,
JsExportAnnotationChecker, JsExportAnnotationChecker,
JsExportDeclarationChecker JsExportDeclarationChecker,
), ),
additionalCallCheckers = listOf( additionalCallCheckers = listOf(
JsModuleCallChecker, JsModuleCallChecker,
@@ -2,8 +2,8 @@ declare namespace JS_TESTS {
type Nullable<T> = T | null | undefined type Nullable<T> = T | null | undefined
namespace foo { namespace foo {
interface I<T, S, U> { interface I<T, S, U> {
x: T; x?: T;
readonly y: S; readonly y?: S;
z(u: U): void; z(u: U): void;
} }
interface I2 { interface I2 {
@@ -1,5 +1,11 @@
declare namespace JS_TESTS { declare namespace JS_TESTS {
type Nullable<T> = T | null | undefined type Nullable<T> = T | null | undefined
namespace foo {
interface OptionalFieldsInterface {
readonly required: number;
readonly notRequired?: Nullable<number>;
}
}
namespace foo { namespace foo {
interface TestInterface { interface TestInterface {
readonly value: string; readonly value: string;
@@ -24,5 +30,6 @@ declare namespace JS_TESTS {
readonly __doNotUseOrImplementIt: foo.TestInterfaceImpl["__doNotUseOrImplementIt"] & foo.AnotherExportedInterface["__doNotUseOrImplementIt"]; readonly __doNotUseOrImplementIt: foo.TestInterfaceImpl["__doNotUseOrImplementIt"] & foo.AnotherExportedInterface["__doNotUseOrImplementIt"];
} }
function processInterface(test: foo.TestInterface): string; function processInterface(test: foo.TestInterface): string;
function processOptionalInterface(a: foo.OptionalFieldsInterface): string;
} }
} }
@@ -31,3 +31,14 @@ class ChildTestInterfaceImpl(): TestInterfaceImpl("Test"), AnotherExportedInterf
fun processInterface(test: TestInterface): String { fun processInterface(test: TestInterface): String {
return "Owner ${test.getOwnerName()} has value '${test.value}'" return "Owner ${test.getOwnerName()} has value '${test.value}'"
} }
@JsExport
external interface OptionalFieldsInterface {
val required: Int
val notRequired: Int?
}
@JsExport
fun processOptionalInterface(a: OptionalFieldsInterface): String {
return "${a.required}${a.notRequired ?: "unknown"}"
}
@@ -2,6 +2,7 @@
var TestInterfaceImpl = JS_TESTS.foo.TestInterfaceImpl; var TestInterfaceImpl = JS_TESTS.foo.TestInterfaceImpl;
var ChildTestInterfaceImpl = JS_TESTS.foo.ChildTestInterfaceImpl; var ChildTestInterfaceImpl = JS_TESTS.foo.ChildTestInterfaceImpl;
var processInterface = JS_TESTS.foo.processInterface; var processInterface = JS_TESTS.foo.processInterface;
var processOptionalInterface = JS_TESTS.foo.processOptionalInterface;
function assert(condition) { function assert(condition) {
if (!condition) { if (!condition) {
throw "Assertion failed"; throw "Assertion failed";
@@ -12,5 +13,8 @@ function box() {
assert(processInterface(new ChildTestInterfaceImpl()) === "Owner TestInterfaceImpl has value 'Test'"); assert(processInterface(new ChildTestInterfaceImpl()) === "Owner TestInterfaceImpl has value 'Test'");
// @ts-expect-error "Just test that this code will throw compilation error for a user" // @ts-expect-error "Just test that this code will throw compilation error for a user"
assert(processInterface({ value: "bar", getOwnerName: function () { return "RandomObject"; } }) === "Owner RandomObject has value 'bar'"); assert(processInterface({ value: "bar", getOwnerName: function () { return "RandomObject"; } }) === "Owner RandomObject has value 'bar'");
assert(processOptionalInterface({ required: 4 }) == "4unknown");
assert(processOptionalInterface({ required: 4, notRequired: null }) == "4unknown");
assert(processOptionalInterface({ required: 4, notRequired: 5 }) == "45");
return "OK"; return "OK";
} }
@@ -1,6 +1,7 @@
import TestInterfaceImpl = JS_TESTS.foo.TestInterfaceImpl; import TestInterfaceImpl = JS_TESTS.foo.TestInterfaceImpl;
import ChildTestInterfaceImpl = JS_TESTS.foo.ChildTestInterfaceImpl; import ChildTestInterfaceImpl = JS_TESTS.foo.ChildTestInterfaceImpl;
import processInterface = JS_TESTS.foo.processInterface; import processInterface = JS_TESTS.foo.processInterface;
import processOptionalInterface = JS_TESTS.foo.processOptionalInterface;
function assert(condition: boolean) { function assert(condition: boolean) {
if (!condition) { if (!condition) {
@@ -15,5 +16,9 @@ function box(): string {
// @ts-expect-error "Just test that this code will throw compilation error for a user" // @ts-expect-error "Just test that this code will throw compilation error for a user"
assert(processInterface({ value: "bar", getOwnerName: () => "RandomObject" }) === "Owner RandomObject has value 'bar'") assert(processInterface({ value: "bar", getOwnerName: () => "RandomObject" }) === "Owner RandomObject has value 'bar'")
assert(processOptionalInterface({ required: 4 }) == "4unknown")
assert(processOptionalInterface({ required: 4, notRequired: null }) == "4unknown")
assert(processOptionalInterface({ required: 4, notRequired: 5 }) == "45")
return "OK"; return "OK";
} }
@@ -2,7 +2,7 @@ declare namespace JS_TESTS {
type Nullable<T> = T | null | undefined type Nullable<T> = T | null | undefined
namespace foo { namespace foo {
interface Object { interface Object {
readonly constructor: any; readonly constructor?: any;
} }
} }
namespace foo { namespace foo {
@@ -2,7 +2,7 @@ declare namespace JS_TESTS {
type Nullable<T> = T | null | undefined type Nullable<T> = T | null | undefined
namespace foo { namespace foo {
interface Object { interface Object {
readonly constructor: any; readonly constructor?: any;
} }
} }
namespace foo { namespace foo {