Better indentation of adjacent strings in argument lists. (#800)
See the CHANGELOG diff for details. The relevant examples are:
1.2.4:
function(
"adjacent"
"string");
function(
"adjacent"
"string", // Not great.
);
function(
"adjacent"
"string",
"another");
function(
"adjacent"
"string",
"another",
);
1.2.5:
function(
"adjacent"
"string"); // Regression, but consistent with trailing comma.
function(
"adjacent"
"string",
);
function(
"adjacent"
"string", // Uintentional, but an improvement.
"another");
function(
"adjacent"
"string", // Unchanged.
"another",
);
Now:
function(
"adjacent"
"string"); // No indent, since not needed to distinguish args.
function(
"adjacent"
"string", // Consistent with non-trailing.
);
function(
"adjacent"
"string", // Intentional, distinguishes from next string.
"another");
function(
"adjacent"
"string", // Consistent with non-trailing.
"another",
);
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fae032d..1178530 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,20 @@
# 1.2.6
* Properly format trailing commas in assertions.
+* Improve indentation of adjacent strings. This fixes a regression introduced
+ in 1.2.5 and hopefully makes adjacent strings generally look better.
+
+ Adjacent strings in argument lists now format the same regardless of whether
+ the argument list contains a trailing comma. The rule is that if the
+ argument list contains no other strings, then the adjacent strings do not
+ get extra indentation. This keeps them lined up when doing so is unlikely to
+ be confused as showing separate independent string arguments.
+
+ Previously, adjacent strings were never indented in argument lists without a
+ trailing comma and always in argument lists that did. With this change,
+ adjacent strings are still always indented in collection literals because
+ readers are likely to interpret a series of unindented lines there as showing
+ separate collection elements.
# 1.2.5
diff --git a/lib/src/source_visitor.dart b/lib/src/source_visitor.dart
index c3f1525..d31f6b8 100644
--- a/lib/src/source_visitor.dart
+++ b/lib/src/source_visitor.dart
@@ -186,9 +186,72 @@
}
visitAdjacentStrings(AdjacentStrings node) {
+ // We generally want to indent adjacent strings because it can be confusing
+ // otherwise when they appear in a list of expressions, like:
+ //
+ // [
+ // "one",
+ // "two"
+ // "three",
+ // "four"
+ // ]
+ //
+ // Especially when these stings are longer, it can be hard to tell that
+ // "three" is a continuation of the previous argument.
+ //
+ // However, the indentation is distracting in argument lists that don't
+ // suffer from this ambiguity:
+ //
+ // test(
+ // "A very long test description..."
+ // "this indentation looks bad.", () { ... });
+ //
+ // To balance these, we omit the indentation when an adjacent string
+ // expression is the only string in an argument list.
+ var shouldNest = true;
+
+ isString(AstNode node) => node is StringLiteral || node is AdjacentStrings;
+
+ var parent = node.parent;
+ if (parent is ArgumentList) {
+ shouldNest = false;
+
+ for (var argument in parent.arguments) {
+ if (argument == node) continue;
+ if (isString(argument)) {
+ shouldNest = true;
+ break;
+ }
+ }
+ } else if (parent is Assertion) {
+ // Treat asserts like argument lists.
+ shouldNest = false;
+ if (parent.condition != node && isString(parent.condition)) {
+ shouldNest = true;
+ }
+
+ if (parent.message != node && isString(parent.message)) {
+ shouldNest = true;
+ }
+ } else if (parent is VariableDeclaration ||
+ parent is AssignmentExpression &&
+ parent.rightHandSide == node &&
+ parent.parent is ExpressionStatement) {
+ // Don't add extra indentation in a variable initializer or assignment:
+ //
+ // var variable =
+ // "no extra"
+ // "indent";
+ shouldNest = false;
+ } else if (parent is NamedExpression) {
+ shouldNest = false;
+ }
+
builder.startSpan();
builder.startRule();
+ if (shouldNest) builder.nestExpression();
visitNodes(node.strings, between: splitOrNewline);
+ if (shouldNest) builder.unnest();
builder.endRule();
builder.endSpan();
}
diff --git a/test/regression/0100/0185.stmt b/test/regression/0100/0185.stmt
index aac3da2..29a2b7a 100644
--- a/test/regression/0100/0185.stmt
+++ b/test/regression/0100/0185.stmt
@@ -9,6 +9,6 @@
const NO_DART_SCRIPT_AND_EXPERIMENTAL = const MessageTemplate(
const MessageId('polymer', 6),
'The experimental bootstrap feature doesn\'t support script tags on '
- 'the main document (for now).',
+ 'the main document (for now).',
'Script tags with experimental bootstrap',
'This experimental feature is no longer supported.');
\ No newline at end of file
diff --git a/test/regression/0200/0211.unit b/test/regression/0200/0211.unit
index 01766d8..aad9674 100644
--- a/test/regression/0200/0211.unit
+++ b/test/regression/0200/0211.unit
@@ -24,7 +24,7 @@
JS(
'void',
'Object.defineProperty(#, #, '
- '{value: #, enumerable: false, writable: true, configurable: true})',
+ '{value: #, enumerable: false, writable: true, configurable: true})',
obj,
property,
value);
diff --git a/test/regression/0400/0467.unit b/test/regression/0400/0467.unit
index 57df4c3..29ef4f3 100644
--- a/test/regression/0400/0467.unit
+++ b/test/regression/0400/0467.unit
@@ -19,8 +19,7 @@
void showNodesAndEntryPoints([_, __]) {
addAll(
'nodesAndEntryPoints',
- new PolymerDom(this).children.map((child) =>
- '${child.outerHtml} ------> '
+ new PolymerDom(this).children.map((child) => '${child.outerHtml} ------> '
'${(new PolymerDom(child).getDestinationInsertionPoints()[0] as Element).outerHtml}'));
}
}
\ No newline at end of file
diff --git a/test/regression/0700/0705.stmt b/test/regression/0700/0705.stmt
new file mode 100644
index 0000000..38f2fd2
--- /dev/null
+++ b/test/regression/0700/0705.stmt
@@ -0,0 +1,10 @@
+>>>
+final _appUrl = Platform.isIOS
+ ? 'https://itunes.apple.com/us/app/google-adwords/id1037457231'
+ : 'https://play.google.com/store/apps/details?id=com.google.android.apps.'
+ 'adwords';
+<<<
+final _appUrl = Platform.isIOS
+ ? 'https://itunes.apple.com/us/app/google-adwords/id1037457231'
+ : 'https://play.google.com/store/apps/details?id=com.google.android.apps.'
+ 'adwords';
\ No newline at end of file
diff --git a/test/regression/0700/0799.unit b/test/regression/0700/0799.unit
new file mode 100644
index 0000000..acd54a4
--- /dev/null
+++ b/test/regression/0700/0799.unit
@@ -0,0 +1,14 @@
+>>>
+@ShouldThrow(
+ 'Could not generate `toJson` code for `watch`.\n'
+ 'None of the provided `TypeHelper` instances support the defined type.',
+ configurations: ['default'],
+)
+class C {}
+<<<
+@ShouldThrow(
+ 'Could not generate `toJson` code for `watch`.\n'
+ 'None of the provided `TypeHelper` instances support the defined type.',
+ configurations: ['default'],
+)
+class C {}
\ No newline at end of file
diff --git a/test/regression/other/flutter.unit b/test/regression/other/flutter.unit
new file mode 100644
index 0000000..98dbde8
--- /dev/null
+++ b/test/regression/other/flutter.unit
@@ -0,0 +1,18 @@
+>>>
+const Vendor _trevor = Vendor(
+ name: 'Trevor’s shop',
+ avatarAsset: 'people/square/trevor.png',
+ avatarAssetPackage: _kGalleryAssetsPackage,
+ description:
+ 'Trevor makes great stuff for awesome people like you. Super cool and extra '
+ 'awesome all of his shop’s goods are handmade with love. Custom orders are '
+ 'available upon request if you need something extra special.');
+<<<
+const Vendor _trevor = Vendor(
+ name: 'Trevor’s shop',
+ avatarAsset: 'people/square/trevor.png',
+ avatarAssetPackage: _kGalleryAssetsPackage,
+ description:
+ 'Trevor makes great stuff for awesome people like you. Super cool and extra '
+ 'awesome all of his shop’s goods are handmade with love. Custom orders are '
+ 'available upon request if you need something extra special.');
\ No newline at end of file
diff --git a/test/whitespace/adjacent_strings.stmt b/test/whitespace/adjacent_strings.stmt
new file mode 100644
index 0000000..40d5da1
--- /dev/null
+++ b/test/whitespace/adjacent_strings.stmt
@@ -0,0 +1,232 @@
+40 columns |
+>>> do not indent adjacent strings if other args are not strings
+function(
+ notString,
+ "adjacent"
+ "string");
+<<<
+function(
+ notString,
+ "adjacent"
+ "string");
+>>> do not indent adjacent strings if other args are not strings
+function(
+ notString,
+ "adjacent"
+ "string",);
+<<<
+function(
+ notString,
+ "adjacent"
+ "string",
+);
+>>> do indent adjacent strings if other arg is string
+function(
+ "string",
+ notString,
+ "adjacent"
+ "string");
+<<<
+function(
+ "string",
+ notString,
+ "adjacent"
+ "string");
+>>> do indent adjacent strings if other arg is string
+function(
+ "string",
+ notString,
+ "adjacent"
+ "string",);
+<<<
+function(
+ "string",
+ notString,
+ "adjacent"
+ "string",
+);
+>>> do indent adjacent strings if other arg is string interpolation
+function(
+ "${str}${ing}",
+ notString,
+ "adjacent"
+ "string");
+<<<
+function(
+ "${str}${ing}",
+ notString,
+ "adjacent"
+ "string");
+>>> do indent adjacent strings if other arg is adjacent string
+function(
+ "adjacent"
+ "string",
+ notString,
+ "adjacent"
+ "string");
+<<<
+function(
+ "adjacent"
+ "string",
+ notString,
+ "adjacent"
+ "string");
+>>> always indent adjacent strings in list
+var list = [
+ "adjacent"
+ "string",
+ "another"
+ "adjacent"
+ "string"
+];
+<<<
+var list = [
+ "adjacent"
+ "string",
+ "another"
+ "adjacent"
+ "string"
+];
+>>> always indent adjacent strings in map key
+var map = {
+ "adjacent"
+ "string": value,
+ "another"
+ "adjacent"
+ "string": value
+};
+<<<
+var map = {
+ "adjacent"
+ "string": value,
+ "another"
+ "adjacent"
+ "string": value
+};
+>>> always indent adjacent strings in map value
+var map = {
+ key: "adjacent"
+ "string",
+ key: "another"
+ "adjacent"
+ "string"
+};
+<<<
+var map = {
+ key: "adjacent"
+ "string",
+ key: "another"
+ "adjacent"
+ "string"
+};
+>>> always indent adjacent strings in set
+var set = {
+ "adjacent"
+ "string",
+ "another"
+ "adjacent"
+ "string"
+};
+<<<
+var set = {
+ "adjacent"
+ "string",
+ "another"
+ "adjacent"
+ "string"
+};
+>>> don't indent in => body
+main() => "adjacent"
+"string";
+<<<
+main() => "adjacent"
+ "string";
+>>> indent in then branch of ?:
+var string = c ? "adjacent"
+"string" : "other";
+<<<
+var string = c
+ ? "adjacent"
+ "string"
+ : "other";
+>>> indent in else branch of ?:
+var string = c ? "other" : "adjacent"
+"string";
+<<<
+var string = c
+ ? "other"
+ : "adjacent"
+ "string";
+>>> don't indent in initializer
+var longVariableName = "very long adjacent"
+"string";
+<<<
+var longVariableName =
+ "very long adjacent"
+ "string";
+>>> don't indent assignment in statement position
+long.receiver.expression = "very long adjacent"
+"string";
+<<<
+long.receiver.expression =
+ "very long adjacent"
+ "string";
+>>> string interpolation counts as a string
+function(
+ "adjacent"
+ "string",
+ "${str}${ing}");
+<<<
+function(
+ "adjacent"
+ "string",
+ "${str}${ing}");
+>>> another adjacent string counts as a string
+function(
+ "adjacent"
+ "string",
+ "another"
+ "adjacent");
+<<<
+function(
+ "adjacent"
+ "string",
+ "another"
+ "adjacent");
+>>> do not indent adjacent strings in assert if other args are not strings
+assert(
+ condition,
+ "adjacent"
+ "string");
+<<<
+assert(
+ condition,
+ "adjacent"
+ "string");
+>>> don't need extra indentation inside named arguments
+function(named: "adjacent"
+"string",
+another: "adjacent"
+"string"
+"more");
+<<<
+function(
+ named: "adjacent"
+ "string",
+ another: "adjacent"
+ "string"
+ "more");
+>>> don't need extra indentation inside named arguments
+function(named: "adjacent"
+"string",
+another: "adjacent"
+"string"
+"more",);
+<<<
+function(
+ named: "adjacent"
+ "string",
+ another: "adjacent"
+ "string"
+ "more",
+);
\ No newline at end of file