diff --git a/idea/resources/inspectionDescriptions/KotlinInvalidBundleOrPropertyInspection.html b/idea/resources/inspectionDescriptions/KotlinInvalidBundleOrPropertyInspection.html
new file mode 100644
index 00000000000..30833822ac8
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/KotlinInvalidBundleOrPropertyInspection.html
@@ -0,0 +1,5 @@
+
+
+This inspection verifies that arguments passed to functions with parameters annotated as @PropertyKey are valid property keys in the respective properties files. It also verifies that the resourceBundle argument of the @PropertyKey annotation is an existing resource bundle.
+
+
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index 53a61b4ae30..73595400b8a 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -1191,6 +1191,13 @@
level="WARNING"
/>
+
+
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinInvalidBundleOrPropertyInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinInvalidBundleOrPropertyInspection.kt
new file mode 100644
index 00000000000..4bd2c898b30
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinInvalidBundleOrPropertyInspection.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2010-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.kotlin.idea.inspections
+
+import com.intellij.codeInsight.CodeInsightBundle
+import com.intellij.codeInspection.ProblemHighlightType
+import com.intellij.codeInspection.ProblemsHolder
+import com.intellij.codeInspection.i18n.JavaI18nUtil
+import com.intellij.lang.properties.ResourceBundleReference
+import com.intellij.lang.properties.psi.Property
+import com.intellij.lang.properties.references.PropertyReference
+import com.intellij.openapi.util.TextRange
+import com.intellij.psi.PsiElementVisitor
+import org.jetbrains.kotlin.idea.caches.resolve.analyze
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
+import org.jetbrains.kotlin.psi.psiUtil.parents
+import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
+import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
+import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument
+import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
+import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
+
+class KotlinInvalidBundleOrPropertyInspection : AbstractKotlinInspection() {
+ override fun getDisplayName() = CodeInsightBundle.message("inspection.unresolved.property.key.reference.name")
+
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
+ return object : JetVisitorVoid() {
+ private fun processResourceBundleReference(ref: ResourceBundleReference, template: JetStringTemplateExpression) {
+ if (ref.resolve() == null) {
+ holder.registerProblem(
+ template,
+ CodeInsightBundle.message("inspection.invalid.resource.bundle.reference", ref.canonicalText),
+ ProblemHighlightType.LIKE_UNKNOWN_SYMBOL,
+ TextRange(0, template.textLength)
+ )
+ }
+ }
+
+ private fun processPropertyReference(ref: PropertyReference, template: JetStringTemplateExpression) {
+ val property = ref.resolve() as? Property
+ if (property == null) {
+ holder.registerProblem(
+ template,
+ CodeInsightBundle.message("inspection.unresolved.property.key.reference.message", ref.canonicalText),
+ ProblemHighlightType.LIKE_UNKNOWN_SYMBOL,
+ TextRange(0, template.textLength),
+ *ref.quickFixes
+ )
+ return
+ }
+
+ val argument = template.parents.firstIsInstanceOrNull() ?: return
+ if (argument.getArgumentExpression() != JetPsiUtil.deparenthesize(template) ) return
+
+ val callExpression = argument.getStrictParentOfType() ?: return
+ val resolvedCall = callExpression.getResolvedCall(callExpression.analyze(BodyResolveMode.PARTIAL)) ?: return
+
+ val resolvedArguments = resolvedCall.valueArgumentsByIndex ?: return
+ val keyArgumentIndex = resolvedArguments.indexOfFirst { it is ExpressionValueArgument && it.valueArgument == argument }
+ if (keyArgumentIndex < 0) return
+
+ val callable = resolvedCall.resultingDescriptor
+ if (callable.valueParameters.size() != keyArgumentIndex + 2) return
+
+ val messageArgument = resolvedArguments[keyArgumentIndex + 1] as? VarargValueArgument ?: return
+ if (messageArgument.arguments.singleOrNull()?.getSpreadElement() != null) return
+
+ val expectedArgumentCount = JavaI18nUtil.getPropertyValuePlaceholdersCount(property.value ?: "")
+ val actualArgumentCount = messageArgument.arguments.size()
+ if (actualArgumentCount < expectedArgumentCount) {
+ val description = CodeInsightBundle.message(
+ "property.has.more.parameters.than.passed",
+ ref.canonicalText, expectedArgumentCount, actualArgumentCount
+ )
+ holder.registerProblem(template, description, ProblemHighlightType.GENERIC_ERROR)
+ }
+ }
+
+ override fun visitStringTemplateExpression(expression: JetStringTemplateExpression) {
+ for (ref in expression.references) {
+ when (ref) {
+ is ResourceBundleReference -> processResourceBundleReference(ref, expression)
+ is PropertyReference -> processPropertyReference(ref, expression)
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/multiFileInspections/invalidBundleOrProperty/before/PropertyKey.kt b/idea/testData/multiFileInspections/invalidBundleOrProperty/before/PropertyKey.kt
new file mode 100644
index 00000000000..1e1932b2d0f
--- /dev/null
+++ b/idea/testData/multiFileInspections/invalidBundleOrProperty/before/PropertyKey.kt
@@ -0,0 +1,5 @@
+package org.jetbrains.annotations
+
+@Retention(AnnotationRetention.BINARY)
+@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.LOCAL_VARIABLE, AnnotationTarget.FIELD)
+public annotation class PropertyKey(public val resourceBundle: String)
\ No newline at end of file
diff --git a/idea/testData/multiFileInspections/invalidBundleOrProperty/before/TestBundle.properties b/idea/testData/multiFileInspections/invalidBundleOrProperty/before/TestBundle.properties
new file mode 100644
index 00000000000..d64999474d5
--- /dev/null
+++ b/idea/testData/multiFileInspections/invalidBundleOrProperty/before/TestBundle.properties
@@ -0,0 +1,2 @@
+foo.bar = test: {0}
+foo.bar2 = test: {0}, {1}
\ No newline at end of file
diff --git a/idea/testData/multiFileInspections/invalidBundleOrProperty/before/jMessage.java b/idea/testData/multiFileInspections/invalidBundleOrProperty/before/jMessage.java
new file mode 100644
index 00000000000..b90940210fa
--- /dev/null
+++ b/idea/testData/multiFileInspections/invalidBundleOrProperty/before/jMessage.java
@@ -0,0 +1,7 @@
+import org.jetbrains.annotations.PropertyKey;
+
+class J {
+ static String message(@PropertyKey(resourceBundle = "TestBundle") String key, Object... args) {
+ return key;
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/multiFileInspections/invalidBundleOrProperty/before/kMessage.kt b/idea/testData/multiFileInspections/invalidBundleOrProperty/before/kMessage.kt
new file mode 100644
index 00000000000..6323c3dc9f5
--- /dev/null
+++ b/idea/testData/multiFileInspections/invalidBundleOrProperty/before/kMessage.kt
@@ -0,0 +1,6 @@
+import org.jetbrains.annotations.PropertyKey
+
+object K {
+ fun message(@PropertyKey(resourceBundle = "TestBundle") key: String, vararg args: Any) = key
+ fun message2(@PropertyKey(resourceBundle = "TestBundle") key: String, n: Int, vararg args: Any) = key
+}
\ No newline at end of file
diff --git a/idea/testData/multiFileInspections/invalidBundleOrProperty/before/tooFewArguments.kt b/idea/testData/multiFileInspections/invalidBundleOrProperty/before/tooFewArguments.kt
new file mode 100644
index 00000000000..71ce3f85fd0
--- /dev/null
+++ b/idea/testData/multiFileInspections/invalidBundleOrProperty/before/tooFewArguments.kt
@@ -0,0 +1,7 @@
+fun test() {
+ K.message("foo.bar")
+ J.message("foo.bar")
+ K.message(key = "foo.bar", args = 1)
+ K.message("foo.bar2", *arrayOf(1, 2))
+ K.message2("foo.bar", 1)
+}
\ No newline at end of file
diff --git a/idea/testData/multiFileInspections/invalidBundleOrProperty/before/unresolvedBundleReference.kt b/idea/testData/multiFileInspections/invalidBundleOrProperty/before/unresolvedBundleReference.kt
new file mode 100644
index 00000000000..af9b9058fa6
--- /dev/null
+++ b/idea/testData/multiFileInspections/invalidBundleOrProperty/before/unresolvedBundleReference.kt
@@ -0,0 +1,6 @@
+import org.jetbrains.annotations.PropertyKey
+
+private val TEST_BUNDLE2 = "TestBundle2"
+
+fun message(@PropertyKey(resourceBundle = "TestBundle2") key: String, vararg args: Any) = key
+fun message2(@PropertyKey(resourceBundle = TEST_BUNDLE2) key: String, vararg args: Any) = key
\ No newline at end of file
diff --git a/idea/testData/multiFileInspections/invalidBundleOrProperty/before/unresolvedPropertyReference.kt b/idea/testData/multiFileInspections/invalidBundleOrProperty/before/unresolvedPropertyReference.kt
new file mode 100644
index 00000000000..cac6edbf9b7
--- /dev/null
+++ b/idea/testData/multiFileInspections/invalidBundleOrProperty/before/unresolvedPropertyReference.kt
@@ -0,0 +1,6 @@
+fun test() {
+ K.message("foo.baz", "arg")
+ K.message("foo.baz")
+ J.message("foo.baz", "arg")
+ J.message("foo.baz")
+}
\ No newline at end of file
diff --git a/idea/testData/multiFileInspections/invalidBundleOrProperty/before/validPropertyKeys.kt b/idea/testData/multiFileInspections/invalidBundleOrProperty/before/validPropertyKeys.kt
new file mode 100644
index 00000000000..b9c2e1523cc
--- /dev/null
+++ b/idea/testData/multiFileInspections/invalidBundleOrProperty/before/validPropertyKeys.kt
@@ -0,0 +1,6 @@
+fun test() {
+ K.message("foo.bar", "arg")
+ K.message("foo.bar", "arg1", "arg2")
+ J.message("foo.bar", "arg")
+ J.message("foo.bar", "arg1", "arg2")
+}
\ No newline at end of file
diff --git a/idea/testData/multiFileInspections/invalidBundleOrProperty/expected.xml b/idea/testData/multiFileInspections/invalidBundleOrProperty/expected.xml
new file mode 100644
index 00000000000..aa160f3a396
--- /dev/null
+++ b/idea/testData/multiFileInspections/invalidBundleOrProperty/expected.xml
@@ -0,0 +1,73 @@
+
+
+ unresolvedPropertyReference.kt
+ 2
+ testInvalidBundleOrProperty_InvalidBundleOrProperty_0
+
+ Invalid property key
+ String literal 'foo.baz' doesn't appear to be valid property key
+
+
+
+ unresolvedPropertyReference.kt
+ 3
+ testInvalidBundleOrProperty_InvalidBundleOrProperty_0
+
+ Invalid property key
+ String literal 'foo.baz' doesn't appear to be valid property key
+
+
+
+ unresolvedPropertyReference.kt
+ 4
+ testInvalidBundleOrProperty_InvalidBundleOrProperty_0
+
+ Invalid property key
+ String literal 'foo.baz' doesn't appear to be valid property key
+
+
+
+ unresolvedPropertyReference.kt
+ 5
+ testInvalidBundleOrProperty_InvalidBundleOrProperty_0
+
+ Invalid property key
+ String literal 'foo.baz' doesn't appear to be valid property key
+
+
+
+ tooFewArguments.kt
+ 2
+ testInvalidBundleOrProperty_InvalidBundleOrProperty_0
+
+ Invalid property key
+ Property 'foo.bar' expected 1 parameter, passed 0
+
+
+
+ tooFewArguments.kt
+ 3
+ testInvalidBundleOrProperty_InvalidBundleOrProperty_0
+
+ Invalid property key
+ Property 'foo.bar' expected 1 parameter, passed 0
+
+
+
+ unresolvedBundleReference.kt
+ 3
+ testInvalidBundleOrProperty_InvalidBundleOrProperty_0
+
+ Invalid property key
+ Invalid resource bundle reference 'TestBundle2'
+
+
+
+ unresolvedBundleReference.kt
+ 5
+ testInvalidBundleOrProperty_InvalidBundleOrProperty_0
+
+ Invalid property key
+ Invalid resource bundle reference 'TestBundle2'
+
+
diff --git a/idea/testData/multiFileInspections/invalidBundleOrProperty/invalidBundleOrProperty.test b/idea/testData/multiFileInspections/invalidBundleOrProperty/invalidBundleOrProperty.test
new file mode 100644
index 00000000000..3bec6249eb9
--- /dev/null
+++ b/idea/testData/multiFileInspections/invalidBundleOrProperty/invalidBundleOrProperty.test
@@ -0,0 +1,4 @@
+{
+ "inspectionClass": "org.jetbrains.kotlin.idea.inspections.KotlinInvalidBundleOrPropertyInspection",
+ "withRuntime": "true"
+}
diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetMultiFileInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetMultiFileInspectionTestGenerated.java
index 134f8f52141..2501653a267 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetMultiFileInspectionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetMultiFileInspectionTestGenerated.java
@@ -35,6 +35,12 @@ public class JetMultiFileInspectionTestGenerated extends AbstractJetMultiFileIns
JetTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/multiFileInspections"), Pattern.compile("^(.+)\\.test$"));
}
+ @TestMetadata("invalidBundleOrProperty/invalidBundleOrProperty.test")
+ public void testInvalidBundleOrProperty_InvalidBundleOrProperty() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/multiFileInspections/invalidBundleOrProperty/invalidBundleOrProperty.test");
+ doTest(fileName);
+ }
+
@TestMetadata("mismatchedProjectAndDirectory/mismatchedProjectAndDirectory.test")
public void testMismatchedProjectAndDirectory_MismatchedProjectAndDirectory() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/multiFileInspections/mismatchedProjectAndDirectory/mismatchedProjectAndDirectory.test");