Highlight incorrect escape instead of entire string literal

GitOrigin-RevId: 5455c5b43f8b19161dfa23643c197c8136cbbeff
This commit is contained in:
Bas Leijdekkers
2022-07-18 12:30:44 +02:00
committed by intellij-monorepo-bot
parent 30ab60d37d
commit 304a1c2df5
6 changed files with 126 additions and 111 deletions

View File

@@ -1218,63 +1218,96 @@ public final class HighlightUtil {
}
}
else if (type == JavaTokenType.CHARACTER_LITERAL) {
int newLineEscape = text.indexOf("\\\n");
if (newLineEscape >= 0) {
String message = JavaErrorBundle.message("illegal.escape.character.in.character.literal");
if (!StringUtil.startsWithChar(text, '\'')) {
return null;
}
if (!StringUtil.endsWithChar(text, '\'') || text.length() == 1) {
String message = JavaErrorBundle.message("unclosed.char.literal");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create();
}
if (value == null) {
if (!StringUtil.startsWithChar(text, '\'')) {
return null;
}
if (!StringUtil.endsWithChar(text, '\'') || text.length() == 1) {
String message = JavaErrorBundle.message("unclosed.char.literal");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create();
}
CharSequence chars = CodeInsightUtilCore.parseStringCharacters(rawText, null);
if (chars == null) {
String message = JavaErrorBundle.message("illegal.escape.character.in.character.literal");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create();
}
int length = chars.length();
if (length > 3) {
String message = JavaErrorBundle.message("too.many.characters.in.character.literal");
HighlightInfo info =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create();
QuickFixAction.registerQuickFixAction(info, getFixFactory().createConvertToStringLiteralAction());
return info;
}
else if (length == 2) {
String message = JavaErrorBundle.message("empty.character.literal");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create();
}
int rawLength = rawText.length();
StringBuilder chars = new StringBuilder(rawLength);
int[] offsets = new int[rawLength + 1];
final boolean success = CodeInsightUtilCore.parseStringCharacters(rawText, chars, offsets, false);
if (!success) {
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.range(expression, calculateErrorRange(rawText, offsets[chars.length()]))
.descriptionAndTooltip(JavaErrorBundle.message("illegal.escape.character.in.character.literal"))
.create();
}
int length = chars.length();
if (length > 3) {
String message = JavaErrorBundle.message("too.many.characters.in.character.literal");
HighlightInfo info =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create();
QuickFixAction.registerQuickFixAction(info, getFixFactory().createConvertToStringLiteralAction());
return info;
}
else if (length == 2) {
String message = JavaErrorBundle.message("empty.character.literal");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create();
}
if (file != null && containsUnescaped(text, "\\s")) {
HighlightInfo info = checkFeature(expression, HighlightingFeature.TEXT_BLOCK_ESCAPES, level, file);
if (info != null) return info;
}
}
else if (type == JavaTokenType.STRING_LITERAL || type == JavaTokenType.TEXT_BLOCK_LITERAL) {
if (type == JavaTokenType.STRING_LITERAL) {
if (value == null) {
for (PsiElement element = expression.getFirstChild(); element != null; element = element.getNextSibling()) {
if (element instanceof OuterLanguageElement) {
return null;
}
for (PsiElement element = expression.getFirstChild(); element != null; element = element.getNextSibling()) {
if (element instanceof OuterLanguageElement) {
return null;
}
}
if (!StringUtil.startsWithChar(text, '\"')) return null;
if (StringUtil.endsWithChar(text, '\"')) {
if (text.length() == 1) {
String message = JavaErrorBundle.message("illegal.line.end.in.string.literal");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create();
}
}
else {
if (!StringUtil.startsWithChar(text, '\"')) return null;
if (StringUtil.endsWithChar(text, '\"')) {
if (text.length() == 1) {
String message = JavaErrorBundle.message("illegal.line.end.in.string.literal");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create();
}
int length = rawText.length();
StringBuilder chars = new StringBuilder(length);
int[] offsets = new int[length + 1];
boolean success = CodeInsightUtilCore.parseStringCharacters(rawText, chars, offsets);
}
else {
String message = JavaErrorBundle.message("illegal.line.end.in.string.literal");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create();
}
int length = rawText.length();
StringBuilder chars = new StringBuilder(length);
int[] offsets = new int[length + 1];
boolean success = CodeInsightUtilCore.parseStringCharacters(rawText, chars, offsets, false);
if (!success) {
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.range(expression, calculateErrorRange(rawText, offsets[chars.length()]))
.descriptionAndTooltip(JavaErrorBundle.message("illegal.escape.character.in.string.literal"))
.create();
}
if (file != null && containsUnescaped(text, "\\s")) {
HighlightInfo info = checkFeature(expression, HighlightingFeature.TEXT_BLOCK_ESCAPES, level, file);
if (info != null) return info;
}
}
else {
if (!text.endsWith("\"\"\"")) {
String message = JavaErrorBundle.message("text.block.unclosed");
int p = expression.getTextRange().getEndOffset();
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(p, p).endOfLine().descriptionAndTooltip(message).create();
}
else {
int i = 3;
char c = text.charAt(i);
while (PsiLiteralUtil.isTextBlockWhiteSpace(c)) {
i++;
c = text.charAt(i);
}
if (c != '\n' && c != '\r') {
String message = JavaErrorBundle.message("text.block.new.line");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create();
}
final int rawLength = rawText.length();
StringBuilder chars = new StringBuilder(rawLength);
int[] offsets = new int[rawLength + 1];
boolean success = CodeInsightUtilCore.parseStringCharacters(rawText, chars, offsets, true);
if (!success) {
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.range(expression, calculateErrorRange(rawText, offsets[chars.length()]))
@@ -1282,44 +1315,6 @@ public final class HighlightUtil {
.create();
}
}
else if (text.contains("\\\n")) {
String message = JavaErrorBundle.message("illegal.escape.character.in.string.literal");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create();
}
}
else {
if (value == null) {
if (!text.endsWith("\"\"\"")) {
String message = JavaErrorBundle.message("text.block.unclosed");
int p = expression.getTextRange().getEndOffset();
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(p, p).endOfLine().descriptionAndTooltip(message).create();
}
else {
StringBuilder chars = new StringBuilder(rawText.length());
int[] offsets = new int[rawText.length() + 1];
boolean success = CodeInsightUtilCore.parseStringCharacters(rawText, chars, offsets);
if (!success) {
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.range(expression, calculateErrorRange(rawText, offsets[chars.length()]))
.descriptionAndTooltip(JavaErrorBundle.message("illegal.escape.character.in.string.literal"))
.create();
}
else {
String message = JavaErrorBundle.message("text.block.new.line");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create();
}
}
}
else {
if (file != null && containsUnescaped(text, "\\\n")) {
HighlightInfo info = checkFeature(expression, HighlightingFeature.TEXT_BLOCK_ESCAPES, level, file);
if (info != null) return info;
}
}
}
if (file != null && containsUnescaped(text, "\\s")) {
HighlightInfo info = checkFeature(expression, HighlightingFeature.TEXT_BLOCK_ESCAPES, level, file);
if (info != null) return info;
}
}
@@ -2747,6 +2742,7 @@ public final class HighlightUtil {
if (illegalEscapeConsumer != null) illegalEscapeConsumer.accept(startOfUnicodeEscape, i + j);
return result.toString();
}
value <<= 4;
c = text.charAt(i + j);
if ('0' <= c && c <= '9') value += c - '0';
else if ('a' <= c && c <= 'f') value += (c - 'a') + 10;

View File

@@ -401,11 +401,15 @@ public final class PsiLiteralUtil {
while (true) {
char c = rawText.charAt(start++);
if (c == '\n') break;
if (!Character.isWhitespace(c) || start == rawText.length()) return null;
if (!isTextBlockWhiteSpace(c) || start == rawText.length()) return null;
}
return rawText.substring(start, rawText.length() - 3).split("\n", -1);
}
public static boolean isTextBlockWhiteSpace(char c) {
return c == ' ' || c == '\t' || c == '\f';
}
/**
* Determines how many whitespaces would be excluded at the beginning of each line of text block content.
* See JEP 368 for more details.

View File

@@ -17,7 +17,7 @@
// string literal
public class a {
char c1 = <error descr="Empty character literal">''</error>;
char c2 = <error descr="Illegal escape character in character literal">'\dd'</error>;
char c2 = '<error descr="Illegal escape character in character literal">\d</error>d';
char c4 = <error descr="Too many characters in character literal">'xxx'</error>;
char c5 = <error descr="Too many characters in character literal">'\78'</error>;
char c6 = <error descr="Too many characters in character literal">'\78'</error>;
@@ -35,14 +35,14 @@ public class a {
String s5 = "<error descr="Illegal escape character in string literal">\u000d</error>";
String s6 = "<error descr="Illegal escape character in string literal">\u000a</error>";
String s7 = <error descr="Illegal escape character in string literal">"\
"</error>;
String s7 = "<error descr="Illegal escape character in string literal">\
</error> ";
String s8 = "<error descr="Illegal Unicode escape sequence">\ubad</error><error descr="Illegal Unicode escape sequence">\ubad</error> <error descr="Illegal Unicode escape sequence">\ubad</error> <error descr="Illegal Unicode escape sequence">\ubad</error>";
char c7 = <error descr="Illegal escape character in character literal">'\u000d'</error>;
char c8 = <error descr="Illegal escape character in character literal">'\u000a'</error>;
char c7 = '<error descr="Illegal escape character in character literal">\u000d</error>';
char c8 = '<error descr="Illegal escape character in character literal">\u000a</error>';
char c9 = '<error descr="Illegal Unicode escape sequence">\u</error>';
char c10 = <error descr="Illegal escape character in character literal">'\
'</error>;
char c10 = '<error descr="Illegal escape character in character literal">\
</error> ';
String perverts = "\uuuuuuuuuuuu1234";
String perverts2 = "<error descr="Illegal Unicode escape sequence">\uuuuuuuuuuuu123</error>";

View File

@@ -24,5 +24,6 @@ class UnsupportedFeatures {
String raw = <error descr="Text block literals are not supported at language level '1.4'">"""hi there"""</error>;
String spaceEscapeSeq = <error descr="'\s' escape sequences are not supported at language level '1.4'">"\s"</error>;
char c = <error descr="'\s' escape sequences are not supported at language level '1.4'">'\s'</error>;
}
}

View File

@@ -16,6 +16,10 @@ class C {
\
""";
String valid3 = """\u000Ahello""";
String valid4 = """\u0020\u0020\u0020\u000Dhello""";
String valid5 = \u0022\u0022\u0022\u0020\u0020\u0020\u000Ahello""";
String backSlash1 = """
\u005c\""";
}

View File

@@ -87,12 +87,17 @@ public abstract class CodeInsightUtilCore extends FileModificationService {
}
public static boolean parseStringCharacters(@NotNull String chars, @NotNull StringBuilder out, int @Nullable [] sourceOffsets) {
return parseStringCharacters(chars, out, sourceOffsets, true);
}
public static boolean parseStringCharacters(@NotNull String chars, @NotNull StringBuilder out, int @Nullable [] sourceOffsets,
boolean textBlock) {
LOG.assertTrue(sourceOffsets == null || sourceOffsets.length == chars.length() + 1);
if (noEscape(chars, sourceOffsets)) {
out.append(chars);
return true;
}
return parseStringCharactersWithEscape(chars, out, sourceOffsets);
return parseStringCharactersWithEscape(chars, textBlock, out, sourceOffsets);
}
/**
@@ -110,18 +115,19 @@ public abstract class CodeInsightUtilCore extends FileModificationService {
return chars;
}
StringBuilder out = new StringBuilder(chars.length());
return parseStringCharactersWithEscape(chars, out, sourceOffsets) ? out : null;
return parseStringCharactersWithEscape(chars, true, out, sourceOffsets) ? out : null;
}
private static boolean noEscape(@NotNull String chars, int @Nullable [] sourceOffsets) {
if (chars.indexOf('\\') < 0) {
if (sourceOffsets != null) Arrays.setAll(sourceOffsets, IntUnaryOperator.identity());
return true;
}
return false;
if (chars.indexOf('\\') >= 0) return false;
if (sourceOffsets != null) Arrays.setAll(sourceOffsets, IntUnaryOperator.identity());
return true;
}
static boolean parseStringCharactersWithEscape(@NotNull String chars, @NotNull StringBuilder out, int @Nullable [] sourceOffsets) {
static boolean parseStringCharactersWithEscape(@NotNull String chars,
boolean textBlock,
@NotNull StringBuilder out,
int @Nullable [] sourceOffsets) {
int index = 0;
final int outOffset = out.length();
while (index < chars.length()) {
@@ -134,7 +140,7 @@ public abstract class CodeInsightUtilCore extends FileModificationService {
out.append(c);
continue;
}
index = parseEscapedSymbol(false, chars, index, out);
index = parseEscapedSymbol(false, chars, index, textBlock, out);
if (index == -1) return false;
if (sourceOffsets != null) {
sourceOffsets[out.length() - outOffset] = index;
@@ -143,17 +149,19 @@ public abstract class CodeInsightUtilCore extends FileModificationService {
return true;
}
private static int parseEscapedSymbol(boolean isAfterEscapedBackslash, @NotNull String chars, int index, @NotNull StringBuilder out) {
private static int parseEscapedSymbol(boolean isAfterEscapedBackslash, @NotNull String chars, int index,
boolean textBlock,
@NotNull StringBuilder out) {
if (index == chars.length()) return -1;
char c = chars.charAt(index++);
if (parseEscapedChar(c, out)) {
if (parseEscapedChar(c, textBlock, out)) {
return index;
}
switch (c) {
case '\\':
boolean isUnicodeSequenceStart = isAfterEscapedBackslash && index < chars.length() && chars.charAt(index) == 'u';
if (isUnicodeSequenceStart) {
index = parseUnicodeEscape(true, chars, index, out);
index = parseUnicodeEscape(true, chars, index, textBlock, out);
}
else {
out.append('\\');
@@ -173,7 +181,7 @@ public abstract class CodeInsightUtilCore extends FileModificationService {
case 'u':
if (isAfterEscapedBackslash) return -1;
index = parseUnicodeEscape(false, chars, index - 1, out);
index = parseUnicodeEscape(false, chars, index - 1, textBlock, out);
break;
default:
@@ -182,7 +190,9 @@ public abstract class CodeInsightUtilCore extends FileModificationService {
return index;
}
private static int parseUnicodeEscape(boolean isAfterEscapedBackslash, @NotNull String s, int index, @NotNull StringBuilder out) {
private static int parseUnicodeEscape(boolean isAfterEscapedBackslash, @NotNull String s, int index,
boolean textBlock,
@NotNull StringBuilder out) {
int len = s.length();
// uuuuu1234 is valid too
do {
@@ -194,8 +204,8 @@ public abstract class CodeInsightUtilCore extends FileModificationService {
char c = s.charAt(index);
if (c == '+' || c == '-') return -1;
int code = Integer.parseInt(s.substring(index, index + 4), 16);
// line separators are invalid here
if (code == 0x000a || code == 0x000d) return -1;
// unicode escaped line separators are invalid here when not a text block
if (!textBlock && (code == 0x000a || code == 0x000d)) return -1;
char escapedChar = (char)code;
if (escapedChar == '\\') {
if (isAfterEscapedBackslash) {
@@ -205,12 +215,12 @@ public abstract class CodeInsightUtilCore extends FileModificationService {
}
else {
// u005cxyz
return parseEscapedSymbol(true, s, index + 4, out);
return parseEscapedSymbol(true, s, index + 4, textBlock, out);
}
}
if (isAfterEscapedBackslash) {
// e.g. \u005c\u006e is converted to newline
if (parseEscapedChar(escapedChar, out)) return index + 4;
if (parseEscapedChar(escapedChar, textBlock, out)) return index + 4;
return -1;
}
// just single unicode escape sequence
@@ -222,7 +232,7 @@ public abstract class CodeInsightUtilCore extends FileModificationService {
}
}
private static boolean parseEscapedChar(char c, @NotNull StringBuilder out) {
private static boolean parseEscapedChar(char c, boolean textBlock, @NotNull StringBuilder out) {
switch (c) {
case 'b':
out.append('\b');
@@ -257,7 +267,7 @@ public abstract class CodeInsightUtilCore extends FileModificationService {
return true;
case '\n':
return true;
return textBlock; // escaped newline only valid inside text block
}
return false;
}