mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Java: better error recovery on annotation parsing (IDEA-209573)
GitOrigin-RevId: a600121ecbd9fa6338237f0d3802486c7f8a5656
This commit is contained in:
committed by
intellij-monorepo-bot
parent
0274e2096d
commit
1bdb332af2
+16
@@ -13,6 +13,7 @@ import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.QuickFixFactory;
|
||||
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
|
||||
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
|
||||
import com.intellij.core.JavaPsiBundle;
|
||||
import com.intellij.java.analysis.JavaAnalysisBundle;
|
||||
import com.intellij.modcommand.ModCommandAction;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
@@ -47,6 +48,21 @@ public final class AnnotationsHighlightUtil {
|
||||
private static final Logger LOG = Logger.getInstance(AnnotationsHighlightUtil.class);
|
||||
|
||||
static HighlightInfo.Builder checkNameValuePair(@NotNull PsiNameValuePair pair) {
|
||||
if (pair.getFirstChild() instanceof PsiErrorElement) return null;
|
||||
PsiIdentifier identifier = pair.getNameIdentifier();
|
||||
if (identifier == null && pair.getParent() instanceof PsiAnnotationParameterList list) {
|
||||
PsiNameValuePair[] attributes = list.getAttributes();
|
||||
if (attributes.length > 1) {
|
||||
String message = JavaPsiBundle.message("annotation.name.is.missing");
|
||||
HighlightInfo.Builder highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
|
||||
.range(pair)
|
||||
.descriptionAndTooltip(message);
|
||||
for (IntentionAction action : QuickFixFactory.getInstance().createAddAnnotationAttributeNameFixes(pair)) {
|
||||
highlightInfo.registerFix(action, null, null, null, null);
|
||||
}
|
||||
return highlightInfo;
|
||||
}
|
||||
}
|
||||
PsiAnnotation annotation = PsiTreeUtil.getParentOfType(pair, PsiAnnotation.class);
|
||||
if (annotation == null) return null;
|
||||
PsiClass annotationClass = annotation.resolveAnnotationType();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
0.is.not.an.identifier=''{0}'' is not an identifier.
|
||||
annotation.name.is.missing=Annotation attribute must be of the form 'name=value'
|
||||
annotation.name.is.missing=Annotation attribute of the form 'name=value' expected
|
||||
implicit.class.context.display=Implicit class
|
||||
anonymous.class.context.display=Anonymous in {0}
|
||||
anonymous.class.derived.display=Anonymous class derived from {0}
|
||||
@@ -154,6 +154,7 @@ expected.module.declaration=Module declaration expected
|
||||
expected.module.statement=Module directive expected
|
||||
expected.package.reference=Package reference expected
|
||||
expected.parameter=Parameter expected
|
||||
expected.parameter.or.rparen=Annotation attribute or ')' expected
|
||||
expected.rbrace='}' expected
|
||||
expected.rbracket=']' expected
|
||||
expected.resource=Resource definition expected
|
||||
|
||||
+40
-62
@@ -492,8 +492,8 @@ public class BasicDeclarationParser {
|
||||
myParser.getReferenceParser()
|
||||
.parseReferenceList(builder, JavaTokenType.THROWS_KEYWORD, myJavaElementTypeContainer.THROWS_LIST, JavaTokenType.COMMA);
|
||||
|
||||
if (anno && expect(builder, JavaTokenType.DEFAULT_KEYWORD)) {
|
||||
parseAnnotationValue(builder);
|
||||
if (anno && expect(builder, JavaTokenType.DEFAULT_KEYWORD) && parseAnnotationValue(builder) == null) {
|
||||
error(builder, JavaPsiBundle.message("expected.value"));
|
||||
}
|
||||
|
||||
return parseMethodBody(builder, declaration, anno);
|
||||
@@ -935,107 +935,85 @@ public class BasicDeclarationParser {
|
||||
private void parseAnnotationParameterList(final PsiBuilder builder) {
|
||||
PsiBuilder.Marker list = builder.mark();
|
||||
|
||||
if (!expect(builder, JavaTokenType.LPARENTH)) {
|
||||
if (!expect(builder, JavaTokenType.LPARENTH) || expect(builder, JavaTokenType.RPARENTH)) {
|
||||
done(list, myJavaElementTypeContainer.ANNOTATION_PARAMETER_LIST, myWhiteSpaceAndCommentSetHolder);
|
||||
return;
|
||||
}
|
||||
|
||||
if (expect(builder, JavaTokenType.RPARENTH)) {
|
||||
if (builder.getTokenType() == null) {
|
||||
error(builder, JavaPsiBundle.message("expected.parameter.or.rparen"));
|
||||
done(list, myJavaElementTypeContainer.ANNOTATION_PARAMETER_LIST, myWhiteSpaceAndCommentSetHolder);
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean isFirstParamNamed = parseAnnotationParameter(builder, true);
|
||||
boolean isFirstParamWarned = false;
|
||||
|
||||
boolean afterBad = false;
|
||||
PsiBuilder.Marker elementMarker = parseAnnotationElement(builder);
|
||||
while (true) {
|
||||
final IElementType tokenType = builder.getTokenType();
|
||||
IElementType tokenType = builder.getTokenType();
|
||||
if (tokenType == null) {
|
||||
error(builder, JavaPsiBundle.message("expected.parameter"));
|
||||
error(builder, JavaPsiBundle.message(elementMarker == null ? "expected.parameter.or.rparen" : "expected.comma.or.rparen"));
|
||||
break;
|
||||
}
|
||||
else if (expect(builder, JavaTokenType.RPARENTH)) {
|
||||
break;
|
||||
}
|
||||
else if (tokenType == JavaTokenType.COMMA) {
|
||||
final PsiBuilder.Marker errorStart = builder.mark();
|
||||
final PsiBuilder.Marker errorEnd = builder.mark();
|
||||
builder.advanceLexer();
|
||||
final boolean hasParamName = parseAnnotationParameter(builder, false);
|
||||
if (!isFirstParamNamed && hasParamName && !isFirstParamWarned) {
|
||||
errorStart.errorBefore(JavaPsiBundle.message("annotation.name.is.missing"), errorEnd);
|
||||
isFirstParamWarned = true;
|
||||
elementMarker = parseAnnotationElement(builder);
|
||||
if (elementMarker == null) {
|
||||
error(builder, JavaPsiBundle.message("annotation.name.is.missing"));
|
||||
tokenType = builder.getTokenType();
|
||||
if (tokenType != JavaTokenType.COMMA && tokenType != JavaTokenType.RPARENTH) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
errorStart.drop();
|
||||
}
|
||||
errorEnd.drop();
|
||||
}
|
||||
else if (!afterBad) {
|
||||
error(builder, JavaPsiBundle.message("expected.comma.or.rparen"));
|
||||
builder.advanceLexer();
|
||||
afterBad = true;
|
||||
}
|
||||
else {
|
||||
afterBad = false;
|
||||
parseAnnotationParameter(builder, false);
|
||||
error(builder, JavaPsiBundle.message(elementMarker == null ? "expected.parameter.or.rparen" : "expected.comma.or.rparen"));
|
||||
tokenType = builder.lookAhead(1);
|
||||
if (tokenType != JavaTokenType.COMMA && tokenType != JavaTokenType.RPARENTH) break;
|
||||
builder.advanceLexer();
|
||||
}
|
||||
}
|
||||
|
||||
done(list, myJavaElementTypeContainer.ANNOTATION_PARAMETER_LIST, myWhiteSpaceAndCommentSetHolder);
|
||||
}
|
||||
|
||||
private boolean parseAnnotationParameter(final PsiBuilder builder, final boolean mayBeSimple) {
|
||||
private PsiBuilder.Marker parseAnnotationElement(final PsiBuilder builder) {
|
||||
PsiBuilder.Marker pair = builder.mark();
|
||||
|
||||
if (mayBeSimple) {
|
||||
parseAnnotationValue(builder);
|
||||
if (builder.getTokenType() != JavaTokenType.EQ) {
|
||||
done(pair, myJavaElementTypeContainer.NAME_VALUE_PAIR, myWhiteSpaceAndCommentSetHolder);
|
||||
return false;
|
||||
}
|
||||
|
||||
pair.rollbackTo();
|
||||
pair = builder.mark();
|
||||
PsiBuilder.Marker valueMarker = parseAnnotationValue(builder);
|
||||
if (valueMarker == null && builder.getTokenType() != JavaTokenType.EQ) {
|
||||
pair.drop();
|
||||
return null;
|
||||
}
|
||||
if (builder.getTokenType() != JavaTokenType.EQ) {
|
||||
done(pair, myJavaElementTypeContainer.NAME_VALUE_PAIR, myWhiteSpaceAndCommentSetHolder);
|
||||
return pair;
|
||||
}
|
||||
|
||||
final boolean hasName = expectOrError(builder, JavaTokenType.IDENTIFIER, "expected.identifier");
|
||||
pair.rollbackTo();
|
||||
pair = builder.mark();
|
||||
|
||||
expectOrError(builder, JavaTokenType.EQ, "expected.eq");
|
||||
|
||||
parseAnnotationValue(builder);
|
||||
expectOrError(builder, JavaTokenType.IDENTIFIER, "expected.identifier");
|
||||
expect(builder, JavaTokenType.EQ);
|
||||
valueMarker = parseAnnotationValue(builder);
|
||||
if (valueMarker == null) error(builder, JavaPsiBundle.message("expected.value"));
|
||||
|
||||
done(pair, myJavaElementTypeContainer.NAME_VALUE_PAIR, myWhiteSpaceAndCommentSetHolder);
|
||||
|
||||
return hasName;
|
||||
}
|
||||
|
||||
public void parseAnnotationValue(PsiBuilder builder) {
|
||||
PsiBuilder.Marker result = doParseAnnotationValue(builder);
|
||||
|
||||
if (result == null) {
|
||||
result = builder.mark();
|
||||
result.error(JavaPsiBundle.message("expected.value"));
|
||||
}
|
||||
return pair;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiBuilder.Marker doParseAnnotationValue(PsiBuilder builder) {
|
||||
PsiBuilder.Marker result;
|
||||
|
||||
public PsiBuilder.Marker parseAnnotationValue(PsiBuilder builder) {
|
||||
IElementType tokenType = builder.getTokenType();
|
||||
if (tokenType == JavaTokenType.AT) {
|
||||
result = parseAnnotation(builder);
|
||||
return parseAnnotation(builder);
|
||||
}
|
||||
else if (tokenType == JavaTokenType.LBRACE) {
|
||||
result = myParser.getExpressionParser().parseArrayInitializer(
|
||||
builder, myJavaElementTypeContainer.ANNOTATION_ARRAY_INITIALIZER, this::doParseAnnotationValue, "expected.value");
|
||||
return myParser.getExpressionParser().parseArrayInitializer(
|
||||
builder, myJavaElementTypeContainer.ANNOTATION_ARRAY_INITIALIZER, this::parseAnnotationValue, "expected.value");
|
||||
}
|
||||
else {
|
||||
result = myParser.getExpressionParser().parseConditional(builder);
|
||||
return myParser.getExpressionParser().parseConditional(builder);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -96,7 +96,8 @@ public final class JavaCompletionContributor extends CompletionContributor imple
|
||||
psiNameValuePair().withSuperParent(2, psiElement(PsiAnnotation.class));
|
||||
private static final ElementPattern<PsiElement> ANNOTATION_ATTRIBUTE_NAME =
|
||||
or(psiElement(PsiIdentifier.class).withParent(NAME_VALUE_PAIR),
|
||||
psiElement().afterLeaf("(").withParent(psiReferenceExpression().withParent(NAME_VALUE_PAIR)));
|
||||
psiElement().afterLeaf("(").withParent(psiReferenceExpression().withParent(NAME_VALUE_PAIR)),
|
||||
psiElement().afterLeaf(",").withParent(psiReferenceExpression().withParent(NAME_VALUE_PAIR)));
|
||||
private static final PsiJavaElementPattern.Capture<PsiElement> IN_TYPE_PARAMETER =
|
||||
psiElement().afterLeaf(PsiKeyword.EXTENDS, PsiKeyword.SUPER, "&").withParent(
|
||||
psiElement(PsiReferenceList.class).withParent(PsiTypeParameter.class));
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
class Foo {
|
||||
|
||||
@<error descr="Cannot resolve symbol 'Deprec'">Deprec</error>(<error descr="Cannot resolve symbol 'ated'">ated</error><EOLError descr="',' or ')' expected"></EOLError>
|
||||
void a() {}
|
||||
|
||||
void b() {}
|
||||
|
||||
void c() {}
|
||||
}
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
@Ann(<error descr="Cannot find method 'value'">0</error>) class D {
|
||||
}
|
||||
|
||||
@In(""<error descr="Annotation attribute must be of the form 'name=value'">,</error> create = "")
|
||||
@In(<error descr="Annotation attribute of the form 'name=value' expected">""</error>, create = "")
|
||||
class ZZZ {
|
||||
}
|
||||
@interface In {
|
||||
|
||||
@@ -12,8 +12,6 @@ PsiJavaFile:FirstNameMissed.java
|
||||
PsiReferenceParameterList
|
||||
<empty list>
|
||||
PsiIdentifier:value1('value1')
|
||||
PsiErrorElement:Annotation attribute must be of the form 'name=value'
|
||||
<empty list>
|
||||
PsiJavaToken:COMMA(',')
|
||||
PsiWhiteSpace(' ')
|
||||
PsiNameValuePair
|
||||
|
||||
@@ -12,8 +12,6 @@ test.java.file
|
||||
REFERENCE_PARAMETER_LIST
|
||||
<empty list>
|
||||
IDENTIFIER
|
||||
ERROR_ELEMENT
|
||||
<empty list>
|
||||
COMMA
|
||||
WHITE_SPACE
|
||||
NAME_VALUE_PAIR
|
||||
|
||||
+1
@@ -38,6 +38,7 @@ public class AnnotationsHighlightingTest extends LightDaemonAnalyzerTestCase {
|
||||
public void testReceiverParameters() { doTest(); }
|
||||
public void testAnnotationOverIncompleteCode() { doTest(); }
|
||||
public void testDeclarations() { doTest(); }
|
||||
public void testErrorRecovery() { doTest(); }
|
||||
|
||||
private void doTest() { doTest(getTestName(true) + ".java"); }
|
||||
private void doTest(String name) { doTest(BASE_PATH + "/" + name, false, false); }
|
||||
|
||||
Reference in New Issue
Block a user