ed32d2456a
Change-Id: I6d7d21b2d02f1f6cbaff81f75f900790f0e8c715 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/400780 Reviewed-by: Nate Biggs <natebiggs@google.com> Commit-Queue: Mayank Patke <fishythefish@google.com> Auto-Submit: Mayank Patke <fishythefish@google.com>
250 lines
5.2 KiB
Dart
250 lines
5.2 KiB
Dart
// Copyright (c) 2015, 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.
|
|
|
|
// Note: This test relies on LF line endings in the source file.
|
|
|
|
// Test that JS printer callbacks occur when expected.
|
|
|
|
import 'package:expect/expect.dart';
|
|
import 'package:js_ast/js_ast.dart';
|
|
|
|
enum TestMode { INPUT, NONE, ENTER, DELIMITER, EXIT }
|
|
|
|
class TestCase {
|
|
final Map<TestMode, String> data;
|
|
|
|
/// Map from template names to the inserted values.
|
|
final Map<String, String> environment;
|
|
|
|
const TestCase(this.data, [this.environment = const {}]);
|
|
}
|
|
|
|
const List<TestCase> DATA = <TestCase>[
|
|
TestCase({
|
|
TestMode.NONE: '''
|
|
function(a, b) {
|
|
return null;
|
|
}''',
|
|
TestMode.ENTER: '''
|
|
@0function(@1a, @2b) @3{
|
|
@4return @5null;
|
|
}''',
|
|
TestMode.DELIMITER: '''
|
|
function(a, b) {
|
|
return null@4;
|
|
@0}''',
|
|
TestMode.EXIT: '''
|
|
function(a@1, b@2) {
|
|
return null@5;
|
|
@4}@3@0''',
|
|
}),
|
|
TestCase({
|
|
TestMode.NONE: '''
|
|
function() {
|
|
if (true) {
|
|
foo1();
|
|
foo2();
|
|
} else if (false) {
|
|
bar1();
|
|
bar2();
|
|
}
|
|
while (false) {
|
|
baz3();
|
|
baz4();
|
|
}
|
|
}''',
|
|
TestMode.ENTER: '''
|
|
@0function() @1{
|
|
@2if (@3true) @4{
|
|
@5@6@7foo1();
|
|
@8@9@10foo2();
|
|
} else @11if (@12false) @13{
|
|
@14@15@16bar1();
|
|
@17@18@19bar2();
|
|
}
|
|
@20while (@21false) @22{
|
|
@23@24@25baz3();
|
|
@26@27@28baz4();
|
|
}
|
|
}''',
|
|
TestMode.DELIMITER: '''
|
|
function() {
|
|
if (true) {
|
|
foo1();
|
|
foo2();
|
|
} else if (false) {
|
|
bar1();
|
|
bar2();
|
|
}
|
|
while (false) {
|
|
baz3();
|
|
baz4();
|
|
}
|
|
@0}''',
|
|
TestMode.EXIT: '''
|
|
function() {
|
|
if (true@3) {
|
|
foo1@7()@6;
|
|
@5 foo2@10()@9;
|
|
@8 }@4 else if (false@12) {
|
|
bar1@16()@15;
|
|
@14 bar2@19()@18;
|
|
@17 }@13
|
|
@11@2 while (false@21) {
|
|
baz3@25()@24;
|
|
@23 baz4@28()@27;
|
|
@26 }@22
|
|
@20}@1@0''',
|
|
}),
|
|
TestCase({
|
|
TestMode.NONE: '''
|
|
function() {
|
|
function foo() {
|
|
}
|
|
}''',
|
|
TestMode.ENTER: '''
|
|
@0function() @1{
|
|
@2@3function @4foo() @5{
|
|
}
|
|
}''',
|
|
TestMode.DELIMITER: '''
|
|
function() {
|
|
function foo() {
|
|
@3}
|
|
@0}''',
|
|
TestMode.EXIT: '''
|
|
function() {
|
|
function foo@4() {
|
|
}@5@3
|
|
@2}@1@0''',
|
|
}),
|
|
TestCase({
|
|
TestMode.INPUT: """
|
|
function() {
|
|
a['b'];
|
|
[1,, 2];
|
|
}""",
|
|
TestMode.NONE: '''
|
|
function() {
|
|
a.b;
|
|
[1,, 2];
|
|
}''',
|
|
TestMode.ENTER: '''
|
|
@0function() @1{
|
|
@2@3@4a.@5b;
|
|
@6@7[@81,@9, @102];
|
|
}''',
|
|
TestMode.DELIMITER: '''
|
|
function() {
|
|
a.b;
|
|
[1,, 2];
|
|
@0}''',
|
|
TestMode.EXIT: '''
|
|
function() {
|
|
a@4.b@5@3;
|
|
@2 [1@8,,@9 2@10]@7;
|
|
@6}@1@0''',
|
|
}),
|
|
TestCase(
|
|
{
|
|
TestMode.INPUT: 'a.#nameTemplate = #nameTemplate',
|
|
TestMode.NONE: 'a.nameValue = nameValue',
|
|
TestMode.ENTER: '@0@1@2a.@3nameValue = @3nameValue',
|
|
TestMode.DELIMITER: 'a.nameValue = nameValue',
|
|
TestMode.EXIT: 'a@2.nameValue@3@1 = nameValue@3@0',
|
|
},
|
|
{'nameTemplate': 'nameValue'},
|
|
),
|
|
];
|
|
|
|
class FixedName extends Name {
|
|
@override
|
|
final String name;
|
|
@override
|
|
String get key => name;
|
|
|
|
FixedName(this.name);
|
|
}
|
|
|
|
void check(TestCase testCase) {
|
|
Map<TestMode, String> map = testCase.data;
|
|
// Unspecified input is the same as output.
|
|
String? code = map[TestMode.INPUT] ?? map[TestMode.NONE]!;
|
|
JavaScriptPrintingOptions options = JavaScriptPrintingOptions();
|
|
Map arguments = {};
|
|
testCase.environment.forEach((String name, String value) {
|
|
arguments[name] = FixedName(value);
|
|
});
|
|
Node node = js.parseForeignJS(code).instantiate(arguments);
|
|
map.forEach((TestMode mode, String expectedOutput) {
|
|
if (mode == TestMode.INPUT) return;
|
|
Context context = Context(mode);
|
|
Printer(options, context).visit(node);
|
|
// TODO(johnniwinther): Remove `replaceAll(...)` when dart2js behaves as the
|
|
// VM on newline in multiline strings.
|
|
Expect.equals(
|
|
expectedOutput.replaceAll('\r\n', '\n'),
|
|
context.getText(),
|
|
'Unexpected output for $code in $mode',
|
|
);
|
|
});
|
|
}
|
|
|
|
class Context extends SimpleJavaScriptPrintingContext {
|
|
final TestMode mode;
|
|
final Map<Node, int> idMap = {};
|
|
final Map<int, List<String>> tagMap = {};
|
|
|
|
Context(this.mode);
|
|
|
|
int id(Node node) => idMap.putIfAbsent(node, () => idMap.length);
|
|
|
|
String tag(int value) => '@$value';
|
|
|
|
@override
|
|
void enterNode(Node node, int startPosition) {
|
|
int value = id(node);
|
|
if (mode == TestMode.ENTER) {
|
|
tagMap.putIfAbsent(startPosition, () => []).add(tag(value));
|
|
}
|
|
}
|
|
|
|
@override
|
|
void exitNode(
|
|
Node node,
|
|
int startPosition,
|
|
int endPosition,
|
|
int? delimiterPosition,
|
|
) {
|
|
int value = id(node);
|
|
if (mode == TestMode.DELIMITER && delimiterPosition != null) {
|
|
tagMap.putIfAbsent(delimiterPosition, () => []).add(tag(value));
|
|
} else if (mode == TestMode.EXIT) {
|
|
tagMap.putIfAbsent(endPosition, () => []).add(tag(value));
|
|
}
|
|
}
|
|
|
|
@override
|
|
String getText() {
|
|
String text = super.getText();
|
|
int offset = 0;
|
|
StringBuffer sb = StringBuffer();
|
|
for (int position in tagMap.keys.toList()..sort()) {
|
|
if (offset < position) {
|
|
sb.write(text.substring(offset, position));
|
|
}
|
|
tagMap[position]!.forEach((String tag) => sb.write(tag));
|
|
offset = position;
|
|
}
|
|
if (offset < text.length) {
|
|
sb.write(text.substring(offset));
|
|
}
|
|
return sb.toString();
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
DATA.forEach(check);
|
|
}
|