Improve annotation identifier recovery
Change-Id: Iaac50ebf39f6f76939f738f97929c8212fa2c098
Reviewed-on: https://dart-review.googlesource.com/56240
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
diff --git a/pkg/analyzer/test/src/fasta/recovery/partial_code/annotation_test.dart b/pkg/analyzer/test/src/fasta/recovery/partial_code/annotation_test.dart
index 1b2fdee..06105e3 100644
--- a/pkg/analyzer/test/src/fasta/recovery/partial_code/annotation_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/partial_code/annotation_test.dart
@@ -12,47 +12,116 @@
class AnnotationTest extends PartialCodeTest {
buildAll() {
- List<TestDescriptor> descriptors = <TestDescriptor>[
- new TestDescriptor(
- 'ampersand',
- '@',
- [ParserErrorCode.MISSING_IDENTIFIER],
- '@_s_',
- allFailing: true,
- ),
- new TestDescriptor(
- 'leftParen',
- '@a(',
- [ParserErrorCode.EXPECTED_TOKEN],
- '@a()',
- allFailing: true,
- ),
- ];
buildTests(
'annotation_topLevel',
- expectErrors(descriptors, [ParserErrorCode.EXPECTED_EXECUTABLE]),
+ [
+ new TestDescriptor(
+ 'ampersand',
+ '@',
+ [
+ ParserErrorCode.MISSING_IDENTIFIER,
+ ParserErrorCode.EXPECTED_EXECUTABLE
+ ],
+ '@_s_',
+ expectedErrorsInValidCode: [ParserErrorCode.EXPECTED_EXECUTABLE]),
+ new TestDescriptor(
+ 'leftParen',
+ '@a(',
+ [
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.EXPECTED_EXECUTABLE
+ ],
+ '@a()',
+ expectedErrorsInValidCode: [ParserErrorCode.EXPECTED_EXECUTABLE]),
+ ],
[],
);
buildTests(
'annotation_topLevel',
- descriptors,
+ [
+ new TestDescriptor(
+ 'ampersand', '@', [ParserErrorCode.MISSING_IDENTIFIER], '@_s_',
+ failing: ['typedef', 'functionNonVoid', 'getter', 'setter']),
+ new TestDescriptor(
+ 'leftParen', '@a(', [ScannerErrorCode.EXPECTED_TOKEN], '@a()',
+ allFailing: true),
+ ],
PartialCodeTest.declarationSuffixes,
includeEof: false,
);
+
buildTests(
'annotation_classMember',
- descriptors,
- PartialCodeTest.classMemberSuffixes,
+ [
+ new TestDescriptor(
+ 'ampersand',
+ '@',
+ [
+ ParserErrorCode.MISSING_IDENTIFIER,
+ ParserErrorCode.EXPECTED_CLASS_MEMBER
+ ],
+ '@_s_',
+ expectedErrorsInValidCode: [ParserErrorCode.EXPECTED_CLASS_MEMBER]),
+ new TestDescriptor(
+ 'leftParen',
+ '@a(',
+ [
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.EXPECTED_CLASS_MEMBER
+ ],
+ '@a()',
+ expectedErrorsInValidCode: [ParserErrorCode.EXPECTED_CLASS_MEMBER]),
+ ],
+ [],
head: 'class C { ',
tail: ' }',
);
buildTests(
+ 'annotation_classMember',
+ [
+ new TestDescriptor(
+ 'ampersand', '@', [ParserErrorCode.MISSING_IDENTIFIER], '@_s_',
+ failing: ['methodNonVoid', 'getter', 'setter']),
+ new TestDescriptor(
+ 'leftParen', '@a(', [ScannerErrorCode.EXPECTED_TOKEN], '@a()',
+ allFailing: true),
+ ],
+ PartialCodeTest.classMemberSuffixes,
+ includeEof: false,
+ head: 'class C { ',
+ tail: ' }',
+ );
+
+ buildTests(
'annotation_local',
- expectErrors(descriptors, [
- ParserErrorCode.EXPECTED_TOKEN,
- ParserErrorCode.EXPECTED_TYPE_NAME,
- ParserErrorCode.MISSING_IDENTIFIER,
- ]),
+ [
+ new TestDescriptor(
+ 'ampersand',
+ '@',
+ [
+ ParserErrorCode.MISSING_IDENTIFIER,
+ ParserErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.MISSING_IDENTIFIER
+ ],
+ '@_s_',
+ expectedErrorsInValidCode: [
+ ParserErrorCode.MISSING_IDENTIFIER,
+ ParserErrorCode.EXPECTED_TOKEN
+ ]),
+ new TestDescriptor(
+ 'leftParen',
+ '@a(',
+ [
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.MISSING_IDENTIFIER
+ ],
+ '@a()',
+ expectedErrorsInValidCode: [
+ ParserErrorCode.MISSING_IDENTIFIER,
+ ParserErrorCode.EXPECTED_TOKEN
+ ]),
+ ],
[],
head: 'f() { ',
tail: ' }',
@@ -60,24 +129,71 @@
// TODO(brianwilkerson) Many of the combinations produced by the following
// produce "valid" code that is not valid. Even when we recover the
// annotation, the following statement is not allowed to have an annotation.
+ const localAllowed = const [
+ 'localVariable',
+ 'localFunctionNonVoid',
+ 'localFunctionVoid'
+ ];
+ List<TestSuffix> localAnnotationAllowedSuffixes = PartialCodeTest
+ .statementSuffixes
+ .where((t) => localAllowed.contains(t.name))
+ .toList();
+ List<TestSuffix> localAnnotationNotAllowedSuffixes = PartialCodeTest
+ .statementSuffixes
+ .where((t) => !localAllowed.contains(t.name))
+ .toList();
+
buildTests(
'annotation_local',
- descriptors,
- PartialCodeTest.statementSuffixes,
- head: 'f() { ',
+ [
+ new TestDescriptor(
+ 'ampersand', '@', [ParserErrorCode.MISSING_IDENTIFIER], '@_s_',
+ failing: ['localFunctionNonVoid']),
+ new TestDescriptor(
+ 'leftParen', '@a(', [ParserErrorCode.MISSING_IDENTIFIER], '@a()',
+ allFailing: true),
+ ],
+ localAnnotationAllowedSuffixes,
includeEof: false,
+ head: 'f() { ',
+ tail: ' }',
+ );
+ buildTests(
+ 'annotation_local',
+ [
+ new TestDescriptor(
+ 'ampersand',
+ '@',
+ [
+ ParserErrorCode.MISSING_IDENTIFIER,
+ ParserErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.MISSING_IDENTIFIER
+ ],
+ '@_s_',
+ expectedErrorsInValidCode: [
+ ParserErrorCode.MISSING_IDENTIFIER,
+ ParserErrorCode.EXPECTED_TOKEN
+ ],
+ failing: ['labeled']),
+ new TestDescriptor(
+ 'leftParen',
+ '@a(',
+ [
+ ScannerErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.EXPECTED_TOKEN,
+ ParserErrorCode.MISSING_IDENTIFIER
+ ],
+ '@a()',
+ expectedErrorsInValidCode: [
+ ParserErrorCode.MISSING_IDENTIFIER,
+ ParserErrorCode.EXPECTED_TOKEN
+ ],
+ allFailing: true),
+ ],
+ localAnnotationNotAllowedSuffixes,
+ includeEof: false,
+ head: 'f() { ',
tail: ' }',
);
}
-
- /**
- * Return a list of descriptors just like the given [descriptors] except that
- * they have the given list of [errors] as the errors that are expected to be
- * in the valid code.
- */
- List<TestDescriptor> expectErrors(
- List<TestDescriptor> descriptors, List<ParserErrorCode> errors) =>
- descriptors
- .map((descriptor) => descriptor.withExpectedErrorsInValidCode(errors))
- .toList();
}
diff --git a/pkg/analyzer/test/src/fasta/recovery/partial_code/partial_code_support.dart b/pkg/analyzer/test/src/fasta/recovery/partial_code/partial_code_support.dart
index faaddf6..533c856 100644
--- a/pkg/analyzer/test/src/fasta/recovery/partial_code/partial_code_support.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/partial_code/partial_code_support.dart
@@ -260,17 +260,6 @@
*/
TestDescriptor(this.name, this.invalid, this.errorCodes, this.valid,
{this.allFailing: false, this.failing, this.expectedErrorsInValidCode});
-
- /**
- * Return a new description that is exactly like this descriptor except with
- * the given [expectedErrorsInValidCode].
- */
- TestDescriptor withExpectedErrorsInValidCode(
- List<ErrorCode> expectedErrorsInValidCode) =>
- new TestDescriptor(name, invalid, errorCodes, valid,
- allFailing: allFailing,
- failing: failing,
- expectedErrorsInValidCode: expectedErrorsInValidCode);
}
/**
diff --git a/pkg/front_end/lib/src/fasta/parser/identifier_context.dart b/pkg/front_end/lib/src/fasta/parser/identifier_context.dart
index 9c65f14..a4075e1 100644
--- a/pkg/front_end/lib/src/fasta/parser/identifier_context.dart
+++ b/pkg/front_end/lib/src/fasta/parser/identifier_context.dart
@@ -39,19 +39,17 @@
/// Identifier is the start of a name in an annotation that precedes a
/// declaration (i.e. it appears directly after an `@`).
- static const metadataReference =
- const IdentifierContext('metadataReference', isScopeReference: true);
+ static const metadataReference = const MetadataReferenceIdentifierContext();
/// Identifier is part of a name in an annotation that precedes a declaration,
/// but it's not the first identifier in the name.
static const metadataContinuation =
- const IdentifierContext('metadataContinuation', isContinuation: true);
+ const MetadataReferenceIdentifierContext.continuation();
/// Identifier is part of a name in an annotation that precedes a declaration,
/// but it appears after type parameters (e.g. `foo` in `@X<Y>.foo()`).
- static const metadataContinuationAfterTypeArguments = const IdentifierContext(
- 'metadataContinuationAfterTypeArguments',
- isContinuation: true);
+ static const metadataContinuationAfterTypeArguments =
+ const MetadataReferenceIdentifierContext.continuationAfterTypeArguments();
/// Identifier is the name being declared by a typedef declaration.
static const typedefDeclaration = const TypedefDeclarationIdentifierContext();
diff --git a/pkg/front_end/lib/src/fasta/parser/identifier_context_impl.dart b/pkg/front_end/lib/src/fasta/parser/identifier_context_impl.dart
index ab54ca0..2beb97c 100644
--- a/pkg/front_end/lib/src/fasta/parser/identifier_context_impl.dart
+++ b/pkg/front_end/lib/src/fasta/parser/identifier_context_impl.dart
@@ -458,6 +458,47 @@
}
}
+/// See [IdentifierContext.metadataReference]
+/// and [IdentifierContext.metadataContinuation]
+/// and [IdentifierContext.metadataContinuationAfterTypeArguments].
+class MetadataReferenceIdentifierContext extends IdentifierContext {
+ const MetadataReferenceIdentifierContext()
+ : super('metadataReference', isScopeReference: true);
+
+ const MetadataReferenceIdentifierContext.continuation()
+ : super('metadataContinuation', isContinuation: true);
+
+ const MetadataReferenceIdentifierContext.continuationAfterTypeArguments()
+ : super('metadataContinuationAfterTypeArguments', isContinuation: true);
+
+ @override
+ Token ensureIdentifier(Token token, Parser parser) {
+ Token identifier = token.next;
+ assert(identifier.kind != IDENTIFIER_TOKEN);
+ if (identifier.isIdentifier) {
+ return identifier;
+ }
+
+ // Recovery
+ if (isOneOfOrEof(identifier, const ['{', '}', '(', ')', ']']) ||
+ looksLikeStartOfNextTopLevelDeclaration(identifier) ||
+ looksLikeStartOfNextClassMember(identifier) ||
+ looksLikeStartOfNextStatement(identifier)) {
+ identifier = parser.insertSyntheticIdentifier(token, this,
+ message: fasta.templateExpectedIdentifier.withArguments(identifier));
+ } else {
+ parser.reportRecoverableErrorWithToken(
+ identifier, fasta.templateExpectedIdentifier);
+ if (!identifier.isKeywordOrIdentifier) {
+ // When in doubt, consume the token to ensure we make progress
+ // but insert a synthetic identifier to satisfy listeners.
+ identifier = parser.rewriter.insertSyntheticIdentifier(identifier);
+ }
+ }
+ return identifier;
+ }
+}
+
/// See [IdentifierContext.methodDeclaration],
/// and [IdentifierContext.methodDeclarationContinuation],
/// and [IdentifierContext.operatorName].
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index 66e5dc0..fc82a91 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -6070,8 +6070,10 @@
// TODO(danrubel): Provide a more specific error message for extra ';'.
reportRecoverableErrorWithToken(next, fasta.templateExpectedClassMember);
listener.handleInvalidMember(next);
- // Ensure we make progress.
- token = next;
+ if (!identical(value, '}')) {
+ // Ensure we make progress.
+ token = next;
+ }
} else {
token = parseFields(beforeStart, externalToken, staticToken,
covariantToken, varFinalOrConst, beforeType, typeInfo, token, false);