JavaHighlightingLexer: support two new escape sequences in string literals (IDEA-231527)

GitOrigin-RevId: ca884a706c6ae4056c6534c9d5e84aeb8bef5b71
This commit is contained in:
Artemiy Sartakov
2020-01-28 12:17:23 +07:00
committed by intellij-monorepo-bot
parent 608c3b5e0b
commit a8e1ee46c9
10 changed files with 150 additions and 53 deletions

View File

@@ -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;

View File

@@ -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));

View File

@@ -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"

View File

@@ -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);
}
}
});
}
}
}

View File

@@ -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;

View File

@@ -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}''

View File

@@ -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>;
}
}

View File

@@ -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 = """
\
""";
}

View File

@@ -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()

View File

@@ -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;