User: joe
Date: 11 Aug 25 17:42
Revision: 9cf229cc9313e7d0428c801b92dae70cd5f5ff24
Summary:
Add handling for translations in deprecated.json. Closes #2511
TeamCity URL: https://ci.mcdev.io/viewModification.html?tab=vcsModificationFiles&modId=10143&personal=false
Index: src/main/kotlin/translations/DeprecatedTranslations.kt
===================================================================
--- src/main/kotlin/translations/DeprecatedTranslations.kt (revision 9cf229cc9313e7d0428c801b92dae70cd5f5ff24)
+++ src/main/kotlin/translations/DeprecatedTranslations.kt (revision 9cf229cc9313e7d0428c801b92dae70cd5f5ff24)
@@ -0,0 +1,111 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2025 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.translations
+
+import com.google.gson.JsonParser
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.roots.OrderEnumerator
+import com.intellij.openapi.roots.ProjectRootModificationTracker
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.psi.util.CachedValueProvider
+import com.intellij.psi.util.CachedValuesManager
+import java.io.InputStreamReader
+import java.io.IOException
+import java.nio.charset.StandardCharsets
+
+class DeprecatedTranslations private constructor(val removed: Set, val renamed: Map) {
+ val inverseRenamed = renamed.entries.associateBy({ it.value }) { it.key }
+
+ val isEmpty
+ get() = removed.isEmpty() && renamed.isEmpty()
+
+ companion object {
+ private val DEFAULT = DeprecatedTranslations(emptySet(), emptyMap())
+ private const val FILE_PATH = "assets/minecraft/lang/deprecated.json"
+
+ fun getInstance(project: Project): DeprecatedTranslations {
+ return CachedValuesManager.getManager(project).getCachedValue(project) {
+ val file = findFile(project) ?: return@getCachedValue CachedValueProvider.Result(
+ DEFAULT,
+ ProjectRootModificationTracker.getInstance(project)
+ )
+ CachedValueProvider.Result(getInstanceFromFile(file), file)
+ }
+ }
+
+ private fun findFile(project: Project): VirtualFile? {
+ for (libraryRoot in OrderEnumerator.orderEntries(project).librariesOnly().classes().roots) {
+ val file = libraryRoot.findFileByRelativePath(FILE_PATH) ?: continue
+ if (!file.isDirectory) {
+ return file
+ }
+ }
+
+ return null
+ }
+
+ private fun getInstanceFromFile(file: VirtualFile): DeprecatedTranslations {
+ try {
+ val rootElement = InputStreamReader(file.inputStream, StandardCharsets.UTF_8).use { reader ->
+ JsonParser.parseReader(reader)
+ }
+
+ if (rootElement == null || !rootElement.isJsonObject) {
+ return DEFAULT
+ }
+
+ val obj = rootElement.asJsonObject
+
+ val removedElt = obj.get("removed")
+ val removed = if (removedElt != null && removedElt.isJsonArray) {
+ val removedArr = removedElt.asJsonArray
+ removedArr.mapNotNullTo(HashSet.newHashSet(removedArr.size())) { e ->
+ if (e.isJsonPrimitive && e.asJsonPrimitive.isString) {
+ e.asString
+ } else {
+ null
+ }
+ }
+ } else {
+ emptySet()
+ }
+
+ val renamedElt = obj.get("renamed")
+ val renamed = if (renamedElt != null && renamedElt.isJsonObject) {
+ val renamedObj = renamedElt.asJsonObject
+ renamedObj.asMap().asSequence().mapNotNull { (k, v) ->
+ if (v.isJsonPrimitive && v.asJsonPrimitive.isString) {
+ k to v.asString
+ } else {
+ null
+ }
+ }.toMap(HashMap.newHashMap(renamedObj.size()))
+ } else {
+ emptyMap()
+ }
+
+ return DeprecatedTranslations(removed, renamed)
+ } catch (_: IOException) {
+ return DEFAULT
+ }
+ }
+ }
+}
Index: src/main/kotlin/translations/TranslationFiles.kt
===================================================================
--- src/main/kotlin/translations/TranslationFiles.kt (revision 63357e498e49aa6e7fcbac7744b8eb35e0e727cf)
+++ src/main/kotlin/translations/TranslationFiles.kt (revision 9cf229cc9313e7d0428c801b92dae70cd5f5ff24)
@@ -238,9 +238,6 @@
is FileEntry.Comment -> result.append("# ${entry.text}\n")
is FileEntry.Translation -> result.append("${entry.key}=${entry.text}\n")
FileEntry.EmptyLine -> result.append('\n')
- // TODO: IntelliJ shows a false error here without the `else`. The compiler doesn't care because
- // FileEntry is a sealed class. When this bug in IntelliJ is fixed, remove this `else`.
- else -> {}
}
}
@@ -282,9 +279,6 @@
result.append("\"${StringUtil.escapeStringCharacters(entry.text)}\",\n")
}
FileEntry.EmptyLine -> result.append('\n')
- // TODO: IntelliJ shows a false error here without the `else`. The compiler doesn't care because
- // FileEntry is a sealed class. When this bug in IntelliJ is fixed, remove this `else`.
- else -> {}
}
}
Index: src/main/kotlin/translations/identification/TranslationIdentifier.kt
===================================================================
--- src/main/kotlin/translations/identification/TranslationIdentifier.kt (revision 63357e498e49aa6e7fcbac7744b8eb35e0e727cf)
+++ src/main/kotlin/translations/identification/TranslationIdentifier.kt (revision 9cf229cc9313e7d0428c801b92dae70cd5f5ff24)
@@ -22,6 +22,7 @@
import com.demonwav.mcdev.platform.mcp.mappings.getMappedClass
import com.demonwav.mcdev.platform.mcp.mappings.getMappedMethod
+import com.demonwav.mcdev.translations.DeprecatedTranslations
import com.demonwav.mcdev.translations.TranslationConstants
import com.demonwav.mcdev.translations.identification.TranslationInstance.FormattingError
import com.demonwav.mcdev.translations.index.TranslationIndex
@@ -89,10 +90,13 @@
else -> element.evaluateString()
}?.replace(CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED, "") ?: return null
- val shouldFold = element is ULiteralExpression && element.isString
+ val deprecations = DeprecatedTranslations.getInstance(project)
+ val key = prefix + translationKey + suffix
+ val shouldFold = element is ULiteralExpression && element.isString && key !in deprecations.removed && key !in deprecations.renamed
+
val entries = TranslationIndex.getAllDefaultEntries(project).merge("")
- val translation = entries[prefix + translationKey + suffix]?.text
+ val translation = entries[deprecations.inverseRenamed[key] ?: key]?.text
?: return TranslationInstance( // translation doesn't exist
null,
index,
Index: src/main/kotlin/translations/inspections/DeprecatedTranslationInspection.kt
===================================================================
--- src/main/kotlin/translations/inspections/DeprecatedTranslationInspection.kt (revision 9cf229cc9313e7d0428c801b92dae70cd5f5ff24)
+++ src/main/kotlin/translations/inspections/DeprecatedTranslationInspection.kt (revision 9cf229cc9313e7d0428c801b92dae70cd5f5ff24)
@@ -0,0 +1,60 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2025 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.translations.inspections
+
+import com.demonwav.mcdev.translations.DeprecatedTranslations
+import com.demonwav.mcdev.translations.identification.TranslationIdentifier
+import com.intellij.codeInspection.ProblemsHolder
+import com.intellij.psi.PsiElementVisitor
+import com.intellij.uast.UastHintedVisitorAdapter
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor
+
+class DeprecatedTranslationInspection : TranslationInspection() {
+ override fun getStaticDescription() = "Detects usage of translations that are removed or renamed in deprecated.json"
+
+ private val typesHint: Array> = arrayOf(UExpression::class.java)
+
+ override fun buildVisitor(holder: ProblemsHolder): PsiElementVisitor =
+ UastHintedVisitorAdapter.create(holder.file.language, Visitor(holder), typesHint)
+
+ private class Visitor(private val holder: ProblemsHolder) : AbstractUastNonRecursiveVisitor() {
+ val deprecations = DeprecatedTranslations.getInstance(holder.project)
+
+ override fun visitExpression(node: UExpression): Boolean {
+ val result = TranslationIdentifier.identify(node)
+ if (result != null && result.text != null) {
+ val isRemoved = result.key.full in deprecations.removed
+ val isRenamed = result.key.full in deprecations.renamed
+ if (isRemoved || isRenamed) {
+ val type = if (isRemoved) "removed" else "renamed"
+ holder.registerProblem(
+ node.sourcePsi!!,
+ "Usage of $type translation in deprecated.json"
+ )
+ }
+ }
+
+ return super.visitExpression(node)
+ }
+ }
+}
Index: src/main/kotlin/translations/reference/TranslationGotoSymbolContributor.kt
===================================================================
--- src/main/kotlin/translations/reference/TranslationGotoSymbolContributor.kt (revision 63357e498e49aa6e7fcbac7744b8eb35e0e727cf)
+++ src/main/kotlin/translations/reference/TranslationGotoSymbolContributor.kt (revision 9cf229cc9313e7d0428c801b92dae70cd5f5ff24)
@@ -20,6 +20,7 @@
package com.demonwav.mcdev.translations.reference
+import com.demonwav.mcdev.translations.DeprecatedTranslations
import com.demonwav.mcdev.translations.index.TranslationIndex
import com.demonwav.mcdev.translations.index.TranslationInverseIndex
import com.demonwav.mcdev.translations.index.merge
@@ -57,7 +58,8 @@
} else {
GlobalSearchScope.projectScope(project)
}
- val elements = TranslationInverseIndex.findElements(name, scope)
+ val deprecations = DeprecatedTranslations.getInstance(project)
+ val elements = TranslationInverseIndex.findElements(deprecations.inverseRenamed[name] ?: name, scope)
return elements.mapToArray { it as NavigationItem }
}
Index: src/main/kotlin/translations/reference/TranslationReference.kt
===================================================================
--- src/main/kotlin/translations/reference/TranslationReference.kt (revision 63357e498e49aa6e7fcbac7744b8eb35e0e727cf)
+++ src/main/kotlin/translations/reference/TranslationReference.kt (revision 9cf229cc9313e7d0428c801b92dae70cd5f5ff24)
@@ -21,12 +21,14 @@
package com.demonwav.mcdev.translations.reference
import com.demonwav.mcdev.asset.PlatformAssets
+import com.demonwav.mcdev.translations.DeprecatedTranslations
import com.demonwav.mcdev.translations.TranslationConstants
import com.demonwav.mcdev.translations.TranslationFiles
import com.demonwav.mcdev.translations.identification.TranslationInstance
import com.demonwav.mcdev.translations.index.TranslationIndex
import com.demonwav.mcdev.translations.index.TranslationInverseIndex
import com.demonwav.mcdev.translations.lang.gen.psi.LangEntry
+import com.demonwav.mcdev.util.filterNotNull
import com.demonwav.mcdev.util.mapToArray
import com.demonwav.mcdev.util.toTypedArray
import com.intellij.codeInsight.lookup.LookupElementBuilder
@@ -52,8 +54,9 @@
) : PsiReferenceBase.Poly(element, textRange, false), PsiPolyVariantReference {
override fun multiResolve(incompleteCode: Boolean): Array {
val project = myElement.project
+ val deprecations = DeprecatedTranslations.getInstance(project)
val entries = TranslationInverseIndex.findElements(
- key.full,
+ deprecations.inverseRenamed[key.full] ?: key.full,
GlobalSearchScope.allScope(project),
TranslationConstants.DEFAULT_LOCALE,
)
@@ -63,16 +66,18 @@
override fun getVariants(): Array {
val project = myElement.project
val defaultTranslations = TranslationIndex.getAllDefaultTranslations(project)
+ val deprecations = DeprecatedTranslations.getInstance(project)
val pattern = Regex("${Regex.escape(key.prefix)}(.*?)${Regex.escape(key.suffix)}")
return defaultTranslations
- .filter { it.key.isNotEmpty() }
- .mapNotNull { entry -> pattern.matchEntire(entry.key)?.let { entry to it } }
- .map { (entry, match) ->
+ .map { it.key }
+ .filter { it.isNotEmpty() && it !in deprecations.removed && it !in deprecations.renamed }
+ .mapNotNull { key -> pattern.matchEntire(key)?.let { key to it } }
+ .map { (key, match) ->
LookupElementBuilder
- .create(if (match.groups.size <= 1) entry.key else match.groupValues[1])
+ .create(if (match.groups.size <= 1) key else match.groupValues[1])
.withIcon(PlatformAssets.MINECRAFT_ICON)
.withTypeText(TranslationConstants.DEFAULT_LOCALE)
- .withPresentableText(entry.key)
+ .withPresentableText(key)
}
.toTypedArray()
}
Index: src/main/resources/META-INF/plugin.xml
===================================================================
--- src/main/resources/META-INF/plugin.xml (revision 63357e498e49aa6e7fcbac7744b8eb35e0e727cf)
+++ src/main/resources/META-INF/plugin.xml (revision 9cf229cc9313e7d0428c801b92dae70cd5f5ff24)
@@ -855,6 +855,13 @@
level="WARNING"
hasStaticDescription="true"
implementationClass="com.demonwav.mcdev.translations.inspections.WrongTypeInTranslationArgsInspection"/>
+