Upgrade to Pro — share decks privately, control downloads, hide ads and more …

What's the big IDEA? Writing IntelliJ plugins for Kotlin

What's the big IDEA? Writing IntelliJ plugins for Kotlin

Given with Egor Andreevich

With Kotlin having full interop with Java, mixed codebases have become common and effective - but have made writing developer tools more challenging. How do you support multiple languages with a single tool? How do you convert existing plugins from Java to Kotlin and is there a way to avoid having to?

This talk will cover UAST (Universal Abstract Syntax Tree), an API for working with languages generically. With UAST you can write a single tool that will work for both Java and Kotlin - no special casing needed. We will talk about how to setup a plugin to use UAST and walk through a sample that works on mixed codebases.

The talk will also dive into the types of problems you can solve by writing an IntelliJ plugin, as well as other applications for UAST outside of IntelliJ IDEA.

Video to follow

Alec Strong

October 05, 2018
Tweet

More Decks by Alec Strong

Other Decks in Programming

Transcript

  1. Plugins • Developer experience • Simplify how you interact with

    code • Problems you can’t solve with code • Demystifying confusing parts • e.g. concurrency
  2. import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void

    someMethod() { System.out.println(injectedString); } } import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void someMethod() { System.out.println(injectedString); } } Imports
  3. import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void

    someMethod() { System.out.println(injectedString); } } import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void someMethod() { System.out.println(injectedString); } } Imports Classes
  4. import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void

    someMethod() { System.out.println(injectedString); } } import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void someMethod() { System.out.println(injectedString); } } Imports Classes • Fields
  5. import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void

    someMethod() { System.out.println(injectedString); } } import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void someMethod() { System.out.println(injectedString); } } Imports Classes • Fields
 • Annotations
  6. import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void

    someMethod() { System.out.println(injectedString); } } import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void someMethod() { System.out.println(injectedString); } } Imports Classes • Fields
 • Annotations Types
  7. import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void

    someMethod() { System.out.println(injectedString); } } import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void someMethod() { System.out.println(injectedString); } } Imports Classes • Fields
 • Annotations Types Expressions
  8. import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void

    someMethod() { System.out.println(injectedString); } } import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void someMethod() { System.out.printlnd(); } } Imports Classes • Fields
 • Annotations Types Expressions Identifiers
  9. import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void

    someMethod() { System.out.println(injectedString); } } import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void someMethod() { System.out.println(injectedString); } } Imports Classes • Fields
 • Annotations Types Expressions Identifiers Tokens
  10. import javax.inject.Inject; class SomeClass {A @Inject String injectedString; private void

    someMethod() {A System.out.println(injectedString); }A }A Imports Classes • Fields
 • Annotations Types Expressions Identifiers Tokens Psi Psi Psi Psi Psi Psi Psi Psi
  11. import javax.inject.Inject; class SomeClass {A @Inject String injectedString; private void

    someMethod() {A System.out.println(injectedString); }A }A PsiImports PsiClasses PsiFields
 PsiAnnotations PsiTypes PsiExpressions PsiIdentifiers PsiTokens
  12. import javax.inject.Inject; class SomeClass {A @Inject String injectedString; private void

    someMethod() {A System.out.println(injectedString); }A }A PsiJavaToken
  13. PsiJavaToken : PsiElement import javax.inject.Inject; class SomeClass {A @Inject String

    injectedString; private void someMethod() {A System.out.println(injectedString); }A }A
  14. import javax.inject.Inject; class SomeClass {A @Inject String injectedString; private void

    someMethod() {A System.out.println(injectedString); }A }A PsiWhitespace : PsiElement
  15. import javax.inject.Inject; class SomeClass {A @Inject String injectedString; private void

    someMethod() {A System.out.println(injectedString); }A }A PsiType
  16. PsiElementType : PsiElement import javax.inject.Inject; class SomeClass {A @Inject String

    injectedString; private void someMethod() {A System.out.println(injectedString); }A }A import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void someMethod() { System.out.println(injectedString); } }
  17. import javax.inject.Inject; class SomeClass {A @Inject String injectedString; private void

    someMethod() {A System.out.println(injectedString); }A }A import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void someMethod() { System.out.println(injectedString); } } val typeElement: PsiTypeElement val type: PsiType = typeElement.type
  18. import javax.inject.Inject; class SomeClass {A @Inject String injectedString; private void

    someMethod() {A System.out.println(injectedString); }A }A import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void someMethod() { System.out.println(injectedString); } } val typeElement: PsiTypeElement val type: PsiJavaCodeReferenceElement? = typeElement.innermostComponentReferenceElement
  19. PsiJavaCodeReferenceElement : PsiElement, PsiReference import javax.inject.Inject; class SomeClass {A @Inject

    String injectedString; private void someMethod() {A System.out.println(injectedString); }A }A String
  20. public interface PsiReference { /** * Returns the underlying (referencing)

    element of the reference. * * @return the underlying element of the reference. */ @NotNull PsiElement getElement(); }
  21. import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void

    someMethod() { System.out.println(injectedString); } } import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void someMethod() { System.out.println(injectedString); } } PsiJavaCodeReferenceElement : PsiElement, PsiReference
  22. import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void

    someMethod() { System.out.println(injectedString); } } PsiFile : PsiElement import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void someMethod() { System.out.println(injectedString); }A }A
  23. import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void

    someMethod() { System.out.println(injectedString); } } import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void someMethod() { System.out.println(injectedString); }A }A PsiViewer
  24. import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void

    someMethod() { System.out.println(injectedString); } } PsiJavaFile : PsiElement import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void someMethod() { System.out.println(injectedString); }A }A
  25. import javax.inject.Inject; class SomeClass { @Inject String injectedString; private void

    someMethod() { System.out.println(injectedString); } } import javax.inject.Inject; class SomeClass {A @Inject String injectedString; private void someMethod() {A System.out.println(injectedString); }A }A val element: PsiElement val file: PsiFile = element.containingFile
  26. import javax.inject.Inject; class SomeClass {A @Inject String injectedString; private void

    someMethod() {A System.out.println(injectedString); }A }A val file: PsiJavaFile
  27. import javax.inject.Inject; class SomeClass {A @Inject String injectedString; private void

    someMethod() {A System.out.println(injectedString); }A }A import javax.inject.Inject; class SomeClass {A @Inject String injectedString; private void someMethod() {A System.out.println(injectedString); }A }A val file: PsiJavaFile val classes: Array<PsiClass> = file.classes
  28. import javax.inject.Inject; class SomeClass {A @Inject String injectedString; private void

    someMethod() {A System.out.println(injectedString); }A }A import javax.inject.Inject; class SomeClass {A @Inject String injectedString; private void someMethod() {A System.out.println(injectedString); }A }A val file: PsiJavaFile val classes: Array<PsiClass> = file.classes val fields: List<PsiField> = classes.flatMap { it.fields.asList() }
  29. import javax.inject.Inject; class SomeClass {A @Inject String injectedString; private void

    someMethod() {A System.out.println(injectedString); }A }A import javax.inject.Inject; class SomeClass {A @Inject String injectedString; private void someMethod() {A System.out.println(injectedString); }A }A val file: PsiJavaFile val classes: Array<PsiClass> = file.classes val fields: List<PsiField> = classes.flatMap { it.fields.asList() } val injectedFields = fields .filter { it.hasAnnotation(“javax.inject.Inject") }
  30. import javax.inject.Inject; class SomeClass {A @Inject String injectedString; private void

    someMethod() {A System.out.println(injectedString); }A }A val file: PsiJavaFile val classes = Array<PsiClass> = file.classes val fields: List<PsiField> = classes.flatMap { it.fields.asList() } val injectedFields = fields .filter { it.hasAnnotation(“javax.inject.Inject") } val file: PsiJavaFile
  31. val virtualFile: VirtualFile val psiFile: PsiFile = PsiManager.getInstance(project).findFile(virtualFile) VirtualFile val

    virtualFile: VirtualFile val psiFile: PsiFile = PsiManager.getInstance(project).findFile(virtualFile) PsiFile
  32. <idea-plugin version="2"> <id>com.squareup.ideaplugin.dagger</id> <name>Dagger Plugin</name> <version>2.0.0</version> <vendor email="[email protected]" url="http:// square.github.io">Square,

    Inc.</vendor> <depends>org.jetbrains.kotlin</depends> <project-components...> <extensions defaultExtensionNs="com.intellij"...> </idea-plugin>
  33. <idea-plugin version="2"> <id>com.squareup.ideaplugin.dagger</id> <name>Dagger Plugin</name> <version>2.0.0</version> <vendor email="[email protected]" url="http:// square.github.io">Square,

    Inc.</vendor> <depends>org.jetbrains.kotlin</depends> <project-components...> <extensions defaultExtensionNs="com.intellij"...> </idea-plugin> <idea-plugin version="2"> <id>com.squareup.ideaplugin.dagger</id> <name>Dagger Plugin</name> <version>2.0.0</version> <vendor email="[email protected]" url="http:// square.github.io">Square, Inc.</vendor> <depends>org.jetbrains.kotlin</depends> <project-components...> <extensions defaultExtensionNs="com.intellij"...> </idea-plugin>
  34. <idea-plugin version="2"> <id>com.squareup.ideaplugin.dagger</id> <name>Dagger Plugin</name> <version>2.0.0</version> <vendor email="[email protected]" url="http:// square.github.io">Square,

    Inc.</vendor> <depends>org.jetbrains.kotlin</depends> <project-components...> <extensions defaultExtensionNs="com.intellij"...> </idea-plugin> <project-components...>
  35. <idea-plugin version="2"> <id>com.squareup.ideaplugin.dagger</id> <name>Dagger Plugin</name> <version>2.0.0</version> <vendor email="[email protected]" url="http:// square.github.io">Square,

    Inc.</vendor> <depends>org.jetbrains.kotlin</depends> <extensions defaultExtensionNs="com.intellij"...> </idea-plugin> <idea-plugin version=“2”> <id...> <project-components> <component> <implementation-class> com.squareup.idea.dagger.DaggerProjectComponent </implementation-class> </component> </project-components> <extensions defaultExtensionNs="com.intellij"...> </idea-plugin> <project-components> <component> <implementation-class> com.squareup.idea.dagger.DaggerProjectComponent </implementation-class> </component> </project-components>
  36. class DaggerProjectComponent : ProjectComponent {A override fun projectOpened() {A println("Hello

    World!") }A override fun projectClosed() {A println("Goodbye!") }A }A
  37. class DaggerProjectComponent( private val project: Project ) : ProjectComponent {A

    override fun projectOpened() {A println("Hello World!") }A override fun projectClosed() {A println("Goodbye!") }A }A
  38. <idea-plugin version=“2”> <id...> <project-components...> <extensions defaultExtensionNs="com.intellij"> <codeInsight.lineMarkerProvider language="JAVA" implementationClass= “com.squareup.idea.dagger.InjectionLineMarkerProvider"

    /> </extensions> </idea-plugin> <idea-plugin version=“2”> <id...> <project-components> <component> <implementation-class> com.squareup.idea.dagger.DaggerProjectComponent </implementation-class> </component> </project-components> </idea-plugin> <extensions defaultExtensionNs="com.intellij"> <codeInsight.lineMarkerProvider language="JAVA" implementationClass= “com.squareup.idea.dagger.InjectionLineMarkerProvider" /> </extensions>
  39. import javax.inject.Inject class SomeClass { @Inject var injectedString: String? =

    null private fun someMethod() { println(injectedString) } }
  40. public class InjectionLineMarkerProvider implements LineMarkerProvider { public LineMarkerInfo getLineMarkerInfo(PsiElement element)

    { … } else if (element instanceof PsiField) { // Field injection. PsiField fieldElement = (PsiField) element; PsiTypeElement typeElement = fieldElement.getTypeElement(); … } return null; }
  41. public class InjectionLineMarkerProvider implements LineMarkerProvider { public LineMarkerInfo getLineMarkerInfo(PsiElement element)

    { … } else if (element instanceof PsiField) { // Field injection. PsiField fieldElement = (PsiField) element; PsiTypeElement typeElement = fieldElement.getTypeElement(); … } return null; }
  42. public class InjectionLineMarkerProvider implements LineMarkerProvider { public LineMarkerInfo getLineMarkerInfo(PsiElement element)

    { … } else if (element instanceof PsiField) { … } else if (element instanceof KtProperty) { … } return null; }
  43. public class InjectionLineMarkerProvider implements LineMarkerProvider { public LineMarkerInfo getLineMarkerInfo(PsiElement element)

    { … } else if (element instanceof PsiField) { // Field injection. PsiField fieldElement = (PsiField) element; PsiTypeElement typeElement = fieldElement.getTypeElement(); … } else if (element instanceof KtProperty) { ??? } return null; }
  44. val psiElement: PsiElement = ... val uElement: UElement = psiElement.toUElement()

    val uField: UField = psiElement.toUElementOfType<UField>()
  45. class InjectionLineMarkerProvider : LineMarkerProvider { fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? {

    val uElement = element.toUElement() if (uElement is UField) { // Field injection. val typeElement = uElement.typeElement … } return null }
  46. “The PsiElement obtained this way is ambiguous. If you need

    "physical" PsiElement please use UElementKt.getSourcePsiElement(), if you need PsiElement that "emulates" behaviour of Java-elements (PsiClass, PsiMethod, etc.) then please use UElementKt.getAsJavaPsiElement().”
  47. <extensions defaultExtensionNs="com.intellij"> <codeInsight.lineMarkerProvider language="JAVA" implementationClass=“com.squareup.idea.dagger.ILMP" /> <codeInsight.lineMarkerProvider language="kotlin" implementationClass=“com.squareup.idea.dagger.ILMP" />

    </extensions> <extensions defaultExtensionNs="com.intellij"> <codeInsight.lineMarkerProvider language="JAVA" implementationClass=“com.squareup.idea.dagger.ILMP" /> <codeInsight.lineMarkerProvider language="kotlin" implementationClass=“com.squareup.idea.dagger.ILMP" /> </extensions>
  48. <idea-plugin> <depends>org.jetbrains.kotlin</depends> <extensions defaultExtensionNs="com.intellij"> <codeInsight.lineMarkerProvider language="JAVA" implementationClass=“com.squareup.idea.dagger.ILMP" /> <codeInsight.lineMarkerProvider language="kotlin"

    implementationClass=“com.squareup.idea.dagger.ILMP" /> </extensions> </idea-plugin> <idea-plugin> <depends>org.jetbrains.kotlin</depends> <extensions defaultExtensionNs="com.intellij"> <codeInsight.lineMarkerProvider language="JAVA" implementationClass=“com.squareup.idea.dagger.ILMP" /> <codeInsight.lineMarkerProvider language="kotlin" implementationClass=“com.squareup.idea.dagger.ILMP" /> </extensions> </idea-plugin>
  49. apply plugin: 'org.jetbrains.intellij' intellij {A version “IC-2018.1" pluginName = 'SQLDelight'

    plugins = [A “org.jetbrains.kotlin:1.2.70-release-IJ2018.1-1” ]A }A
  50. apply plugin: 'org.jetbrains.intellij' intellij {A version “IC-2018.1" pluginName = 'SQLDelight'

    plugins = [A “org.jetbrains.kotlin:1.2.70-release-IJ2018.1-1” ]A }A // Automatically adds tasks.create("runIde")
  51. apply plugin: 'org.jetbrains.intellij' intellij { version “IC-2018.1" pluginName = 'SQLDelight'

    plugins = [ “org.jetbrains.kotlin:1.2.70-release-IJ2018.1-1” ] } // Automatically adds tasks.create("runIde") tasks.create("buildPlugin")
  52. PSI UAST • JVM language analysis • Language-agnostic types •

    Language-agnostic features • e.g. querying annotations • APIs that accept it • Language metadata • Text range
  53. • Language-agnostic Intellij plugins • Build tools • Lint •

    IDEA-based annotation processing • Headless IDEA environment UAST use cases