diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java index e33c618da225..a4d3156c9232 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java @@ -1400,16 +1400,7 @@ public final class HighlightUtil { } if (!StringUtil.startsWithChar(text, '\"')) return null; - if (StringUtil.endsWithChar(text, '\"')) { - if (text.length() == 1) { - String message = JavaErrorBundle.message("illegal.line.end.in.string.literal"); - if (description != null) { - description.set(message); - } - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message); - } - } - else { + if (!StringUtil.endsWithChar(text, '\"') || text.length() == 1) { String message = JavaErrorBundle.message("illegal.line.end.in.string.literal"); if (description != null) { description.set(message); @@ -1514,6 +1505,32 @@ public final class HighlightUtil { return null; } + public static HighlightInfo.Builder checkFragmentError(PsiFragment fragment) { + String text = fragment.getText(); + int length = text.length(); + if (fragment.getTokenType() == JavaTokenType.STRING_TEMPLATE_END) { + if (!StringUtil.endsWithChar(text, '\"') || length == 1) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(fragment) + .descriptionAndTooltip(JavaErrorBundle.message("illegal.line.end.in.string.literal")); + } + } + if (text.endsWith("\\{")) { + text = text.substring(0, length - 2); + length -= 2; + } + StringBuilder chars = new StringBuilder(length); + int[] offsets = new int[length + 1]; + boolean success = CodeInsightUtilCore.parseStringCharacters(text, chars, offsets, fragment.isTextBlock()); + if (!success) { + String message = JavaErrorBundle.message("illegal.escape.character.in.string.literal"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(fragment, calculateErrorRange(text, offsets[chars.length()])) + .descriptionAndTooltip(message); + } + return null; + } + private static HighlightInfo.@Nullable Builder checkStringTemplateEscapes(@NotNull PsiLiteralExpression expression, @NotNull String text, @NotNull LanguageLevel level, diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java index d2439f9102a8..35eaab04245e 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java @@ -943,9 +943,15 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh super.visitTemplateExpression(expression); add(HighlightUtil.checkTemplateExpression(expression)); - if (!myHolder.hasErrorResults()) { - add(HighlightUtil.checkUnhandledExceptions(expression)); - } + if (!myHolder.hasErrorResults()) add(HighlightUtil.checkUnhandledExceptions(expression)); + } + + @Override + public void visitFragment(@NotNull PsiFragment fragment) { + super.visitFragment(fragment); + + HighlightUtil.checkIllegalUnicodeEscapes(fragment, myHolder); + if (!myHolder.hasErrorResults()) add(HighlightUtil.checkFragmentError(fragment)); } @Override diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaHighlightErrorFilter.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaHighlightErrorFilter.java index 7be809bcce86..cac28c38b2d8 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaHighlightErrorFilter.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaHighlightErrorFilter.java @@ -25,24 +25,12 @@ public class JavaHighlightErrorFilter extends HighlightErrorFilter { } else { // reporting missing semicolons after an unclosed string literal is not useful. - PsiElement prevLeaf = PsiTreeUtil.prevCodeLeaf(element); - if (prevLeaf instanceof PsiJavaToken token) { - IElementType type = token.getTokenType(); - if (type == JavaTokenType.STRING_LITERAL) { - String text = token.getText(); - if (text.length() == 1 || !StringUtil.endsWithChar(text, '"')) { - return false; - } - } - else if (type == JavaTokenType.CHARACTER_LITERAL) { - String text = token.getText(); - if (text.length() == 1 || !StringUtil.endsWithChar(text, '\'')) { - return false; - } - } - } + if (isAfterUnclosedStringLiteral(element)) return false; } } + else if (description.equals(JavaPsiBundle.message("expected.comma.or.rparen"))) { + if (isAfterUnclosedStringLiteral(element)) return false; + } else if (description.equals(JavaPsiBundle.message("expected.class.or.interface"))) { String text = element.getText(); if ((text.equals(PsiKeyword.SEALED) || text.equals(PsiKeyword.NON_SEALED)) && @@ -52,4 +40,33 @@ public class JavaHighlightErrorFilter extends HighlightErrorFilter { } return true; } + + private static boolean isAfterUnclosedStringLiteral(@NotNull PsiErrorElement element) { + PsiElement prevLeaf = PsiTreeUtil.prevCodeLeaf(element); + if (prevLeaf instanceof PsiJavaToken token) { + IElementType type = token.getTokenType(); + if (type == JavaTokenType.STRING_LITERAL) { + String text = token.getText(); + if (text.length() == 1 || !StringUtil.endsWithChar(text, '"')) { + return true; + } + } + else if (type == JavaTokenType.CHARACTER_LITERAL) { + String text = token.getText(); + if (text.length() == 1 || !StringUtil.endsWithChar(text, '\'')) { + return true; + } + } + } + else if (prevLeaf instanceof PsiFragment fragment) { + IElementType type = fragment.getTokenType(); + if (type == JavaTokenType.STRING_TEMPLATE_END) { + String text = fragment.getText(); + if (text.length() == 1 || !StringUtil.endsWithChar(text, '"')) { + return true; + } + } + } + return false; + } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingStringTemplates/StringTemplates.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingStringTemplates/StringTemplates.java index 23b1d3df70d3..6e35733779f0 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingStringTemplates/StringTemplates.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingStringTemplates/StringTemplates.java @@ -40,4 +40,13 @@ class X { void nested() { System.out.println(STR."\{STR."\{STR."\{STR."\{STR."\{STR."\{STR.""}"}"}"}"}"}"); } + + String badEscape() { + System.out.println(STR."b\ad \{} esc\ape 1"); + System.out.println(STR. """ + b\ad \{} esc\ape 2 + """); + System.out.println(STR."\{}unclosed); + return STR."\{} \uX"; + } } \ No newline at end of file