mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-30 02:09:59 +07:00
Highlight incorrect escape instead of entire string literal
GitOrigin-RevId: 5455c5b43f8b19161dfa23643c197c8136cbbeff
This commit is contained in:
committed by
intellij-monorepo-bot
parent
30ab60d37d
commit
304a1c2df5
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>";
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
}
|
||||
@@ -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\""";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user