Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ plugins {
}

group = "no.item.xp.plugin"
version = "2.7.0"
version = "2.8.0-SNAPSHOT"

kotlin {
jvmToolchain(11)
Expand Down
5 changes: 3 additions & 2 deletions src/main/kotlin/no/item/xp/plugin/GenerateCodeTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ open class GenerateCodeTask
it.getXmlFile().set(change.file)
it.getTargetFile().set(targetFile)
it.getMixins().value(mixins)
it.getMixinsImportPath().value(resolveMixinsImportPath(targetFile, rootOutputDir))
it.getSingleQuote().value(singleQuote)
it.getPrependText().value(prependText)
}
Expand Down Expand Up @@ -129,12 +130,12 @@ open class GenerateCodeTask
logger.error(left.message)
},
{ right ->
val fileContent = renderTypeModelAsTypeScript(right)

val targetFilePath = Paths.get(rootOutputDir.absolutePath, fileInJar.entry.name)
val indexFilePath = Paths.get(targetFilePath.parent.toString(), "index.d.ts")
val targetFile = File(indexFilePath.toUri())

val fileContent = renderTypeModelAsTypeScript(right, resolveMixinsImportPath(targetFile, rootOutputDir))

writeTargetFile(targetFile, fileContent, prependText, singleQuote)
logger.lifecycle("Updated file: ${Path.of(targetFile.toURI()).toUri()}")
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ interface CodegenWorkParameters : WorkParameters {

fun getMixins(): ListProperty<ObjectTypeModel>

fun getMixinsImportPath(): Property<String>

fun getPrependText(): Property<String>

fun getSingleQuote(): Property<Boolean>
Expand All @@ -39,6 +41,7 @@ abstract class GenerateTypeScriptWorkAction : WorkAction<CodegenWorkParameters>
val file = parameters.getXmlFile().get().asFile
val targetFile = parameters.getTargetFile().get().asFile
val mixins = parameters.getMixins().get()
val mixinsImportPath = parameters.getMixinsImportPath().get()

parseXml(file.inputStream())
.flatMap { doc -> doc.getFormNode() }
Expand All @@ -58,9 +61,9 @@ abstract class GenerateTypeScriptWorkAction : WorkAction<CodegenWorkParameters>
{
val fileContent =
if (file.absolutePath.endsWith(concatFileName("resources", "site", "site.xml"))) {
renderSiteConfig(it)
renderSiteConfig(it, mixinsImportPath)
} else {
renderTypeModelAsTypeScript(it)
renderTypeModelAsTypeScript(it, mixinsImportPath)
}

writeTargetFile(targetFile, fileContent, parameters.getPrependText().get(), parameters.getSingleQuote().get())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,14 @@ data class ObjectField(
override val isNullable: Boolean,
override val isArray: Boolean,
val fields: List<ObjectTypeModelField>,
// Name of the mixin, if it is the only form field. The type will then be imported instead of inlined.
val mixinName: String? = null,
) : ObjectTypeModelField() {
constructor(
field: ObjectTypeModelField,
fields: List<ObjectTypeModelField>,
) : this(field.name, field.comment, field.isNullable, field.isArray, fields)
mixinName: String? = null,
) : this(field.name, field.comment, field.isNullable, field.isArray, fields, mixinName)
}

data class UnknownField(
Expand Down
17 changes: 17 additions & 0 deletions src/main/kotlin/no/item/xp/plugin/parser/ParseInterfaceModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,23 @@ private fun findMixinFields(
return mixins.find { it.nameWithoutExtension == mixinName }?.fields ?: emptyList()
}

/**
* Returns the name of the mixin if it is the only form field in [itemsNode], and the mixin exists
*/
fun findSingleMixinName(
itemsNode: Node,
mixins: List<ObjectTypeModel>,
): String? {
val mixinName =
itemsNode
.getChildNodesAtXPath(xpathInputType)
.singleOrNull()
?.takeIf { it.nodeName == "mixin" }
?.getNodeAttribute("name")

return mixinName?.takeIf { name -> mixins.any { it.nameWithoutExtension == name } }
}

fun parseConfigOptionValue(inputNode: Node): List<String> {
return inputNode
.getChildNodesAtXPath(getXpathExpressionFromString("config/option/@value"))
Expand Down
7 changes: 5 additions & 2 deletions src/main/kotlin/no/item/xp/plugin/parser/ParseItemSet.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ fun parseItemSet(
mixins: List<ObjectTypeModel>,
): ObjectField? {
val unknownField = parseUnknownField(itemSetNode)
val itemsNode = itemSetNode.getChildNodeAtXPath("items")

val subFields =
itemSetNode.getChildNodeAtXPath("items")
itemsNode
?.let { parseFields(it, mixins).getOrNull() } ?: emptyList()

return unknownField?.let { ObjectField(it, subFields) }
val mixinName = itemsNode?.let { findSingleMixinName(it, mixins) }

return unknownField?.let { ObjectField(it, subFields, mixinName) }
}
4 changes: 3 additions & 1 deletion src/main/kotlin/no/item/xp/plugin/parser/ParseOptionSet.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,7 @@ private fun parseOptionSetFieldOption(
itemsNode
?.let { parseFields(it, mixins).getOrNull() } ?: emptyList()

return ObjectField(optionNode.getNodeAttribute("name")!!, optionComment, true, false, fields)
val mixinName = itemsNode?.let { findSingleMixinName(it, mixins) }

return ObjectField(optionNode.getNodeAttribute("name")!!, optionComment, true, false, fields, mixinName)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package no.item.xp.plugin.renderers
import no.item.xp.plugin.models.ObjectTypeModel
import no.item.xp.plugin.renderers.ts.renderInterfaceModelField

fun renderSiteConfig(model: ObjectTypeModel): String {
val fieldList = model.fields.joinToString("\n\n") { renderInterfaceModelField(it, 3) }
fun renderSiteConfig(
model: ObjectTypeModel,
mixinsImportPath: String,
): String {
val fieldList = model.fields.joinToString("\n\n") { renderInterfaceModelField(it, 3, mixinsImportPath) }

return """
#export type SiteConfig = XP.SiteConfig;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import no.item.xp.plugin.models.UnknownField
fun renderInterfaceModelField(
field: ObjectTypeModelField,
indent: Int,
mixinsImportPath: String,
): String {
return when (field) {
is StringField,
Expand All @@ -27,14 +28,19 @@ fun renderInterfaceModelField(
is UnionOfStringLiteralField -> simpleFieldAsString(field, joinOptionList(field.optionList), indent)
is OptionSetField ->
if (field.isMultiSelect) {
renderOptionSetFieldMultiChoice(field, indent)
renderOptionSetFieldMultiChoice(field, indent, mixinsImportPath)
} else {
renderOptionSetField(field, indent)
renderOptionSetField(field, indent, mixinsImportPath)
}
is ObjectField -> renderObjectField(field, indent)
is ObjectField -> renderObjectField(field, indent, mixinsImportPath)
}
}

fun renderMixinTypeReference(
mixinName: String,
mixinsImportPath: String,
): String = "import(\"$mixinsImportPath/$mixinName\").${getTypeName(mixinName)}"

private fun simpleFieldAsString(
field: ObjectTypeModelField,
innerType: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,21 @@ import no.item.xp.plugin.models.ObjectField
fun renderObjectField(
field: ObjectField,
indentLevel: Int,
mixinsImportPath: String,
): String {
val indentation = createIndentation(indentLevel)

val fieldList = field.fields.joinToString("\n\n") { renderInterfaceModelField(it, indentLevel + 1) }
if (field.mixinName != null) {
val typeReference = renderMixinTypeReference(field.mixinName, mixinsImportPath)
val fieldType = if (field.isArray) "Array<$typeReference>" else typeReference

return """
#${renderComment(field.comment, indentLevel)}
#$indentation${escapeName(field.name)}${if (field.isNullable) "?" else ""}: $fieldType;
""".trimMargin("#")
}

val fieldList = field.fields.joinToString("\n\n") { renderInterfaceModelField(it, indentLevel + 1, mixinsImportPath) }

return """
#${renderComment(field.comment, indentLevel)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import no.item.xp.plugin.models.OptionSetField
fun renderOptionSetField(
field: OptionSetField,
indentLevel: Int,
mixinsImportPath: String,
): String {
val indentation = createIndentation(indentLevel)
val options = field.optionList.joinToString("\n") { renderOptionSetFieldOption(it, indentLevel + 2) }
val options = field.optionList.joinToString("\n") { renderOptionSetFieldOption(it, indentLevel + 2, mixinsImportPath) }

return """
#${renderComment(field.comment, indentLevel)}
Expand All @@ -20,11 +21,19 @@ fun renderOptionSetField(
private fun renderOptionSetFieldOption(
fieldOption: ObjectField,
indentLevel: Int,
mixinsImportPath: String,
): String {
val indentation0 = createIndentation(indentLevel - 1)
val indentation1 = createIndentation(indentLevel)
val indentation2 = createIndentation(indentLevel + 1)
val content = fieldOption.fields.joinToString("\n\n") { renderInterfaceModelField(it, indentLevel + 2) }
val content = fieldOption.fields.joinToString("\n\n") { renderInterfaceModelField(it, indentLevel + 2, mixinsImportPath) }

val optionType =
when {
fieldOption.mixinName != null -> renderMixinTypeReference(fieldOption.mixinName, mixinsImportPath) + ";"
fieldOption.fields.isNotEmpty() -> "{\n$content\n#$indentation2};"
else -> "Record<string, unknown>;"
}

return """
#$indentation0| {
Expand All @@ -34,9 +43,7 @@ private fun renderOptionSetFieldOption(
#${indentation2}_selected: "${fieldOption.name}";
#
#${renderComment(fieldOption.comment, indentLevel + 1)}
#$indentation2${escapeName(
fieldOption.name,
)}: ${if (fieldOption.fields.isNotEmpty()) "{\n$content" else "Record<string, unknown>;"}${if (fieldOption.fields.isNotEmpty()) "\n#$indentation2};" else ""}
#$indentation2${escapeName(fieldOption.name)}: $optionType
#$indentation1}
""".trimMargin("#")
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import no.item.xp.plugin.models.OptionSetField
fun renderOptionSetFieldMultiChoice(
field: OptionSetField,
indentLevel: Int,
mixinsImportPath: String,
): String {
val indentation0 = createIndentation(indentLevel)
val indentation1 = createIndentation(indentLevel + 1)

val optionNames = field.optionList.joinToString(" | ") { "\"${it.name}\"" }

val options = field.optionList.joinToString("\n$indentation1\n") { renderMultiSelectOptionSetFieldOption(it, indentLevel) }
val options =
field.optionList.joinToString("\n$indentation1\n") {
renderMultiSelectOptionSetFieldOption(it, indentLevel, mixinsImportPath)
}

return """
#${renderComment(field.comment, indentLevel)}
Expand All @@ -30,14 +34,20 @@ fun renderOptionSetFieldMultiChoice(
private fun renderMultiSelectOptionSetFieldOption(
fieldOption: ObjectField,
indentLevel: Int,
mixinsImportPath: String,
): String {
val indentation = createIndentation(indentLevel + 1)
val content = fieldOption.fields.joinToString("\n\n") { renderInterfaceModelField(it, indentLevel + 2) }
val content = fieldOption.fields.joinToString("\n\n") { renderInterfaceModelField(it, indentLevel + 2, mixinsImportPath) }

val optionType =
when {
fieldOption.mixinName != null -> renderMixinTypeReference(fieldOption.mixinName, mixinsImportPath)
fieldOption.fields.isNotEmpty() -> "{\n$content\n#$indentation}"
else -> "Record<string, unknown>"
}

return """
#${renderComment(fieldOption.comment, indentLevel + 1)}
#$indentation${escapeName(
fieldOption.name,
)}: ${if (fieldOption.fields.isNotEmpty()) "{\n$content" else "Record<string, unknown>"}${if (fieldOption.fields.isNotEmpty()) "\n#$indentation}" else ""};
#$indentation${escapeName(fieldOption.name)}: $optionType;
""".trimMargin("#")
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package no.item.xp.plugin.renderers.ts

import no.item.xp.plugin.models.ObjectTypeModel

fun renderTypeModelAsTypeScript(model: ObjectTypeModel): String {
val fieldList = model.fields.joinToString("\n\n") { renderInterfaceModelField(it, 1) }
fun renderTypeModelAsTypeScript(
model: ObjectTypeModel,
mixinsImportPath: String,
): String {
val fieldList = model.fields.joinToString("\n\n") { renderInterfaceModelField(it, 1, mixinsImportPath) }

return """
#export type ${getTypeName(model.nameWithoutExtension)} = {
Expand Down
14 changes: 14 additions & 0 deletions src/main/kotlin/no/item/xp/plugin/util/TargetFile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,20 @@ fun getTargetFile(
return File(getTargetDirectory(inputFile, rootDir), "index.d.ts")
}

/**
* Returns the relative import path from the directory of [targetFile] to the generated mixins in [rootDir]
*/
fun resolveMixinsImportPath(
targetFile: File,
rootDir: File,
): String {
val mixinsDir = File(rootDir.absoluteFile, concatFileName("site", "mixins")).toPath()
val targetDir = targetFile.absoluteFile.parentFile.toPath()
val relativePath = normalizeFilePath(targetDir.relativize(mixinsDir).toString())

return if (relativePath.startsWith(".")) relativePath else "./$relativePath"
}

fun simpleFilePath(file: File): String = file.canonicalPath.substringAfter("""resources${File.separatorChar}""")

fun normalizeFilePath(filePath: String): String = filePath.replace(File.separatorChar, '/')
Expand Down
Loading
Loading