Skip to content

Commit

Permalink
Add initial insight for Bukkit-like plugin.yml
Browse files Browse the repository at this point in the history
  • Loading branch information
RedNesto committed Aug 12, 2024
1 parent d8fb941 commit bf660ea
Show file tree
Hide file tree
Showing 24 changed files with 1,186 additions and 269 deletions.
26 changes: 24 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,21 @@ repositories {
}
}
mavenCentral()
maven("https://repo.spongepowered.org/maven/")
maven("https://repo.spongepowered.org/maven/") {
content {
includeGroup("org.spongepowered")
}
}
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") {
content {
includeGroup("org.spigotmc")
}
}
maven("https://oss.sonatype.org/content/repositories/snapshots/") {
content {
includeGroup("net.md-5")
}
}
}

dependencies {
Expand Down Expand Up @@ -141,6 +155,8 @@ dependencies {

testLibs(libs.test.mockJdk)
testLibs(libs.test.mixin)
testLibs(libs.test.spigotapi)
testLibs(libs.test.bungeecord)
testLibs(libs.test.spongeapi) {
artifact {
classifier = "shaded"
Expand Down Expand Up @@ -212,6 +228,7 @@ intellij {
"ByteCodeViewer",
"org.intellij.intelliLang",
"properties",
"org.jetbrains.plugins.yaml",
// needed dependencies for unit tests
"junit"
)
Expand Down Expand Up @@ -327,7 +344,12 @@ license {
exclude("META-INF/plugin.xml") // https://youtrack.jetbrains.com/issue/IDEA-345026
include(endings.map { "**/*.$it" })

exclude("com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/*.java")
val projectDir = layout.projectDirectory.asFile
exclude {
it.file.toRelativeString(projectDir)
.replace("\\", "/")
.startsWith("src/test/resources")
}

this.tasks {
register("gradle") {
Expand Down
6 changes: 6 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Minecraft Development for IntelliJ

## [Unreleased]

### Added

- `plugin.yml`, `paper-plugin.yml` and `bungee.yml` main class reference and validity inspection

## [1.8.1] - 2024-08-10

### Added
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ fuel-coroutines = { module = "com.github.kittinunf.fuel:fuel-coroutines", versio
# Testing
test-mockJdk = "org.jetbrains.idea:mock-jdk:1.7-4d76c50"
test-mixin = "org.spongepowered:mixin:0.8.5"
test-spigotapi = "org.spigotmc:spigot-api:1.21-R0.1-SNAPSHOT"
test-bungeecord = "net.md-5:bungeecord-api:1.21-R0.1-SNAPSHOT"
test-spongeapi = "org.spongepowered:spongeapi:7.4.0"
test-fabricloader = "net.fabricmc:fabric-loader:0.15.11"
test-nbt = "com.demonwav.mcdev:all-types-nbt:1.0"
Expand Down
8 changes: 6 additions & 2 deletions src/main/kotlin/platform/bukkit/BukkitModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,25 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiLiteralExpression
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.util.application
import org.jetbrains.uast.UClass
import org.jetbrains.uast.UIdentifier
import org.jetbrains.uast.toUElementOfType

class BukkitModule<out T : AbstractModuleType<*>>(facet: MinecraftFacet, type: T) : AbstractModule(facet) {

// Light test cases only support a single source content root, so we mix sources and resources under unit test mode
private val pluginYmlSourceType = if (application.isUnitTestMode) SourceType.SOURCE else SourceType.RESOURCE

var pluginYml by nullable {
if (moduleType is PaperModuleType) {
val paperPlugin = facet.findFile("paper-plugin.yml", SourceType.RESOURCE)
val paperPlugin = facet.findFile("paper-plugin.yml", pluginYmlSourceType)
if (paperPlugin != null) {
return@nullable paperPlugin
}
}

facet.findFile("plugin.yml", SourceType.RESOURCE)
facet.findFile("plugin.yml", pluginYmlSourceType)
}
private set

Expand Down
8 changes: 6 additions & 2 deletions src/main/kotlin/platform/bungeecord/BungeeCordModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,18 @@ import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.util.application
import org.jetbrains.uast.UClass
import org.jetbrains.uast.UIdentifier
import org.jetbrains.uast.toUElementOfType

class BungeeCordModule<out T : AbstractModuleType<*>>(facet: MinecraftFacet, type: T) : AbstractModule(facet) {

// Light test cases only support a single source content root, so we mix sources and resources under unit test mode
private val pluginYmlSourceType = if (application.isUnitTestMode) SourceType.SOURCE else SourceType.RESOURCE

var pluginYml by nullable {
val file = facet.findFile("bungee.yml", SourceType.RESOURCE)
val file = facet.findFile("bungee.yml", pluginYmlSourceType)
if (file != null) {
return@nullable file
}
Expand All @@ -53,7 +57,7 @@ class BungeeCordModule<out T : AbstractModuleType<*>>(facet: MinecraftFacet, typ
// So we don't check
return@nullable null
}
return@nullable facet.findFile("plugin.yml", SourceType.RESOURCE)
return@nullable facet.findFile("plugin.yml", pluginYmlSourceType)
}
private set

Expand Down
80 changes: 80 additions & 0 deletions src/main/kotlin/yaml/PluginYmlInspection.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Minecraft Development for IntelliJ
*
* https://mcdev.io/
*
* Copyright (C) 2024 minecraft-dev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, version 3.0 only.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package com.demonwav.mcdev.yaml

import com.demonwav.mcdev.facet.MinecraftFacet
import com.demonwav.mcdev.platform.bukkit.PaperModuleType
import com.demonwav.mcdev.platform.bukkit.SpigotModuleType
import com.demonwav.mcdev.platform.bukkit.util.BukkitConstants
import com.demonwav.mcdev.platform.bungeecord.BungeeCordModuleType
import com.demonwav.mcdev.platform.bungeecord.util.BungeeCordConstants
import com.demonwav.mcdev.util.findModule
import com.intellij.codeInspection.LocalInspectionTool
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElementVisitor
import org.jetbrains.annotations.Nls
import org.jetbrains.yaml.psi.YAMLKeyValue
import org.jetbrains.yaml.psi.YAMLScalar
import org.jetbrains.yaml.psi.YamlPsiElementVisitor

class PluginYmlInspection : LocalInspectionTool() {

override fun getStaticDescription(): @Nls String? = "Reports issues in Bukkit-like plugin.yml files"

override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
val module = holder.file.findModule() ?: return PsiElementVisitor.EMPTY_VISITOR
val virtualFile = holder.file.virtualFile ?: return PsiElementVisitor.EMPTY_VISITOR
val pluginClassFqn = when {
MinecraftFacet.getInstance(module, SpigotModuleType, PaperModuleType)?.pluginYml == virtualFile ->
BukkitConstants.PLUGIN
MinecraftFacet.getInstance(module, BungeeCordModuleType)?.pluginYml == virtualFile ->
BungeeCordConstants.PLUGIN
else -> return PsiElementVisitor.EMPTY_VISITOR
}

return Visitor(holder, pluginClassFqn)
}

private class Visitor(val holder: ProblemsHolder, val pluginClassFqn: String) : YamlPsiElementVisitor() {

override fun visitScalar(scalar: YAMLScalar) {
super.visitScalar(scalar)

if ((scalar.parent as? YAMLKeyValue)?.keyText != "main") {
return
}

val resolved = scalar.references.firstNotNullOfOrNull { it.resolve() as? PsiClass }
if (resolved == null) {
holder.registerProblem(scalar, "Unresolved reference")
return
}

val pluginClass = JavaPsiFacade.getInstance(holder.project).findClass(pluginClassFqn, scalar.resolveScope)
?: return
if (!resolved.isInheritor(pluginClass, true)) {
holder.registerProblem(scalar, "Class must implement $pluginClassFqn")
}
}
}
}
141 changes: 141 additions & 0 deletions src/main/kotlin/yaml/PluginYmlReferenceContributor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Minecraft Development for IntelliJ
*
* https://mcdev.io/
*
* Copyright (C) 2024 minecraft-dev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, version 3.0 only.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package com.demonwav.mcdev.yaml

import com.demonwav.mcdev.facet.MinecraftFacet
import com.demonwav.mcdev.platform.bukkit.PaperModuleType
import com.demonwav.mcdev.platform.bukkit.SpigotModuleType
import com.demonwav.mcdev.platform.bukkit.util.BukkitConstants
import com.demonwav.mcdev.platform.bungeecord.BungeeCordModuleType
import com.demonwav.mcdev.platform.bungeecord.util.BungeeCordConstants
import com.demonwav.mcdev.util.findModule
import com.intellij.codeInsight.completion.JavaLookupElementBuilder
import com.intellij.lang.jvm.JvmModifier
import com.intellij.openapi.util.TextRange
import com.intellij.patterns.ObjectPattern
import com.intellij.patterns.PatternCondition
import com.intellij.patterns.PlatformPatterns
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
import com.intellij.psi.PsiReferenceContributor
import com.intellij.psi.PsiReferenceRegistrar
import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReference
import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReferenceProvider
import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReferenceSet
import com.intellij.psi.search.searches.ClassInheritorsSearch
import com.intellij.util.ArrayUtilRt
import com.intellij.util.ProcessingContext
import org.jetbrains.yaml.psi.YAMLKeyValue
import org.jetbrains.yaml.psi.YAMLScalar

private fun <P : ObjectPattern<out PsiElement, P>> P.inSpigotOrPaperPluginYml(): P = with(
object : PatternCondition<PsiElement>("") {
override fun accepts(t: PsiElement, context: ProcessingContext): Boolean {
val module = t.findModule() ?: return false
val instance = MinecraftFacet.getInstance(module, SpigotModuleType, PaperModuleType) ?: return false
return instance.pluginYml == t.containingFile.originalFile.virtualFile
}
}
)

private fun <P : ObjectPattern<out PsiElement, P>> P.inBungeePluginYml(): P = with(
object : PatternCondition<PsiElement>("") {
override fun accepts(t: PsiElement, context: ProcessingContext): Boolean {
val module = t.findModule() ?: return false
val instance = MinecraftFacet.getInstance(module, BungeeCordModuleType) ?: return false
return instance.pluginYml == t.containingFile.originalFile.virtualFile
}
}
)

class PluginYmlReferenceContributor : PsiReferenceContributor() {

override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) {
registrar.registerReferenceProvider(
PlatformPatterns.psiElement(YAMLScalar::class.java)
.withParent(PlatformPatterns.psiElement(YAMLKeyValue::class.java).withName("main"))
.inSpigotOrPaperPluginYml(),
PluginYmlClassReferenceProvider(BukkitConstants.PLUGIN)
)
registrar.registerReferenceProvider(
PlatformPatterns.psiElement(YAMLScalar::class.java)
.withParent(PlatformPatterns.psiElement(YAMLKeyValue::class.java).withName("main"))
.inBungeePluginYml(),
PluginYmlClassReferenceProvider(BungeeCordConstants.PLUGIN)
)
}
}

class PluginYmlClassReferenceProvider(val superClass: String) : JavaClassReferenceProvider() {

init {
setOption(ALLOW_DOLLAR_NAMES, true)
setOption(JVM_FORMAT, true)
setOption(CONCRETE, true)
setOption(INSTANTIATABLE, true)
setOption(SUPER_CLASSES, listOf(superClass))
setOption(ADVANCED_RESOLVE, true)
}

override fun getReferencesByString(
str: String,
position: PsiElement,
offsetInPosition: Int
): Array<out PsiReference?> {
return object : JavaClassReferenceSet(str, position, offsetInPosition, true, this) {
override fun isAllowDollarInNames(): Boolean = true

override fun isAllowSpaces(): Boolean = false

override fun createReference(
referenceIndex: Int,
referenceText: String,
textRange: TextRange,
staticImport: Boolean
): JavaClassReference {
return PluginYmlClassReference(this, referenceIndex, referenceText, textRange, staticImport, superClass)
}
}.allReferences
}
}

class PluginYmlClassReference(
referenceSet: JavaClassReferenceSet,
referenceIndex: Int,
referenceText: String,
textRange: TextRange,
staticImport: Boolean,
val superClass: String
) : JavaClassReference(referenceSet, textRange, referenceIndex, referenceText, staticImport) {

override fun getVariants(): Array<out Any?> {
val pluginClass =
JavaPsiFacade.getInstance(element.project).findClass(superClass, element.resolveScope)
?: return ArrayUtilRt.EMPTY_OBJECT_ARRAY
val candidates =
ClassInheritorsSearch.search(pluginClass, element.resolveScope, true)
.filter { !it.hasModifier(JvmModifier.ABSTRACT) }
.map { JavaLookupElementBuilder.forClass(it, it.qualifiedName) }
.toTypedArray()
return candidates
}
}
Loading

0 comments on commit bf660ea

Please sign in to comment.