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 @@ +