Skip to content

Commit b88a59f

Browse files
JSMonkSpace Team
authored andcommitted
[K/JS] Introduce @JsExport.Default annotation to export default with ESM
^KT-80401 Fixed
1 parent c0ca9c0 commit b88a59f

File tree

58 files changed

+504
-60
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+504
-60
lines changed

compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/FirJsHelpers.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ fun FirBasedSymbol<*>.isExportedObject(session: FirSession): Boolean {
125125
return when {
126126
hasAnnotationOrInsideAnnotatedClass(JsStandardClassIds.Annotations.JsExportIgnore, session) -> false
127127
hasAnnotationOrInsideAnnotatedClass(JsStandardClassIds.Annotations.JsExport, session) ||
128-
getAnnotationBooleanParameter(JsStandardClassIds.Annotations.JsImplicitExport, session) == true
128+
getAnnotationBooleanParameter(JsStandardClassIds.Annotations.JsImplicitExport, session) == true ||
129+
hasAnnotationOrInsideAnnotatedClass(JsStandardClassIds.Annotations.JsExportDefault, session)
129130
-> true
130131
else -> getContainingFile()?.symbol?.hasAnnotation(JsStandardClassIds.Annotations.JsExport, session) ?: false
131132
}

compiler/fir/checkers/checkers.web.common/src/org/jetbrains/kotlin/fir/analysis/web/common/checkers/declaration/FirJsExportAnnotationChecker.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.web.common.FirWebCommonErro
1515
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
1616
import org.jetbrains.kotlin.fir.declarations.FirFile
1717
import org.jetbrains.kotlin.fir.declarations.getAnnotationByClassId
18-
import org.jetbrains.kotlin.name.WebCommonStandardClassIds
18+
import org.jetbrains.kotlin.name.StandardClassIds
1919

2020
object FirJsExportAnnotationChecker : FirBasicDeclarationChecker(MppCheckerKind.Common) {
2121
context(context: CheckerContext, reporter: DiagnosticReporter)
2222
override fun check(declaration: FirDeclaration) {
23-
val jsExport = declaration.getAnnotationByClassId(WebCommonStandardClassIds.Annotations.JsExport, context.session) ?: return
23+
val jsExport = declaration.getAnnotationByClassId(StandardClassIds.Annotations.jsExport, context.session)
24+
?: declaration.getAnnotationByClassId(StandardClassIds.Annotations.jsExportDefault, context.session)
25+
?: return
2426

2527
if (declaration !is FirFile && !context.isTopLevel) {
2628
reporter.reportOn(jsExport.source, FirWebCommonErrors.NESTED_JS_EXPORT)

compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/jsexport/ExportModel.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ import org.jetbrains.kotlin.ir.declarations.IrClass
99
import org.jetbrains.kotlin.ir.declarations.IrFunction
1010
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
1111

12-
sealed class ExportedDeclaration
12+
sealed class ExportedDeclaration {
13+
val attributes: MutableSet<ExportedAttribute> = mutableSetOf()
14+
}
15+
16+
sealed class ExportedAttribute {
17+
object DefaultExport : ExportedAttribute()
18+
}
1319

1420
data class ExportedModule(
1521
val name: String,

compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/jsexport/ExportModelGenerator.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,15 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
4343
else -> irError("Can't export declaration") {
4444
withIrEntry("candidate", candidate)
4545
}
46+
}?.withAttributesFor(candidate)
47+
}
48+
49+
private fun <T : ExportedDeclaration> T.withAttributesFor(declaration: IrDeclaration): T {
50+
if (declaration.isJsExportDefault()) {
51+
attributes.add(ExportedAttribute.DefaultExport)
4652
}
53+
54+
return this
4755
}
4856

4957
private fun exportClass(candidate: IrClass): ExportedDeclaration? {
@@ -423,12 +431,12 @@ private fun shouldDeclarationBeExported(
423431
}
424432
}
425433

426-
if (declaration.isJsExport())
434+
if (declaration.isUnconditionallyExported())
427435
return true
428436

429437
return when (val parent = declaration.parent) {
430438
is IrDeclarationWithName -> shouldDeclarationBeExported(parent, context)
431-
is IrAnnotationContainer -> parent.isJsExport()
439+
is IrAnnotationContainer -> parent.isUnconditionallyExported()
432440
else -> false
433441
}
434442
}

compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/jsexport/ExportModelToJsStatements.kt

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,13 @@ class ExportModelToJsStatements(
8585
namespace != null ->
8686
listOf(jsAssignment(jsElementAccess(declaration.name, namespace), JsNameRef(name)).makeStmt())
8787

88-
esModules -> listOf(JsExport(name.makeRef(), alias = JsName(declaration.name, false)))
88+
esModules -> {
89+
if (declaration.attributes.contains(ExportedAttribute.DefaultExport)) {
90+
listOf(JsExport(JsExport.Subject.Default(name.makeRef())))
91+
} else {
92+
listOf(JsExport(name.makeRef(), alias = JsName(declaration.name, false)))
93+
}
94+
}
8995
else -> emptyList()
9096
}
9197
}
@@ -95,7 +101,13 @@ class ExportModelToJsStatements(
95101
when (namespace) {
96102
null -> {
97103
val property = declaration.generateTopLevelGetters()
98-
listOf(JsVars(property), JsExport(property.name.makeRef(), JsName(declaration.name, false)))
104+
val exportStatement = if (declaration.attributes.contains(ExportedAttribute.DefaultExport)) {
105+
JsExport(JsExport.Subject.Default(property.name.makeRef()))
106+
} else {
107+
JsExport(property.name.makeRef(), JsName(declaration.name, false))
108+
}
109+
110+
listOf(JsVars(property), exportStatement)
99111
}
100112
else -> {
101113
val getter = declaration.irGetter?.let { staticContext.getNameForStaticDeclaration(it) }
@@ -127,7 +139,7 @@ class ExportModelToJsStatements(
127139
declaration.name,
128140
isStatic = parentClass?.isObject != true,
129141
irGetter = declaration.irGetter,
130-
),
142+
).apply { attributes.addAll(declaration.attributes) },
131143
namespace,
132144
esModules,
133145
parentClass
@@ -170,7 +182,13 @@ class ExportModelToJsStatements(
170182
}
171183
val klassExport = when {
172184
namespace != null -> jsAssignment(newNameSpace, name.makeRef()).makeStmt()
173-
esModules -> JsExport(name.makeRef(), alias = JsName(declaration.name, false))
185+
esModules -> {
186+
if (declaration.attributes.contains(ExportedAttribute.DefaultExport)) {
187+
JsExport(JsExport.Subject.Default(name.makeRef()))
188+
} else {
189+
JsExport(name.makeRef(), JsName(declaration.name, false))
190+
}
191+
}
174192
else -> null
175193
}
176194

compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/PrepareSuspendFunctionsForExportLowering.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,15 +248,19 @@ internal class PrepareSuspendFunctionsForExportLowering(private val context: JsI
248248
}
249249

250250
val (exportAnnotations, irrelevantAnnotations) = originalFunc.annotations.partition {
251-
it.isAnnotation(JsAnnotations.jsExportFqn)
251+
it.isAnnotation(JsAnnotations.jsExportFqn) || it.isAnnotation(JsAnnotations.jsExportDefaultFqn)
252252
}
253253

254254
annotations = exportAnnotations.compactIfPossible()
255255

256256
addJsName(originalFunc.getJsNameOrKotlinName().identifier)
257257

258-
if (originalFunc.isTopLevel && !exportAnnotations.hasAnnotation(JsStandardClassIds.Annotations.JsExport)) {
259-
addJsExport()
258+
if (originalFunc.isTopLevel) {
259+
if (!exportAnnotations.hasAnnotation(JsStandardClassIds.Annotations.JsExport)
260+
&& !exportAnnotations.hasAnnotation(JsStandardClassIds.Annotations.JsExportDefault)
261+
) {
262+
addJsExport()
263+
}
260264
}
261265

262266
originalFunc.annotations = irrelevantAnnotations.compactIfPossible()

compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,16 +145,16 @@ class Merger(
145145
if (isEsModules) {
146146
val allExportRelatedStatements = fragments.flatMap { it.exports.statements }
147147
val (allExportStatements, restStatements) = allExportRelatedStatements.partitionIsInstance<JsStatement, JsExport>()
148-
val (currentModuleExportStatements, restExportStatements) = allExportStatements.partition { it.fromModule == null }
149-
val exportedElements = currentModuleExportStatements.takeIf { it.isNotEmpty() }
148+
val (currentModuleNamedExportStatements, restExportStatements) = allExportStatements.partition { it.subject is JsExport.Subject.Elements }
149+
val exportedElements = currentModuleNamedExportStatements.takeIf { it.isNotEmpty() }
150150
?.asSequence()
151151
?.flatMap { (it.subject as JsExport.Subject.Elements).elements }
152152
?.distinctBy { it.alias?.ident ?: it.name.ident }
153153
?.toList()
154154

155-
val oneLargeExportStatement = exportedElements?.let { JsExport(JsExport.Subject.Elements(it)) }
155+
val oneLargeNamedExportStatement = exportedElements?.let { JsExport(JsExport.Subject.Elements(it)) }
156156

157-
return restStatements + listOfNotNull(oneLargeExportStatement) + restExportStatements
157+
return restStatements + restExportStatements + listOfNotNull(oneLargeNamedExportStatement)
158158
} else {
159159
val exportBody = JsBlock(fragments.flatMap { it.exports.statements })
160160
if (exportBody.isEmpty) {

compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/tsexport/ExportModelGenerator.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -804,12 +804,12 @@ private fun shouldDeclarationBeExported(
804804
}
805805
}
806806

807-
if (declaration.isJsExport())
807+
if (declaration.isUnconditionallyExported())
808808
return true
809809

810810
return when (val parent = declaration.parent) {
811811
is IrDeclarationWithName -> shouldDeclarationBeExported(parent, context)
812-
is IrAnnotationContainer -> parent.isJsExport()
812+
is IrAnnotationContainer -> parent.isUnconditionallyExported()
813813
else -> false
814814
}
815815
}
@@ -913,6 +913,10 @@ private val allReservedWords = reservedWords + strictModeReservedWords
913913
fun <T : ExportedDeclaration> T.withAttributesFor(declaration: IrDeclaration): T {
914914
declaration.getDeprecated()?.let { attributes.add(ExportedAttribute.DeprecatedAttribute(it)) }
915915

916+
if (declaration.isJsExportDefault()) {
917+
attributes.add(ExportedAttribute.DefaultExport)
918+
}
919+
916920
return this
917921
}
918922

compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ object JsAnnotations {
2222
val jsFileNameFqn = FqName("kotlin.js.JsFileName")
2323
val jsQualifierFqn = FqName("kotlin.js.JsQualifier")
2424
val jsExportFqn = FqName("kotlin.js.JsExport")
25+
val jsExportDefaultFqn = FqName("kotlin.js.JsExport.Default")
2526
val jsImplicitExportFqn = FqName("kotlin.js.JsImplicitExport")
2627
val jsExportIgnoreFqn = FqName("kotlin.js.JsExport.Ignore")
2728
val jsNativeGetter = FqName("kotlin.js.nativeGetter")
@@ -61,12 +62,22 @@ fun IrAnnotationContainer.hasJsPolyfill(): Boolean =
6162
fun IrAnnotationContainer.isJsExport(): Boolean =
6263
hasAnnotation(JsAnnotations.jsExportFqn)
6364

65+
fun IrAnnotationContainer.isUnconditionallyExported(): Boolean =
66+
isJsExport() || isJsExportDefault()
67+
6468
fun IrAnnotationContainer.isJsImplicitExport(): Boolean =
6569
hasAnnotation(JsAnnotations.jsImplicitExportFqn)
6670

6771
fun IrAnnotationContainer.couldBeConvertedToExplicitExport(): Boolean? =
6872
getAnnotation(JsAnnotations.jsImplicitExportFqn)?.getSingleConstBooleanArgument()
6973

74+
fun IrAnnotationContainer.isJsExportDefault(): Boolean =
75+
annotations.any {
76+
// Using `IrSymbol.hasEqualFqName(FqName)` instead of a usual `hasAnnotation` call, because `JsExport.Default` is a nested class,
77+
// whose FQ name cannot be computed by traversing IR tree parents because it lacks `JsExport` for some reason.
78+
it.symbol.owner.parentAsClass.symbol.hasEqualFqName(JsAnnotations.jsExportDefaultFqn)
79+
}
80+
7081
fun IrAnnotationContainer.isJsExportIgnore(): Boolean =
7182
annotations.any {
7283
// Using `IrSymbol.hasEqualFqName(FqName)` instead of a usual `hasAnnotation` call, because `JsExport.Ignore` is a nested class,

compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/Constants.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ object ImportType {
4343
object ExportType {
4444
const val ALL = 0
4545
const val ITEMS = 1
46+
const val DEFAULT = 2
4647
}
4748

4849
object ExpressionIds {

0 commit comments

Comments
 (0)