blob: 2287d8eb11cf39af4cae789e6c5149a4c4360168 [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.
// @dart = 2.9
import 'package:_fe_analyzer_shared/src/parser/token_stream_rewriter.dart';
import 'package:_fe_analyzer_shared/src/scanner/scanner.dart'
show ScannerResult, scanString;
import 'package:_fe_analyzer_shared/src/scanner/token.dart'
show ReplacementToken, Token, TokenType;
import 'package:_fe_analyzer_shared/src/scanner/token_impl.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(TokenStreamRewriterTest_NoPrevious);
defineReflectiveTests(TokenStreamRewriterTest_UsingPrevious);
defineReflectiveTests(TokenStreamRewriterTest_Undoable);
});
}
/// Abstract base class for tests of [TokenStreamRewriter].
abstract class TokenStreamRewriterTest {
/// Indicates whether the tests should set up [Token.previous].
bool get setPrevious;
TokenStreamRewriter getTokenStreamRewriter();
void setupDone(Token first) {}
void normalTestDone(TokenStreamRewriter rewriter, Token first) {}
void test_insertParens() {
Token a = _makeToken(0, 'a');
Token b = _makeToken(1, 'b');
Token eof = _link([a, b]);
setupDone(a);
TokenStreamRewriter rewriter = getTokenStreamRewriter();
Token openParen = rewriter.insertParens(a, false);
Token closeParen = openParen.next;
expect(openParen.lexeme, '(');
expect(closeParen.lexeme, ')');
expect(a.next, same(openParen));
expect(openParen.next, same(closeParen));
expect(closeParen.next, same(b));
expect(b.next, same(eof));
expect(b.previous, same(closeParen));
expect(closeParen.previous, same(openParen));
expect(openParen.previous, same(a));
normalTestDone(rewriter, a);
}
void test_insertParensWithIdentifier() {
Token a = _makeToken(0, 'a');
Token b = _makeToken(1, 'b');
Token eof = _link([a, b]);
setupDone(a);
TokenStreamRewriter rewriter = getTokenStreamRewriter();
Token openParen = rewriter.insertParens(a, true);
Token identifier = openParen.next;
Token closeParen = identifier.next;
expect(openParen.lexeme, '(');
expect(identifier.lexeme, '');
expect(identifier.isSynthetic, isTrue);
expect(closeParen.lexeme, ')');
expect(a.next, same(openParen));
expect(openParen.next, same(identifier));
expect(identifier.next, same(closeParen));
expect(closeParen.next, same(b));
expect(b.next, same(eof));
expect(b.previous, same(closeParen));
expect(closeParen.previous, same(identifier));
expect(identifier.previous, same(openParen));
expect(openParen.previous, same(a));
normalTestDone(rewriter, a);
}
void test_insertSyntheticIdentifier() {
Token a = _makeToken(0, 'a');
Token b = _makeToken(1, 'b');
Token eof = _link([a, b]);
setupDone(a);
TokenStreamRewriter rewriter = getTokenStreamRewriter();
Token identifier = rewriter.insertSyntheticIdentifier(a);
expect(identifier.lexeme, '');
expect(identifier.isSynthetic, isTrue);
expect(a.next, same(identifier));
expect(identifier.next, same(b));
expect(b.next, same(eof));
expect(b.previous, same(identifier));
expect(identifier.previous, same(a));
normalTestDone(rewriter, a);
}
void test_insertToken_end() {
Token a = _makeToken(0, 'a');
Token b = _makeToken(1, 'b');
Token eof = _link([a]);
setupDone(a);
TokenStreamRewriter rewriter = getTokenStreamRewriter();
expect(rewriter.insertToken(a, b), same(b));
expect(a.next, same(b));
expect(b.next, same(eof));
expect(eof.previous, same(b));
expect(b.previous, same(a));
normalTestDone(rewriter, a);
}
void test_insertToken_middle() {
Token a = _makeToken(0, 'a');
Token b = _makeToken(1, 'b');
Token c = _makeToken(2, 'c');
_link([a, c]);
setupDone(a);
TokenStreamRewriter rewriter = getTokenStreamRewriter();
rewriter.insertToken(a, b);
expect(a.next, same(b));
expect(b.next, same(c));
normalTestDone(rewriter, a);
}
void test_insertToken_second_insertion_earlier_in_stream() {
Token a = _makeToken(0, 'a');
Token b = _makeToken(1, 'b');
Token c = _makeToken(2, 'c');
Token d = _makeToken(3, 'd');
Token e = _makeToken(4, 'e');
_link([a, c, e]);
setupDone(a);
TokenStreamRewriter rewriter = getTokenStreamRewriter();
rewriter.insertToken(c, d);
expect(c.next, same(d));
expect(d.next, same(e));
// The next call to rewriter should be able to find the insertion point
// even though it is before the insertion point used above.
rewriter.insertToken(a, b);
expect(a.next, same(b));
expect(b.next, same(c));
normalTestDone(rewriter, a);
}
void test_replaceNextTokenWithSyntheticToken_1() {
Token a = _makeToken(0, 'a');
StringToken b = _makeToken(5, 'b');
b.precedingComments = new CommentToken.fromSubstring(
TokenType.SINGLE_LINE_COMMENT, "Test comment", 1, 9, 1,
canonicalize: true);
Token c = _makeToken(10, 'c');
_link([a, b, c]);
setupDone(a);
TokenStreamRewriter rewriter = getTokenStreamRewriter();
ReplacementToken replacement =
rewriter.replaceNextTokenWithSyntheticToken(a, TokenType.AMPERSAND);
expect(b.offset, same(replacement.offset));
expect(b.precedingComments, same(replacement.precedingComments));
expect(replacement.replacedToken, same(b));
expect(a.next, same(replacement));
expect(replacement.next, same(c));
expect(c.next.isEof, true);
normalTestDone(rewriter, a);
}
void test_replaceNextTokenWithSyntheticToken_2() {
Token a = _makeToken(0, 'a');
StringToken b = _makeToken(5, 'b');
b.precedingComments = new CommentToken.fromSubstring(
TokenType.SINGLE_LINE_COMMENT, "Test comment", 1, 9, 1,
canonicalize: true);
_link([a, b]);
setupDone(a);
TokenStreamRewriter rewriter = getTokenStreamRewriter();
ReplacementToken replacement =
rewriter.replaceNextTokenWithSyntheticToken(a, TokenType.AMPERSAND);
expect(b.offset, same(replacement.offset));
expect(b.precedingComments, same(replacement.precedingComments));
expect(replacement.replacedToken, same(b));
expect(a.next, same(replacement));
expect(replacement.next.isEof, true);
normalTestDone(rewriter, a);
}
void test_replaceNextTokensWithSyntheticToken_1() {
Token a = _makeToken(0, 'a');
StringToken b = _makeToken(5, 'b');
b.precedingComments = new CommentToken.fromSubstring(
TokenType.SINGLE_LINE_COMMENT, "Test comment", 1, 9, 1,
canonicalize: true);
Token c = _makeToken(10, 'c');
Token d = _makeToken(11, 'd');
Token e = _makeToken(12, 'e');
_link([a, b, c, d, e]);
setupDone(a);
TokenStreamRewriter rewriter = getTokenStreamRewriter();
ReplacementToken replacement =
rewriter.replaceNextTokensWithSyntheticToken(a, 3, TokenType.AMPERSAND);
expect(b.offset, same(replacement.offset));
expect(b.precedingComments, same(replacement.precedingComments));
expect(replacement.replacedToken, same(b));
expect(replacement.replacedToken.next, same(c));
expect(replacement.replacedToken.next.next, same(d));
expect(a.next, same(replacement));
expect(replacement.next, same(e));
expect(e.next.isEof, true);
normalTestDone(rewriter, a);
}
void test_replaceNextTokensWithSyntheticToken_2() {
Token a = _makeToken(0, 'a');
StringToken b = _makeToken(5, 'b');
b.precedingComments = new CommentToken.fromSubstring(
TokenType.SINGLE_LINE_COMMENT, "Test comment", 1, 9, 1,
canonicalize: true);
Token c = _makeToken(10, 'c');
_link([a, b, c]);
setupDone(a);
TokenStreamRewriter rewriter = getTokenStreamRewriter();
ReplacementToken replacement =
rewriter.replaceNextTokensWithSyntheticToken(a, 2, TokenType.AMPERSAND);
expect(b.offset, same(replacement.offset));
expect(b.precedingComments, same(replacement.precedingComments));
expect(replacement.replacedToken, same(b));
expect(replacement.replacedToken.next, same(c));
expect(a.next, same(replacement));
expect(replacement.next.isEof, true);
normalTestDone(rewriter, a);
}
void test_moveSynthetic() {
ScannerResult scanResult = scanString('Foo(bar; baz=0;');
expect(scanResult.hasErrors, isTrue);
Token firstToken = scanResult.tokens;
setupDone(firstToken);
Token open = scanResult.tokens.next.next;
expect(open.lexeme, '(');
Token close = open.endGroup;
expect(close.isSynthetic, isTrue);
expect(close.next.isEof, isTrue);
TokenStreamRewriter rewriter = getTokenStreamRewriter();
Token result = rewriter.moveSynthetic(open.next, close);
expect(result, close);
expect(open.endGroup, close);
expect(open.next.next, close);
expect(close.next.isEof, isFalse);
normalTestDone(rewriter, firstToken);
}
void test_replaceTokenFollowing_multiple() {
Token a = _makeToken(0, 'a');
Token b = _makeToken(1, 'b');
Token c = _makeToken(2, 'c');
Token d = _makeToken(3, 'd');
Token e = _makeToken(4, 'e');
Token f = _makeToken(5, 'f');
_link([a, b, e, f]);
_link([c, d]);
setupDone(a);
TokenStreamRewriter rewriter = getTokenStreamRewriter();
rewriter.replaceTokenFollowing(b, c);
expect(a.next, same(b));
expect(b.next, same(c));
expect(c.next, same(d));
expect(d.next, same(f));
normalTestDone(rewriter, a);
}
void test_replaceTokenFollowing_single() {
Token a = _makeToken(0, 'a');
Token b = _makeToken(1, 'b');
Token c = _makeToken(2, 'c');
Token d = _makeToken(3, 'd');
_link([a, b, d]);
setupDone(a);
TokenStreamRewriter rewriter = getTokenStreamRewriter();
rewriter.replaceTokenFollowing(a, c);
expect(a.next, same(c));
expect(c.next, same(d));
normalTestDone(rewriter, a);
}
/// Links together the given [tokens] and adds an EOF token to the end of the
/// token stream.
///
/// The EOF token is returned.
Token _link(Iterable<Token> tokens) {
Token head = new Token.eof(-1);
if (!setPrevious) head.previous = null;
for (Token token in tokens) {
head.next = token;
if (setPrevious) token.previous = head;
head = token;
}
int eofOffset = head.charOffset + head.lexeme.length;
if (eofOffset < 0) eofOffset = 0;
Token eof = new Token.eof(eofOffset);
if (!setPrevious) eof.previous = null;
head.next = eof;
if (setPrevious) eof.previous = head;
return eof;
}
StringToken _makeToken(int charOffset, String text) {
return new StringToken.fromString(null, text, charOffset);
}
}
/// Concrete implementation of [TokenStreamRewriterTest] in which
/// [Token.previous] values are set to null.
///
/// This forces [TokenStreamRewriter] to use its more complex heuristic for
/// finding previous tokens.
@reflectiveTest
class TokenStreamRewriterTest_NoPrevious extends TokenStreamRewriterTest {
@override
bool get setPrevious => false;
@override
TokenStreamRewriter getTokenStreamRewriter() => new TokenStreamRewriterImpl();
}
/// Concrete implementation of [TokenStreamRewriterTest] in which
/// [Token.previous] values are set to non-null.
///
/// Since [TokenStreamRewriter] makes use of [Token.previous] when it can,
/// these tests do not exercise the more complex heuristics for finding previous
/// tokens.
@reflectiveTest
class TokenStreamRewriterTest_UsingPrevious extends TokenStreamRewriterTest {
@override
bool get setPrevious => true;
@override
TokenStreamRewriter getTokenStreamRewriter() => new TokenStreamRewriterImpl();
}
/// Concrete implementation of [TokenStreamRewriterTest] in which
/// the [UndoableTokenStreamRewriter] is used.
@reflectiveTest
class TokenStreamRewriterTest_Undoable extends TokenStreamRewriterTest {
@override
bool get setPrevious => true;
@override
TokenStreamRewriter getTokenStreamRewriter() =>
new UndoableTokenStreamRewriter();
List<CachedTokenSetup> setup;
@override
void setupDone(Token first) {
setup = [];
Token token = first;
while (token != null && !token.isEof) {
setup.add(new CachedTokenSetup(token));
token = token.next;
}
}
@override
void normalTestDone(TokenStreamRewriter rewriter, Token first) {
UndoableTokenStreamRewriter undoableTokenStreamRewriter = rewriter;
undoableTokenStreamRewriter.undo();
List<CachedTokenSetup> now = [];
Token token = first;
while (token != null && !token.isEof) {
now.add(new CachedTokenSetup(token));
token = token.next;
}
if (setup.length != now.length) {
throw "Different length: ${setup.length} vs ${now.length}";
}
for (int i = 0; i < setup.length; i++) {
if (setup[i] != now[i]) {
throw "Different at $i: ${setup[i]} vs ${now[i]}";
}
}
setup = null;
}
}
class CachedTokenSetup {
final Token token;
final Token prev;
final Token next;
final Token precedingComments;
CachedTokenSetup(this.token)
: prev = token.previous,
next = token.next,
precedingComments = token.precedingComments;
@override
bool operator ==(Object other) {
if (other is! CachedTokenSetup) return false;
CachedTokenSetup o = other;
return token == o.token &&
prev == o.prev &&
next == o.next &&
precedingComments == o.precedingComments;
}
@override
String toString() {
return "CachedTokenSetup["
"token = $token, "
"prev = $prev, "
"next = $next, "
"precedingComments = $precedingComments"
"]";
}
}