Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(script): add support for 'mavenId()' in 'class' #1155

Merged
merged 1 commit into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading