From 4eb5af68f42cab8d392d4958c4dae89e5adc1ab2 Mon Sep 17 00:00:00 2001 From: Artem Kobzar Date: Fri, 3 Mar 2023 10:18:11 +0000 Subject: [PATCH] [K/JS] Add @deprecated commentary if @Deprecated annotation exists on a declaration. ^KT-56405 Fixed --- .../ir/backend/js/export/ExportModel.kt | 8 ++- .../backend/js/export/ExportModelGenerator.kt | 17 ++++-- .../js/export/ExportModelToTsDeclarations.kt | 17 +++++- .../ir/backend/js/utils/AnnotationUtils.kt | 4 ++ .../ir/IrJsTypeScriptExportTestGenerated.java | 32 ++++++++++ .../deprecated.d.ts | 43 +++++++++++++ .../deprecated-in-exported-file/deprecated.kt | 60 +++++++++++++++++++ .../deprecated__main.js | 13 ++++ .../deprecated__main.ts | 15 +++++ .../deprecated-in-exported-file/tsconfig.json | 4 ++ .../deprecated/deprecated.d.ts | 43 +++++++++++++ .../deprecated/deprecated.kt | 56 +++++++++++++++++ .../deprecated/deprecated__main.js | 13 ++++ .../deprecated/deprecated__main.ts | 15 +++++ .../deprecated/tsconfig.json | 4 ++ 15 files changed, 337 insertions(+), 7 deletions(-) create mode 100644 js/js.translator/testData/typescript-export/deprecated-in-exported-file/deprecated.d.ts create mode 100644 js/js.translator/testData/typescript-export/deprecated-in-exported-file/deprecated.kt create mode 100644 js/js.translator/testData/typescript-export/deprecated-in-exported-file/deprecated__main.js create mode 100644 js/js.translator/testData/typescript-export/deprecated-in-exported-file/deprecated__main.ts create mode 100644 js/js.translator/testData/typescript-export/deprecated-in-exported-file/tsconfig.json create mode 100644 js/js.translator/testData/typescript-export/deprecated/deprecated.d.ts create mode 100644 js/js.translator/testData/typescript-export/deprecated/deprecated.kt create mode 100644 js/js.translator/testData/typescript-export/deprecated/deprecated__main.js create mode 100644 js/js.translator/testData/typescript-export/deprecated/deprecated__main.ts create mode 100644 js/js.translator/testData/typescript-export/deprecated/tsconfig.json 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 index b248693717f..40badb49b56 100644 --- 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 @@ -10,7 +10,13 @@ import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.serialization.js.ModuleKind -sealed class ExportedDeclaration +sealed class ExportedDeclaration { + val attributes = mutableListOf() +} + +sealed class ExportedAttribute { + class DeprecatedAttribute(val message: String): ExportedAttribute() +} data class ExportedModule( val name: String, 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 index aaf376aafa8..43c07206b62 100644 --- 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 @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.ir.backend.js.export import org.jetbrains.kotlin.backend.common.ir.isExpect +import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.DescriptorVisibilities @@ -61,7 +62,7 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac is IrClass -> exportClass(candidate) is IrField -> null else -> error("Can't export declaration $candidate") - } + }?.withAttributesFor(candidate) } private fun exportClass(candidate: IrClass): ExportedDeclaration? { @@ -315,16 +316,16 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac when (candidate) { is IrSimpleFunction -> - members.addIfNotNull(exportFunction(candidate)) + members.addIfNotNull(exportFunction(candidate)?.withAttributesFor(candidate)) is IrConstructor -> - members.addIfNotNull(exportConstructor(candidate)) + members.addIfNotNull(exportConstructor(candidate)?.withAttributesFor(candidate)) is IrProperty -> - members.addIfNotNull(exportProperty(candidate)) + members.addIfNotNull(exportProperty(candidate)?.withAttributesFor(candidate)) is IrClass -> { - val ec = exportClass(candidate) + val ec = exportClass(candidate)?.withAttributesFor(candidate) if (ec is ExportedClass) { nestedClasses.add(ec) } else { @@ -549,6 +550,12 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac ) } + private fun ExportedDeclaration.withAttributesFor(declaration: IrDeclaration): ExportedDeclaration { + declaration.getDeprecated()?.let { attributes.add(ExportedAttribute.DeprecatedAttribute(it)) } + + return this + } + private fun exportType(type: IrType, shouldCalculateExportedSupertypeForImplicit: Boolean = true): ExportedType { if (type is IrDynamicType) return ExportedType.Primitive.Any 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 index 20f0c025a14..387178d8189 100644 --- 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 @@ -96,7 +96,7 @@ class ExportModelToTsDeclarations { joinToString("") { it.toTypeScript(indent) + "\n" } private fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = "", esModules: Boolean = false): String = - indent + when (this) { + attributes.toTypeScript(indent) + indent + when (this) { is ErrorDeclaration -> generateTypeScriptString() is ExportedConstructor -> generateTypeScriptString(indent) is ExportedConstructSignature -> generateTypeScriptString(indent) @@ -107,6 +107,17 @@ class ExportModelToTsDeclarations { is ExportedObject -> generateTypeScriptString(indent, prefix, esModules) } + private fun Iterable.toTypeScript(indent: String): String { + return joinToString("\n") { it.toTypeScript(indent) } + .run { if (isNotEmpty()) plus("\n") else this } + } + + private fun ExportedAttribute.toTypeScript(indent: String): String { + return when (this) { + is ExportedAttribute.DeprecatedAttribute -> indent + tsDeprecated(message) + } + } + private fun ErrorDeclaration.generateTypeScriptString(): String { return "/* ErrorDeclaration: $message */" } @@ -479,4 +490,8 @@ class ExportModelToTsDeclarations { private fun tsIgnore(reason: String): String { return "/* @ts-ignore: $reason */" } + + private fun tsDeprecated(message: String): String { + return "/** @deprecated $message */" + } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt index a6ae03671b7..5ffda68623f 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.ir.backend.js.utils +import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName @@ -48,6 +49,9 @@ fun IrAnnotationContainer.getJsQualifier(): String? = fun IrAnnotationContainer.getJsName(): String? = getAnnotation(JsAnnotations.jsNameFqn)?.getSingleConstStringArgument() +fun IrAnnotationContainer.getDeprecated(): String? = + getAnnotation(StandardNames.FqNames.deprecated)?.getSingleConstStringArgument() + fun IrAnnotationContainer.hasJsPolyfill(): Boolean = hasAnnotation(JsAnnotations.JsPolyfillFqn) diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsTypeScriptExportTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsTypeScriptExportTestGenerated.java index 1dbc2d32fe2..b4d5e4e7352 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsTypeScriptExportTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsTypeScriptExportTestGenerated.java @@ -121,6 +121,38 @@ public class IrJsTypeScriptExportTestGenerated extends AbstractIrJsTypeScriptExp } } + @Nested + @TestMetadata("js/js.translator/testData/typescript-export/deprecated") + @TestDataPath("$PROJECT_ROOT") + public class Deprecated { + @Test + public void testAllFilesPresentInDeprecated() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/deprecated"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true); + } + + @Test + @TestMetadata("deprecated.kt") + public void testDeprecated() throws Exception { + runTest("js/js.translator/testData/typescript-export/deprecated/deprecated.kt"); + } + } + + @Nested + @TestMetadata("js/js.translator/testData/typescript-export/deprecated-in-exported-file") + @TestDataPath("$PROJECT_ROOT") + public class Deprecated_in_exported_file { + @Test + public void testAllFilesPresentInDeprecated_in_exported_file() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/deprecated-in-exported-file"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true); + } + + @Test + @TestMetadata("deprecated.kt") + public void testDeprecated() throws Exception { + runTest("js/js.translator/testData/typescript-export/deprecated-in-exported-file/deprecated.kt"); + } + } + @Nested @TestMetadata("js/js.translator/testData/typescript-export/enum-classes") @TestDataPath("$PROJECT_ROOT") diff --git a/js/js.translator/testData/typescript-export/deprecated-in-exported-file/deprecated.d.ts b/js/js.translator/testData/typescript-export/deprecated-in-exported-file/deprecated.d.ts new file mode 100644 index 00000000000..b1124280c9d --- /dev/null +++ b/js/js.translator/testData/typescript-export/deprecated-in-exported-file/deprecated.d.ts @@ -0,0 +1,43 @@ +declare namespace JS_TESTS { + type Nullable = T | null | undefined + namespace foo { + /** @deprecated message 2 */ + const bar: string; + /** @deprecated message 1 */ + function foo(): void; + /** @deprecated message 3 */ + class TestClass { + constructor(); + } + class AnotherClass { + /** @deprecated message 4 */ + constructor(value: string); + get value(): string; + /** @deprecated message 5 */ + static fromNothing(): foo.AnotherClass; + static fromInt(value: number): foo.AnotherClass; + /** @deprecated message 6 */ + foo(): void; + baz(): void; + /** @deprecated message 7 */ + get bar(): string; + } + interface TestInterface { + /** @deprecated message 8 */ + foo(): void; + bar(): void; + /** @deprecated message 9 */ + readonly baz: string; + readonly __doNotUseOrImplementIt: { + readonly "foo.TestInterface": unique symbol; + }; + } + const TestObject: { + /** @deprecated message 10 */ + foo(): void; + bar(): void; + /** @deprecated message 11 */ + get baz(): string; + }; + } +} diff --git a/js/js.translator/testData/typescript-export/deprecated-in-exported-file/deprecated.kt b/js/js.translator/testData/typescript-export/deprecated-in-exported-file/deprecated.kt new file mode 100644 index 00000000000..59db65f0f0b --- /dev/null +++ b/js/js.translator/testData/typescript-export/deprecated-in-exported-file/deprecated.kt @@ -0,0 +1,60 @@ +/** This file is generated by {@link :js:js.test:generateJsExportOnFileTestFilesForTS} task. DO NOT MODIFY MANUALLY */ + +// CHECK_TYPESCRIPT_DECLARATIONS +// RUN_PLAIN_BOX_FUNCTION +// SKIP_MINIFICATION +// SKIP_NODE_JS +// INFER_MAIN_MODULE +// MODULE: JS_TESTS +// FILE: deprecated.kt + +@file:JsExport + +package foo + + +@Deprecated("message 1") +fun foo() {} + + +@Deprecated("message 2") +val bar: String = "Test" + + +@Deprecated("message 3") +class TestClass + + +class AnotherClass @Deprecated("message 4") constructor(val value: String) { + @JsName("fromNothing") + @Deprecated("message 5") constructor(): this("Test") + + @JsName("fromInt") + constructor(value: Int): this(value.toString()) + + @Deprecated("message 6") + fun foo() {} + + fun baz() {} + + @Deprecated("message 7") + val bar: String = "Test" +} + + +interface TestInterface { + @Deprecated("message 8") + fun foo() + fun bar() + @Deprecated("message 9") + val baz: String +} + + +object TestObject { + @Deprecated("message 10") + fun foo() {} + fun bar() {} + @Deprecated("message 11") + val baz: String = "Test" +} diff --git a/js/js.translator/testData/typescript-export/deprecated-in-exported-file/deprecated__main.js b/js/js.translator/testData/typescript-export/deprecated-in-exported-file/deprecated__main.js new file mode 100644 index 00000000000..bb79050a8fc --- /dev/null +++ b/js/js.translator/testData/typescript-export/deprecated-in-exported-file/deprecated__main.js @@ -0,0 +1,13 @@ +"use strict"; +var bar = JS_TESTS.foo.bar; +var foo = JS_TESTS.foo.foo; +function assert(condition) { + if (!condition) { + throw "Assertion failed"; + } +} +function box() { + assert(bar == "Test"); + assert(foo() == undefined); + return "OK"; +} diff --git a/js/js.translator/testData/typescript-export/deprecated-in-exported-file/deprecated__main.ts b/js/js.translator/testData/typescript-export/deprecated-in-exported-file/deprecated__main.ts new file mode 100644 index 00000000000..b60027e4fd9 --- /dev/null +++ b/js/js.translator/testData/typescript-export/deprecated-in-exported-file/deprecated__main.ts @@ -0,0 +1,15 @@ +import bar = JS_TESTS.foo.bar +import foo = JS_TESTS.foo.foo + +function assert(condition: boolean) { + if (!condition) { + throw "Assertion failed"; + } +} + +function box(): string { + assert(bar == "Test"); + assert(foo() == undefined); + + return "OK"; +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/deprecated-in-exported-file/tsconfig.json b/js/js.translator/testData/typescript-export/deprecated-in-exported-file/tsconfig.json new file mode 100644 index 00000000000..17612c56c9d --- /dev/null +++ b/js/js.translator/testData/typescript-export/deprecated-in-exported-file/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/deprecated/deprecated.d.ts b/js/js.translator/testData/typescript-export/deprecated/deprecated.d.ts new file mode 100644 index 00000000000..b1124280c9d --- /dev/null +++ b/js/js.translator/testData/typescript-export/deprecated/deprecated.d.ts @@ -0,0 +1,43 @@ +declare namespace JS_TESTS { + type Nullable = T | null | undefined + namespace foo { + /** @deprecated message 2 */ + const bar: string; + /** @deprecated message 1 */ + function foo(): void; + /** @deprecated message 3 */ + class TestClass { + constructor(); + } + class AnotherClass { + /** @deprecated message 4 */ + constructor(value: string); + get value(): string; + /** @deprecated message 5 */ + static fromNothing(): foo.AnotherClass; + static fromInt(value: number): foo.AnotherClass; + /** @deprecated message 6 */ + foo(): void; + baz(): void; + /** @deprecated message 7 */ + get bar(): string; + } + interface TestInterface { + /** @deprecated message 8 */ + foo(): void; + bar(): void; + /** @deprecated message 9 */ + readonly baz: string; + readonly __doNotUseOrImplementIt: { + readonly "foo.TestInterface": unique symbol; + }; + } + const TestObject: { + /** @deprecated message 10 */ + foo(): void; + bar(): void; + /** @deprecated message 11 */ + get baz(): string; + }; + } +} diff --git a/js/js.translator/testData/typescript-export/deprecated/deprecated.kt b/js/js.translator/testData/typescript-export/deprecated/deprecated.kt new file mode 100644 index 00000000000..37fe86865d9 --- /dev/null +++ b/js/js.translator/testData/typescript-export/deprecated/deprecated.kt @@ -0,0 +1,56 @@ +// CHECK_TYPESCRIPT_DECLARATIONS +// RUN_PLAIN_BOX_FUNCTION +// SKIP_MINIFICATION +// SKIP_NODE_JS +// INFER_MAIN_MODULE +// MODULE: JS_TESTS +// FILE: deprecated.kt + +package foo + +@JsExport +@Deprecated("message 1") +fun foo() {} + +@JsExport +@Deprecated("message 2") +val bar: String = "Test" + +@JsExport +@Deprecated("message 3") +class TestClass + +@JsExport +class AnotherClass @Deprecated("message 4") constructor(val value: String) { + @JsName("fromNothing") + @Deprecated("message 5") constructor(): this("Test") + + @JsName("fromInt") + constructor(value: Int): this(value.toString()) + + @Deprecated("message 6") + fun foo() {} + + fun baz() {} + + @Deprecated("message 7") + val bar: String = "Test" +} + +@JsExport +interface TestInterface { + @Deprecated("message 8") + fun foo() + fun bar() + @Deprecated("message 9") + val baz: String +} + +@JsExport +object TestObject { + @Deprecated("message 10") + fun foo() {} + fun bar() {} + @Deprecated("message 11") + val baz: String = "Test" +} diff --git a/js/js.translator/testData/typescript-export/deprecated/deprecated__main.js b/js/js.translator/testData/typescript-export/deprecated/deprecated__main.js new file mode 100644 index 00000000000..bb79050a8fc --- /dev/null +++ b/js/js.translator/testData/typescript-export/deprecated/deprecated__main.js @@ -0,0 +1,13 @@ +"use strict"; +var bar = JS_TESTS.foo.bar; +var foo = JS_TESTS.foo.foo; +function assert(condition) { + if (!condition) { + throw "Assertion failed"; + } +} +function box() { + assert(bar == "Test"); + assert(foo() == undefined); + return "OK"; +} diff --git a/js/js.translator/testData/typescript-export/deprecated/deprecated__main.ts b/js/js.translator/testData/typescript-export/deprecated/deprecated__main.ts new file mode 100644 index 00000000000..b60027e4fd9 --- /dev/null +++ b/js/js.translator/testData/typescript-export/deprecated/deprecated__main.ts @@ -0,0 +1,15 @@ +import bar = JS_TESTS.foo.bar +import foo = JS_TESTS.foo.foo + +function assert(condition: boolean) { + if (!condition) { + throw "Assertion failed"; + } +} + +function box(): string { + assert(bar == "Test"); + assert(foo() == undefined); + + return "OK"; +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/deprecated/tsconfig.json b/js/js.translator/testData/typescript-export/deprecated/tsconfig.json new file mode 100644 index 00000000000..17612c56c9d --- /dev/null +++ b/js/js.translator/testData/typescript-export/deprecated/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [ "./*" ], + "extends": "../common.tsconfig.json" +} \ No newline at end of file