User: joe Date: 11 Aug 25 15:27 Revision: 63357e498e49aa6e7fcbac7744b8eb35e0e727cf Summary: Simply translation identification code and make interpolation inlining less aggressive TeamCity URL: https://ci.mcdev.io/viewModification.html?tab=vcsModificationFiles&modId=10142&personal=false Index: src/main/kotlin/translations/folding.kt =================================================================== --- src/main/kotlin/translations/folding.kt (revision 099e295015d51c4ffa78114529b41039afba7170) +++ src/main/kotlin/translations/folding.kt (revision 63357e498e49aa6e7fcbac7744b8eb35e0e727cf) @@ -35,7 +35,7 @@ import com.intellij.openapi.options.BeanConfigurable import com.intellij.psi.PsiElement import org.jetbrains.uast.UCallExpression -import org.jetbrains.uast.UElement +import org.jetbrains.uast.UExpression import org.jetbrains.uast.textRange import org.jetbrains.uast.toUElement import org.jetbrains.uast.visitor.AbstractUastVisitor @@ -45,7 +45,7 @@ init { title = "Minecraft" checkBox( - "Translation Strings", + "Translation strings", TranslationFoldingSettings.instance::shouldFoldTranslations, ) { TranslationFoldingSettings.instance.shouldFoldTranslations = it @@ -86,51 +86,52 @@ class TranslationFoldingBuilder : FoldingBuilderEx() { override fun buildFoldRegions(root: PsiElement, document: Document, quick: Boolean): Array { if (ApplicationManager.getApplication().isDispatchThread) { - return emptyArray() + return FoldingDescriptor.EMPTY_ARRAY } val descriptors = mutableListOf() - for (identifier in TranslationIdentifier.INSTANCES) { - val uElement = root.toUElement() ?: continue - val children = mutableListOf() + val uElement = root.toUElement() ?: return FoldingDescriptor.EMPTY_ARRAY + val translations = mutableListOf() - uElement.accept(object : AbstractUastVisitor() { + uElement.accept(object : AbstractUastVisitor() { - override fun visitElement(node: UElement): Boolean { - if (identifier.elementClass().isAssignableFrom(node.javaClass)) { - children.add(node) + override fun visitExpression(node: UExpression): Boolean { + val translation = TranslationIdentifier.identify(node) + if (translation != null) { + translations += translation - } + } - return super.visitElement(node) - } - }) + return super.visitElement(node) + } + }) - for (element in children) { - val translation = identifier.identifyUnsafe(element) - val foldingElement = translation?.foldingElement ?: continue + for (translation in translations) { + if (!translation.shouldFold) { + continue + } + val foldingElement = translation.foldingElement ?: continue - val range = - if (foldingElement is UCallExpression && translation.foldStart != 0) { - val args = foldingElement.valueArguments.drop(translation.foldStart) - val startRange = args.first().textRange ?: continue - val endRange = args.last().textRange ?: continue - startRange.union(endRange) - } else { - foldingElement.textRange ?: continue - } - if (!translation.required && translation.formattingError != null) { - continue - } - descriptors.add( - FoldingDescriptor( - translation.foldingElement.sourcePsi?.node!!, - range, - FoldingGroup.newGroup("mc.translation." + translation.key), + val range = + if (foldingElement is UCallExpression && translation.foldStart != 0) { + val args = foldingElement.valueArguments.drop(translation.foldStart) + val startRange = args.first().textRange ?: continue + val endRange = args.last().textRange ?: continue + startRange.union(endRange) + } else { + foldingElement.textRange ?: continue + } + if (!translation.required && translation.formattingError != null) { + continue + } + descriptors.add( + FoldingDescriptor( + translation.foldingElement.sourcePsi?.node!!, + range, + FoldingGroup.newGroup("mc.translation." + translation.key), - if (translation.formattingError == TranslationInstance.Companion.FormattingError.MISSING) { + if (translation.formattingError == TranslationInstance.FormattingError.MISSING) { - "\"Insufficient parameters for formatting '${translation.text}'\"" - } else { - "\"${translation.text}\"" - }, - ), - ) - } + "\"Insufficient parameters for formatting '${translation.text}'\"" + } else { + "\"${translation.text}\"" + }, + ), + ) + } - } return descriptors.toTypedArray() } Index: src/main/kotlin/translations/identification/LiteralTranslationIdentifier.kt =================================================================== --- src/main/kotlin/translations/identification/LiteralTranslationIdentifier.kt (revision 099e295015d51c4ffa78114529b41039afba7170) +++ src/main/kotlin/translations/identification/LiteralTranslationIdentifier.kt (revision 099e295015d51c4ffa78114529b41039afba7170) @@ -1,40 +0,0 @@ -/* - * 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.identification - -import com.intellij.codeInsight.completion.CompletionUtilCore -import org.jetbrains.uast.ULiteralExpression - -class LiteralTranslationIdentifier : TranslationIdentifier() { - override fun identify(element: ULiteralExpression): TranslationInstance? { - val statement = element.uastParent ?: return null - if (element.value !is String) { - return null - } - - val project = element.sourcePsi?.project ?: return null - val result = identify(project, element, statement, element) ?: return null - val infix = result.key.infix.replace(CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED, "") - return result.copy(key = result.key.copy(infix = infix)) - } - - override fun elementClass(): Class = ULiteralExpression::class.java -} Index: src/main/kotlin/translations/identification/ReferenceTranslationIdentifier.kt =================================================================== --- src/main/kotlin/translations/identification/ReferenceTranslationIdentifier.kt (revision 099e295015d51c4ffa78114529b41039afba7170) +++ src/main/kotlin/translations/identification/ReferenceTranslationIdentifier.kt (revision 099e295015d51c4ffa78114529b41039afba7170) @@ -1,54 +0,0 @@ -/* - * 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.identification - -import com.intellij.codeInsight.completion.CompletionUtilCore -import com.intellij.psi.PsiManager -import com.intellij.psi.PsiType -import org.jetbrains.uast.UReferenceExpression -import org.jetbrains.uast.UVariable -import org.jetbrains.uast.resolveToUElement - -class ReferenceTranslationIdentifier : TranslationIdentifier() { - override fun identify(element: UReferenceExpression): TranslationInstance? { - val statement = element.uastParent ?: return null - val project = element.sourcePsi?.project ?: return null - val reference = element.resolveToUElement() as? UVariable ?: return null - if (!reference.isFinal) { - return null - } - - val resolveScope = element.sourcePsi?.resolveScope ?: return null - val psiManager = PsiManager.getInstance(project) - val stringType = PsiType.getJavaLangString(psiManager, resolveScope) - if (!stringType.isAssignableFrom(reference.type)) { - return null - } - - val referenceElement = reference.uastInitializer ?: return null - val result = identify(project, element, statement, referenceElement) ?: return null - - val infix = result.key.infix.replace(CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED, "") - return result.copy(key = result.key.copy(infix = infix)) - } - - override fun elementClass(): Class = UReferenceExpression::class.java -} Index: src/main/kotlin/translations/identification/TranslationIdentifier.kt =================================================================== --- src/main/kotlin/translations/identification/TranslationIdentifier.kt (revision 099e295015d51c4ffa78114529b41039afba7170) +++ src/main/kotlin/translations/identification/TranslationIdentifier.kt (revision 63357e498e49aa6e7fcbac7744b8eb35e0e727cf) @@ -23,16 +23,17 @@ import com.demonwav.mcdev.platform.mcp.mappings.getMappedClass import com.demonwav.mcdev.platform.mcp.mappings.getMappedMethod import com.demonwav.mcdev.translations.TranslationConstants -import com.demonwav.mcdev.translations.identification.TranslationInstance.Companion.FormattingError +import com.demonwav.mcdev.translations.identification.TranslationInstance.FormattingError import com.demonwav.mcdev.translations.index.TranslationIndex import com.demonwav.mcdev.translations.index.merge import com.demonwav.mcdev.util.constantStringValue import com.demonwav.mcdev.util.constantValue import com.demonwav.mcdev.util.descriptor -import com.demonwav.mcdev.util.extractVarArgs import com.demonwav.mcdev.util.findModule import com.demonwav.mcdev.util.referencedMethod +import com.demonwav.mcdev.util.toTypedArray import com.intellij.codeInsight.AnnotationUtil +import com.intellij.codeInsight.completion.CompletionUtilCore import com.intellij.codeInspection.dataFlow.CommonDataflow import com.intellij.openapi.project.Project import com.intellij.psi.CommonClassNames @@ -41,166 +42,226 @@ import com.intellij.psi.PsiEllipsisType import com.intellij.psi.PsiExpression import com.intellij.psi.PsiParameter +import com.intellij.psi.PsiType import java.util.IllegalFormatException import java.util.MissingFormatArgumentException import org.jetbrains.uast.UCallExpression -import org.jetbrains.uast.UElement import org.jetbrains.uast.UExpression +import org.jetbrains.uast.ULiteralExpression import org.jetbrains.uast.UMethod import org.jetbrains.uast.UQualifiedReferenceExpression import org.jetbrains.uast.evaluateString import org.jetbrains.uast.getContainingUClass +import org.jetbrains.uast.util.isArrayInitializer -abstract class TranslationIdentifier { - @Suppress("UNCHECKED_CAST") - fun identifyUnsafe(element: UElement): TranslationInstance? { - return identify(element as T) - } - - abstract fun identify(element: T): TranslationInstance? - - abstract fun elementClass(): Class - - companion object { - val INSTANCES = listOf(LiteralTranslationIdentifier(), ReferenceTranslationIdentifier()) - +object TranslationIdentifier { - fun identify( + fun identify( - project: Project, - element: UExpression, - container: UElement, - referenceElement: UElement, + element: UExpression - ): TranslationInstance? { + ): TranslationInstance? { - val call = container as? UCallExpression ?: return null - val index = container.valueArguments.indexOf(element) + val call = element.uastParent as? UCallExpression ?: return null + val index = call.valueArguments.indexOf(element) - val method = call.referencedMethod ?: return null - val parameter = method.uastParameters.getOrNull(index) ?: return null - val translatableAnnotation = AnnotationUtil.findAnnotation( - parameter.javaPsi as PsiParameter, - TranslationConstants.TRANSLATABLE_ANNOTATION - ) ?: return null + val method = call.referencedMethod ?: return null + val parameter = method.uastParameters.getOrNull(index) ?: return null + val translatableAnnotation = AnnotationUtil.findAnnotation( + parameter.javaPsi as PsiParameter, + TranslationConstants.TRANSLATABLE_ANNOTATION + ) ?: return null + val project = element.sourcePsi?.project ?: return null + - val prefix = - translatableAnnotation.findAttributeValue(TranslationConstants.PREFIX)?.constantStringValue ?: "" - val suffix = - translatableAnnotation.findAttributeValue(TranslationConstants.SUFFIX)?.constantStringValue ?: "" - val required = - translatableAnnotation.findAttributeValue(TranslationConstants.REQUIRED)?.constantValue as? Boolean - ?: true - val isPreEscapeException = - method.getContainingUClass()?.qualifiedName?.startsWith("net.minecraft.") == true && - isPreEscapeMcVersion(project, element.sourcePsi!!) - val allowArbitraryArgs = isPreEscapeException || translatableAnnotation.findAttributeValue( - TranslationConstants.ALLOW_ARBITRARY_ARGS - )?.constantValue as? Boolean ?: false + val prefix = + translatableAnnotation.findAttributeValue(TranslationConstants.PREFIX)?.constantStringValue ?: "" + val suffix = + translatableAnnotation.findAttributeValue(TranslationConstants.SUFFIX)?.constantStringValue ?: "" + val required = + translatableAnnotation.findAttributeValue(TranslationConstants.REQUIRED)?.constantValue as? Boolean + ?: true + val isPreEscapeException = + method.getContainingUClass()?.qualifiedName?.startsWith("net.minecraft.") == true && + isPreEscapeMcVersion(project, element.sourcePsi!!) + val allowArbitraryArgs = isPreEscapeException || translatableAnnotation.findAttributeValue( + TranslationConstants.ALLOW_ARBITRARY_ARGS + )?.constantValue as? Boolean ?: false - val translationKey = when (val javaPsi = element.javaPsi) { - is PsiExpression -> CommonDataflow.computeValue(javaPsi) as? String - else -> element.evaluateString() + val translationKey = when (val javaPsi = element.javaPsi) { + is PsiExpression -> CommonDataflow.computeValue(javaPsi) as? String + else -> element.evaluateString() - } ?: return null + }?.replace(CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED, "") ?: return null + val shouldFold = element is ULiteralExpression && element.isString + - val entries = TranslationIndex.getAllDefaultEntries(project).merge("") - val translation = entries[prefix + translationKey + suffix]?.text - ?: return TranslationInstance( // translation doesn't exist - null, - index, + val entries = TranslationIndex.getAllDefaultEntries(project).merge("") + val translation = entries[prefix + translationKey + suffix]?.text + ?: return TranslationInstance( // translation doesn't exist + null, + index, - referenceElement, + element, - TranslationInstance.Key(prefix, translationKey, suffix), - null, - required, - allowArbitraryArgs, + TranslationInstance.Key(prefix, translationKey, suffix), + null, + required, + allowArbitraryArgs, + shouldFold = shouldFold, - ) + ) - val foldMethod = - translatableAnnotation.findAttributeValue(TranslationConstants.FOLD_METHOD)?.constantValue as? Boolean - ?: false + val foldMethod = + translatableAnnotation.findAttributeValue(TranslationConstants.FOLD_METHOD)?.constantValue as? Boolean + ?: false - val formatting = - (method.uastParameters.last().type as? PsiEllipsisType) - ?.componentType?.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) == true + val formatting = + (method.uastParameters.last().type as? PsiEllipsisType) + ?.componentType?.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) == true - val foldingElement = if (foldMethod) { - // Make sure qualifiers, like I18n in 'I18n.translate()' is also folded - call.uastParent as? UQualifiedReferenceExpression ?: call - } else if ( - index == 0 && + val foldingElement = if (foldMethod) { + // Make sure qualifiers, like I18n in 'I18n.translate()' is also folded + call.uastParent as? UQualifiedReferenceExpression ?: call + } else if ( + index == 0 && - container.valueArgumentCount > 1 && + call.valueArgumentCount > 1 && - method.uastParameters.size == 2 && - formatting - ) { + method.uastParameters.size == 2 && + formatting + ) { - container + call - } else { - element - } - try { - val (formatted, superfluousParams) = if (formatting) { - format(method, translation, call) ?: (translation to -1) - } else { - (translation to -1) - } - return TranslationInstance( - foldingElement, - index, + } else { + element + } + try { + val (formatted, superfluousParams) = if (formatting) { + format(method, translation, call) ?: (translation to -1) + } else { + (translation to -1) + } + return TranslationInstance( + foldingElement, + index, - referenceElement, + element, - TranslationInstance.Key(prefix, translationKey, suffix), - formatted, - required, - allowArbitraryArgs, - if (superfluousParams >= 0) FormattingError.SUPERFLUOUS else null, - superfluousParams, + TranslationInstance.Key(prefix, translationKey, suffix), + formatted, + required, + allowArbitraryArgs, + if (superfluousParams >= 0) FormattingError.SUPERFLUOUS else null, + superfluousParams, + shouldFold = shouldFold, - ) + ) - } catch (ignored: MissingFormatArgumentException) { + } catch (_: MissingFormatArgumentException) { - return TranslationInstance( - foldingElement, - index, + return TranslationInstance( + foldingElement, + index, - referenceElement, + element, - TranslationInstance.Key(prefix, translationKey, suffix), - translation, - required, - allowArbitraryArgs, - FormattingError.MISSING, + TranslationInstance.Key(prefix, translationKey, suffix), + translation, + required, + allowArbitraryArgs, + FormattingError.MISSING, + shouldFold = shouldFold, - ) - } - } + ) + } + } - private fun format(method: UMethod, translation: String, call: UCallExpression): Pair? { - val format = NUMBER_FORMATTING_PATTERN.replace(translation, "%$1s") - val paramCount = STRING_FORMATTING_PATTERN.findAll(format).count() + private fun format(method: UMethod, translation: String, call: UCallExpression): Pair? { + val format = NUMBER_FORMATTING_PATTERN.replace(translation, "%$1s") + val paramCount = STRING_FORMATTING_PATTERN.findAll(format).count() - val parametersCount = method.uastParameters.size + val parametersCount = method.uastParameters.size - val varargs = call.extractVarArgs(parametersCount - 1, true, true) + val varargs = call.extractVarArgs(parametersCount - 1) - ?: return null - val varargStart = if (varargs.size > paramCount) { - parametersCount - 1 + paramCount - } else { - -1 - } - return try { - String.format(format, *varargs) to varargStart - } catch (e: MissingFormatArgumentException) { - // rethrow this specific exception to be handled by the caller - throw e + ?: return null + val varargStart = if (varargs.size > paramCount) { + parametersCount - 1 + paramCount + } else { + -1 + } + return try { + String.format(format, *varargs) to varargStart + } catch (e: MissingFormatArgumentException) { + // rethrow this specific exception to be handled by the caller + throw e - } catch (e: IllegalFormatException) { + } catch (_: IllegalFormatException) { - null - } - } + null + } + } + fun UCallExpression.extractVarArgs(index: Int): Array? { + val method = this.referencedMethod + val args = this.valueArguments + if (method == null || args.size < (index + 1)) { + return emptyArray() + } + + val psiParam = method.uastParameters[index].javaPsi as? PsiParameter + ?: return null + if (!psiParam.isVarArgs) { + return arrayOf(args[index].paramDisplayString()) + } + + val elements = args.drop(index) + return extractVarArgs(psiParam.type, elements) + } + + private fun extractVarArgs(type: PsiType, elements: List): Array? { + return if (elements[0].getExpressionType() == type) { + val initializer = elements[0] + if (initializer is UCallExpression && initializer.isArrayInitializer()) { + // We're dealing with an array initializer, let's analyse it! + initializer.valueArguments + .asSequence() + .map { it.paramDisplayString() } + .toTypedArray() + } else { + // We're dealing with a more complex expression that results in an array, give up + return null + } + } else { + elements.asSequence().map { it.paramDisplayString() }.toTypedArray() + } + } + + fun UExpression.paramDisplayString(): String { + val visited = mutableSetOf() + + fun eval(expr: UExpression, defaultValue: String = "\${${expr.asSourceString()}}"): String { + if (!visited.add(expr)) { + return defaultValue + } + + when (expr) { + is UQualifiedReferenceExpression -> { + val selector = expr.selector + if (selector is UCallExpression) { + return eval(selector, "\${${expr.asSourceString()}}") + } + } + + is UCallExpression -> for (argument in expr.valueArguments) { + val translation = identify(argument) ?: continue + if (translation.formattingError == null) { + translation.text?.let { return it } + } + } + + else -> expr.evaluateString()?.let { return it } + } + + return defaultValue + } + + return eval(this) + } + - private fun isPreEscapeMcVersion(project: Project, contextElement: PsiElement): Boolean { - val module = contextElement.findModule() ?: return false - val componentClassName = module.getMappedClass("net.minecraft.network.chat.Component") - val componentClass = JavaPsiFacade.getInstance(project) - .findClass(componentClassName, contextElement.resolveScope) ?: return false - val translatableEscapeName = module.getMappedMethod( - "net.minecraft.network.chat.Component", - "translatableEscape", - "(Ljava/lang/String;[Ljava/lang/Object;)Lnet/minecraft/network/chat/Component;" - ) - return componentClass.findMethodsByName(translatableEscapeName, false).any { method -> - method.descriptor?.startsWith("(Ljava/lang/String;[Ljava/lang/Object;)") == true - } - } + private fun isPreEscapeMcVersion(project: Project, contextElement: PsiElement): Boolean { + val module = contextElement.findModule() ?: return false + val componentClassName = module.getMappedClass("net.minecraft.network.chat.Component") + val componentClass = JavaPsiFacade.getInstance(project) + .findClass(componentClassName, contextElement.resolveScope) ?: return false + val translatableEscapeName = module.getMappedMethod( + "net.minecraft.network.chat.Component", + "translatableEscape", + "(Ljava/lang/String;[Ljava/lang/Object;)Lnet/minecraft/network/chat/Component;" + ) + return componentClass.findMethodsByName(translatableEscapeName, false).any { method -> + method.descriptor?.startsWith("(Ljava/lang/String;[Ljava/lang/Object;)") == true + } + } - private val NUMBER_FORMATTING_PATTERN = Regex("%(\\d+\\$)?[\\d.]*[df]") - private val STRING_FORMATTING_PATTERN = Regex("[^%]?%(?:\\d+\\$)?s") - } + private val NUMBER_FORMATTING_PATTERN = Regex("%(\\d+\\$)?[\\d.]*[df]") + private val STRING_FORMATTING_PATTERN = Regex("[^%]?%(?:\\d+\\$)?s") +} -} Index: src/main/kotlin/translations/identification/TranslationInstance.kt =================================================================== --- src/main/kotlin/translations/identification/TranslationInstance.kt (revision 099e295015d51c4ffa78114529b41039afba7170) +++ src/main/kotlin/translations/identification/TranslationInstance.kt (revision 63357e498e49aa6e7fcbac7744b8eb35e0e727cf) @@ -32,6 +32,7 @@ val allowArbitraryArgs: Boolean, val formattingError: FormattingError? = null, val superfluousVarargStart: Int = -1, + val shouldFold: Boolean = true, ) { data class Key(val prefix: String, val infix: String, val suffix: String) { constructor(infix: String) : this("", infix, "") @@ -39,14 +40,7 @@ val full = (prefix + infix + suffix).trim() } - companion object { - enum class FormattingError { - MISSING, SUPERFLUOUS - } + enum class FormattingError { + MISSING, SUPERFLUOUS + } - - fun find(element: UElement): TranslationInstance? = - TranslationIdentifier.INSTANCES - .firstOrNull { it.elementClass().isAssignableFrom(element.javaClass) } - ?.identifyUnsafe(element) - } +} -} Index: src/main/kotlin/translations/inspections/ChangeTranslationQuickFix.kt =================================================================== --- src/main/kotlin/translations/inspections/ChangeTranslationQuickFix.kt (revision 099e295015d51c4ffa78114529b41039afba7170) +++ src/main/kotlin/translations/inspections/ChangeTranslationQuickFix.kt (revision 63357e498e49aa6e7fcbac7744b8eb35e0e727cf) @@ -20,7 +20,7 @@ package com.demonwav.mcdev.translations.inspections -import com.demonwav.mcdev.translations.identification.LiteralTranslationIdentifier +import com.demonwav.mcdev.translations.identification.TranslationIdentifier import com.demonwav.mcdev.translations.reference.TranslationGotoModel import com.demonwav.mcdev.util.runWriteAction import com.intellij.codeInspection.LocalQuickFix @@ -42,7 +42,7 @@ override fun applyFix(project: Project, descriptor: ProblemDescriptor) { try { val literal = descriptor.psiElement.toUElementOfType() ?: return - val key = LiteralTranslationIdentifier().identify(literal)?.key ?: return + val key = TranslationIdentifier.identify(literal)?.key ?: return val popup = ChooseByNamePopup.createPopup( project, TranslationGotoModel(project, key.prefix, key.suffix), Index: src/main/kotlin/translations/inspections/MissingFormatInspection.kt =================================================================== --- src/main/kotlin/translations/inspections/MissingFormatInspection.kt (revision 099e295015d51c4ffa78114529b41039afba7170) +++ src/main/kotlin/translations/inspections/MissingFormatInspection.kt (revision 63357e498e49aa6e7fcbac7744b8eb35e0e727cf) @@ -20,8 +20,8 @@ package com.demonwav.mcdev.translations.inspections -import com.demonwav.mcdev.translations.identification.TranslationInstance -import com.demonwav.mcdev.translations.identification.TranslationInstance.Companion.FormattingError +import com.demonwav.mcdev.translations.identification.TranslationIdentifier +import com.demonwav.mcdev.translations.identification.TranslationInstance.FormattingError import com.intellij.codeInspection.LocalQuickFix import com.intellij.codeInspection.ProblemsHolder import com.intellij.psi.PsiElementVisitor @@ -52,7 +52,7 @@ } private fun visit(expression: UExpression, vararg quickFixes: LocalQuickFix) { - val result = TranslationInstance.find(expression) + val result = TranslationIdentifier.identify(expression) if (result != null && result.required && result.formattingError == FormattingError.MISSING) { holder.registerProblem( expression.sourcePsi!!, Index: src/main/kotlin/translations/inspections/NoTranslationInspection.kt =================================================================== --- src/main/kotlin/translations/inspections/NoTranslationInspection.kt (revision 099e295015d51c4ffa78114529b41039afba7170) +++ src/main/kotlin/translations/inspections/NoTranslationInspection.kt (revision 63357e498e49aa6e7fcbac7744b8eb35e0e727cf) @@ -21,7 +21,7 @@ package com.demonwav.mcdev.translations.inspections import com.demonwav.mcdev.translations.TranslationFiles -import com.demonwav.mcdev.translations.identification.LiteralTranslationIdentifier +import com.demonwav.mcdev.translations.identification.TranslationIdentifier import com.intellij.codeInspection.LocalQuickFix import com.intellij.codeInspection.ProblemDescriptor import com.intellij.codeInspection.ProblemsHolder @@ -33,6 +33,7 @@ import com.intellij.uast.UastHintedVisitorAdapter import com.intellij.util.IncorrectOperationException import org.jetbrains.uast.UElement +import org.jetbrains.uast.UExpression import org.jetbrains.uast.ULiteralExpression import org.jetbrains.uast.toUElementOfType import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor @@ -49,8 +50,8 @@ private class Visitor(private val holder: ProblemsHolder) : AbstractUastNonRecursiveVisitor() { - override fun visitLiteralExpression(node: ULiteralExpression): Boolean { - val result = LiteralTranslationIdentifier().identify(node) + override fun visitExpression(node: UExpression): Boolean { + val result = TranslationIdentifier.identify(node) if (result != null && result.required && result.text == null) { holder.registerProblem( node.sourcePsi!!, @@ -70,7 +71,7 @@ override fun applyFix(project: Project, descriptor: ProblemDescriptor) { try { val literal = descriptor.psiElement.toUElementOfType() ?: return - val translation = LiteralTranslationIdentifier().identify(literal) + val translation = TranslationIdentifier.identify(literal) val literalValue = literal.value as String val key = translation?.key?.copy(infix = literalValue)?.full ?: literalValue val result = Messages.showInputDialog( @@ -82,7 +83,7 @@ if (result != null) { TranslationFiles.add(literal.sourcePsi!!, key, result) } - } catch (ignored: IncorrectOperationException) { + } catch (_: IncorrectOperationException) { } catch (e: Exception) { Notification( "Translation support error", Index: src/main/kotlin/translations/inspections/SuperfluousFormatInspection.kt =================================================================== --- src/main/kotlin/translations/inspections/SuperfluousFormatInspection.kt (revision 099e295015d51c4ffa78114529b41039afba7170) +++ src/main/kotlin/translations/inspections/SuperfluousFormatInspection.kt (revision 63357e498e49aa6e7fcbac7744b8eb35e0e727cf) @@ -20,8 +20,9 @@ package com.demonwav.mcdev.translations.inspections +import com.demonwav.mcdev.translations.identification.TranslationIdentifier import com.demonwav.mcdev.translations.identification.TranslationInstance -import com.demonwav.mcdev.translations.identification.TranslationInstance.Companion.FormattingError +import com.demonwav.mcdev.translations.identification.TranslationInstance.FormattingError import com.demonwav.mcdev.util.runWriteAction import com.intellij.codeInspection.LocalQuickFix import com.intellij.codeInspection.ProblemDescriptor @@ -51,20 +52,8 @@ private class Visitor(private val holder: ProblemsHolder) : AbstractUastNonRecursiveVisitor() { override fun visitExpression(node: UExpression): Boolean { - val result = TranslationInstance.find(node) + val result = TranslationIdentifier.identify(node) if ( - result != null && result.foldingElement is UCallExpression && - result.formattingError == FormattingError.SUPERFLUOUS - ) { - registerProblem(node, result) - } - - return super.visitExpression(node) - } - - override fun visitLiteralExpression(node: ULiteralExpression): Boolean { - val result = TranslationInstance.find(node) - if ( result != null && result.required && result.foldingElement is UCallExpression && result.formattingError == FormattingError.SUPERFLUOUS ) { @@ -79,7 +68,7 @@ ) } - return super.visitLiteralExpression(node) + return super.visitExpression(node) } private fun registerProblem( @@ -106,7 +95,7 @@ descriptor.psiElement.containingFile.runWriteAction { call.element?.valueArguments?.drop(position)?.forEach { it.sourcePsi?.delete() } } - } catch (ignored: IncorrectOperationException) { + } catch (_: IncorrectOperationException) { } } Index: src/main/kotlin/translations/inspections/WrongTypeInTranslationArgsInspection.kt =================================================================== --- src/main/kotlin/translations/inspections/WrongTypeInTranslationArgsInspection.kt (revision 099e295015d51c4ffa78114529b41039afba7170) +++ src/main/kotlin/translations/inspections/WrongTypeInTranslationArgsInspection.kt (revision 63357e498e49aa6e7fcbac7744b8eb35e0e727cf) @@ -22,7 +22,7 @@ import com.demonwav.mcdev.platform.mcp.mappings.getMappedClass import com.demonwav.mcdev.platform.mcp.mappings.getMappedMethod -import com.demonwav.mcdev.translations.identification.TranslationInstance +import com.demonwav.mcdev.translations.identification.TranslationIdentifier import com.demonwav.mcdev.util.findModule import com.intellij.codeInsight.intention.FileModifier import com.intellij.codeInspection.LocalQuickFix @@ -45,6 +45,7 @@ import com.siyeh.ig.psiutils.MethodCallUtils import org.jetbrains.uast.UCallExpression import org.jetbrains.uast.UElement +import org.jetbrains.uast.UExpression import org.jetbrains.uast.UIdentifier import org.jetbrains.uast.ULiteralExpression import org.jetbrains.uast.UMethod @@ -66,21 +67,13 @@ private class Visitor(private val holder: ProblemsHolder) : AbstractUastNonRecursiveVisitor() { - override fun visitElement(node: UElement): Boolean { - if (node is UReferenceExpression) { + override fun visitExpression(node: UExpression): Boolean { - doCheck(node) + doCheck(node) + return super.visitExpression(node) - } + } - return super.visitElement(node) - } - - override fun visitLiteralExpression(node: ULiteralExpression): Boolean { - doCheck(node) - return super.visitLiteralExpression(node) - } - - private fun doCheck(element: UElement) { - val result = TranslationInstance.find(element) + private fun doCheck(element: UExpression) { + val result = TranslationIdentifier.identify(element) if (result == null || result.foldingElement !is UCallExpression || result.allowArbitraryArgs) { return } Index: src/main/kotlin/translations/reference/contributors.kt =================================================================== --- src/main/kotlin/translations/reference/contributors.kt (revision 099e295015d51c4ffa78114529b41039afba7170) +++ src/main/kotlin/translations/reference/contributors.kt (revision 63357e498e49aa6e7fcbac7744b8eb35e0e727cf) @@ -21,6 +21,7 @@ package com.demonwav.mcdev.translations.reference import com.demonwav.mcdev.translations.TranslationFiles +import com.demonwav.mcdev.translations.identification.TranslationIdentifier import com.demonwav.mcdev.translations.identification.TranslationInstance import com.demonwav.mcdev.translations.lang.gen.psi.LangEntry import com.demonwav.mcdev.translations.lang.gen.psi.LangTypes @@ -36,14 +37,14 @@ import com.intellij.psi.registerUastReferenceProvider import com.intellij.psi.uastReferenceProvider import com.intellij.util.ProcessingContext -import org.jetbrains.uast.UElement +import org.jetbrains.uast.UExpression class UastReferenceContributor : PsiReferenceContributor() { override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) { registrar.registerUastReferenceProvider( { _, _ -> true }, - uastReferenceProvider { uExpr, psi -> - val translation = TranslationInstance.find(uExpr) + uastReferenceProvider { uExpr, psi -> + val translation = TranslationIdentifier.identify(uExpr) ?: return@uastReferenceProvider emptyArray() val referenceElement = translation.referenceElement ?: return@uastReferenceProvider emptyArray() Index: src/main/kotlin/util/call-uast-utils.kt =================================================================== --- src/main/kotlin/util/call-uast-utils.kt (revision 099e295015d51c4ffa78114529b41039afba7170) +++ src/main/kotlin/util/call-uast-utils.kt (revision 099e295015d51c4ffa78114529b41039afba7170) @@ -1,72 +0,0 @@ -/* - * 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.util - -import com.intellij.psi.PsiParameter -import com.intellij.psi.PsiType -import org.jetbrains.uast.UCallExpression -import org.jetbrains.uast.UExpression -import org.jetbrains.uast.UMethod -import org.jetbrains.uast.toUElementOfType -import org.jetbrains.uast.util.isArrayInitializer - -val UCallExpression.referencedMethod: UMethod? - get() = this.resolve()?.toUElementOfType() - -fun UCallExpression.extractVarArgs(index: Int, allowReferences: Boolean, allowTranslations: Boolean): Array? { - val method = this.referencedMethod - val args = this.valueArguments - if (method == null || args.size < (index + 1)) { - return emptyArray() - } - - val psiParam = method.uastParameters[index].javaPsi as? PsiParameter - ?: return null - if (!psiParam.isVarArgs) { - return arrayOf(args[index].evaluate(allowTranslations, allowReferences)) - } - - val elements = args.drop(index) - return extractVarArgs(psiParam.type, elements, allowReferences, allowTranslations) -} - -private fun extractVarArgs( - type: PsiType, - elements: List, - allowReferences: Boolean, - allowTranslations: Boolean, -): Array? { - return if (elements[0].getExpressionType() == type) { - val initializer = elements[0] - if (initializer is UCallExpression && initializer.isArrayInitializer()) { - // We're dealing with an array initializer, let's analyse it! - initializer.valueArguments - .asSequence() - .map { it.evaluate(allowReferences, allowTranslations) } - .toTypedArray() - } else { - // We're dealing with a more complex expression that results in an array, give up - return null - } - } else { - elements.asSequence().map { it.evaluate(allowReferences, allowTranslations) }.toTypedArray() - } -} Index: src/main/kotlin/util/expression-utils.kt =================================================================== --- src/main/kotlin/util/expression-utils.kt (revision 099e295015d51c4ffa78114529b41039afba7170) +++ src/main/kotlin/util/expression-utils.kt (revision 099e295015d51c4ffa78114529b41039afba7170) @@ -1,82 +0,0 @@ -/* - * 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.util - -import com.demonwav.mcdev.translations.identification.TranslationInstance -import com.demonwav.mcdev.translations.identification.TranslationInstance.Companion.FormattingError -import org.jetbrains.uast.UBinaryExpressionWithType -import org.jetbrains.uast.UCallExpression -import org.jetbrains.uast.UExpression -import org.jetbrains.uast.UQualifiedReferenceExpression -import org.jetbrains.uast.UReferenceExpression -import org.jetbrains.uast.UVariable -import org.jetbrains.uast.evaluateString -import org.jetbrains.uast.resolveToUElement -import org.jetbrains.uast.util.isTypeCast - -fun UExpression.evaluate(allowReferences: Boolean, allowTranslations: Boolean): String? { - val visited = mutableSetOf() - - fun eval(expr: UExpression?, defaultValue: String? = null): String? { - if (!visited.add(expr)) { - return defaultValue - } - - when { - expr is UBinaryExpressionWithType && expr.isTypeCast() -> - return eval(expr.operand, defaultValue) - - expr is UQualifiedReferenceExpression -> { - val selector = expr.selector - if (selector is UCallExpression) { - return eval(selector, "\${${expr.asSourceString()}}") - } - } - - expr is UReferenceExpression -> { - val reference = expr.resolveToUElement() - if (reference is UVariable && reference.uastInitializer != null) { - return eval(reference.uastInitializer, "\${${expr.asSourceString()}}") - } - } - - expr is UCallExpression && allowTranslations -> - for (argument in expr.valueArguments) { - val translation = TranslationInstance.find(argument) ?: continue - if (translation.formattingError == FormattingError.MISSING) { - return "{ERROR: Missing formatting arguments for '${translation.text}'}" - } - - return translation.text - } - - else -> expr?.evaluateString()?.let { return it } - } - - return if (allowReferences && expr != null) { - "\${${expr.asSourceString()}}" - } else { - defaultValue - } - } - - return eval(this) -} Index: src/main/kotlin/util/uast-utils.kt =================================================================== --- src/main/kotlin/util/uast-utils.kt (revision 099e295015d51c4ffa78114529b41039afba7170) +++ src/main/kotlin/util/uast-utils.kt (revision 63357e498e49aa6e7fcbac7744b8eb35e0e727cf) @@ -21,8 +21,13 @@ package com.demonwav.mcdev.util import com.intellij.psi.PsiClassType +import org.jetbrains.uast.UCallExpression import org.jetbrains.uast.UClass +import org.jetbrains.uast.UMethod import org.jetbrains.uast.UTypeReferenceExpression import org.jetbrains.uast.toUElementOfType fun UTypeReferenceExpression.resolve(): UClass? = (this.type as? PsiClassType)?.resolve().toUElementOfType() + +val UCallExpression.referencedMethod: UMethod? + get() = this.resolve()?.toUElementOfType()