Don't split inside string interpolation. (#709)
* Don't split inside string interpolation.
When it happens, it's usually a sign that you should split the string
yourself to avoid it. But it still looks terrible. And in multi-line
strings, you may *want* to have lines longer than the page width, in
which case the splitting is really bad.
* Reword doc comment.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b2aa4ea..686a406 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 1.1.2
+
+* Don't split inside string interpolations.
+
# 1.1.1
* Format expressions in string interpolations (#226).
diff --git a/bin/format.dart b/bin/format.dart
index 23d0979..e0de12d 100644
--- a/bin/format.dart
+++ b/bin/format.dart
@@ -15,7 +15,7 @@
import 'package:dart_style/src/style_fix.dart';
// Note: The following line of code is modified by tool/grind.dart.
-const version = "1.1.1";
+const version = "1.1.2";
void main(List<String> args) {
var parser = new ArgParser(allowTrailingOptions: true);
diff --git a/lib/src/chunk_builder.dart b/lib/src/chunk_builder.dart
index 34a480d..4bdd166 100644
--- a/lib/src/chunk_builder.dart
+++ b/lib/src/chunk_builder.dart
@@ -93,6 +93,17 @@
/// block.
bool _firstFlushLeft = false;
+ /// The number of calls to [preventSplit()] that have not been ended by a
+ /// call to [endPreventSplit()].
+ ///
+ /// Splitting is completely disabled inside string interpolation. We do want
+ /// to fix the whitespace inside interpolation, though, so we still format
+ /// them. This tracks whether we're inside an interpolation. We can't use a
+ /// simple bool because interpolation can nest.
+ ///
+ /// When this is non-zero, splits are ignored.
+ int _preventSplitNesting = 0;
+
/// Whether there is pending whitespace that depends on the number of
/// newlines in the source.
///
@@ -501,6 +512,10 @@
///
/// Nested blocks are handled using their own independent [LineWriter].
ChunkBuilder startBlock(Chunk argumentChunk) {
+ // If we are not allowed to split at all, don't create a new block. Instead,
+ // the block contents will end up in the current chunk.
+ if (_preventSplitNesting > 0) return this;
+
var chunk = _chunks.last;
chunk.makeBlock(argumentChunk);
@@ -523,6 +538,11 @@
///
/// Returns the previous writer for the surrounding block.
ChunkBuilder endBlock(Rule ignoredSplit, {bool forceSplit}) {
+ // If we are not allowed to split at all, we didn't create a new block and
+ // thus didn't create a new ChunkBuilder, so there is no builder to pop.
+ // We are still in the current one.
+ if (_preventSplitNesting > 0) return this;
+
_divideChunks();
// If we don't already know if the block is going to split, see if it
@@ -602,6 +622,15 @@
selectionLength: selectionLength);
}
+ void preventSplit() {
+ _preventSplitNesting++;
+ }
+
+ void endPreventSplit() {
+ _preventSplitNesting--;
+ assert(_preventSplitNesting >= 0, "Mismatched calls.");
+ }
+
/// Writes the current pending [Whitespace] to the output, if any.
///
/// This should only be called after source lines have been preserved to turn
@@ -738,6 +767,21 @@
/// to be at column zero. Otherwise, it uses the normal indentation and
/// nesting behavior.
void _writeHardSplit({bool isDouble, bool flushLeft, bool nest: false}) {
+ // If we are not allowed to split at all, simply write a space. Instead of:
+ //
+ // foo("${() {
+ // a;
+ // b;
+ // }}");
+ //
+ // produces:
+ //
+ // foo("${() { a; b; }}");
+ if (_preventSplitNesting > 0) {
+ _writeText(" ");
+ return;
+ }
+
// A hard split overrides any other whitespace.
_pendingWhitespace = null;
_writeSplit(new Rule.hard(),
@@ -749,7 +793,16 @@
/// Returns the chunk.
Chunk _writeSplit(Rule rule,
{bool flushLeft, bool isDouble, bool nest, bool space}) {
- if (nest == null) nest = true;
+ nest ??= true;
+ space ??= false;
+
+ // If we are not allowed to split at all, don't. Returning null for the
+ // chunk is safe since the rule that uses the chunk will itself get
+ // discarded because no chunk references it.
+ if (_preventSplitNesting > 0) {
+ if (space) write(" ");
+ return null;
+ }
if (_chunks.isEmpty) {
if (flushLeft != null) _firstFlushLeft = flushLeft;
diff --git a/lib/src/source_visitor.dart b/lib/src/source_visitor.dart
index a05f5c8..da05270 100644
--- a/lib/src/source_visitor.dart
+++ b/lib/src/source_visitor.dart
@@ -1559,11 +1559,13 @@
}
visitInterpolationExpression(InterpolationExpression node) {
+ builder.preventSplit();
token(node.leftBracket);
builder.startSpan();
visit(node.expression);
builder.endSpan();
token(node.rightBracket);
+ builder.endPreventSplit();
}
visitInterpolationString(InterpolationString node) {
diff --git a/pubspec.yaml b/pubspec.yaml
index 0810110..634c852 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: dart_style
# Note: See tool/grind.dart for how to bump the version.
-version: 1.1.1
+version: 1.1.2
author: Dart Team <misc@dartlang.org>
description: Opinionated, automatic Dart source code formatter.
homepage: https://github.com/dart-lang/dart_style
diff --git a/test/regression/0000/0057.stmt b/test/regression/0000/0057.stmt
index be00d6b..77294f8 100644
--- a/test/regression/0000/0057.stmt
+++ b/test/regression/0000/0057.stmt
@@ -5,8 +5,7 @@
name: 'embeddedPlural2', desc: 'An embedded plural', args: [n]);
<<<
embeddedPlural2(n) => Intl.message(
- "${Intl.plural(n,
- zero: 'none', one: 'one', other: 'some')} plus some text.",
+ "${Intl.plural(n, zero: 'none', one: 'one', other: 'some')} plus some text.",
name: 'embeddedPlural2',
desc: 'An embedded plural',
args: [n]);
\ No newline at end of file
diff --git a/test/regression/0100/0189.stmt b/test/regression/0100/0189.stmt
index 4acf089..243f837 100644
--- a/test/regression/0100/0189.stmt
+++ b/test/regression/0100/0189.stmt
@@ -11,9 +11,8 @@
nestedSelect(currency, amount) => Intl.select(
currency,
{
- "CDN": """${Intl.plural(amount,
- one: '$amount Canadian dollar',
- other: '$amount Canadian dollars')}""",
+ "CDN":
+ """${Intl.plural(amount, one: '$amount Canadian dollar', other: '$amount Canadian dollars')}""",
"other": "Whatever",
},
name: "nestedSelect",
diff --git a/test/regression/0400/0467.unit b/test/regression/0400/0467.unit
index 680067c..57df4c3 100644
--- a/test/regression/0400/0467.unit
+++ b/test/regression/0400/0467.unit
@@ -21,8 +21,6 @@
'nodesAndEntryPoints',
new PolymerDom(this).children.map((child) =>
'${child.outerHtml} ------> '
- '${(new PolymerDom(child).getDestinationInsertionPoints()[0]
- as Element)
- .outerHtml}'));
+ '${(new PolymerDom(child).getDestinationInsertionPoints()[0] as Element).outerHtml}'));
}
}
\ No newline at end of file
diff --git a/test/regression/0500/0500.unit b/test/regression/0500/0500.unit
index 82988db..4e1a62f 100644
--- a/test/regression/0500/0500.unit
+++ b/test/regression/0500/0500.unit
@@ -26,10 +26,9 @@
for (int i = 0; i < names.length; ++i) {
env.initialize(names[i],
getter: (TopLevelBinding binding, ExprCont ek, ExprCont k) {
- binding.getter =
- (TopLevelBinding _, ExprCont ek0, ExprCont k0) => ek0(
- "Reading static variable '${binding
- .name}' during its initialization");
+ binding.getter = (TopLevelBinding _, ExprCont ek0,
+ ExprCont k0) =>
+ ek0("Reading static variable '${binding.name}' during its initialization");
initializers[i](env, ek, (v) {
binding.getter =
(TopLevelBinding _, ExprCont ek1, ExprCont k1) => k1(v);
diff --git a/test/splitting/strings.stmt b/test/splitting/strings.stmt
index f6d5529..d3bdaab 100644
--- a/test/splitting/strings.stmt
+++ b/test/splitting/strings.stmt
@@ -138,17 +138,31 @@
"many",
"elements"
]);
->>> interpolation in multi-line does not force unneeded splits
+>>> interpolation is not split even when line is too long
+someMethod("some text that is pretty long ${ interpolate +
+a + thing } more text");
+<<<
+someMethod(
+ "some text that is pretty long ${interpolate + a + thing} more text");
+>>> multi-line string interpolation does not split
someMethod("foo", """
some text that is pretty long
- some more text that is pretty long
- ${ inpolate + a + thing }
- more text
+ some more text that is pretty long ${ interpolate + a + thing } more text
""");
<<<
someMethod("foo", """
some text that is pretty long
- some more text that is pretty long
- ${inpolate + a + thing}
- more text
-""");
\ No newline at end of file
+ some more text that is pretty long ${interpolate + a + thing} more text
+""");
+>>> nested interpolation is not split
+someMethod("some text that is ${pretty + 'long ${ interpolate +
+a + thing } more'} text", "another arg");
+<<<
+someMethod(
+ "some text that is ${pretty + 'long ${interpolate + a + thing} more'} text",
+ "another arg");
+>>> hard splits are not split in interpolation
+someMethod("before ${(){statement();statement();statement();}} after");
+<<<
+someMethod(
+ "before ${() { statement(); statement(); statement(); }} after");
\ No newline at end of file