[java-highlighting] IDEA-369375 A syntax error (PsiErrorElement) should suppress the surrounding error

Also: allow a custom highlighter to supersede the default one

GitOrigin-RevId: c7d7a8be3ef762c9516ed3b637d467d4b544af00
This commit is contained in:
Tagir Valeev
2025-03-18 12:16:32 +01:00
committed by intellij-monorepo-bot
parent daba57cf6e
commit b6806f1e7b
18 changed files with 67 additions and 36 deletions

View File

@@ -82,6 +82,17 @@ public final class JavaErrorKinds {
});
public static final Simple<PsiErrorElement> SYNTAX_ERROR =
error(PsiErrorElement.class, "syntax.error")
.withRange(e -> {
TextRange range = e.getTextRange();
if (range.getLength() == 0) {
PsiFile file = e.getContainingFile();
int endOffset = range.getEndOffset();
if (endOffset < file.getTextLength() && file.getFileDocument().getCharsSequence().charAt(endOffset) != '\n') {
return TextRange.from(0, 1);
}
}
return null;
})
.withDescription(e -> message("syntax.error", e.getErrorDescription()));
public static final Parameterized<PsiElement, JavaPreviewFeatureUtil.PreviewFeatureUsage> PREVIEW_API_USAGE =
parameterized(PsiElement.class, JavaPreviewFeatureUtil.PreviewFeatureUsage.class, "preview.api.usage")

View File

@@ -4,6 +4,7 @@ package com.intellij.codeInsight.daemon.impl.analysis;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
import com.intellij.codeInsight.daemon.impl.HighlightVisitor;
import com.intellij.codeInsight.highlighting.HighlightErrorFilter;
import com.intellij.codeInsight.intention.CommonIntentionAction;
import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider;
import com.intellij.codeInspection.ex.GlobalInspectionContextBase;
@@ -88,6 +89,11 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
return file instanceof PsiImportHolder && !InjectedLanguageManager.getInstance(file.getProject()).isInjectedFragment(file);
}
@Override
public boolean supersedesDefaultHighlighter() {
return true;
}
@Override
public void visit(@NotNull PsiElement element) {
element.accept(this);
@@ -122,9 +128,12 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
myCollector = new JavaErrorCollector(file, error -> reportError(error, holder));
}
private void reportError(@NotNull JavaCompilationError<?, ?> error,
@NotNull HighlightInfoHolder holder) {
if (error.kind() == SYNTAX_ERROR) return; // reported by DefaultHighlightVisitor
private void reportError(@NotNull JavaCompilationError<?, ?> error, @NotNull HighlightInfoHolder holder) {
if (error.psiForKind(SYNTAX_ERROR)
.filter(e -> HighlightErrorFilter.EP_NAME.findFirstSafe(e.getProject(), filter -> !filter.shouldHighlightErrorElement(e)) != null)
.isPresent()) {
return;
}
JavaErrorHighlightType javaHighlightType = error.highlightType();
HighlightInfoType type = switch (javaHighlightType) {
case ERROR, FILE_LEVEL_ERROR -> HighlightInfoType.ERROR;

View File

@@ -4,6 +4,6 @@ class C {
}
void f(int x, int y) {
if (x == 0 ||<error descr="')' expected"><error descr="Expression expected"><error descr="Illegal character: U+00A0"><error descr="Unexpected token"> </error></error></error></error><error descr="Not a statement">y == 0</error><error descr="Unexpected token">)</error> { }
if (x == 0 ||<error descr="')' expected"><error descr="Expression expected"><error descr="Illegal character: U+00A0"> </error></error></error><error descr="Not a statement">y == 0</error><error descr="Unexpected token">)</error> { }
}
}

View File

@@ -122,7 +122,7 @@ class array {
{
int[] a1 =<error descr="Expression expected"> </error><error descr="Unexpected token">.</error>new <error descr="Cannot resolve symbol 'C'">C</error>[0];
int[] a2 = {}.new <error descr="Cannot resolve symbol 'D'">D</error>[0];
int[] a3 = <error descr="Lambda expressions are not supported at language level '1.4'">t -></error><error descr="'{' expected"> </error>.new <error descr="Cannot resolve symbol 'E'">E</error>[0];
int[] a3 = t -><error descr="'{' expected"> </error>.new <error descr="Cannot resolve symbol 'E'">E</error>[0];
int[] a4 = <error descr="Cannot resolve symbol 'a'">a</error>::<error descr="';' expected"><error descr="Identifier expected"><error descr="Unexpected token">.</error></error></error>new <error descr="Cannot resolve symbol 'F'">F</error>[0];
}
}

View File

@@ -3,7 +3,7 @@ class Test {
public static void main(String[] args) {
Box<String> stringBox = new Box<String>("123");
stringBox.transform(new <error descr="Class 'Anonymous class derived from Fn' must implement abstract method 'apply(A)' in 'Fn'">Fn<String,<error descr="Identifier expected"> </error>></error>() {});
stringBox.transform(new Fn<String,<error descr="Identifier expected"> </error>>() {});
}

View File

@@ -54,10 +54,10 @@ class X {
}
int instanceofTest(Object obj) {
if (obj instanceof<error descr="')' expected"><error descr="Type expected"> </error></error><error descr="Not a statement">(Integer</error><error descr="')' expected"> </error><error descr="Cannot resolve symbol 'i'">i</error> && predicate()<error descr="Unexpected token">)</error><error descr="Unexpected token">)</error> {
if (obj instanceof<error descr="')' expected"><error descr="Type expected"> </error></error>(Integer<error descr="')' expected"> </error><error descr="Cannot resolve symbol 'i'">i</error> && predicate()<error descr="Unexpected token">)</error><error descr="Unexpected token">)</error> {
return 1;
}
if (obj instanceof<error descr="')' expected"><error descr="Type expected"> </error></error><error descr="Not a statement">(String</error><error descr="')' expected"> </error><error descr="Cannot resolve symbol 's'">s</error><error descr="Unexpected token">)</error><error descr="Unexpected token">)</error> {
if (obj instanceof<error descr="')' expected"><error descr="Type expected"> </error></error>(String<error descr="')' expected"> </error><error descr="Cannot resolve symbol 's'">s</error><error descr="Unexpected token">)</error><error descr="Unexpected token">)</error> {
return 3;
}
return 2;

View File

@@ -1,3 +1,3 @@
// IDEA-369310
@SuppressWarnings(<error descr="Attribute value must be constant">"unused".</error><error descr="Identifier expected">)</error>
@SuppressWarnings("unused".<error descr="Identifier expected">)</error>
class X {}

View File

@@ -13,7 +13,7 @@ public class LombokDumbModeApplication {
.name("2")
.surname("3")
.email("4")
.<error descr="Incompatible types. Found: 'capture<?>', required: 'UserDao'">name</error>("1")<error descr="';' expected">a</error>
.name("1")<error descr="';' expected">a</error>
.<info descr="Not resolved until the project is fully loaded">id</info>(1)
.<info descr="Not resolved until the project is fully loaded">build</info>();

View File

@@ -3,7 +3,7 @@ class IncompleteSwitch {
int i = switch (o) {
case '2':
yield 2;
case <error descr="Primitive types in patterns, instanceof and switch are not supported at language level '21'">char a</error> when a == '1'<EOLError descr="':' or '->' expected"></EOLError>
case char a when a == '1'<EOLError descr="':' or '->' expected"></EOLError>
};
}
}

View File

@@ -149,8 +149,8 @@ public class ConditionCoveredByFurtherCondition {
}
void testErrorElement(Object obj) {
if(!(obj instanceof Integer) && !(obj instanceof Long) && <error descr="Operator '!' cannot be applied to 'java.lang.Object'">!(obj</error><error descr="')' expected"><error descr="')' expected"> </error></error><error descr="Not a statement">Number</error><error descr="Unexpected token">)</error><error descr="Unexpected token">)</error> {}
if(<warning descr="Condition '!(obj instanceof Integer)' covered by subsequent condition '!(obj instanceof Number)'">!(obj instanceof Integer)</warning> && !(obj instanceof Number) && <error descr="Operator '!' cannot be applied to 'java.lang.Object'">!(obj</error><error descr="')' expected"><error descr="')' expected"> </error></error><error descr="Not a statement">Number</error><error descr="Unexpected token">)</error><error descr="Unexpected token">)</error> {}
if(!(obj instanceof Integer) && !(obj instanceof Long) && !(obj<error descr="')' expected"><error descr="')' expected"> </error></error><error descr="Not a statement">Number</error><error descr="Unexpected token">)</error><error descr="Unexpected token">)</error> {}
if(<warning descr="Condition '!(obj instanceof Integer)' covered by subsequent condition '!(obj instanceof Number)'">!(obj instanceof Integer)</warning> && !(obj instanceof Number) && !(obj<error descr="')' expected"><error descr="')' expected"> </error></error><error descr="Not a statement">Number</error><error descr="Unexpected token">)</error><error descr="Unexpected token">)</error> {}
}
void testErrorElement2(char ch) {
@@ -167,7 +167,7 @@ public class ConditionCoveredByFurtherCondition {
}
void testIncompleteLambda2(Object x) {
if (x != null && <error descr="Unexpected lambda expression">() -> x instanceof</error><error descr="')' expected"><error descr="Type expected"> </error></error>
if (x != null && () -> x instanceof<error descr="')' expected"><error descr="Type expected"> </error></error>
}
void testBooleanChain(boolean b1, boolean b2) {

View File

@@ -4,7 +4,7 @@ class UnnecessaryUnicodeEscape {
// <warning descr="Unicode escape sequence '\uuuuuu0061' can be replaced with 'a'">\uuuuuu0061</warning><warning descr="Unicode escape sequence '\u0062' can be replaced with 'b'">\u0062</warning>
// control char & not representable char: \u0010 \u00e4
char[] surrogates = new char[]{'\ud800','\udc00'};<warning descr="Unicode escape sequence '\u0021' can be replaced with '!'"><error descr="Illegal character: \ (U+005C)"><error descr="Unexpected token">\</error></error><error descr="Cannot resolve symbol 'u0021'">u0021</error></warning><EOLError descr="Identifier expected"></EOLError>
char[] surrogates = new char[]{'\ud800','\udc00'};<warning descr="Unicode escape sequence '\u0021' can be replaced with '!'"><error descr="Illegal character: \ (U+005C)">\</error><error descr="Cannot resolve symbol 'u0021'">u0021</error></warning><EOLError descr="Identifier expected"></EOLError>
String t = "<warning descr="Unicode escape sequence '\u0020' can be replaced with ' '">\u0020</warning>";
String u = "\u200B\u200E\u00A0\u200F";

View File

@@ -1,6 +1,6 @@
class Test {
int testIncomplete(Object obj) {
return switch(<error descr="'switch' expression does not cover all possible input values">obj</error>) {
return switch(obj) {
case String s when<EOLError descr="Expression expected"></EOLError><EOLError descr="':' or '->' expected"></EOLError>
};
}

View File

@@ -1,6 +1,6 @@
class Test {
int testIncomplete(Object obj) {
return switch(<error descr="'switch' expression does not cover all possible input values">obj</error>) {
return switch(obj) {
case String s when<EOLError descr="Expression expected"></EOLError><EOLError descr="':' or '->' expected"></EOLError>
};
}

View File

@@ -1996,7 +1996,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase {
void f() {
//XXX
<caret> # // Unexpected token
<caret> : // Unexpected token
}
}""";

View File

@@ -18,5 +18,5 @@ public class CachedNumberConstructorCallInspectionTest extends LightJavaInspecti
public void testSimple() { doStatementTest("new /*Number constructor call with primitive argument*/Integer/**/(1);"); }
public void testStringArgument() { doStatementTest("new /*Number constructor call with primitive argument*/Byte/**/(\"1\");"); }
public void testNoWarn() { doStatementTest("Long.valueOf(1L);"); }
public void testNoAssertionError() { doStatementTest("Integer i = new /*!Cannot inherit from final class 'java.lang.Integer'*/Integer/*!*/(new String/*!'(' or '[' expected*//*!',' or ')' expected*/{/*!*//*!*/}/*!';' expected*//*!Unexpected token*/)/*!*//*!*/;"); }
public void testNoAssertionError() { doStatementTest("Integer i = new Integer(new String/*!'(' or '[' expected*//*!',' or ')' expected*/{/*!*//*!*/}/*!';' expected*//*!Unexpected token*/)/*!*//*!*/;"); }
}

View File

@@ -738,6 +738,7 @@ com.intellij.codeInsight.daemon.impl.HighlightVisitor
- a:clone():com.intellij.codeInsight.daemon.impl.HighlightVisitor
- order():I
- a:suitableForFile(com.intellij.psi.PsiFile):Z
- supersedesDefaultHighlighter():Z
- a:visit(com.intellij.psi.PsiElement):V
com.intellij.codeInsight.daemon.impl.HighlightingSession
- a:getColorsScheme():com.intellij.openapi.editor.colors.EditorColorsScheme

View File

@@ -21,6 +21,13 @@ public interface HighlightVisitor extends PossiblyDumbAware {
boolean suitableForFile(@NotNull PsiFile file);
/**
* @return true if this highlighter covers the errors reported by {@link DefaultHighlightVisitor}, so the latter should be turned off.
*/
default boolean supersedesDefaultHighlighter() {
return false;
}
void visit(@NotNull PsiElement element);
boolean analyze(@NotNull PsiFile file, boolean updateWholeFile, @NotNull HighlightInfoHolder holder, @NotNull Runnable action);

View File

@@ -9,6 +9,7 @@ import com.intellij.concurrency.JobLauncher;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.editor.colors.TextAttributesScheme;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.IndexNotReadyException;
@@ -20,7 +21,6 @@ import com.intellij.util.ArrayUtil;
import com.intellij.util.Consumer;
import com.intellij.util.ExceptionUtil;
import com.intellij.util.containers.ContainerUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -67,6 +67,13 @@ class HighlightVisitorRunner {
// or we can just clone the visitor, which is not expensive, given that all overrides are just a single new() call.
cloned = visitor.clone();
assert cloned.getClass() == visitor.getClass() : visitor.getClass()+".clone() must return a copy of "+visitor.getClass()+"; but got: "+cloned+" ("+cloned.getClass()+")";
if (cloned.supersedesDefaultHighlighter()) {
int index = ContainerUtil.indexOf(clones, e -> e instanceof DefaultHighlightVisitor);
if (index >= 0) {
clones[index] = cloned;
continue;
}
}
clones[o++] = cloned;
}
}
@@ -97,17 +104,14 @@ class HighlightVisitorRunner {
boolean myUpdateAll,
@NotNull Supplier<? extends HighlightInfoHolder> infoHolderProducer,
@NotNull ResultSink resultSink) {
List<? extends VisitorInfo> visitorInfos = ContainerUtil.map(visitors, v -> new VisitorInfo(v, new HashSet<>(), infoHolderProducer.get()));
List<VisitorInfo> visitorInfos = ContainerUtil.map(visitors, v -> new VisitorInfo(v, new HashSet<>(), infoHolderProducer.get()));
// first, run all visitors in parallel on all visible elements, then run all visitors in parallel on all invisible elements
List<PsiElement> elements = ContainerUtil.concat(elements1, elements2);
LongList ranges = new LongArrayList();
ranges.addAll(ranges1);
ranges.addAll(ranges2);
if (GeneralHighlightingPass.LOG.isDebugEnabled()) {
GeneralHighlightingPass.LOG.debug("HighlightVisitorRunner: visitors: " + Arrays.toString(visitors)+"; myRestrictRange="+myRestrictRange+"; psiFile="+psiFile);
}
boolean res =
JobLauncher.getInstance().invokeConcurrentlyUnderProgress(visitorInfos, ProgressManager.getGlobalProgressIndicator(), visitorInfo -> {
JobLauncher.getInstance().invokeConcurrentlyUnderProgress(visitorInfos, ProgressIndicatorProvider.getGlobalProgressIndicator(), visitorInfo -> {
HighlightVisitor visitor = visitorInfo.visitor();
if (GeneralHighlightingPass.LOG.isDebugEnabled()) {
GeneralHighlightingPass.LOG.debug("HighlightVisitorRunner: running visitor: " + visitor+"("+visitor.getClass()+"); psiFile="+psiFile+"; "+Thread.currentThread());
@@ -117,7 +121,7 @@ class HighlightVisitorRunner {
HighlightInfoHolder holder = visitorInfo.holder();
boolean result = visitor.analyze(psiFile, myUpdateAll, holder, () -> {
reportOutOfRunVisitorInfos(0, ANALYZE_BEFORE_RUN_VISITOR_FAKE_PSI_ELEMENT, holder, visitor, resultSink);
runVisitor(psiFile, myRestrictRange, elements, ranges, chunkSize, visitorInfo.skipParentsSet(), holder, forceHighlightParents, visitor, resultSink);
runVisitor(psiFile, myRestrictRange, elements, chunkSize, visitorInfo.skipParentsSet(), holder, forceHighlightParents, visitor, resultSink);
sizeAfterRunVisitor[0] = holder.size();
});
reportOutOfRunVisitorInfos(sizeAfterRunVisitor[0], ANALYZE_AFTER_RUN_VISITOR_FAKE_PSI_ELEMENT, holder, visitor, resultSink);
@@ -167,16 +171,15 @@ class HighlightVisitorRunner {
resultSink.accept(visitor.getClass(), fakePsiElement, newInfos);
}
private static void runVisitor(@NotNull PsiFile psiFile,
@NotNull TextRange myRestrictRange,
@NotNull List<? extends PsiElement> elements,
@NotNull LongList ranges,
int chunkSize,
@NotNull Set<? super PsiElement> skipParentsSet,
@NotNull HighlightInfoHolder holder,
boolean forceHighlightParents,
@NotNull HighlightVisitor visitor,
@NotNull ResultSink resultSink) {
private static void runVisitor(@NotNull PsiFile psiFile,
@NotNull TextRange myRestrictRange,
@NotNull List<? extends PsiElement> elements,
int chunkSize,
@NotNull Set<? super PsiElement> skipParentsSet,
@NotNull HighlightInfoHolder holder,
boolean forceHighlightParents,
@NotNull HighlightVisitor visitor,
@NotNull ResultSink resultSink) {
boolean failed = false;
int nextLimit = chunkSize;
List<HighlightInfo> infos = new ArrayList<>();