IDEA-97844 Resource bundle should be associated to file even written in constant

This commit is contained in:
Dmitry Batkovich
2015-01-29 16:01:42 +03:00
parent 70f0367989
commit db3980e8a2
17 changed files with 285 additions and 22 deletions
@@ -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}
@@ -0,0 +1,3 @@
frenchKey=french
with.params=some {0} text with {1} parameters {2}
weird={0} {3}
@@ -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>
@@ -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}
@@ -0,0 +1,3 @@
frenchKey=french
with.params=some {0} text with {1} parameters {2}
weird={0} {3}
@@ -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>
@@ -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);
}
@@ -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";
@@ -104,4 +104,9 @@ public class ResourceBundleImpl extends ResourceBundle {
public String getUrl() {
return getBaseDirectory() + "/" + getBaseName();
}
@Override
public String toString() {
return "ResourceBundleImpl:" + getBaseName();
}
}