[java, property] remove folded substitutions for property key values IDEA-369920

GitOrigin-RevId: f49686dd5053e4f1f4b90a2a19145aff0daa0a04
This commit is contained in:
Aleksey Dobrynin
2025-04-16 13:45:51 +02:00
committed by intellij-monorepo-bot
parent d9e548bb55
commit 2096dae2b9
10 changed files with 0 additions and 311 deletions

View File

@@ -37,8 +37,6 @@
<lang.foldingBuilder language="UAST" implementationClass="com.intellij.codeInspection.i18n.folding.PropertyFoldingBuilder"
order="FIRST"/>
<lang.foldingBuilder language="Properties" implementationClass="com.intellij.lang.properties.ResourceBundleContextFoldingBuilder"/>
<gotoDeclarationHandler implementation="com.intellij.codeInspection.i18n.folding.I18nMessageGotoDeclarationHandler" order="FIRST"/>
<inlineActionHandler implementation="com.intellij.refactoring.inline.InlinePropertyHandler"/>
@@ -98,9 +96,6 @@
<completion.confidence language="UAST" implementationClass="com.intellij.lang.properties.PropertyKeyLiteralConfidence" id="propertyKeys" order="before javaSkipAutopopupInStrings"/>
<applicationService serviceImplementation="com.intellij.lang.properties.PropertiesFoldingSettings"/>
<codeFoldingOptionsProvider instance="com.intellij.lang.properties.PropertiesFoldingOptionsProvider"/>
<fileIconProvider implementation="com.intellij.java.i18n.MessagesFileIconProvider"/>
</extensions>

View File

@@ -97,8 +97,6 @@ capitalization.kind.sentence=sentence
value.column.name=Value
key.column.name=Key
command.name.edit.property.value=Edit Property Value
# suppress inspection "DevKitPropertiesQuotesValidation"
checkbox.fold.to.context=Fold '{0}', '{1}', ... placeholders to corresponding context expressions from Java/Kotlin code
inspection.dialog.title.capitalization.display.name=Incorrect string capitalization
inspection.suspicious.locales.languages.display.name=Suspicious resource bundle locale languages
inspection.non.basic.latin.character.display.name=Non-Basic Latin character

View File

@@ -1,19 +0,0 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.lang.properties;
import com.intellij.application.options.editor.CodeFoldingOptionsProvider;
import com.intellij.java.i18n.JavaI18nBundle;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.options.BeanConfigurable;
@State(name = "PropertiesFoldingSettings", storages = @Storage("editor.xml"))
public final class PropertiesFoldingOptionsProvider extends BeanConfigurable<PropertiesFoldingSettings> implements CodeFoldingOptionsProvider {
public PropertiesFoldingOptionsProvider() {
super(PropertiesFoldingSettings.getInstance(), PropertiesFileType.INSTANCE.getDescription());
PropertiesFoldingSettings settings = getInstance();
checkBox(JavaI18nBundle.message("checkbox.fold.to.context"), settings::isFoldPlaceholdersToContext, settings::setFoldPlaceholdersToContext);
}
}

View File

@@ -1,37 +0,0 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.lang.properties;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.SettingsCategory;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.util.xmlb.XmlSerializerUtil;
import org.jetbrains.annotations.NotNull;
@State(name = "PropertiesFoldingSettings", storages = @Storage("editor.xml"), category = SettingsCategory.CODE)
public class PropertiesFoldingSettings implements PersistentStateComponent<PropertiesFoldingSettings> {
private boolean myFoldPlaceholdersToContext;
public static PropertiesFoldingSettings getInstance() {
return ApplicationManager.getApplication().getService(PropertiesFoldingSettings.class);
}
public void setFoldPlaceholdersToContext(boolean fold) {
myFoldPlaceholdersToContext = fold;
}
public boolean isFoldPlaceholdersToContext() {
return myFoldPlaceholdersToContext;
}
@Override
public PropertiesFoldingSettings getState() {
return this;
}
@Override
public void loadState(final @NotNull PropertiesFoldingSettings state) {
XmlSerializerUtil.copyBean(state, this);
}
}

View File

@@ -1,139 +0,0 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.lang.properties;
import com.intellij.lang.ASTNode;
import com.intellij.lang.folding.FoldingBuilderEx;
import com.intellij.lang.folding.FoldingDescriptor;
import com.intellij.lang.properties.psi.PropertiesFile;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.progress.EmptyProgressIndicator;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Folds '{@code {n}}' placeholders in .properties file to corresponding arguments from Java code.
*
* For example, given two files:
*
* <pre>
* <b>myBundle.properties</b>:
* {@code extract.name=Extract {0} name to {1} {2}}
* <b>A.java</b>:
* {@code MyBundle.message("extract.name", "method", "interface", "I.java")}
* </pre>
* This builder will produce these foldings:
* <pre>
* <b>myBundle.properties</b>:
* {@code extract.name=Extract +method+ name to +interface+ +I.java+}</pre>
*/
public final class ResourceBundleContextFoldingBuilder extends FoldingBuilderEx {
@Override
public FoldingDescriptor @NotNull [] buildFoldRegions(@NotNull PsiElement root, @NotNull Document document, boolean quick) {
if (!PropertiesFoldingSettings.getInstance().isFoldPlaceholdersToContext()) {
return FoldingDescriptor.EMPTY_ARRAY;
}
List<FoldingDescriptor> result = new ArrayList<>();
for (IProperty property : ((PropertiesFile)root).getProperties()) {
String value = property.getValue();
if (value != null && value.contains("{0}")) {
fold(property, result);
}
}
return result.toArray(FoldingDescriptor.EMPTY_ARRAY);
}
private static void fold(@NotNull IProperty property, @NotNull List<? super FoldingDescriptor> result) {
ProgressManager.getInstance().executeProcessUnderProgress(() ->
ReferencesSearch.search(property.getPsiElement()).forEach((PsiReference reference) -> !tryToFoldReference(reference, property, result)),
getOrCreateIndicator());
}
private static @NotNull ProgressIndicator getOrCreateIndicator() {
ProgressIndicator progress = ProgressIndicatorProvider.getGlobalProgressIndicator();
if (progress == null) {
progress = new EmptyProgressIndicator();
progress.start();
}
progress.setIndeterminate(false);
return progress;
}
// return true if folded successfully
private static boolean tryToFoldReference(@NotNull PsiReference reference,
@NotNull IProperty property,
@NotNull List<? super FoldingDescriptor> result) {
int before = result.size();
PsiElement referenceElement = reference.getElement();
// all arguments[i+1..] are considered template arguments
String key = property.getUnescapedKey();
if (key == null) return false;
PsiElement psiElement = property.getPsiElement();
String text = psiElement.getText();
PsiElement[] arguments = referenceElement.getLanguage().getID().equals("kotlin") ?
getKotlinArguments(referenceElement) : getJavaArguments(referenceElement);
if (arguments == null) return false;
for (int i=0; i<arguments.length; i++) {
PsiElement argument = arguments[i];
String templateText = "{" + i + "}";
int offset = text.indexOf(templateText, key.length());
if (offset != -1) {
int start = psiElement.getTextRange().getStartOffset();
result.add(new FoldingDescriptor(psiElement, start+ offset, start+offset+templateText.length(), null, (argument.getText())));
}
}
return result.size() != before;
}
/**
* return arguments of the method referencing this property.
* E.g. for {@code MyBundle.message("prop", expr1, argX)} return (expr1, argX)
*/
private static PsiElement[] getJavaArguments(@NotNull PsiElement referenceElement) {
PsiExpression expression = PsiTreeUtil.getParentOfType(referenceElement, PsiExpression.class, false);
if (expression == null) return null;
PsiElement parent = expression.getParent();
if (!(parent instanceof PsiExpressionList)) return null;
PsiExpression[] arguments = ((PsiExpressionList)parent).getExpressions();
int i = ArrayUtil.indexOf(arguments, expression);
if (i == -1) return null;
return Arrays.copyOfRange(arguments, i + 1, arguments.length, PsiElement[].class);
}
// since kotlin and java method calls have different psi structure and no common UAST,
// we have to have two methods for finding Java/Kotlin code referencing this property
private static PsiElement[] getKotlinArguments(@NotNull PsiElement referenceElement) {
PsiElement expression = referenceElement.getParent();
if (expression == null) return null;
List<PsiElement> arguments = new ArrayList<>();
for (PsiElement e = expression.getNextSibling(); e != null; e = e.getNextSibling()) {
if (e instanceof PsiComment || e instanceof LeafPsiElement) continue;
arguments.add(e);
}
if (arguments.isEmpty()) {
return null;
}
return arguments.toArray(PsiElement.EMPTY_ARRAY);
}
@Override
public boolean isCollapsedByDefault(@NotNull ASTNode node) {
return true;
}
@Override
public @Nullable String getPlaceholderText(@NotNull ASTNode node) {
return null;
}
}

View File

@@ -1,15 +0,0 @@
import org.jetbrains.annotations.PropertyKey;
import java.util.ResourceBundle;
public class MyClass {
private final static String BUNDLE_NAME = "i18n";
private final static ResourceBundle BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME);
public static String getMessage(@PropertyKey(resourceBundle = BUNDLE_NAME) String key, Object... args) {
return java.text.MessageFormat.format(BUNDLE.getString(key), args);
}
public static void main(String[] args) {
getMessage("com.example.welcomeMessage2", "our App", BUNDLE_NAME);
}
}

View File

@@ -1,17 +0,0 @@
import java.util.ResourceBundle;
public class MyClass {
private final static String BUNDLE_NAME = "i18n";
private final static ResourceBundle BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME);
public static String getMessage(@org.jetbrains.annotations.PropertyKey(resourceBundle = BUNDLE_NAME) String key, Object... args) {
return java.text.MessageFormat.format(BUNDLE.getString(key), args);
}
public static void main(String[] args) {
int i = 0;
i++;
System.out.println(i);
<selection>MyClass.getMessage("com.example.welcomeMessage3", "our App", BUNDLE_NAME)</selection>;
}
}

View File

@@ -1,2 +0,0 @@
com.example.welcomeMessage2=Welcome to <fold text='"our App"' expand='false'>{0}</fold> by <fold text='BUNDLE_NAME' expand='false'>{1}</fold>
com.example.welcomeMessage3=Welcome

View File

@@ -1,2 +0,0 @@
com.example.welcomeMessage2=Welcome to {0} by {1}
com.example.welcomeMessage3=Welcome

View File

@@ -1,73 +0,0 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.folding.impl;
import com.intellij.codeInsight.folding.JavaCodeFoldingSettings;
import com.intellij.lang.properties.PropertiesFoldingSettings;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiFile;
import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.testFramework.fixtures.BasePlatformTestCase;
import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class PropertiesContextFoldingTest extends BasePlatformTestCase {
private boolean old;
@Override
protected String getTestDataPath() {
return PlatformTestUtil.getCommunityPath() + "/plugins/java-i18n/testData/contextFolding";
}
@Override
protected void setUp() throws Exception {
super.setUp();
old = PropertiesFoldingSettings.getInstance().isFoldPlaceholdersToContext();
PropertiesFoldingSettings.getInstance().setFoldPlaceholdersToContext(true);
}
@Override
protected void tearDown() throws Exception {
try {
PropertiesFoldingSettings.getInstance().setFoldPlaceholdersToContext(old);
}
catch (Throwable e) {
addSuppressedException(e);
}
finally {
super.tearDown();
}
}
public void testPlaceholdersInPropertiesAreFolded() throws IOException {
myFixture.configureByFile("MyClass.java");
myFixture.configureByFile("i18n.properties");
String actual = StringUtil.convertLineSeparators(((CodeInsightTestFixtureImpl)myFixture).getFoldingDescription(true));
String expected = StringUtil.convertLineSeparators(FileUtil.loadFile(new File(myFixture.getTestDataPath(), "i18n.after")));
assertEquals(expected, actual);
}
public void testRangesInJava() {
JavaCodeFoldingSettings foldingSettings = JavaCodeFoldingSettings.getInstance();
boolean collapseMethods = foldingSettings.isCollapseMethods();
boolean i18nMessages = foldingSettings.isCollapseI18nMessages();
try {
foldingSettings.setCollapseMethods(false);
foldingSettings.setCollapseI18nMessages(true);
myFixture.copyFileToProject("i18n.properties");
PsiFile file = myFixture.configureByFile("MyClass1.java");
List<FoldingUpdate.RegionInfo> foldings = FoldingUpdate.getFoldingsFor(file, false);
assertFalse(foldings.stream().map(info -> info.descriptor).anyMatch(descriptor -> descriptor.getPlaceholderText().equals("\"Welcome\"")));
}
finally {
foldingSettings.setCollapseMethods(collapseMethods);
foldingSettings.setCollapseI18nMessages(i18nMessages);
}
}
}