Revise class and mixin header recovery
This updates the fasta parser to use the "incorrect" keyword
when "extend" or "on" is used instead of "extends" in a class declaration and
when "extend" or "extends" is used instead of "on" in a mixin declaration.
Change-Id: I6bef3f897e24c3c0100180d2bf9f4d2ec7608eef
Reviewed-on: https://dart-review.googlesource.com/c/78101
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
diff --git a/pkg/analyzer/lib/src/fasta/ast_builder.dart b/pkg/analyzer/lib/src/fasta/ast_builder.dart
index 476d0a3..23bd360 100644
--- a/pkg/analyzer/lib/src/fasta/ast_builder.dart
+++ b/pkg/analyzer/lib/src/fasta/ast_builder.dart
@@ -1771,7 +1771,10 @@
@override
void handleClassExtends(Token extendsKeyword) {
- assert(optionalOrNull('extends', extendsKeyword));
+ assert(extendsKeyword == null ||
+ identical('extends', extendsKeyword.lexeme) ||
+ identical('on', extendsKeyword.lexeme) ||
+ extendsKeyword.lexeme == 'extend');
debugEvent("ClassExtends");
TypeName supertype = pop();
@@ -1892,7 +1895,10 @@
@override
void handleMixinOn(Token onKeyword, int typeCount) {
- assert(optionalOrNull('on', onKeyword));
+ assert(onKeyword == null ||
+ identical('on', onKeyword.lexeme) ||
+ identical('extends', onKeyword.lexeme) ||
+ onKeyword.lexeme == 'extend');
debugEvent("MixinOn");
if (onKeyword != null) {
diff --git a/pkg/analyzer/test/src/fasta/recovery/partial_code/class_declaration_test.dart b/pkg/analyzer/test/src/fasta/recovery/partial_code/class_declaration_test.dart
index f822baf..e932751 100644
--- a/pkg/analyzer/test/src/fasta/recovery/partial_code/class_declaration_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/partial_code/class_declaration_test.dart
@@ -34,7 +34,8 @@
ParserErrorCode.EXPECTED_TYPE_NAME,
ParserErrorCode.MISSING_CLASS_BODY
],
- 'class A extends _s_ {}',
+ 'class A extend _s_ {}',
+ expectedErrorsInValidCode: [ParserErrorCode.EXPECTED_INSTEAD],
failing: ['functionVoid', 'functionNonVoid', 'getter']),
new TestDescriptor(
'extends',
@@ -53,7 +54,8 @@
ParserErrorCode.EXPECTED_TYPE_NAME,
ParserErrorCode.MISSING_CLASS_BODY
],
- 'class A extends _s_ {}',
+ 'class A on _s_ {}',
+ expectedErrorsInValidCode: [ParserErrorCode.EXPECTED_INSTEAD],
failing: ['functionVoid', 'functionNonVoid', 'getter']),
new TestDescriptor('extendsBody', 'class A extends {}',
[ParserErrorCode.EXPECTED_TYPE_NAME], 'class A extends _s_ {}'),
diff --git a/pkg/analyzer/test/src/fasta/recovery/partial_code/mixin_declaration_test.dart b/pkg/analyzer/test/src/fasta/recovery/partial_code/mixin_declaration_test.dart
index 70198a8..33fe03f 100644
--- a/pkg/analyzer/test/src/fasta/recovery/partial_code/mixin_declaration_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/partial_code/mixin_declaration_test.dart
@@ -43,7 +43,8 @@
ParserErrorCode.EXPECTED_TYPE_NAME,
ParserErrorCode.MISSING_CLASS_BODY
],
- 'mixin A on _s_ {}',
+ 'mixin A extend _s_ {}',
+ expectedErrorsInValidCode: [ParserErrorCode.EXPECTED_INSTEAD],
failing: ['functionVoid', 'functionNonVoid', 'getter']),
new TestDescriptor(
'extends',
@@ -53,7 +54,8 @@
ParserErrorCode.EXPECTED_TYPE_NAME,
ParserErrorCode.MISSING_CLASS_BODY
],
- 'mixin A on _s_ {}',
+ 'mixin A extends _s_ {}',
+ expectedErrorsInValidCode: [ParserErrorCode.EXPECTED_INSTEAD],
failing: ['functionVoid', 'functionNonVoid', 'getter']),
new TestDescriptor('onBody', 'mixin A on {}',
[ParserErrorCode.EXPECTED_TYPE_NAME], 'mixin A on _s_ {}'),
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index fd10332..2b0748d 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -1759,11 +1759,13 @@
const ['extend', 'on'].contains(token.next.lexeme)) {
reportRecoverableError(
token.next, fasta.templateExpectedInstead.withArguments('extends'));
- token = token.next;
- rewriter.insertToken(token,
- new SyntheticKeywordToken(Keyword.EXTENDS, token.next.charOffset));
+ Token incorrectExtendsKeyword = token.next;
+ token = computeType(incorrectExtendsKeyword, true)
+ .ensureTypeNotVoid(incorrectExtendsKeyword, this);
+ listener.handleClassExtends(incorrectExtendsKeyword);
+ } else {
+ token = parseClassExtendsOpt(token);
}
- token = parseClassExtendsOpt(token);
if (recoveryListener.extendsKeyword != null) {
if (hasExtends) {
@@ -1916,11 +1918,10 @@
const ['extend', 'extends'].contains(token.next.lexeme)) {
reportRecoverableError(
token.next, fasta.templateExpectedInstead.withArguments('on'));
- token = token.next;
- rewriter.insertToken(token,
- new SyntheticKeywordToken(Keyword.ON, token.next.charOffset));
+ token = parseMixinOn(token);
+ } else {
+ token = parseMixinOnOpt(token);
}
- token = parseMixinOnOpt(token);
if (recoveryListener.onKeyword != null) {
if (hasOn) {
@@ -1961,16 +1962,24 @@
/// ;
/// ```
Token parseMixinOnOpt(Token token) {
- Token onKeyword;
- int typeCount = 0;
- if (optional('on', token.next)) {
- onKeyword = token.next;
- do {
- token =
- computeType(token.next, true).ensureTypeNotVoid(token.next, this);
- ++typeCount;
- } while (optional(',', token.next));
+ if (!optional('on', token.next)) {
+ listener.handleMixinOn(null, 0);
+ return token;
}
+ return parseMixinOn(token);
+ }
+
+ Token parseMixinOn(Token token) {
+ Token onKeyword = token.next;
+ // During recovery, the [onKeyword] can be "extend" or "extends"
+ assert(optional('on', onKeyword) ||
+ optional('extends', onKeyword) ||
+ onKeyword.lexeme == 'extend');
+ int typeCount = 0;
+ do {
+ token = computeType(token.next, true).ensureTypeNotVoid(token.next, this);
+ ++typeCount;
+ } while (optional(',', token.next));
listener.handleMixinOn(onKeyword, typeCount);
return token;
}