blob: d0cdb3b8349f5308136e6569f5aa839d52228451 [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:test/test.dart';
import '../../../generated/parser_fasta_test.dart';
import '../../../generated/test_support.dart';
/**
* The base class for tests that test how well the parser recovers from various
* syntactic errors.
*/
abstract class AbstractRecoveryTest extends FastaParserTestCase {
void testRecovery(
String invalidCode, List<ErrorCode> errorCodes, String validCode,
{CompilationUnit adjustValidUnitBeforeComparison(CompilationUnit unit),
bool enableControlFlowCollections,
List<ErrorCode> expectedErrorsInValidCode}) {
CompilationUnit validUnit;
// Assert that the valid code is indeed valid.
try {
validUnit = parseCompilationUnit(validCode,
codes: expectedErrorsInValidCode,
enableControlFlowCollections: enableControlFlowCollections);
validateTokenStream(validUnit.beginToken);
} catch (e) {
// print('');
// print(' Errors in valid code.');
// print(' Error: $e');
// print(' Code: $validCode');
// print('');
rethrow;
}
// Compare the structures before asserting valid errors.
GatheringErrorListener listener =
new GatheringErrorListener(checkRanges: true);
CompilationUnit invalidUnit = parseCompilationUnit2(invalidCode, listener,
enableControlFlowCollections: enableControlFlowCollections);
validateTokenStream(invalidUnit.beginToken);
if (adjustValidUnitBeforeComparison != null) {
validUnit = adjustValidUnitBeforeComparison(validUnit);
}
ResultComparator.compare(invalidUnit, validUnit);
// Assert valid errors.
if (errorCodes != null) {
listener.assertErrorsWithCodes(errorCodes);
} else {
listener.assertNoErrors();
}
}
void validateTokenStream(Token token) {
while (!token.isEof) {
Token next = token.next;
expect(token.end, lessThanOrEqualTo(next.offset));
if (next.isSynthetic) {
if (const [')', ']', '}'].contains(next.lexeme)) {
expect(next.beforeSynthetic, token);
}
}
token = next;
}
}
}
/**
* An object used to compare to AST structures and cause the test to fail if
* they differ in any important ways.
*/
class ResultComparator extends AstComparator {
bool failDifferentLength(List first, List second) {
StringBuffer buffer = new StringBuffer();
buffer.writeln('Expected a list of length ${second.length}');
buffer.writeln(' $second');
buffer.writeln('But found a list of length ${first.length}');
buffer.writeln(' $first');
if (first is NodeList) {
_safelyWriteNodePath(buffer, first.owner);
}
fail(buffer.toString());
}
@override
bool failIfNotNull(Object first, Object second) {
if (second != null) {
StringBuffer buffer = new StringBuffer();
buffer.write('Expected null; found a ');
buffer.writeln(second.runtimeType);
if (second is AstNode) {
_safelyWriteNodePath(buffer, second);
}
fail(buffer.toString());
}
return true;
}
@override
bool failIsNull(Object first, Object second) {
StringBuffer buffer = new StringBuffer();
buffer.write('Expected a ');
buffer.write(first.runtimeType);
buffer.writeln('; found null');
if (first is AstNode) {
_safelyWriteNodePath(buffer, first);
}
fail(buffer.toString());
}
@override
bool failRuntimeType(Object first, Object second) {
StringBuffer buffer = new StringBuffer();
buffer.write('Expected a ');
buffer.writeln(second.runtimeType);
buffer.write('; found ');
buffer.writeln(first.runtimeType);
if (first is AstNode) {
_safelyWriteNodePath(buffer, first);
}
fail(buffer.toString());
}
/**
* Overridden to allow the valid code to contain an explicit identifier where
* a synthetic identifier is expected to be inserted by recovery.
*/
@override
bool isEqualNodes(AstNode first, AstNode second) {
if (first is SimpleIdentifier && second is SimpleIdentifier) {
if (first.isSynthetic && second.name == '_s_') {
return true;
}
}
return super.isEqualNodes(first, second);
}
/**
* Overridden to ignore the offsets of tokens because these can legitimately
* be different.
*/
@override
bool isEqualTokensNotNull(Token first, Token second) =>
(first.isSynthetic && first.type == second.type) ||
(first.length == second.length && first.lexeme == second.lexeme);
void _safelyWriteNodePath(StringBuffer buffer, AstNode node) {
buffer.write(' path: ');
if (node == null) {
buffer.write(' null');
} else {
_writeNodePath(buffer, node);
}
}
void _writeNodePath(StringBuffer buffer, AstNode node) {
AstNode parent = node.parent;
if (parent != null) {
_writeNodePath(buffer, parent);
buffer.write(', ');
}
buffer.write(node.runtimeType);
}
/**
* Compare the [actual] and [expected] nodes, failing the test if they are
* different.
*/
static void compare(AstNode actual, AstNode expected) {
ResultComparator comparator = new ResultComparator();
if (!comparator.isEqualNodes(actual, expected)) {
fail('Expected: $expected\n Found: $actual');
}
}
}