Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor GenerateBuilders.transform.kts and provide an overload of builder functions #36

Merged
merged 5 commits into from
Jun 24, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ ktcodeshift -t ktcodeshift-cli/src/test/resources/examples/GenerateBuilders.tran

*/

import ktast.ast.Node
import ktast.ast.NodePath
import ktast.ast.NodeSupplement
import ktast.ast.Visitor
import ktast.ast.Writer
import ktast.ast.*
import ktcodeshift.*
import org.jetbrains.kotlin.util.capitalizeDecapitalize.decapitalizeSmart
import java.nio.charset.StandardCharsets
Expand Down Expand Up @@ -38,120 +34,7 @@ transform { fileInfo ->
stringBuilder.appendLine("import ktast.ast.Node")
stringBuilder.appendLine("import ktast.ast.NodeSupplement")

fun toFqNameType(type: Node.Type.SimpleType, nestedNames: List<String>): Node.Type.SimpleType {

// e.g. Make List<Expression> to List<Node.Expression>
if (type.name.text == "List") {
return type.copy(
typeArguments = type.typeArguments.map { typeArgument ->
typeArgument.copy(
type = toFqNameType(
typeArgument.type as Node.Type.SimpleType,
nestedNames
),
)
}
)
}

generateSequence(nestedNames) { if (it.isNotEmpty()) it.dropLast(1) else null }.forEach { prefixNames ->
val fqName = prefixNames + type.qualifiers.map { it.name.text } + type.name.text
if (fqNames.contains(fqName)) {
return simpleType(
qualifiers = fqName.dropLast(1).map { simpleTypeQualifier(nameExpression(it)) },
name = nameExpression(fqName.last()),
)
}
}

return type
}

object : Visitor() {
override fun visit(path: NodePath<*>) {
val v = path.node
if (v is Node.Declaration.ClassDeclaration) {
val nestedNames = nestedClassNames(path)

if (v.isDataClass && nestedNames[1] != "Keyword") {
val name = v.name.text
val parameters = v.primaryConstructor?.parameters.orEmpty()
val functionName = functionNameOf(name)

val func = functionDeclaration(
supplement = NodeSupplement(
extrasBefore = listOf(
comment(
"""
/**
* Creates a new [${nestedNames.joinToString(".")}] instance.
*/
""".trimIndent()
)
)
),
name = nameExpression(functionName),
parameters = parameters.map { p ->
val fqType = when (val type = p.type) {
is Node.Type.SimpleType -> toFqNameType(type, nestedNames)
is Node.Type.NullableType -> type.copy(
innerType = toFqNameType(
type.innerType as Node.Type.SimpleType,
nestedNames
)
)
else -> type
}
functionParameter(
name = p.name,
type = fqType,
defaultValue = defaultValueOf(fqType),
)
},
body = callExpression(
calleeExpression = nameExpression(nestedNames.joinToString(".")),
arguments = parameters.map { p ->
valueArgument(
name = p.name,
expression = expressionOf(name, p.name),
)
},
)
)
stringBuilder.appendLine(Writer.write(func))
val firstParameter = func.parameters.firstOrNull()
if (firstParameter?.name?.text == "statements") {
val firstParamType = firstParameter.type as? Node.Type.SimpleType
if (firstParamType != null) {
if (firstParamType.name.text == "List") {
val listElementType = firstParamType.typeArguments[0].type
val varargFunc = functionDeclaration(
name = nameExpression(functionName),
parameters = listOf(
functionParameter(
modifiers = listOf(Node.Keyword.Vararg()),
name = firstParameter.name.copy(),
type = listElementType,
)
),
body = callExpression(
calleeExpression = nameExpression(functionName),
arguments = listOf(
valueArgument(
expression = nameExpression("${firstParameter.name.text}.toList()"),
)
),
)
)
stringBuilder.appendLine(Writer.write(varargFunc))
}
}
}
}
}
super.visit(path)
}
}.traverse(fileNode)
GeneratorVisitor(stringBuilder, fqNames).traverse(fileNode)
}
java.io.File("ktcodeshift-dsl/src/main/kotlin/ktcodeshift/Builder.kt")
.writeText(stringBuilder.toString(), StandardCharsets.UTF_8)
Expand All @@ -169,13 +52,17 @@ fun functionNameOf(className: String): String {
return className.decapitalizeSmart()
}

fun defaultValueOf(type: Node.Type?): Node.Expression? {
fun defaultValueOf(parameterName: String, type: Node.Type?): Node.Expression? {
return if (type is Node.Type.NullableType) {
nameExpression("null")
} else if (type is Node.Type.SimpleType) {
val fqName = (type.qualifiers.map { it.name } + type.name).joinToString(".") { it.text }
if (fqName == "List") {
nameExpression("listOf()")
if (parameterName == "variables") {
null
} else {
nameExpression("listOf()")
}
} else if (fqName == "Boolean") {
nameExpression("false")
} else if (fqName == "NodeSupplement") {
Expand All @@ -195,7 +82,7 @@ fun defaultValueOf(type: Node.Type?): Node.Expression? {
val parenthesizedParamNames = mapOf(
"EnumEntry" to "arguments",
"PropertyDeclaration" to "variables",
"LambdaParameters" to "variables",
"LambdaParameter" to "variables",
"Annotation" to "arguments",
)

Expand Down Expand Up @@ -230,9 +117,16 @@ fun expressionOf(className: String, paramName: Node.Expression.NameExpression):
if (className == "CallExpression") {
return nameExpression("if (arguments.isNotEmpty() || lambdaArgument == null) $name ?: $keywordType() else $name")
}
val parenthesizedParamName = parenthesizedParamNames[className]
if (parenthesizedParamName != null) {
return nameExpression("if ($parenthesizedParamName.isNotEmpty()) $name ?: $keywordType() else $name")
when (val parenthesizedParamName = parenthesizedParamNames[className]) {
null -> {
// do nothing
}
"variables" -> {
return nameExpression("$name ?: $keywordType()")
}
else -> {
return nameExpression("if ($parenthesizedParamName.isNotEmpty()) $name ?: $keywordType() else $name")
}
}
}
"lAngle", "rAngle" -> {
Expand All @@ -249,3 +143,173 @@ fun expressionOf(className: String, paramName: Node.Expression.NameExpression):
}
return paramName.copy()
}

class GeneratorVisitor(
private val stringBuilder: StringBuilder,
private val fqNames: Set<List<String>>,
) : Visitor() {
override fun visit(path: NodePath<*>) {
val v = path.node
if (v is Node.Declaration.ClassDeclaration) {
val nestedNames = nestedClassNames(path)

if (v.isDataClass && nestedNames[1] != "Keyword") {
val name = v.name.text
val parameters = v.primaryConstructor?.parameters.orEmpty()
val functionName = functionNameOf(name)

val func = makeBuilderFunction(nestedNames, functionName, parameters, name)
stringBuilder.appendLine(Writer.write(func))
val firstParameter = func.parameters.firstOrNull()
if (firstParameter?.name?.text == "statements") {
val firstParamType = firstParameter.type as? Node.Type.SimpleType
if (firstParamType != null) {
if (firstParamType.name.text == "List") {
val listElementType = firstParamType.typeArguments[0].type
val varargFunc = makeVarargBuilderFunction(func, firstParameter.name.text, listElementType)
stringBuilder.appendLine(Writer.write(varargFunc))
}
}
}
if (func.parameters.map { it.name.text }.containsAll(listOf("lPar", "variables", "rPar"))) {
val singleVariableFunc = makeSingleVariableBuilderFunction(func)
stringBuilder.appendLine(Writer.write(singleVariableFunc))
}
}
}
super.visit(path)
}

private fun makeBuilderFunction(
nestedNames: List<String>,
functionName: String,
parameters: List<Node.FunctionParameter>,
name: String
) = functionDeclaration(
supplement = NodeSupplement(
extrasBefore = listOf(
comment(
"""
/**
* Creates a new [${nestedNames.joinToString(".")}] instance.
*/
""".trimIndent()
)
)
),
name = nameExpression(functionName),
parameters = parameters.map { p ->
val fqType = when (val type = p.type) {
is Node.Type.SimpleType -> toFqNameType(type, nestedNames)
is Node.Type.NullableType -> type.copy(
innerType = toFqNameType(
type.innerType as Node.Type.SimpleType,
nestedNames
)
)
else -> type
}
functionParameter(
name = p.name,
type = fqType,
defaultValue = defaultValueOf(p.name.text, fqType),
)
},
body = callExpression(
calleeExpression = nameExpression(nestedNames.joinToString(".")),
arguments = parameters.map { p ->
valueArgument(
name = p.name,
expression = expressionOf(name, p.name),
)
},
)
)

private fun makeVarargBuilderFunction(
func: Node.Declaration.FunctionDeclaration,
firstParameterName: String,
listElementType: Node.Type
) = func.copy(
parameters = listOf(
functionParameter(
modifiers = listOf(Node.Keyword.Vararg()),
name = nameExpression(firstParameterName),
type = listElementType,
)
),
body = callExpression(
calleeExpression = func.name!!.copy(),
arguments = listOf(
valueArgument(
expression = nameExpression("$firstParameterName.toList()"),
)
),
)
)

private fun makeSingleVariableBuilderFunction(func: Node.Declaration.FunctionDeclaration): Node.Declaration.FunctionDeclaration {
return func.copy(
parameters = func.parameters.map { param ->
if (param.name.text == "variables") {
param.copy(
name = nameExpression("variable"),
type = (param.type as Node.Type.SimpleType).typeArguments[0].type,
defaultValue = null,
)
} else {
param
}
}.filterNot {
setOf("lPar", "rPar", "destructuringType").contains(it.name.text)
},
body = (func.body as Node.Expression.CallExpression).copy(
arguments = (func.body as Node.Expression.CallExpression).arguments.map {
when (it.name?.text) {
"variables" -> {
it.copy(
expression = nameExpression("listOf(variable)"),
)
}
"lPar", "rPar", "destructuringType" -> {
it.copy(
expression = nullLiteralExpression(),
)
}
else -> {
it
}
}
}
)
)
}

private fun toFqNameType(type: Node.Type.SimpleType, nestedNames: List<String>): Node.Type.SimpleType {
// e.g. Make List<Expression> to List<Node.Expression>
if (type.name.text == "List") {
return type.copy(
typeArguments = type.typeArguments.map { typeArgument ->
typeArgument.copy(
type = toFqNameType(
typeArgument.type as Node.Type.SimpleType,
nestedNames
),
)
}
)
}

generateSequence(nestedNames) { if (it.isNotEmpty()) it.dropLast(1) else null }.forEach { prefixNames ->
val fqName = prefixNames + type.qualifiers.map { it.name.text } + type.name.text
if (fqNames.contains(fqName)) {
return simpleType(
qualifiers = fqName.dropLast(1).map { simpleTypeQualifier(nameExpression(it)) },
name = nameExpression(fqName.last()),
)
}
}

return type
}
}
Loading