| Index: packages/dart_style/lib/src/chunk_builder.dart | 
| diff --git a/packages/dart_style/lib/src/chunk_builder.dart b/packages/dart_style/lib/src/chunk_builder.dart | 
| index c7ff91bb200efe5ed2b961456910e6b34da1e193..3aa95a5caf60adbf4b0aabede74e244216916081 100644 | 
| --- a/packages/dart_style/lib/src/chunk_builder.dart | 
| +++ b/packages/dart_style/lib/src/chunk_builder.dart | 
| @@ -61,9 +61,6 @@ class ChunkBuilder { | 
| /// written before they start. | 
| final _lazyRules = <Rule>[]; | 
|  | 
| -  /// The indexes of the chunks owned by each rule (except for hard splits). | 
| -  final _ruleChunks = <Rule, List<int>>{}; | 
| - | 
| /// The nested stack of spans that are currently being written. | 
| final _openSpans = <OpenSpan>[]; | 
|  | 
| @@ -101,8 +98,8 @@ class ChunkBuilder { | 
| /// token pair. | 
| bool get needsToPreserveNewlines => | 
| _pendingWhitespace == Whitespace.oneOrTwoNewlines || | 
| -          _pendingWhitespace == Whitespace.spaceOrNewline || | 
| -          _pendingWhitespace == Whitespace.splitOrNewline; | 
| +      _pendingWhitespace == Whitespace.spaceOrNewline || | 
| +      _pendingWhitespace == Whitespace.splitOrNewline; | 
|  | 
| /// The number of characters of code that can fit in a single line. | 
| int get pageWidth => _formatter.pageWidth; | 
| @@ -151,25 +148,18 @@ class ChunkBuilder { | 
|  | 
| /// Write a split owned by the current innermost rule. | 
| /// | 
| -  /// If [nesting] is given, uses that. Otherwise, uses the current nesting | 
| -  /// level. If unsplit, it expands to a space if [space] is `true`. | 
| -  /// | 
| /// If [flushLeft] is `true`, then forces the next line to start at column | 
| /// one regardless of any indentation or nesting. | 
| /// | 
| /// If [isDouble] is passed, forces the split to either be a single or double | 
| /// newline. Otherwise, leaves it indeterminate. | 
| -  Chunk split({bool space, bool isDouble, bool flushLeft}) => | 
| -      _writeSplit(_rules.last, null, | 
| -          flushLeft: flushLeft, isDouble: isDouble, spaceWhenUnsplit: space); | 
| - | 
| -  /// Write a split owned by the current innermost rule. | 
| /// | 
| -  /// Unlike [split()], this ignores any current expression nesting. It always | 
| -  /// indents the next line at the statement level. | 
| -  Chunk blockSplit({bool space, bool isDouble}) => | 
| -      _writeSplit(_rules.last, _nesting.blockNesting, | 
| -          isDouble: isDouble, spaceWhenUnsplit: space); | 
| +  /// If [nest] is `false`, ignores any current expression nesting. Otherwise, | 
| +  /// uses the current nesting level. If unsplit, it expands to a space if | 
| +  /// [space] is `true`. | 
| +  Chunk split({bool flushLeft, bool isDouble, bool nest, bool space}) => | 
| +      _writeSplit(_rules.last, | 
| +          flushLeft: flushLeft, isDouble: isDouble, nest: nest, space: space); | 
|  | 
| /// Outputs the series of [comments] and associated whitespace that appear | 
| /// before [token] (which is not written by this). | 
| @@ -257,9 +247,9 @@ class ChunkBuilder { | 
| } else { | 
| // The comment starts a line, so make sure it stays on its own line. | 
| _writeHardSplit( | 
| -            nest: true, | 
| flushLeft: comment.flushLeft, | 
| -            double: comment.linesBefore > 1); | 
| +            isDouble: comment.linesBefore > 1, | 
| +            nest: true); | 
| } | 
|  | 
| _writeText(comment.text); | 
| @@ -291,7 +281,7 @@ class ChunkBuilder { | 
| } | 
| } | 
|  | 
| -      if (linesAfter > 0) _writeHardSplit(nest: true, double: linesAfter > 1); | 
| +      if (linesAfter > 0) _writeHardSplit(isDouble: linesAfter > 1, nest: true); | 
| } | 
|  | 
| // If the comment has text following it (aside from a grouping character), | 
| @@ -368,15 +358,15 @@ class ChunkBuilder { | 
| var span = new Span(openSpan.cost); | 
| for (var i = openSpan.start; i < end; i++) { | 
| var chunk = _chunks[i]; | 
| -      if (!chunk.isHardSplit) chunk.spans.add(span); | 
| +      if (!chunk.rule.isHardened) chunk.spans.add(span); | 
| } | 
| } | 
|  | 
| /// Starts a new [Rule]. | 
| /// | 
| -  /// If omitted, defaults to a new [SimpleRule]. | 
| +  /// If omitted, defaults to a new [Rule]. | 
| void startRule([Rule rule]) { | 
| -    if (rule == null) rule = new SimpleRule(); | 
| +    if (rule == null) rule = new Rule(); | 
|  | 
| // See if any of the rules that contain this one care if it splits. | 
| _rules.forEach((outer) => outer.contain(rule)); | 
| @@ -390,9 +380,9 @@ class ChunkBuilder { | 
| /// first operand but not get forced to split if a comment appears before the | 
| /// entire expression. | 
| /// | 
| -  /// If [rule] is omitted, defaults to a new [SimpleRule]. | 
| +  /// If [rule] is omitted, defaults to a new [Rule]. | 
| void startLazyRule([Rule rule]) { | 
| -    if (rule == null) rule = new SimpleRule(); | 
| +    if (rule == null) rule = new Rule(); | 
|  | 
| _lazyRules.add(rule); | 
| } | 
| @@ -431,8 +421,14 @@ class ChunkBuilder { | 
| /// | 
| /// Expressions that are more nested will get increased indentation when split | 
| /// if the previous line has a lower level of nesting. | 
| -  void unnest() { | 
| +  /// | 
| +  /// If [now] is `false`, does not commit the nesting change until after the | 
| +  /// next chunk of text is written. | 
| +  void unnest({bool now}) { | 
| +    if (now == null) now = true; | 
| + | 
| _nesting.unnest(); | 
| +    if (now) _nesting.commitNesting(); | 
| } | 
|  | 
| /// Marks the selection starting point as occurring [fromEnd] characters to | 
| @@ -469,9 +465,12 @@ class ChunkBuilder { | 
| /// Starts a new block as a child of the current chunk. | 
| /// | 
| /// Nested blocks are handled using their own independent [LineWriter]. | 
| -  ChunkBuilder startBlock() { | 
| +  ChunkBuilder startBlock(Chunk argumentChunk) { | 
| +    var chunk = _chunks.last; | 
| +    chunk.makeBlock(argumentChunk); | 
| + | 
| var builder = | 
| -        new ChunkBuilder._(this, _formatter, _source, _chunks.last.blockChunks); | 
| +        new ChunkBuilder._(this, _formatter, _source, chunk.block.chunks); | 
|  | 
| // A block always starts off indented one level. | 
| builder.indent(); | 
| @@ -488,7 +487,7 @@ class ChunkBuilder { | 
| /// `true`, the block is considered to always split. | 
| /// | 
| /// Returns the previous writer for the surrounding block. | 
| -  ChunkBuilder endBlock(HardSplitRule ignoredSplit, {bool forceSplit}) { | 
| +  ChunkBuilder endBlock(Rule ignoredSplit, {bool forceSplit}) { | 
| _divideChunks(); | 
|  | 
| // If we don't already know if the block is going to split, see if it | 
| @@ -502,21 +501,33 @@ class ChunkBuilder { | 
| break; | 
| } | 
|  | 
| -        if (chunk.isHardSplit && chunk.rule != ignoredSplit) { | 
| +        if (chunk.rule != null && | 
| +            chunk.rule.isHardened && | 
| +            chunk.rule != ignoredSplit) { | 
| forceSplit = true; | 
| break; | 
| } | 
| } | 
| } | 
|  | 
| +    _parent._endChildBlock( | 
| +        firstFlushLeft: _firstFlushLeft, forceSplit: forceSplit); | 
| + | 
| +    return _parent; | 
| +  } | 
| + | 
| +  /// Finishes off the last chunk in a child block of this parent. | 
| +  void _endChildBlock({bool firstFlushLeft, bool forceSplit}) { | 
| // If there is a hard newline within the block, force the surrounding rule | 
| // for it so that we apply that constraint. | 
| -    if (forceSplit) _parent.forceRules(); | 
| +    if (forceSplit) forceRules(); | 
|  | 
| // Write the split for the block contents themselves. | 
| -    _parent._writeSplit(_parent._rules.last, _parent._blockArgumentNesting.last, | 
| -        flushLeft: _firstFlushLeft); | 
| -    return _parent; | 
| +    var chunk = _chunks.last; | 
| +    chunk.applySplit(rule, _nesting.indentation, _blockArgumentNesting.last, | 
| +        flushLeft: firstFlushLeft); | 
| + | 
| +    if (chunk.rule.isHardened) _handleHardSplit(); | 
| } | 
|  | 
| /// Finishes writing and returns a [SourceCode] containing the final output | 
| @@ -577,11 +588,11 @@ class ChunkBuilder { | 
| break; | 
|  | 
| case Whitespace.newlineFlushLeft: | 
| -        _writeHardSplit(nest: true, flushLeft: true); | 
| +        _writeHardSplit(flushLeft: true, nest: true); | 
| break; | 
|  | 
| case Whitespace.twoNewlines: | 
| -        _writeHardSplit(double: true); | 
| +        _writeHardSplit(isDouble: true); | 
| break; | 
|  | 
| case Whitespace.spaceOrNewline: | 
| @@ -661,46 +672,38 @@ class ChunkBuilder { | 
| /// Appends a hard split with the current indentation and nesting (the latter | 
| /// only if [nest] is `true`). | 
| /// | 
| -  /// If [double] is `true` or `false`, forces a since or double line to be | 
| +  /// If [double] is `true` or `false`, forces a single or double line to be | 
| /// output. Otherwise, it is left indeterminate. | 
| /// | 
| /// If [flushLeft] is `true`, then the split will always cause the next line | 
| /// to be at column zero. Otherwise, it uses the normal indentation and | 
| /// nesting behavior. | 
| -  void _writeHardSplit({bool nest: false, bool double, bool flushLeft}) { | 
| +  void _writeHardSplit({bool isDouble, bool flushLeft, bool nest: false}) { | 
| // A hard split overrides any other whitespace. | 
| _pendingWhitespace = null; | 
| -    _writeSplit(new HardSplitRule(), nest ? null : _nesting.blockNesting, | 
| -        flushLeft: flushLeft, isDouble: double); | 
| +    _writeSplit(new Rule.hard(), | 
| +        flushLeft: flushLeft, isDouble: isDouble, nest: nest); | 
| } | 
|  | 
| /// Ends the current chunk (if any) with the given split information. | 
| /// | 
| /// Returns the chunk. | 
| -  Chunk _writeSplit(Rule rule, NestingLevel nesting, | 
| -      {bool flushLeft, bool isDouble, bool spaceWhenUnsplit}) { | 
| +  Chunk _writeSplit(Rule rule, | 
| +      {bool flushLeft, bool isDouble, bool nest, bool space}) { | 
| +    if (nest == null) nest = true; | 
| + | 
| if (_chunks.isEmpty) { | 
| if (flushLeft != null) _firstFlushLeft = flushLeft; | 
|  | 
| return null; | 
| } | 
|  | 
| -    if (nesting == null) nesting = _nesting.nesting; | 
| +    _chunks.last.applySplit(rule, _nesting.indentation, | 
| +        nest ? _nesting.nesting : new NestingLevel(), | 
| +        flushLeft: flushLeft, isDouble: isDouble, space: space); | 
|  | 
| -    var chunk = _chunks.last; | 
| -    chunk.applySplit(rule, _nesting.indentation, nesting, | 
| -        flushLeft: flushLeft, | 
| -        isDouble: isDouble, | 
| -        spaceWhenUnsplit: spaceWhenUnsplit); | 
| - | 
| -    // Keep track of which chunks are owned by the rule. | 
| -    if (rule is! HardSplitRule) { | 
| -      _ruleChunks.putIfAbsent(rule, () => []).add(_chunks.length - 1); | 
| -    } | 
| - | 
| -    if (chunk.isHardSplit) _handleHardSplit(); | 
| - | 
| -    return chunk; | 
| +    if (_chunks.last.rule.isHardened) _handleHardSplit(); | 
| +    return _chunks.last; | 
| } | 
|  | 
| /// Writes [text] to either the current chunk or a new one if the current | 
| @@ -716,14 +719,13 @@ class ChunkBuilder { | 
| /// Returns true if we can divide the chunks at [index] and line split the | 
| /// ones before and after that separately. | 
| bool _canDivideAt(int i) { | 
| +    // Don't divide after the last chunk. | 
| +    if (i == _chunks.length - 1) return false; | 
| + | 
| var chunk = _chunks[i]; | 
| -    if (!chunk.isHardSplit) return false; | 
| +    if (!chunk.rule.isHardened) return false; | 
| if (chunk.nesting.isNested) return false; | 
| -    if (chunk.blockChunks.isNotEmpty) return false; | 
| - | 
| -    // Make sure we don't split the line in the middle of a rule. | 
| -    var chunks = _ruleChunks[chunk.rule]; | 
| -    if (chunks != null && chunks.any((other) => other > i)) return false; | 
| +    if (chunk.isBlock) return false; | 
|  | 
| return true; | 
| } | 
| @@ -765,18 +767,16 @@ class ChunkBuilder { | 
| void _hardenRules() { | 
| if (_hardSplitRules.isEmpty) return; | 
|  | 
| -    // Harden all of the rules that are constrained by [rules] as well. | 
| -    var hardenedRules = new Set(); | 
| walkConstraints(rule) { | 
| -      if (hardenedRules.contains(rule)) return; | 
| -      hardenedRules.add(rule); | 
| +      rule.harden(); | 
|  | 
| // Follow this rule's constraints, recursively. | 
| -      for (var other in _ruleChunks.keys) { | 
| +      for (var other in rule.constrainedRules) { | 
| if (other == rule) continue; | 
|  | 
| -        if (rule.constrain(rule.fullySplitValue, other) == | 
| -            other.fullySplitValue) { | 
| +        if (!other.isHardened && | 
| +            rule.constrain(rule.fullySplitValue, other) == | 
| +                other.fullySplitValue) { | 
| walkConstraints(other); | 
| } | 
| } | 
| @@ -786,10 +786,11 @@ class ChunkBuilder { | 
| walkConstraints(rule); | 
| } | 
|  | 
| -    // Harden every chunk that uses one of these rules. | 
| +    // Discard spans in hardened chunks since we know for certain they will | 
| +    // split anyway. | 
| for (var chunk in _chunks) { | 
| -      if (hardenedRules.contains(chunk.rule)) { | 
| -        chunk.harden(); | 
| +      if (chunk.rule != null && chunk.rule.isHardened) { | 
| +        chunk.spans.clear(); | 
| } | 
| } | 
| } | 
|  |