Java: error highlighting for string templates (IDEA-321503)

GitOrigin-RevId: d17a540698ecf5330326a7757475756ea2f85f86
This commit is contained in:
Bas Leijdekkers
2023-07-19 11:40:26 +02:00
committed by intellij-monorepo-bot
parent 4e43fa56a0
commit 6abbbdd360
7 changed files with 114 additions and 24 deletions

View File

@@ -1329,7 +1329,8 @@ public final class HighlightUtil {
}
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message);
}
final HighlightInfo.Builder info1 = checkStringTemplateEscapes(expression, text, level, file, description);
if (info1 != null) return info1;
int rawLength = rawText.length();
StringBuilder chars = new StringBuilder(rawLength);
int[] offsets = new int[rawLength + 1];
@@ -1341,8 +1342,7 @@ public final class HighlightUtil {
}
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.range(expression, calculateErrorRange(rawText, offsets[chars.length()]))
.descriptionAndTooltip(message)
;
.descriptionAndTooltip(message);
}
int length = chars.length();
if (length > 3) {
@@ -1363,15 +1363,8 @@ public final class HighlightUtil {
}
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message);
}
if (file != null && containsUnescaped(text, "\\s")) {
HighlightInfo.Builder info = checkFeature(expression, HighlightingFeature.TEXT_BLOCK_ESCAPES, level, file);
if (info != null) {
if (description != null) {
description.set(getUnsupportedFeatureMessage(HighlightingFeature.TEXT_BLOCK_ESCAPES, level, file));
}
return info;
}
}
final HighlightInfo.Builder info = checkTextBlockEscapes(expression, text, level, file, description);
if (info != null) return info;
}
else if (type == JavaTokenType.STRING_LITERAL || type == JavaTokenType.TEXT_BLOCK_LITERAL) {
if (type == JavaTokenType.STRING_LITERAL) {
@@ -1398,6 +1391,8 @@ public final class HighlightUtil {
}
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message);
}
final HighlightInfo.Builder info1 = checkStringTemplateEscapes(expression, text, level, file, description);
if (info1 != null) return info1;
int length = rawText.length();
StringBuilder chars = new StringBuilder(length);
int[] offsets = new int[length + 1];
@@ -1411,15 +1406,8 @@ public final class HighlightUtil {
.range(expression, calculateErrorRange(rawText, offsets[chars.length()]))
.descriptionAndTooltip(message);
}
if (file != null && containsUnescaped(text, "\\s")) {
HighlightInfo.Builder info = checkFeature(expression, HighlightingFeature.TEXT_BLOCK_ESCAPES, level, file);
if (info != null) {
if (description != null) {
description.set(getUnsupportedFeatureMessage(HighlightingFeature.TEXT_BLOCK_ESCAPES, level, file));
}
return info;
}
}
final HighlightInfo.Builder info2 = checkTextBlockEscapes(expression, text, level, file, description);
if (info2 != null) return info2;
}
else {
if (!text.endsWith("\"\"\"")) {
@@ -1444,6 +1432,8 @@ public final class HighlightUtil {
}
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message);
}
final HighlightInfo.Builder info = checkStringTemplateEscapes(expression, text, level, file, description);
if (info != null) return info;
final int rawLength = rawText.length();
StringBuilder chars = new StringBuilder(rawLength);
int[] offsets = new int[rawLength + 1];
@@ -1499,6 +1489,34 @@ public final class HighlightUtil {
return null;
}
private static HighlightInfo.@Nullable Builder checkStringTemplateEscapes(@NotNull PsiLiteralExpression expression,
@NotNull String text,
@NotNull LanguageLevel level,
@Nullable PsiFile file,
@Nullable Ref<? super String> description) {
if (file == null || !containsUnescaped(text, "\\{")) return null;
HighlightInfo.Builder info = checkFeature(expression, HighlightingFeature.STRING_TEMPLATES, level, file);
if (info == null) return null;
if (description != null) {
description.set(getUnsupportedFeatureMessage(HighlightingFeature.STRING_TEMPLATES, level, file));
}
return info;
}
private static HighlightInfo.@Nullable Builder checkTextBlockEscapes(@NotNull PsiLiteralExpression expression,
@NotNull String text,
@NotNull LanguageLevel level,
@Nullable PsiFile file,
@Nullable Ref<? super String> description) {
if (file == null || !containsUnescaped(text, "\\s")) return null;
HighlightInfo.Builder info = checkFeature(expression, HighlightingFeature.TEXT_BLOCK_ESCAPES, level, file);
if (info == null) return null;
if (description != null) {
description.set(getUnsupportedFeatureMessage(HighlightingFeature.TEXT_BLOCK_ESCAPES, level, file));
}
return info;
}
@NotNull
private static TextRange calculateErrorRange(@NotNull String rawText, int start) {
int end;

View File

@@ -909,6 +909,19 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!myHolder.hasErrorResults()) add(HighlightUtil.checkLabelAlreadyInUse(statement));
}
@Override
public void visitTemplate(@NotNull PsiTemplate expression) {
visitExpression(expression);
PsiElement parent = expression.getParent();
if (!(parent instanceof PsiTemplateExpression)) {
String message = JavaErrorBundle.message("processor.missing.from.string.template.expression");
HighlightInfo.Builder highlightInfo =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message);
add(highlightInfo);
}
}
@Override
public void visitLiteralExpression(@NotNull PsiLiteralExpression expression) {
super.visitLiteralExpression(expression);

View File

@@ -13,13 +13,13 @@ public class JavaHighlightingLexer extends LayeredLexer {
public JavaHighlightingLexer(@NotNull LanguageLevel languageLevel) {
super(JavaParserDefinition.createLexer(languageLevel));
registerSelfStoppingLayer(new JavaStringLiteralLexer('\"', JavaTokenType.STRING_LITERAL, false, "s"),
registerSelfStoppingLayer(new JavaStringLiteralLexer('\"', JavaTokenType.STRING_LITERAL, false, "s{"),
new IElementType[]{JavaTokenType.STRING_LITERAL}, IElementType.EMPTY_ARRAY);
registerSelfStoppingLayer(new JavaStringLiteralLexer('\'', JavaTokenType.STRING_LITERAL),
registerSelfStoppingLayer(new JavaStringLiteralLexer('\'', JavaTokenType.STRING_LITERAL, false, "s"),
new IElementType[]{JavaTokenType.CHARACTER_LITERAL}, IElementType.EMPTY_ARRAY);
registerSelfStoppingLayer(new JavaStringLiteralLexer(StringLiteralLexer.NO_QUOTE_CHAR, JavaTokenType.TEXT_BLOCK_LITERAL, true, "s"),
registerSelfStoppingLayer(new JavaStringLiteralLexer(StringLiteralLexer.NO_QUOTE_CHAR, JavaTokenType.TEXT_BLOCK_LITERAL, true, "s{"),
new IElementType[]{JavaTokenType.TEXT_BLOCK_LITERAL}, IElementType.EMPTY_ARRAY);
registerSelfStoppingLayer(new JavaStringLiteralLexer(StringLiteralLexer.NO_QUOTE_CHAR, JavaTokenType.TEXT_BLOCK_TEMPLATE_BEGIN, true, "s"),

View File

@@ -358,6 +358,7 @@ floating.point.number.too.small=Floating point number too small
illegal.underscore=Illegal underscore
text.block.new.line=Illegal text block start: missing new line after opening quotes
text.block.unclosed=Unclosed text block
processor.missing.from.string.template.expression=Processor missing from string template expression
# suppress inspection "UnusedProperty"
expected.identifier=Identifier expected

View File

@@ -26,5 +26,7 @@ class UnsupportedFeatures {
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>;
String template = <error descr="Cannot resolve symbol 'STR'">STR</error>.<error descr="String templates are not supported at language level '1.4'">"Hello \{args[0]}"</error>;
}
}

View File

@@ -0,0 +1,18 @@
class X {
void processorMissing() {
System.out.println(<error descr="Processor missing from string template expression">"""
\{1}
"""</error>);
<error descr="Processor missing from string template expression">"\{}"</error>;
System.out.println(<error descr="Cannot resolve symbol 'NOPE'">NOPE</error>."\{false}");
}
void correct(int i) {
System.out.println(STR."the value is \{i}");
}
String unresolvedValues() {
return STR."\{<error descr="Cannot resolve symbol 'logic'">logic</error>} \{<error descr="Cannot resolve symbol 'proportion'">proportion</error>}";
}
}

View File

@@ -0,0 +1,38 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.codeInsight.daemon;
import com.intellij.JavaTestUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull;
public class LightStringTemplatesHighlightingTest extends LightJavaCodeInsightFixtureTestCase {
@Override
protected String getBasePath() {
return JavaTestUtil.getRelativeJavaTestDataPath() + "/codeInsight/daemonCodeAnalyzer/advHighlightingStringTemplates";
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return JAVA_21;
}
public void testStringTemplates() { doTest(); }
private void doTest() {
myFixture.addClass("""
package java.lang;
public interface StringTemplate {
Processor<String, RuntimeException> STR = null;
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
@FunctionalInterface
public interface Processor<R, E extends Throwable> {
R process(StringTemplate stringTemplate) throws E;
}
}""");
myFixture.configureByFile(getTestName(false) + ".java");
myFixture.checkHighlighting();
}
}