Allow disabling formatting for a region of code. (#1522)
Allow disabling formatting for a region of code.
There are two special line comments:
```
// dart format off
// dart format on
```
Any code between those keeps its original formatting completely,
including indentation. It just splats the original unformatted code into
the output.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 12998dc..211bba4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,10 @@
* Allow passing a language version to `DartFomatter()`. Formatted code will be
parsed at that version. If omitted, defaults to the latest version. In a
future release, this parameter will become required.
+* Allow opting out of formatting for a region of code using `// dart format off`
+ and `// dart format on` comments. Note: This only works using the new tall
+ style and requires passing the `--enable-experiment=tall-style` experiment
+ flag (#361).
* Preserve type parameters on old-style function-typed formals that also use
`this.` or `super.` (#1321).
* Remove temporary work around for analyzer 6.2.0 from dart_style 2.3.6.
diff --git a/benchmark/run.dart b/benchmark/run.dart
index 8d8f646..bf8da68 100644
--- a/benchmark/run.dart
+++ b/benchmark/run.dart
@@ -160,7 +160,7 @@
result = visitor.run(parseResult.unit).text;
} else {
var visitor = AstNodeVisitor(formatter, parseResult.lineInfo, source);
- result = visitor.run(parseResult.unit).text;
+ result = visitor.run(source, parseResult.unit).text;
}
}
diff --git a/example/format.dart b/example/format.dart
index ba66c08..d6d0603 100644
--- a/example/format.dart
+++ b/example/format.dart
@@ -25,7 +25,7 @@
class C {}
''');
- _runTest('selection/selection.stmt', 2);
+ _runTest('other/selection.stmt', 2);
}
void _formatStmt(String source, {bool tall = true, int pageWidth = 40}) {
@@ -68,7 +68,7 @@
/// directory.
Future<void> _runTest(String path, int line,
{int pageWidth = 40, bool tall = true}) async {
- var testFile = await TestFile.read(path);
+ var testFile = await TestFile.read('${tall ? 'tall' : 'short'}/$path');
var formatTest = testFile.tests.firstWhere((test) => test.line == line);
var formatter = DartFormatter(
diff --git a/lib/src/back_end/code.dart b/lib/src/back_end/code.dart
index 47c06eb..52416a1 100644
--- a/lib/src/back_end/code.dart
+++ b/lib/src/back_end/code.dart
@@ -2,6 +2,8 @@
// 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 '../source_code.dart';
+
/// Base class for an object that represents fully formatted code.
///
/// We use this instead of immediately generating a string for the resulting
@@ -13,14 +15,59 @@
/// to be fast. Appending strings to a [StringBuffer] is fairly fast, but not
/// as fast simply appending a single [GroupCode] to the parent solution's
/// [GroupCode].
-sealed class Code {}
+sealed class Code {
+ /// Traverse the [Code] tree and generate a string for debugging purposes.
+ String toDebugString() {
+ var buffer = StringBuffer();
+ var prefix = '';
+
+ void write(String text) {
+ if (buffer.isNotEmpty) buffer.write(prefix);
+ buffer.writeln(text);
+ }
+
+ void trace(Code code) {
+ switch (code) {
+ case _NewlineCode():
+ write('Newline(blank: ${code._blank}, indent: ${code._indent})');
+
+ case _TextCode():
+ write('`${code._text}`');
+
+ case GroupCode():
+ write('Group(indent: ${code._indent}):');
+ prefix += '| ';
+ for (var child in code._children) {
+ trace(child);
+ }
+ prefix = prefix.substring(2);
+
+ case _MarkerCode():
+ write('Marker(${code._marker}, offset: ${code._offset})');
+
+ case _EnableFormattingCode():
+ write('EnableFormattingCode(enabled: ${code._enabled}, '
+ 'offset: ${code._sourceOffset})');
+ }
+ }
+
+ trace(this);
+
+ return buffer.toString();
+ }
+}
/// A [Code] object which can be written to and contain other child [Code]
/// objects.
final class GroupCode extends Code {
+ /// How many spaces the first text inside this group should be indented.
+ final int _indent;
+
/// The child [Code] objects contained in this group.
final List<Code> _children = [];
+ GroupCode(this._indent);
+
/// Appends [text] to this code.
void write(String text) {
_children.add(_TextCode(text));
@@ -32,7 +79,10 @@
/// single newline is written. The [indent] parameter is the number of spaces
/// of leading indentation on the next line after the newline.
void newline({required bool blank, required int indent}) {
- _children.add(_NewlineCode(blank: blank, indent: indent));
+ // Don't insert a redundant newline at the top of a group.
+ if (_children.isNotEmpty) {
+ _children.add(_NewlineCode(blank: blank, indent: indent));
+ }
}
/// Adds an entire existing code [group] as a child of this one.
@@ -52,65 +102,32 @@
_children.add(_MarkerCode(_Marker.end, offset));
}
+ /// Disables or re-enables formatting in a region of code.
+ void setFormattingEnabled(bool enabled, int sourceOffset) {
+ _children.add(_EnableFormattingCode(enabled, sourceOffset));
+ }
+
/// Traverse the [Code] tree and build the final formatted string.
///
/// Whenever a newline is written, writes [lineEnding]. If omitted, defaults
/// to '\n'.
///
/// Returns the formatted string and the selection markers if there are any.
- ({String code, int? selectionStart, int? selectionEnd}) build(
- [String? lineEnding]) {
+ SourceCode build(SourceCode source, [String? lineEnding]) {
lineEnding ??= '\n';
- var buffer = StringBuffer();
- int? selectionStart;
- int? selectionEnd;
-
- _build(buffer, lineEnding, (marker, offset) {
- if (marker == _Marker.start) {
- selectionStart = offset;
- } else {
- selectionEnd = offset;
- }
- });
-
- return (
- code: buffer.toString(),
- selectionStart: selectionStart,
- selectionEnd: selectionEnd
- );
- }
-
- void _build(StringBuffer buffer, String lineEnding,
- void Function(_Marker marker, int offset) markSelection) {
- for (var i = 0; i < _children.length; i++) {
- var child = _children[i];
- switch (child) {
- case _NewlineCode():
- // Don't write any leading newlines at the top of the buffer.
- if (i > 0) {
- buffer.write(lineEnding);
- if (child._blank) buffer.write(lineEnding);
- }
-
- buffer.write(_indents[child._indent] ?? (' ' * child._indent));
-
- case _TextCode():
- buffer.write(child._text);
-
- case GroupCode():
- child._build(buffer, lineEnding, markSelection);
-
- case _MarkerCode():
- markSelection(child._marker, buffer.length + child._offset);
- }
- }
+ var builder = _StringBuilder(source, lineEnding);
+ builder.traverse(this);
+ return builder.finish();
}
}
/// 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.
final bool _blank;
+
+ /// The number of spaces of indentation after this newline.
final int _indent;
_NewlineCode({required bool blank, required int indent})
@@ -132,49 +149,230 @@
/// What kind of selection endpoint is being marked.
final _Marker _marker;
- /// The number of characters past this object where the marker should appear
- /// in the resulting code.
+ /// The number of characters into the next [Code] object where the marker
+ /// should appear in the resulting output.
final int _offset;
_MarkerCode(this._marker, this._offset);
}
+final class _EnableFormattingCode extends Code {
+ /// Whether this comment disables formatting (`format off`) or re-enables it
+ /// (`format on`).
+ final bool _enabled;
+
+ /// The number of code points from the beginning of the unformatted source
+ /// where the unformatted code should begin or end.
+ ///
+ /// If this piece is for `// dart format off`, then the offset is just past
+ /// the `off`. If this piece is for `// dart format on`, it points to just
+ /// before `//`.
+ final int _sourceOffset;
+
+ _EnableFormattingCode(this._enabled, this._sourceOffset);
+}
+
/// Which selection marker is pointed to by a [_MarkerCode].
enum _Marker { start, end }
-/// Pre-calculated whitespace strings for various common levels of indentation.
-///
-/// Generating these ahead of time is faster than concatenating multiple spaces
-/// at runtime.
-const _indents = {
- 2: ' ',
- 4: ' ',
- 6: ' ',
- 8: ' ',
- 10: ' ',
- 12: ' ',
- 14: ' ',
- 16: ' ',
- 18: ' ',
- 20: ' ',
- 22: ' ',
- 24: ' ',
- 26: ' ',
- 28: ' ',
- 30: ' ',
- 32: ' ',
- 34: ' ',
- 36: ' ',
- 38: ' ',
- 40: ' ',
- 42: ' ',
- 44: ' ',
- 46: ' ',
- 48: ' ',
- 50: ' ',
- 52: ' ',
- 54: ' ',
- 56: ' ',
- 58: ' ',
- 60: ' ',
-};
+/// Traverses a [Code] tree and produces the final string of output code and
+/// the selection markers, if any.
+class _StringBuilder {
+ /// Pre-calculated whitespace strings for various common levels of
+ /// indentation.
+ ///
+ /// Generating these ahead of time is faster than concatenating multiple
+ /// spaces at runtime.
+ static const _indents = {
+ 2: ' ',
+ 4: ' ',
+ 6: ' ',
+ 8: ' ',
+ 10: ' ',
+ 12: ' ',
+ 14: ' ',
+ 16: ' ',
+ 18: ' ',
+ 20: ' ',
+ 22: ' ',
+ 24: ' ',
+ 26: ' ',
+ 28: ' ',
+ 30: ' ',
+ 32: ' ',
+ 34: ' ',
+ 36: ' ',
+ 38: ' ',
+ 40: ' ',
+ 42: ' ',
+ 44: ' ',
+ 46: ' ',
+ 48: ' ',
+ 50: ' ',
+ 52: ' ',
+ 54: ' ',
+ 56: ' ',
+ 58: ' ',
+ 60: ' ',
+ };
+
+ final SourceCode _source;
+ final String _lineEnding;
+ final StringBuffer _buffer = StringBuffer();
+
+ /// The offset from the beginning of the source to where the selection start
+ /// marker is, if there is one.
+ int? _selectionStart;
+
+ /// The offset from the beginning of the source to where the selection end
+ /// marker is, if there is one.
+ int? _selectionEnd;
+
+ /// How many spaces of indentation should be written before the next text.
+ int _indent = 0;
+
+ /// If formatting has been disabled, then this is the offset from the
+ /// beginning of the source, to where the disabled formatting begins.
+ ///
+ /// Otherwise, -1 to indicate that formatting is enabled.
+ int _disableFormattingStart = -1;
+
+ _StringBuilder(this._source, this._lineEnding);
+
+ void traverse(Code code) {
+ switch (code) {
+ case _NewlineCode():
+ // If formatting has been disabled, then don't write the formatted
+ // output. The unformatted output will be written when formatting is
+ // re-enabled.
+ if (_disableFormattingStart == -1) {
+ _buffer.write(_lineEnding);
+ if (code._blank) _buffer.write(_lineEnding);
+ _indent = code._indent;
+ }
+
+ case _TextCode():
+ // If formatting has been disabled, then don't write the formatted
+ // output. The unformatted output will be written when formatting is
+ // re-enabled.
+ if (_disableFormattingStart == -1) {
+ // Write any pending indentation.
+ _buffer.write(_indents[_indent] ?? (' ' * _indent));
+ _indent = 0;
+
+ _buffer.write(code._text);
+ }
+
+ case GroupCode():
+ _indent = code._indent;
+ for (var i = 0; i < code._children.length; i++) {
+ var child = code._children[i];
+ traverse(child);
+ }
+
+ case _MarkerCode():
+ if (_disableFormattingStart == -1) {
+ // Calculate the absolute offset from the beginning of the formatted
+ // output where the selection marker will appear based on how much
+ // formatted output we've written, pending indentation, and then the
+ // relative offset of the marker into the subsequent [Code] we will
+ // write.
+ var absolutePosition = _buffer.length + _indent + code._offset;
+ switch (code._marker) {
+ case _Marker.start:
+ _selectionStart = absolutePosition;
+ case _Marker.end:
+ _selectionEnd = absolutePosition;
+ }
+ } else {
+ // The marker appears inside a region where formatting is disabled.
+ // In that case, calculating where the marker will end up in the
+ // final formatted output is more complicated because we haven't
+ // actually written any of the code between the `// dart format off`
+ // comment and this marker to [_buffer] yet. However, we do know the
+ // *absolute* position of the selection markers in the original
+ // source.
+ //
+ // Let's say the source file looks like:
+ //
+ // 1 2 3
+ // 0123456789012345678901234567890123456789
+ // bef + ore off code | inside on more
+ //
+ // Here, `bef + ore` is some amount of code appearing before
+ // formatting is disabled, `off` is the `// dart format off` comment,
+ // `code` is some code inside the unformatted region, `|` is the
+ // selection marker, `inside` is more code in the unformatted region,
+ // `on` turns formatting back on, and `more` is formatted code at the
+ // end.
+ //
+ // We know the beginning of the unformatted region is at offset 15
+ // (just after the comment) in the original source. We know the
+ // selection marker is at offset 21 in the original source. From that,
+ // we know the selection marker should end up 6 code points after the
+ // beginning of the unformatted region in the resulting output.
+ switch (code._marker) {
+ case _Marker.start:
+ // Calculate how far into the unformatted code where the marker
+ // should appear.
+ var markerOffsetInUnformatted =
+ _source.selectionStart! - _disableFormattingStart;
+ _selectionStart = _buffer.length + markerOffsetInUnformatted;
+
+ case _Marker.end:
+ var end = _source.selectionStart! + _source.selectionLength!;
+
+ // Calculate how far into the unformatted code where the marker
+ // should appear.
+ var markerOffsetInUnformatted = end - _disableFormattingStart;
+ _selectionEnd = _buffer.length + markerOffsetInUnformatted;
+ }
+ }
+
+ case _EnableFormattingCode(_enabled: false):
+ // Region markers don't nest. If we've already turned off formatting,
+ // then ignore any subsequent `// dart format off` comments until it's
+ // been turned back on.
+ if (_disableFormattingStart == -1) {
+ _disableFormattingStart = code._sourceOffset;
+ }
+
+ case _EnableFormattingCode(_enabled: true):
+ // If we didn't disable formatting, then enabling it does nothing.
+ if (_disableFormattingStart != -1) {
+ // Write all of the unformatted text from the `// dart format off`
+ // comment to the end of the `// dart format on` comment.
+ _buffer.write(_source.text
+ .substring(_disableFormattingStart, code._sourceOffset));
+ _disableFormattingStart = -1;
+ }
+ }
+ }
+
+ SourceCode finish() {
+ if (_disableFormattingStart != -1) {
+ // Formatting was disabled and never re-enabled, so write the rest of the
+ // source file as unformatted text.
+ _buffer.write(_source.text.substring(_disableFormattingStart));
+ } else if (_source.isCompilationUnit) {
+ // Be a good citizen, end with a newline.
+ _buffer.write(_lineEnding);
+ }
+
+ var selectionStart = _selectionStart;
+ int? selectionLength;
+ if (_source.selectionStart != null) {
+ // If we haven't hit the beginning and/or end of the selection yet, they
+ // must be at the very end of the code.
+ selectionStart ??= _buffer.length;
+ var selectionEnd = _selectionEnd ?? _buffer.length;
+ selectionLength = selectionEnd - selectionStart;
+ }
+
+ return SourceCode(_buffer.toString(),
+ uri: _source.uri,
+ isCompilationUnit: _source.isCompilationUnit,
+ selectionStart: selectionStart,
+ selectionLength: selectionLength);
+ }
+}
diff --git a/lib/src/back_end/code_writer.dart b/lib/src/back_end/code_writer.dart
index e60f593..c266dc1 100644
--- a/lib/src/back_end/code_writer.dart
+++ b/lib/src/back_end/code_writer.dart
@@ -30,7 +30,7 @@
final Solution _solution;
/// The code being written.
- final GroupCode _code = GroupCode();
+ final GroupCode _code;
/// What whitespace should be written before the next non-whitespace text.
///
@@ -41,7 +41,7 @@
///
/// Initially [Whitespace.newline] so that we write the leading indentation
/// before the first token.
- Whitespace _pendingWhitespace = Whitespace.newline;
+ Whitespace _pendingWhitespace = Whitespace.none;
/// The number of spaces of indentation that should be begin the next line
/// when [_pendingWhitespace] is [Whitespace.newline] or
@@ -100,11 +100,13 @@
/// [leadingIndent] is the number of spaces of leading indentation at the
/// beginning of each line independent of indentation created by pieces being
/// written.
- CodeWriter(this._pageWidth, int leadingIndent, this._cache, this._solution) {
+ CodeWriter(this._pageWidth, int leadingIndent, this._cache, this._solution)
+ : _code = GroupCode(leadingIndent) {
_indentStack.add(_Indent(leadingIndent, 0));
- // Write the leading indent before the first line.
+ // Track the leading indent before the first line.
_pendingIndent = leadingIndent;
+ _column = _pendingIndent;
}
/// Returns the final formatted code and the next pieces that can be expanded
@@ -331,6 +333,11 @@
_code.endSelection(end);
}
+ /// Disables or re-enables formatting in a region of code.
+ void setFormattingEnabled(bool enabled, int sourceOffset) {
+ _code.setFormattingEnabled(enabled, sourceOffset);
+ }
+
/// Write any pending whitespace.
///
/// This is called before non-whitespace text is about to be written, or
diff --git a/lib/src/back_end/solver.dart b/lib/src/back_end/solver.dart
index 948ec6b..df087f8 100644
--- a/lib/src/back_end/solver.dart
+++ b/lib/src/back_end/solver.dart
@@ -103,7 +103,7 @@
if (debug.traceSolver) {
debug.log(debug.bold('Try #$attempts $solution'));
- debug.log(solution.code.build().code);
+ debug.log(solution.code.toDebugString());
debug.log('');
}
@@ -133,8 +133,8 @@
// If we didn't find a solution without overflow, pick the least bad one.
if (debug.traceSolver) {
debug.unindent();
- debug.log(debug.bold('Solved $root to $best:'));
- debug.log(solution.code.build().code);
+ debug.log(debug.bold('Solved $root to $best'));
+ debug.log(solution.code.toDebugString());
debug.log('');
}
diff --git a/lib/src/dart_formatter.dart b/lib/src/dart_formatter.dart
index d814dde..67a97b1 100644
--- a/lib/src/dart_formatter.dart
+++ b/lib/src/dart_formatter.dart
@@ -219,7 +219,7 @@
SourceCode output;
if (experimentFlags.contains(tallStyleExperimentFlag)) {
var visitor = AstNodeVisitor(this, lineInfo, unitSourceCode);
- output = visitor.run(node);
+ output = visitor.run(unitSourceCode, node);
} else {
var visitor = SourceVisitor(this, lineInfo, unitSourceCode);
output = visitor.run(node);
diff --git a/lib/src/front_end/ast_node_visitor.dart b/lib/src/front_end/ast_node_visitor.dart
index 36d5f4e..b3cdf9a 100644
--- a/lib/src/front_end/ast_node_visitor.dart
+++ b/lib/src/front_end/ast_node_visitor.dart
@@ -66,7 +66,7 @@
///
/// This is the only method that should be called externally. Everything else
/// is effectively private.
- SourceCode run(AstNode node) {
+ SourceCode run(SourceCode source, AstNode node) {
Profile.begin('AstNodeVisitor.run()');
Profile.begin('AstNodeVisitor build Piece tree');
@@ -123,7 +123,7 @@
Profile.end('AstNodeVisitor build Piece tree');
// Finish writing and return the complete result.
- var result = pieces.finish(unitPiece);
+ var result = pieces.finish(source, unitPiece);
Profile.end('AstNodeVisitor.run()');
diff --git a/lib/src/front_end/piece_writer.dart b/lib/src/front_end/piece_writer.dart
index 90e7595..e2e1f0b 100644
--- a/lib/src/front_end/piece_writer.dart
+++ b/lib/src/front_end/piece_writer.dart
@@ -277,7 +277,18 @@
/// Creates a new [Piece] for [comment] and returns it.
Piece commentPiece(SourceComment comment,
[Whitespace trailingWhitespace = Whitespace.none]) {
- var piece = CommentPiece(trailingWhitespace);
+ var piece = switch (comment.text) {
+ '// dart format off' => EnableFormattingCommentPiece(
+ enable: false,
+ comment.offset + comment.text.length,
+ trailingWhitespace),
+ '// dart format on' => EnableFormattingCommentPiece(
+ enable: true,
+ comment.offset + comment.text.length,
+ trailingWhitespace),
+ _ => CommentPiece(trailingWhitespace),
+ };
+
_write(piece, comment.text, comment.offset,
multiline: comment.type.mayBeMultiline);
return piece;
@@ -379,7 +390,7 @@
/// Finishes writing and returns a [SourceCode] containing the final output
/// and updated selection, if any.
- SourceCode finish(Piece rootPiece) {
+ SourceCode finish(SourceCode source, Piece rootPiece) {
if (debug.tracePieceBuilder) {
debug.log(debug.pieceTree(rootPiece));
}
@@ -390,28 +401,11 @@
var solver = Solver(cache,
pageWidth: _formatter.pageWidth, leadingIndent: _formatter.indent);
var solution = solver.format(rootPiece);
- var (:code, :selectionStart, :selectionEnd) =
- solution.code.build(_formatter.lineEnding);
+ var output = solution.code.build(source, _formatter.lineEnding);
Profile.end('PieceWriter.finish() format piece tree');
- // Be a good citizen, end with a newline.
- if (_source.isCompilationUnit) code += _formatter.lineEnding!;
-
- int? selectionLength;
- if (_source.selectionStart != null) {
- // If we haven't hit the beginning and/or end of the selection yet, they
- // must be at the very end of the code.
- selectionStart ??= code.length;
- selectionEnd ??= code.length;
- selectionLength = selectionEnd - selectionStart;
- }
-
- return SourceCode(code,
- uri: _source.uri,
- isCompilationUnit: _source.isCompilationUnit,
- selectionStart: selectionStart,
- selectionLength: selectionLength);
+ return output;
}
/// Returns the number of characters past [position] in the source where the
diff --git a/lib/src/piece/text.dart b/lib/src/piece/text.dart
index 274516c..b5c159f 100644
--- a/lib/src/piece/text.dart
+++ b/lib/src/piece/text.dart
@@ -59,18 +59,6 @@
}
}
- /// Sets [selectionStart] to be [start] code units after the end of the
- /// current text in this piece.
- void startSelection(int start) {
- _selectionStart = _adjustSelection(start);
- }
-
- /// Sets [selectionEnd] to be [end] code units after the end of the
- /// current text in this piece.
- void endSelection(int end) {
- _selectionEnd = _adjustSelection(end);
- }
-
/// Adjust [offset] by the current length of this [TextPiece].
int _adjustSelection(int offset) {
for (var line in _lines) {
@@ -163,7 +151,7 @@
/// Whitespace at the end of the comment.
final Whitespace _trailingWhitespace;
- CommentPiece([this._trailingWhitespace = Whitespace.none]);
+ CommentPiece(this._trailingWhitespace);
@override
void format(CodeWriter writer, State state) {
@@ -180,6 +168,32 @@
void forEachChild(void Function(Piece piece) callback) {}
}
+/// A piece for the special `// dart format off` and `// dart format on`
+/// comments that are used to opt a region of code out of being formatted.
+class EnableFormattingCommentPiece extends CommentPiece {
+ /// Whether this comment disables formatting (`format off`) or re-enables it
+ /// (`format on`).
+ final bool _enabled;
+
+ /// The number of code points from the beginning of the unformatted source
+ /// where the unformatted code should begin or end.
+ ///
+ /// If this piece is for `// dart format off`, then the offset is just past
+ /// the `off`. If this piece is for `// dart format on`, it points to just
+ /// before `//`.
+ final int _sourceOffset;
+
+ EnableFormattingCommentPiece(this._sourceOffset, super._trailingWhitespace,
+ {required bool enable})
+ : _enabled = enable;
+
+ @override
+ void format(CodeWriter writer, State state) {
+ super.format(writer, state);
+ writer.setFormattingEnabled(_enabled, _sourceOffset);
+ }
+}
+
/// A piece that writes a single space.
class SpacePiece extends Piece {
@override
diff --git a/test/tall/other/format_off.unit b/test/tall/other/format_off.unit
new file mode 100644
index 0000000..fc96f28
--- /dev/null
+++ b/test/tall/other/format_off.unit
@@ -0,0 +1,196 @@
+40 columns |
+### Tests for disabling formatting on regions of code.
+>>> Simple unformatted region.
+main() {
+ here + gets + formatted ;
+ // dart format off
+ here + does + not ;
+ // dart format on
+ but + here + does ;
+}
+<<<
+main() {
+ here + gets + formatted;
+ // dart format off
+ here + does + not ;
+ // dart format on
+ but + here + does;
+}
+>>> Multiple unformatted regions.
+main() {
+ here + gets + formatted ;
+ // dart format off
+ here + does + not ;
+ // dart format on
+ but + here + does ;
+
+ {
+ more + formatted ;
+ // dart format off
+ more + unformatted ;
+ multiple + lines ;
+ // dart format on
+ }
+}
+<<<
+main() {
+ here + gets + formatted;
+ // dart format off
+ here + does + not ;
+ // dart format on
+ but + here + does;
+
+ {
+ more + formatted;
+ // dart format off
+ more + unformatted ;
+ multiple + lines ;
+ // dart format on
+ }
+}
+>>> Begin outside block and end inside.
+main() {
+ before ;
+ // dart format off
+ inside + region + outside + block ;
+ {
+ inside + block + and + region ;
+ // dart format on
+ inside + block + outside + region ;
+ }
+ outside + block ;
+}
+<<<
+main() {
+ before;
+ // dart format off
+ inside + region + outside + block ;
+ {
+ inside + block + and + region ;
+ // dart format on
+ inside + block + outside + region;
+ }
+ outside + block;
+}
+>>> Begin inside block and end outside.
+main() {
+ before ;
+ {
+ inside + block + outside + region ;
+ // dart format off
+ inside + block + and + region ;
+ }
+ outside + block + inside + region ;
+ // dart format on
+ outside + block ;
+}
+<<<
+main() {
+ before;
+ {
+ inside + block + outside + region;
+ // dart format off
+ inside + block + and + region ;
+ }
+ outside + block + inside + region ;
+ // dart format on
+ outside + block;
+}
+>>> Preserve indentation in region but not region marker comments.
+main() {
+ too + much ;
+ // dart format off
+ still + too + much ;
+ // dart format on
+ again + too + much ;
+}
+<<<
+main() {
+ too + much;
+ // dart format off
+ still + too + much ;
+ // dart format on
+ again + too + much;
+}
+>>> On without off does nothing.
+main() {
+ before + marker ;
+ // dart format on
+ after + marker ;
+}
+<<<
+main() {
+ before + marker;
+ // dart format on
+ after + marker;
+}
+>>> Off without on leaves rest of file unformatted.
+main() {
+ before + marker ;
+ // dart format off
+ after + marker ;
+}
+<<<
+main() {
+ before + marker;
+ // dart format off
+ after + marker ;
+}
+>>> Markers do not nest.
+main() {
+ one ;
+ // dart format off
+ two ;
+ // dart format off
+ three ;
+ // dart format on
+ four ;
+ // dart format on
+ five ;
+}
+<<<
+main() {
+ one;
+ // dart format off
+ two ;
+ // dart format off
+ three ;
+ // dart format on
+ four;
+ // dart format on
+ five;
+}
+>>> Formatted code on same line before format off comment.
+main() {
+ long + code + before + comment + // dart format off
+ unformatted + code +
+ // dart format on
+ after + region;
+}
+<<<
+main() {
+ long +
+ code +
+ before +
+ comment + // dart format off
+ unformatted + code +
+ // dart format on
+ after +
+ region;
+}
+>>> Unformatted code on same line before format on comment.
+main() {
+ before + region +
+ // dart format off
+ unformatted + code + // dart format on
+ after + region;
+}
+<<<
+main() {
+ before +
+ region +
+ // dart format off
+ unformatted + code + // dart format on
+ after +
+ region;
+}
\ No newline at end of file
diff --git a/test/tall/other/format_off_selection.unit b/test/tall/other/format_off_selection.unit
new file mode 100644
index 0000000..e0ff161
--- /dev/null
+++ b/test/tall/other/format_off_selection.unit
@@ -0,0 +1,65 @@
+40 columns |
+>>> Selection start inside disabled formatting.
+main() {
+ here + gets + format‹ted ;
+ // dart format off
+ here + does › + not ;
+ // dart format on
+ but + here + does ;
+}
+<<<
+main() {
+ here + gets + format‹ted;
+ // dart format off
+ here + does › + not ;
+ // dart format on
+ but + here + does;
+}
+>>> Selection end inside disabled formatting.
+main() {
+ here + gets + formatted ;
+ // dart format off
+ here + does ‹ + not ;
+ // dart format on
+ but + here + do›es ;
+}
+<<<
+main() {
+ here + gets + formatted;
+ // dart format off
+ here + does ‹ + not ;
+ // dart format on
+ but + here + do›es;
+}
+>>> Selection inside disabled formatting.
+main() {
+ here + gets + formatted ;
+ // dart format off
+ here ‹ + does + › not ;
+ // dart format on
+ but + here + does ;
+}
+<<<
+main() {
+ here + gets + formatted;
+ // dart format off
+ here ‹ + does + › not ;
+ // dart format on
+ but + here + does;
+}
+>>> Selection around disabled formatting.
+main() {
+ here + gets + format‹ted ;
+ // dart format off
+ here + does + not ;
+ // dart format on
+ but + here + do›es ;
+}
+<<<
+main() {
+ here + gets + format‹ted;
+ // dart format off
+ here + does + not ;
+ // dart format on
+ but + here + do›es;
+}
\ No newline at end of file
diff --git a/test/tall/selection/selection.stmt b/test/tall/other/selection.stmt
similarity index 100%
rename from test/tall/selection/selection.stmt
rename to test/tall/other/selection.stmt
diff --git a/test/tall/selection/selection.unit b/test/tall/other/selection.unit
similarity index 100%
rename from test/tall/selection/selection.unit
rename to test/tall/other/selection.unit
diff --git a/test/tall/selection/comma.stmt b/test/tall/other/selection_comma.stmt
similarity index 100%
rename from test/tall/selection/comma.stmt
rename to test/tall/other/selection_comma.stmt
diff --git a/test/tall/selection/comment.stmt b/test/tall/other/selection_comment.stmt
similarity index 100%
rename from test/tall/selection/comment.stmt
rename to test/tall/other/selection_comment.stmt
diff --git a/test/tall/top_level/trailing_whitespace.unit b/test/tall/other/trailing_whitespace.unit
similarity index 100%
rename from test/tall/top_level/trailing_whitespace.unit
rename to test/tall/other/trailing_whitespace.unit
diff --git a/test/tall/top_level/unicode.unit b/test/tall/other/unicode.unit
similarity index 100%
rename from test/tall/top_level/unicode.unit
rename to test/tall/other/unicode.unit
diff --git a/test/tall_format_test.dart b/test/tall_format_test.dart
index d55f4a3..35404ba 100644
--- a/test/tall_format_test.dart
+++ b/test/tall_format_test.dart
@@ -14,8 +14,8 @@
await testDirectory('tall/expression');
await testDirectory('tall/function');
await testDirectory('tall/invocation');
+ await testDirectory('tall/other');
await testDirectory('tall/pattern');
- await testDirectory('tall/selection');
await testDirectory('tall/statement');
await testDirectory('tall/top_level');
await testDirectory('tall/type');