diff --git a/plugins/java-i18n/src/com/intellij/codeInspection/i18n/InvalidPropertyKeyInspection.java b/plugins/java-i18n/src/com/intellij/codeInspection/i18n/InvalidPropertyKeyInspection.java index b0b36a045fae..c1cefff1c00f 100644 --- a/plugins/java-i18n/src/com/intellij/codeInspection/i18n/InvalidPropertyKeyInspection.java +++ b/plugins/java-i18n/src/com/intellij/codeInspection/i18n/InvalidPropertyKeyInspection.java @@ -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 resourceBundleName = new Ref(); 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 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 problems, boolean onTheFly) { diff --git a/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaI18nUtil.java b/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaI18nUtil.java index 0d705dc8a47b..e55198224ef9 100644 --- a/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaI18nUtil.java +++ b/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaI18nUtil.java @@ -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 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 outResourceBundle) { final HashMap annotationAttributeValues = new HashMap(); @@ -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 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 paramsCount = getPropertyValueParamsCount(expression, resourceBundleName); + if (paramsCount.isEmpty()) { + return -1; + } + return paramsCount.last(); + } + + @NotNull + public static SortedSet 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 paramsCount = new TreeSet(); + 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; } } diff --git a/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit/expected.xml b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit/expected.xml new file mode 100644 index 000000000000..75e5e35e49e9 --- /dev/null +++ b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit/expected.xml @@ -0,0 +1,13 @@ + + + + Test.java + 22 + String literal 'defaultKey' doesn't appear to be valid property key + + + Test.java + 24 + Property 'with.params' expected 5 parameters, passed 3 + + diff --git a/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit/src/x/Bu.properties b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit/src/x/Bu.properties new file mode 100644 index 000000000000..7efee976cf73 --- /dev/null +++ b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit/src/x/Bu.properties @@ -0,0 +1,3 @@ +defaultKey=default +with.params=some {0} text with {1} parameters {2} +weird={0} {3} \ No newline at end of file diff --git a/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit/src/x/Bu_fr.properties b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit/src/x/Bu_fr.properties new file mode 100644 index 000000000000..31d2bcef49f7 --- /dev/null +++ b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit/src/x/Bu_fr.properties @@ -0,0 +1,3 @@ +frenchKey=french +with.params=some {0} text with {1} parameters {2} +weird={0} {3} \ No newline at end of file diff --git a/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit/src/x/Foo.properties b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit/src/x/Foo.properties new file mode 100644 index 000000000000..df3f96bfd38f --- /dev/null +++ b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit/src/x/Foo.properties @@ -0,0 +1 @@ +with.params=some {0} text with {1} parameters {2} {3} {4} diff --git a/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit/src/x/Test.java b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit/src/x/Test.java new file mode 100644 index 000000000000..05b20cc87ce5 --- /dev/null +++ b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit/src/x/Test.java @@ -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 "";} +} diff --git a/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit2/expected.xml b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit2/expected.xml new file mode 100644 index 000000000000..1e28cf6f8a89 --- /dev/null +++ b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit2/expected.xml @@ -0,0 +1,8 @@ + + + + Test.java + 9 + Property 'with.params2' expected 3 parameters, passed 1 + + diff --git a/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit2/src/x/Bu.properties b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit2/src/x/Bu.properties new file mode 100644 index 000000000000..dfad3d2186a4 --- /dev/null +++ b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit2/src/x/Bu.properties @@ -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} \ No newline at end of file diff --git a/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit2/src/x/Bu_fr.properties b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit2/src/x/Bu_fr.properties new file mode 100644 index 000000000000..31d2bcef49f7 --- /dev/null +++ b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit2/src/x/Bu_fr.properties @@ -0,0 +1,3 @@ +frenchKey=french +with.params=some {0} text with {1} parameters {2} +weird={0} {3} \ No newline at end of file diff --git a/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit2/src/x/Foo.properties b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit2/src/x/Foo.properties new file mode 100644 index 000000000000..c261aab7dd19 --- /dev/null +++ b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit2/src/x/Foo.properties @@ -0,0 +1,2 @@ +weird={0} {3} {666} +with.params=some {0} diff --git a/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit2/src/x/Test.java b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit2/src/x/Test.java new file mode 100644 index 000000000000..b5a479d3444e --- /dev/null +++ b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit2/src/x/Test.java @@ -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); +} + diff --git a/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit3/expected.xml b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit3/expected.xml new file mode 100644 index 000000000000..a92ba3110868 --- /dev/null +++ b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit3/expected.xml @@ -0,0 +1,8 @@ + + + + Test.java + 12 + Property 'with.params' expected 4 parameters, passed 1 + + diff --git a/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit3/src/x/Bu.properties b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit3/src/x/Bu.properties new file mode 100644 index 000000000000..b6e833d4ff74 --- /dev/null +++ b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit3/src/x/Bu.properties @@ -0,0 +1 @@ +with.params=some {0} text {1} with {2} params {3} \ No newline at end of file diff --git a/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit3/src/x/Test.java b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit3/src/x/Test.java new file mode 100644 index 000000000000..c498178a3235 --- /dev/null +++ b/plugins/java-i18n/testData/inspections/invalidPropertyKey/implicit3/src/x/Test.java @@ -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); +} \ No newline at end of file diff --git a/plugins/java-i18n/testSrc/com/intellij/codeInspection/i18n/InvalidPropertyKeyInspectionTest.java b/plugins/java-i18n/testSrc/com/intellij/codeInspection/i18n/InvalidPropertyKeyInspectionTest.java index e5e9c9695f6a..03045d8a7408 100644 --- a/plugins/java-i18n/testSrc/com/intellij/codeInspection/i18n/InvalidPropertyKeyInspectionTest.java +++ b/plugins/java-i18n/testSrc/com/intellij/codeInspection/i18n/InvalidPropertyKeyInspectionTest.java @@ -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"; diff --git a/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/ResourceBundleImpl.java b/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/ResourceBundleImpl.java index 44d6fa8c1e6e..b12d18778360 100644 --- a/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/ResourceBundleImpl.java +++ b/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/ResourceBundleImpl.java @@ -104,4 +104,9 @@ public class ResourceBundleImpl extends ResourceBundle { public String getUrl() { return getBaseDirectory() + "/" + getBaseName(); } + + @Override + public String toString() { + return "ResourceBundleImpl:" + getBaseName(); + } } \ No newline at end of file