| Index: pkg/front_end/test/scanner_replacement_test.dart
|
| diff --git a/pkg/front_end/test/scanner_replacement_test.dart b/pkg/front_end/test/scanner_replacement_test.dart
|
| index 171d943906d6aaf0ec019bb2d50ead2b5641ff37..63ea94f6bcfd3a64785afcd084da010d82f24886 100644
|
| --- a/pkg/front_end/test/scanner_replacement_test.dart
|
| +++ b/pkg/front_end/test/scanner_replacement_test.dart
|
| @@ -9,6 +9,8 @@ import 'package:front_end/src/fasta/scanner/precedence.dart'
|
| import 'package:front_end/src/fasta/scanner/recover.dart'
|
| show defaultRecoveryStrategy;
|
| import 'package:front_end/src/fasta/scanner.dart' as fasta;
|
| +import 'package:front_end/src/fasta/scanner/token.dart' as fasta;
|
| +import 'package:front_end/src/fasta/scanner/error_token.dart' as fasta;
|
| import 'package:front_end/src/scanner/token.dart' as analyzer;
|
| import 'package:front_end/src/scanner/errors.dart'
|
| show ScannerErrorCode, translateErrorToken;
|
| @@ -54,10 +56,11 @@ class ScannerTest_Replacement extends ScannerTest {
|
| }));
|
| fasta.Token tokens = result.tokens;
|
| assertValidTokenStream(tokens);
|
| + assertValidBeginTokens(tokens);
|
| if (result.hasErrors) {
|
| List<int> bytes = UTF8.encode(source);
|
| tokens = defaultRecoveryStrategy(bytes, tokens, result.lineStarts);
|
| - assertValidTokenStream(tokens);
|
| + assertValidTokenStream(tokens, errorsFirst: true);
|
| }
|
| return extractErrors(tokens, listener);
|
| }
|
| @@ -94,25 +97,126 @@ class ScannerTest_Replacement extends ScannerTest {
|
| super.test_comment_generic_method_type_list();
|
| }
|
|
|
| + void _assertOpenClosePair(String source) {
|
| + fasta.BeginGroupToken open = _scan(source);
|
| + fasta.Token close = open.next;
|
| + expect(close.next.isEof, isTrue);
|
| + expect(open.endGroup, close);
|
| + expect(open.isSynthetic, isFalse);
|
| + expect(close.isSynthetic, isFalse);
|
| + }
|
| +
|
| + void _assertOpenOnly(String source) {
|
| + fasta.BeginGroupToken open = _scan(source);
|
| + fasta.Token close = open.next;
|
| + expect(close.next.isEof, isTrue);
|
| + expect(open.endGroup, close);
|
| + expect(open.isSynthetic, isFalse);
|
| + expect(close.isSynthetic, isTrue);
|
| + }
|
| +
|
| + void test_lt() {
|
| + // fasta does not automatically insert a closer for '<'
|
| + // because it could be part of an expression rather than an opener
|
| + fasta.BeginGroupToken lt = _scan('<');
|
| + expect(lt.next.isEof, isTrue);
|
| + expect(lt.isSynthetic, isFalse);
|
| + }
|
| +
|
| + void test_lt_gt() {
|
| + _assertOpenClosePair('< >');
|
| + }
|
| +
|
| + @override
|
| + void test_open_curly_bracket() {
|
| + _assertOpenOnly('{');
|
| + }
|
| +
|
| + void test_open_curly_bracket_with_close() {
|
| + _assertOpenClosePair('{ }');
|
| + }
|
| +
|
| + void test_open_paren() {
|
| + _assertOpenOnly('(');
|
| + }
|
| +
|
| + void test_open_paren_with_close() {
|
| + _assertOpenClosePair('( )');
|
| + }
|
| +
|
| + void test_open_square_bracket() {
|
| + _assertOpenOnly('[');
|
| + }
|
| +
|
| + void test_open_square_bracket_with_close() {
|
| + _assertOpenClosePair('[ ]');
|
| + }
|
| +
|
| @override
|
| - @failingTest
|
| void test_mismatched_closer() {
|
| - // TODO(danrubel): investigate and fix
|
| - super.test_mismatched_closer();
|
| + // When openers and closers are mismatched,
|
| + // fasta favors considering the opener to be mismatched,
|
| + // and inserts synthetic closers as needed.
|
| + // `(])` is parsed as `()])` where the first `)` is synthetic
|
| + // and the trailing `])` are unmatched.
|
| + fasta.BeginGroupToken openParen = _scan('(])');
|
| + fasta.Token closeParen = openParen.next;
|
| + fasta.Token closeBracket = closeParen.next;
|
| + fasta.Token closeParen2 = closeBracket.next;
|
| + fasta.Token eof = closeParen2.next;
|
| +
|
| + expect(openParen.endToken, same(closeParen));
|
| + expect(closeParen.isSynthetic, isTrue);
|
| + expect(eof.isEof, isTrue);
|
| }
|
|
|
| @override
|
| - @failingTest
|
| void test_mismatched_opener() {
|
| - // TODO(danrubel): investigate and fix
|
| - super.test_mismatched_opener();
|
| + // When openers and closers are mismatched,
|
| + // fasta favors considering the opener to be mismatched
|
| + // and inserts synthetic closers as needed.
|
| + // `([)` is parsed as `([])` where `]` is synthetic.
|
| + fasta.BeginGroupToken openParen = _scan('([)');
|
| + fasta.BeginGroupToken openBracket = openParen.next;
|
| + fasta.Token closeBracket = openBracket.next; // <-- synthetic
|
| + fasta.Token closeParen = closeBracket.next;
|
| + fasta.Token eof = closeParen.next;
|
| +
|
| + expect(openParen.endToken, same(closeParen));
|
| + expect(closeParen.isSynthetic, isFalse);
|
| + expect(openBracket.endToken, same(closeBracket));
|
| + expect(closeBracket.isSynthetic, isTrue);
|
| + expect(eof.isEof, isTrue);
|
| }
|
|
|
| @override
|
| - @failingTest
|
| void test_mismatched_opener_in_interpolation() {
|
| - // TODO(danrubel): investigate and fix
|
| - super.test_mismatched_opener_in_interpolation();
|
| + // When openers and closers are mismatched,
|
| + // fasta favors considering the opener to be mismatched
|
| + // and inserts synthetic closers as needed.
|
| + // r'"${({(}}"' is parsed as r'"${({()})}"'
|
| + // where both ')' are synthetic
|
| + var stringStart = _scan(r'"${({(}}"');
|
| + var interpolationStart = stringStart.next as fasta.BeginGroupToken;
|
| + var openParen1 = interpolationStart.next as fasta.BeginGroupToken;
|
| + var openBrace = openParen1.next as fasta.BeginGroupToken;
|
| + var openParen2 = openBrace.next as fasta.BeginGroupToken;
|
| + var closeParen2 = openParen2.next;
|
| + var closeBrace = closeParen2.next;
|
| + var closeParen1 = closeBrace.next;
|
| + var interpolationEnd = closeParen1.next;
|
| + var stringEnd = interpolationEnd.next;
|
| + var eof = stringEnd.next;
|
| +
|
| + expect(interpolationStart.endToken, same(interpolationEnd));
|
| + expect(interpolationEnd.isSynthetic, isFalse);
|
| + expect(openParen1.endToken, same(closeParen1));
|
| + expect(closeParen1.isSynthetic, isTrue);
|
| + expect(openBrace.endToken, same(closeBrace));
|
| + expect(closeBrace.isSynthetic, isFalse);
|
| + expect(openParen2.endToken, same(closeParen2));
|
| + expect(closeParen2.isSynthetic, isTrue);
|
| + expect(eof.isEof, isTrue);
|
| }
|
|
|
| @override
|
| @@ -199,24 +303,22 @@ class ScannerTest_Replacement extends ScannerTest {
|
| super.test_string_simple_unterminated_interpolation_identifier();
|
| }
|
|
|
| - @failingTest
|
| @override
|
| void test_unmatched_openers() {
|
| - // fasta recovery inserts closers
|
| - var openBrace = _scan('{[(<') as analyzer.BeginToken;
|
| - var openBracket = openBrace.next as analyzer.BeginToken;
|
| - var openParen = openBracket.next as analyzer.BeginToken;
|
| - var openLT = openParen.next as analyzer.BeginToken;
|
| - var closeGT = openLT.next;
|
| - var closeParen = closeGT.next;
|
| + // fasta inserts missing closers except for '<'
|
| + var openBrace = _scan('{[(<') as fasta.BeginGroupToken;
|
| + var openBracket = openBrace.next as fasta.BeginGroupToken;
|
| + var openParen = openBracket.next as fasta.BeginGroupToken;
|
| + var openLT = openParen.next as fasta.BeginGroupToken;
|
| + var closeParen = openLT.next;
|
| var closeBracket = closeParen.next;
|
| var closeBrace = closeBracket.next;
|
| - expect(closeBrace.next.type, analyzer.TokenType.EOF);
|
| + var eof = closeBrace.next;
|
|
|
| - expect(openBrace.endToken, closeBrace);
|
| - expect(openBracket.endToken, closeBracket);
|
| - expect(openParen.endToken, closeParen);
|
| - expect(openLT.endToken, closeGT);
|
| + expect(openBrace.endGroup, same(closeBrace));
|
| + expect(openBracket.endGroup, same(closeBracket));
|
| + expect(openParen.endGroup, same(closeParen));
|
| + expect(eof.isEof, true);
|
| }
|
|
|
| analyzer.Token _scan(String source,
|
| @@ -250,13 +352,21 @@ class ScannerTest_Replacement extends ScannerTest {
|
| }
|
|
|
| /// Assert that the tokens in the stream are correctly connected prev/next.
|
| - void assertValidTokenStream(fasta.Token firstToken) {
|
| + void assertValidTokenStream(fasta.Token firstToken,
|
| + {bool errorsFirst: false}) {
|
| fasta.Token token = firstToken;
|
| fasta.Token previous = token.previousToken;
|
| expect(previous.isEof, isTrue, reason: 'Missing leading EOF');
|
| expect(previous.next, token, reason: 'Invalid leading EOF');
|
| expect(previous.previous, previous, reason: 'Invalid leading EOF');
|
| + if (errorsFirst) {
|
| + while (!token.isEof && token is fasta.ErrorToken) {
|
| + token = token.next;
|
| + }
|
| + }
|
| + var isNotErrorToken = isNot(new isInstanceOf<fasta.ErrorToken>());
|
| while (!token.isEof) {
|
| + if (errorsFirst) expect(token, isNotErrorToken);
|
| previous = token;
|
| token = token.next;
|
| expect(token, isNotNull, reason: previous.toString());
|
| @@ -264,4 +374,29 @@ class ScannerTest_Replacement extends ScannerTest {
|
| }
|
| expect(token.next, token, reason: 'Invalid trailing EOF');
|
| }
|
| +
|
| + /// Assert that all [fasta.BeginGroupToken] has a valid `endGroup`
|
| + /// that is in the stream.
|
| + void assertValidBeginTokens(fasta.Token firstToken) {
|
| + var openerStack = <fasta.BeginGroupToken>[];
|
| + fasta.BeginGroupToken lastClosedGroup;
|
| + fasta.Token token = firstToken;
|
| + while (!token.isEof) {
|
| + if (token is fasta.BeginGroupToken) {
|
| + if (token.lexeme != '<')
|
| + expect(token.endGroup, isNotNull, reason: token.lexeme);
|
| + if (token.endGroup != null) openerStack.add(token);
|
| + } else if (openerStack.isNotEmpty && openerStack.last.endGroup == token) {
|
| + lastClosedGroup = openerStack.removeLast();
|
| + expect(token.isSynthetic, token.next is fasta.UnmatchedToken,
|
| + reason: 'Expect synthetic closer then error token');
|
| + } else if (token is fasta.UnmatchedToken) {
|
| + expect(lastClosedGroup?.endGroup?.next, same(token),
|
| + reason: 'Unexpected error token for group: $lastClosedGroup');
|
| + expect(token.begin, lastClosedGroup);
|
| + }
|
| + token = token.next;
|
| + }
|
| + expect(openerStack, isEmpty, reason: 'Missing closers');
|
| + }
|
| }
|
|
|