mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-20 05:21:29 +07:00
JavaHighlightingLexer: support two new escape sequences in string literals (IDEA-231527)
GitOrigin-RevId: ca884a706c6ae4056c6534c9d5e84aeb8bef5b71
This commit is contained in:
committed by
intellij-monorepo-bot
parent
608c3b5e0b
commit
a8e1ee46c9
@@ -1158,60 +1158,72 @@ public class HighlightUtil extends HighlightUtilBase {
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (type == JavaTokenType.STRING_LITERAL) {
|
||||
if (value == null) {
|
||||
for (PsiElement element = expression.getFirstChild(); element != null; element = element.getNextSibling()) {
|
||||
if (element instanceof OuterLanguageElement) {
|
||||
return null;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!StringUtil.startsWithChar(text, '\"')) return null;
|
||||
if (StringUtil.endsWithChar(text, '\"')) {
|
||||
if (text.length() == 1) {
|
||||
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();
|
||||
}
|
||||
text = text.substring(1, text.length() - 1);
|
||||
}
|
||||
else {
|
||||
String message = JavaErrorBundle.message("illegal.line.end.in.string.literal");
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create();
|
||||
}
|
||||
text = text.substring(1, text.length() - 1);
|
||||
}
|
||||
else {
|
||||
String message = JavaErrorBundle.message("illegal.line.end.in.string.literal");
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create();
|
||||
}
|
||||
|
||||
StringBuilder chars = new StringBuilder();
|
||||
boolean success = PsiLiteralExpressionImpl.parseStringCharacters(text, chars, null);
|
||||
if (!success) {
|
||||
String message = JavaErrorBundle.message("illegal.escape.character.in.string.literal");
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (type == JavaTokenType.TEXT_BLOCK_LITERAL) {
|
||||
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();
|
||||
int[] offsets = new int[text.length() + 1];
|
||||
boolean success = CodeInsightUtilCore.parseStringCharacters(text, chars, offsets);
|
||||
boolean success = PsiLiteralExpressionImpl.parseStringCharacters(text, chars, null);
|
||||
if (!success) {
|
||||
String message = JavaErrorBundle.message("illegal.escape.character.in.string.literal");
|
||||
TextRange textRange = chars.length() < text.length() - 1 ? new TextRange(offsets[chars.length()], offsets[chars.length() + 1])
|
||||
: expression.getTextRange();
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
|
||||
.range(expression, textRange)
|
||||
.descriptionAndTooltip(message).create();
|
||||
}
|
||||
else {
|
||||
String message = JavaErrorBundle.message("text.block.new.line");
|
||||
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();
|
||||
int[] offsets = new int[text.length() + 1];
|
||||
boolean success = CodeInsightUtilCore.parseStringCharacters(text, chars, offsets);
|
||||
if (!success) {
|
||||
String message = JavaErrorBundle.message("illegal.escape.character.in.string.literal");
|
||||
TextRange textRange = chars.length() < text.length() - 1 ? new TextRange(offsets[chars.length()], offsets[chars.length() + 1])
|
||||
: expression.getTextRange();
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
|
||||
.range(expression, textRange)
|
||||
.descriptionAndTooltip(message).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")) {
|
||||
final HighlightInfo info = checkFeature(expression, Feature.TEXT_BLOCK_ESCAPES, level, file);
|
||||
if (info != null) return info;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (file != null && containsUnescaped(text, "\\s")) {
|
||||
final HighlightInfo info = checkFeature(expression, Feature.TEXT_BLOCK_ESCAPES, level, file);
|
||||
if (info != null) return info;
|
||||
}
|
||||
}
|
||||
|
||||
if (value instanceof Float) {
|
||||
@@ -1240,6 +1252,20 @@ public class HighlightUtil extends HighlightUtilBase {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean containsUnescaped(@NotNull String text, @NotNull String subText) {
|
||||
int start = 0;
|
||||
while ((start = StringUtil.indexOf(text, subText, start)) != -1) {
|
||||
int nSlashes = 0;
|
||||
for (int pos = start - 1; pos >= 0; pos--) {
|
||||
if (text.charAt(pos) != '\\') break;
|
||||
nSlashes++;
|
||||
}
|
||||
if (nSlashes % 2 == 0) return true;
|
||||
start += subText.length();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static final Pattern FP_LITERAL_PARTS =
|
||||
Pattern.compile("(?:" +
|
||||
"(?:0x([_\\p{XDigit}]*)\\.?([_\\p{XDigit}]*)p[+-]?([_\\d]*))" +
|
||||
@@ -3211,7 +3237,8 @@ public class HighlightUtil extends HighlightUtilBase {
|
||||
},
|
||||
TEXT_BLOCKS(LanguageLevel.JDK_13_PREVIEW, "feature.text.blocks"),
|
||||
RECORDS(LanguageLevel.JDK_14_PREVIEW, "feature.records"),
|
||||
PATTERNS(LanguageLevel.JDK_14_PREVIEW, "feature.patterns.instanceof");
|
||||
PATTERNS(LanguageLevel.JDK_14_PREVIEW, "feature.patterns.instanceof"),
|
||||
TEXT_BLOCK_ESCAPES(LanguageLevel.JDK_14_PREVIEW, "feature.text.block.escape.sequences");
|
||||
|
||||
private final LanguageLevel level;
|
||||
private final String key;
|
||||
|
||||
@@ -13,13 +13,13 @@ public class JavaHighlightingLexer extends LayeredLexer {
|
||||
public JavaHighlightingLexer(@NotNull LanguageLevel languageLevel) {
|
||||
super(JavaParserDefinition.createLexer(languageLevel));
|
||||
|
||||
registerSelfStoppingLayer(new StringLiteralLexer('\"', JavaTokenType.STRING_LITERAL),
|
||||
registerSelfStoppingLayer(new StringLiteralLexer('\"', JavaTokenType.STRING_LITERAL, false, "s"),
|
||||
new IElementType[]{JavaTokenType.STRING_LITERAL}, IElementType.EMPTY_ARRAY);
|
||||
|
||||
registerSelfStoppingLayer(new StringLiteralLexer('\'', JavaTokenType.STRING_LITERAL),
|
||||
new IElementType[]{JavaTokenType.CHARACTER_LITERAL}, IElementType.EMPTY_ARRAY);
|
||||
|
||||
registerSelfStoppingLayer(new StringLiteralLexer(StringLiteralLexer.NO_QUOTE_CHAR, JavaTokenType.TEXT_BLOCK_LITERAL),
|
||||
registerSelfStoppingLayer(new StringLiteralLexer(StringLiteralLexer.NO_QUOTE_CHAR, JavaTokenType.TEXT_BLOCK_LITERAL, true, "s"),
|
||||
new IElementType[]{JavaTokenType.TEXT_BLOCK_LITERAL}, IElementType.EMPTY_ARRAY);
|
||||
|
||||
LayeredLexer docLexer = new LayeredLexer(JavaParserDefinition.createDocLexer(languageLevel));
|
||||
|
||||
@@ -1273,6 +1273,7 @@
|
||||
<actionPromoter implementation="com.intellij.codeInsight.editorActions.JavaMethodOverloadSwitchActionPromoter"/>
|
||||
<actionPromoter implementation="com.intellij.codeInsight.editorActions.JavaNextParameterActionPromoter"/>
|
||||
<java.error.fix errorCode="lambda.variable.must.be.final" implementationClass="com.intellij.codeInsight.daemon.impl.quickfix.VariableAccessFromInnerClassJava10Fix"/>
|
||||
<stripTrailingSpacesFilterFactory implementation="com.intellij.codeEditor.JavaStripTrailingSpacesFilterFactory"/>
|
||||
|
||||
<localInspection groupPath="Java" language="JAVA" shortName="TestFailedLine"
|
||||
enabledByDefault="true" level="WARNING"
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.codeEditor;
|
||||
|
||||
import com.intellij.lang.Language;
|
||||
import com.intellij.lang.java.JavaLanguage;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.impl.PsiBasedStripTrailingSpacesFilter;
|
||||
import com.intellij.psi.JavaRecursiveElementVisitor;
|
||||
import com.intellij.psi.JavaTokenType;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiLiteralExpression;
|
||||
import com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class JavaStripTrailingSpacesFilterFactory extends PsiBasedStripTrailingSpacesFilter.Factory {
|
||||
|
||||
@Override
|
||||
protected @NotNull PsiBasedStripTrailingSpacesFilter createFilter(@NotNull Document document) {
|
||||
return new JavaPsiBasedStripTrailingSpacesFilter(document);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isApplicableTo(@NotNull Language language) {
|
||||
return language.isKindOf(JavaLanguage.INSTANCE);
|
||||
}
|
||||
|
||||
private static class JavaPsiBasedStripTrailingSpacesFilter extends PsiBasedStripTrailingSpacesFilter {
|
||||
|
||||
protected JavaPsiBasedStripTrailingSpacesFilter(@NotNull Document document) {
|
||||
super(document);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void process(@NotNull PsiFile psiFile) {
|
||||
psiFile.accept(new JavaRecursiveElementVisitor() {
|
||||
@Override
|
||||
public void visitLiteralExpression(PsiLiteralExpression expression) {
|
||||
PsiLiteralExpressionImpl literal = ObjectUtils.tryCast(expression, PsiLiteralExpressionImpl.class);
|
||||
if (literal != null && literal.getLiteralElementType() == JavaTokenType.TEXT_BLOCK_LITERAL) {
|
||||
disableRange(literal.getTextRange(), false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,7 +163,7 @@ public class PsiLiteralExpressionImpl
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
String line = lines[i];
|
||||
if (line.length() > 0) {
|
||||
sb.append(StringUtil.trimTrailing(line.substring(prefix), ' '));
|
||||
sb.append(trimTrailingSpaces(line.substring(prefix)));
|
||||
}
|
||||
if (i < lines.length - 1) {
|
||||
sb.append('\n');
|
||||
@@ -172,6 +172,14 @@ public class PsiLiteralExpressionImpl
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String trimTrailingSpaces(@NotNull String line) {
|
||||
int index = line.length() - 1;
|
||||
while (index >= 0 && line.charAt(index) == ' ') index--;
|
||||
if (index >= 0 && index < line.length() - 1 && line.charAt(index) == '\\') index++;
|
||||
return line.substring(0, index + 1);
|
||||
}
|
||||
|
||||
public int getTextBlockIndent() {
|
||||
String[] lines = getTextBlockLines();
|
||||
if (lines == null) return -1;
|
||||
|
||||
@@ -519,4 +519,5 @@ feature.enhanced.switch=Enhanced 'switch' blocks
|
||||
feature.switch.expressions='switch' expressions
|
||||
feature.records=Records
|
||||
feature.patterns.instanceof=Patterns in 'instanceof'
|
||||
feature.text.block.escape.sequences='\\s' and '\\' escape sequences
|
||||
insufficient.language.level={0} are not supported at language level ''{1}''
|
||||
|
||||
@@ -22,6 +22,8 @@ class UnsupportedFeatures {
|
||||
switch (<error descr="Incompatible types. Found: 'java.lang.annotation.ElementType', required: 'byte, char, short or int'">t</error>) { }
|
||||
|
||||
String raw = <error descr="Text block literals are not supported at language level '1.4'">"""hi there"""</error>;
|
||||
|
||||
String spaceEscapeSeq = <error descr="'\s' and '\' escape sequences are not supported at language level '1.4'">"\s"</error>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,15 @@ class C {
|
||||
String invalid1 = <error descr="Illegal text block start: missing new line after opening quotes">""""""</error>;
|
||||
String invalid2 = <error descr="Illegal text block start: missing new line after opening quotes">""" """</error>;
|
||||
String invalid3 = <error descr="Illegal text block start: missing new line after opening quotes">"""\\n """</error>;
|
||||
String invalid4 = """
|
||||
invalid escape <error descr="Illegal escape character in string literal">\</error>
|
||||
continue;
|
||||
""";
|
||||
String invalid5 = """
|
||||
\n\n\n\n<error descr="Illegal escape character in string literal">\</error>
|
||||
""";
|
||||
|
||||
String s9 = "\s";
|
||||
String s10 = <error descr="Illegal escape character in string literal">" \ "</error>;
|
||||
|
||||
String valid1 = """
|
||||
\s
|
||||
""";
|
||||
|
||||
String valid2 = """
|
||||
\
|
||||
""";
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import com.intellij.openapi.actionSystem.IdeActions
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase
|
||||
|
||||
class JavaTextBlocksHighlightingTest : LightJavaCodeInsightFixtureTestCase() {
|
||||
override fun getProjectDescriptor() = JAVA_13
|
||||
override fun getProjectDescriptor() = JAVA_14
|
||||
override fun getBasePath() = "${JavaTestUtil.getRelativeJavaTestDataPath()}/codeInsight/daemonCodeAnalyzer/textBlocks"
|
||||
|
||||
fun testTextBlocks() = doTest()
|
||||
|
||||
@@ -142,6 +142,13 @@ public abstract class CodeInsightUtilCore extends FileModificationService {
|
||||
outChars.append('\r');
|
||||
break;
|
||||
|
||||
case's':
|
||||
outChars.append(' ');
|
||||
break;
|
||||
|
||||
case '\n':
|
||||
break;
|
||||
|
||||
case'\\':
|
||||
outChars.append('\\');
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user