Skip to content

Commit

Permalink
feat(java): refactor method name and add JavaCodeModifier #14
Browse files Browse the repository at this point in the history
Refactored the method name from 'getClassStructure' to 'build' in JavaClassStructureProvider and other related classes. Added a new class 'JavaCodeModifier' for inserting test code into a specified source file in a Kotlin project.
  • Loading branch information
phodal committed Jun 17, 2024
1 parent 6e65fa1 commit 319cd74
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface ClassStructureProvider {
* @param gatherUsages specifies whether to gather usages of the class
* @return the class context for the given [psiElement], or null if the class context cannot be determined
*/
fun getClassStructure(psiElement: PsiElement, gatherUsages: Boolean): ClassStructure?
fun build(psiElement: PsiElement, gatherUsages: Boolean): ClassStructure?

companion object {
private val languageExtension = LanguageExtension<ClassStructureProvider>("com.phodal.classStructureProvider")
Expand All @@ -34,7 +34,7 @@ interface ClassStructureProvider {
fun from(psiElement: PsiElement, gatherUsages: Boolean = false): ClassStructure? {
for (provider in providers) {
try {
return provider.getClassStructure(psiElement, gatherUsages)
return provider.build(psiElement, gatherUsages)
} catch (e: Exception) {
logger.error("Error while getting class context from $provider", e)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package com.phodal.shirelang.java.codeedit

import com.intellij.lang.Language
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.guessProjectDir
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.*
import com.phodal.shirecore.codeedit.CodeModifier

open class JavaCodeModifier : CodeModifier {
private val log = logger<JavaCodeModifier>()

override fun isApplicable(language: Language) = language is JavaLanguage

private fun lookupFile(project: Project, sourceFile: VirtualFile) =
PsiManager.getInstance(project).findFile(sourceFile) as PsiJavaFile

/**
* This function is used to insert test code into a specified source file in a Kotlin project.
* It takes the source file, project, and the test code as parameters.
*
* The function first trims the test code by removing leading and trailing whitespaces, as well as any surrounding triple backticks and "java" prefix.
* If the trimmed code does not contain the "@Test" annotation, a warning is logged and the method code is inserted into the source file.
*
* It then checks if the trimmed code is a full class code (starts with "import" or "package" and contains "class ").
* If the source file already contains classes, the function inserts the test code into an existing class.
*
* If the trimmed code is a full class code, the function inserts a new class into the source file.
*
* If none of the above conditions are met, the function inserts the test code as a method into the source file.
*
* @param sourceFile The VirtualFile representing the source file where the test code will be inserted.
* @param project The Project to which the source file belongs.
* @param code The test code to be inserted into the source file.
* @return Boolean value indicating whether the test code was successfully inserted.
*/
override fun insertTestCode(sourceFile: VirtualFile, project: Project, code: String): Boolean {
val trimCode = code.trim().removeSurrounding("```").removePrefix("java").trim()

val isFullTestCode =
(trimCode.startsWith("import") || trimCode.startsWith("package")) && trimCode.contains("class ")

val existTestFileClasses = runReadAction { lookupFile(project, sourceFile).classes }
val alreadyExtTestFile = existTestFileClasses.isNotEmpty()

when {
alreadyExtTestFile -> return insertToExistClass(existTestFileClasses, project, trimCode)
isFullTestCode -> return insertClass(sourceFile, project, trimCode)
trimCode.contains("@Test") -> insertMethod(sourceFile, project, trimCode)
else -> {
log.warn("methodCode does not contain @Test annotation: $trimCode")
insertMethod(sourceFile, project, trimCode)
}
}
return true
}

private fun insertToExistClass(
testFileClasses: Array<out PsiClass>,
project: Project,
trimCode: String,
): Boolean {
// todo: check to naming testFile, but since Java only has One Class under file
val lastClass = testFileClasses.last()
val classEndOffset = runReadAction { lastClass.textRange.endOffset }

val psiFile = try {
PsiFileFactory.getInstance(project)
.createFileFromText("Test.java", JavaLanguage.INSTANCE, trimCode)
} catch (e: Throwable) {
log.warn("Failed to create file from text: $trimCode", e)
null
}

val newCode = psiFile?.text ?: trimCode
try {
val newClassMethods = runReadAction {
psiFile?.children?.firstOrNull { it is PsiClass }?.children?.filterIsInstance<PsiMethod>()
}

WriteCommandAction.runWriteCommandAction(project) {
newClassMethods?.forEach {
lastClass.add(it)
}
}
} catch (e: Throwable) {
WriteCommandAction.runWriteCommandAction(project) {
val document = PsiDocumentManager.getInstance(project).getDocument(lastClass.containingFile)
document?.insertString(classEndOffset - 1, "\n $newCode")
}

return false
}

return true
}

override fun insertMethod(sourceFile: VirtualFile, project: Project, code: String): Boolean {
val rootElement = runReadAction {
val psiJavaFile = lookupFile(project, sourceFile)
val psiClass = psiJavaFile.classes.firstOrNull()
if (psiClass == null) {
log.error("Failed to find PsiClass in the source file: $psiJavaFile, code: $code")
return@runReadAction null
}

return@runReadAction psiClass
} ?: return false

val newTestMethod = ReadAction.compute<PsiMethod, Throwable> {
val psiElementFactory = PsiElementFactory.getInstance(project)
try {
val methodCode = psiElementFactory.createMethodFromText(code, rootElement)
if (rootElement.findMethodsByName(methodCode.name, false).isNotEmpty()) {
log.error("Method already exists in the class: ${methodCode.name}")
}

methodCode
} catch (e: Throwable) {
log.error("Failed to create method from text: $code", e)
return@compute null
}
}

WriteCommandAction.runWriteCommandAction(project) {
try {
rootElement.add(newTestMethod)
} catch (e: Throwable) {
val classEndOffset = rootElement.textRange.endOffset
val document = PsiDocumentManager.getInstance(project).getDocument(rootElement.containingFile)
document?.insertString(classEndOffset - 1, "\n ")
document?.insertString(classEndOffset - 1 + "\n ".length, newTestMethod.text)
}
}

project.guessProjectDir()?.refresh(true, true)

return true
}

override fun insertClass(sourceFile: VirtualFile, project: Project, code: String): Boolean {
return WriteCommandAction.runWriteCommandAction<Boolean>(project) {
val psiFile = lookupFile(project, sourceFile)
val document = psiFile.viewProvider.document!!
document.insertString(document.textLength, code)

true
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import com.phodal.shirecore.codemodel.ClassStructureProvider
import com.phodal.shirecore.codemodel.model.ClassStructure

class JavaClassStructureProvider : ClassStructureProvider {
override fun getClassStructure(psiElement: PsiElement, gatherUsages: Boolean): ClassStructure? {
override fun build(psiElement: PsiElement, gatherUsages: Boolean): ClassStructure? {
if (psiElement !is PsiClass) return null

val supers = runReadAction {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class JavaElementStrategyBuilder : PsiElementStrategyBuilder {
.findClass(canonicalName, GlobalSearchScope.projectScope(project))
?: return null

return JavaClassStructureProvider().getClassStructure(psiClass, false)
return JavaClassStructureProvider().build(psiClass, false)
}

override fun relativeElement(project: Project, givenElement: PsiElement, type: PsiComment): PsiElement? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class MvcContextService(private val project: Project) {

val path = file?.virtualFile?.path ?: ""
val classList = relevantModel.map {
JavaClassStructureProvider().getClassStructure(it, true)?.format()
JavaClassStructureProvider().build(it, true)?.format()
}

return "\n${classList.joinToString("\n")}\n//current path: ${path}\n"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
language="JAVA"
implementationClass="com.phodal.shirelang.java.variable.JavaPsiContextVariableProvider"/>

<shireCodeModifier language="JAVA"
implementationClass="com.phodal.shirelang.java.codeedit.JavaCodeModifier"/>

<shireElementStrategyBuilder implementation="com.phodal.shirelang.java.impl.JavaElementStrategyBuilder"/>
</extensions>
</idea-plugin>

0 comments on commit 319cd74

Please sign in to comment.