How can I modify PSI of Kotlin? - kotlin

I'm trying to create a plugin for IntelliJ IDEA that functions similarly to lombok.
This is what I'm trying to do, with an existing interface:
interface TestInterface {
var testProperty: Int
}
I want to modify the PSI so that the IntelliJ IDEA can recognize it as:
interface TestInterface {
var testProperty: Int
companion object: TestInterface {
override var testProperty: Int
get() {
TODO("Do something")
}
set(value) {
TODO("Do something")
}
}
}
With kapt, I can achieve the functions I want, and here is my code: Github, but it dosen't work.
Could you help me to solve this problem? Thank you!

After communicating with the IDEA team, I got the following information:
Hi! Unfortunately, generating the Kotlin PSI on the fly for use by the Kotlin resolve is impossible. Java resolution in the IDE uses PSI to resolve things, and that's why it works for Java. Kotlin resolution in the IDE uses the Kotlin compiler, so creating PSI on the fly and using PsiAugmentProvider will not work.
To sum up, if you want to achieve a function similar to Lombok plugin, you cannot use the method of editing PSI. Then I found a feasible method based on the above information, that is KSP: https://kotlinlang.org/docs/ksp-overview.html

Related

How to bind i18next-browser-languagedetector to Kotlin?

The Gradle project is set by the JS plugin:
plugins {
kotlin("js") version("1.6.10")
}
and uses the LEGACY compilation backend:
kotlin {
js(LEGACY) {
// ...
}
}
My goal is to use the following dependencies in Kotlin sources:
dependencies {
implementation(npm("i18next", "21.6.11"))
implementation(npm("react-i18next", "11.15.4"))
implementation(npm("i18next-browser-languagedetector", "6.1.3"))
}
It was pretty easy to describe JS-Kotlin bridging for the first two dependencies:
#JsModule("i18next")
#JsNonModule
external val i18next: I18n
external interface I18n {
fun use(module: dynamic): I18n
}
#JsModule("react-i18next")
#JsNonModule
external val reactI18next: ReactI18next
external interface ReactI18next {
val initReactI18next: dynamic
}
Unfortunately, the last one - i18next-browser-languagedetector - is driving me some nuts with its configuration. Something like this:
#JsModule("i18next-browser-languagedetector")
#JsNonModule
external val LanguageDetector: dynamic
doesn't work - the actual LanguageDetector provided by the declaration above is {}, so i18next doesn't consume it in Kotlin code (the JS code throws You are passing a wrong module! Please check the object you are passing to i18next.use()):
i18next.use(LanguageDetector) // fails
Can anyone please help me with a declaration of a JS-Kotlin bridge for the LanguageDetector?
Well, by debugging a little bit I've managed to solve this JS-Kotlin bridging issue. The working solution is the following declaration:
#JsModule("i18next-browser-languagedetector")
#JsNonModule
external val i18nextBrowserLanguageDetector: I18nextBrowserLanguageDetector
external interface I18nextBrowserLanguageDetector {
#JsName("default")
val LanguageDetector: dynamic
}
Now it's possible to do first parts of the i18next initialization chain:
i18next
.use(i18nextBrowserLanguageDetector.LanguageDetector)
.use(reactI18next.initReactI18next)
// ...
Unfortunately, it's difficult to say that I'm getting any intuition behind it (maybe because of my huge blind spots in JS) - so any additional clarification or explanations would be helpful still.
My biggest concern is that LanguageDetector from the declaration above should be a class, but it seems like no way to use something else rather than dynamic property. When I try to lift up the #JsName("default") annotation to mark some class protocol with it, it doesn't compile:
#JsModule("i18next-browser-languagedetector")
#JsNonModule
#JsName("default")
external class LanguageDetector
It's not possible to use a nested class inside of the interface as well in this case:
#JsModule("i18next-browser-languagedetector")
#JsNonModule
external interface I18nextBrowserLanguageDetector {
#JsName("default")
class LanguageDetector
}
So while it seems to be solved, it's super-frustrating still.

Null property provided by Gradle when using custom plugin

I'm trying to follow the Gradle custom plugin documentation to create a plugin that can be configured.
My plugin code:
interface MyExtension {
var myValue: Property<String>
}
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
val extension = project.extensions.create<MyExtension>("myExt")
}
}
in build.gradle.kts:
plugins {
`java-library`
}
apply<MyPlugin>()
the<MyExtension>().myValue.set("some-value")
Running this will give
Build file '<snip>/build.gradle.kts' line: 6
java.lang.NullPointerException (no error message)
Turns out the the<MyExtension>().myValue is null, so the set call fails. How do I do this correctly? Did I miss something in the documentation, or is it just wrong?
The documentation is not wrong. Properties can be managed by either you or by Gradle. For the latter, certain conditions have to be met.
Without managed properties
If you want to be completely in charge, you can instantiate any variables you declare yourself. For example, to declare a property on an extension that is an interface, it could look like this:
override fun apply(project: Project) {
val extension = project.extensions.create("myExt", MyExtension::class.java)
extension.myValue = project.objects.property(String::class.java)
}
Or you could instantiate it directly in the extension by making it a class instead:
open class MessageExtension(objects: ObjectFactory) {
val myValue: Property<String> = objects.property(String::class.java)
}
However, a property field is not really supposed to have a setter as the property itself has both a setter and a getter. So you should generally avoid the first approach and remove the setter on the second.
See here for more examples on managing the properties yourself.
With managed properties
To help you reduce boilerplate code, Gradle can instantiate the properties for you with what is called managed properties. To do use these, the property must not have a setter, and the getter should be abstract (which it implicitly is on an interface). So you could go back to your first example and fix it by changing var to val:
interface MyExtension {
val myValue: Property<String> // val (getter only)
}
Now Gradle will instantiate the field for you. The same thing works for abstract classes.
Read more about managed properties in the documentation here.

Create a Gradle function for dependencies block in Kotlin

Currently, I'm creating a function, which is available for the dependencies block in Groovy with:
project.dependencies.ext.foo = { String value ->
project.files(extension.getFooDependency(project).jarFiles).asFileTree
}
Thanks to that, I'm able to do:
afterEvaluate {
dependencies {
compileOnly foo('junit')
}
}
I'm converting the Groovy code to Kotlin, and I'm wondering how to rewrite this foo extension.
I've ended up with:
project.dependencies.extensions.extraProperties.set("foo", Action { value: String ->
project.files(extension.getIdeaDependency(project).jarFiles).asFileTree
})
After calling foo('junit'), I get the following exception:
> Could not find method foo() for arguments [junit] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
I do not think that would work the same way in Kotlin DSL. Instead, you may declare a Kotlin extension function somewhere in the project. Then calling it would include all necessary receivers to you.
For multiple projects, I would recommend using a buildSrc project. Declarations there are visible to all project files below.
Speaking about Groovy and Kotlin support, I would do something like that:
private fun getFooImpl(scope: getFooImpl, name: String) { /*here is the implementation */ }
fun DependencyHandlerScope.getFoo(name:String) = getFooImpl(this, name)
//in Groovy
project.dependencies.extensions.extraProperties.set("foo", {getFooImpl(..)})
The same code could fit into a plugin as well. A more generic way could be to register a custom DLS extension, so to allow a custom block-like thisIsMyPlugin { .. } in the Gradle DSL and define all necessary helper functions in the extension class. Here the downside is in forcing users to wrap their code into the thisIsMyPlugin block.

How does Generic work if generic is Int in Kotlin?

I tried to make abstract class for testing because I found weird problem for using generics
abstract class Test<T> {
open fun hello(vararg data: T) {
print("Default function")
}
}
This very simple abstract class has one opened method with vararg keyword. Problem can be reproduced by making another class which extends Test class.
class Hello : Test<Int>() {
//Problem 1
override fun hello(vararg data: Int) {
super.hello(*data) //Problem 2
println("Override function")
}
}
About first problem, Kotlin says method doesn't override anything even though this method surely overrides something. Weirdly, this error happens randomly, so I can't tell exact way to reproduce this bug
This error got removed when I add some codes (like really simple code such as println(), etc), but when you compile, it causes same error again.
About second problem, super.hello(*data) causes problem because this requires Array<out Int>, but found parameter is IntArray. I think Kotlin is considering IntArray and Array<*> as different class, but it shouldn't act like this...
I'm using Kotlin 1.4.10 which seems the latest version according to this site.
I'm posting this to check if these 2 problems are bug or if I did something incorrectly because when I change generic to String, all problems get removed.
Are there any mistakes I made in these sample codes above?
Known issue: https://youtrack.jetbrains.com/issue/KT-9495
As a workaround, you can use the boxed java.lang.Integer.
class Hello : Test<Integer>() {
override fun hello(vararg data: Integer) {
super.hello(*data)
println("Override function")
}
}

How can one add static methods to Java classes in Kotlin

Is it possible to add a new static method to the java.lang.Math class in Kotlin? Usually, such things are possible in Kotlin thanks to Kotlin Extensions.
I already tried doing the following in a file I made called Extensions.kt:
fun Math.Companion.clamp(value:Double,minValue:Double,maxValue:Double):Double
{
return Math.max(Math.min(value,maxValue),minValue)
}
but Math.Companion could not be resolved...
As of Kotlin 1.3, this is not possible. However, it's being considered for a future release!
To help this feature get implemented, go vote on this issue: https://youtrack.jetbrains.com/issue/KT-11968
Because all proposals are basically in limbo right now, I wouldn't hold my breath that this will get in any time soon
I think this is not possible. Documentation says the following:
If a class has a companion object defined, you can also define extension functions and properties for the companion object.
The Math class is a Java class, not a Kotlin one and does not have a companion object in it. You can add a clamp method to the Double class instead.
As of Kotlin 1.2 it is still not possible.
As a workaround, to statically "extend" Environment class I am currently using:
Class EnvironmentExtensions {
companion object {
#JvmStatic
fun getSomething(): File {
...
return Environment.something()
}
}
}
It is not an ideal solution but IntelliJ/Android Studio code completion helps with the usage:
val something = EnvironmentExtensions.getSomething()