[K/JS] Import @JsModule declarations without asterisk

This commit is contained in:
Artem Kobzar
2023-05-11 12:39:57 +00:00
committed by Space Team
parent 1920bbde07
commit 669efc781d
10 changed files with 130 additions and 72 deletions
@@ -223,7 +223,7 @@ class IrModuleToJsTransformer(
private val optimizeGeneratedJs = backendContext.configuration.get(JSConfigurationKeys.OPTIMIZE_GENERATED_JS, true)
private fun generateProgramFragment(fileExports: IrFileExports, minimizedMemberNames: Boolean): JsIrProgramFragment {
val nameGenerator = JsNameLinkingNamer(backendContext, minimizedMemberNames)
val nameGenerator = JsNameLinkingNamer(backendContext, minimizedMemberNames, isEsModules)
val globalNameScope = NameTable<IrDeclaration>()
@@ -329,9 +329,9 @@ class IrModuleToJsTransformer(
}
}
nameGenerator.imports.entries.forEach { (declaration, importExpression) ->
nameGenerator.imports.entries.forEach { (declaration, importStatement) ->
val tag = computeTag(declaration) ?: error("No tag for imported declaration ${declaration.render()}")
result.imports[tag] = importExpression
result.imports[tag] = importStatement
result.optionalCrossModuleImports += tag
}
@@ -17,7 +17,7 @@ class JsIrProgramFragment(val packageFqn: String) {
val declarations = JsCompositeBlock()
val exports = JsCompositeBlock()
val importedModules = mutableListOf<JsImportedModule>()
val imports = mutableMapOf<String, JsExpression>()
val imports = mutableMapOf<String, JsStatement>()
var dts: TypeScriptFragment? = null
val classes = mutableMapOf<JsName, JsIrIcClassModel>()
val initializers = JsCompositeBlock()
@@ -19,7 +19,11 @@ import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.utils.DFS
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class JsNameLinkingNamer(private val context: JsIrBackendContext, private val minimizedMemberNames: Boolean) : IrNamerBase() {
class JsNameLinkingNamer(
private val context: JsIrBackendContext,
private val minimizedMemberNames: Boolean,
private val isEsModules: Boolean
) : IrNamerBase() {
val nameMap = mutableMapOf<IrDeclaration, JsName>()
@@ -33,50 +37,19 @@ class JsNameLinkingNamer(private val context: JsIrBackendContext, private val mi
}
val importedModules = mutableListOf<JsImportedModule>()
val imports = mutableMapOf<IrDeclaration, JsExpression>()
val imports = mutableMapOf<IrDeclaration, JsStatement>()
override fun getNameForStaticDeclaration(declaration: IrDeclarationWithName): JsName {
if (declaration.isEffectivelyExternal()) {
val jsModule: String? = declaration.getJsModule()
val maybeParentFile: IrFile? = declaration.parent as? IrFile
val fileJsModule: String? = maybeParentFile?.getJsModule()
val jsQualifier: List<JsName>? = maybeParentFile?.getJsQualifier()?.split('.')?.map { JsName(it, false) }
when {
jsModule != null -> {
val nameString = if (declaration.isJsNonModule()) {
declaration.getJsNameOrKotlinName().asString()
} else {
val parent = declaration.fqNameWhenAvailable!!.parent()
parent.child(declaration.getJsNameOrKotlinName()).asString()
}
val name = JsName(sanitizeName(nameString), false)
importedModules += JsImportedModule(jsModule, name, name.makeRef())
return name
}
fileJsModule != null -> {
if (declaration !in nameMap) {
val moduleName = JsName(sanitizeName("\$module\$$fileJsModule"), true)
importedModules += JsImportedModule(fileJsModule, moduleName, null)
val qualifiedReference =
if (jsQualifier == null) moduleName.makeRef() else (listOf(moduleName) + jsQualifier).makeRef()
imports[declaration] = jsElementAccess(declaration.getJsNameOrKotlinName().identifier, qualifiedReference)
return declaration.getName()
}
}
else -> {
val name = declaration.getJsNameOrKotlinName().identifier
if (jsQualifier != null) {
imports[declaration] = jsElementAccess(name, jsQualifier.makeRef())
return declaration.getName()
}
return name.toJsName(temporary = false)
}
return when {
jsModule != null -> declaration.generateImportForDeclarationWithJsModule(jsModule)
fileJsModule != null -> declaration.generateImportForDeclarationInFileWithJsModule(fileJsModule, jsQualifier)
else -> declaration.generateRegularQualifiedImport(jsQualifier)
}
}
@@ -104,6 +77,70 @@ class JsNameLinkingNamer(private val context: JsIrBackendContext, private val mi
return JsName(field.parentAsClass.fieldData()[field]!!, false)
}
private fun IrDeclarationWithName.generateImportForDeclarationWithJsModule(jsModule: String): JsName {
val nameString = if (isJsNonModule()) {
getJsNameOrKotlinName().asString()
} else {
val parent = fqNameWhenAvailable!!.parent()
parent.child(getJsNameOrKotlinName()).asString()
}
val name = JsName(sanitizeName(nameString), true)
if (isEsModules) {
imports[this] = JsImport(jsModule, JsImport.Target.Default(name.makeRef()))
} else {
importedModules += JsImportedModule(jsModule, name, name.makeRef())
}
return name
}
private fun IrDeclarationWithName.generateImportForDeclarationInFileWithJsModule(
fileJsModule: String,
jsQualifier: List<JsName>?
): JsName {
if (this in nameMap) return getName()
val declarationStableName = getJsNameOrKotlinName().identifier
if (isEsModules) {
val importedName = jsQualifier?.firstOrNull() ?: declarationStableName.toJsName(temporary = false)
val importStatement = JsImport(fileJsModule, JsImport.Element(importedName))
imports[this] = when (val qualifiedReference = jsQualifier?.makeRef()) {
null -> importStatement
else -> JsCompositeBlock(
listOf(
importStatement,
jsElementAccess(declarationStableName, qualifiedReference).putIntoVariableWitName(
declarationStableName.toJsName()
)
)
)
}
} else {
val moduleName = JsName(sanitizeName("\$module\$$fileJsModule"), true)
importedModules += JsImportedModule(fileJsModule, moduleName, null)
val qualifiedReference =
if (jsQualifier == null) moduleName.makeRef() else (listOf(moduleName) + jsQualifier).makeRef()
imports[this] =
jsElementAccess(declarationStableName, qualifiedReference).putIntoVariableWitName(declarationStableName.toJsName())
}
return getName()
}
private fun IrDeclarationWithName.generateRegularQualifiedImport(jsQualifier: List<JsName>?): JsName {
val name = getJsNameOrKotlinName().identifier
if (jsQualifier != null) {
imports[this] = jsElementAccess(name, jsQualifier.makeRef()).putIntoVariableWitName(name.toJsName())
return getName()
}
return name.toJsName(temporary = false)
}
private fun IrClass.fieldData(): Map<IrField, String> {
return context.fieldDataCache.getOrPut(this) {
val nameCnt = hashMapOf<String, Int>()
@@ -34,9 +34,14 @@ class Merger(
rename(f.declarations)
rename(f.exports)
f.imports.entries.forEach { (declaration, importExpression) ->
val importName = nameMap[declaration] ?: error("Missing name for declaration '${declaration}'")
importStatements.putIfAbsent(declaration, JsVars(JsVars.JsVar(importName, rename(importExpression))))
f.imports.entries.forEach { (declaration, importStatement) ->
val importName = nameMap[declaration]
if (importName == null && !isEsModules) {
error("Missing name for declaration '${declaration}'")
}
importStatements.putIfAbsent(declaration, rename(importStatement).importStatementWithName(importName))
}
val classModels = (mutableMapOf<JsName, JsIrIcClassModel>() + f.classes)
@@ -60,7 +65,6 @@ class Merger(
for ((tag, crossModuleJsImport) in crossModuleReferences.jsImports) {
val importName = nameMap[tag] ?: error("Missing name for declaration '$tag'")
importStatements.putIfAbsent(tag, crossModuleJsImport.renameImportedSymbolInternalName(importName))
}
@@ -330,4 +334,29 @@ class Merger(
this += statements
endRegion()
}
private fun JsStatement.importStatementWithName(name: JsName?): JsStatement {
if (name == null) return this
return when (this) {
is JsVars -> JsVars(JsVars.JsVar(name, vars.single().initExpression))
is JsImport -> JsImport(
module,
when (target) {
is JsImport.Target.All -> JsImport.Target.All(alias = name.makeRef())
is JsImport.Target.Default -> JsImport.Target.Default(name = name.makeRef())
is JsImport.Target.Elements -> JsImport.Target.Elements(
mutableListOf(
JsImport.Element(
elements.single().name,
name.makeRef()
)
)
)
}
)
is JsCompositeBlock -> JsCompositeBlock(statements.dropLast(1) + statements.last().importStatementWithName(name))
else -> error("Unexpected import statement ${this::class.qualifiedName}")
}
}
}
@@ -53,7 +53,7 @@ object ModuleWrapperTranslation {
ModuleKind.COMMON_JS -> wrapCommonJs(function, importedModules, program)
ModuleKind.UMD -> wrapUmd(moduleId, function, importedModules, program)
ModuleKind.PLAIN -> wrapPlain(moduleId, function, importedModules, program)
ModuleKind.ES -> wrapEsModule(function, importedModules)
ModuleKind.ES -> wrapEsModule(function)
}
}
@@ -128,27 +128,19 @@ object ModuleWrapperTranslation {
return listOf(invocation.makeStmt())
}
private fun wrapEsModule(function: JsFunction, importedModules: List<JsImportedModule>): List<JsStatement> {
val (alreadyPresentedImportStatements, restStatements) = function.body.statements.partitionIsInstance<JsStatement, JsImport>()
private fun wrapEsModule(function: JsFunction): List<JsStatement> {
val (alreadyPresentedImportStatements, restStatements) = function.body.statements
.flatMap { if (it is JsCompositeBlock) it.statements else listOf(it) }
.partitionIsInstance<JsStatement, JsImport>()
val (multipleElementsImport, defaultImports) = alreadyPresentedImportStatements.partition { it.target is JsImport.Target.Elements }
val importStatements = importedModules.zip(function.parameters.drop(1)).map {
JsImport(
it.first.getRequireName(true),
if (it.first.plainReference == null) {
JsImport.Target.All(alias = it.second.name.makeRef())
} else {
JsImport.Target.Default(name = it.second.name.makeRef())
}
)
}
val alreadyPresentedImportStatementsWithoutDuplicates = alreadyPresentedImportStatements
val mergedImports = multipleElementsImport
.groupBy { it.module }
.map { (module, import) ->
JsImport(module, *import.flatMap { it.elements }.toTypedArray())
JsImport(module, *import.flatMap { it.elements }.distinctBy { it.name.ident }.toTypedArray())
}
return importStatements + alreadyPresentedImportStatementsWithoutDuplicates + restStatements.dropLast(1)
return mergedImports + defaultImports + restStatements.dropLast(1)
}
@@ -63,6 +63,10 @@ fun <T : JsNode> IrWhen.toJsNode(
fun jsElementAccess(name: String, receiver: JsExpression?): JsExpression =
jsElementAccess(JsName(name, false), receiver)
fun JsExpression.putIntoVariableWitName(name: JsName): JsVars {
return JsVars(JsVars.JsVar(name, this))
}
fun jsElementAccess(name: JsName, receiver: JsExpression?): JsExpression =
if (receiver == null || name.ident.isValidES5Identifier()) {
JsNameRef(name, receiver)
@@ -120,12 +120,8 @@ private fun JsNode.computeScopes(): Scope {
when (val target = import.target) {
is JsImport.Target.All -> target.alias.name?.let { currentScope.declaredNames += it }
is JsImport.Target.Default -> target.name.name?.let { currentScope.declaredNames += it }
is JsImport.Target.Elements -> target.elements.forEach { element ->
if (element.alias != null) {
element.alias?.name?.let { currentScope.declaredNames += it }
} else {
currentScope.declaredNames += element.name
}
is JsImport.Target.Elements -> target.elements.forEach {
currentScope.declaredNames += it.alias?.name ?: it.name
}
}
super.visitImport(import)
@@ -86,7 +86,7 @@ private class JsIrAstDeserializer(private val source: ByteArray) {
)
}
readRepeated { imports[stringTable[readInt()]] = readExpression() }
readRepeated { imports[stringTable[readInt()]] = readStatement() }
readRepeated { declarations.statements += readStatement() }
readRepeated { initializers.statements += readStatement() }
@@ -118,9 +118,9 @@ private class JsIrAstSerializer {
}
}
writeCollection(fragment.imports.entries) { (signatureId, expression) ->
writeCollection(fragment.imports.entries) { (signatureId, statement) ->
writeInt(internalizeString(signatureId))
writeExpression(expression)
writeStatement(statement)
}
writeCompositeBlock(fragment.declarations)
@@ -27,7 +27,7 @@ class JsImport(
class Element(
val name: JsName,
val alias: JsNameRef?
val alias: JsNameRef? = null
)
override fun accept(visitor: JsVisitor) {