diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lossyEncoding/surrogate.txt b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lossyEncoding/surrogate.txt new file mode 100644 index 000000000000..f84cce68bef5 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lossyEncoding/surrogate.txt @@ -0,0 +1 @@ +console.log("𝌆"); \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LossyEncodingTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LossyEncodingTest.java index 292c61aa02a5..5256c68c7312 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LossyEncodingTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LossyEncodingTest.java @@ -175,6 +175,18 @@ public class LossyEncodingTest extends DaemonAnalyzerTestCase { assertEquals("File was loaded in the wrong encoding: 'UTF-8'", info.getDescription()); } + public void testSurrogateUTF8() throws Exception { + VirtualFile virtualFile = getVirtualFile(BASE_PATH + "/" + "surrogate.txt"); + virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET); + configureByExistingFile(virtualFile); + final Document document = FileDocumentManager.getInstance().getDocument(virtualFile); + + assertFalse(FileDocumentManager.getInstance().isDocumentUnsaved(document)); + assertEquals(CharsetToolkit.UTF8_CHARSET, virtualFile.getCharset()); + + assertEmpty(doHighlighting()); + } + public void testInconsistentLineSeparators() throws Exception { VirtualFile virtualFile = getVirtualFile(BASE_PATH + "/" + getTestName(false) + ".txt"); configureByExistingFile(virtualFile); diff --git a/platform/lang-impl/src/com/intellij/codeInspection/LossyEncodingInspection.java b/platform/lang-impl/src/com/intellij/codeInspection/LossyEncodingInspection.java index c82a3c149b16..3270612c3a82 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/LossyEncodingInspection.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/LossyEncodingInspection.java @@ -38,6 +38,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.ListPopup; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.encoding.ChangeFileEncodingAction; import com.intellij.openapi.vfs.encoding.EncodingUtil; @@ -173,8 +174,10 @@ public class LossyEncodingInspection extends LocalInspectionTool { 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() || isRepresentable(c, charset)) { + char c = i >= text.length() ? 0 : text.charAt(i); + char next = i + 1 >= text.length() ? 0 : text.charAt(i + 1); + char prev = i == 0 ? 0 : text.charAt(i - 1); + if (i == text.length() || isRepresentable(c, next, prev, charset)) { if (start != -1) { TextRange range = new TextRange(start, i); String message = InspectionsBundle.message("unsupported.character.for.the.charset", charset); @@ -192,8 +195,11 @@ public class LossyEncodingInspection extends LocalInspectionTool { } } - private static boolean isRepresentable(final char c, @NotNull Charset charset) { - String str = Character.toString(c); + private static boolean isRepresentable(final char c, final char next, char prev, @NotNull Charset charset) { + if (charset == CharsetToolkit.UTF8_CHARSET) { + if (Character.isSurrogatePair(c, next) || Character.isSurrogatePair(prev, c)) return true; + } + String str = String.valueOf(c); ByteBuffer out = charset.encode(str); CharBuffer buffer = charset.decode(out); return str.equals(buffer.toString());