diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/nonAsciiCharacters/Simple.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/nonAsciiCharacters/Simple.java
new file mode 100644
index 000000000000..42c434cce987
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/nonAsciiCharacters/Simple.java
@@ -0,0 +1,9 @@
+class X {
+ int Ж = 0;
+ class InnerП {}
+ // commentжп 234
+ String s = "12л3орыва0";
+ void жжж() {
+ жжж();
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/NonAsciiCharactersTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/NonAsciiCharactersTest.java
new file mode 100644
index 000000000000..6e2afec75430
--- /dev/null
+++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/NonAsciiCharactersTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2000-2017 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.
+ */
+
+/*
+ * Created by IntelliJ IDEA.
+ * User: max
+ * Date: Apr 11, 2002
+ * Time: 7:51:16 PM
+ * To change template for new class use
+ * Code Style | Class Templates options (Tools | IDE Options).
+ */
+package com.intellij.codeInsight.daemon;
+
+import com.intellij.codeInspection.NonAsciiCharactersInspection;
+import com.intellij.codeInspection.LocalInspectionTool;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+public class NonAsciiCharactersTest extends DaemonAnalyzerTestCase {
+ @NonNls private static final String BASE_PATH = "/codeInsight/daemonCodeAnalyzer/nonAsciiCharacters";
+
+ @NotNull
+ @Override
+ protected LocalInspectionTool[] configureLocalInspectionTools() {
+ NonAsciiCharactersInspection inspection = new NonAsciiCharactersInspection();
+ inspection.CHECK_FOR_DIFFERENT_LANGUAGES_IN_IDENTIFIER_NAME = true;
+ inspection.CHECK_FOR_NOT_ASCII_IDENTIFIER_NAME = true;
+ inspection.CHECK_FOR_NOT_ASCII_COMMENT = true;
+ inspection.CHECK_FOR_NOT_ASCII_STRING_LITERAL = true;
+ inspection.CHECK_FOR_FILES_CONTAINING_BOM = true;
+ return new LocalInspectionTool[]{inspection};
+ }
+
+ private void doTest() throws Exception {
+ doTest(BASE_PATH + "/" + getTestName(false)+".java", true, false);
+ UIUtil.dispatchAllInvocationEvents();
+ }
+
+ public void testSimple() throws Exception {
+ doTest();
+ }
+}
diff --git a/platform/lang-impl/src/com/intellij/codeInspection/NonAsciiCharactersInspection.form b/platform/lang-impl/src/com/intellij/codeInspection/NonAsciiCharactersInspection.form
new file mode 100644
index 000000000000..7572dd0db29c
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/codeInspection/NonAsciiCharactersInspection.form
@@ -0,0 +1,111 @@
+
+
diff --git a/platform/lang-impl/src/com/intellij/codeInspection/NonAsciiCharactersInspection.java b/platform/lang-impl/src/com/intellij/codeInspection/NonAsciiCharactersInspection.java
new file mode 100644
index 000000000000..4530712f10cb
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/codeInspection/NonAsciiCharactersInspection.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2000-2017 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 com.intellij.codeInspection;
+
+import com.intellij.lang.injection.InjectedLanguageManager;
+import com.intellij.lang.properties.charset.Native2AsciiCharset;
+import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.vfs.CharsetToolkit;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.*;
+import com.intellij.ui.components.JBCheckBox;
+import com.intellij.util.ReflectionUtil;
+import com.intellij.util.io.IOUtil;
+import gnu.trove.THashMap;
+import org.jetbrains.annotations.Nls;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.nio.charset.Charset;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+public class NonAsciiCharactersInspection extends LocalInspectionTool {
+ public boolean CHECK_FOR_NOT_ASCII_IDENTIFIER_NAME = true;
+ public boolean CHECK_FOR_NOT_ASCII_STRING_LITERAL;
+ public boolean CHECK_FOR_NOT_ASCII_COMMENT;
+
+ public boolean CHECK_FOR_DIFFERENT_LANGUAGES_IN_IDENTIFIER_NAME = true;
+ public boolean CHECK_FOR_FILES_CONTAINING_BOM;
+
+ private JBCheckBox myASCIIIdentifiers;
+ private JBCheckBox myASCIIComments;
+ private JBCheckBox myASCIIStringLiterals;
+ private JBCheckBox myAlienIdentifiers;
+ private JPanel myPanel;
+ private JBCheckBox myFilesContainingBOM;
+ private final Map myBindings = new THashMap<>();
+
+ public NonAsciiCharactersInspection() {
+ bind(myASCIIIdentifiers, "CHECK_FOR_NOT_ASCII_IDENTIFIER_NAME");
+ bind(myASCIIStringLiterals, "CHECK_FOR_NOT_ASCII_STRING_LITERAL");
+ bind(myASCIIComments, "CHECK_FOR_NOT_ASCII_COMMENT");
+ bind(myAlienIdentifiers, "CHECK_FOR_DIFFERENT_LANGUAGES_IN_IDENTIFIER_NAME");
+ bind(myFilesContainingBOM, "CHECK_FOR_FILES_CONTAINING_BOM");
+ }
+
+ @Override
+ @Nls
+ @NotNull
+ public String getGroupDisplayName() {
+ return InspectionsBundle.message("group.names.internationalization.issues");
+ }
+
+ @Override
+ @Nls
+ @NotNull
+ public String getDisplayName() {
+ return InspectionsBundle.message("non.ascii.characters");
+ }
+
+ @Override
+ @NonNls
+ @NotNull
+ public String getShortName() {
+ return "NonAsciiCharacters";
+ }
+
+
+ @NotNull
+ @Override
+ public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder,
+ boolean isOnTheFly,
+ @NotNull LocalInspectionToolSession session) {
+ if (!isFileWorthIt(session.getFile())) return PsiElementVisitor.EMPTY_VISITOR;
+ return new PsiElementVisitor() {
+ @Override
+ public void visitElement(PsiElement element) {
+ if (CHECK_FOR_NOT_ASCII_IDENTIFIER_NAME || CHECK_FOR_DIFFERENT_LANGUAGES_IN_IDENTIFIER_NAME) {
+ PsiElement parent = element.getParent();
+ if (parent instanceof PsiNameIdentifierOwner && ((PsiNameIdentifierOwner)parent).getNameIdentifier() == element) {
+ String text = element.getText();
+ if (CHECK_FOR_NOT_ASCII_IDENTIFIER_NAME) {
+ checkAscii(element, text, holder, "an identifier");
+ }
+ if (CHECK_FOR_DIFFERENT_LANGUAGES_IN_IDENTIFIER_NAME) {
+ checkSameLanguage(element, text, holder);
+ }
+ }
+ }
+ if (CHECK_FOR_NOT_ASCII_COMMENT) {
+ if (element instanceof PsiComment) {
+ checkAsciiRange(element, element.getText(), holder, "a comment");
+ }
+ }
+ if (CHECK_FOR_NOT_ASCII_STRING_LITERAL) {
+ if (element instanceof PsiLiteralValue) {
+ checkAsciiRange(element, element.getText(), holder, "a string literal");
+ }
+ }
+ }
+
+ @Override
+ public void visitFile(PsiFile file) {
+ super.visitFile(file);
+ if (CHECK_FOR_FILES_CONTAINING_BOM) {
+ VirtualFile virtualFile = file.getVirtualFile();
+ byte[] bom = virtualFile == null ? null : virtualFile.getBOM();
+ if (bom != null) {
+ String hex = IntStream.range(0, bom.length)
+ .map(i -> bom[i])
+ .mapToObj(b -> Integer.toString(b & 0x00ff, 16).toUpperCase())
+ .collect(Collectors.joining());
+ Charset charsetFromBOM = CharsetToolkit.guessFromBOM(bom);
+ holder.registerProblem(file, "File contains BOM: '" + hex +"'"+
+ (charsetFromBOM == null ? "" : " (charset '"+charsetFromBOM.displayName()+"' signature)"),
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
+ }
+ }
+ }
+ };
+ }
+
+ private static boolean isFileWorthIt(PsiFile file) {
+ if (InjectedLanguageManager.getInstance(file.getProject()).isInjectedFragment(file)) return false;
+ VirtualFile virtualFile = file.getVirtualFile();
+ if (virtualFile == null) return false;
+ CharSequence text = file.getViewProvider().getContents();
+
+ Charset charset = LoadTextUtil.extractCharsetFromFileContent(file.getProject(), virtualFile, text);
+
+ // no sense in checking transparently decoded file: all characters there are already safely encoded
+ return !(charset instanceof Native2AsciiCharset);
+ }
+
+
+ private static void checkSameLanguage(PsiElement element,
+ String text,
+ ProblemsHolder holder) {
+ Set scripts = text.codePoints()
+ .mapToObj(Character.UnicodeScript::of)
+ .filter(script -> !script.equals(Character.UnicodeScript.COMMON))
+ .collect(Collectors.toSet());
+ if (scripts.size() > 1) {
+ List list = new ArrayList<>(scripts);
+ Collections.sort(list); // a little bit of stability
+ holder.registerProblem(element, "Identifier contains symbols from different languages: " + list,
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
+ }
+ }
+
+ private static void checkAscii(PsiElement element,
+ String text,
+ ProblemsHolder holder,
+ String where) {
+ if (!IOUtil.isAscii(text)) {
+ holder.registerProblem(element, "Non-ASCII characters in " + where, ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
+ }
+ }
+ private static void checkAsciiRange(PsiElement element,
+ String text,
+ ProblemsHolder holder,
+ String where) {
+ int errorCount = 0;
+ int start = -1;
+ for (int i = 0; i <= text.length(); i++) {
+ char c = i >= text.length() ? 0 : text.charAt(i);
+ if (i == text.length() || c<128) {
+ if (start != -1) {
+ TextRange range = new TextRange(start, i);
+ String message = "Non-ASCII characters in " + where;
+ holder.registerProblem(element, range, message);
+ start = -1;
+ //do not report too many errors
+ if (errorCount++ > 200) break;
+ }
+ }
+ else if (start == -1) {
+ start = i;
+ }
+ }
+ }
+
+ @Nullable
+ @Override
+ public JComponent createOptionsPanel() {
+ reset();
+ return myPanel;
+ }
+
+ private void bind(JCheckBox checkBox, String property) {
+ myBindings.put(checkBox, property);
+ reset(checkBox, property);
+ checkBox.addChangeListener(__ -> {
+ boolean selected = checkBox.isSelected();
+ ReflectionUtil.setField(getClass(), this, boolean.class, property, selected);
+ });
+ }
+
+ private void reset(JCheckBox checkBox, String property) {
+ checkBox.setSelected(ReflectionUtil.getField(getClass(), this, boolean.class, property));
+ }
+
+ private void reset() {
+ for (Map.Entry entry : myBindings.entrySet()) {
+ JCheckBox checkBox = entry.getKey();
+ String property = entry.getValue();
+ reset(checkBox, property);
+ }
+ }
+}
diff --git a/platform/platform-resources-en/src/inspectionDescriptions/NonAsciiCharacters.html b/platform/platform-resources-en/src/inspectionDescriptions/NonAsciiCharacters.html
new file mode 100644
index 000000000000..07c70937380c
--- /dev/null
+++ b/platform/platform-resources-en/src/inspectionDescriptions/NonAsciiCharacters.html
@@ -0,0 +1,18 @@
+
+
+
+This inspection warns you of the code accidentally written in the wrong encoding.
+
+For example,
+
+ - Non-ASCII characters used in the names/strings/comments or
+ - Identifiers written in different languages
+ (e.g. when in the variable "
myCollection"
+ the letter C written in Cyrillic) or
+ - Unicode symbols used in comments or strings
+ (e.g. long dashes and arrows in the comment
+ "
// a → !a — please note")
+
+
+
+
\ No newline at end of file
diff --git a/platform/platform-resources-en/src/messages/InspectionsBundle.properties b/platform/platform-resources-en/src/messages/InspectionsBundle.properties
index 2a4f64eb0955..cbc25d9bf284 100644
--- a/platform/platform-resources-en/src/messages/InspectionsBundle.properties
+++ b/platform/platform-resources-en/src/messages/InspectionsBundle.properties
@@ -714,6 +714,7 @@ cleanup.in.scope=Cleanup code on...
severities.default.settings.message=Edit Settings|Colors \\& Fonts
unsupported.character.for.the.charset=Unsupported characters for the charset ''{0}''
lossy.encoding=Lossy encoding
+non.ascii.characters=Non-ASCII characters
inspection.duplicated.code.display.name=Duplicated Code
inconsistent.line.separators=Inconsistent line separators
inspection.unused.symbol.public.method.parameters=Check parameters in &Non-private methods
diff --git a/platform/platform-resources/src/META-INF/LangExtensions.xml b/platform/platform-resources/src/META-INF/LangExtensions.xml
index 9af16133aea8..73c200780741 100644
--- a/platform/platform-resources/src/META-INF/LangExtensions.xml
+++ b/platform/platform-resources/src/META-INF/LangExtensions.xml
@@ -831,6 +831,9 @@
+