Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Commit f67ba57

Browse files
author
John Messerly
committed
better handling of string literals and interpolation
fixes #42 V8 has shipped template strings: https://code.google.com/p/v8/issues/detail?id=3230 [email protected] Review URL: https://chromereviews.googleplex.com/152127013
1 parent a731632 commit f67ba57

File tree

13 files changed

+305
-260
lines changed

13 files changed

+305
-260
lines changed

lib/src/codegen/js_codegen.dart

+61-15
Original file line numberDiff line numberDiff line change
@@ -584,10 +584,6 @@ $name.prototype[Symbol.iterator] = function() {
584584
out.write('set ');
585585
}
586586

587-
// V8 does not yet support ComputedPropertyName syntax in MethodDefinitions.
588-
//
589-
// MethodDefinition: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-method-definitions
590-
// PropertyName: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object-initializer
591587
var name = _canonicalMethodName(node.name.name);
592588
out.write('$name(');
593589
_visitNode(node.parameters);
@@ -1624,38 +1620,88 @@ $name.prototype[Symbol.iterator] = function() {
16241620

16251621
@override
16261622
void visitSimpleStringLiteral(SimpleStringLiteral node) {
1627-
// TODO(jmesserly): this does not handle all quote styles
1628-
out.write('"${node.stringValue}"');
1623+
if (node.isSingleQuoted) {
1624+
var escaped = _escapeForJs(node.stringValue, "'");
1625+
out.write("'$escaped'");
1626+
} else {
1627+
var escaped = _escapeForJs(node.stringValue, '"');
1628+
out.write('"$escaped"');
1629+
}
16291630
}
16301631

16311632
@override
16321633
void visitAdjacentStrings(AdjacentStrings node) {
1633-
_visitNodeList(node.strings, separator: ' + ');
1634+
// These are typically used for splitting long strings across lines, so
1635+
// generate accordingly, with each on its own line and +4 indent.
1636+
1637+
// TODO(jmesserly): we could linebreak before the first string too, but
1638+
// that means inserting a linebreak in expression context, which might
1639+
// not be valid and leaves trailing whitespace.
1640+
for (int i = 0, last = node.strings.length - 1; i <= last; i++) {
1641+
if (i == 1) {
1642+
out.write(' +\n', 4);
1643+
} else if (i > 1) {
1644+
out.write(' +\n');
1645+
}
1646+
node.strings[i].accept(this);
1647+
if (i == last && i > 0) {
1648+
out.write('', -4);
1649+
}
1650+
}
16341651
}
16351652

16361653
@override
16371654
void visitStringInterpolation(StringInterpolation node) {
1638-
_visitNodeList(node.elements, separator: ' + ');
1655+
out.write('`');
1656+
_visitNodeList(node.elements);
1657+
out.write('`');
16391658
}
16401659

16411660
@override
16421661
void visitInterpolationString(InterpolationString node) {
1643-
out.write('"${node.value}"');
1662+
out.write(_escapeForJs(node.value, '`'));
1663+
}
1664+
1665+
/// Escapes the string from [value], handling escape sequences as needed.
1666+
/// The surrounding [quote] style must be supplied to know which quotes to
1667+
/// escape, but quotes are not added to the resulting string.
1668+
String _escapeForJs(String value, String quote) {
1669+
// Start by escaping the backslashes.
1670+
String escaped = value.replaceAll('\\', '\\\\');
1671+
// Do not escape unicode characters and ' because they are allowed in the
1672+
// string literal anyway.
1673+
return escaped.replaceAllMapped(new RegExp('\n|$quote|\b|\t|\v'), (m) {
1674+
switch (m.group(0)) {
1675+
case "\n":
1676+
return r"\n";
1677+
case "\b":
1678+
return r"\b";
1679+
case "\t":
1680+
return r"\t";
1681+
case "\f":
1682+
return r"\f";
1683+
case "\v":
1684+
return r"\v";
1685+
// Quotes are only replaced if they conflict with the containing quote
1686+
case '"':
1687+
return r'\"';
1688+
case "'":
1689+
return r"\'";
1690+
case "`":
1691+
return r"\`";
1692+
}
1693+
});
16441694
}
16451695

16461696
@override
16471697
void visitInterpolationExpression(InterpolationExpression node) {
1648-
// TODO(jmesserly): skip parens if not needed.
1649-
// TODO(jmesserly): we could also use ES6 template strings here:
1650-
// https://github.com/lukehoban/es6features#template-strings
1651-
out.write('(');
1698+
out.write('\${');
16521699
node.expression.accept(this);
16531700
// Assuming we implement toString() on our objects, we can avoid calling it
16541701
// in most cases. Builtin types may differ though.
16551702
// For example, Dart's concrete List type does not have the same toString
16561703
// as Array.prototype.toString().
1657-
// https://people.mozilla.org/~jorendorff/es6-draft.html#sec-addition-operator-plus-runtime-semantics-evaluation
1658-
out.write(')');
1704+
out.write('}');
16591705
}
16601706

16611707
@override

test/codegen/expect/BenchmarkBase/BenchmarkBase.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ var BenchmarkBase;
44
class Expect {
55
static equals(expected, actual) {
66
if (!dart.equals(expected, actual)) {
7-
throw "Values not equal: " + (expected) + " vs " + (actual) + "";
7+
throw `Values not equal: ${expected} vs ${actual}`;
88
}
99
}
1010
static listEquals(expected, actual) {
1111
if (expected.length !== actual.length) {
12-
throw "Lists have different lengths: " + (expected.length) + " vs " + (actual.length) + "";
12+
throw `Lists have different lengths: ${expected.length} vs ${actual.length}`;
1313
}
1414
for (let i = 0; i < actual.length; i++) {
1515
equals(expected.get(i), actual.get(i));
@@ -64,7 +64,7 @@ var BenchmarkBase;
6464
}
6565
report() {
6666
let score = this.measure();
67-
core.print("" + (this.name) + "(RunTime): " + (score) + " us.");
67+
core.print(`${this.name}(RunTime): ${score} us.`);
6868
}
6969
}
7070

test/codegen/expect/DeltaBlue/DeltaBlue.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ var DeltaBlue;
370370
plan.execute();
371371
if (last.value !== i) {
372372
core.print("Chain test failed:");
373-
core.print("Expected last value to be " + (i) + " but it was " + (last.value) + ".");
373+
core.print(`Expected last value to be ${i} but it was ${last.value}.`);
374374
}
375375
}
376376
}

test/codegen/expect/_internal/_internal.js

+20-13
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ var _internal;
115115
let length = this.length;
116116
if (!separator.isEmpty) {
117117
if (length === 0) return "";
118-
let first = "" + (this.elementAt(0)) + "";
118+
let first = `${this.elementAt(0)}`;
119119
if (length !== this.length) {
120120
throw new core.ConcurrentModificationError(this);
121121
}
@@ -901,7 +901,7 @@ var _internal;
901901
static joinList(list, separator) {
902902
if (separator === undefined) separator = null;
903903
if (list.isEmpty) return "";
904-
if (list.length === 1) return "" + (list.get(0)) + "";
904+
if (list.length === 1) return `${list.get(0)}`;
905905
let buffer = new core.StringBuffer();
906906
if (separator.isEmpty) {
907907
for (let i = 0; i < list.length; i++) {
@@ -1355,7 +1355,7 @@ var _internal;
13551355
core.RangeError.checkNotNegative(length);
13561356
core.RangeError.checkNotNegative(start);
13571357
if (start + length > a.length) {
1358-
let message = "" + (start) + " + " + (length) + " must be in the range [0.." + (a.length) + "]";
1358+
let message = `${start} + ${length} must be in the range [0..${a.length}]`;
13591359
throw new core.RangeError.range(length, 0, a.length - start, "length", message);
13601360
}
13611361
}
@@ -1597,28 +1597,35 @@ var _internal;
15971597
let arbitraryPrime = 664597;
15981598
return 536870911 & (arbitraryPrime * this._name.hashCode);
15991599
}
1600-
toString() { return "Symbol("" + (this._name) + "")"; }
1600+
toString() { return `Symbol("${this._name}")`; }
16011601
static getName(symbol) { return symbol._name; }
16021602
static validatePublicSymbol(name) {
16031603
if (name.isEmpty || publicSymbolPattern.hasMatch(name)) return name;
1604-
if (name.startsWith("_")) {
1605-
throw new core.ArgumentError(""" + (name) + "" is a private identifier");
1604+
if (name.startsWith('_')) {
1605+
throw new core.ArgumentError(`"${name}" is a private identifier`);
16061606
}
1607-
throw new core.ArgumentError(""" + (name) + "" is not a valid (qualified) symbol name");
1607+
throw new core.ArgumentError(`"${name}" is not a valid (qualified) symbol name`);
16081608
}
16091609
static isValidSymbol(name) {
16101610
return (name.isEmpty || symbolPattern.hasMatch(name));
16111611
}
16121612
}
16131613
dart.defineNamedConstructor(Symbol, "unvalidated");
16141614
dart.defineNamedConstructor(Symbol, "validated");
1615-
Symbol.reservedWordRE = "(?:assert|break|c(?:a(?:se|tch)|lass|on(?:st|tinue))|d(?:efault|o)|" + "e(?:lse|num|xtends)|f(?:alse|inal(?:ly)?|or)|i[fns]|n(?:ew|ull)|" + "ret(?:hrow|urn)|s(?:uper|witch)|t(?:h(?:is|row)|r(?:ue|y))|" + "v(?:ar|oid)|w(?:hile|ith))";
1616-
Symbol.publicIdentifierRE = "(?!" + "" + (reservedWordRE) + "" + "\b(?!\$))[a-zA-Z$][\w$]*";
1617-
Symbol.identifierRE = "(?!" + "" + (reservedWordRE) + "" + "\b(?!\$))[a-zA-Z$_][\w$]*";
1618-
Symbol.operatorRE = "(?:[\-+*/%&|^]|\[\]=?|==|~/?|<[<=]?|>[>=]?|unary-)";
1615+
Symbol.reservedWordRE = '(?:assert|break|c(?:a(?:se|tch)|lass|on(?:st|tinue))|d(?:efault|o)|' +
1616+
'e(?:lse|num|xtends)|f(?:alse|inal(?:ly)?|or)|i[fns]|n(?:ew|ull)|' +
1617+
'ret(?:hrow|urn)|s(?:uper|witch)|t(?:h(?:is|row)|r(?:ue|y))|' +
1618+
'v(?:ar|oid)|w(?:hile|ith))';
1619+
Symbol.publicIdentifierRE = '(?!' +
1620+
`${reservedWordRE}` +
1621+
'\\b(?!\\$))[a-zA-Z$][\\w$]*';
1622+
Symbol.identifierRE = '(?!' +
1623+
`${reservedWordRE}` +
1624+
'\\b(?!\\$))[a-zA-Z$_][\\w$]*';
1625+
Symbol.operatorRE = '(?:[\\-+*/%&|^]|\\[\\]=?|==|~/?|<[<=]?|>[>=]?|unary-)';
16191626
dart.defineLazyProperties(Symbol, {
1620-
get publicSymbolPattern() { return new core.RegExp("^(?:" + (operatorRE) + "$|" + (publicIdentifierRE) + "(?:=?$|[.](?!$)))+?$") },
1621-
get symbolPattern() { return new core.RegExp("^(?:" + (operatorRE) + "$|" + (identifierRE) + "(?:=?$|[.](?!$)))+?$") },
1627+
get publicSymbolPattern() { return new core.RegExp(`^(?:${operatorRE}$|${publicIdentifierRE}(?:=?$|[.](?!$)))+?$`) },
1628+
get symbolPattern() { return new core.RegExp(`^(?:${operatorRE}$|${identifierRE}(?:=?$|[.](?!$)))+?$`) },
16221629
});
16231630

16241631
// Exports:

test/codegen/expect/async/async.js

+7-8
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,9 @@ var async;
3131
return null;
3232
}
3333
toString() {
34-
let result = "Uncaught Error: " + (this.error) + "";
34+
let result = `Uncaught Error: ${this.error}`;
3535
if (this.stackTrace !== null) {
36-
result = "
37-
Stack Trace:
38-
" + (this.stackTrace) + "";
36+
result = `\nStack Trace:\n${this.stackTrace}`;
3937
}
4038
return result;
4139
}
@@ -450,7 +448,7 @@ var async;
450448
constructor(_s) {
451449
this._s = _s;
452450
}
453-
toString() { return "DeferredLoadException: '" + (this._s) + "'"; }
451+
toString() { return `DeferredLoadException: '${this._s}'`; }
454452
}
455453

456454
let Future$ = dart.generic(function(T) {
@@ -621,8 +619,8 @@ var async;
621619
}
622620
toString() {
623621
let result = "TimeoutException";
624-
if (this.duration !== null) result = "TimeoutException after " + (this.duration) + "";
625-
if (this.message !== null) result = "" + (result) + ": " + (this.message) + "";
622+
if (this.duration !== null) result = `TimeoutException after ${this.duration}`;
623+
if (this.message !== null) result = `${result}: ${this.message}`;
626624
return result;
627625
}
628626
}
@@ -4244,7 +4242,8 @@ var async;
42444242
if (specification === null) {
42454243
specification = new ZoneSpecification();
42464244
} else if (!dart.is(specification, _ZoneSpecification)) {
4247-
throw new core.ArgumentError("ZoneSpecifications must be instantiated" + " with the provided constructor.");
4245+
throw new core.ArgumentError("ZoneSpecifications must be instantiated" +
4246+
" with the provided constructor.");
42484247
}
42494248
let valueMap = null;
42504249
if (zoneValues === null) {

0 commit comments

Comments
 (0)