Language version all of the formatting changes since Dart 3.7. (#1707)
Language version all of the formatting changes since Dart 3.7.
The new formatter launch was pretty rocky, but one thing I think helped mitigate that was that the formatting style was gated on language version. Users could install the new SDK and their code wouldn't be spontaneously reformatted under them until they deliberately updated their SDK constraint to opt in to the new language version.
This PR applies that same logic to the changes we've made since Dart 3.7.
Any source file at language version 3.7 is formatted (almost, see below) identically to how it would be formatted with the SDK as it shipped in 3.7. Source files at language version 3.8 or higher get all of the new style changes I've landed since then.
This increases the complexity of the formatter codebase a decent amount, but it's mostly just a lot of "if 3.7 do X else do Y". Tedious but not intractable.
I expect to continue to language version changes like this going forward. So after Dart 3.8 ships, whatever formatter style is in that version will be locked down and style changes after that will have to support 3.7 and 3.8 fallback behavior. I expect there to be much less style churn going forward, so it's probably manageable.
In cases where there are *bugs* (i.e. the formatter produces invalid syntax or rearranges code), those won't be language versioned. But most other style changes will be.
It's important to make sure we don't regress and accidentally affect the formatting of older language versioned files when making changes to the formatter. To avoid that, this also expands testing to be multi-versioned. Every test is now run on multiple versions and for cases where the style differs between versions, there are different expectations for each. Fortunately, the tests run very fast, so it doesn't slow down things more than a couple of seconds.
In addition to running through the formatter's own test suite and regression tests, I also tested this on a giant corpus of pub packages. I formatted them all using the shipped Dart 3.7 formatter, then using this PR with `--language-version=3.7`. Of the 89,468 files, 7 had differences. They all relate to a single weird corner case around giant multiline strings containing multiline string interpolation expressions where the formatting is slightly different. #1706 helps that somewhat -- there were about dozen diffs before -- but doesn't totally eliminate it. I think it's close enough to be tolerable.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 60645b4..ea84a6d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,32 @@
## 3.1.0-wip
-* Format null-aware elements.
+### Features
+
+* Allow preserving trailing commas and forcing the surrounding construct to
+ split even when it would otherwise fit on one line. This is off by default
+ (because it breaks [reversibility][] among other reasons) but can be enabled
+ by adding this to a surrounding `analysis_options.yaml` file:
+
+ ```yaml
+ formatter:
+ trailing_commas: preserve
+ ```
+
+ This is similar to how trailing commas work in the old short style formatter
+ applied to code before language version 3.7.
+
+[reversibility]: https://github.com/dart-lang/dart_style/wiki/Reversibility-principle
+
+### Bug fixes
+
+* Don't add a trailing comma in lists that don't allow it, even when there is
+ a trailing comment (#1639).
+
+### Style changes
+
+The following style changes are language versioned and only affect code whose
+language version is 3.8 or later. Dart code at 3.7 or earlier is formatted the
+same as it was before.
* Allow more code on the same line as a named argument or `=>` (#1536, #1545,
#1668, #1679).
@@ -175,26 +201,6 @@
);
```
-* Allow preserving trailing commas and forcing the surrounding construct to
- split even when it would otherwise fit on one line. This is off by default
- (because it breaks [reversibility][] among other reasons) but can be enabled
- by adding this to a surrounding `analysis_options.yaml` file:
-
- ```yaml
- formatter:
- trailing_commas: preserve
- ```
-
- This is similar to how trailing commas worked in the old short style
- formatter.
-
-* Don't add a trailing comma in lists that don't allow it, even when there is
- a trailing comment (#1639).
-
-* Add tests for digit separators.
-
-[reversibility]: https://github.com/dart-lang/dart_style/wiki/Reversibility-principle
-
## 3.0.1
* Handle trailing commas in for-loop updaters (#1354).
diff --git a/example/format.dart b/example/format.dart
index e71c21d..af55908 100644
--- a/example/format.dart
+++ b/example/format.dart
@@ -5,6 +5,7 @@
import 'package:dart_style/dart_style.dart';
import 'package:dart_style/src/debug.dart' as debug;
import 'package:dart_style/src/testing/test_file.dart';
+import 'package:pub_semver/pub_semver.dart';
void main(List<String> args) {
// Enable debugging so you can see some of the formatter's internal state.
@@ -32,14 +33,14 @@
void _formatStmt(
String source, {
- bool tall = true,
+ Version? version,
int pageWidth = 40,
TrailingCommas trailingCommas = TrailingCommas.automate,
}) {
_runFormatter(
source,
pageWidth,
- tall: tall,
+ version: version ?? DartFormatter.latestLanguageVersion,
isCompilationUnit: false,
trailingCommas: trailingCommas,
);
@@ -47,14 +48,14 @@
void _formatUnit(
String source, {
- bool tall = true,
+ Version? version,
int pageWidth = 40,
TrailingCommas trailingCommas = TrailingCommas.automate,
}) {
_runFormatter(
source,
pageWidth,
- tall: tall,
+ version: version ?? DartFormatter.latestLanguageVersion,
isCompilationUnit: true,
trailingCommas: trailingCommas,
);
@@ -63,16 +64,13 @@
void _runFormatter(
String source,
int pageWidth, {
- required bool tall,
+ required Version version,
required bool isCompilationUnit,
TrailingCommas trailingCommas = TrailingCommas.automate,
}) {
try {
var formatter = DartFormatter(
- languageVersion:
- tall
- ? DartFormatter.latestLanguageVersion
- : DartFormatter.latestShortStyleLanguageVersion,
+ languageVersion: version,
pageWidth: pageWidth,
trailingCommas: trailingCommas,
);
@@ -110,7 +108,7 @@
var formatTest = testFile.tests.firstWhere((test) => test.line == line);
var formatter = testFile.formatterForTest(formatTest);
- var actual = formatter.formatSource(formatTest.input);
+ var actual = formatter.formatSource(formatTest.input.code);
// The test files always put a newline at the end of the expectation.
// Statements from the formatter (correctly) don't have that, so add
@@ -118,11 +116,12 @@
var actualText = actual.textWithSelectionMarkers;
if (!testFile.isCompilationUnit) actualText += '\n';
- var expectedText = formatTest.output.textWithSelectionMarkers;
+ // TODO(rnystrom): Handle multiple outputs.
+ var expectedText = formatTest.outputs.first.code.textWithSelectionMarkers;
- print('$path ${formatTest.description}');
+ print('$path ${formatTest.input.description}');
_drawRuler('before', pageWidth);
- print(formatTest.input.textWithSelectionMarkers);
+ print(formatTest.input.code.textWithSelectionMarkers);
if (actualText == expectedText) {
_drawRuler('result', pageWidth);
print(actualText);
diff --git a/lib/src/ast_extensions.dart b/lib/src/ast_extensions.dart
index cb8a289..1fabefa 100644
--- a/lib/src/ast_extensions.dart
+++ b/lib/src/ast_extensions.dart
@@ -420,15 +420,11 @@
/// To balance these, we omit the indentation in argument lists only if there
/// are no other string arguments.
bool get indentStrings {
- bool hasOtherStringArgument(List<Expression> arguments) => arguments.any(
- (argument) => argument != this && argument is StringLiteral,
- );
-
return switch (parent) {
- ArgumentList(:var arguments) => hasOtherStringArgument(arguments),
+ ArgumentList(:var arguments) => _hasOtherStringArgument(arguments),
// Treat asserts like argument lists.
- Assertion(:var condition, :var message) => hasOtherStringArgument([
+ Assertion(:var condition, :var message) => _hasOtherStringArgument([
condition,
if (message != null) message,
]),
@@ -436,6 +432,64 @@
_ => true,
};
}
+
+ /// Whether subsequent strings should be indented relative to the first
+ /// string (in 3.7 style).
+ ///
+ /// 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 strings are longer, it can be hard to tell that
+ /// "three" is a continuation of the previous element.
+ ///
+ /// However, the indentation is distracting in places that don't suffer from
+ /// this ambiguity:
+ ///
+ /// var description =
+ /// "A very long description..."
+ /// "this extra indentation is unnecessary.");
+ ///
+ /// To balance these, we omit the indentation when an adjacent string
+ /// expression is in a context where it's unlikely to be confusing.
+ bool get indentStringsV37 {
+ return switch (parent) {
+ ArgumentList(:var arguments) => _hasOtherStringArgument(arguments),
+
+ // Treat asserts like argument lists.
+ Assertion(:var condition, :var message) => _hasOtherStringArgument([
+ condition,
+ if (message != null) message,
+ ]),
+
+ // Don't add extra indentation in a variable initializer or assignment:
+ //
+ // var variable =
+ // "no extra"
+ // "indent";
+ VariableDeclaration() => false,
+ AssignmentExpression(:var rightHandSide) when rightHandSide == this =>
+ false,
+
+ // Don't indent when following `:`.
+ MapLiteralEntry(:var value) when value == this => false,
+ NamedExpression() => false,
+
+ // Don't indent when the body of a `=>` function.
+ ExpressionFunctionBody() => false,
+ _ => true,
+ };
+ }
+
+ bool _hasOtherStringArgument(List<Expression> arguments) => arguments.any(
+ (argument) => argument != this && argument is StringLiteral,
+ );
}
extension PatternExtensions on DartPattern {
@@ -445,7 +499,7 @@
/// Whether this expression is a non-empty delimited container for inner
/// expressions that allows "block-like" formatting in some contexts.
///
- /// See [ExpressionExtensions.canBlockSplit].
+ /// See [ExpressionExtensions38.canBlockSplit].
bool get canBlockSplit => switch (this) {
ConstantPattern(:var expression) => expression.canBlockSplit,
ListPattern(:var elements, :var rightBracket) => elements.canSplit(
diff --git a/lib/src/back_end/code.dart b/lib/src/back_end/code.dart
index a207d38..23c6fd5 100644
--- a/lib/src/back_end/code.dart
+++ b/lib/src/back_end/code.dart
@@ -135,7 +135,7 @@
/// A [Code] object for a newline followed by any leading indentation.
final class _NewlineCode extends Code {
- /// True if a blank line (two newlines) should be written.
+ /// Whether a blank line (two newlines) should be written.
final bool _blank;
/// The number of spaces of indentation after this newline.
diff --git a/lib/src/back_end/code_writer.dart b/lib/src/back_end/code_writer.dart
index 4f312fc..8a27445 100644
--- a/lib/src/back_end/code_writer.dart
+++ b/lib/src/back_end/code_writer.dart
@@ -154,31 +154,100 @@
/// Increases the indentation by [indent] relative to the current amount of
/// indentation.
void pushIndent(Indent indent) {
- var parent = _indentStack.last;
+ if (_cache.isVersion37) {
+ _pushIndentV37(indent);
+ } else {
+ var parent = _indentStack.last;
- // Combine the new indentation with the surrounding one.
- var offset = switch ((parent.type, indent)) {
- // On the right-hand side of `=`, `:`, or `=>`, don't indent subsequent
- // infix operands so that they all align:
- //
- // variable =
- // operand +
- // another;
- (Indent.assignment, Indent.infix) => 0,
+ // Combine the new indentation with the surrounding one.
+ var offset = switch ((parent.type, indent)) {
+ // On the right-hand side of `=`, `:`, or `=>`, don't indent subsequent
+ // infix operands so that they all align:
+ //
+ // variable =
+ // operand +
+ // another;
+ (Indent.assignment, Indent.infix) => 0,
- // We have already indented the control flow header, so collapse the
- // duplicate indentation.
- (Indent.controlFlowClause, Indent.expression) => 0,
- (Indent.controlFlowClause, Indent.infix) => 0,
+ // We have already indented the control flow header, so collapse the
+ // duplicate indentation.
+ (Indent.controlFlowClause, Indent.expression) => 0,
+ (Indent.controlFlowClause, Indent.infix) => 0,
- // If we get here, the parent context has no effect, so just apply the
- // indentation directly.
- (_, _) => indent.spaces,
- };
+ // If we get here, the parent context has no effect, so just apply the
+ // indentation directly.
+ (_, _) => indent.spaces,
+ };
- _indentStack.add(_IndentLevel(indent, parent.spaces + offset));
- if (debug.traceIndent) {
- debug.log('pushIndent: ${_indentStack.join(' ')}');
+ _indentStack.add(_IndentLevel(indent, parent.spaces + offset));
+ if (debug.traceIndent) {
+ debug.log('pushIndent: ${_indentStack.join(' ')}');
+ }
+ }
+ }
+
+ /// Increases the indentation in a control flow clause in a "collapsible" way.
+ ///
+ /// This is only used in a couple of corners of if-case and for-in headers
+ /// where the indentation is unusual.
+ void pushCollapsibleIndent() {
+ if (_cache.isVersion37) {
+ _pushIndentV37(Indent.expression, canCollapse: true);
+ } else {
+ pushIndent(Indent.controlFlowClause);
+ }
+ }
+
+ /// The 3.7 style of indentation and collapsible indentation tracking.
+ ///
+ /// Splits in if-case and for-in loop headers are tricky to indent gracefully.
+ /// For example, if an infix expression inside the case splits, we don't want
+ /// it to be double indented:
+ ///
+ /// if (object
+ /// case veryLongConstant
+ /// as VeryLongType) {
+ /// ;
+ /// }
+ ///
+ /// That suggests that the [IfCasePiece] shouldn't add indentation for the
+ /// case pattern since the [InfixPiece] inside it will already indent the RHS.
+ ///
+ /// But if the case is a variable pattern that splits, the [VariablePiece]
+ /// does *not* add indentation because in most other places where it occurs,
+ /// that's what we want. If the [IfCasePiece] doesn't indent the pattern, you
+ /// get:
+ ///
+ /// if (object
+ /// case VeryLongType
+ /// veryLongVariable
+ /// ) {
+ /// ;
+ /// }
+ ///
+ /// To deal with this, 3.7 had a notion of "collapsible" indentation. In 3.8
+ /// and later, there is a different mechanism for merging indentation kinds.
+ /// This function implements the former.
+ void _pushIndentV37(Indent indent, {bool canCollapse = false}) {
+ var parentIndent = _indentStack.last.spaces;
+ var parentCollapse = _indentStack.last.collapsible;
+
+ if (parentCollapse == indent.spaces) {
+ // We're indenting by the same existing collapsible amount, so collapse
+ // this new indentation with that existing one.
+ _indentStack.add(_IndentLevel.v37(parentIndent, 0));
+ } else if (canCollapse) {
+ // We should never get multiple levels of nested collapsible indentation.
+ assert(parentCollapse == 0);
+
+ // Increase the indentation and note that it can be collapsed with
+ // further indentation.
+ _indentStack.add(
+ _IndentLevel.v37(parentIndent + indent.spaces, indent.spaces),
+ );
+ } else {
+ // Regular indentation, so just increase the indent.
+ _indentStack.add(_IndentLevel.v37(parentIndent + indent.spaces, 0));
}
}
@@ -230,7 +299,7 @@
/// and multi-line strings.
void whitespace(Whitespace whitespace, {bool flushLeft = false}) {
if (whitespace case Whitespace.newline || Whitespace.blankLine) {
- _applyNewlineToShape();
+ _applyNewlineToShape(_pieceFormats.last);
_pendingIndent = flushLeft ? 0 : _indentStack.last.spaces;
}
@@ -261,11 +330,9 @@
void format(Piece piece, {bool separate = false}) {
if (separate) {
Profile.count('CodeWriter.format() piece separate');
-
_formatSeparate(piece);
} else {
Profile.count('CodeWriter.format() piece inline');
-
_formatInline(piece);
}
}
@@ -328,23 +395,30 @@
if (_pieceFormats.lastOrNull case var parent?) {
var allowedShapes = parent.piece.allowedChildShapes(
_solution.pieceState(parent.piece),
- piece,
+ child.piece,
);
- if (!allowedShapes.contains(child.shape)) {
- _solution.invalidate(parent.piece);
- if (debug.traceSolver) {
- debug.log(
- 'invalidate ${parent.piece} '
- '(${_solution.pieceState(parent.piece)}) '
- 'because child $piece (${_solution.pieceState(piece)}) '
- 'has ${child.shape} and allowed are $allowedShapes',
- );
- }
+ bool invalid;
+ if (_cache.isVersion37) {
+ // If the child must be inline, then invalidate because we know it
+ // contains some kind of newline.
+ // TODO(rnystrom): It would be better if this logic wasn't different for
+ // 3.7. The only place where the distinction between this code and the
+ // logic in the else clause comes into play is with CaseExpressionPiece.
+ invalid =
+ child.shape != Shape.inline &&
+ allowedShapes.length == 1 &&
+ allowedShapes.contains(Shape.inline);
+ } else {
+ invalid = !allowedShapes.contains(child.shape);
}
+ if (invalid) _solution.invalidate(parent.piece);
+
// If the child had newlines, propagate that to the parent's shape.
- if (child.shape != Shape.inline) _applyNewlineToShape(child.shape);
+ if (child.shape != Shape.inline) {
+ _applyNewlineToShape(parent, child.shape);
+ }
}
}
@@ -413,18 +487,17 @@
}
/// Determine how a newline affects the current piece's shape.
- void _applyNewlineToShape([Shape shape = Shape.other]) {
- var format = _pieceFormats.last;
- format.shape = switch (format.mode) {
- ShapeMode.merge => format.shape.merge(shape),
+ void _applyNewlineToShape(_FormatState state, [Shape shape = Shape.other]) {
+ state.shape = switch (state.mode) {
+ ShapeMode.merge => state.shape.merge(shape),
ShapeMode.block => Shape.block,
ShapeMode.beforeHeadline => Shape.other,
// If there were no newlines inside the headline, now that there is one,
// we have a headline shape.
- ShapeMode.afterHeadline when format.shape == Shape.inline =>
+ ShapeMode.afterHeadline when state.shape == Shape.inline =>
Shape.headline,
// If there was already a newline in the headline, preserve that shape.
- ShapeMode.afterHeadline => format.shape,
+ ShapeMode.afterHeadline => state.shape,
ShapeMode.other => Shape.other,
};
}
@@ -555,12 +628,21 @@
/// A level of indentation in the indentation stack.
final class _IndentLevel {
/// The reason this indentation was added.
+ ///
+ /// Not used for 3.7 style.
final Indent type;
/// The total number of spaces of indentation.
final int spaces;
- _IndentLevel(this.type, this.spaces);
+ /// How many spaces of [spaces] can be collapsed with further indentation.
+ ///
+ /// Only used for 3.7 style.
+ final int collapsible;
+
+ _IndentLevel.v37(this.spaces, this.collapsible) : type = Indent.none;
+
+ _IndentLevel(this.type, this.spaces) : collapsible = 0;
@override
String toString() => '${type.name}:$spaces';
diff --git a/lib/src/back_end/solution.dart b/lib/src/back_end/solution.dart
index 5d0780d..19708ed 100644
--- a/lib/src/back_end/solution.dart
+++ b/lib/src/back_end/solution.dart
@@ -121,13 +121,12 @@
required int subsequentIndent,
State? rootState,
}) {
- var solution = Solution._(cache, root, 0, {}, {}, rootState);
+ var solution = Solution._(root, 0, {}, {}, rootState);
solution._format(cache, root, pageWidth, leadingIndent, subsequentIndent);
return solution;
}
Solution._(
- SolutionCache cache,
Piece root,
this._cost,
this._pieceStates,
@@ -241,7 +240,6 @@
for (var state
in _allowedStates[expandPiece] ?? expandPiece.additionalStates) {
var expanded = Solution._(
- cache,
root,
_cost,
{..._pieceStates},
@@ -345,6 +343,7 @@
cache,
this,
);
+
writer.format(root);
var (code, expandPieces) = writer.finish();
diff --git a/lib/src/back_end/solution_cache.dart b/lib/src/back_end/solution_cache.dart
index 574baec..4c42100 100644
--- a/lib/src/back_end/solution_cache.dart
+++ b/lib/src/back_end/solution_cache.dart
@@ -23,8 +23,13 @@
/// indentation. When that happens, sharing this cache allows us to reuse that
/// cached subtree Solution.
final class SolutionCache {
+ /// Whether this cache and all solutions in it use the 3.7 style solver.
+ final bool isVersion37;
+
final _cache = <_Key, Solution>{};
+ SolutionCache({required bool version37}) : isVersion37 = version37;
+
/// Returns a previously cached solution for formatting [root] with leading
/// [indent] (and [subsequentIndent] for lines after the first) or produces a
/// new solution, caches it, and returns it.
diff --git a/lib/src/dart_formatter.dart b/lib/src/dart_formatter.dart
index 73bb8bf..0767103 100644
--- a/lib/src/dart_formatter.dart
+++ b/lib/src/dart_formatter.dart
@@ -34,7 +34,7 @@
final class DartFormatter {
/// The latest Dart language version that can be parsed and formatted by this
/// version of the formatter.
- static final latestLanguageVersion = Version(3, 7, 0);
+ static final latestLanguageVersion = Version(3, 8, 0);
/// The latest Dart language version that will be formatted using the older
/// "short" style.
diff --git a/lib/src/front_end/ast_node_visitor.dart b/lib/src/front_end/ast_node_visitor.dart
index 7f958ad..6f0d5ba 100644
--- a/lib/src/front_end/ast_node_visitor.dart
+++ b/lib/src/front_end/ast_node_visitor.dart
@@ -10,6 +10,7 @@
import '../back_end/code_writer.dart';
import '../dart_formatter.dart';
import '../piece/assign.dart';
+import '../piece/assign_v37.dart';
import '../piece/case.dart';
import '../piece/constructor.dart';
import '../piece/control_flow.dart';
@@ -60,7 +61,7 @@
SourceCode source,
) {
var comments = CommentWriter(lineInfo);
- var pieces = PieceWriter(source, comments);
+ var pieces = PieceWriter(formatter, source, comments);
return AstNodeVisitor._(formatter, pieces, comments);
}
@@ -142,6 +143,7 @@
formatter.lineEnding,
pageWidth: pageWidthFromComment ?? formatter.pageWidth,
leadingIndent: formatter.indent,
+ version37: isVersion37,
);
Profile.end('AstNodeVisitor.run()');
@@ -151,9 +153,11 @@
@override
void visitAdjacentStrings(AdjacentStrings node) {
+ var indent = isVersion37 ? node.indentStringsV37 : node.indentStrings;
var piece = InfixPiece(
node.strings.map(nodePiece).toList(),
- indent: node.indentStrings ? Indent.infix : Indent.none,
+ indent: indent ? Indent.infix : Indent.none,
+ version37: isVersion37,
);
// Adjacent strings always split.
@@ -183,12 +187,12 @@
node.expression,
node.asOperator,
node.type,
- // Don't use Indent.infix, which will flatten the indentation if the
- // expression occurs in an assignment.
+ // Don't use Indent.infix after 3.7 because it will flatten the
+ // indentation if the expression occurs in an assignment.
// TODO(rnystrom): We probably do want to format `as` the same as other
// binary operators, but the tall style already treats them differently,
// so leaving that alone for now.
- indent: Indent.expression,
+ indent: isVersion37 ? Indent.infix : Indent.expression,
);
}
@@ -228,9 +232,14 @@
@override
void visitBinaryExpression(BinaryExpression node) {
+ // In 3.7 style, flatten binary operands in assignment contexts. (In 3.8
+ // and later, indentation merging accomplishes the same thing.)
+ var indent = !isVersion37 || _parentContext != NodeContext.assignment;
+
writeInfixChain<BinaryExpression>(
node,
precedence: node.operator.type.precedence,
+ indent: indent,
(expression) => (
expression.leftOperand,
expression.operator,
@@ -367,9 +376,11 @@
var operands = [nodePiece(node.condition)];
void addOperand(Token operator, Expression operand) {
- // If there are comments before a branch, then hoist them so they aren't
- // indented with the branch body.
- operands.addAll(pieces.takeCommentsBefore(operator));
+ if (!isVersion37) {
+ // If there are comments before a branch, then hoist them so they aren't
+ // indented with the branch body.
+ operands.addAll(pieces.takeCommentsBefore(operator));
+ }
operands.add(
pieces.build(() {
@@ -394,8 +405,7 @@
}
}
- // Don't redundantly indent a split conditional in an assignment context.
- var piece = InfixPiece(operands, conditional: true);
+ var piece = InfixPiece.conditional(operands, version37: isVersion37);
// If conditional expressions are directly nested, force them all to split,
// both parents and children.
@@ -417,7 +427,7 @@
if (node.equalToken case var equals?) {
// Hoist comments so that they don't force the `==` to split.
pieces.hoistLeadingComments(node.name.firstNonCommentToken, () {
- return InfixPiece([
+ return InfixPiece(version37: isVersion37, [
pieces.build(() {
pieces.visit(node.name);
pieces.space();
@@ -437,11 +447,15 @@
@override
void visitConstantPattern(ConstantPattern node) {
- if (node.constKeyword case var constKeyword?) {
- pieces.token(constKeyword, spaceAfter: true);
- pieces.visit(node.expression);
+ if (isVersion37) {
+ writePrefix(node.constKeyword, space: true, node.expression);
} else {
- pieces.visit(node.expression);
+ if (node.constKeyword case var constKeyword?) {
+ pieces.token(constKeyword, spaceAfter: true);
+ pieces.visit(node.expression);
+ } else {
+ pieces.visit(node.expression);
+ }
}
}
@@ -468,7 +482,15 @@
pieces.space();
});
- redirect = AssignPiece(separator, nodePiece(constructor));
+ if (isVersion37) {
+ redirect = AssignPieceV37(
+ separator,
+ nodePiece(constructor, context: NodeContext.assignment),
+ canBlockSplitRight: false,
+ );
+ } else {
+ redirect = AssignPiece(separator, nodePiece(constructor));
+ }
} else if (node.initializers.isNotEmpty) {
initializerSeparator = tokenPiece(node.separator!);
initializers = createCommaSeparated(node.initializers);
@@ -667,30 +689,47 @@
pieces.token(node.functionDefinition);
});
- var expression = nodePiece(node.expression);
- var assignPiece = AssignPiece(
- operatorPiece,
- expression,
- // Prefer splitting at `=>` and keeping the expression together unless
- // it's a collection literal.
- avoidSplit: node.expression.isHomogeneousCollectionBody,
+ var expression = nodePiece(
+ node.expression,
+ context: NodeContext.assignment,
);
- // If a `=>` is directly nested inside another, force the outer one to
- // split. This is for performance reasons. A series of nested AssignPieces
- // can have combinatorial performance otherwise.
- // TODO(rnystrom): Figure out a better way to handle this. We could possibly
- // collapse a series of curried function expressions into a single piece
- // similar to how we handle nested conditional expressions. In practice,
- // outside of a few libraries that lean heavily on currying, this is very
- // rare.
- if (node.expression case FunctionExpression(
- body: ExpressionFunctionBody(),
- )) {
- assignPiece.pin(State.split);
+ if (isVersion37) {
+ pieces.add(
+ AssignPieceV37(
+ operatorPiece,
+ expression,
+ canBlockSplitRight: node.expression.canBlockSplit,
+ avoidBlockSplitRight:
+ node.expression.blockFormatType == BlockFormat.invocation,
+ ),
+ );
+ } else {
+ var assignPiece = AssignPiece(
+ operatorPiece,
+ expression,
+ // Prefer splitting at `=>` and keeping the expression together unless
+ // it's a collection literal.
+ avoidSplit: node.expression.isHomogeneousCollectionBody,
+ );
+
+ // If a `=>` is directly nested inside another, force the outer one to
+ // split. This is for performance reasons. A series of nested AssignPieces
+ // can have combinatorial performance otherwise.
+ // TODO(rnystrom): Figure out a better way to handle this. We could
+ // possibly collapse a series of curried function expressions into a
+ // single piece similar to how we handle nested conditional expressions.
+ // In practice, outside of a few libraries that lean heavily on currying,
+ // this is very rare.
+ if (node.expression case FunctionExpression(
+ body: ExpressionFunctionBody(),
+ )) {
+ assignPiece.pin(State.split);
+ }
+
+ pieces.add(assignPiece);
}
- pieces.add(assignPiece);
pieces.token(node.semicolon);
}
@@ -978,7 +1017,13 @@
pieces.token(node.name);
pieces.visit(node.typeParameters);
pieces.space();
- pieces.add(AssignPiece(tokenPiece(node.equals), nodePiece(node.type)));
+ if (isVersion37) {
+ pieces.add(
+ AssignPieceV37(tokenPiece(node.equals), nodePiece(node.type)),
+ );
+ } else {
+ pieces.add(AssignPiece(tokenPiece(node.equals), nodePiece(node.type)));
+ }
pieces.token(node.semicolon);
});
}
@@ -1224,19 +1269,23 @@
traverse(piece);
- // Wrap in grouping so that an infix split inside the interpolation doesn't
- // get collapsed in the surrounding context, like:
- //
- // // Wrong:
- // var x =
- // '${"a"
- // "b"}';
- //
- // // Right:
- // var x =
- // '${"a"
- // "b"}';
- pieces.add(GroupingPiece(piece));
+ if (isVersion37) {
+ pieces.add(piece);
+ } else {
+ // Wrap in grouping so that an infix split inside the interpolation
+ // doesn't get collapsed in the surrounding context, like:
+ //
+ // // Wrong:
+ // var x =
+ // '${"a"
+ // "b"}';
+ //
+ // // Right:
+ // var x =
+ // '${"a"
+ // "b"}';
+ pieces.add(GroupingPiece(piece));
+ }
}
@override
@@ -1255,12 +1304,12 @@
node.isOperator,
operator2: node.notOperator,
node.type,
- // Don't use Indent.infix, which will flatten the indentation if the
- // expression occurs in an assignment.
+ // Don't use Indent.infix after 3.7 because it will flatten the
+ // indentation if the expression occurs in an assignment.
// TODO(rnystrom): We probably do want to format `as` the same as other
// binary operators, but the tall style already treats them differently,
// so leaving that alone for now.
- indent: Indent.expression,
+ indent: isVersion37 ? Indent.infix : Indent.expression,
);
}
@@ -1320,9 +1369,23 @@
@override
void visitLogicalAndPattern(LogicalAndPattern node) {
+ var indent = true;
+ if (isVersion37) {
+ // If a logical and pattern occurs inside a map pattern entry, we want to
+ // format the operands in parallel, like:
+ //
+ // var {
+ // key:
+ // operand1 &&
+ // operand2,
+ // } = value;
+ indent = _parentContext != NodeContext.assignment;
+ }
+
writeInfixChain<LogicalAndPattern>(
node,
precedence: node.operator.type.precedence,
+ indent: indent,
(expression) => (
expression.leftOperand,
expression.operator,
@@ -1333,7 +1396,7 @@
@override
void visitLogicalOrPattern(LogicalOrPattern node) {
- // If a logical and pattern is the outermost pattern in a switch expression
+ // If a logical or pattern is the outermost pattern in a switch expression
// case, we flatten the operands like parallel cases:
//
// e = switch (obj) {
@@ -1342,6 +1405,18 @@
// };
var indent = _parentContext != NodeContext.switchExpressionCase;
+ if (isVersion37) {
+ // If a logical or pattern occurs inside a map pattern entry, we want to
+ // format the operands in parallel, like:
+ //
+ // var {
+ // key:
+ // operand1 ||
+ // operand2,
+ // } = value;
+ if (_parentContext == NodeContext.assignment) indent = false;
+ }
+
writeInfixChain<LogicalOrPattern>(
node,
precedence: node.operator.type.precedence,
@@ -1356,18 +1431,22 @@
@override
void visitMapLiteralEntry(MapLiteralEntry node) {
- var leftPiece = pieces.build(() {
- pieces.token(node.keyQuestion);
- pieces.visit(node.key);
- pieces.token(node.separator);
- });
+ if (isVersion37) {
+ writeAssignment(node.key, node.separator, node.value);
+ } else {
+ var leftPiece = pieces.build(() {
+ pieces.token(node.keyQuestion);
+ pieces.visit(node.key);
+ pieces.token(node.separator);
+ });
- var rightPiece = pieces.build(() {
- pieces.token(node.valueQuestion);
- pieces.visit(node.value);
- });
+ var rightPiece = pieces.build(() {
+ pieces.token(node.valueQuestion);
+ pieces.visit(node.value);
+ });
- pieces.add(AssignPiece(leftPiece, rightPiece));
+ pieces.add(AssignPiece(leftPiece, rightPiece));
+ }
}
@override
@@ -1939,6 +2018,7 @@
bodyPiece,
canBlockSplitPattern: node.guardedPattern.pattern.canBlockSplit,
patternIsLogicalOr: node.guardedPattern.pattern is LogicalOrPattern,
+ canBlockSplitBody: !isVersion37 || node.expression.canBlockSplit,
),
);
}
@@ -1976,7 +2056,12 @@
var patternPiece = nodePiece(member.guardedPattern.pattern);
if (member.guardedPattern.whenClause case var whenClause?) {
- pieces.add(InfixPiece([patternPiece, nodePiece(whenClause)]));
+ pieces.add(
+ InfixPiece([
+ patternPiece,
+ nodePiece(whenClause),
+ ], version37: isVersion37),
+ );
} else {
pieces.add(patternPiece);
}
@@ -2093,27 +2178,57 @@
});
var variables = <Piece>[];
+
for (var variable in node.variables) {
if ((variable.equals, variable.initializer) case (
var equals?,
var initializer?,
)) {
- var variablePiece = pieces.build(() {
- pieces.token(variable.name);
- pieces.space();
- pieces.token(equals);
- });
+ if (isVersion37) {
+ var variablePiece = tokenPiece(variable.name);
- var initializerPiece = nodePiece(initializer, commaAfter: true);
+ var equalsPiece = pieces.build(() {
+ pieces.space();
+ pieces.token(equals);
+ });
- variables.add(AssignPiece(variablePiece, initializerPiece));
+ var initializerPiece = nodePiece(
+ initializer,
+ commaAfter: true,
+ context: NodeContext.assignment,
+ );
+
+ variables.add(
+ AssignPieceV37(
+ left: variablePiece,
+ equalsPiece,
+ initializerPiece,
+ canBlockSplitRight: initializer.canBlockSplit,
+ ),
+ );
+ } else {
+ var variablePiece = pieces.build(() {
+ pieces.token(variable.name);
+ pieces.space();
+ pieces.token(equals);
+ });
+
+ var initializerPiece = nodePiece(initializer, commaAfter: true);
+
+ variables.add(AssignPiece(variablePiece, initializerPiece));
+ }
} else {
variables.add(tokenPiece(variable.name, commaAfter: true));
}
}
pieces.add(
- VariablePiece(header, variables, hasType: node.type != null),
+ VariablePiece(
+ header,
+ variables,
+ hasType: node.type != null,
+ version37: isVersion37,
+ ),
);
},
);
diff --git a/lib/src/front_end/chain_builder.dart b/lib/src/front_end/chain_builder.dart
index 413a2ea..91ac3a2 100644
--- a/lib/src/front_end/chain_builder.dart
+++ b/lib/src/front_end/chain_builder.dart
@@ -55,6 +55,18 @@
/// The left-most target of the chain.
late Piece _target;
+ /// Whether the target expression may contain newlines when the chain is not
+ /// fully split. (It may always contain newlines when the chain splits.)
+ ///
+ /// This is true for most expressions but false for delimited ones to avoid
+ /// ugly formatting like:
+ ///
+ /// function(
+ /// argument,
+ /// )
+ /// .method();
+ late final bool _allowSplitInTarget;
+
/// The dotted property accesses and method calls following the target.
final List<ChainCall> _calls = [];
@@ -119,6 +131,8 @@
cascade: true,
indent: Indent.cascade,
blockCallIndex: blockCallIndex,
+ allowSplitInTarget: _allowSplitInTarget,
+ version37: _visitor.isVersion37,
);
if (!(_root as CascadeExpression).allowInline) chain.pin(State.split);
@@ -219,6 +233,8 @@
indent: isCascadeTarget ? Indent.cascade : Indent.expression,
leadingProperties: leadingProperties,
blockCallIndex: blockCallIndex,
+ allowSplitInTarget: _allowSplitInTarget,
+ version37: _visitor.isVersion37,
);
}
@@ -340,6 +356,7 @@
/// If [cascadeTarget] is `true`, then this is the target of a cascade
/// expression. Otherwise, it's the target of a call chain.
void _visitTarget(Expression target, {bool cascadeTarget = false}) {
+ _allowSplitInTarget = target.canBlockSplit;
_target = _visitor.nodePiece(
target,
context: cascadeTarget ? NodeContext.cascadeTarget : NodeContext.none,
diff --git a/lib/src/front_end/expression_contents.dart b/lib/src/front_end/expression_contents.dart
index 60a1938..bf7f778 100644
--- a/lib/src/front_end/expression_contents.dart
+++ b/lib/src/front_end/expression_contents.dart
@@ -97,7 +97,7 @@
}
/// Begin tracking a collection literal and its contents.
- void beginCollection({required bool isNamed}) {
+ void beginCollection({bool isNamed = false}) {
_stack.last.collections++;
_stack.add(_Contents(isNamed ? _Type.namedCollection : _Type.collection));
}
diff --git a/lib/src/front_end/piece_factory.dart b/lib/src/front_end/piece_factory.dart
index c9e5b38..ea0ad0c 100644
--- a/lib/src/front_end/piece_factory.dart
+++ b/lib/src/front_end/piece_factory.dart
@@ -3,12 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
+import 'package:pub_semver/pub_semver.dart';
import '../ast_extensions.dart';
import '../back_end/code_writer.dart';
import '../dart_formatter.dart';
import '../piece/adjacent.dart';
import '../piece/assign.dart';
+import '../piece/assign_v37.dart';
import '../piece/clause.dart';
import '../piece/control_flow.dart';
import '../piece/for.dart';
@@ -21,7 +23,6 @@
import '../piece/sequence.dart';
import '../piece/type.dart';
import '../piece/variable.dart';
-import 'ast_node_visitor.dart';
import 'chain_builder.dart';
import 'comment_writer.dart';
import 'delimited_list_builder.dart';
@@ -57,6 +58,12 @@
/// No specified context.
none,
+ /// The child is the right-hand side of an assignment-like form.
+ ///
+ /// This includes assignments, variable declarations, named arguments, map
+ /// entries, and `=>` function bodies.
+ assignment,
+
/// The child is the target of a cascade expression.
cascadeTarget,
@@ -108,6 +115,9 @@
NodeContext get parentContext;
+ /// Whether the code being formatted is at language version 3.7.
+ bool get isVersion37 => formatter.languageVersion == Version(3, 7, 0);
+
void visitNode(AstNode node, NodeContext context);
/// Writes a [ListPiece] for an argument list.
@@ -125,6 +135,17 @@
List<Expression> arguments,
Token rightBracket,
) {
+ // In 3.7, we don't support preserving trailing commas or eager splitting.
+ if (isVersion37) {
+ writeList(
+ leftBracket: leftBracket,
+ arguments,
+ rightBracket: rightBracket,
+ allowBlockArgument: true,
+ );
+ return;
+ }
+
// If the argument list is completely empty, write the brackets inline so
// we create fewer pieces.
if (!arguments.canSplit(rightBracket)) {
@@ -303,6 +324,7 @@
return;
}
+ // Add this collection to the stack.
if (splitEagerly) {
_contents.beginCollection(
isNamed: parentContext == NodeContext.namedExpression,
@@ -501,6 +523,7 @@
// Create a nested list builder for the updaters so that they can
// remain unsplit even while the clauses split.
var updaterBuilder = DelimitedListBuilder(this, style);
+
forParts.updaters.forEach(updaterBuilder.visit);
// Add the updater builder to the clause builder so that any comments
@@ -754,7 +777,12 @@
writeFunction();
});
- return VariablePiece(returnTypePiece, [signature], hasType: true);
+ return VariablePiece(
+ returnTypePiece,
+ [signature],
+ hasType: true,
+ version37: isVersion37,
+ );
});
}
@@ -772,16 +800,28 @@
}
var (separator, value) = defaultValue;
- var leftPiece = pieces.build(() {
- pieces.add(parameter);
+
+ var operatorPiece = pieces.build(() {
+ if (!isVersion37) pieces.add(parameter);
if (separator.type == TokenType.EQ) pieces.space();
pieces.token(separator);
if (separator.type != TokenType.EQ) pieces.space();
});
- var valuePiece = nodePiece(value);
+ var valuePiece = nodePiece(value, context: NodeContext.assignment);
- pieces.add(AssignPiece(leftPiece, valuePiece));
+ if (isVersion37) {
+ pieces.add(
+ AssignPieceV37(
+ left: parameter,
+ operatorPiece,
+ valuePiece,
+ canBlockSplitRight: value.canBlockSplit,
+ ),
+ );
+ } else {
+ pieces.add(AssignPiece(operatorPiece, valuePiece));
+ }
}
/// Writes a function type or function-typed formal.
@@ -1022,7 +1062,7 @@
tokenPiece(combinatorNode.keyword),
for (var name in names)
tokenPiece(name.token, commaAfter: true),
- ]),
+ ], version37: isVersion37),
);
}
}
@@ -1051,17 +1091,23 @@
pieces.token(index.question);
pieces.token(index.period);
- // Wrap the index expression in a [GroupingPiece] so that a split inside
- // the index doesn't cause the surrounding piece to have a certain shape.
- pieces.add(
- GroupingPiece(
- pieces.build(() {
- pieces.token(index.leftBracket);
- pieces.visit(index.index);
- pieces.token(index.rightBracket);
- }),
- ),
- );
+ if (isVersion37) {
+ pieces.token(index.leftBracket);
+ pieces.visit(index.index);
+ pieces.token(index.rightBracket);
+ } else {
+ // Wrap the index expression in a [GroupingPiece] so that a split inside
+ // the index doesn't cause the surrounding piece to have a certain shape.
+ pieces.add(
+ GroupingPiece(
+ pieces.build(() {
+ pieces.token(index.leftBracket);
+ pieces.visit(index.index);
+ pieces.token(index.rightBracket);
+ }),
+ ),
+ );
+ }
}
/// Writes a single infix operation.
@@ -1099,7 +1145,13 @@
pieces.visit(right);
});
- pieces.add(InfixPiece([leftPiece, rightPiece], indent: indent));
+ pieces.add(
+ InfixPiece(
+ [leftPiece, rightPiece],
+ indent: indent,
+ version37: isVersion37,
+ ),
+ );
}
/// Writes a chained infix operation: a binary operator expression, or
@@ -1155,7 +1207,11 @@
);
pieces.add(
- InfixPiece(operands, indent: indent ? Indent.infix : Indent.none),
+ InfixPiece(
+ operands,
+ indent: indent ? Indent.infix : Indent.none,
+ version37: isVersion37,
+ ),
);
}
@@ -1288,7 +1344,12 @@
});
pieces.add(
- VariablePiece(header, [tokenPiece(name)], hasType: type != null),
+ VariablePiece(
+ header,
+ [tokenPiece(name)],
+ hasType: type != null,
+ version37: isVersion37,
+ ),
);
}
@@ -1302,7 +1363,13 @@
///
/// If [space] is `true` and there is an operator, writes a space between the
/// operator and operand.
- void writePrefix(Token operator, AstNode? operand, {bool space = false}) {
+ void writePrefix(Token? operator, AstNode? operand, {bool space = false}) {
+ if (isVersion37) {
+ pieces.token(operator, spaceAfter: space);
+ pieces.visit(operand);
+ return;
+ }
+
// Wrap in grouping so that an infix split inside the prefix operator
// doesn't get collapsed in the surrounding context, like:
//
@@ -1451,7 +1518,7 @@
InfixPiece([
tokenPiece(keyword),
for (var type in types) nodePiece(type, commaAfter: true),
- ]),
+ ], version37: isVersion37),
);
}
@@ -1539,6 +1606,17 @@
NodeContext leftHandSideContext = NodeContext.none,
NodeContext rightHandSideContext = NodeContext.none,
}) {
+ if (isVersion37) {
+ _writeAssignmentV37(
+ leftHandSide,
+ operator,
+ rightHandSide,
+ includeComma: includeComma,
+ leftHandSideContext: leftHandSideContext,
+ );
+ return;
+ }
+
var leftPiece = pieces.build(() {
pieces.visit(leftHandSide, context: leftHandSideContext);
if (operator.type != TokenType.COLON) pieces.space();
@@ -1565,7 +1643,12 @@
context: NodeContext.forLoopVariable,
);
var sequencePiece = _createForInSequence(inKeyword, sequence);
- return ForInPiece(leftPiece, sequencePiece);
+ return ForInPiece(
+ leftPiece,
+ sequencePiece,
+ canBlockSplitSequence: sequence.canBlockSplit,
+ version37: isVersion37,
+ );
});
}
@@ -1599,7 +1682,14 @@
var sequencePiece = _createForInSequence(inKeyword, sequence);
- pieces.add(ForInPiece(leftPiece, sequencePiece));
+ pieces.add(
+ ForInPiece(
+ leftPiece,
+ sequencePiece,
+ canBlockSplitSequence: sequence.canBlockSplit,
+ version37: isVersion37,
+ ),
+ );
},
);
});
@@ -1615,8 +1705,83 @@
});
}
+ void _writeAssignmentV37(
+ AstNode leftHandSide,
+ Token operator,
+ AstNode rightHandSide, {
+ bool includeComma = false,
+ NodeContext leftHandSideContext = NodeContext.none,
+ }) {
+ // If an operand can have block formatting, then a newline in it doesn't
+ // force the operator to split, as in:
+ //
+ // var [
+ // element,
+ // ] = list;
+ //
+ // Or:
+ //
+ // var list = [
+ // element,
+ // ];
+ var canBlockSplitLeft = switch (leftHandSide) {
+ // Treat method chains and cascades on the LHS as if they were blocks.
+ // They don't really fit the "block" term, but it looks much better to
+ // force a method chain to split on the left than to try to avoid
+ // splitting it and split at the assignment instead:
+ //
+ // // Worse:
+ // target.method(
+ // argument,
+ // ).setter =
+ // value;
+ //
+ // // Better:
+ // target.method(argument)
+ // .setter = value;
+ //
+ MethodInvocation() => true,
+ PropertyAccess() => true,
+ PrefixedIdentifier() => true,
+
+ // Otherwise, it must be an actual block construct.
+ Expression() => leftHandSide.canBlockSplit,
+ DartPattern() => leftHandSide.canBlockSplit,
+ _ => false,
+ };
+
+ var canBlockSplitRight = switch (rightHandSide) {
+ Expression() => rightHandSide.canBlockSplit,
+ DartPattern() => rightHandSide.canBlockSplit,
+ _ => false,
+ };
+
+ var leftPiece = nodePiece(leftHandSide, context: leftHandSideContext);
+
+ var operatorPiece = pieces.build(() {
+ if (operator.type != TokenType.COLON) pieces.space();
+ pieces.token(operator);
+ });
+
+ var rightPiece = nodePiece(
+ rightHandSide,
+ commaAfter: includeComma,
+ context: NodeContext.assignment,
+ );
+
+ pieces.add(
+ AssignPieceV37(
+ left: leftPiece,
+ operatorPiece,
+ rightPiece,
+ canBlockSplitLeft: canBlockSplitLeft,
+ canBlockSplitRight: canBlockSplitRight,
+ ),
+ );
+ }
+
/// Whether there is a trailing comma at the end of the list delimited by
- /// [rightBracket].
+ /// [rightBracket] which should be preserved.
bool hasPreservedTrailingComma(Token rightBracket) =>
formatter.trailingCommas == TrailingCommas.preserve &&
rightBracket.hasCommaBefore;
@@ -1669,7 +1834,12 @@
Piece parameterPiece;
if (typePiece != null && namePiece != null) {
// We have both a type and name, allow splitting between them.
- parameterPiece = VariablePiece(typePiece, [namePiece], hasType: true);
+ parameterPiece = VariablePiece(
+ typePiece,
+ [namePiece],
+ hasType: true,
+ version37: isVersion37,
+ );
} else {
// Will have at least a type or name.
parameterPiece = typePiece ?? namePiece!;
diff --git a/lib/src/front_end/piece_writer.dart b/lib/src/front_end/piece_writer.dart
index 24a756a..eb92967 100644
--- a/lib/src/front_end/piece_writer.dart
+++ b/lib/src/front_end/piece_writer.dart
@@ -7,6 +7,7 @@
import '../back_end/code_writer.dart';
import '../back_end/solution_cache.dart';
import '../back_end/solver.dart';
+import '../dart_formatter.dart';
import '../debug.dart' as debug;
import '../piece/adjacent.dart';
import '../piece/leading_comment.dart';
@@ -25,6 +26,8 @@
/// Handles updating selection markers and attaching comments to the tokens
/// before and after the comments.
final class PieceWriter {
+ final DartFormatter _formatter;
+
final SourceCode _source;
final CommentWriter _comments;
@@ -74,7 +77,7 @@
/// previous code or adding a [SpacePiece] yet.
bool _pendingSpace = false;
- PieceWriter(this._source, this._comments);
+ PieceWriter(this._formatter, this._source, this._comments);
/// Wires the [PieceWriter] to the [AstNodeVisitor] (which implements
/// [PieceFactory]) so that [PieceWriter] can visit nodes.
@@ -455,6 +458,7 @@
String? lineEnding, {
required int pageWidth,
required int leadingIndent,
+ required bool version37,
}) {
if (debug.tracePieceBuilder) {
debug.log(debug.pieceTree(rootPiece));
@@ -462,12 +466,13 @@
Profile.begin('PieceWriter.finish() format piece tree');
- var cache = SolutionCache();
+ var cache = SolutionCache(version37: version37);
var solver = Solver(
cache,
pageWidth: pageWidth,
- leadingIndent: leadingIndent,
+ leadingIndent: _formatter.indent,
);
+
var solution = solver.format(rootPiece);
var output = solution.code.build(source, lineEnding);
diff --git a/lib/src/piece/assign_v37.dart b/lib/src/piece/assign_v37.dart
new file mode 100644
index 0000000..3940041
--- /dev/null
+++ b/lib/src/piece/assign_v37.dart
@@ -0,0 +1,255 @@
+// Copyright (c) 2025, 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 '../back_end/code_writer.dart';
+import 'piece.dart';
+
+/// A piece for the 3.7 style of an assignment-like construct where an operator
+/// is followed by an expression but where the left side of the operator isn't
+/// also an expression.
+///
+/// Used for:
+///
+/// - Assignment (`=`, `+=`, etc.)
+/// - Named arguments (`:`)
+/// - Map entries (`:`)
+/// - Record fields (`:`)
+/// - Expression function bodies (`=>`)
+///
+/// These constructs can be formatted four ways:
+///
+/// [State.unsplit] No split at all:
+///
+/// var x = 123;
+///
+/// This state also allows splitting the right side if it can be block
+/// formatted:
+///
+/// var list = [
+/// element,
+/// ];
+///
+/// [_blockSplitLeft] Force the left-hand side, which must be a [ListPiece], to
+/// split. Allow the right side to split or not. Allows all of:
+///
+/// var [
+/// element,
+/// ] = unsplitRhs;
+///
+/// var [
+/// element,
+/// ] = [
+/// 'block split RHS',
+/// ];
+///
+/// var [
+/// element,
+/// ] = 'expression split' +
+/// 'the right hand side';
+///
+/// [_blockSplitRight] Allow the right-hand side to block split or not, if it
+/// wants. Since [State.unsplit] and [_blockSplitLeft] also allow the
+/// right-hand side to block split, this state is only used when the left-hand
+/// side expression splits, like:
+///
+/// var (variable &&
+/// anotherVariable) = [
+/// element,
+/// ];
+///
+/// [_atOperator] Split at the `=` or `in` operator and allow expression
+/// splitting in either operand. Allows all of:
+///
+/// var (longVariable &&
+/// anotherVariable) =
+/// longOperand +
+/// anotherOperand;
+///
+/// var [unsplitBlock] =
+/// longOperand +
+/// anotherOperand;
+final class AssignPieceV37 extends Piece {
+ /// Force the block left-hand side to split and allow the right-hand side to
+ /// split.
+ static const State _blockSplitLeft = State(1);
+
+ /// Allow the right-hand side to block split.
+ static const State _blockSplitRight = State(2, cost: 0);
+
+ /// Split at the operator.
+ static const State _atOperator = State(3);
+
+ /// The left-hand side of the operation.
+ final Piece? _left;
+
+ // TODO(rnystrom): If it wasn't for the need to constrain [_left] to split
+ // in [applyConstraints()], we could write the operator into the same piece
+ // as [_left]. In the common case where the AssignPiece is for a named
+ // argument, the name and `:` would then end up in a single atomic
+ // [CodePiece].
+
+ /// The `=` or other operator.
+ final Piece _operator;
+
+ /// The right-hand side of the operation.
+ final Piece _right;
+
+ /// If `true`, then the left side supports being block-formatted, like:
+ ///
+ /// var [
+ /// element1,
+ /// element2,
+ /// ] = value;
+ final bool _canBlockSplitLeft;
+
+ /// If `true` then the right side supports being block-formatted, like:
+ ///
+ /// var list = [
+ /// element1,
+ /// element2,
+ /// ];
+ final bool _canBlockSplitRight;
+
+ /// If `true` then prefer to split at the operator instead of block splitting
+ /// the right side.
+ ///
+ /// This is `true` for `=>` functions whose body is a function call. This
+ /// keeps the called function next to its arguments instead having the
+ /// function name stick to the `=>` while the arguments split. In other words,
+ /// prefer:
+ ///
+ /// someMethod() =>
+ /// someFunction(argument, another);
+ ///
+ /// Over:
+ ///
+ /// someMethod() => someFunction(
+ /// argument,
+ /// another,
+ /// );
+ final bool _avoidBlockSplitRight;
+
+ AssignPieceV37(
+ this._operator,
+ this._right, {
+ Piece? left,
+ bool canBlockSplitLeft = false,
+ bool canBlockSplitRight = false,
+ bool avoidBlockSplitRight = false,
+ }) : _left = left,
+ _canBlockSplitLeft = canBlockSplitLeft,
+ _canBlockSplitRight = canBlockSplitRight,
+ _avoidBlockSplitRight = avoidBlockSplitRight;
+
+ @override
+ List<State> get additionalStates => [
+ // If at least one operand can block split, allow splitting in operands
+ // without splitting at the operator.
+ if (_canBlockSplitLeft) _blockSplitLeft,
+ if (_canBlockSplitRight) _blockSplitRight,
+ _atOperator,
+ ];
+
+ @override
+ int stateCost(State state) {
+ // Allow block splitting the right side, but increase the cost so that we
+ // prefer splitting at the operator and not splitting in the right piece if
+ // possible.
+ if (state == _blockSplitRight && _avoidBlockSplitRight) return 1;
+
+ return super.stateCost(state);
+ }
+
+ @override
+ void applyConstraints(State state, Constrain constrain) {
+ // Force the left side to block split when in that state.
+ //
+ // Otherwise, the solver may instead leave it unsplit and then split the
+ // right side incorrectly as in:
+ //
+ // (x, y) = longOperand2 +
+ // longOperand2 +
+ // longOperand3;
+ if (state == _blockSplitLeft) constrain(_left!, State.split);
+ }
+
+ @override
+ Set<Shape> allowedChildShapes(State state, Piece child) =>
+ Shape.anyIf(state != State.unsplit);
+
+ @override
+ void format(CodeWriter writer, State state) {
+ // When splitting at the operator, both operands may split or not and
+ // will be indented if they do.
+ if (state == _atOperator) writer.pushIndent(Indent.expression);
+
+ if (_left case var left?) writer.format(left);
+
+ writer.pushIndent(Indent.expression);
+ writer.format(_operator);
+ writer.popIndent();
+ writer.splitIf(state == _atOperator);
+
+ // If the left side block splits and the right doesn't, then indent the
+ // right side if it splits as in:
+ //
+ // var [
+ // a,
+ // b,
+ // ] = long +
+ // expression;
+ var indentRight = state == _blockSplitLeft && !_canBlockSplitRight;
+ if (indentRight) writer.pushIndent(Indent.expression);
+ writer.format(_right);
+ if (indentRight) writer.popIndent();
+
+ if (state == _atOperator) writer.popIndent();
+ }
+
+ @override
+ void forEachChild(void Function(Piece piece) callback) {
+ if (_left case var left?) callback(left);
+ callback(_operator);
+ callback(_right);
+ }
+
+ @override
+ State? fixedStateForPageWidth(int pageWidth) {
+ // If either side (or both) can block split, then they may allow a long
+ // assignment to still not end up splitting at the operator.
+ if (_canBlockSplitLeft || _canBlockSplitRight) return null;
+
+ // Edge case: If the left operand is only a single character, then splitting
+ // at the operator won't actually make the line any smaller, so don't apply
+ // the optimization in that case:
+ //
+ // e = someVeryLongExpression;
+ //
+ // Is no worse than:
+ //
+ // e =
+ // someVeryLongExpression;
+ if (_left case var left? when left.totalCharacters == 1) return null;
+
+ // If either operand contains a newline or the whole assignment doesn't
+ // fit then it will split at the operator since there's no other way it
+ // can split because there are no block operands.
+ var totalLength = 0;
+ if (_left case var left? when !_canBlockSplitLeft) {
+ if (left.containsHardNewline) return _atOperator;
+
+ totalLength += left.totalCharacters;
+ }
+
+ totalLength += _operator.totalCharacters;
+
+ if (!_canBlockSplitRight) {
+ if (_right.containsHardNewline) return _atOperator;
+ totalLength += _right.totalCharacters;
+ }
+
+ if (totalLength > pageWidth) return _atOperator;
+
+ return null;
+ }
+}
diff --git a/lib/src/piece/case.dart b/lib/src/piece/case.dart
index bf53dc5..dd74ba3 100644
--- a/lib/src/piece/case.dart
+++ b/lib/src/piece/case.dart
@@ -47,6 +47,9 @@
/// }
final bool _patternIsLogicalOr;
+ /// Whether the body expression can be block formatted.
+ final bool _canBlockSplitBody;
+
CaseExpressionPiece(
this._pattern,
this._guard,
@@ -54,12 +57,14 @@
this._body, {
required bool canBlockSplitPattern,
required bool patternIsLogicalOr,
+ bool canBlockSplitBody = true,
}) : _canBlockSplitPattern = canBlockSplitPattern,
- _patternIsLogicalOr = patternIsLogicalOr;
+ _patternIsLogicalOr = patternIsLogicalOr,
+ _canBlockSplitBody = canBlockSplitBody;
@override
List<State> get additionalStates => [
- _blockSplitBody,
+ if (_canBlockSplitBody) _blockSplitBody,
_beforeBody,
if (_guard != null) ...[_beforeWhenAndBody],
];
diff --git a/lib/src/piece/chain.dart b/lib/src/piece/chain.dart
index e9c37ca..e504dbc 100644
--- a/lib/src/piece/chain.dart
+++ b/lib/src/piece/chain.dart
@@ -48,7 +48,44 @@
/// argument,
/// argument,
/// );
-final class ChainPiece extends Piece {
+///
+/// A challenge with formatting call chains is how they look inside an
+/// assignment, named argument, or `=>`. We don't a newline in a call chain to
+/// force the surrounding assignment to split, because that's unnecessarily
+/// spread out:
+///
+/// variable =
+/// target
+/// .method()
+/// .another();
+///
+/// It's better as:
+///
+/// variable = target
+/// .method()
+/// .another();
+///
+/// But if the target itself splits, then we do want to force the assignment to
+/// split:
+///
+/// // Worse:
+/// variable = (veryLongTarget +
+/// anotherOperand)
+/// .method()
+/// .another();
+///
+/// // Better:
+/// variable =
+/// (veryLongTarget
+/// anotherOperand)
+/// .method()
+/// .another();
+///
+/// The 3.7 formatter had a limited ability to handle code like the above. In
+/// 3.8 and later, [Shape.headline] lets us express the constraint we want here
+/// directly. Since the logic is different, there are two different [ChainPiece]
+/// subclasses for each.
+abstract base class ChainPiece extends Piece {
/// Allow newlines in the last (or next-to-last) call but nowhere else.
static const State _blockFormatTrailingCall = State(1, cost: 0);
@@ -87,7 +124,42 @@
/// Creates a new ChainPiece.
///
/// Instead of calling this directly, prefer using [ChainBuilder].
- ChainPiece(
+ factory ChainPiece(
+ Piece target,
+ List<ChainCall> calls, {
+ required bool cascade,
+ int leadingProperties = 0,
+ int blockCallIndex = -1,
+ Indent indent = Indent.expression,
+ required bool allowSplitInTarget,
+ required bool version37,
+ }) {
+ if (version37) {
+ return _ChainPieceV37(
+ target,
+ calls,
+ cascade: cascade,
+ leadingProperties: leadingProperties,
+ blockCallIndex: blockCallIndex,
+ allowSplitInTarget: allowSplitInTarget,
+ indent: indent,
+ );
+ } else {
+ return _ChainPiece(
+ target,
+ calls,
+ cascade: cascade,
+ leadingProperties: leadingProperties,
+ blockCallIndex: blockCallIndex,
+ indent: indent,
+ );
+ }
+ }
+
+ /// Creates a new ChainPiece.
+ ///
+ /// Instead of calling this directly, prefer using [ChainBuilder].
+ ChainPiece._(
this._target,
this._calls, {
required bool cascade,
@@ -110,68 +182,24 @@
@override
int stateCost(State state) {
- if (state == State.split) {
- // If the chain is a cascade, lower the cost so that we prefer splitting
- // the cascades instead of the target. Prefers:
- //
- // [element1, element2]
- // ..cascade();
- //
- // Over:
- //
- // [
- // element1,
- // element2,
- // ]..cascade();
- if (_isCascade) return 0;
-
- // If the chain is only properties, try to keep them together. Prefers:
- //
- // variable =
- // target.property.another;
- //
- // Over:
- //
- // variable = target
- // .property
- // .another;
- if (_leadingProperties == _calls.length) return 2;
- }
+ // If the chain is a cascade, lower the cost so that we prefer splitting
+ // the cascades instead of the target. Prefers:
+ //
+ // [element1, element2]
+ // ..cascade();
+ //
+ // Over:
+ //
+ // [
+ // element1,
+ // element2,
+ // ]..cascade();
+ if (state == State.split) return _isCascade ? 0 : 1;
return super.stateCost(state);
}
@override
- Set<Shape> allowedChildShapes(State state, Piece child) {
- if (child == _target) {
- return switch (state) {
- // If the chain itself isn't fully split, only allow block splitting
- // in the target.
- State.unsplit ||
- _blockFormatTrailingCall ||
- _splitAfterProperties => const {Shape.inline, Shape.block},
- _ => Shape.all,
- };
- } else {
- switch (state) {
- case State.unsplit:
- return Shape.onlyInline;
-
- case _splitAfterProperties:
- // Don't allow splitting inside the properties.
- for (var i = 0; i < _leadingProperties; i++) {
- if (_calls[i]._call == child) return Shape.onlyInline;
- }
-
- case _blockFormatTrailingCall:
- return Shape.anyIf(_calls[_blockCallIndex]._call == child);
- }
-
- return Shape.all;
- }
- }
-
- @override
void format(CodeWriter writer, State state) {
switch (state) {
case State.unsplit:
@@ -181,7 +209,7 @@
writer.format(_calls[i]._call);
}
- case _splitAfterProperties:
+ case ChainPiece._splitAfterProperties:
writer.pushIndent(_indent);
writer.setShapeMode(ShapeMode.beforeHeadline);
writer.format(_target);
@@ -201,7 +229,7 @@
writer.popIndent();
- case _blockFormatTrailingCall:
+ case ChainPiece._blockFormatTrailingCall:
// Don't treat a cascade as block-shaped in the surrounding context
// even if it block splits. Prefer:
//
@@ -252,6 +280,122 @@
}
}
+/// A [ChainPiece] subclass for 3.8 and later style.
+final class _ChainPiece extends ChainPiece {
+ /// Creates a new ChainPiece.
+ ///
+ /// Instead of calling this directly, prefer using [ChainBuilder].
+ _ChainPiece(
+ super.target,
+ super.calls, {
+ required super.cascade,
+ super.leadingProperties,
+ super.blockCallIndex,
+ super.indent,
+ }) : super._();
+
+ @override
+ int stateCost(State state) {
+ // If the chain is only properties, try to keep them together. Prefers:
+ //
+ // variable =
+ // target.property.another;
+ //
+ // Over:
+ //
+ // variable = target
+ // .property
+ // .another;
+ if (state == State.split &&
+ !_isCascade &&
+ _leadingProperties == _calls.length) {
+ return 2;
+ }
+
+ return super.stateCost(state);
+ }
+
+ @override
+ Set<Shape> allowedChildShapes(State state, Piece child) {
+ if (child == _target) {
+ return switch (state) {
+ // If the chain itself isn't fully split, only allow block splitting
+ // in the target.
+ State.unsplit ||
+ ChainPiece._blockFormatTrailingCall ||
+ ChainPiece._splitAfterProperties => const {Shape.inline, Shape.block},
+ _ => Shape.all,
+ };
+ } else {
+ switch (state) {
+ case State.unsplit:
+ return Shape.onlyInline;
+
+ case ChainPiece._splitAfterProperties:
+ // Don't allow splitting inside the properties.
+ for (var i = 0; i < _leadingProperties; i++) {
+ if (_calls[i]._call == child) return Shape.onlyInline;
+ }
+
+ case ChainPiece._blockFormatTrailingCall:
+ return Shape.anyIf(_calls[_blockCallIndex]._call == child);
+ }
+
+ return Shape.all;
+ }
+ }
+}
+
+/// A [ChainPiece] subclass for 3.7 style.
+final class _ChainPieceV37 extends ChainPiece {
+ /// Whether the target expression may contain newlines when the chain is not
+ /// fully split. (It may always contain newlines when the chain splits.)
+ ///
+ /// This is true for most expressions but false for delimited ones to avoid
+ /// this weird output:
+ ///
+ /// function(
+ /// argument,
+ /// )
+ /// .method();
+ final bool _allowSplitInTarget;
+
+ /// Creates a new ChainPiece.
+ ///
+ /// Instead of calling this directly, prefer using [ChainBuilder].
+ _ChainPieceV37(
+ super.target,
+ super.calls, {
+ required super.cascade,
+ super.leadingProperties,
+ super.blockCallIndex,
+ super.indent,
+ required bool allowSplitInTarget,
+ }) : _allowSplitInTarget = allowSplitInTarget,
+ super._();
+
+ @override
+ Set<Shape> allowedChildShapes(State state, Piece child) {
+ switch (state) {
+ case _ when child == _target:
+ return Shape.anyIf(_allowSplitInTarget || state == State.split);
+
+ case State.unsplit:
+ return Shape.onlyInline;
+
+ case ChainPiece._splitAfterProperties:
+ for (var i = 0; i < _leadingProperties; i++) {
+ if (_calls[i]._call == child) return Shape.onlyInline;
+ }
+
+ case ChainPiece._blockFormatTrailingCall:
+ return Shape.anyIf(_calls[_blockCallIndex]._call == child);
+ }
+
+ return Shape.all;
+ }
+}
+
/// A method or getter call in a call chain, along with any postfix operations
/// applies to it.
final class ChainCall {
diff --git a/lib/src/piece/constructor.dart b/lib/src/piece/constructor.dart
index bcbdcaa..3120320 100644
--- a/lib/src/piece/constructor.dart
+++ b/lib/src/piece/constructor.dart
@@ -52,7 +52,7 @@
static const _splitBetweenInitializers = State(2, cost: 2);
- /// True if there are parameters or comments inside the parameter list.
+ /// Whether there are parameters or comments inside the parameter list.
///
/// If so, then we allow splitting the parameter list while leaving the `:`
/// on the same line as the `)`.
diff --git a/lib/src/piece/for.dart b/lib/src/piece/for.dart
index 9348f7f..0f1ff54 100644
--- a/lib/src/piece/for.dart
+++ b/lib/src/piece/for.dart
@@ -34,7 +34,7 @@
void format(CodeWriter writer, State state) {
writer.format(_forKeyword);
writer.space();
- if (_indent) writer.pushIndent(Indent.controlFlowClause);
+ if (_indent) writer.pushCollapsibleIndent();
writer.format(_parts);
if (_indent) writer.popIndent();
}
@@ -72,27 +72,36 @@
/// anotherOperand) {
/// ...
/// }
-final class ForInPiece extends Piece {
+abstract base class ForInPiece extends Piece {
/// The variable or pattern initialized with each loop iteration.
final Piece _variable;
/// The `in` keyword followed by the sequence expression.
final Piece _sequence;
- ForInPiece(this._variable, this._sequence);
+ factory ForInPiece(
+ Piece variable,
+ Piece sequence, {
+ bool canBlockSplitSequence = false,
+ required bool version37,
+ }) {
+ if (version37) {
+ return _ForInPieceV37(
+ variable,
+ sequence,
+ canBlockSplitSequence: canBlockSplitSequence,
+ );
+ } else {
+ return _ForInPiece(variable, sequence);
+ }
+ }
+
+ ForInPiece._(this._variable, this._sequence);
@override
List<State> get additionalStates => const [State.split];
@override
- Set<Shape> allowedChildShapes(State state, Piece child) => switch (state) {
- // Always allow block-splitting the sequence if it supports it.
- State.unsplit when child == _sequence => const {Shape.inline, Shape.block},
- State.unsplit => Shape.onlyInline,
- _ => Shape.all,
- };
-
- @override
void format(CodeWriter writer, State state) {
// When splitting at `in`, both operands may split or not and will be
// indented if they do.
@@ -111,3 +120,45 @@
callback(_sequence);
}
}
+
+/// A [ForInPiece] subclass for 3.8 and later style.
+final class _ForInPiece extends ForInPiece {
+ _ForInPiece(super._variable, super._sequence) : super._();
+
+ @override
+ Set<Shape> allowedChildShapes(State state, Piece child) => switch (state) {
+ // Always allow block-splitting the sequence if it supports it.
+ State.unsplit when child == _sequence => const {Shape.inline, Shape.block},
+ State.unsplit => Shape.onlyInline,
+ _ => Shape.all,
+ };
+}
+
+/// A [ForInPiece] subclass for 3.7 style.
+final class _ForInPieceV37 extends ForInPiece {
+ /// If `true` then the sequence expression supports being block-formatted,
+ /// like:
+ ///
+ /// for (var e in [
+ /// element1,
+ /// element2,
+ /// ]) {
+ /// // ...
+ /// }
+ final bool _canBlockSplitSequence;
+
+ _ForInPieceV37(
+ super._variable,
+ super._sequence, {
+ bool canBlockSplitSequence = false,
+ }) : _canBlockSplitSequence = canBlockSplitSequence,
+ super._();
+
+ @override
+ Set<Shape> allowedChildShapes(State state, Piece child) {
+ if (state == State.split) return Shape.all;
+
+ // Always allow block-splitting the sequence if it supports it.
+ return Shape.anyIf(child == _sequence && _canBlockSplitSequence);
+ }
+}
diff --git a/lib/src/piece/if_case.dart b/lib/src/piece/if_case.dart
index c4da5a7..8203936 100644
--- a/lib/src/piece/if_case.dart
+++ b/lib/src/piece/if_case.dart
@@ -101,7 +101,7 @@
writer.splitIf(state == _beforeCase || state == _beforeCaseAndWhen);
if (!_canBlockSplitPattern) {
- writer.pushIndent(Indent.controlFlowClause);
+ writer.pushCollapsibleIndent();
}
writer.format(_pattern);
diff --git a/lib/src/piece/infix.dart b/lib/src/piece/infix.dart
index 7cef38a..9efc6b9 100644
--- a/lib/src/piece/infix.dart
+++ b/lib/src/piece/infix.dart
@@ -7,7 +7,7 @@
/// A piece for a series of binary expressions at the same precedence, like:
///
/// a + b + c
-final class InfixPiece extends Piece {
+abstract base class InfixPiece extends Piece {
/// The series of operands.
///
/// Since we don't split on both sides of the operator, the operators will be
@@ -17,17 +17,35 @@
final List<Piece> _operands;
/// What kind of indentation should be applied to the subsequent operands.
- final Indent _indentType;
+ final Indent _indent;
- /// Whether this piece is for a conditional expression.
- final bool _isConditional;
-
- InfixPiece(
- this._operands, {
+ /// Creates an [InfixPiece] for the given series of [operands].
+ factory InfixPiece(
+ List<Piece> operands, {
+ required bool version37,
bool conditional = false,
- Indent indent = Indent.infix,
- }) : _indentType = indent,
- _isConditional = conditional;
+ Indent indent = Indent.expression,
+ }) {
+ if (version37) {
+ return _InfixPieceV37(operands, indent);
+ } else {
+ return _InfixPiece(operands, indent, conditional);
+ }
+ }
+
+ /// Creates an [InfixPiece] for a conditional (`?:`) expression.
+ factory InfixPiece.conditional(
+ List<Piece> operands, {
+ required bool version37,
+ }) {
+ if (version37) {
+ return _InfixPieceV37(operands, Indent.expression);
+ } else {
+ return _InfixPiece(operands, Indent.infix, true);
+ }
+ }
+
+ InfixPiece._(this._operands, this._indent);
@override
List<State> get additionalStates => const [State.split];
@@ -37,8 +55,39 @@
Shape.anyIf(state == State.split);
@override
+ void forEachChild(void Function(Piece piece) callback) {
+ _operands.forEach(callback);
+ }
+
+ @override
+ State? fixedStateForPageWidth(int pageWidth) {
+ var totalLength = 0;
+
+ for (var operand in _operands) {
+ // If any operand contains a newline, then we have to split.
+ if (operand.containsHardNewline) return State.split;
+
+ totalLength += operand.totalCharacters;
+ if (totalLength > pageWidth) break;
+ }
+
+ // If the total length doesn't fit in the page, then we have to split.
+ if (totalLength > pageWidth) return State.split;
+
+ return null;
+ }
+}
+
+/// InfixPiece subclass for 3.8 and newer style.
+final class _InfixPiece extends InfixPiece {
+ /// Whether this piece is for a conditional expression.
+ final bool _isConditional;
+
+ _InfixPiece(super.operands, super.indent, this._isConditional) : super._();
+
+ @override
void format(CodeWriter writer, State state) {
- writer.pushIndent(_indentType);
+ writer.pushIndent(_indent);
// If this is a conditional expression (or chain of them), then allow the
// leading condition to be headline formatted in an assignment, like:
@@ -76,27 +125,26 @@
writer.popIndent();
}
+}
+
+/// [InfixPiece] subclass for 3.7 style.
+final class _InfixPieceV37 extends InfixPiece {
+ _InfixPieceV37(super.operands, super.indent) : super._();
@override
- void forEachChild(void Function(Piece piece) callback) {
- _operands.forEach(callback);
- }
+ void format(CodeWriter writer, State state) {
+ writer.pushIndent(_indent);
- @override
- State? fixedStateForPageWidth(int pageWidth) {
- var totalLength = 0;
+ for (var i = 0; i < _operands.length; i++) {
+ // We can format each operand separately if the operand is on its own
+ // line. This happens when the operator is split and we aren't the first
+ // or last operand.
+ var separate = state == State.split && i > 0 && i < _operands.length - 1;
- for (var operand in _operands) {
- // If any operand contains a newline, then we have to split.
- if (operand.containsHardNewline) return State.split;
-
- totalLength += operand.totalCharacters;
- if (totalLength > pageWidth) break;
+ writer.format(_operands[i], separate: separate);
+ if (i < _operands.length - 1) writer.splitIf(state == State.split);
}
- // If the total length doesn't fit in the page, then we have to split.
- if (totalLength > pageWidth) return State.split;
-
- return null;
+ writer.popIndent();
}
}
diff --git a/lib/src/piece/piece.dart b/lib/src/piece/piece.dart
index c25f7d1..963572a 100644
--- a/lib/src/piece/piece.dart
+++ b/lib/src/piece/piece.dart
@@ -85,10 +85,6 @@
/// child piece and the state that child should be constrained to.
void applyConstraints(State state, Constrain constrain) {}
- /// Whether the [child] of this piece should be allowed to contain newlines
- /// (directly or transitively) when this piece is in [state].
- bool allowNewlineInChild(State state, Piece child) => true;
-
/// What shapes the [child] of this piece may take when this piece is in
/// [state].
Set<Shape> allowedChildShapes(State state, Piece child) => Shape.all;
diff --git a/lib/src/piece/variable.dart b/lib/src/piece/variable.dart
index be9adbc..1002e1a 100644
--- a/lib/src/piece/variable.dart
+++ b/lib/src/piece/variable.dart
@@ -45,12 +45,20 @@
/// Whether the variable declaration has a type annotation.
final bool _hasType;
+ /// Whether we are using the 3.7 style.
+ final bool _isVersion37;
+
/// Creates a [VariablePiece].
///
/// The [hasType] parameter should be `true` if the variable declaration has
/// a type annotation.
- VariablePiece(this._header, this._variables, {required bool hasType})
- : _hasType = hasType;
+ VariablePiece(
+ this._header,
+ this._variables, {
+ required bool hasType,
+ required bool version37,
+ }) : _hasType = hasType,
+ _isVersion37 = version37;
@override
List<State> get additionalStates => [
@@ -64,7 +72,7 @@
// `var x` etc.) then allow any shape. That way, if there's a comment
// inside, the solver doesn't get confused trying to invalidate the
// VariablePiece.
- if (_variables.length == 1 && !_hasType) return Shape.all;
+ if (!_isVersion37 && _variables.length == 1 && !_hasType) return Shape.all;
if (child == _header) {
return Shape.anyIf(state != State.unsplit);
diff --git a/lib/src/testing/test_file.dart b/lib/src/testing/test_file.dart
index f54ec57..3d2802b 100644
--- a/lib/src/testing/test_file.dart
+++ b/lib/src/testing/test_file.dart
@@ -11,12 +11,20 @@
import '../source_code.dart';
final _indentPattern = RegExp(r'\(indent (\d+)\)');
-final _versionPattern = RegExp(r'\(version (\d+)\.(\d+)\)');
final _experimentPattern = RegExp(r'\(experiment ([a-z-]+)\)');
final _preserveTrailingCommasPattern = RegExp(r'\(trailing_commas preserve\)');
final _unicodeUnescapePattern = RegExp(r'×([0-9a-fA-F]{2,4})');
final _unicodeEscapePattern = RegExp('[\x0a\x0c\x0d]');
+/// Matches an output header line with an optional version and description.
+/// Examples:
+///
+/// >>>
+/// >>> Only description.
+/// >>> 1.2
+/// >>> 1.2 Version and description.
+final _outputPattern = RegExp(r'<<<( (\d+)\.(\d+))?(.*)');
+
/// Get the absolute local file path to the dart_style package's root directory.
Future<String> findPackageDirectory() async {
var libraryPath =
@@ -68,6 +76,8 @@
factory TestFile._load(File file, String relativePath) {
var lines = file.readAsLinesSync();
+ var isCompilationUnit = file.path.endsWith('.unit');
+
// The first line may have a "|" to indicate the page width.
var i = 0;
int? pageWidth;
@@ -82,14 +92,14 @@
(fileOptions, _) = _parseOptions(lines[i]);
i++;
} else {
- fileOptions = TestOptions(null, null, null, const []);
+ fileOptions = TestOptions(null, null, const []);
}
var tests = <FormatTest>[];
List<String> readComments() {
var comments = <String>[];
- while (lines[i].startsWith('###')) {
+ while (i < lines.length && lines[i].startsWith('###')) {
comments.add(lines[i]);
i++;
}
@@ -107,62 +117,66 @@
var (options, description) = _parseOptions(line);
var inputComments = readComments();
-
var inputBuffer = StringBuffer();
- while (i < lines.length) {
- var line = readLine();
- if (line.startsWith('<<<')) break;
- inputBuffer.writeln(line);
+ while (i < lines.length && !lines[i].startsWith('<<<')) {
+ inputBuffer.writeln(readLine());
}
- var outputDescription = lines[i - 1].replaceAll('<<<', '');
-
- var outputComments = readComments();
-
- var outputBuffer = StringBuffer();
- while (i < lines.length) {
- var line = readLine();
- if (line.startsWith('>>>')) {
- // Found another test, so roll back to the test description for the
- // next iteration through the loop.
- i--;
- break;
- }
- outputBuffer.writeln(line);
- }
-
- var isCompilationUnit = file.path.endsWith('.unit');
-
- // The output always has a trailing newline. When formatting a statement,
- // the formatter (correctly) doesn't output trailing newlines when
- // formatting a statement, so remove it from the expectation to match.
- var outputText = outputBuffer.toString();
- if (!isCompilationUnit) {
- assert(outputText.endsWith('\n'));
- outputText = outputText.substring(0, outputText.length - 1);
- }
-
- var input = _extractSelection(
+ var inputCode = _extractSelection(
_unescapeUnicode(inputBuffer.toString()),
isCompilationUnit: isCompilationUnit,
);
- var output = _extractSelection(
- _unescapeUnicode(outputText),
- isCompilationUnit: isCompilationUnit,
- );
- tests.add(
- FormatTest(
- input,
- output,
- description.trim(),
- outputDescription.trim(),
- lineNumber,
- options,
- inputComments,
- outputComments,
- ),
- );
+ var input = TestEntry(description.trim(), null, inputComments, inputCode);
+
+ var outputs = <TestEntry>[];
+ while (i < lines.length && lines[i].startsWith('<<<')) {
+ var match = _outputPattern.firstMatch(readLine())!;
+ var outputDescription = match[4]!;
+ Version? outputVersion;
+ if (match[1] != null) {
+ outputVersion = Version(
+ int.parse(match[2]!),
+ int.parse(match[3]!),
+ 0,
+ );
+ }
+
+ var outputComments = readComments();
+
+ var outputBuffer = StringBuffer();
+ while (i < lines.length &&
+ !lines[i].startsWith('>>>') &&
+ !lines[i].startsWith('<<<')) {
+ var line = readLine();
+ outputBuffer.writeln(line);
+ }
+
+ // The output always has a trailing newline. When formatting a
+ // statement, the formatter (correctly) doesn't output trailing
+ // newlines when formatting a statement, so remove it from the
+ // expectation to match.
+ var outputText = outputBuffer.toString();
+ if (!isCompilationUnit) {
+ assert(outputText.endsWith('\n'));
+ outputText = outputText.substring(0, outputText.length - 1);
+ }
+ var outputCode = _extractSelection(
+ _unescapeUnicode(outputText),
+ isCompilationUnit: isCompilationUnit,
+ );
+
+ outputs.add(
+ TestEntry(
+ outputDescription.trim(),
+ outputVersion,
+ outputComments,
+ outputCode,
+ ),
+ );
+ }
+
+ tests.add(FormatTest(lineNumber, options, input, outputs));
}
return TestFile._(
@@ -179,15 +193,6 @@
/// Returns the options and the text remaining on the line after the options
/// are removed.
static (TestOptions, String) _parseOptions(String line) {
- // Let the test specify a language version to parse it at.
- Version? languageVersion;
- line = line.replaceAllMapped(_versionPattern, (match) {
- var major = int.parse(match[1]!);
- var minor = int.parse(match[2]!);
- languageVersion = Version(major, minor, 0);
- return '';
- });
-
// Let the test specify a leading indentation. This is handy for
// regression tests which often come from a chunk of nested code.
int? leadingIndent;
@@ -210,10 +215,7 @@
return '';
});
- return (
- TestOptions(languageVersion, leadingIndent, trailingCommas, experiments),
- line,
- );
+ return (TestOptions(leadingIndent, trailingCommas, experiments), line);
}
TestFile._(
@@ -243,19 +245,22 @@
bool get isCompilationUnit => path.endsWith('.unit');
+ /// Whether the test uses the tall or short style.
+ bool get isTall => p.split(path).contains('tall');
+
/// Creates a [DartFormatter] configured with all of the options that should
/// be applied for [test] in this test file.
- DartFormatter formatterForTest(FormatTest test) {
+ ///
+ /// If [version] is given, then it specifies the language version to run the
+ /// test at. Otherwise, the test's default version is used.
+ DartFormatter formatterForTest(FormatTest test, [Version? version]) {
var defaultLanguageVersion =
- p.split(path).contains('tall')
+ isTall
? DartFormatter.latestLanguageVersion
: DartFormatter.latestShortStyleLanguageVersion;
return DartFormatter(
- languageVersion:
- test.options.languageVersion ??
- options.languageVersion ??
- defaultLanguageVersion,
+ languageVersion: version ?? defaultLanguageVersion,
pageWidth: pageWidth,
indent: test.options.leadingIndent ?? options.leadingIndent ?? 0,
experimentFlags: [
@@ -272,54 +277,48 @@
/// A single formatting test inside a [TestFile].
final class FormatTest {
- /// The unformatted input.
- final SourceCode input;
-
- /// The expected output.
- final SourceCode output;
-
- /// The optional description of the test.
- final String description;
-
- /// The `###` comment lines appearing after the test description before the
- /// input code.
- final List<String> inputComments;
-
- /// If there is a remark on the "<<<" line, this is it.
- final String outputDescription;
-
- /// The `###` comment lines appearing after the "<<<" before the output code.
- final List<String> outputComments;
-
/// The 1-based index of the line where this test begins.
final int line;
/// The options specific to this test.
final TestOptions options;
- FormatTest(
- this.input,
- this.output,
- this.description,
- this.outputDescription,
- this.line,
- this.options,
- this.inputComments,
- this.outputComments,
- );
+ /// The unformatted input.
+ final TestEntry input;
+
+ // TODO(rnystrom): Consider making this a map of version (or null) to output
+ // and then validating that there aren't duplicate outputs for a single
+ // version.
+ /// The expected output.
+ final List<TestEntry> outputs;
+
+ FormatTest(this.line, this.options, this.input, this.outputs);
/// The line and description of the test.
String get label {
- if (description.isEmpty) return 'line $line';
- return 'line $line: $description';
+ if (input.description.isEmpty) return 'line $line';
+ return 'line $line: ${input.description}';
}
}
+/// A single test input or output.
+final class TestEntry {
+ /// Any remark on the "<<<" or ">>>" line.
+ final String description;
+
+ /// If this is a test output for a specific version, the version.
+ final Version? version;
+
+ /// The `###` comment lines appearing after the header line before the code.
+ final List<String> comments;
+
+ final SourceCode code;
+
+ TestEntry(this.description, this.version, this.comments, this.code);
+}
+
/// Options for configuring all tests in a file or an individual test.
final class TestOptions {
- /// The language version the test code should be parsed at.
- final Version? languageVersion;
-
/// The number of spaces of leading indentation that should be added to each
/// line.
final int? leadingIndent;
@@ -330,12 +329,7 @@
/// Experiments that should be enabled when running this test.
final List<String> experimentFlags;
- TestOptions(
- this.languageVersion,
- this.leadingIndent,
- this.trailingCommas,
- this.experimentFlags,
- );
+ TestOptions(this.leadingIndent, this.trailingCommas, this.experimentFlags);
}
extension SourceCodeExtensions on SourceCode {
diff --git a/test/README.md b/test/README.md
index e665163..257f14b 100644
--- a/test/README.md
+++ b/test/README.md
@@ -9,37 +9,117 @@
entire Dart compilation unit (roughly library or part file). The ".stmt" files
parse each expectation as a statement.
-These test files have a custom diff-like format:
+Each test file has an optional header followed by a number of test cases. Lines
+that start with `###` are comments and are ignored.
-```
-40 columns |
->>> (indent 4) arithmetic operators
-var a=1+2/(3*-b~/4);
-<<<
- var a = 1 + 2 / (3 * -b ~/ 4);
-```
+### Test file header
If the first line contains a `|`, then it indicates the page width that all
tests in this file should be formatted using. All other text on that line is
ignored. This is used so that tests can test line wrapping behavior without
having to create long code to force things to wrap.
-The `>>>` line begins a test. It may have comment text afterwards describing the
-test. If the line contains `(indent <n>)` for some `n`, then the formatter is
-told to run with that level of indentation. This is mainly for regression tests
-where the erroneous code appeared deeply nested inside some class or function
-and the test wants to reproduce that same surrounding indentation.
+After that, if there is a line containing parenthesized options like
+`(indent 4)` or `(experiment monads)` then those options are applied to all
+test cases in the file.
-Lines after the `>>>` line are the input code to be formatted.
+### Test cases
-The `<<<` ends the input and begins the expected formatted result. The end of
-the file or the next `>>>` marks the end of the expected output.
+Each test case begins with a header line like:
-For each pair of input and expected output, the test runner creates a separate
-test. It runs the input code through the formatter and validates that the
-resulting code matches the expectation.
+```
+>>> (indent 4) Some description.
+```
-Lines starting with `###` are treated as comments and are ignored.
+The `>>>` marks the beginning of a new test. After that are optional
+parenthesized options that will be applied to that test. then an optional
+description for the test. Lines after that define the input code to be
+formatted.
+
+After the input are one or more output sections. Each output section starts
+with a header like:
+
+```
+<<< 3.7 Optional description.
+```
+
+The `<<<` marks the beginning of a new output section. If it has a language
+version number, then this output is expected only on that language version. If
+it has no version number, then this is the expected output on all versions.
+
+### Test options
+
+A few parenthesized options are supported:
+
+* `(indent <n>)` Tells the formatter to apply that many spaces of leading
+ indentation. This is mainly for regression tests where the erroneous code
+ appeared deeply nested inside some class or function and the test wants to
+ reproduce that same surrounding indentation.
+
+* `(experiment <name>)` Enable that named experiment in the parser and
+ formatter. A test can have multiple of these.
+
+* `(trailing_commas preserve)` Enable the preserved trailing commas option.
+
+### Test versions
+
+All tests in the "short" directory are run at language version
+[DartFormatter.latestShortStyleLanguageVersion].
+
+Tests in the "tall" directory are run (potentially) on multiple versions. By
+default, tests are run against every language version from just after
+[DartFormatter.latestShortStyleLanguageVersion] up to
+[DartFormatter.latestLanguageVersion].
+
+If the test has an output expectation for a specific version, then when the
+test is run at that version, it is validated against that output. If the test
+has an output expectation with no version marker, than that is the default
+expectation for all other unspecified versions. If a test has no unversioned
+output expectation, then it is only run against the versions that it has
+expectations for.
+
+For example, let's say the supported tall versions are 3.7, 3.8, and 3.9. A
+test like:
+
+```
+<<<
+some . code;
+>>>
+some.code;
+```
+
+This will be run at versions 3.7, 3.8, and 3.9. For all of them, the expected
+output is `some.code;`.
+
+A test like:
+
+```
+<<<
+some . code;
+>>>
+some.code;
+>>> 3.7
+some . code ;
+```
+
+This will be run at versions 3.7, 3.8, and 3.9. For version 3.7, the expected
+output is `some . code ;`. For 3.7 and 3.9, the expected output is `some.code;`.
+
+A test like:
+
+```
+<<<
+some . code;
+>>> 3.8
+some.code;
+>>> 3.9
+some . code ;
+```
+
+Is *only* run at versions 3.8 and 3.9. At 3.8, the expected output is
+`some.code;` and at 3.8 it's `some . code ;`. Tests like this are usually for
+testing language features or formatter features that didn't exist prior to some
+version, like preserved trailing commas, or null-aware elements.
## Test directories
diff --git a/test/cli/language_version_test.dart b/test/cli/language_version_test.dart
index d08245c..c3b151a 100644
--- a/test/cli/language_version_test.dart
+++ b/test/cli/language_version_test.dart
@@ -259,14 +259,14 @@
await d.dir('code', [d.file('a.dart', after)]).validate();
});
- test('uses the tall style on 3.7 or earlier', () async {
- const before = 'main() { f(argument, // comment\nanother);}';
+ test('uses the 3.7 tall style on 3.7', () async {
+ const before = 'main() { x = cond ? a // comment\n: b;}';
const after = '''
main() {
- f(
- argument, // comment
- another,
- );
+ x =
+ cond
+ ? a // comment
+ : b;
}
''';
@@ -278,6 +278,24 @@
await d.dir('code', [d.file('a.dart', after)]).validate();
});
+ test('uses the 3.8 tall style on 3.8 or later', () async {
+ const before = 'main() { x = cond ? a // comment\n: b;}';
+ const after = '''
+main() {
+ x = cond
+ ? a // comment
+ : b;
+}
+''';
+
+ await d.dir('code', [d.file('a.dart', before)]).create();
+
+ var process = await runFormatterOnDir(['--language-version=3.8']);
+ await process.shouldExit(0);
+
+ await d.dir('code', [d.file('a.dart', after)]).validate();
+ });
+
test('language version comment override opts into short style', () async {
const before = '''
// @dart=3.6
diff --git a/test/short/whitespace/switch.stmt b/test/short/whitespace/switch.stmt
index be07343..a077c20 100644
--- a/test/short/whitespace/switch.stmt
+++ b/test/short/whitespace/switch.stmt
@@ -227,7 +227,7 @@
1 => 'one',
var two => 'two'
};
->>> (version 2.19) handle cases in old code that are not valid patterns
+>>> handle cases in old code that are not valid patterns
switch (obj) {
case {1, 2}:
case -pi:
@@ -258,7 +258,7 @@
case 1 is! int:
body;
}
-<<<
+<<< 2.19
switch (obj) {
case {1, 2}:
case -pi:
diff --git a/test/tall/declaration/typedef.unit b/test/tall/declaration/typedef.unit
index 0100cc1..fa50c4b 100644
--- a/test/tall/declaration/typedef.unit
+++ b/test/tall/declaration/typedef.unit
@@ -164,4 +164,22 @@
typedef SomeType = (
int first,
int second,
-);
\ No newline at end of file
+);
+<<< 3.7
+typedef SomeType =
+ (int first, int second);
+>>> Don't allow block-formatting a record typedef.
+typedef SomeType = (int first, int second, String third);
+<<<
+typedef SomeType = (
+ int first,
+ int second,
+ String third,
+);
+<<< 3.7
+typedef SomeType =
+ (
+ int first,
+ int second,
+ String third,
+ );
\ No newline at end of file
diff --git a/test/tall/expression/assignment.stmt b/test/tall/expression/assignment.stmt
index 869bfef..eefd8f0 100644
--- a/test/tall/expression/assignment.stmt
+++ b/test/tall/expression/assignment.stmt
@@ -88,6 +88,14 @@
element3,
element4,
];
+<<< 3.7
+outer =
+ inner = [
+ element1,
+ element2,
+ element3,
+ element4,
+ ];
>>> Headline format unsplit target of call chain.
variable = (tar + get).method().another().third();
<<<
@@ -95,6 +103,12 @@
.method()
.another()
.third();
+<<< 3.7
+variable =
+ (tar + get)
+ .method()
+ .another()
+ .third();
>>> Don't headline format target of call chain if target splits.
variable = (veryLongTarget + expressionThatSplits).method().another().third();
<<<
@@ -111,3 +125,9 @@
.method()
.another()
.third();
+<<< 3.7
+variable =
+ (tar + get).prop.erty
+ .method()
+ .another()
+ .third();
\ No newline at end of file
diff --git a/test/tall/expression/collection_null_aware.stmt b/test/tall/expression/collection_null_aware.stmt
index fc55a31..523cd3c 100644
--- a/test/tall/expression/collection_null_aware.stmt
+++ b/test/tall/expression/collection_null_aware.stmt
@@ -2,27 +2,27 @@
(experiment null-aware-elements)
>>> List element.
var list = [ ? x ];
-<<<
+<<< 3.8
var list = [?x];
>>> Set element.
var set = { ? x };
-<<<
+<<< 3.8
var set = {?x};
>>> Map key.
var map = { ? key : value};
-<<<
+<<< 3.8
var map = {?key: value};
>>> Map value.
var map = { key: ? value };
-<<<
+<<< 3.8
var map = {key: ?value};
>>> Both key and value.
var map = { ? key : ? value };
-<<<
+<<< 3.8
var map = {?key: ?value};
>>> Split inside element.
var list = [?(veryLongExpression +thatIsForcedToSplit)];
-<<<
+<<< 3.8
var list = [
?(veryLongExpression +
thatIsForcedToSplit),
diff --git a/test/tall/expression/collection_null_aware_comment.stmt b/test/tall/expression/collection_null_aware_comment.stmt
index 6f2a2ed..2f1f26e 100644
--- a/test/tall/expression/collection_null_aware_comment.stmt
+++ b/test/tall/expression/collection_null_aware_comment.stmt
@@ -2,18 +2,18 @@
(experiment null-aware-elements)
>>> Inline comment after `?`.
var list = [ ? /* c */ x ];
-<<<
+<<< 3.8
var list = [? /* c */ x];
>>>
var map = { ? /* c */ key : ? /* c */ value };
-<<<
+<<< 3.8
var map = {
? /* c */ key: ? /* c */ value,
};
>>> Line comment after `?`.
var list = [ ? // c
x ];
-<<<
+<<< 3.8
### This is an odd place for a comment so the formatting is odd, but we want to
### at least pin it down with a test.
var list = [
@@ -24,7 +24,7 @@
var map = { ? // c
key : ? // c
value };
-<<<
+<<< 3.8
### This is an odd place for a comment so the formatting is odd, but we want to
### at least pin it down with a test.
var map = {
diff --git a/test/tall/expression/condition.stmt b/test/tall/expression/condition.stmt
index 2967fd2..75d15bc 100644
--- a/test/tall/expression/condition.stmt
+++ b/test/tall/expression/condition.stmt
@@ -18,6 +18,11 @@
? veryLongExpression +
otherLongExpression
: elseExpr;
+<<< 3.7
+condition
+ ? veryLongExpression +
+ otherLongExpression
+ : elseExpr;
>>> Split because of split in else branch.
condition ? thenExpression
: veryLongExpression +
@@ -27,6 +32,11 @@
? thenExpression
: veryLongExpression +
otherLongExpression;
+<<< 3.7
+condition
+ ? thenExpression
+ : veryLongExpression +
+ otherLongExpression;
>>> Force split all conditionals when nested.
var kind = a ? b ? c : d : e;
<<<
@@ -35,6 +45,13 @@
? c
: d
: e;
+<<< 3.7
+var kind =
+ a
+ ? b
+ ? c
+ : d
+ : e;
>>>
var kind = a ? b : c ? d : e;
<<<
@@ -43,6 +60,13 @@
: c
? d
: e;
+<<< 3.7
+var kind =
+ a
+ ? b
+ : c
+ ? d
+ : e;
>>> Don't force split conditionals when indirectly nested.
var kind = a ? b : (c ? d : e);
<<<
@@ -59,6 +83,17 @@
: c4
? e4
: e5;
+<<< 3.7
+var kind =
+ c1
+ ? e1
+ : c2
+ ? e2
+ : c3
+ ? e3
+ : c4
+ ? e4
+ : e5;
>>> Indent block-style then and else branches past the operators.
### Note that the condition does not get extra +2 indentation.
x = condition(argument1, argument2, argument3)
@@ -81,6 +116,23 @@
argument2,
argument3,
);
+<<< 3.7
+x =
+ condition(
+ argument1,
+ argument2,
+ argument3,
+ )
+ ? thenBranch(
+ argument1,
+ argument2,
+ argument3,
+ )
+ : elseBranch(
+ argument1,
+ argument2,
+ argument3,
+ );
>>> Indent chained block-style then and else branches past the operators.
### Note that the condition does not get extra +2 indentation.
x = condition(argument1, argument2, argument3)
@@ -115,6 +167,33 @@
argument2,
argument3,
);
+<<< 3.7
+x =
+ condition(
+ argument1,
+ argument2,
+ argument3,
+ )
+ ? thenBranch1(
+ argument1,
+ argument2,
+ argument3,
+ )
+ : elseBranch1(
+ argument1,
+ argument2,
+ argument3,
+ )
+ ? thenBranch2(
+ argument1,
+ argument2,
+ argument3,
+ )
+ : elseBranch2(
+ argument1,
+ argument2,
+ argument3,
+ );
>>> Indent expression-style then and else branches past the operators.
x = condition1 && condition2 && condition3
? thenBranch1 + thenBranch2 + thenBranch3
@@ -130,6 +209,17 @@
: elseBranch1 -
elseBranch2 -
elseBranch3;
+<<< 3.7
+x =
+ condition1 &&
+ condition2 &&
+ condition3
+ ? thenBranch1 +
+ thenBranch2 +
+ thenBranch3
+ : elseBranch1 -
+ elseBranch2 -
+ elseBranch3;
>>> Indent chained expression-style then and else branches past the operators.
x = condition1 && condition2 && condition3
? thenBranch1 + thenBranch2 + thenBranch3
@@ -152,4 +242,21 @@
thenBranch6
: elseBranch4 -
elseBranch5 -
- elseBranch6;
\ No newline at end of file
+ elseBranch6;
+<<< 3.7
+x =
+ condition1 &&
+ condition2 &&
+ condition3
+ ? thenBranch1 +
+ thenBranch2 +
+ thenBranch3
+ : elseBranch1 -
+ elseBranch2 -
+ elseBranch3
+ ? thenBranch4 +
+ thenBranch5 +
+ thenBranch6
+ : elseBranch4 -
+ elseBranch5 -
+ elseBranch6;
\ No newline at end of file
diff --git a/test/tall/expression/condition_comment.stmt b/test/tall/expression/condition_comment.stmt
index d188716..570d5e9 100644
--- a/test/tall/expression/condition_comment.stmt
+++ b/test/tall/expression/condition_comment.stmt
@@ -35,6 +35,12 @@
? // c
1
: 2;
+<<< 3.7
+### Looks weird, but users generally won't put comments here.
+cond
+ ? // c
+ 1
+ : 2;
>>> Line comment before `:`.
cond ? 1 // c
: 2;
@@ -51,6 +57,12 @@
? 1
: // c
2;
+<<< 3.7
+### Looks weird, but users generally won't put comments here.
+cond
+ ? 1
+ : // c
+ 2;
>>> Line comment after else.
cond ? 1 : 2 // c
;
@@ -60,6 +72,12 @@
? 1
: 2 // c
;
+<<< 3.7
+### Looks weird, but users generally won't put comments here.
+cond
+ ? 1
+ : 2 // c
+ ;
>>> Don't split with leading line comment before first operand.
value =
// comment
diff --git a/test/tall/expression/string_adjacent.stmt b/test/tall/expression/string_adjacent.stmt
index dec0b87..6fae130 100644
--- a/test/tall/expression/string_adjacent.stmt
+++ b/test/tall/expression/string_adjacent.stmt
@@ -208,6 +208,12 @@
? "adjacent"
"string"
: "other";
+<<< 3.7
+var string =
+ condition
+ ? "adjacent"
+ "string"
+ : "other";
>>> Indent in else branch of `?:`.
var string = condition ? "other" : "adjacent"
"string";
@@ -216,6 +222,12 @@
? "other"
: "adjacent"
"string";
+<<< 3.7
+var string =
+ condition
+ ? "other"
+ : "adjacent"
+ "string";
>>> Don't indent in initializer.
var longVariableName = "very long adjacent"
"string";
@@ -279,4 +291,4 @@
<<<
variable =
!'Some very long message '
- 'that continues.';
+ 'that continues.';
\ No newline at end of file
diff --git a/test/tall/function/expression.stmt b/test/tall/function/expression.stmt
index 2865fc6..45f1612 100644
--- a/test/tall/function/expression.stmt
+++ b/test/tall/function/expression.stmt
@@ -37,4 +37,11 @@
int secondArgument,
) {
print('42');
- };
\ No newline at end of file
+ };
+<<< 3.7
+function = (
+ int firstArgument,
+ int secondArgument,
+) {
+ print('42');
+};
\ No newline at end of file
diff --git a/test/tall/invocation/cascade_mixed.stmt b/test/tall/invocation/cascade_mixed.stmt
index d53e04a..c6fd147 100644
--- a/test/tall/invocation/cascade_mixed.stmt
+++ b/test/tall/invocation/cascade_mixed.stmt
@@ -46,6 +46,10 @@
<<<
variable = target
..method(argument1, argument2);
+<<< 3.7
+variable =
+ target
+ ..method(argument1, argument2);
>>> Don't treat cascade as block-shaped even if the argument list splits.
variable = target..method(argument1, argument2, argument3);
<<<
@@ -55,3 +59,10 @@
argument2,
argument3,
);
+<<< 3.7
+variable =
+ target..method(
+ argument1,
+ argument2,
+ argument3,
+ );
\ No newline at end of file
diff --git a/test/tall/invocation/chain_comment.stmt b/test/tall/invocation/chain_comment.stmt
index 95b8740..4369620 100644
--- a/test/tall/invocation/chain_comment.stmt
+++ b/test/tall/invocation/chain_comment.stmt
@@ -98,3 +98,6 @@
target // c
.prop =
value;
+<<< 3.7
+target // c
+ .prop = value;
\ No newline at end of file
diff --git a/test/tall/invocation/chain_property.stmt b/test/tall/invocation/chain_property.stmt
index 431ace8..b1437ee 100644
--- a/test/tall/invocation/chain_property.stmt
+++ b/test/tall/invocation/chain_property.stmt
@@ -65,11 +65,22 @@
.equine
.feline
.galline;
+<<< 3.7
+variable =
+ avian
+ .bovine
+ .canine
+ .equine
+ .feline
+ .galline;
>>> Don't prefer splitting an assignment if there are methods in the chain.
variable = avian.bovine.canine.equine.feline();
<<<
variable = avian.bovine.canine.equine
.feline();
+<<< 3.7
+variable =
+ avian.bovine.canine.equine.feline();
>>> Don't prefer splitting an assignment if there are methods in the chain.
variable = avian.bovine().canine.equine().feline;
<<<
@@ -78,3 +89,10 @@
.canine
.equine()
.feline;
+<<< 3.7
+variable =
+ avian
+ .bovine()
+ .canine
+ .equine()
+ .feline;
\ No newline at end of file
diff --git a/test/tall/invocation/named_argument.stmt b/test/tall/invocation/named_argument.stmt
index 168a80d..73fc33c 100644
--- a/test/tall/invocation/named_argument.stmt
+++ b/test/tall/invocation/named_argument.stmt
@@ -73,6 +73,15 @@
body;
},
);
+<<< 3.7
+function(
+ name: (
+ longParameter,
+ anotherLongParameter,
+ ) {
+ body;
+ },
+);
>>> Headline formatting for a split `=>` function.
function(name: (param, another) => veryLongBody);
<<<
@@ -80,6 +89,11 @@
name: (param, another) =>
veryLongBody,
);
+<<< 3.7
+function(
+ name:
+ (param, another) => veryLongBody,
+);
>>> Block-like formatting of a `=>` body containing a function call.
function(name: (param) => another(argument1, argument2, argument3));
<<<
@@ -89,4 +103,13 @@
argument2,
argument3,
),
+);
+<<< 3.7
+function(
+ name:
+ (param) => another(
+ argument1,
+ argument2,
+ argument3,
+ ),
);
\ No newline at end of file
diff --git a/test/tall/invocation/nested.stmt b/test/tall/invocation/nested.stmt
index c409017..ffd5877 100644
--- a/test/tall/invocation/nested.stmt
+++ b/test/tall/invocation/nested.stmt
@@ -23,6 +23,8 @@
a: a,
b: inner(c: c),
);
+<<< 3.7
+outer(a: a, b: inner(c: c));
>>> Split outer call if there are more than two outer and inner named arguments.
outer(a: a, inner(b: b, c: c));
<<<
@@ -30,48 +32,64 @@
a: a,
inner(b: b, c: c),
);
+<<< 3.7
+outer(a: a, inner(b: b, c: c));
>>> Split outer call if there are more than two outer and inner named arguments.
outer(a: inner(b: b, c: c));
<<<
outer(
a: inner(b: b, c: c),
);
+<<< 3.7
+outer(a: inner(b: b, c: c));
>>> Count deeper nested calls.
outer(a: inner(b: b, nest(c: c)));
<<<
outer(
a: inner(b: b, nest(c: c)),
);
+<<< 3.7
+outer(a: inner(b: b, nest(c: c)));
>>> Split outer call on indirect inner call.
outer(name: !(inner(x: x, y: y)));
<<<
outer(
name: !(inner(x: x, y: y)),
);
+<<< 3.7
+outer(name: !(inner(x: x, y: y)));
>>> Split outer `new` expression.
new Outer(name: inner(x: x, y: y));
<<<
new Outer(
name: inner(x: x, y: y),
);
+<<< 3.7
+new Outer(name: inner(x: x, y: y));
>>> Split outer `const` expression.
const Outer(name: inner(x: x, y: y));
<<<
const Outer(
name: inner(x: x, y: y),
);
+<<< 3.7
+const Outer(name: inner(x: x, y: y));
>>> Split on inner `new` expression.
outer(name: new Inner(x: x, y: y));
<<<
outer(
name: new Inner(x: x, y: y),
);
+<<< 3.7
+outer(name: new Inner(x: x, y: y));
>>> Split on inner `const` expression.
outer(name: const Inner(x: x, y: y));
<<<
outer(
name: const Inner(x: x, y: y),
);
+<<< 3.7
+outer(name: const Inner(x: x, y: y));
>>> Don't count named argument if it's a trivial expression.
{
outer(name: inner(x: x, y: 123));
@@ -119,6 +137,14 @@
name: inner(x: x, y: 1 + 2),
);
}
+<<< 3.7
+{
+ outer(name: inner(x: x, y: 'string'));
+ outer(name: inner(x: x, y: (123)));
+ outer(name: inner(x: x, y: this));
+ outer(name: inner(x: x, y: -(1)));
+ outer(name: inner(x: x, y: 1 + 2));
+}
>>> Split named list argument with multiple elements and any named arguments.
{
// Only one element.
@@ -144,6 +170,15 @@
],
);
}
+<<< 3.7
+{
+ // Only one element.
+ f(name: [inner(x: x, y: y)]);
+ // No named arguments.
+ f(name: [a, inner(x, y)]);
+ // Multiple elements and a named argument.
+ f(name: [a, inner(x: x)]);
+}
>>> Split named map argument with multiple elements and any named arguments.
{
// Only one element.
@@ -169,6 +204,15 @@
},
);
}
+<<< 3.7
+{
+ // Only one element.
+ f(name: {a: inner(x: x, y: y)});
+ // No named arguments.
+ f(name: {a: a, b: inner(x, y)});
+ // Multiple elements and a named argument.
+ f(name: {a: a, b: inner(x: x)});
+}
>>> Split named set argument with multiple elements and any named arguments.
{
// Only one element.
@@ -194,6 +238,15 @@
},
);
}
+<<< 3.7
+{
+ // Only one element.
+ f(name: {inner(x: x, y: y)});
+ // No named arguments.
+ f(name: {a, inner(x, y)});
+ // Multiple elements and a named argument.
+ f(name: {a, inner(x: x)});
+}
>>> Don't eagerly split named record arg regardless of contents.
f(name: (a, inner(x: x, y: y), b: b));
<<<
@@ -201,6 +254,9 @@
f(
name: (a, inner(x: x, y: y), b: b),
);
+<<< 3.7
+### The outer call splits, but not the record.
+f(name: (a, inner(x: x, y: y), b: b));
>>> Split when inner call isn't itself named argument.
outer(x: x, inner(y: y), z: z);
<<<
@@ -209,9 +265,11 @@
inner(y: y),
z: z,
);
+<<< 3.7
+outer(x: x, inner(y: y), z: z);
>>> Don't force split if the outer call can be block formatted.
outer(() {;}, name: inner(x: x, y: y));
<<<
outer(() {
;
-}, name: inner(x: x, y: y));
+}, name: inner(x: x, y: y));
\ No newline at end of file
diff --git a/test/tall/pattern/declared_variable_comment.stmt b/test/tall/pattern/declared_variable_comment.stmt
index bb039c0..844f6c4 100644
--- a/test/tall/pattern/declared_variable_comment.stmt
+++ b/test/tall/pattern/declared_variable_comment.stmt
@@ -17,6 +17,11 @@
x) {
;
}
+<<< 3.7
+if (obj case var // c
+ x) {
+ ;
+}
>>> Line comment after variable (looks weird, but user should move comment).
if (obj case var x // c
) {;}
@@ -46,4 +51,9 @@
case final // c
thisIsReallyQuiteAVeryLongVariableName) {
;
+}
+<<< 3.7
+if (obj case final // c
+ thisIsReallyQuiteAVeryLongVariableName) {
+ ;
}
\ No newline at end of file
diff --git a/test/tall/regression/0000/0005.stmt b/test/tall/regression/0000/0005.stmt
index 8d1ebd7..914d23a 100644
--- a/test/tall/regression/0000/0005.stmt
+++ b/test/tall/regression/0000/0005.stmt
@@ -10,6 +10,15 @@
path.isWithin(rootDirectory, directory),
)
.toList();
+<<< 3.7
+var overlapping =
+ _directories.keys
+ .where(
+ (directory) =>
+ path.isWithin(directory, rootDirectory) ||
+ path.isWithin(rootDirectory, directory),
+ )
+ .toList();
>>>
return isLoopback(server.address.host) == isLoopback(url.host) ||
server.address.host == url.host;
diff --git a/test/tall/regression/0000/0019.stmt b/test/tall/regression/0000/0019.stmt
index f86b60b..4731a53 100644
--- a/test/tall/regression/0000/0019.stmt
+++ b/test/tall/regression/0000/0019.stmt
@@ -23,9 +23,15 @@
format.maximumFractionDigits = decimalPos >= 0
? totalDigits - decimalPos
: 0;
+<<< 3.7
+ format.maximumFractionDigits =
+ decimalPos >= 0 ? totalDigits - decimalPos : 0;
>>> (indent 6)
var libname = _builtins.containsKey(package) ? _builtins[package] : package;
<<<
var libname = _builtins.containsKey(package)
? _builtins[package]
- : package;
\ No newline at end of file
+ : package;
+<<< 3.7
+ var libname =
+ _builtins.containsKey(package) ? _builtins[package] : package;
\ No newline at end of file
diff --git a/test/tall/regression/0000/0036.unit b/test/tall/regression/0000/0036.unit
index c5beb36..122ad12 100644
--- a/test/tall/regression/0000/0036.unit
+++ b/test/tall/regression/0000/0036.unit
@@ -12,4 +12,11 @@
* DO NOT EDIT. This is code generated via pkg/intl/generate_localized.dart
* This is a library that provides messages for a $locale locale. All the
...
+""";
+<<< 3.7
+String prologue(String locale) => """
+/**
+ * DO NOT EDIT. This is code generated via pkg/intl/generate_localized.dart
+ * This is a library that provides messages for a $locale locale. All the
+...
""";
\ No newline at end of file
diff --git a/test/tall/regression/0000/0040.stmt b/test/tall/regression/0000/0040.stmt
index efddfc9..a9a8c12 100644
--- a/test/tall/regression/0000/0040.stmt
+++ b/test/tall/regression/0000/0040.stmt
@@ -7,6 +7,13 @@
<<<
var s = new Serialization()
..addRuleFor(Various, constructor: "Foo", constructorFields: ["d", "e"]);
+<<< 3.7
+ var s =
+ new Serialization()..addRuleFor(
+ Various,
+ constructor: "Foo",
+ constructorFields: ["d", "e"],
+ );
>>> (indent 6)
var s = new Serialization()
..addRuleFor(Various, constructor: "Foo", constructorFields: [
@@ -19,4 +26,11 @@
Various,
constructor: "Foo",
constructorFields: ["d", "e"],
- );
\ No newline at end of file
+ );
+<<< 3.7
+ var s =
+ new Serialization()..addRuleFor(
+ Various,
+ constructor: "Foo",
+ constructorFields: ["d", "e"],
+ );
\ No newline at end of file
diff --git a/test/tall/regression/0000/0044.stmt b/test/tall/regression/0000/0044.stmt
index 652e0a6..e97b015 100644
--- a/test/tall/regression/0000/0044.stmt
+++ b/test/tall/regression/0000/0044.stmt
@@ -62,4 +62,11 @@
var xxxxxxxYyyyyyyy = aaaabbb
? CCCCCCCCCCCC.mmmmmmmmmmmmmmmmmm1(arg00000000000)
: CCCCCCCCCCCC.mmmmmmmmmmmmmmmmmm(arg1111111111111);
+}
+<<< 3.7
+void main() {
+ var xxxxxxxYyyyyyyy =
+ aaaabbb
+ ? CCCCCCCCCCCC.mmmmmmmmmmmmmmmmmm1(arg00000000000)
+ : CCCCCCCCCCCC.mmmmmmmmmmmmmmmmmm(arg1111111111111);
}
\ No newline at end of file
diff --git a/test/tall/regression/0000/0055.unit b/test/tall/regression/0000/0055.unit
index cd2dac6..08b36ef 100644
--- a/test/tall/regression/0000/0055.unit
+++ b/test/tall/regression/0000/0055.unit
@@ -8,6 +8,11 @@
bool isSdkDir(String dirname) => new File(
path.join(dirname, 'lib', '_internal', 'libraries.dart'),
).existsSync();
+<<< 3.7
+ bool isSdkDir(String dirname) =>
+ new File(
+ path.join(dirname, 'lib', '_internal', 'libraries.dart'),
+ ).existsSync();
>>> (indent 6)
bool isSdkDir(String dirname) =>
new File(path.join(dirname, 'lib', '_internal', 'libraries.dart'))
@@ -16,6 +21,11 @@
bool isSdkDir(String dirname) => new File(
path.join(dirname, 'lib', '_internal', 'libraries.dart'),
).existsSync();
+<<< 3.7
+ bool isSdkDir(String dirname) =>
+ new File(
+ path.join(dirname, 'lib', '_internal', 'libraries.dart'),
+ ).existsSync();
>>> (indent 12)
bool isSdkDir(String dirname) =>
new File(path.join(dirname, 'lib', '_internal', 'libraries.dart'))
@@ -23,4 +33,9 @@
<<<
bool isSdkDir(String dirname) => new File(
path.join(dirname, 'lib', '_internal', 'libraries.dart'),
- ).existsSync();
\ No newline at end of file
+ ).existsSync();
+<<< 3.7
+ bool isSdkDir(String dirname) =>
+ new File(
+ path.join(dirname, 'lib', '_internal', 'libraries.dart'),
+ ).existsSync();
\ No newline at end of file
diff --git a/test/tall/regression/0000/0056.stmt b/test/tall/regression/0000/0056.stmt
index a1de670..a921abf 100644
--- a/test/tall/regression/0000/0056.stmt
+++ b/test/tall/regression/0000/0056.stmt
@@ -4,4 +4,7 @@
_sources[uri] = src = new _MockSdkSource(
uri,
'library dart.${uri.path};',
- );
\ No newline at end of file
+ );
+<<< 3.7
+ _sources[uri] =
+ src = new _MockSdkSource(uri, 'library dart.${uri.path};');
\ No newline at end of file
diff --git a/test/tall/regression/0000/0070.stmt b/test/tall/regression/0000/0070.stmt
index 64ef4e2..59e7b4c 100644
--- a/test/tall/regression/0000/0070.stmt
+++ b/test/tall/regression/0000/0070.stmt
@@ -6,4 +6,11 @@
### and we split at `.` instead.
bool isSdkDir(String dirname) => new File(
path.join(dirname, 'lib', '_internal', 'libraries.dart'),
- ).existsSync();
\ No newline at end of file
+ ).existsSync();
+<<< 3.7
+### TODO(rnystrom): Would look better if the constructor arguments didn't split
+### and we split at `.` instead.
+ bool isSdkDir(String dirname) =>
+ new File(
+ path.join(dirname, 'lib', '_internal', 'libraries.dart'),
+ ).existsSync();
\ No newline at end of file
diff --git a/test/tall/regression/0000/0080.unit b/test/tall/regression/0000/0080.unit
index a9638a5..04990b1 100644
--- a/test/tall/regression/0000/0080.unit
+++ b/test/tall/regression/0000/0080.unit
@@ -114,4 +114,86 @@
results.infoMap,
results.rules,
);
+}
+<<< 3.7
+void main(List argv) {
+ // This is ok (it moves the ? and : to the ends of the previous lines)
+ var cg =
+ outputDart
+ ? new A(
+ outputDir,
+ uri,
+ results.libraries,
+ results.infoMap,
+ results.rules,
+ formatOutput,
+ )
+ : new B(
+ outputDir,
+ uri,
+ results.libraries,
+ results.infoMap,
+ results.rules,
+ );
+
+ // This gets rewritten to something much harder to read
+ // var cg = outputDart ?
+ // new ALongerClass(outputDir, uri, results.libraries, results.infoMap,
+ // results.rules, formatOutput) : new BLongerClass(
+ // outputDir, uri, results.libraries, results.infoMap, results.rules);
+ var cg =
+ outputDart
+ ? new ALongerClass(
+ outputDir,
+ uri,
+ results.libraries,
+ results.infoMap,
+ results.rules,
+ formatOutput,
+ )
+ : new BLongerClass(
+ outputDir,
+ uri,
+ results.libraries,
+ results.infoMap,
+ results.rules,
+ );
+
+ // OK, left unchanged
+ var cg =
+ outputDart
+ ? new A(
+ outputDir,
+ uri,
+ results.libraries,
+ results.infoMap,
+ results.rules,
+ formatOutput,
+ )
+ : new B(
+ outputDir,
+ uri,
+ results.libraries,
+ results.infoMap,
+ results.rules,
+ );
+
+ // Bad, as above
+ var cg =
+ outputDart
+ ? new ALongerClass(
+ outputDir,
+ uri,
+ results.libraries,
+ results.infoMap,
+ results.rules,
+ formatOutput,
+ )
+ : new BLongerClass(
+ outputDir,
+ uri,
+ results.libraries,
+ results.infoMap,
+ results.rules,
+ );
}
\ No newline at end of file
diff --git a/test/tall/regression/0100/0112.stmt b/test/tall/regression/0100/0112.stmt
index 1182bc2..9443930 100644
--- a/test/tall/regression/0100/0112.stmt
+++ b/test/tall/regression/0100/0112.stmt
@@ -11,6 +11,14 @@
something.field.where((i) => i is SomeClassGoesHere),
key: (i) => i.someField.name,
);
+<<< 3.7
+ var initializers =
+ something == null
+ ? {}
+ : new Map.fromIterable(
+ something.field.where((i) => i is SomeClassGoesHere),
+ key: (i) => i.someField.name,
+ );
>>> (indent 4)
var cg = outputDart ? new DartGenerator(
outputDir, uri, results.libraries, results.rules, formatOutput) :
@@ -24,4 +32,15 @@
results.rules,
formatOutput,
)
- : new JSGenerator(outputDir, uri, results.libraries, results.rules);
\ No newline at end of file
+ : new JSGenerator(outputDir, uri, results.libraries, results.rules);
+<<< 3.7
+ var cg =
+ outputDart
+ ? new DartGenerator(
+ outputDir,
+ uri,
+ results.libraries,
+ results.rules,
+ formatOutput,
+ )
+ : new JSGenerator(outputDir, uri, results.libraries, results.rules);
\ No newline at end of file
diff --git a/test/tall/regression/0100/0122.unit b/test/tall/regression/0100/0122.unit
index de70a38..07f9a9d 100644
--- a/test/tall/regression/0100/0122.unit
+++ b/test/tall/regression/0100/0122.unit
@@ -10,6 +10,13 @@
? xsrfCookieName
: defaults.xsrfCookieName]
: null;
+<<< 3.7
+var xsrfValue =
+ _urlIsSameOrigin(url)
+ ? _cookies[xsrfCookieName != null
+ ? xsrfCookieName
+ : defaults.xsrfCookieName]
+ : null;
>>>
class _Streams {
_Streams(this._scope, this._exceptionHandler, _Streams inheritStreams)
@@ -23,4 +30,12 @@
: _typeCounts = inheritStreams == null
? new HashMap<String, int>()
: new HashMap.from(inheritStreams._typeCounts);
+}
+<<< 3.7
+class _Streams {
+ _Streams(this._scope, this._exceptionHandler, _Streams inheritStreams)
+ : _typeCounts =
+ inheritStreams == null
+ ? new HashMap<String, int>()
+ : new HashMap.from(inheritStreams._typeCounts);
}
\ No newline at end of file
diff --git a/test/tall/regression/0100/0135.stmt b/test/tall/regression/0100/0135.stmt
index 66b0cd6..05d3b99 100644
--- a/test/tall/regression/0100/0135.stmt
+++ b/test/tall/regression/0100/0135.stmt
@@ -6,4 +6,10 @@
var handler = new Cascade()
.add((_) => new Response.notFound('handler 1'))
.add((_) => new Response.ok('handler 2'))
- .handler;
\ No newline at end of file
+ .handler;
+<<< 3.7
+ var handler =
+ new Cascade()
+ .add((_) => new Response.notFound('handler 1'))
+ .add((_) => new Response.ok('handler 2'))
+ .handler;
\ No newline at end of file
diff --git a/test/tall/regression/0100/0158.unit b/test/tall/regression/0100/0158.unit
index 9e4c556..1298038 100644
--- a/test/tall/regression/0100/0158.unit
+++ b/test/tall/regression/0100/0158.unit
@@ -17,6 +17,15 @@
}
}
}
+<<< 3.7
+void foo() {
+ if (bar) {
+ if (baz) {
+ _sources[uri] =
+ src = new _MockSdkSource(uri, 'library dart.${uri.path};');
+ }
+ }
+}
>>>
class Foo {
static LibrarySummary parse(Map json) => new LibrarySummary(json[
diff --git a/test/tall/regression/0100/0186.stmt b/test/tall/regression/0100/0186.stmt
index 90fe592..8ed44f5 100644
--- a/test/tall/regression/0100/0186.stmt
+++ b/test/tall/regression/0100/0186.stmt
@@ -34,4 +34,26 @@
},
},
],
+);
+<<< 3.7
+return JSON.encode(
+ (span == null)
+ ? [
+ {
+ 'method': kind,
+ 'params': {'message': entry.message},
+ },
+ ]
+ : [
+ {
+ 'method': kind,
+ 'params': {
+ 'file': span.sourceUrl.toString(),
+ 'message': entry.message,
+ 'line': span.start.line + 1,
+ 'charStart': span.start.offset,
+ 'charEnd': span.end.offset,
+ },
+ },
+ ],
);
\ No newline at end of file
diff --git a/test/tall/regression/0200/0203.stmt b/test/tall/regression/0200/0203.stmt
index 980684a..ef5ecde 100644
--- a/test/tall/regression/0200/0203.stmt
+++ b/test/tall/regression/0200/0203.stmt
@@ -5,6 +5,11 @@
<<<
var constructedRequest = new Request()
..campaignId = (rollup.campaignId == null ? new Int64(0) : rollup.campaignId);
+<<< 3.7
+var constructedRequest =
+ new Request()
+ ..campaignId =
+ (rollup.campaignId == null ? new Int64(0) : rollup.campaignId);
>>> (indent 2)
var constructedRequest = new Request()
..campaignId =
@@ -13,4 +18,9 @@
var constructedRequest = new Request()
..campaignId = (rollup.campaignId == null
? new Int64(0)
- : rollup.campaignId);
\ No newline at end of file
+ : rollup.campaignId);
+<<< 3.7
+ var constructedRequest =
+ new Request()
+ ..campaignId =
+ (rollup.campaignId == null ? new Int64(0) : rollup.campaignId);
\ No newline at end of file
diff --git a/test/tall/regression/0200/0204.stmt b/test/tall/regression/0200/0204.stmt
index a5df56c..4eab978 100644
--- a/test/tall/regression/0200/0204.stmt
+++ b/test/tall/regression/0200/0204.stmt
@@ -7,6 +7,11 @@
var serverDuration = new Duration(
microseconds: response.requestProcessingTimeUsec.toInt(),
).inMilliseconds.toString();
+<<< 3.7
+var serverDuration =
+ new Duration(
+ microseconds: response.requestProcessingTimeUsec.toInt(),
+ ).inMilliseconds.toString();
>>> (indent 8)
var serverDuration = new Duration(
microseconds: response.requestProcessingTimeUsec.toInt())
@@ -15,4 +20,9 @@
### TODO(rnystrom): I think it would look better to split the method chain.
var serverDuration = new Duration(
microseconds: response.requestProcessingTimeUsec.toInt(),
- ).inMilliseconds.toString();
\ No newline at end of file
+ ).inMilliseconds.toString();
+<<< 3.7
+ var serverDuration =
+ new Duration(
+ microseconds: response.requestProcessingTimeUsec.toInt(),
+ ).inMilliseconds.toString();
\ No newline at end of file
diff --git a/test/tall/regression/0200/0221.unit b/test/tall/regression/0200/0221.unit
index a792dbb..ea74b6d 100644
--- a/test/tall/regression/0200/0221.unit
+++ b/test/tall/regression/0200/0221.unit
@@ -19,6 +19,19 @@
..cell = new Cell(onSelection))
.build();
}
+<<< 3.7
+class Foo {
+ static Column column(Handler onSelection) =>
+ (Column.defaultBuilder(videoMsg())
+ ..id = 'VIDEO'
+ ..segment =
+ ((row) =>
+ row.segmentedStats
+ .map((s) => s.get(Stats.SEGMENTATION))
+ .toList())
+ ..cell = new Cell(onSelection))
+ .build();
+}
>>>
void _updateChart() {
if (_model.settings != null) {
@@ -108,4 +121,14 @@
? OrderBySortOrder.ASCENDING
: OrderBySortOrder.DESCENDING)
.toList();
+}
+<<< 3.7
+main() {
+ (new OrderBy()
+ ..field = s.column
+ ..sortOrder =
+ s.isAscending
+ ? OrderBySortOrder.ASCENDING
+ : OrderBySortOrder.DESCENDING)
+ .toList();
}
\ No newline at end of file
diff --git a/test/tall/regression/0200/0224.stmt b/test/tall/regression/0200/0224.stmt
index 1800438..26d2982 100644
--- a/test/tall/regression/0200/0224.stmt
+++ b/test/tall/regression/0200/0224.stmt
@@ -51,6 +51,33 @@
.then((_) {
_logger.info("Let's eat!");
});
+<<< 3.7
+ return doughnutFryer
+ .start()
+ .then((_) => _frostingGlazer.start())
+ .then(
+ (_) => Future.wait([
+ _conveyorBelts.start(),
+ sprinkleSprinkler.start(),
+ sauceDripper.start(),
+ ]),
+ )
+ .catchError(cannotGetConveyorBeltRunning)
+ .then((_) => tellEveryoneDonutsAreJustAboutDone())
+ .then(
+ (_) => Future.wait([
+ croissantFactory.start(),
+ _giantBakingOvens.start(),
+ butterbutterer.start(),
+ ])
+ .catchError(_handleBakingFailures)
+ .timeout(scriptLoadingTimeout, onTimeout: _handleBakingFailures)
+ .catchError(cannotGetConveyorBeltRunning),
+ )
+ .catchError(cannotGetConveyorBeltRunning)
+ .then((_) {
+ _logger.info("Let's eat!");
+ });
>>> (indent 22)
someVeryLongFutureWithManyChars().then((foo) {
doAThing();
diff --git a/test/tall/regression/0200/0257.unit b/test/tall/regression/0200/0257.unit
index d9460fa..b287643 100644
--- a/test/tall/regression/0200/0257.unit
+++ b/test/tall/regression/0200/0257.unit
@@ -11,4 +11,12 @@
: foo = (bar == null)
? 'bar is null this is a very long string that causes a split'
: bar.myField;
+}
+<<< 3.7
+class FooConstructor {
+ FooConstructor(Object bar)
+ : foo =
+ (bar == null)
+ ? 'bar is null this is a very long string that causes a split'
+ : bar.myField;
}
\ No newline at end of file
diff --git a/test/tall/regression/0300/0367.stmt b/test/tall/regression/0300/0367.stmt
index 20b683e..13e8062 100644
--- a/test/tall/regression/0300/0367.stmt
+++ b/test/tall/regression/0300/0367.stmt
@@ -15,6 +15,17 @@
),
)
.asBroadcastStream();
+<<< 3.7
+ identifier__ =
+ identifier_____.identifier___
+ .transform(
+ new StreamTransformer<TypeArg____, Type_>.fromHandlers(
+ handleData: (TypeName___ arg__, EventSink<Type_> arg_) {
+ ;
+ },
+ ),
+ )
+ .asBroadcastStream();
>>> (indent 4)
_trigger
.then(ut.expectAsync((result) {
diff --git a/test/tall/regression/0300/0368.unit b/test/tall/regression/0300/0368.unit
index b443514..682d184 100644
--- a/test/tall/regression/0300/0368.unit
+++ b/test/tall/regression/0300/0368.unit
@@ -132,4 +132,75 @@
) {
;
}
+}
+<<< 3.7
+class Compiler {
+ Compiler()
+ : this.options = options,
+ this.allowedLibraryCategories = getAllowedLibraryCategories(options),
+ super(
+ outputProvider: outputProvider,
+ enableTypeAssertions: hasOption(options, '--enable-checked-mode'),
+ enableUserAssertions: hasOption(options, '--enable-checked-mode'),
+ trustTypeAnnotations: hasOption(options, '--trust-type-annotations'),
+ trustPrimitives: hasOption(options, '--trust-primitives'),
+ enableMinification: hasOption(options, '--minify'),
+ preserveUris: hasOption(options, '--preserve-uris'),
+ enableNativeLiveTypeAnalysis:
+ !hasOption(options, '--disable-native-live-type-analysis'),
+ emitJavaScript:
+ !(hasOption(options, '--output-type=dart') ||
+ hasOption(options, '--output-type=dart-multi')),
+ dart2dartMultiFile: hasOption(options, '--output-type=dart-multi'),
+ generateSourceMap: !hasOption(options, '--no-source-maps'),
+ analyzeAllFlag: hasOption(options, '--analyze-all'),
+ analyzeOnly: hasOption(options, '--analyze-only'),
+ analyzeMain: hasOption(options, '--analyze-main'),
+ analyzeSignaturesOnly: hasOption(options, '--analyze-signatures-only'),
+ strips: extractCsvOption(options, '--force-strip='),
+ enableConcreteTypeInference: hasOption(
+ options,
+ '--enable-concrete-type-inference',
+ ),
+ disableTypeInferenceFlag: hasOption(
+ options,
+ '--disable-type-inference',
+ ),
+ preserveComments: hasOption(options, '--preserve-comments'),
+ useCpsIr: hasOption(options, '--use-cps-ir'),
+ verbose: hasOption(options, '--verbose'),
+ sourceMapUri: extractUriOption(options, '--source-map='),
+ outputUri: extractUriOption(options, '--out='),
+ terseDiagnostics: hasOption(options, '--terse'),
+ deferredMapUri: extractUriOption(options, '--deferred-map='),
+ dumpInfo: hasOption(options, '--dump-info'),
+ buildId: extractStringOption(
+ options,
+ '--build-id=',
+ "build number could not be determined",
+ ),
+ showPackageWarnings: hasOption(options, '--show-package-warnings'),
+ useContentSecurityPolicy: hasOption(options, '--csp'),
+ hasIncrementalSupport:
+ forceIncrementalSupport ||
+ hasOption(options, '--incremental-support'),
+ suppressWarnings: hasOption(options, '--suppress-warnings'),
+ fatalWarnings: hasOption(options, '--fatal-warnings'),
+ enableExperimentalMirrors: hasOption(
+ options,
+ '--enable-experimental-mirrors',
+ ),
+ generateCodeWithCompileTimeErrors: hasOption(
+ options,
+ '--generate-code-with-compile-time-errors',
+ ),
+ testMode: hasOption(options, '--test-mode'),
+ allowNativeExtensions: hasOption(options, '--allow-native-extensions'),
+ enableNullAwareOperators: hasOption(
+ options,
+ '--enable-null-aware-operators',
+ ),
+ ) {
+ ;
+ }
}
\ No newline at end of file
diff --git a/test/tall/regression/0300/0380.unit b/test/tall/regression/0300/0380.unit
index 6a3a6dd..adfd05e 100644
--- a/test/tall/regression/0300/0380.unit
+++ b/test/tall/regression/0300/0380.unit
@@ -22,4 +22,17 @@
.then((item) => results.add(item.name.value)),
)
.toList();
+}
+<<< 3.7
+test() {
+ var fooService, ids, objectName, results;
+
+ var futures =
+ ids
+ .map(
+ (id) => fooService
+ .getItem(objectName, id)
+ .then((item) => results.add(item.name.value)),
+ )
+ .toList();
}
\ No newline at end of file
diff --git a/test/tall/regression/0300/0383.unit b/test/tall/regression/0300/0383.unit
index c6d7ca3..56e4f28 100644
--- a/test/tall/regression/0300/0383.unit
+++ b/test/tall/regression/0300/0383.unit
@@ -9,4 +9,12 @@
: publishers = publishers != null
? publishers
: defaultPublishers.add(unittestPublisher);
+}
+<<< 3.7
+class MummyMatchers {
+ MummyMatchers([List<ResultPublisher> publishers])
+ : publishers =
+ publishers != null
+ ? publishers
+ : defaultPublishers.add(unittestPublisher);
}
\ No newline at end of file
diff --git a/test/tall/regression/0300/0394.stmt b/test/tall/regression/0300/0394.stmt
index d7843ff..d836bb4 100644
--- a/test/tall/regression/0300/0394.stmt
+++ b/test/tall/regression/0300/0394.stmt
@@ -39,4 +39,29 @@
inner: [$.P(id: "notes", inner: "${seeds} seeds")],
),
],
+);
+<<< 3.7
+return $.Div(
+ inner: [
+ $.Div(
+ id: "container",
+ inner: [
+ $.Canvas(width: maxD, height: maxD, clazz: "center", ref: canvas),
+ $.Form(
+ clazz: "center",
+ inner: [
+ $.Input(
+ type: "range",
+ max: 1000,
+ value: seeds,
+ onChange: onSliderChange,
+ ),
+ ],
+ ),
+ $.Img(src: "math.png", width: "350px", height: "42px", clazz: "center"),
+ ],
+ ),
+
+ $.Footer(inner: [$.P(id: "notes", inner: "${seeds} seeds")]),
+ ],
);
\ No newline at end of file
diff --git a/test/tall/regression/0400/0407.unit b/test/tall/regression/0400/0407.unit
index f6a06f7..28fb288 100644
--- a/test/tall/regression/0400/0407.unit
+++ b/test/tall/regression/0400/0407.unit
@@ -12,6 +12,18 @@
..tags = (new Account_Tags()
..accountHotlist.add(new Hotlist()..hotlistId = new Int64(10))));
}
+<<< 3.7
+void main() {
+ model
+ ..account =
+ (new Account()
+ ..accountId = new Int64(111)
+ ..tags =
+ (new Account_Tags()
+ ..accountHotlist.add(
+ new Hotlist()..hotlistId = new Int64(10),
+ )));
+}
>>> (indent 4)
main() {
receiver
@@ -30,6 +42,19 @@
? _formatter.formatAsPercent(item.value / _total, fractionDigits: 1)
: _formatter.formatValue(item.value, item.valueType);
}
+<<< 3.7
+ main() {
+ receiver
+ ..formattedTotal =
+ _total == 0
+ ? ""
+ : _chartType == "PieChart"
+ ? _formatter.formatAsPercent(
+ item.value / _total,
+ fractionDigits: 1,
+ )
+ : _formatter.formatValue(item.value, item.valueType);
+ }
>>> (indent 6)
main() {
receiver
@@ -50,4 +75,17 @@
fractionDigits: 1,
)
: _formatter.formatValue(item.value, item.valueType);
+ }
+<<< 3.7
+ main() {
+ receiver
+ ..formattedTotal =
+ _total == 0
+ ? ""
+ : _chartType == "PieChart"
+ ? _formatter.formatAsPercent(
+ item.value / _total,
+ fractionDigits: 1,
+ )
+ : _formatter.formatValue(item.value, item.valueType);
}
\ No newline at end of file
diff --git a/test/tall/regression/0400/0436.unit b/test/tall/regression/0400/0436.unit
index 2769243..b072758 100644
--- a/test/tall/regression/0400/0436.unit
+++ b/test/tall/regression/0400/0436.unit
@@ -14,4 +14,13 @@
? interfaceTypeContext.element.typeParameters
: null;
}
+}
+<<< 3.7
+class Foo {
+ void main() {
+ List<TypeParameterElement> typeParameterElements =
+ interfaceTypeContext.element != null
+ ? interfaceTypeContext.element.typeParameters
+ : null;
+ }
}
\ No newline at end of file
diff --git a/test/tall/regression/0400/0456.unit b/test/tall/regression/0400/0456.unit
index 32fe575..db650f7 100644
--- a/test/tall/regression/0400/0456.unit
+++ b/test/tall/regression/0400/0456.unit
@@ -4467,6 +4467,43 @@
..aaaAaaaaaaaaaa = aaaaa.aaaAaaaaaaaaaa
..aaaaAaaaaaaa = aaaaa.aaaaAaaaaaaa;
}
+<<< 3.7
+class Aaa {
+ @AaaaAaaaaaaaaa()
+ static AaaaaAaaaAaaaa aaaaaaaaaaa(
+ AaaaaAaaaAaaaa aaaaa,
+ AaaaaAaaaAaaaa aaaaa,
+ aaaaaa aaaaaaaAaaaaa,
+ ) =>
+ new AaaaaAaaaAaaaa()
+ ..aaaaAaaaaa = new Aaa64(
+ (aaaaa.aaaaAaaaaa.aaAaaaaa() +
+ ((aaaaa.aaaaAaaaaa - aaaaa.aaaaAaaaaa).aaAaaaaa() *
+ aaaaaaaAaaaaa))
+ .aaAaa(),
+ )
+ ..aaaAaaAaaaaa = new Aaa64(
+ (aaaaa.aaaAaaAaaaaa.aaAaaaaa() +
+ ((aaaaa.aaaAaaAaaaaa - aaaaa.aaaAaaAaaaaa).aaAaaaaa() *
+ aaaaaaaAaaaaa))
+ .aaAaa(),
+ )
+ ..aaaaaa =
+ aaaaa.aaaaaa + ((aaaaa.aaaaaa - aaaaa.aaaaaa) * aaaaaaaAaaaaa)
+ ..aaaaaaaaaaa =
+ aaaaa.aaaaaaaaaaa +
+ ((aaaaa.aaaaaaaaaaa - aaaaa.aaaaaaaaaaa) * aaaaaaaAaaaaa)
+ ..aaaaAaaaa =
+ aaaaa.aaaaAaaaa +
+ ((aaaaa.aaaaAaaaa - aaaaa.aaaaAaaaa) * aaaaaaaAaaaaa)
+ ..aaaaaaaaAaaaaaaaaaa =
+ aaaaa.aaaaaaaaAaaaaaaaaaa +
+ ((aaaaa.aaaaaaaaAaaaaaaaaaa - aaaaa.aaaaaaaaAaaaaaaaaaa) *
+ aaaaaaaAaaaaa)
+ ..aaaaa = aaaaa.aaaaa + ((aaaaa.aaaaa - aaaaa.aaaaa) * aaaaaaaAaaaaa)
+ ..aaaAaaaaaaaaaa = aaaaa.aaaAaaaaaaaaaa
+ ..aaaaAaaaaaaa = aaaaa.aaaaAaaaaaaa;
+}
>>>
main() {
{
diff --git a/test/tall/regression/0500/0500.unit b/test/tall/regression/0500/0500.unit
index 638b625..47d0681 100644
--- a/test/tall/regression/0500/0500.unit
+++ b/test/tall/regression/0500/0500.unit
@@ -42,4 +42,30 @@
ek("NoSuchMethodError: method not found: '${names[i]}='"),
);
}
+ };
+<<< 3.7
+ Decl immutableVariableDeclaration(
+ List<String> names,
+ List<Expr> initializers,
+ ) => (Environment env) {
+ assert(names.length == initializers.length);
+ 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",
+ );
+ initializers[i](env, ek, (v) {
+ binding.getter =
+ (TopLevelBinding _, ExprCont ek1, ExprCont k1) => k1(v);
+ return k(v);
+ });
+ },
+ setter:
+ (value, ExprCont ek, ExprCont k) =>
+ ek("NoSuchMethodError: method not found: '${names[i]}='"),
+ );
+ }
};
\ No newline at end of file
diff --git a/test/tall/regression/0500/0506.unit b/test/tall/regression/0500/0506.unit
index 11beb69..b7dc6eb 100644
--- a/test/tall/regression/0500/0506.unit
+++ b/test/tall/regression/0500/0506.unit
@@ -12,6 +12,15 @@
Optional<double> get finiteSignificantCost =>
isSignificant && cost.value.isFinite ? cost : const Optional.absent();
}
+<<< 3.7
+class Foo {
+ Optional<double> get finiteSignificantCost =>
+ isSignificant && cost.value.isFinite
+ ? cost
+ : const Optional<double>.absent();
+ Optional<double> get finiteSignificantCost =>
+ isSignificant && cost.value.isFinite ? cost : const Optional.absent();
+}
>>> (indent 4)
main() {
this.changes.exec();
@@ -48,6 +57,19 @@
? 1
: 0;
}
+<<< 3.7
+ main() {
+ var openParensMatch = new RegExp(r'\(').allMatches(matchStr),
+ closeParensMatch = new RegExp(r'\)').allMatches(matchStr),
+ numOpenParens =
+ (openParensMatch != null && openParensMatch.length != null)
+ ? 1
+ : 0,
+ numCloseParens =
+ (closeParensMatch != null && closeParensMatch.length != null)
+ ? 1
+ : 0;
+ }
>>> (indent 4)
main() {
tree.Node definition = new tree.FunctionExpression(
diff --git a/test/tall/regression/0500/0589.unit b/test/tall/regression/0500/0589.unit
index 86ebe06..40c6abb 100644
--- a/test/tall/regression/0500/0589.unit
+++ b/test/tall/regression/0500/0589.unit
@@ -29,4 +29,21 @@
reference(globalComponent.className, globalComponent.importPath),
]..addAll(_generateUsedComponentReferences(globals, c));
}).toList();
+}
+<<< 3.7
+List<dynamic> _generateUsedComponentReferences(
+ Globals globals,
+ FormComponent component,
+) {
+ return component.children == null
+ ? <dynamic>[]
+ : component.children.map<List<dynamic>>((c) {
+ final globalComponent = globals.components.firstWhere(
+ (c) => c.id == component.component.componentId,
+ orElse: () => null,
+ );
+ <dynamic>[
+ reference(globalComponent.className, globalComponent.importPath),
+ ]..addAll(_generateUsedComponentReferences(globals, c));
+ }).toList();
}
\ No newline at end of file
diff --git a/test/tall/regression/0600/0620.unit b/test/tall/regression/0600/0620.unit
index 4420115..1fef7a0 100644
--- a/test/tall/regression/0600/0620.unit
+++ b/test/tall/regression/0600/0620.unit
@@ -70,4 +70,66 @@
),
false,
(compiledRules, valueCount, reducer) => new _ChoiceRule(compiledRules),
+);
+<<< 3.7
+_Rule OR([
+ a,
+ b,
+ c,
+ d,
+ e,
+ f,
+ g,
+ h,
+ i,
+ j,
+ k,
+ l,
+ m,
+ n,
+ o,
+ p,
+ q,
+ r,
+ s, //
+ t,
+ u,
+ v,
+ w,
+ x,
+ y,
+ z,
+]) => _compileMultiRule(
+ (a is List && b == null) // Backward compat. OR([a, b]) => OR(a, b).
+ ? a
+ : _unspread(
+ a,
+ b,
+ c,
+ d,
+ e,
+ f,
+ g,
+ h,
+ i,
+ j,
+ k,
+ l,
+ m,
+ n,
+ o,
+ p,
+ q,
+ r,
+ s,
+ t,
+ u,
+ v,
+ w,
+ x,
+ y,
+ z,
+ ),
+ false,
+ (compiledRules, valueCount, reducer) => new _ChoiceRule(compiledRules),
);
\ No newline at end of file
diff --git a/test/tall/regression/0700/0705.stmt b/test/tall/regression/0700/0705.stmt
index 110bec9..1804ec8 100644
--- a/test/tall/regression/0700/0705.stmt
+++ b/test/tall/regression/0700/0705.stmt
@@ -7,4 +7,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';
\ No newline at end of file
+ 'adwords';
+<<< 3.7
+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/tall/regression/0700/0711.stmt b/test/tall/regression/0700/0711.stmt
index fa8b929..714a1b0 100644
--- a/test/tall/regression/0700/0711.stmt
+++ b/test/tall/regression/0700/0711.stmt
@@ -10,4 +10,10 @@
a
b
c
+"""}''';
+<<< 3.7
+var str = '''${"""
+a
+b
+c
"""}''';
\ No newline at end of file
diff --git a/test/tall/regression/0700/0713.stmt b/test/tall/regression/0700/0713.stmt
index 4a47471..70fd4b6 100644
--- a/test/tall/regression/0700/0713.stmt
+++ b/test/tall/regression/0700/0713.stmt
@@ -9,4 +9,13 @@
? 'warnings'
: status == 'BAD'
? 'errors'
- : '';
\ No newline at end of file
+ : '';
+<<< 3.7
+String type =
+ status == 'OK'
+ ? 'notices'
+ : status == 'NO'
+ ? 'warnings'
+ : status == 'BAD'
+ ? 'errors'
+ : '';
\ No newline at end of file
diff --git a/test/tall/regression/0700/0722.stmt b/test/tall/regression/0700/0722.stmt
index ef45634..4901195 100644
--- a/test/tall/regression/0700/0722.stmt
+++ b/test/tall/regression/0700/0722.stmt
@@ -28,4 +28,19 @@
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.w600),
),
+);
+<<< 3.7
+Widget(
+ child:
+ project.locked
+ ? Icon(Icons.lock)
+ : project.fav
+ ? Icon(Icons.star)
+ : project.taps == null
+ ? Icon(Icons.notifications)
+ : Text(
+ suffixNumber(project.taps),
+ textAlign: TextAlign.center,
+ style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.w600),
+ ),
);
\ No newline at end of file
diff --git a/test/tall/regression/0700/0731.stmt b/test/tall/regression/0700/0731.stmt
index 9a7a3b3..5deab10 100644
--- a/test/tall/regression/0700/0731.stmt
+++ b/test/tall/regression/0700/0731.stmt
@@ -78,6 +78,15 @@
);
return entryList;
});
+<<< 3.7
+ return Observable(_databaseService.getChatEntries(event)).map((entryList) {
+ entryList.forEach(
+ (entry) =>
+ entry.isFromCurrentUser =
+ (entry.userId == _userManager.currentUser.id),
+ );
+ return entryList;
+ });
>>> (indent 4)
return Observable(_databaseService.getChatEntries(
event,
@@ -104,4 +113,20 @@
})
.map((entry) {
return entry.toString();
+ });
+<<< 3.7
+ return Observable(_databaseService.getChatEntries(event))
+ .map((entryList) {
+ entryList.forEach(
+ (entry) =>
+ entry.isFromCurrentUser =
+ (entry.userId == _userManager.currentUser.id),
+ );
+ return entryList;
+ })
+ .where((entry) {
+ return entry != null;
+ })
+ .map((entry) {
+ return entry.toString();
});
\ No newline at end of file
diff --git a/test/tall/regression/0700/0796.unit b/test/tall/regression/0700/0796.unit
index 8cfec1c..e1d009b 100644
--- a/test/tall/regression/0700/0796.unit
+++ b/test/tall/regression/0700/0796.unit
@@ -18,4 +18,14 @@
Its scent can fill a room for days. And all this before you\'ve even cooked it.
'''
.replaceAll('\n', '');
+}
+<<< 3.7
+class C {
+ static final String paragraph1 =
+ '''Have you ever held a quince? It\'s strange;
+ covered in a fuzz somewhere between peach skin and a spider web. And it\'s
+ hard as soft lumber. You\'d be forgiven for thinking it\'s veneered Larch-wood.
+ But inhale the aroma and you\'ll instantly know you have something wonderful.
+ Its scent can fill a room for days. And all this before you\'ve even cooked it.
+'''.replaceAll('\n', '');
}
\ No newline at end of file
diff --git a/test/tall/regression/0800/0837.unit b/test/tall/regression/0800/0837.unit
index 79b84bd..c888fb8 100644
--- a/test/tall/regression/0800/0837.unit
+++ b/test/tall/regression/0800/0837.unit
@@ -11,6 +11,19 @@
..ff8[var2aoeu])] =
{},
);
+<<< 3.7
+String var80 =
+ () => ('Ss\u2665\u26659d').replanullceRange(
+ {},
+ var3,
+ /* aaaaaaaaaaaaaaaaaaaaaaaaaaexportaaa */ -9223372036854775807,
+ -64,
+ arexternalg3:
+ '\u26650P'
+ ..[(var2 << var5
+ ..ff8[var2aoeu])] =
+ {},
+ );
>>>
String var80 = () => ('Ss\u2665\u26659d').replanullceRange({}, var3,
/* aaaaaaaaaaaaaaaaaaaaaaaaaaexportaaa */ -9223372036854775807, -64,
@@ -28,6 +41,19 @@
..ff8[var2aoeu])] =
{},
);
+<<< 3.7
+String var80 =
+ () => ('Ss\u2665\u26659d').replanullceRange(
+ {},
+ var3,
+ /* aaaaaaaaaaaaaaaaaaaaaaaaaaexportaaa */ -9223372036854775807,
+ -64,
+ arexternalg3:
+ '\u26650P'
+ ..[(var2 << var5
+ ..ff8[var2aoeu])] =
+ {},
+ );
>>>
doString var80 = ('Ss\u2665\u26659d').replaceRange( { }, var3,/* aaaaaaaaaaaaaaaaaaaaaaaaaaaaa static*/-9223372036854775807, -64, arg3: '\u26650P');
<<<
diff --git a/test/tall/regression/0800/0855.unit b/test/tall/regression/0800/0855.unit
index e97db9e..c90858e 100644
--- a/test/tall/regression/0800/0855.unit
+++ b/test/tall/regression/0800/0855.unit
@@ -55,4 +55,43 @@
).nullSafeProperty('send').call([refer('result')]).statement,
refer('exitCode', 'dart:io').assign(refer('result')).statement,
]),
+);
+<<< 3.7
+Method _main() => Method(
+ (b) =>
+ b
+ ..name = 'main'
+ ..modifier = MethodModifier.async
+ ..requiredParameters.add(
+ Parameter(
+ (b) =>
+ b
+ ..name = 'args'
+ ..type = TypeReference(
+ (b) =>
+ b
+ ..symbol = 'List'
+ ..types.add(refer('String')),
+ ),
+ ),
+ )
+ ..optionalParameters.add(
+ Parameter(
+ (b) =>
+ b
+ ..name = 'sendPort'
+ ..type = refer('SendPort', 'dart:isolate'),
+ ),
+ )
+ ..body = Block.of([
+ refer('run', 'package:build_runner/build_runner.dart')
+ .call([refer('args'), refer('_builders')])
+ .awaited
+ .assignVar('result')
+ .statement,
+ refer(
+ 'sendPort',
+ ).nullSafeProperty('send').call([refer('result')]).statement,
+ refer('exitCode', 'dart:io').assign(refer('result')).statement,
+ ]),
);
\ No newline at end of file
diff --git a/test/tall/regression/0800/0881.unit b/test/tall/regression/0800/0881.unit
index c4fb1fe..a555109 100644
--- a/test/tall/regression/0800/0881.unit
+++ b/test/tall/regression/0800/0881.unit
@@ -64,6 +64,50 @@
});
});
}
+<<< 3.7
+void main() async {
+ group('my group', () {
+ setUp(() async {
+ final requestHandler =
+ FakeRequestHandler()
+ ..when(
+ withServiceName(Ng2ProtoFooBarBazService.serviceName),
+ ).thenRespond(
+ FooBarBazListResponse()
+ ..entities.add(
+ FooBarBaz()
+ ..fooBarBazEntityId = entityId
+ ..name = 'Test entity'
+ ..baseFooBarBazId = baseFooBarBazId
+ ..entityFooBarBazId = entityFooBarBazId,
+ ),
+ )
+ ..when(
+ allOf(
+ withServiceName(Ng2ProtoFooBarBazService.serviceName),
+ hasFooBarBazId(baseFooBarBazId),
+ ),
+ ).thenRespond(FooBarBazListResponse()..entities.add(baseFooBarBaz))
+ ..when(
+ allOf(
+ withServiceName(Ng2ProtoFooBarBazService.serviceName),
+ hasFooBarBazId(entityFooBarBazId),
+ ),
+ ).thenRespond(
+ FooBarBazListResponse()..entities.add(entityFooBarBaz),
+ )
+ ..when(
+ allOf(
+ withServiceName(Ng2ProtoFooBarBazService.serviceName),
+ hasFooBarBazIds(Set.from([baseFooBarBazId, entityFooBarBazId])),
+ ),
+ ).thenRespond(
+ FooBarBazListResponse()
+ ..entities.addAll([baseFooBarBaz, entityFooBarBaz]),
+ );
+ });
+ });
+}
>>>
aaaa() {
{
@@ -117,6 +161,41 @@
}
}
}
+<<< 3.7
+aaaa() {
+ {
+ {
+ {
+ aaaaa aaaaa = AaaAaaaa.aaaaaaa().aaaaaaa(
+ (a) =>
+ a
+ ..aaaaAaaaa.aaaaaaaaaaaAaAaaa[AaaaAaaaaaaaaaAaaa_Aaaa
+ .AAA_AAAAAAAAAAA_AAAAA_AAAAAAA
+ .aaaa] =
+ aaaa
+ ..aaaaAaaaa.aaaaaaaaaaaAaAaaa[AaaaAaaaaaaaaaAaaa_Aaaa
+ .AAA_AAAAAAAAAAA_AAAAAAAA_AAAA_AAAAAAAAA
+ .aaaa] =
+ aaaa
+ ..aaaaAaaaa.aaaaaaaaaaaAaAaaa[AaaaAaaaaaaaaaAaaa_Aaaa
+ .AAA_AAAAAAAAAAA_AAAAAAA_AAAA_AAAAAAAAA
+ .aaaa] =
+ aaaa
+ ..aaaaAaaaa.aaaaaaaaaaaAaAaaa[AaaaAaaaaaaaaaAaaa_Aaaa
+ .AAA_AAAAAAAAAAA_AAAAAA_AAAAAAA_AAA_AAAAAAAAAAA_AAAAA
+ .aaaa] =
+ aaaa
+ ..aaaaaaaaAaaaaaAaaaa.aaaaaaa(
+ AaaaaaaaAaaaaaAaaaaAaaaa.aaaaaaa(),
+ )
+ ..aaaaaaaaAaaaa.aaaaaaaa =
+ (Aaaaaaaa()
+ ..aaaaaaaaAaaa = (AaaaaaaaAaaa()..aaAaaaaaa = aaaaa)),
+ );
+ }
+ }
+ }
+}
>>>
void main() {
Example()
diff --git a/test/tall/regression/0900/0900.stmt b/test/tall/regression/0900/0900.stmt
index 7bcef02..71363ce 100644
--- a/test/tall/regression/0900/0900.stmt
+++ b/test/tall/regression/0900/0900.stmt
@@ -21,6 +21,17 @@
});
})
.toList();
+<<< 3.7
+final commonParameters =
+ constructrorsNeedsGeneration.first.parameters.allParameters.where((
+ parameter,
+ ) {
+ return constructrorsNeedsGeneration.every((constructor) {
+ return constructor.parameters.allParameters.any((p) {
+ return p.name == parameter.name && p.type == parameter.type;
+ });
+ });
+ }).toList();
>>>
void main() {
final f = stdin.readLineSync,
diff --git a/test/tall/regression/0900/0927.unit b/test/tall/regression/0900/0927.unit
index 939d134..acee3d7 100644
--- a/test/tall/regression/0900/0927.unit
+++ b/test/tall/regression/0900/0927.unit
@@ -9,4 +9,13 @@
: _currentSunAngleDeg < 10
? 2
: 3;
+}
+<<< 3.7
+class C {
+ int get currentAngleDigits =>
+ _currentSunAngleDeg < 0
+ ? 1
+ : _currentSunAngleDeg < 10
+ ? 2
+ : 3;
}
\ No newline at end of file
diff --git a/test/tall/regression/1000/1022.stmt b/test/tall/regression/1000/1022.stmt
index 9b2a1c0..7ef266f 100644
--- a/test/tall/regression/1000/1022.stmt
+++ b/test/tall/regression/1000/1022.stmt
@@ -114,4 +114,105 @@
)
: (json['documentChanges'] == null
? null
+ : (throw '''${json['documentChanges']} was not one of (List<TextDocumentEdit>, List<Either4<TextDocumentEdit, CreateFile, RenameFile, DeleteFile>>)''')));
+<<< 3.7
+final documentChanges =
+ (json['documentChanges'] is List &&
+ (json['documentChanges'].every(
+ (item) => TextDocumentEdit.canParse(item, nullLspJsonReporter),
+ )))
+ ? Either2<
+ List<TextDocumentEdit>,
+ List<Either4<TextDocumentEdit, CreateFile, RenameFile, DeleteFile>>
+ >.t1(
+ json['documentChanges']
+ ?.map(
+ (item) => item != null ? TextDocumentEdit.fromJson(item) : null,
+ )
+ ?.cast<TextDocumentEdit>()
+ ?.toList(),
+ )
+ : ((json['documentChanges'] is List &&
+ (json['documentChanges'].every(
+ (item) =>
+ (TextDocumentEdit.canParse(item, nullLspJsonReporter) ||
+ CreateFile.canParse(item, nullLspJsonReporter) ||
+ RenameFile.canParse(item, nullLspJsonReporter) ||
+ DeleteFile.canParse(item, nullLspJsonReporter)),
+ )))
+ ? Either2<
+ List<TextDocumentEdit>,
+ List<
+ Either4<TextDocumentEdit, CreateFile, RenameFile, DeleteFile>
+ >
+ >.t2(
+ json['documentChanges']
+ ?.map(
+ (item) =>
+ TextDocumentEdit.canParse(item, nullLspJsonReporter)
+ ? Either4<
+ TextDocumentEdit,
+ CreateFile,
+ RenameFile,
+ DeleteFile
+ >.t1(
+ item != null
+ ? TextDocumentEdit.fromJson(item)
+ : null,
+ )
+ : (CreateFile.canParse(item, nullLspJsonReporter)
+ ? Either4<
+ TextDocumentEdit,
+ CreateFile,
+ RenameFile,
+ DeleteFile
+ >.t2(
+ item != null
+ ? CreateFile.fromJson(item)
+ : null,
+ )
+ : (RenameFile.canParse(
+ item,
+ nullLspJsonReporter,
+ )
+ ? Either4<
+ TextDocumentEdit,
+ CreateFile,
+ RenameFile,
+ DeleteFile
+ >.t3(
+ item != null
+ ? RenameFile.fromJson(item)
+ : null,
+ )
+ : (DeleteFile.canParse(
+ item,
+ nullLspJsonReporter,
+ )
+ ? Either4<
+ TextDocumentEdit,
+ CreateFile,
+ RenameFile,
+ DeleteFile
+ >.t4(
+ item != null
+ ? DeleteFile.fromJson(item)
+ : null,
+ )
+ : (item == null
+ ? null
+ : (throw '''$item was not one of (TextDocumentEdit, CreateFile, RenameFile, DeleteFile)'''))))),
+ )
+ ?.cast<
+ Either4<
+ TextDocumentEdit,
+ CreateFile,
+ RenameFile,
+ DeleteFile
+ >
+ >()
+ ?.toList(),
+ )
+ : (json['documentChanges'] == null
+ ? null
: (throw '''${json['documentChanges']} was not one of (List<TextDocumentEdit>, List<Either4<TextDocumentEdit, CreateFile, RenameFile, DeleteFile>>)''')));
\ No newline at end of file
diff --git a/test/tall/regression/1000/1085.unit b/test/tall/regression/1000/1085.unit
index 270e987..a940a10 100644
--- a/test/tall/regression/1000/1085.unit
+++ b/test/tall/regression/1000/1085.unit
@@ -15,4 +15,14 @@
param2,
)
: doB();
+}
+<<< 3.7
+void main() {
+ var result =
+ condition
+ ? doA(
+ param1, // comment to force split
+ param2,
+ )
+ : doB();
}
\ No newline at end of file
diff --git a/test/tall/regression/1100/1121.unit b/test/tall/regression/1100/1121.unit
index 6fca36a..e5f6c92 100644
--- a/test/tall/regression/1100/1121.unit
+++ b/test/tall/regression/1100/1121.unit
@@ -14,6 +14,15 @@
bar('baz'), // comment to force split
);
}
+<<< 3.7
+void main() async {
+ (await {
+ 'asdf': 'fdsa', // comment to force split
+ }.toString())
+ .foo(
+ bar('baz'), // comment to force split
+ );
+}
>>>
Future<void> a() {
dynamic foo;
@@ -152,4 +161,24 @@
.map((tokenResponse) {
return tokenResponse.body!.token!;
});
+ }
+<<< 3.7
+ main() {
+ return Stream.value(null)
+ .flatMap(
+ (value) =>
+ _client
+ .apiUserLoginPost(
+ body: LoginRequest(email: email, password: password),
+ )
+ .asStream(),
+ )
+ .doOnData((response) {
+ if (response.error != null) {
+ throw Exception(response.error);
+ }
+ })
+ .map((tokenResponse) {
+ return tokenResponse.body!.token!;
+ });
}
\ No newline at end of file
diff --git a/test/tall/regression/1100/1197.unit b/test/tall/regression/1100/1197.unit
index b007a12..f2fd27b 100644
--- a/test/tall/regression/1100/1197.unit
+++ b/test/tall/regression/1100/1197.unit
@@ -50,6 +50,32 @@
);
}
}
+<<< 3.7
+main() {
+ {
+ return TextFieldTapRegion(
+ onLongPressMoveUpdate: (longPressMoveUpdateDetails) {
+ (switch (Theme.of(this.context).platform) {
+ TargetPlatform.iOS || TargetPlatform.macOS => _renderEditable
+ .selectPositionAt(
+ from: longPressMoveUpdateDetails.globalPosition,
+ cause: SelectionChangedCause.longPress,
+ ),
+ TargetPlatform.android ||
+ TargetPlatform.fuchsia ||
+ TargetPlatform.linux ||
+ TargetPlatform.windows => _renderEditable.selectWordsInRange(
+ from:
+ longPressMoveUpdateDetails.globalPosition -
+ longPressMoveUpdateDetails.offsetFromOrigin,
+ to: longPressMoveUpdateDetails.globalPosition,
+ cause: SelectionChangedCause.longPress,
+ ),
+ });
+ },
+ );
+ }
+}
>>>
main() {
{
@@ -99,4 +125,31 @@
},
);
}
+}
+<<< 3.7
+main() {
+ {
+ return TextFieldTapRegion(
+ onLongPressMoveUpdate:
+ (longPressMoveUpdateDetails) => switch (Theme.of(
+ this.context,
+ ).platform) {
+ TargetPlatform.iOS || TargetPlatform.macOS => _renderEditable
+ .selectPositionAt(
+ from: longPressMoveUpdateDetails.globalPosition,
+ cause: SelectionChangedCause.longPress,
+ ),
+ TargetPlatform.android ||
+ TargetPlatform.fuchsia ||
+ TargetPlatform.linux ||
+ TargetPlatform.windows => _renderEditable.selectWordsInRange(
+ from:
+ longPressMoveUpdateDetails.globalPosition -
+ longPressMoveUpdateDetails.offsetFromOrigin,
+ to: longPressMoveUpdateDetails.globalPosition,
+ cause: SelectionChangedCause.longPress,
+ ),
+ },
+ );
+ }
}
\ No newline at end of file
diff --git a/test/tall/regression/1200/1202.stmt b/test/tall/regression/1200/1202.stmt
index c7b21c0..abcfb68 100644
--- a/test/tall/regression/1200/1202.stmt
+++ b/test/tall/regression/1200/1202.stmt
@@ -15,6 +15,15 @@
listElement,
],
);
+<<< 3.7
+SomeLongFunctionName(
+ argument:
+ () => [
+ listElement,
+ listElement,
+ listElement,
+ ],
+);
>>>
SomeLongFunctionName(
argument: (longParameterName______) =>
@@ -33,6 +42,16 @@
listElement,
],
);
+<<< 3.7
+SomeLongFunctionName(
+ argument:
+ (longParameterName______) =>
+ <LongTypeArgument>[
+ listElement,
+ listElement,
+ listElement,
+ ],
+);
>>>
SomeLongFunctionName(
argument: (longParameterName______) =>
@@ -50,4 +69,14 @@
2 => listElement,
3 => listElement,
},
+);
+<<< 3.7
+SomeLongFunctionName(
+ argument:
+ (longParameterName______) =>
+ switch (e) {
+ 1 => listElement,
+ 2 => listElement,
+ 3 => listElement,
+ },
);
\ No newline at end of file
diff --git a/test/tall/regression/1200/1249.unit b/test/tall/regression/1200/1249.unit
index aa3762a..93019e8 100644
--- a/test/tall/regression/1200/1249.unit
+++ b/test/tall/regression/1200/1249.unit
@@ -11,4 +11,10 @@
_lookup<
ffi.NativeFunction<ffi_Pointer_ffi_Void_ Function(ffi_Size__ffi_Size)>
>('calloc');
+}
+<<< 3.7
+class C {
+ late final _callocPtr = _lookup<
+ ffi.NativeFunction<ffi_Pointer_ffi_Void_ Function(ffi_Size__ffi_Size)>
+ >('calloc');
}
\ No newline at end of file
diff --git a/test/tall/regression/1400/1429.stmt b/test/tall/regression/1400/1429.stmt
index c2a4639..cc8d46f 100644
--- a/test/tall/regression/1400/1429.stmt
+++ b/test/tall/regression/1400/1429.stmt
@@ -65,6 +65,10 @@
PdfDocumentHelper.getHelper(
_helper.crossTable!.document!,
).catalog.beginSaveList ??= <SavePdfPrimitiveCallback>[];
+<<< 3.7
+ PdfDocumentHelper.getHelper(_helper.crossTable!.document!)
+ .catalog
+ .beginSaveList ??= <SavePdfPrimitiveCallback>[];
>>> (indent 12)
PdfSecurityHelper.getHelper(
_helper._security!,
@@ -74,6 +78,10 @@
PdfSecurityHelper.getHelper(
_helper._security!,
).encryptor.encryptOnlyAttachment = false;
+<<< 3.7
+ PdfSecurityHelper.getHelper(_helper._security!)
+ .encryptor
+ .encryptOnlyAttachment = false;
>>> (indent 6)
PdfGridHelper.getHelper(
PdfGridRowHelper.getHelper(_helper.row!).grid,
@@ -83,6 +91,9 @@
PdfGridHelper.getHelper(
PdfGridRowHelper.getHelper(_helper.row!).grid,
).hasColumnSpan = true;
+<<< 3.7
+ PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(_helper.row!).grid)
+ .hasColumnSpan = true;
>>> (indent 2)
Widget build(BuildContext context) => CompositionRoot(
configureOverrides: configureOverrides,
@@ -106,4 +117,17 @@
//We need the BuildContext from the Builder here so the children
//can access the container in the CompositionRoot
Builder(elided),
+ );
+<<< 3.7
+ Widget build(BuildContext context) => CompositionRoot(
+ configureOverrides: configureOverrides,
+ compose:
+ (builder) =>
+ builder
+ //Adds a singleton CounterController to the container
+ ..addSingleton((container) => CounterController()),
+ child:
+ //We need the BuildContext from the Builder here so the children
+ //can access the container in the CompositionRoot
+ Builder(elided),
);
\ No newline at end of file
diff --git a/test/tall/regression/1500/1516.unit b/test/tall/regression/1500/1516.unit
index 889ea4d..0eb8d3f 100644
--- a/test/tall/regression/1500/1516.unit
+++ b/test/tall/regression/1500/1516.unit
@@ -41,4 +41,25 @@
${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
+''';
+<<< 3.7
+var _ = '''
+${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
+${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
+${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
+${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
+${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
+${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
+${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
+${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
+${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
+${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
+${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
+${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
+${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
+${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
+${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
+${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
+${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
+${x(1).x(1).xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
''';
\ No newline at end of file
diff --git a/test/tall/regression/1500/1525.stmt b/test/tall/regression/1500/1525.stmt
index 1c2a69f..1365edc 100644
--- a/test/tall/regression/1500/1525.stmt
+++ b/test/tall/regression/1500/1525.stmt
@@ -36,4 +36,12 @@
'something': someMap['something'],
//...
}),
+);
+<<< 3.7
+map(
+ (innerMap) =>
+ innerMap..addAll({
+ 'something': someMap['something'],
+ //...
+ }),
);
\ No newline at end of file
diff --git a/test/tall/regression/1500/1534.unit b/test/tall/regression/1500/1534.unit
index 4e54ec6..b0f8abc 100644
--- a/test/tall/regression/1500/1534.unit
+++ b/test/tall/regression/1500/1534.unit
@@ -22,4 +22,17 @@
).toString()
: importCache.sourceMapUrl(Uri.parse(url)).toString(),
);
+}
+<<< 3.7
+void main() {
+ mapInPlace(
+ resultSourceMap.urls,
+ (url) =>
+ url == ''
+ ? Uri.dataFromString(
+ stylesheet.span.file.getText(0),
+ encoding: utf8,
+ ).toString()
+ : importCache.sourceMapUrl(Uri.parse(url)).toString(),
+ );
}
\ No newline at end of file
diff --git a/test/tall/regression/1500/1536.unit b/test/tall/regression/1500/1536.unit
index 99e35f2..4458f79 100644
--- a/test/tall/regression/1500/1536.unit
+++ b/test/tall/regression/1500/1536.unit
@@ -20,3 +20,14 @@
limitRepetition: !verbose,
);
}
+<<< 3.7
+void main() {
+ DeprecationProcessingLogger deprecationLogger =
+ logger = DeprecationProcessingLogger(
+ logger ?? Logger.stderr(),
+ silenceDeprecations: {...?silenceDeprecations},
+ fatalDeprecations: {...?fatalDeprecations},
+ futureDeprecations: {...?futureDeprecations},
+ limitRepetition: !verbose,
+ );
+}
\ No newline at end of file
diff --git a/test/tall/regression/1500/1545.unit b/test/tall/regression/1500/1545.unit
index bdb99c4..ad684a9 100644
--- a/test/tall/regression/1500/1545.unit
+++ b/test/tall/regression/1500/1545.unit
@@ -35,6 +35,26 @@
),
);
}
+<<< 3.7
+void _showDialog(Widget child, BuildContext context) {
+ showCupertinoModalPopup<void>(
+ context: context,
+ builder:
+ (BuildContext context) => Container(
+ height: 216,
+ padding: const EdgeInsets.only(top: 6.0),
+ // The Bottom margin is provided to align the popup above the system
+ // navigation bar.
+ margin: EdgeInsets.only(
+ bottom: MediaQuery.of(context).viewInsets.bottom,
+ ),
+ // Provide a background color for the popup.
+ color: CupertinoColors.systemBackground.resolveFrom(context),
+ // Use a SafeArea widget to avoid system overlaps.
+ child: SafeArea(top: false, child: child),
+ ),
+ );
+}
>>>
void _showDialog(Widget child, BuildContext context) {
showCupertinoModalPopup<void>(
@@ -104,4 +124,29 @@
),
),
);
+}
+<<< 3.7
+Widget example1() {
+ return ValueListenableBuilder(
+ valueListenable: userInfoSource,
+ builder:
+ (context, userInfo, _) => ListBody(
+ children: [
+ Text('Hello ${userInfo.name}'),
+ Text('Your email is ${userInfo.email}'),
+ ],
+ ),
+ );
+}
+
+Widget example2() {
+ return LayoutBuilder(
+ builder:
+ (context, constraints) => SingleChildScrollView(
+ child: ConstrainedBox(
+ constraints: BoxConstraints(minHeight: constraints.maxHeight),
+ child: PotentiallyTallWidget(),
+ ),
+ ),
+ );
}
\ No newline at end of file
diff --git a/test/tall/regression/1500/1584.unit b/test/tall/regression/1500/1584.unit
index f7b144a..98cf0e7 100644
--- a/test/tall/regression/1500/1584.unit
+++ b/test/tall/regression/1500/1584.unit
@@ -235,6 +235,147 @@
'aaaaa://aaa.aaaaaaa.aaa/aaaaaaa/aaaaaaaaaaaa/aaaaaa/aaaaaaa_aaaaaaaaaaaa.aaa'), // AA AAAA
]))
..aaaaaa();
+<<< 3.7
+final aaaaaaaaaaaAaaaaaaaAaaa =
+ (AaaaaaaaaaaAaaaaaaaAaaa()
+ ..aaaAaaaaaaa.aaaAaa([
+ (AaaaaaaaaaaAaaaaaaaAaaaaaa()
+ ..aaaaaaaAaaa =
+ 'Aaaaaaa aaaa 1 is aaa 2 aaaaa aaa aaaa aa aaaaaaaaa if aa is aaaa '
+ 'aaaa aaa aaaaa'
+ ..aaaaaaaAaaaa =
+ (AaaaaaaaaaaAaaaaaaaAaaaaaAaaaa()
+ ..aaaaaAaaaaaAaaaaa = 1111.0
+ ..aaaaaAaaaaaAaaaaaaAaaaaa = 0.22)
+ ..aaaaaaaAaaaaaaaaAaa =
+ 'aaaaa://aaa.aaaaaaa.aaa/aaaaaaa/aaaaaaaaaaaa/aaaaaa/aaaaaaa_aaaaaaaaaaaa.aaa'), // AA AAAA
+ (AaaaaaaaaaaAaaaaaaaAaaaaaa()
+ ..aaaaaaaAaaa = 'Aaaaaaa aaaa 2 is a aaaaa aaaa'
+ ..aaaaaaaAaaaa =
+ (AaaaaaaaaaaAaaaaaaaAaaaaaAaaaa()
+ ..aaaaaAaaaaaAaaaaa = 222.0
+ ..aaaaaAaaaaaAaaaaaaAaaaaa = 0.19)
+ ..aaaaaaaAaaaaaaaaAaa =
+ 'aaaaa://aaa.aaaaaaa.aaa/aaaaaaa/aaaaaaaaaaaa/aaaaaa/aaaaaaa_aaaaaaaaaaaa.aaa'), // AA AAAA
+ (AaaaaaaaaaaAaaaaaaaAaaaaaa()
+ ..aaaaaaaAaaa = 'Aaaaaaa aaaa 3'
+ ..aaaaaaaAaaaa =
+ (AaaaaaaaaaaAaaaaaaaAaaaaaAaaaa()
+ ..aaaaaAaaaaaAaaaaa = 33.3
+ ..aaaaaAaaaaaAaaaaaaAaaaaa = 0.15)
+ ..aaaaaaaAaaaaaaaaAaa =
+ 'aaaaa://aaa.aaaaaaa.aaa/aaaaaaa/aaaaaaaaaaaa/aaaaaa/aaaaaaa_aaaaaaaaaaaa.aaa'), // AA AAAA
+ (AaaaaaaaaaaAaaaaaaaAaaaaaa()
+ ..aaaaaaaAaaa = 'Aaaaaaa aaaa 4'
+ ..aaaaaaaAaaaa =
+ (AaaaaaaaaaaAaaaaaaaAaaaaaAaaaa()
+ ..aaaaaAaaaaaAaaaaa = 4
+ ..aaaaaAaaaaaAaaaaaaAaaaaa = 0.11)
+ ..aaaaaaaAaaaaaaaaAaa =
+ 'aaaaa://aaa.aaaaaaa.aaa/aaaaaaa/aaaaaaaaaaaa/aaaaaa/aaaaaaa_aaaaaaaaaaaa.aaa'), // AA AAAA
+ ])
+ ..aaaAaaaaaaAaaaa.aaaAaa([
+ (AaaaaaaaaaaAaaaaaaaAaaaaaaAaaa()
+ ..aaaaaaaAaaaAaaa.aaaAaa(['Aaaaaaa', 'Aaaaa', 'Aaaaa'])
+ ..aaaaaaaAaaaAaaaa =
+ (AaaaaaaaaaaAaaaaaaaAaaaaaAaaaa()
+ ..aaaaaAaaaaaAaaaaa = 1111000000.0
+ ..aaaaaAaaaaaAaaaaaaAaaaaa = 0.22)),
+ (AaaaaaaaaaaAaaaaaaaAaaaaaaAaaa()
+ ..aaaaaaaAaaaAaaa.aaaAaa([
+ 'Aaaaaaa',
+ 'Aaaa',
+ 'Aaaaaa',
+ 'Aaaaaa Aaaaaa',
+ ])
+ ..aaaaaaaAaaaAaaaa =
+ (AaaaaaaaaaaAaaaaaaaAaaaaaAaaaa()
+ ..aaaaaAaaaaaAaaaaa = 112000000.0
+ ..aaaaaAaaaaaAaaaaaaAaaaaa = 0.234)),
+ (AaaaaaaaaaaAaaaaaaaAaaaaaaAaaa()
+ ..aaaaaaaAaaaAaaa.aaaAaa(['Aaaaaaa', 'Aaaa', 'Aaaaaaa'])
+ ..aaaaaaaAaaaAaaaa =
+ (AaaaaaaaaaaAaaaaaaaAaaaaaAaaaa()
+ ..aaaaaAaaaaaAaaaaa = 82000000.0
+ ..aaaaaAaaaaaAaaaaaaAaaaaa = 0.54)),
+ (AaaaaaaaaaaAaaaaaaaAaaaaaaAaaa()
+ ..aaaaaaaAaaaAaaa.aaaAaa([
+ 'Aaaaaaa',
+ 'Aaaaaaaaaaa',
+ 'Aaaa',
+ 'Aaaaaaaa Aaaa',
+ 'Aaaa Aaaaaaaa Aaaa',
+ 'Aaaaaa Aaaa Aaaaaaaa Aaaa',
+ 'Aaaaaa Aaaa Aaaaaaaa Aaaa Aaa Aaaaaaaa',
+ ])
+ ..aaaaaaaAaaaAaaaa =
+ (AaaaaaaaaaaAaaaaaaaAaaaaaAaaaa()
+ ..aaaaaAaaaaaAaaaaa = 82000000.0
+ ..aaaaaAaaaaaAaaaaaaAaaaaa = 0.54)),
+ ]))
+ ..aaaAaaaaaaAaaaaa.aaaAaa([
+ aaaaAaaaaaaAaaaa(
+ aaaaaaaAaaaa1,
+ aaaaaa: 1111000000.0,
+ aaaaaaaAaaaaa: 0.22,
+ ),
+ aaaaAaaaaaaAaaaa(
+ aaaaaaaAaaaa2,
+ aaaaaa: 112000000.0,
+ aaaaaaaAaaaaa: 0.234,
+ ),
+ aaaaAaaaaaaAaaaa(
+ aaaaaaaAaaaa3,
+ aaaaaa: 82000000.0,
+ aaaaaaaAaaaaa: 0.54,
+ ),
+ aaaaAaaaaaaAaaaa(
+ aaaaaaaAaaaa4,
+ aaaaaa: 82000000.0,
+ aaaaaaaAaaaaa: 0.54,
+ ),
+ ])
+ ..aaaaaa();
+
+final aaaaaaaaaaaAaaaaaaaAaaaAaaAaaaaaa =
+ (AaaaaaaaaaaAaaaaaaaAaaa()
+ ..aaaAaaaaaaa.aaaAaa([
+ (AaaaaaaaaaaAaaaaaaaAaaaaaa()
+ ..aaaaaaaAaaa =
+ 'Aaaaaaa aaaa 1 is aaa 2 aaaaa aaa aaaa aa aaaaaaaaa if aa is aaaa '
+ 'aaaa aaa aaaaa'
+ ..aaaaaaaAaaaa =
+ (AaaaaaaaaaaAaaaaaaaAaaaaaAaaaa()
+ ..aaaaaAaaaaaAaaaaa = 1111.0
+ ..aaaaaAaaaaaAaaaaaaAaaaaa = 0.22)
+ ..aaaaaaaAaaaaaaaaAaa =
+ 'aaaaa://aaa.aaaaaaa.aaa/aaaaaaa/aaaaaaaaaaaa/aaaaaa/aaaaaaa_aaaaaaaaaaaa.aaa'), // AA AAAA
+ (AaaaaaaaaaaAaaaaaaaAaaaaaa()
+ ..aaaaaaaAaaa = 'Aaaaaaa aaaa 2 is a aaaaa aaaa'
+ ..aaaaaaaAaaaa =
+ (AaaaaaaaaaaAaaaaaaaAaaaaaAaaaa()
+ ..aaaaaAaaaaaAaaaaa = 222.0
+ ..aaaaaAaaaaaAaaaaaaAaaaaa = 0.19)
+ ..aaaaaaaAaaaaaaaaAaa =
+ 'aaaaa://aaa.aaaaaaa.aaa/aaaaaaa/aaaaaaaaaaaa/aaaaaa/aaaaaaa_aaaaaaaaaaaa.aaa'), // AA AAAA
+ (AaaaaaaaaaaAaaaaaaaAaaaaaa()
+ ..aaaaaaaAaaa = 'Aaaaaaa aaaa 3'
+ ..aaaaaaaAaaaa =
+ (AaaaaaaaaaaAaaaaaaaAaaaaaAaaaa()
+ ..aaaaaAaaaaaAaaaaa = 33.3
+ ..aaaaaAaaaaaAaaaaaaAaaaaa = 0.15)
+ ..aaaaaaaAaaaaaaaaAaa =
+ 'aaaaa://aaa.aaaaaaa.aaa/aaaaaaa/aaaaaaaaaaaa/aaaaaa/aaaaaaa_aaaaaaaaaaaa.aaa'), // AA AAAA
+ (AaaaaaaaaaaAaaaaaaaAaaaaaa()
+ ..aaaaaaaAaaa = 'Aaaaaaa aaaa 4'
+ ..aaaaaaaAaaaa =
+ (AaaaaaaaaaaAaaaaaaaAaaaaaAaaaa()
+ ..aaaaaAaaaaaAaaaaa = 4
+ ..aaaaaAaaaaaAaaaaaaAaaaaa = 0.11)
+ ..aaaaaaaAaaaaaaaaAaa =
+ 'aaaaa://aaa.aaaaaaa.aaa/aaaaaaa/aaaaaaaaaaaa/aaaaaa/aaaaaaa_aaaaaaaaaaaa.aaa'), // AA AAAA
+ ]))
+ ..aaaaaa();
>>>
void aaaa() {
aaaa('aaaaaaaa aaaaa aaaa', () {
diff --git a/test/tall/regression/1600/1603.stmt b/test/tall/regression/1600/1603.stmt
index 183a922..1c26fd2 100644
--- a/test/tall/regression/1600/1603.stmt
+++ b/test/tall/regression/1600/1603.stmt
@@ -8,6 +8,13 @@
..anInt = 3
..aString = 'four',
);
+<<< 3.7
+ final value = Value(
+ (b) =>
+ b
+ ..anInt = 3
+ ..aString = 'four',
+ );
>>>
final value = Value((b) {
b
diff --git a/test/tall/regression/1600/1606.unit b/test/tall/regression/1600/1606.unit
index 4975f21..5c5c6cd 100644
--- a/test/tall/regression/1600/1606.unit
+++ b/test/tall/regression/1600/1606.unit
@@ -33,4 +33,25 @@
],
))
: AaaaAaaaaaaAaaaaaaAaaaaaaa();
+}
+<<< 3.7
+class AaaaAaaaAaaaAaaaaaaaAaaaaaaa {
+ @aaaaaaaa
+ AaaaaAaaaAaaaAaaaaaaa get aaaaAaaaAaaaaaaa =>
+ aaaaaaaaAaaaaaaaaAaaaaa
+ ? (AaaaaaaaaAaaAaaaaaaa()
+ // AAA/Aaaaa aaaaaaaaa aaaaaaaa aaaa AAAAAAAA aaaaaaaaa aaaaaa aaaaaaaa.
+ ..aaaaAaaaaaaaaAaaaaaAaagetaaa =
+ AaaaAaaaaaaaaAaaaaaAaagetaaa.aaaaAaaaaa(
+ aaaaaaaa: [
+ AaaaaaaaAaaaaaa()..aaaaaaaa = AaaaaaaaAA_Aaaaaaaa.AAAAA,
+ ],
+ aaaaaaAaaaaa: [
+ AaaaaaAaaaaaAaaaaaa()
+ ..aaaaaaAaaaaa = AaaaaaAaaaaa.AAAAAA_AAAAAAAAAA_AAAAAA,
+ AaaaaaAaaaaaAaaaaaa()
+ ..aaaaaaAaaaaa = AaaaaaAaaaaa.AAAAAA_AAAA_AAAAAAA,
+ ],
+ ))
+ : AaaaAaaaaaaAaaaaaaAaaaaaaa();
}
\ No newline at end of file
diff --git a/test/tall/regression/1600/1645.unit b/test/tall/regression/1600/1645.unit
index 5cefb04..a39c062 100644
--- a/test/tall/regression/1600/1645.unit
+++ b/test/tall/regression/1600/1645.unit
@@ -11,3 +11,7 @@
Future<String> fetchUserOrder() =>
// Imagine that this function is more complex and slow.
Future.delayed(const Duration(seconds: 2), () => 'Large Latte');
+<<< 3.7
+Future<String> fetchUserOrder() =>
+// Imagine that this function is more complex and slow.
+Future.delayed(const Duration(seconds: 2), () => 'Large Latte');
\ No newline at end of file
diff --git a/test/tall/regression/1600/1651.unit b/test/tall/regression/1600/1651.unit
index 12acb92..bde3e9a 100644
--- a/test/tall/regression/1600/1651.unit
+++ b/test/tall/regression/1600/1651.unit
@@ -12,4 +12,12 @@
int secondParameter,
String thirdParameter,
String fourthParameter,
-);
\ No newline at end of file
+);
+<<< 3.7
+typedef ExampleRecordTypedef =
+ (
+ String firstParameter,
+ int secondParameter,
+ String thirdParameter,
+ String fourthParameter,
+ );
\ No newline at end of file
diff --git a/test/tall/regression/1600/1667.unit b/test/tall/regression/1600/1667.unit
index dfbb37f..5459405 100644
--- a/test/tall/regression/1600/1667.unit
+++ b/test/tall/regression/1600/1667.unit
@@ -44,4 +44,4 @@
main() {
for (
/* 1 */ /* 2 */ datapoint in data) {}
-}
+}
\ No newline at end of file
diff --git a/test/tall/regression/1600/1668.unit b/test/tall/regression/1600/1668.unit
index cd9db3b..4a5ef46 100644
--- a/test/tall/regression/1600/1668.unit
+++ b/test/tall/regression/1600/1668.unit
@@ -42,6 +42,30 @@
);
}
}
+<<< 3.7
+class SetupApp extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return ScreenUtilInit(
+ builder:
+ (_, __) => GetMaterialApp(
+ builder: (BuildContext context, Widget? child) {
+ final MediaQueryData data = MediaQuery.of(context);
+ return MediaQuery(
+ data: data.copyWith(textScaler: TextScaler.noScaling),
+ child: child!,
+ );
+ },
+ title: ConstantStringShared.appName,
+ debugShowCheckedModeBanner: false,
+ theme: kAppTheme,
+ initialRoute: RouteNames.splash,
+ getPages: AppPages.pages,
+ ),
+ designSize: const Size(414, 894), // width and height of design file
+ );
+ }
+}
>>>
class StatusIndicator extends StatelessWidget {
final MachineStatus status;
@@ -111,6 +135,42 @@
);
}
}
+<<< 3.7
+class StatusIndicator extends StatelessWidget {
+ final MachineStatus status;
+ const StatusIndicator({required this.status, super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final f = NumberFormat.decimalPercentPattern(decimalDigits: 2);
+ return AnimatedContainer(
+ key: const ValueKey('indi'),
+ duration: const Duration(seconds: 1),
+ alignment: Alignment.center,
+ width: 100,
+ margin: const EdgeInsets.all(4),
+ padding: const EdgeInsets.symmetric(horizontal: 8),
+ decoration: BoxDecoration(
+ color: status.color,
+ border: Border.all(),
+ borderRadius: BorderRadius.circular(24),
+ ),
+ child: switch (status) {
+ MachineStatus.generating => BlocBuilder<MachineBloc, MachineState>(
+ buildWhen:
+ (previous, current) =>
+ current.genProgress != previous.genProgress,
+ builder:
+ (context, state) =>
+ state.genProgress != 0.0
+ ? Text(f.format(state.genProgress))
+ : const Text(''),
+ ),
+ _ => Text(status.text),
+ },
+ );
+ }
+}
>>>
class StatePanel extends StatelessWidget {
const StatePanel({super.key});
@@ -173,4 +233,36 @@
},
);
}
+}
+<<< 3.7
+class StatePanel extends StatelessWidget {
+ const StatePanel({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return BlocBuilder<MachineBloc, MachineState>(
+ buildWhen: (previous, current) => current.status != previous.status,
+ builder: (context, state) {
+ return Row(
+ children: [
+ state.status.isRefreshable
+ ? IconButton(
+ color: Colors.amber,
+ onPressed:
+ () => context.read<MachineBloc>().add(
+ WeatherRefreshRequested(),
+ ),
+ icon: const Icon(Icons.restart_alt),
+ )
+ : const IconButton(
+ color: Colors.grey,
+ onPressed: null,
+ icon: Icon(Icons.restart_alt),
+ ),
+ StatusIndicator(status: state.status),
+ ],
+ );
+ },
+ );
+ }
}
\ No newline at end of file
diff --git a/test/tall/regression/1600/1679.unit b/test/tall/regression/1600/1679.unit
index 53011ee..e993ef8 100644
--- a/test/tall/regression/1600/1679.unit
+++ b/test/tall/regression/1600/1679.unit
@@ -85,4 +85,49 @@
'Afterwards',
),
);
+}
+<<< 3.7
+void something() => functionCall(
+ 'Hello world!',
+ 'Good night city!',
+ 'Goodbye, world!',
+ 'Afterwards',
+);
+
+class Something {
+ void something() => functionCall(
+ 'Hello world!',
+ 'Good night city!',
+ 'Goodbye, world!',
+ 'Afterwards',
+ );
+}
+
+void main() {
+ final result = switch (value) {
+ Something() => functionCall(
+ 'Hello world!',
+ 'Good night city!',
+ 'Goodbye, world!',
+ ),
+ };
+
+ calling(
+ () => functionCall(
+ 'Hello world!',
+ 'Good night city!',
+ 'Goodbye, world!',
+ 'Afterwards',
+ ),
+ );
+
+ calling(
+ function:
+ () => functionCall(
+ 'Hello world!',
+ 'Good night city!',
+ 'Goodbye, world!',
+ 'Afterwards',
+ ),
+ );
}
\ No newline at end of file
diff --git a/test/tall/regression/1600/1685.stmt b/test/tall/regression/1600/1685.stmt
index cf526c2..1b5a001 100644
--- a/test/tall/regression/1600/1685.stmt
+++ b/test/tall/regression/1600/1685.stmt
@@ -35,3 +35,22 @@
eeeeeeeeeeeeeeeeeee: 3,
ffffffffffffffffff: 4,
);
+<<< 3.7
+final x =
+ true
+ ? foo(
+ aaaaaaaaaaaaaa: 4,
+ bbbbbbbbbbbbbbb: 6,
+ ccccccccccccccc: 0,
+ dddddddddddddddddd: 3,
+ eeeeeeeeeeeeeeeeeee: 3,
+ ffffffffffffffffff: 1,
+ )
+ : foo(
+ aaaaaaaaaaaaaa: 1,
+ bbbbbbbbbbbbbbb: 2,
+ ccccccccccccccc: 0,
+ dddddddddddddddddd: 6,
+ eeeeeeeeeeeeeeeeeee: 3,
+ ffffffffffffffffff: 4,
+ );
\ No newline at end of file
diff --git a/test/tall/regression/other/dart.unit b/test/tall/regression/other/dart.unit
index a29d094..52eb185 100644
--- a/test/tall/regression/other/dart.unit
+++ b/test/tall/regression/other/dart.unit
@@ -37,6 +37,14 @@
.transform(const LineSplitter())
.asBroadcastStream();
}
+<<< 3.7
+main() {
+ var stdErrLines =
+ proc.stderr
+ .transform(utf8.decoder)
+ .transform(const LineSplitter())
+ .asBroadcastStream();
+}
>>> Don't split return type if comment after metadata annotation.
class Benchmark {
@override
diff --git a/test/tall/regression/other/large.stmt b/test/tall/regression/other/large.stmt
index b0dd3cb..5aa96c4 100644
--- a/test/tall/regression/other/large.stmt
+++ b/test/tall/regression/other/large.stmt
@@ -232,4 +232,152 @@
: '${aaaa.aaaaa}';
}),
)
+ .aaaaaa('#aaaaaaaaa-aaaaa', /*aaaAaaaaAaAaaAaa=*/ false);
+<<< 3.7
+ aaaaaaaaaAaaaa!
+ .aaaAaaaaaaaaAaaa(_aaaaaaaAaaaaaa.aaaaAaaa == AaaaAaaa.aaaaaa)
+ .aaaAaaaaaAaaaAaaaaaaaaa(aaaaaaaaaAaaaaAaaAaaaaa!.aaaaAaaaaaaaaa)
+ .aaaAaaaaaAaaaAaaaAaaaaa(
+ aaaaaaaaaAaaaaAaaAaaaaa!.aaaaaaAaaaaaaAaaaAaaaaa,
+ )
+ .aaaAaaaaaAaaaAaaaAaaaaa(
+ aaaaaaaaaAaaaaAaaAaaaaa!.aaaaaaAaaaaaaAaaaAaaaaa,
+ )
+ .aaaAaaaaaaaAaaaa(
+ aaaaaaaaaAaaaaAaaAaaaaa!.aaaAaaaAaaaaaaa,
+ aaaaaaaaaAaaaaAaaAaaaaa!.aaaAaaaAaaaaaaa,
+ )
+ .aaaAaaaa(
+ _aaaaaAaaaAaaaa.aaaaa.aaAaaa(),
+ aaaaaAaaaaaa((aaaa) => aaaa.aaaaAa.aa),
+ /* aaAaaaAaaaAa = */
+ aaaaaAaaaaaa(
+ (aaaa) => _aaaaaAaaaAaaaa.aaaaAaaaAaa.aaaaaaaa(aaaa.aaaaAa.aa),
+ ),
+ /* aaaaaaAaaaAaaaaAaaaAaaaaaaaAa = */
+ aaaaaAaaaaaa((aaaa) => _aaAaaaaAaaaAaaa(aaaa)),
+ /* aaaaaaAaaaAaaaaAaaaaAaaaaaaaAa = */
+ aaaaaAaaaaaa((aaaa) => _aaAaaaaAaaaAaaa(aaaa)),
+ aaaaaAaaaaaa((aaaa) => _aaaaaaaAaaaAaaaa(aaaa)),
+ )
+ .aaaAaaaa(
+ _aaaaaAaaaAaaaa.aaaaa.aaAaaa(),
+ aaaaaAaaaaaa((aaaa) => aaaa.aaaaaaAa),
+ aaaaaAaaaaaa((aaaa) => aaaa.aaaaaaAa),
+ aaaaaAaaaaaa((aaaa) => aaaa.aaAaaaaaaaaaaaa),
+ aaaaaAaaaaaa((aaaa) => _aaAaaaAaaa(aaaa)),
+ )
+ .aaaaAaaaaAaaaaaa(aaaaaaaaaAaaaaAaaAaaaaa!.aaaaAaaaaAaaaaaa)
+ .aaaAaaaAaaaaaaAa(
+ aaaaaAaaaaaa(
+ (aaaa) =>
+ aaaaaaaaaAaaaaAaaAaaaaa!.aaaaaaaAaaaAaaaaAaAaaaaaaa
+ ? '${aaaa.aaaa}: ${aaaa.aaaaa}'
+ : '${aaaa.aaaa}',
+ ),
+ )
+ .aaaAaaaAaaaaaaAa(
+ aaaaaAaaaaaa((aaaa) {
+ if (!aaAaaaaAaaaaa && !aaAaaaaaAaaaaa) {
+ return '${aaaa.aaaaa}';
+ }
+ aaaaa aaaaaAaAaaaaaaaa = aaaa.aaaaa.aaaaa(',').aaaaaa;
+ return aaaaaAaAaaaaaaaa > 1
+ ? '${aaaa.aaaaaaaaAaaaAaaAaaaaa.aaaaa} + ${aaaaaAaAaaaaaaaa - 1} aaaa'
+ : '${aaaa.aaaaa}';
+ }),
+ )
+ .aaaAaaaAaaaaaAaaaaAa(
+ aaaaaAaaaaaa(
+ (aaaa) =>
+ aaaaaaaaaAaaaaAaaAaaaaa!.aaaaaaaAaaaAaaaaa
+ ? null
+ : _aaAaaaAaaaaaAaaaaaa(aaaa),
+ ),
+ )
+ .aaaAaaaAaaaaAaaaAa(aaaaaAaaaaaa((aaaa) => aaaaAaaaAaaaaaa()))
+ .aaaAaaaaaaaaAaaaaaaa(
+ aaaaaAaaaaaa((aaaaaaaaAaaaa) => _aaAaaaaAaaaaaaa(aaaaaaaaAaaaa)),
+ )
+ .aaaAaaaaaAaaaaAaaaaaaa(
+ aaaaaAaaaaaa(
+ (aaaaaaAaaaa) =>
+ _aaaaaAaaaaaaaaaaAaaaaaa.aaaaaaAaaaa = Aaaa<Aaaa>.aaaa(
+ aaaaaaAaaaa,
+ ),
+ ),
+ )
+ .aaaAaaaAaaaaAa(
+ aaaaaAaaaaaa(
+ (aaaa, aaaaa) =>
+ aaaaaaaaaAaaaaAaaAaaaaa!.aaaaaaaAaaaAaaaaa
+ ? null
+ : _aaAaaaAaaaaaa(aaaa, aaaaa),
+ ),
+ )
+ .aaaAaaaAaaaaAa(
+ aaaaaAaaaaaa((aaaa, aaaaa) => _aaAaaaAaaaaaa(aaaa, aaaaa)),
+ )
+ .aaaAaaaAaaaaaAa(
+ aaaaaAaaaaaa(
+ (aaaa) => _aaaaAaAaaaaAaaa(
+ aaaa,
+ _aaaaaaaAaaaaaAaaaaaaaaAaaaaAaaaaaa.aaaaAaaaaaaaAaaaaaa,
+ ),
+ ),
+ )
+ .aaaAaaaaAaaaaaaa(aaaaaaaaaAaaaaAaaAaaaaa!.aaaaaAaaaaaaa)
+ .aaaAaaaAaaaaaaAaaa(!aaAaaaaAaaaaa)
+ .aaaAaaaaaaAaaaAaaaaAa(
+ aaaaaAaaaaaa((aaaa) => _aaaaaaaAaaaAaaaaa.aaaaAaaaaAaaAaaa(aaaa)),
+ aaaaaAaaaaaa(
+ (aaaaa) => _aaaaaaaAaaaAaaaaa.aaaaAaaaaAaaAaaaaaaaAaaaa(
+ Aaaa<Aaaa>.aaaa(aaaaa),
+ ),
+ ),
+ )
+ .aaaAaaaAaaaAa(aaaaaAaaaaaa((aaaa) => aaaaAaaa(aaaa)))
+ .aaaAaaaAaaaAaaaAa(
+ aaaaaAaaaaaa((aaaa) {
+ if (aaAaaaaaAaaaaa && aaaa.aaaaaaaaaa.aaaaaaaaAaa('aaaa_aaaa')) {
+ return aaa.aaaAaaaa(aaaa.aaaaaaaaaa['aaaa_aaaa']!.aaaaaa.aaaaa) ??
+ 20;
+ }
+ return 20;
+ }),
+ )
+ .aaaAaaaAaaaaAa(
+ aaaaaAaaaaaa((aaaa) {
+ // Aa AaaaaaAaaaAaaaa aaaaaa, aaaaaaa aaaaaa (0~1) aa aaaaa (0~3),
+ // aaaaaaaaa return aaa aaaaaaa aaaaa = 3
+ return aaAaaaaaAaaaaa && aaaa.aaaAaaaaa != null
+ ? aaaa.aaaAaaaaa * 3
+ : 3;
+ }),
+ )
+ .aaaAaaaAaaaaAa(
+ aaaaaAaaaaaa(
+ (aaaa) =>
+ aaaaaaaaaAaaaaAaaAaaaaa!.aaaaaaAaaaaaaAaaaAaaaaa
+ ? '${aaaa.aaaaa}'
+ : '',
+ ),
+ )
+ .aaaAaaaAaaaaAa(
+ aaaaaAaaaaaa((aaaa) {
+ if (!aaaaaaaaaAaaaaAaaAaaaaa!.aaaaaaAaaaaaaAaaaAaaaaa) {
+ return '';
+ }
+ if (aaAaaaaAaaaaa) {
+ return '${aaaaaaAaaaaaaaa.aaaaaa(aaaa.aaaaaAaaaAaaaa)}';
+ }
+ aaaaa aaaaaAaAaaaaaaaa = aaaa.aaaaa.aaaaa(',').aaaaaa;
+ if (aaAaaaaaAaaaaa) {
+ return '${aaaa.aaaaaaAaAaaaaaaaa}';
+ }
+ return aaaaaAaAaaaaaaaa > 1
+ ? '$aaaaaAaAaaaaaaaa+ aaaaaaaaa'
+ : '${aaaa.aaaaa}';
+ }),
+ )
.aaaaaa('#aaaaaaaaa-aaaaa', /*aaaAaaaaAaAaaAaa=*/ false);
\ No newline at end of file
diff --git a/test/tall/regression/other/tall_prototype.unit b/test/tall/regression/other/tall_prototype.unit
index 32671a6..df2a341 100644
--- a/test/tall/regression/other/tall_prototype.unit
+++ b/test/tall/regression/other/tall_prototype.unit
@@ -27,6 +27,12 @@
.transform(utf8.decoder)
.transform(const LineSplitter())
.asBroadcastStream();
+<<< 3.7
+var stdErrLines =
+ proc.stderr
+ .transform(utf8.decoder)
+ .transform(const LineSplitter())
+ .asBroadcastStream();
>>> Don't block format a complex method chain.
var keyLen = sortedEntries.map((e) => e.key).fold(
0,
diff --git a/test/tall/statement/switch_legacy.stmt b/test/tall/statement/switch_legacy.stmt
index 7177147..7254506 100644
--- a/test/tall/statement/switch_legacy.stmt
+++ b/test/tall/statement/switch_legacy.stmt
@@ -1,7 +1,7 @@
40 columns |
### Tests syntax that used to be valid in a switch case before Dart 3.0 but is
### invalid in 3.0 and later.
->>> (version 2.19) Handle cases that are not valid patterns.
+>>> Handle cases that are not valid patterns.
switch (obj) {
case {1, 2}:
case -pi:
@@ -32,7 +32,7 @@
case 1 is! int:
body;
}
-<<<
+<<< 2.19
switch (obj) {
case {1, 2}:
case -pi:
diff --git a/test/tall/variable/local.stmt b/test/tall/variable/local.stmt
index ef1c870..f3cb9dc 100644
--- a/test/tall/variable/local.stmt
+++ b/test/tall/variable/local.stmt
@@ -232,6 +232,14 @@
) {
body;
};
+<<< 3.7
+var variableName = (
+ parameter1,
+ parameter2,
+ parameter3,
+) {
+ body;
+};
>>> Don't use block-like splitting for expression-bodied function expressions.
var variableName = (parameter, parameter, parameter) => body;
<<<
@@ -296,4 +304,19 @@
SomeType a = () {
body;
},
- b;
\ No newline at end of file
+ b;
+>>> Block-like splitting for block-bodied function expressions.
+var variableName = (parameter, parameter, parameter) { body; };
+<<<
+var variableName =
+ (parameter, parameter, parameter) {
+ body;
+ };
+<<< 3.7
+var variableName = (
+ parameter,
+ parameter,
+ parameter,
+) {
+ body;
+};
\ No newline at end of file
diff --git a/test/utils.dart b/test/utils.dart
index 41ce6ac..0c5ae9b 100644
--- a/test/utils.dart
+++ b/test/utils.dart
@@ -9,6 +9,7 @@
import 'package:dart_style/src/testing/benchmark.dart';
import 'package:dart_style/src/testing/test_file.dart';
import 'package:path/path.dart' as p;
+import 'package:pub_semver/pub_semver.dart';
import 'package:test/test.dart';
import 'package:test_descriptor/test_descriptor.dart' as d;
import 'package:test_process/test_process.dart';
@@ -24,6 +25,16 @@
/// it.
String? _formatterPath;
+/// All of the supported tall style versions that we run the tests against.
+final List<Version> _testedTallVersions = [
+ for (
+ var version = DartFormatter.latestShortStyleLanguageVersion.nextMinor;
+ version <= DartFormatter.latestLanguageVersion;
+ version = version.nextMinor
+ )
+ version,
+];
+
/// Compiles `bin/format.dart` to a native executable for tests to use.
///
/// Calls [setupAll()] and [tearDownAll()] to coordinate this with the
@@ -135,31 +146,79 @@
void _testFile(TestFile testFile) {
group(testFile.path, () {
for (var formatTest in testFile.tests) {
- test(formatTest.label, () {
- var formatter = testFile.formatterForTest(formatTest);
+ // Collect all of the outputs that are specific to a single version.
+ // We'll run those tests at only that version. If there is is an output
+ // with no version specified, then we'll run it on all of the remaining
+ // supported versions.
+ var specificTestedVersions = {
+ for (var output in formatTest.outputs)
+ if (output.version case var version?) version,
+ };
- var actual = _validateFormat(
- formatter,
- formatTest.input,
- formatTest.output,
- 'did not match expectation',
- testFile.isCompilationUnit,
- );
-
- // Make sure that formatting is idempotent. Format the output and make
- // sure we get the same result.
- _validateFormat(
- formatter,
- actual,
- actual,
- 'was not idempotent',
- testFile.isCompilationUnit,
- );
- });
+ // Run it for each of the expected outputs.
+ for (var output in formatTest.outputs) {
+ // If the output is version-specific, then only test on that version.
+ if (output.version case var version?) {
+ _runTestAtVersion(testFile, formatTest, output, version);
+ } else if (testFile.isTall) {
+ // This output is unversioned, so test it on all of the versions
+ // that aren't otherwise covered.
+ for (var version in _testedTallVersions) {
+ if (specificTestedVersions.contains(version)) continue;
+ _runTestAtVersion(testFile, formatTest, output, version);
+ }
+ } else {
+ // Short style test so just test against one short version.
+ _runTestAtVersion(
+ testFile,
+ formatTest,
+ output,
+ DartFormatter.latestShortStyleLanguageVersion,
+ );
+ }
+ }
}
});
}
+void _runTestAtVersion(
+ TestFile testFile,
+ FormatTest formatTest,
+ TestEntry output,
+ Version version,
+) {
+ // If the output is version-specific, we can only run it at that version.
+ assert(output.version == null || output.version == version);
+
+ var description =
+ 'line ${formatTest.line} at ${version.major}.${version.minor}';
+ if (formatTest.input.description.isNotEmpty) {
+ description += ': ${formatTest.input.description}';
+ }
+
+ test(description, () {
+ var formatter = testFile.formatterForTest(formatTest, version);
+
+ var actual = _validateFormat(
+ formatter,
+ formatTest.input.code,
+ output.code,
+ 'did not match expectation',
+ testFile.isCompilationUnit,
+ );
+
+ // Make sure that formatting is idempotent. Format the output and make
+ // sure we get the same result.
+ _validateFormat(
+ formatter,
+ actual,
+ actual,
+ 'was not idempotent',
+ testFile.isCompilationUnit,
+ );
+ });
+}
+
/// Run [formatter] on [input] and validate that the result matches [expected].
///
/// If not, fails with an error using [reason].
diff --git a/tool/update_tests.dart b/tool/update_tests.dart
index 0af9c86..a46e36c 100644
--- a/tool/update_tests.dart
+++ b/tool/update_tests.dart
@@ -20,6 +20,13 @@
/// "×XX" Unicode markers or selections.
// TODO(rnystrom): Support updating individual tests within a file.
void main(List<String> arguments) async {
+ // TODO(rnystrom): Tests now support being run against multiple versions with
+ // test expectations for different versions. This script hasn't been updated
+ // to support that yet.
+ print('The update script isn\'t working right now.');
+ return;
+
+ // ignore: dead_code
if (arguments.isEmpty) {
print('Usage: update_tests.dart <tests...>');
exit(1);
@@ -99,7 +106,7 @@
for (var formatTest in testFile.tests) {
var formatter = testFile.formatterForTest(formatTest);
- var actual = formatter.formatSource(formatTest.input);
+ var actual = formatter.formatSource(formatTest.input.code);
// The test files always put a newline at the end of the expectation.
// Statements from the formatter (correctly) don't have that, so add
@@ -112,15 +119,15 @@
var description = [
..._optionStrings(formatTest.options),
- formatTest.description,
+ formatTest.input.description,
].join(' ');
buffer.writeln('>>> $description'.trim());
- _writeComments(buffer, formatTest.inputComments);
- buffer.write(formatTest.input.text);
+ _writeComments(buffer, formatTest.input.comments);
+ buffer.write(formatTest.input.code.text);
- buffer.writeln('<<< ${formatTest.outputDescription}'.trim());
- _writeComments(buffer, formatTest.outputComments);
+ buffer.writeln('<<< ${formatTest.outputs.first.description}'.trim());
+ _writeComments(buffer, formatTest.outputs.first.comments);
var output = actual.text;
@@ -131,7 +138,7 @@
// Fail with an explicit message because it's easier to read than
// the matcher output.
- if (actualText != formatTest.output.text) {
+ if (actualText != formatTest.outputs.first.code.text) {
print('Updated ${testFile.path} ${formatTest.label}');
_changedTests++;
}
@@ -147,8 +154,6 @@
List<String> _optionStrings(TestOptions options) => [
for (var experiment in options.experimentFlags) '(experiment $experiment)',
if (options.leadingIndent case var indent?) '(indent $indent)',
- if (options.languageVersion case var version?)
- '(version ${version.major}.${version.minor})',
if (options.trailingCommas == TrailingCommas.preserve)
'(trailing_commas preserve)',
];