Prepare js_ast for more advanced use of DeferredExpression
Change-Id: I008dc7ce21437475c2fa1d5505b42f9d4a4aa166
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/102363
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/compiler/lib/src/io/position_information.dart b/pkg/compiler/lib/src/io/position_information.dart
index 732b841..63851e9 100644
--- a/pkg/compiler/lib/src/io/position_information.dart
+++ b/pkg/compiler/lib/src/io/position_information.dart
@@ -647,18 +647,18 @@
/// (@ marks the current JavaScript position and ^ point to the mapped Dart
/// code position.)
static CallPosition getSemanticPositionForCall(js.Call node) {
- if (node.target is js.PropertyAccess) {
- js.PropertyAccess access = node.target;
+ js.Expression access = js.undefer(node.target);
+ if (access is js.PropertyAccess) {
js.Node target = access;
bool pureAccess = false;
while (target is js.PropertyAccess) {
js.PropertyAccess targetAccess = target;
- if (targetAccess.receiver is js.VariableUse ||
- targetAccess.receiver is js.This) {
+ js.Node receiver = js.undefer(targetAccess.receiver);
+ if (receiver is js.VariableUse || receiver is js.This) {
pureAccess = true;
break;
} else {
- target = targetAccess.receiver;
+ target = receiver;
}
}
if (pureAccess) {
@@ -672,19 +672,19 @@
return new CallPosition(
access.selector, CodePositionKind.START, SourcePositionKind.INNER);
}
- } else if (node.target is js.VariableUse || node.target is js.This) {
+ } else if (access is js.VariableUse || access is js.This) {
// m() this()
// ^ ^
return new CallPosition(
node, CodePositionKind.START, SourcePositionKind.START);
- } else if (node.target is js.Fun ||
- node.target is js.New ||
- node.target is js.NamedFunction) {
+ } else if (access is js.Fun ||
+ access is js.New ||
+ access is js.NamedFunction) {
// function(){}() new Function("...")() function foo(){}()
// ^ ^ ^
return new CallPosition(
node.target, CodePositionKind.END, SourcePositionKind.INNER);
- } else if (node.target is js.Binary || node.target is js.Call) {
+ } else if (access is js.Binary || access is js.Call) {
// (0,a)() m()()
// ^ ^
return new CallPosition(
@@ -1271,6 +1271,11 @@
statementOffset = null;
}
+ @override
+ visitDeferredExpression(js.DeferredExpression node) {
+ visit(node.value);
+ }
+
Offset getOffsetForNode(js.Node node, int codeOffset) {
if (codeOffset == null) {
CodePosition codePosition = codePositions[node];
diff --git a/pkg/js_ast/lib/src/nodes.dart b/pkg/js_ast/lib/src/nodes.dart
index 0bec49b..28923cf 100644
--- a/pkg/js_ast/lib/src/nodes.dart
+++ b/pkg/js_ast/lib/src/nodes.dart
@@ -981,7 +981,7 @@
/// In particular, there is no guarantee that implementations of [compareTo]
/// will implement some form of lexicographic ordering like [String.compareTo].
abstract class Name extends Literal
- implements Declaration, Parameter, Comparable {
+ implements Declaration, Parameter, Comparable<Name> {
T accept<T>(NodeVisitor<T> visitor) => visitor.visitName(this);
R accept1<R, A>(NodeVisitor1<R, A> visitor, A arg) =>
@@ -1750,10 +1750,11 @@
}
class Property extends Node {
- final Literal name;
+ final Expression name;
final Expression value;
- Property(this.name, this.value);
+ Property(this.name, this.value)
+ : assert(name is Literal || name is DeferredExpression);
T accept<T>(NodeVisitor<T> visitor) => visitor.visitProperty(this);
@@ -1987,3 +1988,9 @@
void visitChildren1<R, A>(NodeVisitor1<R, A> visitor, A arg) {}
}
+
+/// Returns the value of [node] if it is a [DeferredExpression]. Otherwise
+/// returns the [node] itself.
+Node undefer(Node node) {
+ return node is DeferredExpression ? undefer(node.value) : node;
+}
diff --git a/pkg/js_ast/lib/src/printer.dart b/pkg/js_ast/lib/src/printer.dart
index 9ff26a6..d5eba7b 100644
--- a/pkg/js_ast/lib/src/printer.dart
+++ b/pkg/js_ast/lib/src/printer.dart
@@ -12,7 +12,7 @@
final bool preferSemicolonToNewlineInMinifiedOutput;
final Renamer renamerForNames;
- JavaScriptPrintingOptions(
+ const JavaScriptPrintingOptions(
{this.shouldCompressOutput: false,
this.minifyLocalVariables: false,
this.preferSemicolonToNewlineInMinifiedOutput: false,
@@ -223,6 +223,9 @@
void startNode(Node node) {
currentNode = new EnterExitNode(currentNode, node);
+ if (node is DeferredExpression) {
+ startNode(node.value);
+ }
}
void enterNode() {
@@ -230,6 +233,9 @@
}
void endNode(Node node) {
+ if (node is DeferredExpression) {
+ endNode(node.value);
+ }
assert(currentNode.node == node);
currentNode = currentNode.exitNode(context, _charCount);
}
@@ -715,7 +721,7 @@
static bool _isSmallInitialization(Node node) {
if (node is VariableInitialization) {
- Node value = node.value;
+ Node value = undefer(node.value);
if (value == null) return true;
if (value is This) return true;
if (value is LiteralNull) return true;
@@ -723,17 +729,79 @@
if (value is LiteralString && value.value.length <= 8) return true;
if (value is ObjectInitializer && value.properties.isEmpty) return true;
if (value is ArrayInitializer && value.elements.isEmpty) return true;
+ if (value is Name && value.name.length <= 8) return true;
}
return false;
}
+ void _outputIncDec(String op, Expression variable, [Expression alias]) {
+ if (op == '+') {
+ if (lastCharCode == charCodes.$PLUS) out(" ", isWhitespace: true);
+ out('++');
+ } else {
+ if (lastCharCode == charCodes.$MINUS) out(" ", isWhitespace: true);
+ out('--');
+ }
+ if (alias != null) startNode(alias);
+ visitNestedExpression(variable, UNARY,
+ newInForInit: inForInit, newAtStatementBegin: false);
+ if (alias != null) endNode(alias);
+ }
+
@override
visitAssignment(Assignment assignment) {
+ /// To print assignments like `a = a + 1` and `a = a + b` compactly as
+ /// `++a` and `a += b` in the face of [DeferredExpression]s we detect the
+ /// pattern of the undeferred assignment.
+ String op = assignment.op;
+ Node leftHandSide = undefer(assignment.leftHandSide);
+ Node rightHandSide = undefer(assignment.value);
+ if ((op == '+' || op == '-') &&
+ leftHandSide is VariableUse &&
+ rightHandSide is LiteralNumber &&
+ rightHandSide.value == "1") {
+ // Output 'a += 1' as '++a' and 'a -= 1' as '--a'.
+ _outputIncDec(op, assignment.leftHandSide);
+ return;
+ } else if (leftHandSide is VariableUse && rightHandSide is Binary) {
+ Node rLeft = undefer(rightHandSide.left);
+ Node rRight = undefer(rightHandSide.right);
+ String op = rightHandSide.op;
+ if (op == '+' ||
+ op == '-' ||
+ op == '/' ||
+ op == '*' ||
+ op == '%' ||
+ op == '^' ||
+ op == '&' ||
+ op == '|') {
+ if (rLeft is VariableUse && rLeft.name == leftHandSide.name) {
+ // Output 'a = a + 1' as '++a' and 'a = a - 1' as '--a'.
+ if ((op == '+' || op == '-') &&
+ rRight is LiteralNumber &&
+ rRight.value == "1") {
+ _outputIncDec(op, assignment.leftHandSide, rightHandSide.left);
+ return;
+ }
+ // Output 'a = a + b' as 'a += b'.
+ startNode(rightHandSide.left);
+ visitNestedExpression(assignment.leftHandSide, CALL,
+ newInForInit: inForInit, newAtStatementBegin: atStatementBegin);
+ endNode(rightHandSide.left);
+ spaceOut();
+ out(op);
+ out("=");
+ spaceOut();
+ visitNestedExpression(rRight, ASSIGNMENT,
+ newInForInit: inForInit, newAtStatementBegin: false);
+ return;
+ }
+ }
+ }
visitNestedExpression(assignment.leftHandSide, CALL,
newInForInit: inForInit, newAtStatementBegin: atStatementBegin);
if (assignment.value != null) {
spaceOut();
- String op = assignment.op;
if (op != null) out(op);
out("=");
spaceOut();
@@ -972,34 +1040,33 @@
void visitAccess(PropertyAccess access) {
visitNestedExpression(access.receiver, CALL,
newInForInit: inForInit, newAtStatementBegin: atStatementBegin);
- Node selector = access.selector;
+ Node selector = undefer(access.selector);
if (selector is LiteralString) {
- LiteralString selectorString = selector;
- String fieldWithQuotes = selectorString.value;
+ String fieldWithQuotes = selector.value;
if (isValidJavaScriptId(fieldWithQuotes)) {
if (access.receiver is LiteralNumber &&
lastCharCode != charCodes.$CLOSE_PAREN) {
out(" ", isWhitespace: true);
}
out(".");
- startNode(selector);
+ startNode(access.selector);
out(fieldWithQuotes.substring(1, fieldWithQuotes.length - 1));
- endNode(selector);
+ endNode(access.selector);
return;
}
} else if (selector is Name) {
- if (access.receiver is LiteralNumber &&
- lastCharCode != charCodes.$CLOSE_PAREN) {
+ Node receiver = undefer(access.receiver);
+ if (receiver is LiteralNumber && lastCharCode != charCodes.$CLOSE_PAREN) {
out(" ", isWhitespace: true);
}
out(".");
- startNode(selector);
+ startNode(access.selector);
selector.accept(this);
- endNode(selector);
+ endNode(access.selector);
return;
}
out("[");
- visitNestedExpression(selector, EXPRESSION,
+ visitNestedExpression(access.selector, EXPRESSION,
newInForInit: false, newAtStatementBegin: false);
out("]");
}
@@ -1156,18 +1223,18 @@
@override
void visitProperty(Property node) {
startNode(node.name);
- if (node.name is LiteralString) {
- LiteralString nameString = node.name;
- String name = nameString.value;
- if (isValidJavaScriptId(name)) {
- out(name.substring(1, name.length - 1));
+ Node name = undefer(node.name);
+ if (name is LiteralString) {
+ String text = name.value;
+ if (isValidJavaScriptId(text)) {
+ out(text.substring(1, text.length - 1));
} else {
- out(name);
+ out(text);
}
- } else if (node.name is Name) {
+ } else if (name is Name) {
node.name.accept(this);
} else {
- assert(node.name is LiteralNumber);
+ assert(name is LiteralNumber);
LiteralNumber nameNumber = node.name;
out(nameNumber.value);
}
diff --git a/pkg/js_ast/lib/src/template.dart b/pkg/js_ast/lib/src/template.dart
index 0503e9f..a7dcf48 100644
--- a/pkg/js_ast/lib/src/template.dart
+++ b/pkg/js_ast/lib/src/template.dart
@@ -236,7 +236,7 @@
var nameOrPosition = node.nameOrPosition;
return (arguments) {
var value = arguments[nameOrPosition];
- if (value is Literal) return value;
+ if (value is Literal || value is DeferredExpression) return value;
error('Interpolated value #$nameOrPosition is not a Literal: $value');
};
}
diff --git a/pkg/js_ast/test/deferred_expression_test.dart b/pkg/js_ast/test/deferred_expression_test.dart
new file mode 100644
index 0000000..397aaaa
--- /dev/null
+++ b/pkg/js_ast/test/deferred_expression_test.dart
@@ -0,0 +1,160 @@
+// Copyright (c) 2019, 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:expect/expect.dart';
+import 'package:js_ast/js_ast.dart';
+
+main() {
+ Map<Expression, DeferredExpression> map = {};
+ VariableUse variableUse = new VariableUse('variable');
+ DeferredExpression deferred =
+ map[variableUse] = new _DeferredExpression(variableUse);
+ VariableUse variableUseAlias = new VariableUse('variable');
+ map[variableUseAlias] = new _DeferredExpression(variableUseAlias);
+
+ map[deferred] = new _DeferredExpression(deferred);
+ Literal literal = new LiteralString('"literal"');
+ map[literal] = new _DeferredExpression(literal);
+
+ test(map, '#', [variableUse], 'variable');
+ test(map, '#', [deferred], 'variable');
+ test(map, '{#: #}', [literal, variableUse], '{literal: variable}');
+ test(map, '{#: #}', [literal, deferred], '{literal: variable}');
+ test(map, '#.#', [variableUse, literal], 'variable.literal');
+ test(map, '#.#', [deferred, literal], 'variable.literal');
+ test(map, '# = # + 1', [variableUse, variableUseAlias], '++variable');
+ test(map, '# = # + 1', [deferred, variableUseAlias], '++variable');
+ test(map, '# = # - 1', [variableUse, variableUseAlias], '--variable');
+ test(map, '# = # - 1', [deferred, variableUseAlias], '--variable');
+ test(map, '# = # + 2', [variableUse, variableUseAlias], 'variable += 2');
+ test(map, '# = # + 2', [deferred, variableUseAlias], 'variable += 2');
+}
+
+void test(Map<Expression, DeferredExpression> map, String template,
+ List<Expression> arguments, String expectedOutput) {
+ Expression directExpression =
+ js.expressionTemplateFor(template).instantiate(arguments);
+ _Context directContext = new _Context();
+ Printer directPrinter =
+ new Printer(const JavaScriptPrintingOptions(), directContext);
+ directPrinter.visit(directExpression);
+ Expect.equals(expectedOutput, directContext.text);
+
+ Expression deferredExpression = js
+ .expressionTemplateFor(template)
+ .instantiate(arguments.map((e) => map[e]).toList());
+ _Context deferredContext = new _Context();
+ Printer deferredPrinter =
+ new Printer(const JavaScriptPrintingOptions(), deferredContext);
+ deferredPrinter.visit(deferredExpression);
+ Expect.equals(expectedOutput, deferredContext.text);
+
+ for (Expression argument in arguments) {
+ DeferredExpression deferred = map[argument];
+ Expect.isTrue(
+ directContext.enterPositions.containsKey(argument),
+ "Argument ${DebugPrint(argument)} not found in direct enter positions: "
+ "${directContext.enterPositions.keys}");
+ Expect.isTrue(
+ deferredContext.enterPositions.containsKey(argument),
+ "Argument ${DebugPrint(argument)} not found in "
+ "deferred enter positions: "
+ "${deferredContext.enterPositions.keys}");
+ Expect.isTrue(
+ deferredContext.enterPositions.containsKey(deferred),
+ "Argument ${DebugPrint(deferred)} not found in "
+ "deferred enter positions: "
+ "${deferredContext.enterPositions.keys}");
+ Expect.equals(directContext.enterPositions[argument],
+ deferredContext.enterPositions[argument]);
+ Expect.equals(directContext.enterPositions[argument],
+ deferredContext.enterPositions[deferred]);
+
+ Expect.isTrue(
+ directContext.exitPositions.containsKey(argument),
+ "Argument ${DebugPrint(argument)} not found in direct enter positions: "
+ "${directContext.exitPositions.keys}");
+ Expect.isTrue(
+ deferredContext.exitPositions.containsKey(argument),
+ "Argument ${DebugPrint(argument)} not found in "
+ "deferred enter positions: "
+ "${deferredContext.exitPositions.keys}");
+ Expect.isTrue(
+ deferredContext.exitPositions.containsKey(deferred),
+ "Argument ${DebugPrint(deferred)} not found in "
+ "deferred enter positions: "
+ "${deferredContext.exitPositions.keys}");
+ Expect.equals(directContext.exitPositions[argument],
+ deferredContext.exitPositions[argument]);
+ Expect.equals(directContext.exitPositions[argument],
+ deferredContext.exitPositions[deferred]);
+ }
+}
+
+class _DeferredExpression extends DeferredExpression {
+ final Expression value;
+
+ _DeferredExpression(this.value);
+
+ @override
+ int get precedenceLevel => value.precedenceLevel;
+}
+
+class _Context implements JavaScriptPrintingContext {
+ StringBuffer sb = new StringBuffer();
+ List<String> errors = [];
+ Map<Node, int> enterPositions = {};
+ Map<Node, _Position> exitPositions = {};
+
+ @override
+ void emit(String string) {
+ sb.write(string);
+ }
+
+ @override
+ void enterNode(Node node, int startPosition) {
+ enterPositions[node] = startPosition;
+ }
+
+ @override
+ void exitNode(
+ Node node, int startPosition, int endPosition, int closingPosition) {
+ exitPositions[node] =
+ new _Position(startPosition, endPosition, closingPosition);
+ Expect.equals(enterPositions[node], startPosition);
+ }
+
+ @override
+ void error(String message) {
+ errors.add(message);
+ }
+
+ String get text => sb.toString();
+}
+
+class _Position {
+ final int startPosition;
+ final int endPosition;
+ final int closingPosition;
+
+ _Position(this.startPosition, this.endPosition, this.closingPosition);
+
+ int get hashCode =>
+ 13 * startPosition.hashCode +
+ 17 * endPosition.hashCode +
+ 19 * closingPosition.hashCode;
+
+ bool operator ==(Object other) {
+ if (identical(this, other)) return true;
+ return other is _Position &&
+ startPosition == other.startPosition &&
+ endPosition == other.endPosition &&
+ closingPosition == other.closingPosition;
+ }
+
+ String toString() {
+ return '_Position(start=$startPosition,'
+ 'end=$endPosition,closing=$closingPosition)';
+ }
+}