Java: highlight bad escapes in string template fragments (IDEA-321466)

GitOrigin-RevId: 3d7fb0ab16db230d216bac4eb8b9ac5301232902
This commit is contained in:
Bas Leijdekkers
2023-08-01 11:58:28 +00:00
committed by intellij-monorepo-bot
parent d50045f34f
commit 8354caf1f5
4 changed files with 78 additions and 29 deletions
@@ -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,
@@ -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
@@ -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;
}
}
@@ -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<error descr="Illegal escape character in string literal">\a</error>d \{} esc<error descr="Illegal escape character in string literal">\a</error>pe 1");
System.out.println(STR. """
b<error descr="Illegal escape character in string literal">\a</error>d \{} esc<error descr="Illegal escape character in string literal">\a</error>pe 2
""");
System.out.println(STR."\{<error descr="Illegal line end in string literal">}unclosed);</error>
return STR."\{} <error descr="Illegal Unicode escape sequence">\u</error>X";
}
}