Improve import/export combinator identifier recovery

Change-Id: I9f0a418d3b1fae001199c27850b1a85daf2d07a6
Reviewed-on: https://dart-review.googlesource.com/56040
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
This commit is contained in:
danrubel 2018-05-21 21:12:19 +00:00 committed by commit-bot@chromium.org
parent 79a0ef60e0
commit 1cf871a4d2
7 changed files with 82 additions and 24 deletions

View file

@ -1158,7 +1158,9 @@ class ForwardingTestListener extends ForwardingListener {
@override
void handleImportPrefix(Token deferredKeyword, Token asKeyword) {
expectIn('Import');
// This event normally happens within "Import",
// but happens within "CompilationUnit" during recovery.
expectInOneOf(const ['Import', 'CompilationUnit']);
listener.handleImportPrefix(deferredKeyword, asKeyword);
}

View file

@ -4016,6 +4016,11 @@ class RecoveryParserTest_Forest extends FastaBodyBuilderTestCase
super.test_classTypeAlias_withBody();
}
@failingTest
void test_combinator_badIdentifier() {
super.test_combinator_badIdentifier();
}
@failingTest
void test_combinator_missingIdentifier() {
super.test_combinator_missingIdentifier();

View file

@ -10787,6 +10787,25 @@ class B = Object with A {}''',
: [ParserErrorCode.EXPECTED_TOKEN]);
}
void test_combinator_badIdentifier() {
createParser('import "/testB.dart" show @');
parser.parseCompilationUnit2();
listener.assertErrors(usingFastaParser
? [
expectedError(ParserErrorCode.MISSING_IDENTIFIER, 26, 1),
expectedError(ParserErrorCode.EXPECTED_TOKEN, 27, 0),
expectedError(
ParserErrorCode.MISSING_CONST_FINAL_VAR_OR_TYPE, 27, 0),
expectedError(ParserErrorCode.EXPECTED_TOKEN, 27, 0)
]
: [
expectedError(ParserErrorCode.MISSING_IDENTIFIER, 26, 1),
expectedError(ParserErrorCode.EXPECTED_TOKEN, 26, 1),
expectedError(ParserErrorCode.MISSING_IDENTIFIER, 26, 1),
expectedError(ParserErrorCode.EXPECTED_EXECUTABLE, 27, 1)
]);
}
void test_combinator_missingIdentifier() {
createParser('import "/testB.dart" show ;');
parser.parseCompilationUnit2();

View file

@ -12,20 +12,6 @@ main() {
class ExportDirectivesTest extends PartialCodeTest {
buildAll() {
List<String> allExceptEof = <String>[
'import',
'export',
'part',
'class',
'typedef',
'functionVoid',
'functionNonVoid',
'var',
'const',
'final',
'getter',
'setter'
];
buildTests(
'export_directive',
[
@ -51,7 +37,7 @@ class ExportDirectivesTest extends PartialCodeTest {
ParserErrorCode.EXPECTED_TOKEN
],
"export 'a.dart' hide _s_;",
failing: allExceptEof),
failing: ['functionNonVoid', 'getter']),
new TestDescriptor('hideName', "export 'a.dart' hide A",
[ParserErrorCode.EXPECTED_TOKEN], "export 'a.dart' hide A;"),
new TestDescriptor(
@ -62,7 +48,7 @@ class ExportDirectivesTest extends PartialCodeTest {
ParserErrorCode.EXPECTED_TOKEN
],
"export 'a.dart' hide A, _s_;",
failing: allExceptEof),
failing: ['functionNonVoid', 'getter']),
new TestDescriptor('hideCommaName', "export 'a.dart' hide A, B",
[ParserErrorCode.EXPECTED_TOKEN], "export 'a.dart' hide A, B;"),
new TestDescriptor(
@ -73,7 +59,7 @@ class ExportDirectivesTest extends PartialCodeTest {
ParserErrorCode.EXPECTED_TOKEN
],
"export 'a.dart' hide A show _s_;",
failing: allExceptEof),
failing: ['functionNonVoid', 'getter']),
new TestDescriptor(
'show',
"export 'a.dart' show",
@ -82,7 +68,7 @@ class ExportDirectivesTest extends PartialCodeTest {
ParserErrorCode.EXPECTED_TOKEN
],
"export 'a.dart' show _s_;",
failing: allExceptEof),
failing: ['functionNonVoid', 'getter']),
new TestDescriptor('showName', "export 'a.dart' show A",
[ParserErrorCode.EXPECTED_TOKEN], "export 'a.dart' show A;"),
new TestDescriptor(
@ -93,7 +79,7 @@ class ExportDirectivesTest extends PartialCodeTest {
ParserErrorCode.EXPECTED_TOKEN
],
"export 'a.dart' show A, _s_;",
failing: allExceptEof),
failing: ['functionNonVoid', 'getter']),
new TestDescriptor('showCommaName', "export 'a.dart' show A, B",
[ParserErrorCode.EXPECTED_TOKEN], "export 'a.dart' show A, B;"),
new TestDescriptor(
@ -104,7 +90,7 @@ class ExportDirectivesTest extends PartialCodeTest {
ParserErrorCode.EXPECTED_TOKEN
],
"export 'a.dart' show A hide _s_;",
failing: allExceptEof),
failing: ['functionNonVoid', 'getter']),
],
PartialCodeTest.prePartSuffixes);
}

View file

@ -85,6 +85,15 @@ class ImportDirectivesTest extends PartialCodeTest {
],
"import 'a.dart' as _s_;",
failing: ['functionNonVoid', 'getter']),
new TestDescriptor(
'show',
"import 'a.dart' show",
[
ParserErrorCode.EXPECTED_TOKEN,
ParserErrorCode.MISSING_IDENTIFIER
],
"import 'a.dart' show _s_;",
failing: ['functionNonVoid', 'getter']),
],
PartialCodeTest.prePartSuffixes);
}

View file

@ -35,7 +35,7 @@ class IdentifierContext {
/// Identifier is one of the shown/hidden names in an import/export
/// combinator.
static const combinator = const IdentifierContext('combinator');
static const combinator = const CombinatorIdentifierContext();
/// Identifier is the start of a name in an annotation that precedes a
/// declaration (i.e. it appears directly after an `@`).

View file

@ -52,6 +52,44 @@ class ClassOrNamedMixinIdentifierContext extends IdentifierContext {
}
}
/// See [IdentifierContext.combinator].
class CombinatorIdentifierContext extends IdentifierContext {
const CombinatorIdentifierContext() : super('combinator');
@override
Token ensureIdentifier(Token token, Parser parser) {
Token identifier = token.next;
assert(identifier.kind != IDENTIFIER_TOKEN);
const followingValues = const [';', ',', 'if', 'as', 'show', 'hide'];
if (identifier.isIdentifier) {
if (!looksLikeStartOfNextTopLevelDeclaration(identifier) ||
isOneOfOrEof(identifier.next, followingValues)) {
return identifier;
}
// Although this is a valid identifier name, the import declaration
// is invalid and this looks like the start of the next declaration.
// In this situation, fall through to insert a synthetic identifier.
}
// Recovery
if (isOneOfOrEof(identifier, followingValues) ||
looksLikeStartOfNextTopLevelDeclaration(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.dottedName].
class DottedNameIdentifierContext extends IdentifierContext {
const DottedNameIdentifierContext() : super('dottedName');
@ -655,5 +693,4 @@ bool looksLikeStartOfNextStatement(Token token) => isOneOfOrEof(token, const [
bool looksLikeStartOfNextTopLevelDeclaration(Token token) =>
token.isTopLevelKeyword ||
isOneOfOrEof(
token, const ['class', 'const', 'get', 'final', 'set', 'var', 'void']);
isOneOfOrEof(token, const ['const', 'get', 'final', 'set', 'var', 'void']);