diff --git a/idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/androidUtil.kt b/idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/androidUtil.kt
new file mode 100644
index 00000000000..138fb7d9b91
--- /dev/null
+++ b/idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/androidUtil.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2010-2016 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.android
+
+import com.intellij.psi.PsiElement
+import org.jetbrains.android.facet.AndroidFacet
+
+fun PsiElement.getAndroidFacetForFile(): AndroidFacet? {
+ val file = containingFile ?: return null
+ return AndroidFacet.getInstance(file)
+}
\ No newline at end of file
diff --git a/idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/inspection/IllegalIdentifierInspection.kt b/idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/inspection/IllegalIdentifierInspection.kt
new file mode 100644
index 00000000000..f849a8e6aa5
--- /dev/null
+++ b/idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/inspection/IllegalIdentifierInspection.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2010-2016 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.android.inspection
+
+import com.intellij.codeInsight.FileModificationService
+import com.intellij.codeInspection.*
+import com.intellij.ide.DataManager
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.fileEditor.FileEditorManager
+import com.intellij.openapi.project.Project
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiElementVisitor
+import com.intellij.refactoring.rename.RenameHandlerRegistry
+import org.jetbrains.kotlin.android.getAndroidFacetForFile
+import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.*
+
+class IllegalIdentifierInspection : AbstractKotlinInspection() {
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
+ return object : KtVisitorVoid() {
+ override fun visitElement(element: PsiElement) {
+ if (element.node.elementType != KtTokens.IDENTIFIER) return
+
+ val text = element.text
+ // '`' can't be escaped now
+ if (!text.startsWith('`') || !text.endsWith('`')) return
+
+ val unquotedName = KtPsiUtil.unquoteIdentifier(text)
+ // This is already an error
+ if (unquotedName.isEmpty()) return
+
+ if (!unquotedName.all { isValidDalvikCharacter(it) } && checkAndroidFacet(element)) {
+ holder.registerProblem(element,
+ "Identifier not allowed in Android projects",
+ ProblemHighlightType.GENERIC_ERROR,
+ RenameIdentifierFix())
+ }
+ }
+
+ fun checkAndroidFacet(element: PsiElement): Boolean {
+ return element.getAndroidFacetForFile() != null || ApplicationManager.getApplication().isUnitTestMode
+ }
+
+ // https://source.android.com/devices/tech/dalvik/dex-format.html#string-syntax
+ fun isValidDalvikCharacter(c: Char) = when (c) {
+ in 'A'..'Z' -> true
+ in 'a'..'z' -> true
+ in '0'..'9' -> true
+ '$', '-', '_' -> true
+ in '\u00a1' .. '\u1fff' -> true
+ in '\u2010' .. '\u2027' -> true
+ in '\u2030' .. '\ud7ff' -> true
+ in '\ue000' .. '\uffef' -> true
+ else -> false
+ }
+ }
+ }
+
+ class RenameIdentifierFix : LocalQuickFix {
+ override fun getName() = "Rename"
+ override fun getFamilyName() = name
+
+ override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
+ val element = descriptor.psiElement ?: return
+ val file = element.containingFile ?: return
+ if (!FileModificationService.getInstance().prepareFileForWrite(file)) return
+ val editorManager = FileEditorManager.getInstance(project)
+ val editor = editorManager.getSelectedEditor(file.virtualFile) ?: return
+ val dataContext = DataManager.getInstance().getDataContext(editor.component)
+ val renameHandler = RenameHandlerRegistry.getInstance().getRenameHandler(dataContext)
+ renameHandler?.invoke(project, arrayOf(element), dataContext);
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/navigation/KotlinAndroidGotoDeclarationHandler.java b/idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/navigation/KotlinAndroidGotoDeclarationHandler.java
index d29d11c8a1d..0410dfb1624 100644
--- a/idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/navigation/KotlinAndroidGotoDeclarationHandler.java
+++ b/idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/navigation/KotlinAndroidGotoDeclarationHandler.java
@@ -21,7 +21,6 @@ import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
import com.intellij.psi.xml.XmlElement;
import org.jetbrains.android.dom.AndroidAttributeValue;
import org.jetbrains.android.dom.manifest.Manifest;
@@ -35,6 +34,7 @@ import org.jetbrains.android.util.AndroidResourceUtil;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.jetbrains.kotlin.android.AndroidUtilKt;
import org.jetbrains.kotlin.psi.KtSimpleNameExpression;
import java.util.ArrayList;
@@ -51,12 +51,7 @@ public class KotlinAndroidGotoDeclarationHandler implements GotoDeclarationHandl
return null;
}
- PsiFile file = referenceExpression.getContainingFile();
- if (file == null) {
- return null;
- }
-
- AndroidFacet facet = AndroidFacet.getInstance(file);
+ AndroidFacet facet = AndroidUtilKt.getAndroidFacetForFile(referenceExpression);
if (facet == null) {
return null;
}
diff --git a/idea/src/META-INF/android.xml b/idea/src/META-INF/android.xml
index ee848ec70b5..98485f2cf97 100644
--- a/idea/src/META-INF/android.xml
+++ b/idea/src/META-INF/android.xml
@@ -7,6 +7,12 @@
+
+
diff --git a/idea/testData/inspections/androidIllegalIdentifiers/inspectionData/expected.xml b/idea/testData/inspections/androidIllegalIdentifiers/inspectionData/expected.xml
new file mode 100644
index 00000000000..937410f6457
--- /dev/null
+++ b/idea/testData/inspections/androidIllegalIdentifiers/inspectionData/expected.xml
@@ -0,0 +1,46 @@
+
+
+ temp:///src/test.kt
+ 7
+ light_idea_test_case
+
+ Illegal Android Identifier
+ Illegal Android Identifier
+
+
+
+ temp:///src/test.kt
+ 8
+ light_idea_test_case
+
+ Illegal Android Identifier
+ Illegal Android Identifier
+
+
+
+ temp:///src/test.kt
+ 9
+ light_idea_test_case
+
+ Illegal Android Identifier
+ Illegal Android Identifier
+
+
+
+ temp:///src/test.kt
+ 10
+ light_idea_test_case
+
+ Illegal Android Identifier
+ Illegal Android Identifier
+
+
+
+ temp:///src/test.kt
+ 11
+ light_idea_test_case
+
+ Illegal Android Identifier
+ Illegal Android Identifier
+
+
diff --git a/idea/testData/inspections/androidIllegalIdentifiers/inspectionData/inspections.test b/idea/testData/inspections/androidIllegalIdentifiers/inspectionData/inspections.test
new file mode 100644
index 00000000000..ebd8e2d72f6
--- /dev/null
+++ b/idea/testData/inspections/androidIllegalIdentifiers/inspectionData/inspections.test
@@ -0,0 +1 @@
+// INSPECTION_CLASS: org.jetbrains.kotlin.android.inspection.IllegalIdentifierInspection
diff --git a/idea/testData/inspections/androidIllegalIdentifiers/test.kt b/idea/testData/inspections/androidIllegalIdentifiers/test.kt
new file mode 100644
index 00000000000..8e59b93137b
--- /dev/null
+++ b/idea/testData/inspections/androidIllegalIdentifiers/test.kt
@@ -0,0 +1,11 @@
+val `Kotlin` = 0
+val `Котлин` = 0
+val `ことりん` = 0
+val `§*!`` = 0
+val `0a0b0c` = 0
+val `$a-b_c` = 0
+val `with space` = 0 //illegal
+val `a+b` = 0 //illegal
+val `#` = 0 //illegal
+val `!` = 0 //illegal
+val `()` = 0 //illegal
\ No newline at end of file
diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java
index 7e2585e5cda..3f5d4341517 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java
@@ -106,6 +106,12 @@ public class InspectionTestGenerated extends AbstractInspectionTest {
KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/inspections"), Pattern.compile("^(inspections\\.test)$"));
}
+ @TestMetadata("androidIllegalIdentifiers/inspectionData/inspections.test")
+ public void testAndroidIllegalIdentifiers_inspectionData_Inspections_test() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/androidIllegalIdentifiers/inspectionData/inspections.test");
+ doTest(fileName);
+ }
+
@TestMetadata("conflictingExtensionProperty/inspectionData/inspections.test")
public void testConflictingExtensionProperty_inspectionData_Inspections_test() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/conflictingExtensionProperty/inspectionData/inspections.test");