mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-97844 Resource bundle should be associated to file even written in constant
This commit is contained in:
+40
-10
@@ -169,11 +169,28 @@ public class InvalidPropertyKeyInspection extends BaseJavaLocalInspectionTool {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLiteralExpression(PsiLiteralExpression expression) {
|
||||
Object value = expression.getValue();
|
||||
if (!(value instanceof String)) return;
|
||||
String key = (String)value;
|
||||
if (isComputablePropertyExpression(expression)) return;
|
||||
public void visitReferenceExpression(PsiReferenceExpression expression) {
|
||||
final PsiElement resolvedExpression = expression.resolve();
|
||||
if (!(resolvedExpression instanceof PsiField)) {
|
||||
return;
|
||||
}
|
||||
final PsiField field = (PsiField) resolvedExpression;
|
||||
PsiExpression initializer;
|
||||
if ((initializer = field.getInitializer()) == null || !(initializer instanceof PsiLiteralExpression)) {
|
||||
return;
|
||||
}
|
||||
if (!field.hasModifierProperty(PsiModifier.FINAL)) {
|
||||
return;
|
||||
}
|
||||
final Object initializerValue = ((PsiLiteralExpression)initializer).getValue();
|
||||
if (!(initializerValue instanceof String)) {
|
||||
return;
|
||||
}
|
||||
String key = (String)initializerValue;
|
||||
visitPropertyKeyAnnotationParameter(expression, key);
|
||||
}
|
||||
|
||||
private void visitPropertyKeyAnnotationParameter(PsiExpression expression, String key) {
|
||||
Ref<String> resourceBundleName = new Ref<String>();
|
||||
if (!JavaI18nUtil.isValidPropertyReference(myManager.getProject(), expression, key, resourceBundleName)) {
|
||||
String bundleName = resourceBundleName.get();
|
||||
@@ -204,8 +221,12 @@ public class InvalidPropertyKeyInspection extends BaseJavaLocalInspectionTool {
|
||||
annotationParams.put(AnnotationUtil.PROPERTY_KEY_RESOURCE_BUNDLE_PARAMETER, null);
|
||||
if (!JavaI18nUtil.mustBePropertyKey(myManager.getProject(), expression, annotationParams)) return;
|
||||
|
||||
final int paramsCount = JavaI18nUtil.getPropertyValueParamsMaxCount(expression);
|
||||
if (paramsCount == -1) return;
|
||||
final SortedSet<Integer> paramsCount = JavaI18nUtil.getPropertyValueParamsCount(expression, resourceBundleName.get());
|
||||
if (paramsCount.isEmpty() || (paramsCount.size() != 1 && resourceBundleName.get() == null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int maxParamCount = paramsCount.last();
|
||||
|
||||
final PsiExpressionList expressions = (PsiExpressionList)expression.getParent();
|
||||
final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expressions.getParent();
|
||||
@@ -213,13 +234,13 @@ public class InvalidPropertyKeyInspection extends BaseJavaLocalInspectionTool {
|
||||
final PsiExpression[] args = expressions.getExpressions();
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i] == expression) {
|
||||
if (i + paramsCount >= args.length
|
||||
if (i + maxParamCount >= args.length
|
||||
&& method != null
|
||||
&& method.getParameterList().getParametersCount() == i + 2
|
||||
&& method.getParameterList().getParameters()[i + 1].isVarArgs()
|
||||
&& !hasArrayTypeAt(i + 1, methodCall)) {
|
||||
myProblems.add(myManager.createProblemDescriptor(methodCall,
|
||||
CodeInsightBundle.message("property.has.more.parameters.than.passed", key, paramsCount, args.length - i - 1),
|
||||
CodeInsightBundle.message("property.has.more.parameters.than.passed", key, maxParamCount, args.length - i - 1),
|
||||
onTheFly, new LocalQuickFix[0],
|
||||
ProblemHighlightType.GENERIC_ERROR));
|
||||
}
|
||||
@@ -229,9 +250,18 @@ public class InvalidPropertyKeyInspection extends BaseJavaLocalInspectionTool {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLiteralExpression(PsiLiteralExpression expression) {
|
||||
Object value = expression.getValue();
|
||||
if (!(value instanceof String)) return;
|
||||
String key = (String)value;
|
||||
if (isComputablePropertyExpression(expression)) return;
|
||||
visitPropertyKeyAnnotationParameter(expression, key);
|
||||
}
|
||||
|
||||
private static void appendPropertyKeyNotFoundProblem(@NotNull String bundleName,
|
||||
@NotNull String key,
|
||||
@NotNull PsiLiteralExpression expression,
|
||||
@NotNull PsiExpression expression,
|
||||
@NotNull InspectionManager manager,
|
||||
@NotNull List<ProblemDescriptor> problems,
|
||||
boolean onTheFly) {
|
||||
|
||||
@@ -17,8 +17,8 @@ package com.intellij.codeInspection.i18n;
|
||||
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.codeInsight.template.macro.MacroUtil;
|
||||
import com.intellij.lang.properties.IProperty;
|
||||
import com.intellij.lang.properties.PropertiesImplUtil;
|
||||
import com.intellij.lang.properties.*;
|
||||
import com.intellij.lang.properties.ResourceBundle;
|
||||
import com.intellij.lang.properties.psi.PropertiesFile;
|
||||
import com.intellij.lang.properties.psi.PropertyCreationHandler;
|
||||
import com.intellij.lang.properties.references.I18nUtil;
|
||||
@@ -69,7 +69,7 @@ public class JavaI18nUtil extends I18nUtil {
|
||||
}
|
||||
|
||||
public static boolean mustBePropertyKey(@NotNull Project project,
|
||||
@NotNull PsiLiteralExpression expression,
|
||||
@NotNull PsiExpression expression,
|
||||
@NotNull Map<String, Object> annotationAttributeValues) {
|
||||
final PsiElement parent = expression.getParent();
|
||||
if (parent instanceof PsiVariable) {
|
||||
@@ -228,7 +228,7 @@ public class JavaI18nUtil extends I18nUtil {
|
||||
}
|
||||
|
||||
public static boolean isValidPropertyReference(@NotNull Project project,
|
||||
@NotNull PsiLiteralExpression expression,
|
||||
@NotNull PsiExpression expression,
|
||||
@NotNull String key,
|
||||
@NotNull Ref<String> outResourceBundle) {
|
||||
final HashMap<String, Object> annotationAttributeValues = new HashMap<String, Object>();
|
||||
@@ -239,9 +239,30 @@ public class JavaI18nUtil extends I18nUtil {
|
||||
return false;
|
||||
}
|
||||
PsiExpression expr = (PsiExpression)resourceBundleName;
|
||||
final Object value = JavaPsiFacade.getInstance(expr.getProject()).getConstantEvaluationHelper().computeConstantExpression(expr);
|
||||
final PsiConstantEvaluationHelper constantEvaluationHelper = JavaPsiFacade.getInstance(project).getConstantEvaluationHelper();
|
||||
Object value = constantEvaluationHelper.computeConstantExpression(expr);
|
||||
if (value == null) {
|
||||
return false;
|
||||
if (expr instanceof PsiReferenceExpression) {
|
||||
final PsiElement resolve = ((PsiReferenceExpression)expr).resolve();
|
||||
if (resolve instanceof PsiField && ((PsiField)resolve).hasModifierProperty(PsiModifier.FINAL)) {
|
||||
value = constantEvaluationHelper.computeConstantExpression(((PsiField)resolve).getInitializer());
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (value == null) {
|
||||
final ResourceBundle resourceBundle = resolveResourceBundleByKey(key, project);
|
||||
if (resourceBundle == null) {
|
||||
return false;
|
||||
}
|
||||
final PropertiesFile defaultPropertiesFile = resourceBundle.getDefaultPropertiesFile();
|
||||
final String bundleName = BundleNameEvaluator.DEFAULT.evaluateBundleName(defaultPropertiesFile.getContainingFile());
|
||||
if (bundleName == null) {
|
||||
return false;
|
||||
}
|
||||
value = bundleName;
|
||||
}
|
||||
}
|
||||
String bundleName = value.toString();
|
||||
outResourceBundle.set(bundleName);
|
||||
@@ -250,7 +271,29 @@ public class JavaI18nUtil extends I18nUtil {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isPropertyRef(final PsiLiteralExpression expression, final String key, final String resourceBundleName) {
|
||||
@Nullable
|
||||
public static ResourceBundle resolveResourceBundleByKey(final @NotNull String key, final @NotNull Project project) {
|
||||
final Ref<ResourceBundle> bundleRef = Ref.create();
|
||||
final boolean r = PropertiesReferenceManager.getInstance(project).processAllPropertiesFiles(new PropertiesFileProcessor() {
|
||||
@Override
|
||||
public boolean process(String baseName, PropertiesFile propertiesFile) {
|
||||
if (propertiesFile.findPropertyByKey(key) != null) {
|
||||
if (bundleRef.get() == null) {
|
||||
bundleRef.set(propertiesFile.getResourceBundle());
|
||||
}
|
||||
else {
|
||||
if (!bundleRef.get().equals(propertiesFile.getResourceBundle())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return r ? bundleRef.get() : null;
|
||||
}
|
||||
|
||||
public static boolean isPropertyRef(final PsiExpression expression, final String key, final String resourceBundleName) {
|
||||
if (resourceBundleName == null) {
|
||||
return !PropertiesImplUtil.findPropertiesByKey(expression.getProject(), key).isEmpty();
|
||||
}
|
||||
@@ -339,19 +382,54 @@ public class JavaI18nUtil extends I18nUtil {
|
||||
* @param expression i18n literal
|
||||
* @return number of parameters
|
||||
*/
|
||||
public static int getPropertyValueParamsMaxCount(final @NotNull PsiLiteralExpression expression) {
|
||||
int maxCount = -1;
|
||||
for (PsiReference reference : expression.getReferences()) {
|
||||
public static int getPropertyValueParamsMaxCount(final @NotNull PsiExpression expression) {
|
||||
return getPropertyValueParamsMaxCount(expression, null);
|
||||
}
|
||||
|
||||
public static int getPropertyValueParamsMaxCount(final @NotNull PsiExpression expression, final @Nullable String resourceBundleName) {
|
||||
final SortedSet<Integer> paramsCount = getPropertyValueParamsCount(expression, resourceBundleName);
|
||||
if (paramsCount.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
return paramsCount.last();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static SortedSet<Integer> getPropertyValueParamsCount(final @NotNull PsiExpression expression, final @Nullable String resourceBundleName) {
|
||||
final PsiLiteralExpression literalExpression;
|
||||
if (expression instanceof PsiLiteralExpression) {
|
||||
literalExpression = (PsiLiteralExpression)expression;
|
||||
} else if (expression instanceof PsiReferenceExpression) {
|
||||
final PsiElement resolved = ((PsiReferenceExpression)expression).resolve();
|
||||
final PsiField field = resolved == null ? null : (PsiField)resolved;
|
||||
literalExpression =
|
||||
field != null && field.hasModifierProperty(PsiModifier.FINAL) && field.getInitializer() instanceof PsiLiteralExpression
|
||||
? (PsiLiteralExpression)field.getInitializer()
|
||||
: null;
|
||||
} else {
|
||||
literalExpression = null;
|
||||
}
|
||||
final TreeSet<Integer> paramsCount = new TreeSet<Integer>();
|
||||
if (literalExpression == null) {
|
||||
return paramsCount;
|
||||
}
|
||||
for (PsiReference reference : literalExpression.getReferences()) {
|
||||
if (reference instanceof PsiPolyVariantReference) {
|
||||
for (ResolveResult result : ((PsiPolyVariantReference)reference).multiResolve(false)) {
|
||||
if (result.isValidResult() && result.getElement() instanceof IProperty) {
|
||||
try {
|
||||
final IProperty property = (IProperty)result.getElement();
|
||||
if (resourceBundleName != null) {
|
||||
final PsiFile file = property.getPropertiesFile().getContainingFile();
|
||||
if (!resourceBundleName.equals(BundleNameEvaluator.DEFAULT.evaluateBundleName(file))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
final String propertyValue = property.getValue();
|
||||
if (propertyValue == null) {
|
||||
continue;
|
||||
}
|
||||
maxCount = Math.max(maxCount, getPropertyValuePlaceholdersCount(propertyValue));
|
||||
paramsCount.add(getPropertyValuePlaceholdersCount(propertyValue));
|
||||
}
|
||||
catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
@@ -359,6 +437,6 @@ public class JavaI18nUtil extends I18nUtil {
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxCount;
|
||||
return paramsCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<problems>
|
||||
<problem>
|
||||
<file>Test.java</file>
|
||||
<line>22</line>
|
||||
<description>String literal 'defaultKey' doesn't appear to be valid property key</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>Test.java</file>
|
||||
<line>24</line>
|
||||
<description>Property 'with.params' expected 5 parameters, passed 3</description>
|
||||
</problem>
|
||||
</problems>
|
||||
@@ -0,0 +1,3 @@
|
||||
defaultKey=default
|
||||
with.params=some {0} text with {1} parameters {2}
|
||||
weird={0} {3}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
frenchKey=french
|
||||
with.params=some {0} text with {1} parameters {2}
|
||||
weird={0} {3}
|
||||
+1
@@ -0,0 +1 @@
|
||||
with.params=some {0} text with {1} parameters {2} {3} {4}
|
||||
@@ -0,0 +1,28 @@
|
||||
package x;
|
||||
import org.jetbrains.annotations.PropertyKey;
|
||||
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.PropertyKey;
|
||||
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
class IBundle {
|
||||
|
||||
public static String message(@org.jetbrains.annotations.NotNull @PropertyKey(resourceBundle = BUNDLE) String key,
|
||||
@org.jetbrains.annotations.NotNull Object... params) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@NonNls
|
||||
public static final String BUNDLE = "x.Foo";
|
||||
private static final ResourceBundle ourBundle = ResourceBundle.getBundle(bundleClassName);
|
||||
}
|
||||
|
||||
public class Test {
|
||||
String s0 = IBundle.message("defaultKey");
|
||||
|
||||
String ss4 = IBundle.message("with.params", 1, 2, 3);
|
||||
|
||||
String f1(@PropertyKey(resourceBundle = IBundle.BUNDLE) String s, Object...params) {return "";}
|
||||
String f2(@PropertyKey(resourceBundle = IBundle.BUNDLE) String s) {return "";}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<problems>
|
||||
<problem>
|
||||
<file>Test.java</file>
|
||||
<line>9</line>
|
||||
<description>Property 'with.params2' expected 3 parameters, passed 1</description>
|
||||
</problem>
|
||||
</problems>
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
defaultKey=default
|
||||
with.params=some {0} text with {1} parameters {2}
|
||||
with.params2=some {0} text with {1} parameters {2}
|
||||
weird={0} {3}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
frenchKey=french
|
||||
with.params=some {0} text with {1} parameters {2}
|
||||
weird={0} {3}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
weird={0} {3} {666}
|
||||
with.params=some {0}
|
||||
@@ -0,0 +1,37 @@
|
||||
package x;
|
||||
import org.jetbrains.annotations.PropertyKey;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
public class Test {
|
||||
/**
|
||||
* exists only in "Bu" resource bundle => can resolve
|
||||
*/
|
||||
String s3 = IBundle.message("with.params2", "a");
|
||||
|
||||
/**
|
||||
* no way to determine source of "with.params" property, because this key exists in both of resource bundles
|
||||
*/
|
||||
String ss1 = f1("with.params");
|
||||
String ss2 = f1("with.params", "", "", "");
|
||||
String ss3 = f2("with.params");
|
||||
String ss4 = IBundle.message("with.params", new Object[3]); // don't check if array passed
|
||||
|
||||
String f1(@PropertyKey(resourceBundle = IBundle.BUNDLE) String s, Object...params) {return "";}
|
||||
String f2(@PropertyKey(resourceBundle = IBundle.BUNDLE) String s) {return "";}
|
||||
}
|
||||
|
||||
class IBundle {
|
||||
|
||||
public static String message(@org.jetbrains.annotations.NotNull @PropertyKey(resourceBundle = BUNDLE) String key,
|
||||
@org.jetbrains.annotations.NotNull Object... params) {
|
||||
return "";
|
||||
}
|
||||
|
||||
/*
|
||||
* not final field -- there is no way to determine value
|
||||
*/
|
||||
@NonNls
|
||||
public static String BUNDLE = "x.Baz";
|
||||
private static final ResourceBundle ourBundle = ResourceBundle.getBundle(bundleClassName);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<problems>
|
||||
<problem>
|
||||
<file>Test.java</file>
|
||||
<line>12</line>
|
||||
<description>Property 'with.params' expected 4 parameters, passed 1</description>
|
||||
</problem>
|
||||
</problems>
|
||||
+1
@@ -0,0 +1 @@
|
||||
with.params=some {0} text {1} with {2} params {3}
|
||||
@@ -0,0 +1,27 @@
|
||||
package x;
|
||||
|
||||
import java.lang.String;
|
||||
import org.jetbrains.annotations.PropertyKey;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
public class Test {
|
||||
|
||||
private final String keyNameField = "with.params";
|
||||
|
||||
void m() {
|
||||
String sss = IBundle.message(keyNameField, "");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class IBundle {
|
||||
|
||||
public static String message(@org.jetbrains.annotations.NotNull @PropertyKey(resourceBundle = BUNDLE) String key,
|
||||
@org.jetbrains.annotations.NotNull Object... params) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@NonNls
|
||||
public static final String BUNDLE = "x.Bu";
|
||||
private static final ResourceBundle ourBundle = ResourceBundle.getBundle(bundleClassName);
|
||||
}
|
||||
+12
@@ -26,6 +26,18 @@ public class InvalidPropertyKeyInspectionTest extends InspectionTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testImplicit() throws Exception {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testImplicit2() throws Exception {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testImplicit3() throws Exception {
|
||||
doTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTestDataPath() {
|
||||
return PluginPathManager.getPluginHomePath("java-i18n") + "/testData/inspections";
|
||||
|
||||
+5
@@ -104,4 +104,9 @@ public class ResourceBundleImpl extends ResourceBundle {
|
||||
public String getUrl() {
|
||||
return getBaseDirectory() + "/" + getBaseName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ResourceBundleImpl:" + getBaseName();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user