Skip to content

Commit

Permalink
feat(script): add support for 'mavenId()' in 'class'
Browse files Browse the repository at this point in the history
  • Loading branch information
tangcent committed Aug 12, 2024
1 parent 5fddde4 commit 444d24b
Show file tree
Hide file tree
Showing 7 changed files with 371 additions and 19 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/co.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
./gradlew check --stacktrace
./gradlew codeCoverageReport
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: build/reports/jacoco/report.xml
Expand Down
2 changes: 1 addition & 1 deletion idea-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ intellij {
type.set("IC")
pluginName.set("easy-yapi")
sandboxDir.set("idea-sandbox")
plugins.set(listOf("java"))
plugins.set(listOf("java", "maven", "gradle"))
}

tasks {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import com.itangcent.common.utils.mapToTypedArray
import com.itangcent.http.RequestUtils
import com.itangcent.idea.plugin.api.MethodInferHelper
import com.itangcent.idea.plugin.format.Json5Formatter
import com.itangcent.idea.utils.MavenHelper
import com.itangcent.idea.utils.MavenIdData
import com.itangcent.intellij.config.rule.*
import com.itangcent.intellij.context.ActionContext
import com.itangcent.intellij.extend.notReentrant
Expand Down Expand Up @@ -470,6 +472,10 @@ abstract class ScriptRuleParser : AbstractRuleParser() {
}
}

override fun mavenId(): MavenIdData? {
return actionContext.callInReadUI { MavenHelper.getMavenId(psiClass) }
}

override fun toString(): String {
return name()
}
Expand Down Expand Up @@ -1069,6 +1075,7 @@ abstract class ScriptRuleParser : AbstractRuleParser() {
*/
abstract fun implements(): Array<ScriptClassContext>?

abstract fun mavenId(): MavenIdData?
}

/**
Expand Down Expand Up @@ -1231,6 +1238,11 @@ abstract class ScriptRuleParser : AbstractRuleParser() {
}
}

override fun mavenId(): MavenIdData? {
val psiClass = getResource() as? PsiClass ?: return null
return actionContext.callInReadUI { MavenHelper.getMavenId(psiClass) }
}

override fun toString(): String {
return name()
}
Expand Down Expand Up @@ -1439,6 +1451,11 @@ abstract class ScriptRuleParser : AbstractRuleParser() {
}
}

override fun mavenId(): MavenIdData? {
val psiClass = getResource() as? PsiClass ?: return null
return actionContext.callInReadUI { MavenHelper.getMavenId(psiClass) }
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ScriptClassContext) return false
Expand Down
163 changes: 163 additions & 0 deletions idea-plugin/src/main/kotlin/com/itangcent/idea/utils/MavenHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package com.itangcent.idea.utils

import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.psi.PsiClass
import com.itangcent.annotation.script.ScriptTypeName
import com.itangcent.common.utils.safe
import org.jetbrains.idea.maven.project.MavenProjectsManager
import org.jetbrains.plugins.gradle.service.project.data.ExternalProjectDataCache


/**
* Utility object for obtaining Maven identifiers of a given PsiClass through Maven or Gradle.
*
* @author tangcent
* @date 2024/08/11
*/
object MavenHelper {

/**
* Attempts to retrieve the Maven ID of a PsiClass using Maven or Gradle.
*
* @param psiClass the PsiClass for which the Maven ID is to be retrieved.
* @return MavenIdData if found, null otherwise.
*/
fun getMavenId(psiClass: PsiClass): MavenIdData? {
return safe {
getMavenIdByMaven(psiClass)
} ?: safe {
getMavenIdByGradle(psiClass)
}
}

/**
* Retrieves the Maven ID of a PsiClass using Maven.
*
* @param psiClass the PsiClass for which the Maven ID is to be retrieved.
* @return MavenIdData if found, null otherwise.
*/
private fun getMavenIdByMaven(psiClass: PsiClass): MavenIdData? {
val project = psiClass.project
val module = ModuleUtilCore.findModuleForPsiElement(psiClass)
?: return null

val mavenProjectsManager = MavenProjectsManager.getInstance(project)
val mavenProject = mavenProjectsManager.findProject(module) ?: return null
val mavenId = mavenProject.mavenId
return MavenIdData(
groupId = mavenId.groupId!!,
artifactId = mavenId.artifactId!!,
version = mavenId.version!!
)
}

/**
* Retrieves the Maven ID of a PsiClass using Gradle.
*
* @param psiClass the PsiClass for which the Maven ID is to be retrieved.
* @return MavenIdData if found, null otherwise.
*/
private fun getMavenIdByGradle(psiClass: PsiClass): MavenIdData? {
val project = psiClass.project
val projectPath = project.basePath ?: return null
val externalProject = ExternalProjectDataCache.getInstance(project).getRootExternalProject(projectPath)
?: return null

return MavenIdData(
groupId = externalProject.group,
artifactId = externalProject.name,
version = externalProject.version
)
}
}

/**
* Data class representing Maven ID information.
*/
@ScriptTypeName("MavenId")
class MavenIdData(
val groupId: String,
val artifactId: String,
val version: String
) {

/**
* Generates a Maven dependency snippet
*/
fun maven(): String {
return """
<dependency>
<groupId>$groupId</groupId>
<artifactId>$artifactId</artifactId>
<version>$version</version>
</dependency>
""".trimIndent()
}

/**
* Generates a Gradle implementation dependency snippet
*/
fun gradle(): String {
return """
implementation group: '$groupId', name: '$artifactId', version: '$version'
""".trimIndent()
}

/**
* Generates a Gradle implementation dependency snippet in short form.
*/
fun gradleShort(): String {
return """
implementation '$groupId:$artifactId:$version'
""".trimIndent()
}

/**
* Generates a Gradle implementation dependency snippet in Kotlin DSL.
*/
fun gradleKotlin(): String {
return """
implementation("$groupId:$artifactId:$version")
""".trimIndent()
}

/**
* Generates an SBT dependency snippet.
*/
fun sbt(): String {
return """
libraryDependencies += "$groupId" % "$artifactId" % "$version"
""".trimIndent()
}

/**
* Generates an Ivy dependency snippet.
*/
fun ivy(): String {
return """
<dependency org="$groupId" name="$artifactId" rev="$version" />
""".trimIndent()
}

override fun toString(): String {
return "$groupId:$artifactId:$version"
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is MavenIdData) return false

if (groupId != other.groupId) return false
if (artifactId != other.artifactId) return false
if (version != other.version) return false

return true
}

override fun hashCode(): Int {
var result = groupId.hashCode()
result = 31 * result + artifactId.hashCode()
result = 31 * result + version.hashCode()
return result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1186,6 +1186,10 @@ abstract class ScriptClassContextBaseTest : PluginContextLightCodeInsightFixture
assertTrue(modelPsiClass.asClassContext().implements()!!.isEmpty())
}

fun testMavenId() {
assertNull(objectPsiClass.asClassContext().mavenId())
}

//endregion

//region tests of ScriptFieldContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.itangcent.idea.plugin.settings

import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty
import kotlin.reflect.KProperty
import kotlin.reflect.full.memberProperties
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
Expand All @@ -13,18 +14,19 @@ import kotlin.test.assertNotNull
*/
object ETHUtils {

fun <T> testCETH(original: T, copy: T.() -> T) {
fun <T : Any> testCETH(original: T, copy: T.() -> T) {
assertEquals(original, original)
assertNotNull(original, "original should not be null")
original!!
assertNotEquals<Any>(original, "original should not be equals to a string")
// Create a copy using the data class generated `copy` method
val copied = original.copy()
assertEquals(original, copied)
assertEquals(original.hashCode(), copied.hashCode())
assertEquals(original.toString(), copied.toString())

// Use reflection to fetch all properties
Settings::class.memberProperties
.filterIsInstance<KMutableProperty<*>>()
val memberProperties = original::class.memberProperties
memberProperties.filterIsInstance<KMutableProperty<*>>()
.forEach { property ->
val newCopied = original.copy()

Expand All @@ -33,27 +35,61 @@ object ETHUtils {
val updateValue = backup?.backup() ?: propClass?.fake()

property.setter.call(newCopied, updateValue)

// Check that the modified object does not equal the copied object
assertNotEquals(
original,
newCopied,
"[${original!!::class}] Change Property: ${property.name} from $backup to $updateValue}"
)
assertNotEquals(
original.hashCode(), newCopied.hashCode(),
"[${original!!::class}] Change Property: ${property.name} from $backup to $updateValue}"
)
assertNotEquals(
original.toString(), newCopied.toString(),
"[${original!!::class}] Change Property: ${property.name} from $backup to $updateValue}"
)
checkEquals(original, newCopied, property, backup, updateValue)

// Restore original property to continue clean tests
property.setter.call(original, backup)
}

val originalProperties = memberProperties.associate { it.name to it.getter.call(original) }
val constructor = original::class.constructors.maxByOrNull { it.parameters.size } ?: return
memberProperties.filter { it !is KMutableProperty<*> }
.forEach { property ->
val propertyName = property.name
val backup = property.getter.call(original)
val propClass = property.returnType.classifier as? KClass<*>
val updateValue = backup?.backup() ?: propClass?.fake()

val newCopied = constructor.callBy(
constructor.parameters.associateWith {
if (it.name == propertyName) updateValue else originalProperties[it.name]
}
)

// Check that the modified object does not equal the copied object
checkEquals(original, newCopied, property, backup, updateValue)
}
}

/**
* Check that the modified object does not equal the copied object
*/
private fun checkEquals(
original: Any,
newCopied: Any,
property: KProperty<*>,
backup: Any?,
updateValue: Any?
) {
assertNotEquals(
original,
newCopied,
"[${original::class}] Change Property: ${property.name} from $backup to $updateValue}"
)
assertNotEquals(
original.hashCode(), newCopied.hashCode(),
"[${original::class}] Change Property: ${property.name} from $backup to $updateValue}"
)
assertNotEquals(
original.toString(), newCopied.toString(),
"[${original::class}] Change Property: ${property.name} from $backup to $updateValue}"
)
}
}

@Suppress("UNCHECKED_CAST")
fun Any.backup(): Any {
return when (this) {
is Array<*> -> {
Expand Down
Loading

0 comments on commit 444d24b

Please sign in to comment.