I have a problem with processing Kotlin source code via kotlin-compiler-embeddable. I need to obtain PSI class representations for all classes in an external project, build them, and get BindingContext, but I can't find any examples.
I would like to use the kotlin-compiler-embeddable library to analyse dependencies in a Kotlin project outside the IntelliJ environment.
I manage to obtain PSI representations for individual classes. Unfortunately this is not enough. I would like to track dependencies between classes.
private val project by lazy {
KotlinCoreEnvironment.createForProduction(
Disposer.newDisposable(),
CompilerConfiguration(),
EnvironmentConfigFiles.JVM_CONFIG_FILES
).project
}
fun createKtFile(codeString: String, fileName: String) =
PsiManager.getInstance(project)
.findFile(
LightVirtualFile(fileName, KotlinFileType.INSTANCE, codeString)
) as KtFile
For example, I need to get a PSI Class representation or fully-qualified class name for the service parameter type.
class ShapeEndpoint(service: ShapeService)
Is there somewhere a comprehensive example that would show how to compile a group of source files, build a BindingContext, access it and then use it effectively?
Related
Assume I have gradle mudule structure like that: module1 => module2 => gson.
Module2 exposes gson as a return type in one of its public interfaces' methods but it's never used in module1. The dependencies are provided using
implementation
configuration. the question is should I provide gson dependency to module1 considering it's not used there or not? is there any standard for this? I'm asking because in kotlin 1.6.10 it works fine but kotlin 1.7.20 seems to break it and during dagger2 processing step I get an error like this:
ComponentProcessingStep was unable to process
'module1.MyComponent' because
'Gson'
could not be resolved.
This is what an api (instead of implementation) dependency is for in gradle. Just replace implementation("gson:...") with api("gson:...")
See https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_separation
So when should you use the api configuration? An API dependency is one that contains at least one type that is exposed in the library binary interface, often referred to as its ABI (Application Binary Interface). This includes, but is not limited to:
types used in super classes or interfaces
types used in public method parameters, including generic parameter types (where public is something that is visible to compilers. I.e. , public, protected and package private members in the Java world)
...
The latter is your use case.
As for why dagger didn't complain in 1.6, I wouldn't know, in any case it was wrong in 1.6 as well, you just got lucky that nothing tripped over it.
I'm trying to define libraries in a common location. So in an our.libraries.gradle.kts script in a shared build plugin, I have this:
inner class Libraries {
val junit get() = ...
val junitParams get() = ...
}
val libraries = Libraries()
project.extra["libraries"] = libraries
In one of the Groovy build scripts elsewhere in the project, this is referred to like this:
allprojects {
dependencies {
testImplementation libraries.junit
}
}
This works fine.
So I try converting that script to Kotlin:
allprojects {
dependencies {
"testImplementation"(libraries.junit)
}
}
And now this fails, because it can't see the libraries property on the project, so I try explicitly pulling it out at the start:
val libraries: Libraries by project.extra
allprojects {
dependencies {
"testImplementation"(libraries.junit)
}
}
But this doesn't work either, because the script can't find the Libraries class.
I also tried putting Libraries in Libraries.kt, but then I can't seem to call methods like exclude using named parameters because for whatever reason Gradle doesn't support using the DSL when it's moved to a top-level class.
This is sort of similar to this question, but in the case of wanting to put in simple types, everything works fine. Which is to say, I can put the libraries in as a Map, but then any time I want to reference one, I have to write this:
"testImplementation"(libraries["junit"]!!)
This is obviously ugly, so I have been trying to avoid it.
So I'm stumped again.
This is part of a long saga trying to get this to work in many different ways, so the essential question is still the same: how can we define all our libraries in one location, and then refer to those in a type-safe way from other build scripts?
Recently, Gradle added shared dependencies via a TOML file, but that method only supports the version numbers, whereas our library definitions also include the excluded dependencies.
It was hard to put a completely self-contained example in the question because multiple files are involved, so here's a test repo.
Data classes print out just fine in MPP projects. When I toString() the KClass object for my class, I get:
class com.example.MySimpleClass (Kotlin reflection is not available)
How Can I do what data class does and have a nice clean name without reflection?
I don't have it set up myself to test, so answer is based purely on documentation:
KClass.simpleName is available in Common code; qualifiedName isn't, but since 1.3 it is on every platform, so you could define an expect fun in your multiplatform part and make all actual implementations access qualifiedName.
If you have a file MyUtils.kt in app/utils/:
package app.utils
fun log(message: String) {
println(message)
}
And you want to access this log() function from another file App.kt at app/, you will do this:
package app
import app.utils.log
fun main() {
log("hey")
}
But I don't like how the log() function is imported from the /utils package and not from the MyUtils.kt file explicitly.
One alternative would be to declare MyUtils.kt with package app.utils.MyUtils but I don't think it's a good practice to declare files in packages that don't have a matching folder.
Is there a way around this?
Edit: declaring an object or class wouldn't be a good solution either because of memory issues.
TL;DR
You can't.
Long answer
You seem to have a misconception that a class or object somehow adds some memory issues to your application.
That's not the case.
In fact, if you're running on the JVM, your log function will compile to the following:
public final class UtilsKt {
public static final void log(#NotNull String message) {
Intrinsics.checkParameterIsNotNull(message, "message");
System.out.println(message);
}
}
You can hit Meta+Shift+A on IntelliJ, then do Show Kotlin bytecode if you don't believe me.
Also, you seem to believe it should be possible to refer to a file name explicitly in a Kotlin import. There isn't a way to do that. Files have almost no bearings on the Kotlin compiled-code (it's kind of an accident that the file name actually reflects on the generated Java class name, as shown above).
Packages in Kotlin are usually arranged to meet the directories they are in... but that's not mandatory, by the way. You can write classes in several packages under the same directory. This means that the file names and even directory names do not really affect Kotlin runtime types and imports.
If you are worried about not being able to quickly find out where your functions are declared, I suggest you use the Java convention of calling the files by the name of the class that live in it, and then wrap all functions into an object (there's absolutely no runtime costs associated with that).
package app.utils
object MyUtils {
fun log(message:String) => println(message)
}
File: app/utils/MyUtils.kt
But notice that with any decent IDE, navigating to the declaration of a function is trivial (Meta+B in IntelliJ, usually) regardless of in which file it is in, so this problem is not normally an issue when you work with an IDE.
I'd like to write a plugin for Intellij IDEA that should modify a Java and Kotlin code.
I use the method
PsiClass.getMethods()
in order to get all methods of Java and Kotlin classes. So far so good, so then I use methods like
PsiClass.add(), PsiClass.addAfter(), PsiClass.addBefore()
that all work fine once they are called on Java files, but start to throw an exception
IncorrectOperationException
once I called them on a Kotlin class.
I'd appreciate any hint on how I can modify Kotlin and Java classes (preferably using the same approach).
When you search for a Kotlin class via the JavaPsiFacade, it returns the light class which is a shallow representation that is just based on the information in the class file. In order to add PSI elements, you have to call navigationElement on it. Then, IJ will parse the source and build a full PSI tree that can be modified.
However, if the class is a Kotlin class, navigationElement will return a KtClass which is not derived from PsiClass. You will have to use the facilities in the Kotlin hierarchy to modify it. Method instances in Kotlin are also not instances of PsiMethod, but instances of KtMethod.
For analyzing Java and Kotlin sources in a common fashion there is a different syntax tree called "UAST", but for modifications you need a language-specific approach.