User: joe
Date: 23 Jan 24 19:19
Revision: 47053d6936a459a88ae0973f04b49b3965015c2b
Summary:
Add completion for At.args
TeamCity URL: https://ci.mcdev.io/viewModification.html?tab=vcsModificationFiles&modId=9045&personal=false
Index: src/main/kotlin/platform/mixin/completion/AtArgsCompletionContributor.kt
===================================================================
--- src/main/kotlin/platform/mixin/completion/AtArgsCompletionContributor.kt (revision 47053d6936a459a88ae0973f04b49b3965015c2b)
+++ src/main/kotlin/platform/mixin/completion/AtArgsCompletionContributor.kt (revision 47053d6936a459a88ae0973f04b49b3965015c2b)
@@ -0,0 +1,107 @@
+/*
+ * 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 .
+ */
+
+package com.demonwav.mcdev.platform.mixin.completion
+
+import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.InjectionPoint
+import com.demonwav.mcdev.platform.mixin.util.MixinConstants
+import com.demonwav.mcdev.util.constantStringValue
+import com.demonwav.mcdev.util.insideAnnotationAttribute
+import com.intellij.codeInsight.completion.CompletionContributor
+import com.intellij.codeInsight.completion.CompletionParameters
+import com.intellij.codeInsight.completion.CompletionProvider
+import com.intellij.codeInsight.completion.CompletionResultSet
+import com.intellij.codeInsight.completion.CompletionType
+import com.intellij.codeInsight.lookup.LookupElement
+import com.intellij.codeInsight.lookup.LookupElementBuilder
+import com.intellij.openapi.util.TextRange
+import com.intellij.patterns.PsiJavaPatterns
+import com.intellij.patterns.StandardPatterns
+import com.intellij.psi.JavaTokenType
+import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.PsiLanguageInjectionHost
+import com.intellij.psi.PsiLiteral
+import com.intellij.psi.PsiNamedElement
+import com.intellij.psi.util.parentOfType
+import com.intellij.util.ProcessingContext
+
+class AtArgsCompletionContributor : CompletionContributor() {
+ init {
+ for (tokenType in arrayOf(JavaTokenType.STRING_LITERAL, JavaTokenType.TEXT_BLOCK_LITERAL)) {
+ extend(
+ CompletionType.BASIC,
+ PsiJavaPatterns.psiElement(tokenType).withParent(
+ PsiJavaPatterns.psiLiteral(StandardPatterns.string())
+ .insideAnnotationAttribute(MixinConstants.Annotations.AT, "args")
+ ),
+ Provider,
+ )
+ }
+ }
+
+ object Provider : CompletionProvider() {
+ override fun addCompletions(
+ parameters: CompletionParameters,
+ context: ProcessingContext,
+ result: CompletionResultSet
+ ) {
+ val literal = parameters.position.parentOfType(withSelf = true) ?: return
+ val atAnnotation = literal.parentOfType() ?: return
+ val atCode = atAnnotation.findDeclaredAttributeValue("value")?.constantStringValue ?: return
+ val injectionPoint = InjectionPoint.byAtCode(atCode) ?: return
+ val escaper = (literal as? PsiLanguageInjectionHost)?.createLiteralTextEscaper() ?: return
+ val beforeCursor = buildString {
+ escaper.decode(TextRange(1, parameters.offset - (literal as PsiLiteral).textRange.startOffset), this)
+ }
+ val equalsIndex = beforeCursor.indexOf('=')
+ if (equalsIndex == -1) {
+ val argsKeys = injectionPoint.getArgsKeys(atAnnotation)
+ result.addAllElements(
+ argsKeys.map { completion ->
+ LookupElementBuilder.create(if (completion.contains('=')) completion else "$completion=")
+ .withPresentableText(completion)
+ }
+ )
+ if (argsKeys.isNotEmpty()) {
+ result.stopHere()
+ }
+ } else {
+ val key = beforeCursor.substring(0, equalsIndex)
+ val argsValues = injectionPoint.getArgsValues(atAnnotation, key)
+ var prefix = beforeCursor.substring(equalsIndex + 1)
+ if (injectionPoint.isArgValueList(atAnnotation, key)) {
+ prefix = prefix.substringAfterLast(',').trimStart()
+ }
+ result.withPrefixMatcher(prefix).addAllElements(
+ argsValues.map { completion ->
+ when (completion) {
+ is LookupElement -> completion
+ is PsiNamedElement -> LookupElementBuilder.create(completion)
+ else -> LookupElementBuilder.create(completion)
+ }
+ }
+ )
+ if (argsValues.isNotEmpty()) {
+ result.stopHere()
+ }
+ }
+ }
+ }
+}
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt (revision 612b136916ea6be2a1b264f01a324ef5178de675)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt (revision 47053d6936a459a88ae0973f04b49b3965015c2b)
@@ -37,11 +37,13 @@
import com.intellij.psi.PsiClassType
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiExpression
+import com.intellij.psi.PsiModifierList
import com.intellij.psi.PsiQualifiedReference
import com.intellij.psi.PsiReference
import com.intellij.psi.PsiReferenceExpression
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.parentOfType
+import com.intellij.psi.util.parents
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.MethodNode
@@ -132,7 +134,14 @@
else -> constant.toString()
}
}
+
+ fun findInjectorAnnotation(at: PsiAnnotation): PsiAnnotation? {
+ return at.parents(false)
+ .takeWhile { it !is PsiClass }
+ .filterIsInstance()
+ .firstOrNull { it.parent is PsiModifierList }
- }
+ }
+ }
fun isUnresolved(): InsnResolutionInfo.Failure? {
val injectionPoint = getInjectionPoint(at)
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantInjectionPoint.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantInjectionPoint.kt (revision 612b136916ea6be2a1b264f01a324ef5178de675)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantInjectionPoint.kt (revision 47053d6936a459a88ae0973f04b49b3965015c2b)
@@ -20,14 +20,20 @@
package com.demonwav.mcdev.platform.mixin.handlers.injectionPoint
+import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler
import com.demonwav.mcdev.platform.mixin.reference.MixinSelector
+import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember
import com.demonwav.mcdev.util.constantValue
import com.demonwav.mcdev.util.createLiteralExpression
import com.demonwav.mcdev.util.descriptor
+import com.demonwav.mcdev.util.enumValueOfOrNull
import com.demonwav.mcdev.util.ifNotBlank
+import com.demonwav.mcdev.util.mapToArray
+import com.demonwav.mcdev.util.toTypedArray
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
+import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.CommonClassNames
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.JavaTokenType
@@ -43,9 +49,11 @@
import com.intellij.psi.PsiLiteralExpression
import com.intellij.psi.PsiSwitchLabelStatementBase
import com.intellij.psi.util.PsiUtil
+import com.intellij.util.ArrayUtilRt
import java.util.Locale
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
+import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FrameNode
import org.objectweb.asm.tree.InsnNode
@@ -57,10 +65,71 @@
import org.objectweb.asm.tree.TypeInsnNode
class ConstantInjectionPoint : InjectionPoint() {
+ companion object {
+ private val ARGS_KEYS = arrayOf(
+ "nullValue=true",
+ "intValue",
+ "floatValue",
+ "longValue",
+ "doubleValue",
+ "stringValue",
+ "classValue",
+ "expandZeroConditions"
+ )
+ }
+
override fun onCompleted(editor: Editor, reference: PsiLiteral) {
completeExtraStringAtAttribute(editor, reference, "args")
}
+ override fun getArgsKeys(at: PsiAnnotation) = ARGS_KEYS
+
+ override fun getArgsValues(at: PsiAnnotation, key: String): Array {
+ fun collectTargets(constantToCompletion: (Any) -> Any?): Array {
+ val injectorAnnotation = AtResolver.findInjectorAnnotation(at) ?: return ArrayUtilRt.EMPTY_OBJECT_ARRAY
+ val handler = injectorAnnotation.qualifiedName
+ ?.let { MixinAnnotationHandler.forMixinAnnotation(it, at.project) }
+ ?: return ArrayUtilRt.EMPTY_OBJECT_ARRAY
+
+ val expandConditions = parseExpandConditions(AtResolver.getArgs(at))
+
+ return handler.resolveTarget(injectorAnnotation)
+ .asSequence()
+ .filterIsInstance()
+ .flatMap { target ->
+ target.classAndMethod.method.instructions?.let { insns ->
+ Iterable { insns.iterator() }.asSequence()
+ .mapNotNull { it.computeConstantValue(expandConditions) }
+ .mapNotNull(constantToCompletion)
+ } ?: emptySequence()
+ }
+ .toTypedArray()
+ }
+
+ return when (key) {
+ "expandZeroConditions" -> ExpandCondition.values().mapToArray { it.name.lowercase(Locale.ROOT) }
+ "intValue" -> collectTargets { cst -> cst.takeIf { it is Int } }
+ "floatValue" -> collectTargets { cst -> cst.takeIf { it is Float } }
+ "longValue" -> collectTargets { cst -> cst.takeIf { it is Long } }
+ "doubleValue" -> collectTargets { cst -> cst.takeIf { it is Double } }
+ "stringValue" -> collectTargets { cst ->
+ (cst as? String)?.let { str ->
+ val escapedStr = StringUtil.escapeStringCharacters(str)
+ when {
+ str.isEmpty() -> null
+ escapedStr.trim() != escapedStr -> LookupElementBuilder.create(escapedStr)
+ .withPresentableText("\"${escapedStr}\"")
+ else -> escapedStr
+ }
+ }
+ }
+ "classValue" -> collectTargets { cst -> (cst as? Type)?.internalName }
+ else -> ArrayUtilRt.EMPTY_OBJECT_ARRAY
+ }
+ }
+
+ override fun isArgValueList(at: PsiAnnotation, key: String) = key == "expandZeroConditions"
+
fun getConstantInfo(at: PsiAnnotation): ConstantInfo? {
val args = AtResolver.getArgs(at)
val nullValue = args["nullValue"]?.let(java.lang.Boolean::parseBoolean) ?: false
@@ -88,19 +157,15 @@
intValue ?: floatValue ?: longValue ?: doubleValue ?: stringValue ?: classValue!!
}
- val expandConditions = args["expandZeroConditions"]
+ return ConstantInfo(constant, parseExpandConditions(args))
+ }
+
+ private fun parseExpandConditions(args: Map): Set {
+ return args["expandZeroConditions"]
?.replace(" ", "")
?.split(',')
- ?.mapNotNull {
- try {
- ExpandCondition.valueOf(it.uppercase(Locale.ENGLISH))
- } catch (e: IllegalArgumentException) {
- null
- }
- }
+ ?.mapNotNull { enumValueOfOrNull(it.uppercase(Locale.ROOT)) }
?.toSet() ?: emptySet()
-
- return ConstantInfo(constant, expandConditions)
}
private fun Boolean.toInt(): Int {
@@ -248,60 +313,11 @@
) : CollectVisitor(mode) {
override fun accept(methodNode: MethodNode) {
methodNode.instructions?.iterator()?.forEachRemaining { insn ->
- val constant = when (insn) {
- is InsnNode -> when (insn.opcode) {
- in Opcodes.ICONST_M1..Opcodes.ICONST_5 -> insn.opcode - Opcodes.ICONST_0
- Opcodes.LCONST_0 -> 0L
- Opcodes.LCONST_1 -> 1L
- Opcodes.FCONST_0 -> 0.0f
- Opcodes.FCONST_1 -> 1.0f
- Opcodes.FCONST_2 -> 2.0f
- Opcodes.DCONST_0 -> 0.0
- Opcodes.DCONST_1 -> 1.0
- Opcodes.ACONST_NULL -> null
- else -> return@forEachRemaining
- }
+ val constant = (
+ insn.computeConstantValue(constantInfo?.expandConditions ?: emptySet())
+ ?: return@forEachRemaining
+ ).let { if (it is NullSentinel) null else it }
- is IntInsnNode -> when (insn.opcode) {
- Opcodes.BIPUSH, Opcodes.SIPUSH -> insn.operand
- else -> return@forEachRemaining
- }
-
- is LdcInsnNode -> insn.cst
- is JumpInsnNode -> {
- if (constantInfo == null || !constantInfo.expandConditions.any { insn.opcode in it.opcodes }) {
- return@forEachRemaining
- }
- var lastInsn = insn.previous
- while (lastInsn != null && (lastInsn is LabelNode || lastInsn is FrameNode)) {
- lastInsn = lastInsn.previous
- }
- if (lastInsn != null) {
- val lastOpcode = lastInsn.opcode
- if (lastOpcode == Opcodes.LCMP ||
- lastOpcode == Opcodes.FCMPL ||
- lastOpcode == Opcodes.FCMPG ||
- lastOpcode == Opcodes.DCMPL ||
- lastOpcode == Opcodes.DCMPG
- ) {
- return@forEachRemaining
- }
- }
- 0
- }
-
- is TypeInsnNode -> {
- if (insn.opcode < Opcodes.CHECKCAST) {
- // Don't treat NEW and ANEWARRAY as constants
- // Matches Mixin's handling
- return@forEachRemaining
- }
- Type.getObjectType(insn.desc)
- }
-
- else -> return@forEachRemaining
- }
-
if (constantInfo != null && constant != constantInfo.constant) {
return@forEachRemaining
}
@@ -344,3 +360,61 @@
}
}
}
+
+private object NullSentinel
+
+private fun AbstractInsnNode.computeConstantValue(expandConditions: Set): Any? {
+ return when (this) {
+ is InsnNode -> when (opcode) {
+ in Opcodes.ICONST_M1..Opcodes.ICONST_5 -> opcode - Opcodes.ICONST_0
+ Opcodes.LCONST_0 -> 0L
+ Opcodes.LCONST_1 -> 1L
+ Opcodes.FCONST_0 -> 0.0f
+ Opcodes.FCONST_1 -> 1.0f
+ Opcodes.FCONST_2 -> 2.0f
+ Opcodes.DCONST_0 -> 0.0
+ Opcodes.DCONST_1 -> 1.0
+ Opcodes.ACONST_NULL -> NullSentinel
+ else -> null
+ }
+
+ is IntInsnNode -> when (opcode) {
+ Opcodes.BIPUSH, Opcodes.SIPUSH -> operand
+ else -> null
+ }
+
+ is LdcInsnNode -> cst
+ is JumpInsnNode -> {
+ if (expandConditions.none { opcode in it.opcodes }) {
+ return null
+ }
+ var lastInsn = previous
+ while (lastInsn != null && (lastInsn is LabelNode || lastInsn is FrameNode)) {
+ lastInsn = lastInsn.previous
+ }
+ if (lastInsn != null) {
+ val lastOpcode = lastInsn.opcode
+ if (lastOpcode == Opcodes.LCMP ||
+ lastOpcode == Opcodes.FCMPL ||
+ lastOpcode == Opcodes.FCMPG ||
+ lastOpcode == Opcodes.DCMPL ||
+ lastOpcode == Opcodes.DCMPG
+ ) {
+ return null
+ }
+ }
+ 0
+ }
+
+ is TypeInsnNode -> {
+ if (opcode < Opcodes.CHECKCAST) {
+ // Don't treat NEW and ANEWARRAY as constants
+ // Matches Mixin's handling
+ return null
+ }
+ Type.getObjectType(desc)
+ }
+
+ else -> null
+ }
+}
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantStringMethodInjectionPoint.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantStringMethodInjectionPoint.kt (revision 612b136916ea6be2a1b264f01a324ef5178de675)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantStringMethodInjectionPoint.kt (revision 47053d6936a459a88ae0973f04b49b3965015c2b)
@@ -20,24 +20,117 @@
package com.demonwav.mcdev.platform.mixin.handlers.injectionPoint
+import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler
import com.demonwav.mcdev.platform.mixin.reference.MixinSelector
+import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember
import com.demonwav.mcdev.platform.mixin.util.fakeResolve
import com.demonwav.mcdev.platform.mixin.util.findOrConstructSourceMethod
import com.demonwav.mcdev.util.MemberReference
import com.demonwav.mcdev.util.constantStringValue
+import com.demonwav.mcdev.util.createLiteralExpression
+import com.demonwav.mcdev.util.toTypedArray
+import com.intellij.codeInsight.lookup.LookupElementBuilder
+import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
+import com.intellij.openapi.util.text.StringUtil
+import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiAnnotation
import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiLiteral
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiType
import com.intellij.psi.PsiTypes
+import com.intellij.psi.codeStyle.CodeStyleManager
+import com.intellij.psi.util.parentOfType
+import com.intellij.util.ArrayUtilRt
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.LdcInsnNode
import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.MethodNode
class ConstantStringMethodInjectionPoint : AbstractMethodInjectionPoint() {
+ companion object {
+ private val ARGS_KEYS = arrayOf("ldc")
+ }
+
+ override fun onCompleted(editor: Editor, reference: PsiLiteral) {
+ val at = reference.parentOfType() ?: return
+ var setCursorTo: String? = null
+
+ if (at.findDeclaredAttributeValue("target") == null) {
+ at.setDeclaredAttributeValue(
+ "target",
+ JavaPsiFacade.getElementFactory(reference.project).createLiteralExpression(""),
+ )
+ setCursorTo = "target"
+ }
+
+ if (at.findDeclaredAttributeValue("args") == null) {
+ at.setDeclaredAttributeValue(
+ "args",
+ JavaPsiFacade.getElementFactory(reference.project).createLiteralExpression("ldc="),
+ )
+ if (setCursorTo == null) {
+ setCursorTo = "args"
+ }
+ }
+
+ if (setCursorTo == null) {
+ return
+ }
+
+ CodeStyleManager.getInstance(reference.project).reformat(at.parameterList)
+
+ val cursorElement = at.findDeclaredAttributeValue(setCursorTo) ?: return
+ editor.caretModel.moveToOffset(cursorElement.textRange.endOffset - 1)
+ }
+
+ override fun getArgsKeys(at: PsiAnnotation) = ARGS_KEYS
+
+ override fun getArgsValues(at: PsiAnnotation, key: String): Array {
+ if (key != "ldc") {
+ return ArrayUtilRt.EMPTY_OBJECT_ARRAY
+ }
+
+ val injectorAnnotation = AtResolver.findInjectorAnnotation(at) ?: return ArrayUtilRt.EMPTY_OBJECT_ARRAY
+ val handler = injectorAnnotation.qualifiedName
+ ?.let { MixinAnnotationHandler.forMixinAnnotation(it, at.project) }
+ ?: return ArrayUtilRt.EMPTY_OBJECT_ARRAY
+
+ return handler.resolveTarget(injectorAnnotation).asSequence()
+ .filterIsInstance()
+ .flatMap { target ->
+ val insns = target.classAndMethod.method.instructions ?: return@flatMap emptySequence()
+ var lastSeenString: String? = null
+ val completionOptions = mutableSetOf()
+ insns.iterator().forEachRemaining { insn ->
+ val lastSeenStr = lastSeenString
+ if (lastSeenStr != null && insn is MethodInsnNode && insn.desc == "(Ljava/lang/String;)V") {
+ if (lastSeenStr.isNotEmpty()) {
+ val escaped = StringUtil.escapeStringCharacters(lastSeenStr)
+ completionOptions += if (escaped.trim() != escaped) {
+ LookupElementBuilder.create(escaped)
+ .withPresentableText("\"$escaped\"")
+ } else {
+ escaped
+ }
+ }
+ }
+
+ val cst = (insn as? LdcInsnNode)?.cst
+ if (cst is String) {
+ lastSeenString = cst
+ } else if (insn.opcode != -1) {
+ lastSeenString = null
+ }
+ }
+
+ completionOptions.asSequence()
+ }
+ .toTypedArray()
+ }
+
override fun createNavigationVisitor(
at: PsiAnnotation,
target: MixinSelector?,
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/CtorHeadInjectionPoint.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/CtorHeadInjectionPoint.kt (revision 612b136916ea6be2a1b264f01a324ef5178de675)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/CtorHeadInjectionPoint.kt (revision 47053d6936a459a88ae0973f04b49b3965015c2b)
@@ -30,6 +30,7 @@
import com.demonwav.mcdev.util.enumValueOfOrNull
import com.demonwav.mcdev.util.findContainingClass
import com.demonwav.mcdev.util.findInspection
+import com.demonwav.mcdev.util.mapToArray
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
@@ -48,6 +49,7 @@
import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.util.PsiUtil
import com.intellij.psi.util.parentOfType
+import com.intellij.util.ArrayUtilRt
import com.intellij.util.JavaPsiConstructorUtil
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
@@ -55,6 +57,10 @@
import org.objectweb.asm.tree.MethodNode
class CtorHeadInjectionPoint : InjectionPoint() {
+ companion object {
+ private val ARGS_KEYS = arrayOf("enforce")
+ }
+
override fun onCompleted(editor: Editor, reference: PsiLiteral) {
val project = reference.project
@@ -73,6 +79,13 @@
CodeStyleManager.getInstance(project).reformat(at)
}
+ override fun getArgsKeys(at: PsiAnnotation) = ARGS_KEYS
+ override fun getArgsValues(at: PsiAnnotation, key: String): Array = if (key == "enforce") {
+ EnforceMode.values().mapToArray { it.name }
+ } else {
+ ArrayUtilRt.EMPTY_OBJECT_ARRAY
+ }
+
override fun createNavigationVisitor(
at: PsiAnnotation,
target: MixinSelector?,
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/FieldInjectionPoint.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/FieldInjectionPoint.kt (revision 612b136916ea6be2a1b264f01a324ef5178de675)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/FieldInjectionPoint.kt (revision 47053d6936a459a88ae0973f04b49b3965015c2b)
@@ -40,6 +40,7 @@
import com.intellij.psi.PsiModifier
import com.intellij.psi.PsiReferenceExpression
import com.intellij.psi.util.PsiUtil
+import com.intellij.util.ArrayUtilRt
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.objectweb.asm.tree.AbstractInsnNode
@@ -50,12 +51,19 @@
class FieldInjectionPoint : QualifiedInjectionPoint() {
companion object {
private val VALID_OPCODES = setOf(Opcodes.GETFIELD, Opcodes.GETSTATIC, Opcodes.PUTFIELD, Opcodes.PUTSTATIC)
+ private val ARGS_KEYS = arrayOf("array")
+ private val ARRAY_VALUES = arrayOf("length", "get", "set")
}
override fun onCompleted(editor: Editor, reference: PsiLiteral) {
completeExtraStringAtAttribute(editor, reference, "target")
}
+ override fun getArgsKeys(at: PsiAnnotation) = ARGS_KEYS
+
+ override fun getArgsValues(at: PsiAnnotation, key: String): Array =
+ ARRAY_VALUES.takeIf { key == "array" } ?: ArrayUtilRt.EMPTY_OBJECT_ARRAY
+
private fun getArrayAccessType(args: Map): ArrayAccessType? {
return when (args["array"]) {
"length" -> ArrayAccessType.LENGTH
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt (revision 612b136916ea6be2a1b264f01a324ef5178de675)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt (revision 47053d6936a459a88ae0973f04b49b3965015c2b)
@@ -60,6 +60,7 @@
import com.intellij.psi.util.PsiUtil
import com.intellij.psi.util.parentOfType
import com.intellij.serviceContainer.BaseKeyedLazyInstance
+import com.intellij.util.ArrayUtilRt
import com.intellij.util.KeyedLazyInstance
import com.intellij.util.xmlb.annotations.Attribute
import org.objectweb.asm.tree.AbstractInsnNode
@@ -97,6 +98,16 @@
editor.caretModel.moveToOffset(targetElement.textRange.startOffset + 1)
}
+ open fun getArgsKeys(at: PsiAnnotation): Array {
+ return ArrayUtilRt.EMPTY_STRING_ARRAY
+ }
+
+ open fun getArgsValues(at: PsiAnnotation, key: String): Array {
+ return ArrayUtilRt.EMPTY_OBJECT_ARRAY
+ }
+
+ open fun isArgValueList(at: PsiAnnotation, key: String) = false
+
abstract fun createNavigationVisitor(
at: PsiAnnotation,
target: MixinSelector?,
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/NewInsnInjectionPoint.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/NewInsnInjectionPoint.kt (revision 612b136916ea6be2a1b264f01a324ef5178de675)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/NewInsnInjectionPoint.kt (revision 47053d6936a459a88ae0973f04b49b3965015c2b)
@@ -20,8 +20,10 @@
package com.demonwav.mcdev.platform.mixin.handlers.injectionPoint
+import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler
import com.demonwav.mcdev.platform.mixin.reference.MixinSelector
import com.demonwav.mcdev.platform.mixin.reference.MixinSelectorParser
+import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember
import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.AT
import com.demonwav.mcdev.platform.mixin.util.findClassNodeByPsiClass
import com.demonwav.mcdev.platform.mixin.util.findMethod
@@ -32,18 +34,22 @@
import com.demonwav.mcdev.util.fullQualifiedName
import com.demonwav.mcdev.util.internalName
import com.demonwav.mcdev.util.shortName
+import com.demonwav.mcdev.util.toTypedArray
import com.intellij.codeInsight.completion.JavaLookupElementBuilder
import com.intellij.codeInsight.lookup.LookupElementBuilder
+import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiAnnotation
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiLiteral
import com.intellij.psi.PsiMember
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiNewExpression
import com.intellij.psi.PsiSubstitutor
import com.intellij.psi.util.parentOfType
+import com.intellij.util.ArrayUtilRt
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.ClassNode
@@ -52,6 +58,32 @@
import org.objectweb.asm.tree.TypeInsnNode
class NewInsnInjectionPoint : InjectionPoint() {
+ override fun onCompleted(editor: Editor, reference: PsiLiteral) {
+ completeExtraStringAtAttribute(editor, reference, "target")
+ }
+
+ override fun getArgsKeys(at: PsiAnnotation) = ARGS_KEYS
+
+ override fun getArgsValues(at: PsiAnnotation, key: String): Array {
+ if (key != "class") {
+ return ArrayUtilRt.EMPTY_OBJECT_ARRAY
+ }
+
+ val injectorAnnotation = AtResolver.findInjectorAnnotation(at) ?: return ArrayUtilRt.EMPTY_OBJECT_ARRAY
+ val handler = injectorAnnotation.qualifiedName
+ ?.let { MixinAnnotationHandler.forMixinAnnotation(it, at.project) }
+ ?: return ArrayUtilRt.EMPTY_OBJECT_ARRAY
+
+ return handler.resolveTarget(injectorAnnotation).asSequence()
+ .filterIsInstance()
+ .flatMap { target ->
+ target.classAndMethod.method.instructions?.asSequence()?.mapNotNull { insn ->
+ (insn as? TypeInsnNode)?.desc?.takeIf { insn.opcode == Opcodes.NEW }
+ } ?: emptySequence()
+ }
+ .toTypedArray()
+ }
+
private fun getTarget(at: PsiAnnotation, target: MixinSelector?): MixinSelector? {
if (target != null) {
return target
@@ -151,6 +183,8 @@
}
companion object {
+ private val ARGS_KEYS = arrayOf("class")
+
fun findInitCall(newInsn: TypeInsnNode): MethodInsnNode? {
var newInsns = 0
var insn: AbstractInsnNode? = newInsn
Index: src/main/kotlin/platform/mixin/inspection/injector/CtorHeadPostInitInspection.kt
===================================================================
--- src/main/kotlin/platform/mixin/inspection/injector/CtorHeadPostInitInspection.kt (revision 612b136916ea6be2a1b264f01a324ef5178de675)
+++ src/main/kotlin/platform/mixin/inspection/injector/CtorHeadPostInitInspection.kt (revision 47053d6936a459a88ae0973f04b49b3965015c2b)
@@ -33,10 +33,7 @@
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.JavaElementVisitor
import com.intellij.psi.PsiAnnotation
-import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElementVisitor
-import com.intellij.psi.PsiModifierList
-import com.intellij.psi.util.parents
import org.objectweb.asm.Opcodes
class CtorHeadPostInitInspection : MixinInspection() {
@@ -59,11 +56,7 @@
return
}
- val injectorAnnotation = annotation.parents(false)
- .takeWhile { it !is PsiClass }
- .filterIsInstance()
- .firstOrNull { it.parent is PsiModifierList }
- ?: return
+ val injectorAnnotation = AtResolver.findInjectorAnnotation(annotation) ?: return
val handler = injectorAnnotation.qualifiedName
?.let { MixinAnnotationHandler.forMixinAnnotation(it, holder.project) }
?: return
Index: src/main/kotlin/platform/mixin/inspection/injector/UnnecessaryUnsafeInspection.kt
===================================================================
--- src/main/kotlin/platform/mixin/inspection/injector/UnnecessaryUnsafeInspection.kt (revision 612b136916ea6be2a1b264f01a324ef5178de675)
+++ src/main/kotlin/platform/mixin/inspection/injector/UnnecessaryUnsafeInspection.kt (revision 47053d6936a459a88ae0973f04b49b3965015c2b)
@@ -22,6 +22,7 @@
import com.demonwav.mcdev.platform.fabric.util.isFabric
import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler
+import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.AtResolver
import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection
import com.demonwav.mcdev.platform.mixin.inspection.fix.AnnotationAttributeFix
import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember
@@ -34,10 +35,7 @@
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaElementVisitor
import com.intellij.psi.PsiAnnotation
-import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElementVisitor
-import com.intellij.psi.PsiModifierList
-import com.intellij.psi.util.parents
import java.awt.FlowLayout
import javax.swing.JCheckBox
import javax.swing.JComponent
@@ -96,11 +94,7 @@
companion object {
fun mightTargetConstructor(project: Project, at: PsiAnnotation): Boolean {
- val injectorAnnotation = at.parents(false)
- .takeWhile { it !is PsiClass }
- .filterIsInstance()
- .firstOrNull { it.parent is PsiModifierList }
- ?: return true
+ val injectorAnnotation = AtResolver.findInjectorAnnotation(at) ?: return true
val handler = injectorAnnotation.qualifiedName?.let {
MixinAnnotationHandler.forMixinAnnotation(it, project)
} ?: return true
Index: src/main/resources/META-INF/plugin.xml
===================================================================
--- src/main/resources/META-INF/plugin.xml (revision 612b136916ea6be2a1b264f01a324ef5178de675)
+++ src/main/resources/META-INF/plugin.xml (revision 47053d6936a459a88ae0973f04b49b3965015c2b)
@@ -431,6 +431,8 @@
+