⁠
joseph burton: Add support for MixinExtras expressions (#2274 )
* Start on MixinExtras Expression language
* MEExpression color settings page
* MEExpression annotator
* MEExpression brace matcher and quote handler
* Switch LHS of MEExpression assignmentExpression to themselves be certain types of expression
* MEExpression language injection inside @Expression
* Fix formatting and licenses
* Add MIXINEXTRAS:EXPRESSION injection point and add @Expression annotation on completion
* Fix licenser errors
* Add new ME expression features
* Implement MixinExtras expression collect visitor
* Fix cast expressions
* Simple best-effort source matching for ME expressions
* Fix name expression source matching
* Fix MEName.isWildcard
* Fix MELitExpression source matching
* operationSign - operationTokenType
* Add built-in definitions
* Update MixinExtras
* Start with ME definition references
* Attempt to overhaul ME expression injection
* Some fixes to the new injection + navigation
* MixinExtras: Add handler signature support for expressions. (#2244 )
* Partially fix ME definition renaming
* Attempt to get inplace rename refactoring to work (it doesn't)
* MixinExtras: Use expression-suggested parameter names if they're present. (#2257 )
* Fix MEExpressionInjector. Rename refactoring works!
* Suppress deprecation warning
* ME expression `@Definition` find usages
* Fix/expressions int like types (#2261 )
* Mixin: Combine parameter and return type inspections.
* MixinExtras: Offer a choice between all valid int-like types.
* Mixin: Fix tests for handler signature inspection.
* Add simple keyword completion to ME expressions
* Why didn't my local ktlint tell me about these
* Store whether a declaration is a type in the ME PSI
* Add completions for items that already have a definition
* Extract some ME expression matching into its own class, and cache some more things
* Remove some debug...
* Start on MixinExtras Expression language
* MEExpression color settings page
* MEExpression annotator
* MEExpression brace matcher and quote handler
* Switch LHS of MEExpression assignmentExpression to themselves be certain types of expression
* MEExpression language injection inside @Expression
* Fix formatting and licenses
* Add MIXINEXTRAS:EXPRESSION injection point and add @Expression annotation on completion
* Fix licenser errors
* Add new ME expression features
* Implement MixinExtras expression collect visitor
* Fix cast expressions
* Simple best-effort source matching for ME expressions
* Fix name expression source matching
* Fix MEName.isWildcard
* Fix MELitExpression source matching
* operationSign - operationTokenType
* Add built-in definitions
* Update MixinExtras
* Start with ME definition references
* Attempt to overhaul ME expression injection
* Some fixes to the new injection + navigation
* MixinExtras: Add handler signature support for expressions. (#2244 )
* Partially fix ME definition renaming
* Attempt to get inplace rename refactoring to work (it doesn't)
* MixinExtras: Use expression-suggested parameter names if they're present. (#2257 )
* Fix MEExpressionInjector. Rename refactoring works!
* Suppress deprecation warning
* ME expression `@Definition` find usages
* Fix/expressions int like types (#2261 )
* Mixin: Combine parameter and return type inspections.
* MixinExtras: Offer a choice between all valid int-like types.
* Mixin: Fix tests for handler signature inspection.
* Add simple keyword completion to ME expressions
* Why didn't my local ktlint tell me about these
* Store whether a declaration is a type in the ME PSI
* Add completions for items that already have a definition
* Extract some ME expression matching into its own class, and cache some more things
* Remove some debug...
- /*
- * 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.util
- import com.demonwav.mcdev.platform.mixin.reference.MixinSelector
- import com.google.gson.JsonDeserializationContext
- import com.google.gson.JsonDeserializer
- import com.google.gson.JsonElement
- import com.intellij.psi.PsiClass
- import com.intellij.psi.PsiField
- import com.intellij.psi.PsiMethod
- import java.io.Serializable
- import java.lang.reflect.Type
- /**
- * Represents a reference to a class member (a method or a field). It may
- * resolve to multiple members if [matchAllNames] or [matchAllDescs] is set or if the member is
- * not full qualified.
- */
- data class MemberReference(
- val name: String,
- val descriptor: String? = null,
- override val owner: String? = null,
- val matchAllNames: Boolean = false,
- val matchAllDescs: Boolean = false,
- ) : Serializable, MixinSelector {
- init {
- assert(owner?.contains('/') != true)
- }
- val withoutDescriptor
- get() = if (this.descriptor == null) {
- this
- } else {
- copy(descriptor = null)
- }
- val withoutOwner
- get() = if (this.owner == null) {
- this
- } else {
- copy(owner = null)
- }
- override val methodDescriptor = descriptor?.takeIf { it.contains("(") }
- override val fieldDescriptor = descriptor?.takeUnless { it.contains("(") }
- override val displayName = name
- override fun canEverMatch(name: String): Boolean {
- return matchAllNames || this.name == name
- }
- private fun matchOwner(clazz: String): Boolean {
- assert(!clazz.contains('.'))
- return this.owner == null || this.owner == clazz.replace('/', '.')
- }
- override fun matchField(owner: String, name: String, desc: String): Boolean {
- assert(!owner.contains('.'))
- return (this.matchAllNames || this.name == name) &&
- matchOwner(owner) &&
- (this.descriptor == null || this.descriptor == desc)
- }
- override fun matchMethod(owner: String, name: String, desc: String): Boolean {
- assert(!owner.contains('.'))
- return (this.matchAllNames || this.name == name) &&
- matchOwner(owner) &&
- (this.descriptor == null || this.descriptor == desc)
- }
- object Deserializer : JsonDeserializer<MemberReference> {
- override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): MemberReference {
- val ref = json.asString
- val className = ref.substringBefore('#')
- val methodName = ref.substring(className.length + 1, ref.indexOf("("))
- val methodDesc = ref.substring(className.length + methodName.length + 1)
- return MemberReference(methodName, methodDesc, className)
- }
- }
- }
- // Class
- fun PsiClass.findMethods(member: MixinSelector, checkBases: Boolean = false): Sequence<PsiMethod> {
- val methods = if (checkBases) {
- allMethods.asSequence()
- } else {
- methods.asSequence()
- } + constructors
- return methods.filter { member.matchMethod(it, this) }
- }
- fun PsiClass.findField(selector: MixinSelector, checkBases: Boolean = false): PsiField? {
- val fields = if (checkBases) {
- allFields.toList()
- } else {
- fields.toList()
- }
- return fields.firstOrNull { selector.matchField(it, this) }
- }
- // Method
- val PsiMethod.memberReference
- get() = MemberReference(internalName, descriptor)
- val PsiMethod.qualifiedMemberReference
- get() = MemberReference(internalName, descriptor, containingClass?.fullQualifiedName)
- fun PsiMethod.getQualifiedMemberReference(owner: PsiClass): MemberReference {
- return getQualifiedMemberReference(owner.fullQualifiedName)
- }
- fun PsiMethod.getQualifiedMemberReference(owner: String?): MemberReference {
- return MemberReference(internalName, descriptor, owner)
- }
- fun PsiMethod?.isSameReference(reference: PsiMethod?): Boolean =
- this != null && (this === reference || qualifiedMemberReference == reference?.qualifiedMemberReference)
- // Field
- val PsiField.simpleMemberReference
- get() = MemberReference(name)
- val PsiField.memberReference
- get() = MemberReference(name, descriptor)
- val PsiField.simpleQualifiedMemberReference
- get() = MemberReference(name, null, containingClass!!.fullQualifiedName)
- val PsiField.qualifiedMemberReference
- get() = MemberReference(name, descriptor, containingClass!!.fullQualifiedName)
- fun PsiField.getQualifiedMemberReference(owner: PsiClass): MemberReference {
- return getQualifiedMemberReference(owner.fullQualifiedName)
- }
- fun PsiField.getQualifiedMemberReference(owner: String?): MemberReference {
- return MemberReference(name, descriptor, owner)
- }
- /*
- * 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.util
- import com.demonwav.mcdev.platform.mixin.reference.MixinSelector
- import com.intellij.openapi.util.text.StringUtil
- import com.intellij.psi.PsiClass
- import com.intellij.psi.PsiField
- import com.intellij.psi.PsiMethod
- import java.io.Serializable
- import org.objectweb.asm.Type
- /**
- * Represents a reference to a class member (a method or a field). It may
- * resolve to multiple members if [matchAllNames] or [matchAllDescs] is set or if the member is
- * not full qualified.
- */
- data class MemberReference(
- val name: String,
- val descriptor: String? = null,
- override val owner: String? = null,
- val matchAllNames: Boolean = false,
- val matchAllDescs: Boolean = false,
- ) : Serializable, MixinSelector {
- init {
- assert(owner?.contains('/') != true)
- }
- val withoutDescriptor
- get() = if (this.descriptor == null) {
- this
- } else {
- copy(descriptor = null)
- }
- val withoutOwner
- get() = if (this.owner == null) {
- this
- } else {
- copy(owner = null)
- }
- override val methodDescriptor = descriptor?.takeIf { it.contains("(") }
- override val fieldDescriptor = descriptor?.takeUnless { it.contains("(") }
- override val displayName = name
- val presentableText: String get() = buildString {
- if (owner != null) {
- append(owner.substringAfterLast('.'))
- append('.')
- }
- append(name)
- if (descriptor != null && descriptor.startsWith("(")) {
- append('(')
- append(Type.getArgumentTypes(descriptor).joinToString { it.className.substringAfterLast('.') })
- append(')')
- }
- }
- override fun canEverMatch(name: String): Boolean {
- return matchAllNames || this.name == name
- }
- private fun matchOwner(clazz: String): Boolean {
- assert(!clazz.contains('.'))
- return this.owner == null || this.owner == clazz.replace('/', '.')
- }
- override fun matchField(owner: String, name: String, desc: String): Boolean {
- assert(!owner.contains('.'))
- return (this.matchAllNames || this.name == name) &&
- matchOwner(owner) &&
- (this.descriptor == null || this.descriptor == desc)
- }
- override fun matchMethod(owner: String, name: String, desc: String): Boolean {
- assert(!owner.contains('.'))
- return (this.matchAllNames || this.name == name) &&
- matchOwner(owner) &&
- (this.descriptor == null || this.descriptor == desc)
- }
- companion object {
- fun parse(value: String): MemberReference? {
- val reference = value.replace(" ", "")
- val owner: String?
- var pos = reference.lastIndexOf('.')
- if (pos != -1) {
- // Everything before the dot is the qualifier/owner
- owner = reference.substring(0, pos).replace('/', '.')
- } else {
- pos = reference.indexOf(';')
- if (pos != -1 && reference.startsWith('L')) {
- val internalOwner = reference.substring(1, pos)
- if (!StringUtil.isJavaIdentifier(internalOwner.replace('/', '_'))) {
- // Invalid: Qualifier should only contain slashes
- return null
- }
- owner = internalOwner.replace('/', '.')
- // if owner is all there is to the selector, match anything with the owner
- if (pos == reference.length - 1) {
- return MemberReference("", null, owner, matchAllNames = true, matchAllDescs = true)
- }
- } else {
- // No owner/qualifier specified
- pos = -1
- owner = null
- }
- }
- val descriptor: String?
- val name: String
- val matchAllNames = reference.getOrNull(pos + 1) == '*'
- val matchAllDescs: Boolean
- // Find descriptor separator
- val methodDescPos = reference.indexOf('(', pos + 1)
- if (methodDescPos != -1) {
- // Method descriptor
- descriptor = reference.substring(methodDescPos)
- name = reference.substring(pos + 1, methodDescPos)
- matchAllDescs = false
- } else {
- val fieldDescPos = reference.indexOf(':', pos + 1)
- if (fieldDescPos != -1) {
- descriptor = reference.substring(fieldDescPos + 1)
- name = reference.substring(pos + 1, fieldDescPos)
- matchAllDescs = false
- } else {
- descriptor = null
- matchAllDescs = reference.endsWith('*')
- name = if (matchAllDescs) {
- reference.substring(pos + 1, reference.lastIndex)
- } else {
- reference.substring(pos + 1)
- }
- }
- }
- if (!matchAllNames && !StringUtil.isJavaIdentifier(name) && name != "<init>" && name != "<clinit>") {
- return null
- }
- return MemberReference(if (matchAllNames) "*" else name, descriptor, owner, matchAllNames, matchAllDescs)
- }
- }
- }
- // Class
- fun PsiClass.findMethods(member: MixinSelector, checkBases: Boolean = false): Sequence<PsiMethod> {
- val methods = if (checkBases) {
- allMethods.asSequence()
- } else {
- methods.asSequence()
- } + constructors
- return methods.filter { member.matchMethod(it, this) }
- }
- fun PsiClass.findField(selector: MixinSelector, checkBases: Boolean = false): PsiField? {
- val fields = if (checkBases) {
- allFields.toList()
- } else {
- fields.toList()
- }
- return fields.firstOrNull { selector.matchField(it, this) }
- }
- // Method
- val PsiMethod.memberReference
- get() = MemberReference(internalName, descriptor)
- val PsiMethod.qualifiedMemberReference
- get() = MemberReference(internalName, descriptor, containingClass?.fullQualifiedName)
- fun PsiMethod.getQualifiedMemberReference(owner: PsiClass): MemberReference {
- return getQualifiedMemberReference(owner.fullQualifiedName)
- }
- fun PsiMethod.getQualifiedMemberReference(owner: String?): MemberReference {
- return MemberReference(internalName, descriptor, owner)
- }
- fun PsiMethod?.isSameReference(reference: PsiMethod?): Boolean =
- this != null && (this === reference || qualifiedMemberReference == reference?.qualifiedMemberReference)
- // Field
- val PsiField.simpleMemberReference
- get() = MemberReference(name)
- val PsiField.memberReference
- get() = MemberReference(name, descriptor)
- val PsiField.simpleQualifiedMemberReference
- get() = MemberReference(name, null, containingClass!!.fullQualifiedName)
- val PsiField.qualifiedMemberReference
- get() = MemberReference(name, descriptor, containingClass!!.fullQualifiedName)
- fun PsiField.getQualifiedMemberReference(owner: PsiClass): MemberReference {
- return getQualifiedMemberReference(owner.fullQualifiedName)
- }
- fun PsiField.getQualifiedMemberReference(owner: String?): MemberReference {
- return MemberReference(name, descriptor, owner)
- }