Skip to content

Commit 4fd58b4

Browse files
committed
Merge branch '2024.2' into 2024.3
2 parents 4cd5d91 + 215cf26 commit 4fd58b4

File tree

60 files changed

+3211
-476
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+3211
-476
lines changed

.github/workflows/test.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,9 @@ jobs:
3838
echo -e '\norg.gradle.jvmargs=-Xmx4G\n' >> ~/.gradle/gradle.properties
3939
- name: Build
4040
run: ./gradlew build --stacktrace
41+
- name: Upload test reports
42+
if: always()
43+
uses: actions/upload-artifact@v4
44+
with:
45+
name: test-reports-${{ matrix.os }}
46+
path: build/reports

build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ dependencies {
122122
pluginVerifier()
123123
}
124124

125-
testLibs(libs.test.mockJdk)
126125
testLibs(libs.test.mixin)
127126
testLibs(libs.test.spigotapi)
128127
testLibs(libs.test.bungeecord)

changelog.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,30 @@
11
# Minecraft Development for IntelliJ
22

3+
## [1.8.5]
4+
5+
### Added
6+
7+
- Support injection point specifiers everywhere
8+
- Support for the 2025.2 EAP
9+
10+
### Changed
11+
12+
- Demote a couple of mixin inspections from error to warning
13+
- Allow handler methods to be static even when not required, except for `@Inject` on stock mixin
14+
- Allow `CallbackInfoReturnable.cancel()` in MixinCancellableInspection
15+
- Check bases for `CIR.cancel()`
16+
- Allow `.aw` as alternative access widener file extension
17+
- Remove shadow completions explicit proximity
18+
19+
### Fixed
20+
21+
- [#2148](https://github.com/minecraft-dev/MinecraftDev/issues/2148) Allow assignment to `@Final` fields in class initializer blocks
22+
- [#2468](https://github.com/minecraft-dev/MinecraftDev/issues/2468) entrypoint checks on non-ending parts of entrypoint references, and handle escapes in entrypoint reference strings
23+
- [#2470](https://github.com/minecraft-dev/MinecraftDev/issues/2470) Various Mixin fixes
24+
- `@Shadow` suggestions being incorrectly prioritized
25+
- `NoSuchElementException` in MixinAnnotationTargetInspection
26+
- `IllegalStateException` when typing a colon in Java code
27+
328
## [1.8.4]
429

530
### Added

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ org.gradle.jvmargs=-Xmx1g
2323

2424
ideaVersionName = 2024.3
2525

26-
coreVersion = 1.8.4
26+
coreVersion = 1.8.5
2727

2828
# Silences a build-time warning because we are bundling our own kotlin library
2929
kotlin.stdlib.default.dependency = false

gradle/libs.versions.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ fuel = { module = "com.github.kittinunf.fuel:fuel", version.ref = "fuel" }
5757
fuel-coroutines = { module = "com.github.kittinunf.fuel:fuel-coroutines", version.ref = "fuel" }
5858

5959
# Testing
60-
test-mockJdk = "org.jetbrains.idea:mock-jdk:1.7-4d76c50"
6160
test-mixin = "org.spongepowered:mixin:0.8.5"
6261
test-spigotapi = "org.spigotmc:spigot-api:1.21-R0.1-SNAPSHOT"
6362
test-bungeecord = "net.md-5:bungeecord-api:1.21-R0.3-SNAPSHOT"

readme.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ Minecraft Development for IntelliJ
2323
<td align="left">2025.1</td>
2424
<td align="left"><a href="https://ci.mcdev.io/viewType.html?buildTypeId=MinecraftDev_Nightly_20251"><img src="https://ci.mcdev.io/app/rest/builds/buildType:(id:MinecraftDev_Nightly_20251)/statusIcon.svg" alt="2025.1 Nightly Status" /></a></td>
2525
</tr>
26+
<tr>
27+
<td align="left">2025.2</td>
28+
<td align="left"><a href="https://ci.mcdev.io/viewType.html?buildTypeId=MinecraftDev_Nightly_20252"><img src="https://ci.mcdev.io/app/rest/builds/buildType:(id:MinecraftDev_Nightly_20252)/statusIcon.svg" alt="2025.2 Nightly Status" /></a></td>
29+
</tr>
2630
<tr>
2731
<td align="right"><b>OS Tests</b></td>
2832
<td align="left" colspan="2">
@@ -31,7 +35,7 @@ Minecraft Development for IntelliJ
3135
</tr>
3236
</table>
3337

34-
Info and Documentation [![Current Release](https://img.shields.io/badge/release-1.8.4-orange.svg?style=flat-square)](https://plugins.jetbrains.com/plugin/8327)
38+
Info and Documentation [![Current Release](https://img.shields.io/badge/release-1.8.5-orange.svg?style=flat-square)](https://plugins.jetbrains.com/plugin/8327)
3539
----------------------
3640

3741
<a href="https://discord.gg/j6UNcfr"><img src="https://i.imgur.com/JXu9C1G.png" height="48px"></img></a>

src/main/kotlin/facet/MinecraftFacetDetector.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import com.intellij.facet.FacetManager
3232
import com.intellij.facet.impl.ui.libraries.LibrariesValidatorContextImpl
3333
import com.intellij.framework.library.LibraryVersionProperties
3434
import com.intellij.openapi.application.EDT
35+
import com.intellij.openapi.application.readAction
3536
import com.intellij.openapi.components.Service
3637
import com.intellij.openapi.components.service
3738
import com.intellij.openapi.module.Module
@@ -132,8 +133,14 @@ class MinecraftFacetDetector : ProjectActivity {
132133
}
133134
}
134135

135-
private fun checkNoFacet(module: Module) {
136-
val platforms = autoDetectTypes(module).ifEmpty { return }
136+
private suspend fun checkNoFacet(module: Module) {
137+
val platforms = readAction {
138+
if (!module.isDisposed) {
139+
autoDetectTypes(module)
140+
} else {
141+
emptyList()
142+
}
143+
}.ifEmpty { return }
137144

138145
runWriteTaskLater {
139146
// Only add the new facet if there isn't a Minecraft facet already - double check here since this

src/main/kotlin/platform/fabric/inspection/FabricEntrypointsInspection.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ class FabricEntrypointsInspection : LocalInspectionTool() {
7979
)
8080
}
8181

82+
if (!reference.isFinalPart) {
83+
continue
84+
}
85+
8286
val element = resolved.singleOrNull()?.element
8387
when {
8488
element is PsiClass && !literal.text.contains("::") -> {

src/main/kotlin/platform/fabric/reference/EntryPointReference.kt

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import com.intellij.psi.PsiClassType
3535
import com.intellij.psi.PsiElement
3636
import com.intellij.psi.PsiElementResolveResult
3737
import com.intellij.psi.PsiField
38+
import com.intellij.psi.PsiLanguageInjectionHost
3839
import com.intellij.psi.PsiMethod
3940
import com.intellij.psi.PsiModifier
4041
import com.intellij.psi.PsiPolyVariantReference
@@ -51,12 +52,17 @@ import com.intellij.util.ProcessingContext
5152

5253
object EntryPointReference : PsiReferenceProvider() {
5354
override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array<PsiReference> {
54-
if (element !is JsonStringLiteral) {
55+
if (element !is JsonStringLiteral || element !is PsiLanguageInjectionHost) {
5556
return PsiReference.EMPTY_ARRAY
5657
}
5758
val manipulator = element.manipulator ?: return PsiReference.EMPTY_ARRAY
5859
val range = manipulator.getRangeInElement(element)
59-
val text = element.text.substring(range.startOffset, range.endOffset)
60+
val escaper = element.createLiteralTextEscaper()
61+
val text = buildString {
62+
if (!escaper.decode(range, this)) {
63+
return PsiReference.EMPTY_ARRAY
64+
}
65+
}
6066
val memberParts = text.split("::", limit = 2)
6167
val clazzParts = memberParts[0].split("$", limit = 0)
6268
val references = mutableListOf<Reference>()
@@ -68,7 +74,10 @@ object EntryPointReference : PsiReferenceProvider() {
6874
references.add(
6975
Reference(
7076
element,
71-
range.cutOut(TextRange.from(cursor, clazzPart.length)),
77+
TextRange.create(
78+
escaper.getOffsetInHost(cursor, range),
79+
escaper.getOffsetInHost(cursor + clazzPart.length, range)
80+
),
7281
innerClassDepth,
7382
false,
7483
),
@@ -80,12 +89,16 @@ object EntryPointReference : PsiReferenceProvider() {
8089
references.add(
8190
Reference(
8291
element,
83-
range.cutOut(TextRange.from(cursor, memberParts[1].length)),
92+
TextRange.create(
93+
escaper.getOffsetInHost(cursor, range),
94+
escaper.getOffsetInHost(cursor + memberParts[1].length, range),
95+
),
8496
innerClassDepth,
8597
true,
8698
),
8799
)
88100
}
101+
references.lastOrNull()?.isFinalPart = true
89102
return references.toTypedArray()
90103
}
91104

@@ -167,6 +180,9 @@ object EntryPointReference : PsiReferenceProvider() {
167180
PsiPolyVariantReference,
168181
InspectionReference {
169182

183+
var isFinalPart = false
184+
internal set
185+
170186
override val description = "entry point '%s'"
171187
override val unresolved = resolve() == null
172188

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Minecraft Development for IntelliJ
3+
*
4+
* https://mcdev.io/
5+
*
6+
* Copyright (C) 2025 minecraft-dev
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU Lesser General Public License as published
10+
* by the Free Software Foundation, version 3.0 only.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
package com.demonwav.mcdev.platform.mixin.completion
22+
23+
import com.demonwav.mcdev.platform.mixin.reference.InjectionPointReference
24+
import com.demonwav.mcdev.util.reference.findContextElement
25+
import com.intellij.codeInsight.AutoPopupController
26+
import com.intellij.codeInsight.editorActions.TypedHandlerDelegate
27+
import com.intellij.lang.java.JavaLanguage
28+
import com.intellij.openapi.editor.Editor
29+
import com.intellij.openapi.project.Project
30+
import com.intellij.psi.PsiFile
31+
32+
class InjectionPointTypedHandlerDelegate : TypedHandlerDelegate() {
33+
override fun checkAutoPopup(charTyped: Char, project: Project, editor: Editor, file: PsiFile): Result {
34+
if (charTyped != ':' || !file.language.isKindOf(JavaLanguage.INSTANCE)) {
35+
return Result.CONTINUE
36+
}
37+
AutoPopupController.getInstance(project).autoPopupMemberLookup(editor) {
38+
val offset = editor.caretModel.offset
39+
val element = it.findElementAt(offset)?.findContextElement()
40+
InjectionPointReference.ELEMENT_PATTERN.accepts(element)
41+
}
42+
return Result.CONTINUE
43+
}
44+
}

src/main/kotlin/platform/mixin/completion/MixinCompletionContributor.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,13 @@ class MixinCompletionContributor : CompletionContributor() {
106106
}
107107

108108
// Process methods and fields from target class
109-
findShadowTargets(psiClass, start, superMixin != null)
109+
val elements = findShadowTargets(psiClass, start, superMixin != null)
110110
.map { it.createLookupElement(psiClass.project) }
111111
.filter { prefixMatcher.prefixMatches(it) }
112112
.filter(filter, position)
113-
.map { PrioritizedLookupElement.withExplicitProximity(it, 1) }
114-
.forEach(r::addElement)
113+
.toList()
114+
115+
r.addAllElements(elements)
115116
}
116117
}
117118
}

src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ import com.intellij.codeInsight.template.TemplateBuilderImpl
8484
import com.intellij.codeInsight.template.TemplateEditingAdapter
8585
import com.intellij.codeInsight.template.TemplateManager
8686
import com.intellij.codeInsight.template.TextResult
87+
import com.intellij.injected.editor.VirtualFileWindow
88+
import com.intellij.lang.injection.InjectedLanguageManager
8789
import com.intellij.openapi.application.runWriteAction
8890
import com.intellij.openapi.command.CommandProcessor
8991
import com.intellij.openapi.command.WriteCommandAction
@@ -103,6 +105,7 @@ import com.intellij.psi.PsiModifierList
103105
import com.intellij.psi.codeStyle.CodeStyleManager
104106
import com.intellij.psi.codeStyle.JavaCodeStyleManager
105107
import com.intellij.psi.createSmartPointer
108+
import com.intellij.psi.impl.source.resolve.FileContextUtil
106109
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageEditorUtil
107110
import com.intellij.psi.tree.TokenSet
108111
import com.intellij.psi.util.PsiTreeUtil
@@ -142,6 +145,7 @@ private typealias TemplateExpressionContext = com.intellij.codeInsight.template.
142145

143146
object MEExpressionCompletionUtil {
144147
private const val DEBUG_COMPLETION = false
148+
var debugCompletionUnitTest = false
145149

146150
private val NORMAL_ELEMENT = PlatformPatterns.psiElement()
147151
.inside(MEStatement::class.java)
@@ -953,19 +957,23 @@ object MEExpressionCompletionUtil {
953957
isStore: Boolean,
954958
mixinClass: PsiClass,
955959
): List<EliminableLookup> {
960+
val isStatic = targetMethod.hasAccess(Opcodes.ACC_STATIC)
956961
// ignore "this"
957-
if (!targetMethod.hasAccess(Opcodes.ACC_STATIC) && index == 0) {
962+
if (!isStatic && index == 0) {
958963
return emptyList()
959964
}
960965

961966
var argumentsSize = Type.getArgumentsAndReturnSizes(targetMethod.desc) shr 2
962-
if (targetMethod.hasAccess(Opcodes.ACC_STATIC)) {
967+
if (isStatic) {
963968
argumentsSize--
964969
}
965970
val isArgsOnly = index < argumentsSize
966971

967972
if (targetMethod.localVariables != null) {
968973
val localsHere = targetMethod.localVariables.filter { localVariable ->
974+
if (!isStatic && localVariable.index == 0) {
975+
return@filter false
976+
}
969977
val firstValidInstruction = if (isStore) {
970978
generateSequence<AbstractInsnNode>(localVariable.start) { it.previous }
971979
.firstOrNull { it.opcode >= 0 }
@@ -1012,7 +1020,11 @@ object MEExpressionCompletionUtil {
10121020

10131021
// fallback to ASM dataflow
10141022
val localTypes = AsmDfaUtil.getLocalVariableTypes(project, targetClass, targetMethod, originalInsn)
1023+
?.toMutableList()
10151024
?: return emptyList()
1025+
if (!isStatic) {
1026+
localTypes[0] = null
1027+
}
10161028
val localType = localTypes.getOrNull(index) ?: return emptyList()
10171029
val ordinal = localTypes.asSequence().take(index).filter { it == localType }.count()
10181030
val localName = localType.typeNameToInsert().replace("[]", "Array") + (ordinal + 1)
@@ -1191,6 +1203,32 @@ object MEExpressionCompletionUtil {
11911203
id: String,
11921204
definitionValue: String
11931205
): PsiAnnotation? {
1206+
if (debugCompletionUnitTest) {
1207+
val injectedLanguageManager = InjectedLanguageManager.getInstance(project)
1208+
System.err.println("Here 1")
1209+
val injectionHostElement = injectedLanguageManager.getInjectionHost(contextElement) ?: run {
1210+
val injectedFile = contextElement.containingFile ?: return null
1211+
System.err.println("Here 6, ${injectedFile.text}")
1212+
val virtualFile = injectedFile.virtualFile ?: return null
1213+
System.err.println("Here 7, ${virtualFile.javaClass.name}")
1214+
if (virtualFile is VirtualFileWindow) {
1215+
val hostPtr = injectedFile.getUserData(FileContextUtil.INJECTED_IN_ELEMENT) ?: return null
1216+
System.err.println("Here 8, $hostPtr")
1217+
val hostElement = hostPtr.element ?: return null
1218+
System.err.println("Here 9, ${hostElement.text}")
1219+
}
1220+
return null
1221+
}
1222+
System.err.println("Here 2, ${injectionHostElement.text}")
1223+
val injectionHostFile = injectionHostElement.containingFile ?: return null
1224+
System.err.println("Here 3, ${injectionHostFile.text}")
1225+
val hostOffset =
1226+
injectedLanguageManager.injectedToHost(contextElement, contextElement.textRange.startOffset)
1227+
System.err.println("Here 4, ${contextElement.textRange.startOffset}, $hostOffset")
1228+
val hostElement = injectionHostFile.findElementAt(hostOffset) ?: return null
1229+
System.err.println("Here 5, ${hostElement.text}")
1230+
}
1231+
11941232
val injectionHost = contextElement.findMultiInjectionHost() ?: return null
11951233
val expressionAnnotation = injectionHost.parentOfType<PsiAnnotation>() ?: return null
11961234
if (!expressionAnnotation.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION)) {

src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ package com.demonwav.mcdev.platform.mixin.expression
2323
import com.demonwav.mcdev.platform.mixin.util.MixinConstants
2424
import com.demonwav.mcdev.util.findContainingModifierList
2525
import com.demonwav.mcdev.util.findContainingNameValuePair
26+
import com.demonwav.mcdev.util.parseArray
2627
import com.intellij.lang.injection.InjectedLanguageManager
2728
import com.intellij.lang.injection.MultiHostInjector
2829
import com.intellij.lang.injection.MultiHostRegistrar
@@ -117,23 +118,24 @@ class MEExpressionInjector : MultiHostInjector {
117118
}
118119
}
119120
} else if (annotation.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION)) {
120-
val valueExpr = annotation.findDeclaredAttributeValue("value") ?: continue
121-
val places = mutableListOf<Pair<PsiLanguageInjectionHost, TextRange>>()
122-
iterateConcatenation(valueExpr) { op ->
123-
if (op is PsiLanguageInjectionHost) {
124-
for (textRange in getTextRanges(op)) {
125-
places += op to textRange
121+
for (valueExpr in annotation.findDeclaredAttributeValue("value")?.parseArray { it }.orEmpty()) {
122+
val places = mutableListOf<Pair<PsiLanguageInjectionHost, TextRange>>()
123+
iterateConcatenation(valueExpr) { op ->
124+
if (op is PsiLanguageInjectionHost) {
125+
for (textRange in getTextRanges(op)) {
126+
places += op to textRange
127+
}
128+
} else {
129+
isFrankenstein = true
126130
}
127-
} else {
128-
isFrankenstein = true
129131
}
130-
}
131-
if (places.isNotEmpty()) {
132-
for ((i, place) in places.withIndex()) {
133-
val (host, range) = place
134-
val prefix = "\ndo { ".takeIf { i == 0 }
135-
val suffix = " }".takeIf { i == places.size - 1 }
136-
registrar.addPlace(prefix, suffix, host, range)
132+
if (places.isNotEmpty()) {
133+
for ((i, place) in places.withIndex()) {
134+
val (host, range) = place
135+
val prefix = "\ndo { ".takeIf { i == 0 }
136+
val suffix = " }".takeIf { i == places.size - 1 }
137+
registrar.addPlace(prefix, suffix, host, range)
138+
}
137139
}
138140
}
139141
}

0 commit comments

Comments
 (0)