Skip to content

Commit

Permalink
feat(kotlin): implement structure providers for Kotlin plugin #58
Browse files Browse the repository at this point in the history
  • Loading branch information
phodal committed Aug 21, 2024
1 parent 4214e50 commit 393b747
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.phodal.shirelang.kotlin

import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiNameIdentifierOwner
import com.intellij.psi.PsiReference
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.SearchScope
import com.intellij.psi.search.searches.MethodReferencesSearch
import com.intellij.psi.search.searches.ReferencesSearch
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtNamedFunction

object KotlinPsiUtil {
fun getFunctions(kotlinClass: KtClassOrObject): List<KtFunction> {
return kotlinClass.getDeclarations().filterIsInstance<KtFunction>()
}

fun getClasses(ktFile: KtFile): List<KtClassOrObject> {
return ktFile.declarations.filterIsInstance<KtClassOrObject>()
}

fun signatureString(signatureString: KtNamedFunction): String {
val bodyBlockExpression = signatureString.bodyBlockExpression
val startOffsetInParent = if (bodyBlockExpression != null) {
bodyBlockExpression.startOffsetInParent
} else {
val bodyExpression = signatureString.bodyExpression
bodyExpression?.startOffsetInParent ?: signatureString.textLength
}

val text = signatureString.text
val substring = text.substring(0, startOffsetInParent)
return substring.replace('\n', ' ').trim()
}

fun findUsages(nameIdentifierOwner: PsiNameIdentifierOwner): List<PsiReference> {
val project = nameIdentifierOwner.project
val searchScope = GlobalSearchScope.allScope(project) as SearchScope

return when (nameIdentifierOwner) {
is PsiMethod -> {
MethodReferencesSearch.search(nameIdentifierOwner, searchScope, true)
}

else -> {
ReferencesSearch.search((nameIdentifierOwner as PsiElement), searchScope, true)
}
}.findAll().map { it as PsiReference }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.phodal.shirelang.kotlin.codemodel

import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNameIdentifierOwner
import com.phodal.shirecore.codemodel.ClassStructureProvider
import com.phodal.shirecore.codemodel.model.ClassStructure
import com.phodal.shirelang.kotlin.KotlinPsiUtil
import org.jetbrains.kotlin.psi.*

class KotlinClassStructureProvider : ClassStructureProvider {
private fun getPrimaryConstructorFields(kotlinClass: KtClassOrObject): List<KtParameter> {
return kotlinClass.getPrimaryConstructorParameters().filter { it.hasValOrVar() }
}

override fun build(psiElement: PsiElement, gatherUsages: Boolean): ClassStructure? {
if (psiElement !is KtClassOrObject) return null

val text = psiElement.text
val name = psiElement.name
val functions = KotlinPsiUtil.getFunctions(psiElement)
val allFields = getPrimaryConstructorFields(psiElement)
val usages =
if (gatherUsages) KotlinPsiUtil.findUsages(psiElement as PsiNameIdentifierOwner) else emptyList()

val annotations: List<String> = psiElement.annotationEntries.mapNotNull {
it.text
}

val displayName = psiElement.fqName?.asString() ?: psiElement.name ?: ""
return ClassStructure(
psiElement,
text,
name,
displayName,
functions,
allFields,
null,
annotations = annotations,
usages
)
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.phodal.shirelang.kotlin.codemodel

import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiTreeUtil
import com.phodal.shirecore.codemodel.FileStructureProvider
import com.phodal.shirecore.codemodel.model.FileStructure
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtImportList
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtPackageDirective

class KotlinFileStructureProvider : FileStructureProvider {
override fun build(psiFile: PsiFile): FileStructure? {
val name = psiFile.name
val path = psiFile.virtualFile?.path ?: ""

val packageDirective = PsiTreeUtil.getChildrenOfTypeAsList(psiFile, KtPackageDirective::class.java).firstOrNull()
val packageName = packageDirective?.text ?: ""

val importList = PsiTreeUtil.getChildrenOfTypeAsList(psiFile, KtImportList::class.java)
val imports = importList.flatMap { it.imports }

val classOrObjects = PsiTreeUtil.getChildrenOfTypeAsList(psiFile, KtClassOrObject::class.java)
val namedFunctions = PsiTreeUtil.getChildrenOfTypeAsList(psiFile, KtNamedFunction::class.java)

return FileStructure(psiFile, name, path, packageName, imports, classOrObjects, namedFunctions)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.phodal.shirelang.kotlin.codemodel

import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNameIdentifierOwner
import com.phodal.shirecore.codemodel.MethodStructureProvider
import com.phodal.shirecore.codemodel.model.MethodStructure
import com.phodal.shirelang.kotlin.KotlinPsiUtil
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.containingClass

class KotlinMethodStructureProvider : MethodStructureProvider {
override fun build(psiElement: PsiElement, includeClassContext: Boolean, gatherUsages: Boolean): MethodStructure? {
if (psiElement !is KtNamedFunction) return null

val returnType = psiElement.getReturnTypeReference()?.text
val containingClass = psiElement.containingClass()
val signatureString = KotlinPsiUtil.signatureString(psiElement)
val displayName = psiElement.language.displayName
val valueParameters = psiElement.valueParameters.mapNotNull { it.name }
val usages =
if (gatherUsages) KotlinPsiUtil.findUsages(psiElement as PsiNameIdentifierOwner) else emptyList()

return MethodStructure(
psiElement,
psiElement.text,
psiElement.name,
signatureString,
containingClass,
displayName,
returnType,
valueParameters,
includeClassContext,
usages
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.phodal.shirelang.kotlin.codemodel

import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNameIdentifierOwner
import com.intellij.psi.util.PsiTreeUtil
import com.phodal.shirecore.codemodel.VariableStructureProvider
import com.phodal.shirecore.codemodel.model.VariableStructure
import com.phodal.shirelang.kotlin.KotlinPsiUtil
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtVariableDeclaration
import org.jetbrains.kotlin.psi.psiUtil.containingClass

class KotlinVariableStructureProvider : VariableStructureProvider {
override fun build(
psiElement: PsiElement,
withMethodContext: Boolean,
withClassContext: Boolean,
gatherUsages: Boolean
): VariableStructure? {
when (psiElement) {
is KtVariableDeclaration -> {
val text = psiElement.text
val name = psiElement.name
val parentOfType = PsiTreeUtil.getParentOfType(psiElement, KtNamedFunction::class.java, true)
val containingClass = psiElement.containingClass()
val psiNameIdentifierOwner = psiElement as? PsiNameIdentifierOwner

val usages = if (gatherUsages && psiNameIdentifierOwner != null) {
KotlinPsiUtil.findUsages(psiNameIdentifierOwner)
} else {
emptyList()
}

return VariableStructure(psiElement, text, name, parentOfType, containingClass, usages, withMethodContext, withClassContext)
}

is KtParameter -> {
val text = psiElement.text
val name = psiElement.name
val parentOfType = PsiTreeUtil.getParentOfType(psiElement, KtNamedFunction::class.java, true)
val containingClass = psiElement.containingClass()
val psiNameIdentifierOwner = psiElement as? PsiNameIdentifierOwner

val usages = if (gatherUsages && psiNameIdentifierOwner != null) {
KotlinPsiUtil.findUsages(psiNameIdentifierOwner)
} else {
emptyList()
}

return VariableStructure(psiElement, text, name, parentOfType, containingClass, usages, withMethodContext, withClassContext)
}

else -> {
return null
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<idea-plugin package="com.phodal.shirelang.kotlin">
<!--suppress PluginXmlValidity -->
<dependencies>
<plugin id="org.jetbrains.kotlin"/>
<plugin id="org.jetbrains.plugins.gradle"/>
</dependencies>

<extensions defaultExtensionNs="com.phodal">
<fileStructureProvider language="kotlin"
implementationClass="com.phodal.shirelang.kotlin.codemodel.KotlinFileStructureProvider"/>
<classStructureProvider language="kotlin"
implementationClass="com.phodal.shirelang.kotlin.codemodel.KotlinClassStructureProvider"/>
<methodStructureProvider language="kotlin"
implementationClass="com.phodal.shirelang.kotlin.codemodel.KotlinMethodStructureProvider"/>
<variableStructureProvider language="kotlin"
implementationClass="com.phodal.shirelang.kotlin.codemodel.KotlinVariableStructureProvider"/>
</extensions>
</idea-plugin>

0 comments on commit 393b747

Please sign in to comment.