Decembrist Kotlin2Js Reflection
Plugin for kotlin js annotation processing
The plugin allows you to not be disappointed when you see the message "Unsupported [This reflection API is not supported yet in JavaScript]"
Plugin for the moment can process higher-ordered functions, classes and methods annotations.
License
Apache 2.0
Installation
For MAVEN: pom.xml
...
<dependencies>
    <dependency>
        <groupId>org.decembrist</groupId>
        <artifactId>kotlin2js-reflection-api</artifactId>
        <version>0.1.0-beta-1</version>
    </dependency>
    <dependency>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-stdlib-js</artifactId>
        <version>${kotlin.version}</version>
    </dependency>
</dependencies>
<build>
    <sourceDirectory>src/main/kotlin</sourceDirectory>
    <plugins>
        <plugin>
            <artifactId>kotlin-maven-plugin</artifactId>
            <groupId>org.jetbrains.kotlin</groupId>
            <version>${kotlin.version}</version>
            <executions>
                <execution>
                    <id>compile</id>
                    <phase>compile</phase>
                    <goals>
                        <goal>js</goal>
                    </goals>
                    <configuration>
                        <sourceDirs>
                            <sourceDir>src/main/kotlin</sourceDir>
                            <sourceDir>${build.directory}/generated-sources/decembrist</sourceDir>
                        </sourceDirs>
                        <outputFile>${project.build.outputDirectory}/${project.artifactId}.js</outputFile>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.decembrist</groupId>
            <artifactId>kotlin2js-reflection-mplugin</artifactId>
            <version>0.1.0-beta-1</version>
            <executions>
                <execution>
                    <goals>
                        <goal>process</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <artifactId>maven-dependency-plugin</artifactId>
            <executions>
                <!-- unpack kotlin.js to target/classes/lib for example index.html -->
                <execution>
                    <id>extract-kotlinjs</id>
                    <phase>prepare-package</phase>
                    <goals>
                        <goal>unpack-dependencies</goal>
                    </goals>
                    <configuration>
                        <includeArtifactIds>kotlin-stdlib-js</includeArtifactIds>
                        <outputDirectory>${build.outputDirectory}/lib</outputDirectory>
                        <includes>**\/*.js</includes>
                        <excludes>**\/*.meta.js</excludes>
                    </configuration>
                </execution>
                <!-- unpack kotlin2js-reflection-api.js to target/classes/lib for example index.html -->
                <execution>
                    <id>extract-reflection-api</id>
                    <phase>prepare-package</phase>
                    <goals>
                        <goal>unpack-dependencies</goal>
                    </goals>
                    <configuration>
                        <includeArtifactIds>kotlin2js-reflection-api</includeArtifactIds>
                        <outputDirectory>${build.outputDirectory}/lib</outputDirectory>
                        <includes>**\/*.js</includes>
                        <excludes>**\/*.meta.js
                        </excludes>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build> 
<pom.xml directory>/index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'>
    <title>Test page</title>
    <!-- include kotlin runtime (run mvn package to get it there) -->
    <script type="text/javascript" src="target/classes/lib/kotlin.js"></script>
    <!-- include kotlin reflection api (run mvn package to get it there) order must be observed (after kotlin.js)  -->
    <script type="text/javascript" src="target/classes/lib/kotlin2js-reflection-api.js"></script>
    <!-- your compiled script (run mvn compile to get it up to date) -->
    <script type="text/javascript" src="target/classes/<your-file>.js"></script>
</head>
<body>
</body>
</html> 
use package maven task
For GRADLE: build.gradle
plugins {
    id 'kotlin2js' version '1.3.0'
    id 'org.decembrist.kotlin2js.reflection' version '0.1.0-beta-1'
}
group '<your-group>'
version '<your-version>'
repositories {
    mavenCentral()
}
dependencies {
    compile "org.decembrist:kotlin2js-reflection-api:0.1.0-beta-1"
    compile "org.jetbrains.kotlin:kotlin-stdlib-js"
}
kotlin2JsReflection {
    generatedSourcesDir = file("${project.buildDir}/generated/decembrist")
}
sourceSets.main.kotlin.srcDirs += kotlin2JsReflection.generatedSourcesDir
//unpack kotlin.js and reflection-api dependencies
task assembleWeb(type: Sync) {
    configurations.compile.each { File file ->
        from(zipTree(file.absolutePath), {
            includeEmptyDirs = false
            include { fileTreeElement ->
                def path = fileTreeElement.path
                path.endsWith(".js") && (path.startsWith("META-INF/resources/") ||
                        !path.startsWith("META-INF/"))
            }
        })
    }
    from compileKotlin2Js.destinationDir
    into "${projectDir}/web"
    dependsOn classes
}
assemble.dependsOn assembleWeb 
settings.gralde
pluginManagement {
    resolutionStrategy {
        eachPlugin {
            if (requested.id.id == "kotlin2js") {
                useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}")
            }
        }
    }
}
rootProject.name = <your project name> 
<build.gradle directory>/index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'>
    <title>Test page</title>
    <!-- include kotlin runtime -->
    <script type="text/javascript" src="web/kotlin.js"></script>
    <!-- include kotlin reflection api, order must be observed (after kotlin.js)  -->
    <script type="text/javascript" src="web/kotlin2js-reflection-api.js"></script>
    <!-- your compiled script -->
    <script type="text/javascript" src="web/<your-file>.js"></script>
</head>
<body>
</body>
</html> 
use build gradle task
Code example
import org.decembrist.utils.jsReflect
import kotlin.browser.document
import kotlin.browser.window
annotation class MyFirstAnnotation(val text: String)
@MyFirstAnnotation("test")
fun main() {
    val annotations = ::main.jsReflect.getAnnotations()
    val myFirstAnnotation = annotations.first { it is MyFirstAnnotation } as MyFirstAnnotation
    window.onload = {
        document.body!!.innerHTML = myFirstAnnotation.text
        ""
    }
} 
Let's open index.html in browser
test
 
Reflection-api
- KClass.jsReflect - extention property, returns JsClassReflect type
 
val <T : Any> KClass<T>.jsReflect  
- KFunction.jsReflect - extention property, returns JsFunctionReflect for hider-ordered function or JsMethodReflect for method
 
val KFunction<*>.jsReflect 
- JsClassReflect interface
 
    /**
     * Reflection data annotations
     */
    val annotations: List<Annotation>
    /**
     * @return class name from compiled js file
     */
    val jsName: String
    /**
     * @return class js constructor function
     */
    val jsConstructor: dynamic
    /**
     * @return class methods reflection data list
     */
    val methods: List<JsMethodReflect<T>>
    /**
     * Create a new class instance through js constructor
     */
    fun createInstance(arguments: Array<Any> = js("[]")): T 
- JsFunctionReflect interface
 
    /**
     * Reflection data annotations
     */
    val annotations: List<Annotation>
    /**
    * Return function js name
    */
    val jsName: String
    /**
     * Get function annotations
     * @param kClass should be provided if this is method
     */
    fun getAnnotations(kClass: KClass<*>? = null): List<Annotation> 
- JsMethodReflect interface
 
    /**
     * Reflection data annotations
     */
    val annotations: List<Annotation>
    /**
     * Return method name
     */
    val name: String
    /**
     * Invoke function with arguments
     *
     * @param receiver object
     * @param args arguments
     * @return function result
     */
    operator fun invoke(receiver: T, vararg args: Any): Any 
Reflection object
    /**
     * Get annotations for class
     * @param kClass
     * @return annotations list
     */
    fun getAnnotations(kClass: KClass<*>): List<Annotation>
    /**
     * Get annotations for function or class method (if present)
     * @param kClass class
     * @param kFunction function or method
     * @return annotations list
     */
    fun getAnnotations(kFunction: KFunction<*>, kClass: KClass<*>? = null): List<Annotation>
    /**
     * Get methods for class
     * @param kClass
     * @return methods list
     */
    fun getMethods(kClass: KClass<*>): List<MethodInfo> 
Example
You can use our test as example
Plugin configuration
TODO(sorry)
Restrictions
The plugin works with raw code and therefore has some limitations at the moment.
- Annotations can be processed for public members only
 - KotlinJs Annotations won't be processed (@JsName, @JsModule e.t.c)
 - Only primitive type annotation params allowed (and arrays of them)
 - Cross-project annotations will be added in upcoming patches
 
We plan to fix these limitations in the future.
Tested annotation examples
annotation class Annotation(val name: String)
annotation class ZeroParamsAnnotation
annotation class ZeroParamsBracesAnnotation()
@ZeroParamsAnnotation
@Annotation("test")
@ZeroParamsBracesAnnotation
fun test() {
}
_____________________________
annotation class Annotation(val string: String,
                             val byte: Byte,
                             val short: Short,
                             val int: Int,
                             val long: Long,
                             val float: Float,
                             val double: Double,
                             val char: Char,
                             val bool: Boolean)
@Annotation("test1", 1, 1, 1, 1L, 1.0f, 1.0, 't', true)
fun main(args: Array<String>) {
}
___________________________
annotation class Annotation(val string: Array<String>,
                                 val byte: ByteArray,
                                 val short: ShortArray,
                                 val int: IntArray,
                                 val long: LongArray,
                                 val float: FloatArray,
                                 val double: DoubleArray,
                                 val char: CharArray,
                                 val bool: BooleanArray)
@Annotation(["test1", "test2"], [1, 2], [1, 2], [1, 2], [1L, 2L], [1.0f, 2.0f], [1.0, 2.0], ['1', '2'], [true, false])
fun main(args: Array<String>) {
}
___________________________
annotation class Annotation1(vararg val string: String)
annotation class Annotation2(vararg val byte: Byte)
annotation class Annotation3(vararg val short: Short)
annotation class Annotation4(vararg val int: Int)
annotation class Annotation5(vararg val long: Long)
annotation class Annotation6(vararg val float: Float)
annotation class Annotation7(vararg val double: Double)
annotation class Annotation8(vararg val char: Char)
annotation class Annotation9(vararg val bool: Boolean)
@Annotation1(*arrayOf("test1", "test2"))
@Annotation2(1, 2)
@Annotation3(1, 2)
@Annotation4(*[1, 2])
@Annotation5(*[1L, 2L])
@Annotation6(*[1.0f, 2.0f])
@Annotation7(*[1.0, 2.0])
@Annotation8(*['1', '2'])
@Annotation9(*[true, false])
fun main(args: Array<String>) {
}
__________________________
annotation class StringVarargAnnotation(vararg val string: String)
annotation class ByteVarargAnnotation(vararg val byte: Byte)
annotation class ShortVarargAnnotation(vararg val short: Short)
annotation class IntVarargAnnotation(vararg val int: Int)
annotation class LongVarargAnnotation(vararg val long: Long)
annotation class FloatVarargAnnotation(vararg val float: Float)
annotation class DoubleVarargAnnotation(vararg val double: Double)
annotation class CharVarargAnnotation(vararg val char: Char)
annotation class BooleanVarargAnnotation(vararg val bool: Boolean)
@StringVarargAnnotation("test1", "test2")
@ByteVarargAnnotation(1, 2)
@ShortVarargAnnotation(1, 2)
@IntVarargAnnotation(1, 2)
@LongVarargAnnotation(1L, 2L)
@FloatVarargAnnotation(1.0f, 2.0f)
@DoubleVarargAnnotation(1.0, 2.0)
@CharVarargAnnotation('1', '2')
@BooleanVarargAnnotation(true, false)
fun main(args: Array<String>) {
}
___________________________
annotation class SpreadVarargAnnotation(vararg val string: String)
annotation class SquaredAnnotation(val string: Array<String>)
annotation class BracedAnnotation(val string: Array<String>)
annotation class SquaredVarargsAnnotation(vararg val string: String)
@SpreadVarargAnnotation(*arrayOf("test1", "test2"))
@SquaredAnnotation(["test1", "test2"])
@BracedAnnotation((["test1", "test2"]))
@SquaredVarargsAnnotation(*["test1", "test2"])
fun main(args: Array<String>) {
}
___________________________
annotation class StringTemplate(val string: String)
annotation class MultiStringTemplate(val string: String)
@StringTemplate("""template""")
@MultiStringTemplate("""
    multistring-template
""")
fun main(args: Array<String>) {
} 
Community
Your issue tickets or any help will be very appreciated.