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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
build
*~
*.swp
out
out
/runs/
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package me.commandblock2.tsGenerator

import me.ntrrgc.tsGenerator.TypeScriptGenerator
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.writeText

// extensions for TypeScriptGenerator
fun TypeScriptGenerator.generateNPMPackage(packageName: String): NPMPackageGenerator {
return NPMPackageGenerator(this, packageName)
}

// The generator class

class NPMPackageGenerator(val typeScriptGenerator: TypeScriptGenerator, val packageName: String) {

val typesFolder = "types"

val packageJson = """
{
"name": "@$packageName/types",
"version": "1.0.0",
"private": true,
"files": [
"$typesFolder/**/*.d.ts"
],
"typesVersions": {
"*": {
"*": [
"./$typesFolder/*"
]
}
}
}
""".trimIndent()

val tsConfig = """
{
"compilerOptions": {
"target": "es2018",
"module": "commonjs",
"declaration": true,
"declarationMap": true,
"baseUrl": ".",
"paths": {
"*": ["$typesFolder/*"]
},
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": false,
"forceConsistentCasingInFileNames": true
},
"include": [
"$typesFolder/**/*.d.ts"
]
}
""".trimIndent()

fun writePackageTo(path: Path) {
val packageFolder = path.resolve(packageName)
val typesPath = packageFolder.resolve(typesFolder)

Files.createDirectories(packageFolder)
Files.createDirectories(typesPath)

// package.json
packageFolder.resolve("package.json").writeText(packageJson)

// tsconfig.json
packageFolder.resolve("tsconfig.json").writeText(tsConfig)

typeScriptGenerator.definitionsAsModules.forEach { (path, content) ->
val definition = typesPath.resolve(path)
Files.createDirectories(definition.parent)
definition.writeText(content)
}
}
}
60 changes: 40 additions & 20 deletions src/main/kotlin/me/ntrrgc/tsGenerator/TypeScriptGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -92,25 +92,31 @@ class TypeScriptGenerator(
) {
val path: String

val dependentTypes = mutableSetOf<KClass<*>>(Any::class)
val dependentTypes = mutableSetOf<KClass<*>>()

var definition: String

val moduleText: String by lazy {
val depth = path.count { it == '/' }

dependentTypes.joinToString("\n", postfix = "\n") {
val path = modules[modules.keys.find { key -> isSameClass(key, it) }]!!.path
"import { ${it.simpleName} } from './$path'"
val importPath = modules[modules.keys.find { key -> isSameClass(key, it) }]!!.path

val upLevels = "../".repeat(depth)
val downPath = importPath.removePrefix("/")

"import type { ${it.simpleName} } from '$upLevels$downPath'"
} + "export " + definition
}


init {
path = getFilePathForClass(klass)
path = getFilePathForClassWithoutExtension(klass)

definition = generateDefinition()
}

private fun getFilePathForClass(klass: KClass<*>): String {
private fun getFilePathForClassWithoutExtension(klass: KClass<*>): String {
val packagePath = klass.java.`package`?.name?.replace('.', '/') ?: ""
val className = klass.simpleName
return if (packagePath.isEmpty()) {
Expand All @@ -136,7 +142,7 @@ class TypeScriptGenerator(
if (existingMapping != null) {
return TypeScriptType.single(predefinedMappings[classifier]!!, kType.isMarkedNullable, voidType)
}
if (!shouldIgnoreSuperclass(classifier))
if (!shouldIgnoreSuperclass(classifier) && !isSameClass(classifier, klass))
dependentTypes.add(classifier)
}

Expand All @@ -162,15 +168,38 @@ class TypeScriptGenerator(
return TypeScriptType.single(classifierTsType, kType.isMarkedNullable, voidType)
}

private fun nonPrimitiveFromKType(kType: KType): String =
// Use class name, with or without template parameters
(kType.classifier as KClass<*>).simpleName!! + if (kType.arguments.isNotEmpty()) {
private fun nonPrimitiveFromKType(kType: KType): String {
val kClass = kType.classifier as KClass<*>
val simpleName = kClass.simpleName!!

// If the counts don't match, this might indicate a specialized type
// This is actually infuriating and fucking frustrating that Kotlin does not fucking
// provided a well-defined way of getting the actual type of specialized class

// Example: your kType evaluates to
// kotlin.reflect.KFunction1<me.ntrrgc.tsGenerator.tests.ClassWithMethodsThatReturnsOrTakesFunctionalType, () -> () -> kotlin.Int>
// in debugger and kType.classifier evaluates to class kotlin.reflect.KFunction (not the KFunction1)
// but you can see there is definitely no way of acquiring the actual type with proper API
// would you rather rely on .toString() and parse it and rely on the alternative shitty hack?
// place the breakpoint and see for yourself
if (kType.arguments.size != kClass.typeParameters.size) {
return simpleName + if (kClass.typeParameters.isNotEmpty()) "<${
(1..kClass.typeParameters.size).joinToString(
", "
) { "Any" }
}>" else ""
}

// Only add generic parameters if counts match
return simpleName + if (kType.arguments.isNotEmpty()) {
"<" + kType.arguments.joinToString(", ") { arg ->
formatKType(
arg.type ?: KotlinAnyOrNull
).formatWithoutParenthesis()
} + ">"
} else ""
}


private fun getIterableElementType(kType: KType): KType? {
// Traverse supertypes to find `Iterable<T>`
Expand Down Expand Up @@ -227,12 +256,6 @@ class TypeScriptGenerator(
private fun generateInterface(klass: KClass<*>): String {
val supertypes = klass.supertypes
.filterNot { it.classifier in ignoredSuperclasses }
.filter {
if (it.classifier is KClass<*>) !isSameClass(
it.classifier as KClass<*>,
Any::class
) else true
}

val extendsString = if (supertypes.isNotEmpty()) {
" extends " + supertypes.joinToString(", ") { formatKType(it).formatWithoutParenthesis() }
Expand All @@ -241,7 +264,6 @@ class TypeScriptGenerator(
val templateParameters = if (klass.typeParameters.isNotEmpty()) {
"<" + klass.typeParameters.joinToString(", ") { typeParameter ->
val bounds = typeParameter.upperBounds
.filter { it.classifier != Any::class }
typeParameter.name + if (bounds.isNotEmpty()) {
" extends " + bounds.joinToString(" & ") { bound ->
formatKType(bound).formatWithoutParenthesis()
Expand Down Expand Up @@ -283,9 +305,8 @@ class TypeScriptGenerator(
"${param.name}: ${formatKType(paramType).formatWithoutParenthesis()}"

}
val abstractSpecifier = if (function.isAbstract) "abstract " else ""
val formattedReturnType = formatKType(returnType).formatWithoutParenthesis()
" $abstractSpecifier$functionName($parameters): $formattedReturnType;\n"
" $functionName($parameters): $formattedReturnType;\n"
}
} catch (exception: kotlin.reflect.jvm.internal.KotlinReflectionInternalError) {
print(exception.toString())
Expand Down Expand Up @@ -353,8 +374,7 @@ class TypeScriptGenerator(
Float::class to "number",
Double::class to "number",

Any::class to "any"
).plus(mappings) // mappings has a higher priority
).plus(mappings) // mappings has a higher priority

private val shouldIgnoreSuperclass: (KClass<*>) -> Boolean = { klass: KClass<*> ->
klass.isSubclassOf(Iterable::class) || klass.javaObjectType.isArray || klass.isSubclassOf(Map::class)
Expand Down
Loading