| Index: packages/dart_style/lib/src/rule/rule.dart
|
| diff --git a/packages/dart_style/lib/src/rule/rule.dart b/packages/dart_style/lib/src/rule/rule.dart
|
| index 43a7f6aae2c14119d5c198a80997627560d981cd..e01d3b0ce100cdd3fb1e904efdcaa1ea02ebd494 100644
|
| --- a/packages/dart_style/lib/src/rule/rule.dart
|
| +++ b/packages/dart_style/lib/src/rule/rule.dart
|
| @@ -9,26 +9,43 @@ import '../fast_hash.dart';
|
|
|
| /// A constraint that determines the different ways a related set of chunks may
|
| /// be split.
|
| -abstract class Rule extends FastHash {
|
| +class Rule extends FastHash {
|
| + /// Rule value that splits no chunks.
|
| + ///
|
| + /// Every rule is required to treat this value as fully unsplit.
|
| + static const unsplit = 0;
|
| +
|
| + /// Rule constraint value that means "any value as long as something splits".
|
| + ///
|
| + /// It disallows [unsplit] but allows any other value.
|
| + static const mustSplit = -1;
|
| +
|
| /// The number of different states this rule can be in.
|
| ///
|
| /// Each state determines which set of chunks using this rule are split and
|
| /// which aren't. Values range from zero to one minus this. Value zero
|
| /// always means "no chunks are split" and increasing values by convention
|
| /// mean increasingly undesirable splits.
|
| - int get numValues;
|
| + ///
|
| + /// By default, a rule has two values: fully unsplit and fully split.
|
| + int get numValues => 2;
|
|
|
| /// The rule value that forces this rule into its maximally split state.
|
| ///
|
| /// By convention, this is the highest of the range of allowed values.
|
| int get fullySplitValue => numValues - 1;
|
|
|
| - int get cost => Cost.normal;
|
| + final int cost;
|
|
|
| /// During line splitting [LineSplitter] sets this to the index of this
|
| /// rule in its list of rules.
|
| int index;
|
|
|
| + /// If `true`, the rule has been "hardened" meaning it's been placed into a
|
| + /// permanent "must fully split" state.
|
| + bool get isHardened => _isHardened;
|
| + bool _isHardened = false;
|
| +
|
| /// The other [Rule]s that "surround" this one (and care about that fact).
|
| ///
|
| /// In many cases, if a split occurs inside an expression, surrounding rules
|
| @@ -41,7 +58,6 @@ abstract class Rule extends FastHash {
|
| ///
|
| /// This contains all direct as well as transitive relationships. If A
|
| /// contains B which contains C, C's outerRules contains both B and A.
|
| - Iterable<Rule> get outerRules => _outerRules;
|
| final Set<Rule> _outerRules = new Set<Rule>();
|
|
|
| /// Adds [inner] as an inner rule of this rule if it cares about inner rules.
|
| @@ -60,7 +76,38 @@ abstract class Rule extends FastHash {
|
| /// rules.
|
| bool get splitsOnInnerRules => true;
|
|
|
| - bool isSplit(int value, Chunk chunk);
|
| + Rule([int cost]) : cost = cost ?? Cost.normal;
|
| +
|
| + /// Creates a new rule that is already fully split.
|
| + Rule.hard() : cost = 0 {
|
| + // Set the cost to zero since it will always be applied, so there's no
|
| + // point in penalizing it.
|
| + //
|
| + // Also, this avoids doubled counting in literal blocks where there is both
|
| + // a split in the outer chunk containing the block and the inner hard split
|
| + // between the elements or statements.
|
| + harden();
|
| + }
|
| +
|
| + /// Fixes this rule into a "fully split" state.
|
| + void harden() {
|
| + _isHardened = true;
|
| + }
|
| +
|
| + /// Returns `true` if [chunk] should split when this rule has [value].
|
| + bool isSplit(int value, Chunk chunk) {
|
| + if (_isHardened) return true;
|
| +
|
| + if (value == Rule.unsplit) return false;
|
| +
|
| + // Let the subclass decide.
|
| + return isSplitAtValue(value, chunk);
|
| + }
|
| +
|
| + /// Subclasses can override this to determine which values split which chunks.
|
| + ///
|
| + /// By default, this assumes every chunk splits.
|
| + bool isSplitAtValue(value, chunk) => true;
|
|
|
| /// Given that this rule has [value], determine if [other]'s value should be
|
| /// constrained.
|
| @@ -70,51 +117,70 @@ abstract class Rule extends FastHash {
|
| /// value. Returns -1 to allow [other] to take any non-zero value. Returns
|
| /// null to not constrain other.
|
| int constrain(int value, Rule other) {
|
| - // By default, any implied rule will be fully split if this one is fully
|
| - // split.
|
| - if (value == 0) return null;
|
| + // By default, any containing rule will be fully split if this one is split.
|
| + if (value == Rule.unsplit) return null;
|
| if (_outerRules.contains(other)) return other.fullySplitValue;
|
|
|
| return null;
|
| }
|
|
|
| - String toString() => "$id";
|
| -}
|
| -
|
| -/// A rule that always splits a chunk.
|
| -class HardSplitRule extends Rule {
|
| - int get numValues => 1;
|
| + /// A protected method for subclasses to add the rules that they constrain
|
| + /// to [rules].
|
| + ///
|
| + /// Called by [Rule] the first time [constrainedRules] is accessed.
|
| + void addConstrainedRules(Set<Rule> rules) {}
|
|
|
| - /// It's always going to be applied, so there's no point in penalizing it.
|
| + /// Discards constraints on any rule that doesn't have an index.
|
| + ///
|
| + /// This is called by [LineSplitter] after it has indexed all of the in-use
|
| + /// rules. A rule may end up with a constraint on a rule that's no longer
|
| + /// used by any chunk. This can happen if the rule gets hardened, or if it
|
| + /// simply never got used by a chunk. For example, a rule for splitting an
|
| + /// empty list of metadata annotations.
|
| ///
|
| - /// Also, this avoids doubled counting in literal blocks where there is both
|
| - /// a split in the outer chunk containing the block and the inner hard split
|
| - /// between the elements or statements.
|
| - int get cost => 0;
|
| + /// This removes all of those.
|
| + void forgetUnusedRules() {
|
| + _outerRules.retainWhere((rule) => rule.index != null);
|
|
|
| - /// It's always split anyway.
|
| - bool get splitsOnInnerRules => false;
|
| + // Clear the cached ones too.
|
| + _constrainedRules = null;
|
| + _allConstrainedRules = null;
|
| + }
|
|
|
| - bool isSplit(int value, Chunk chunk) => true;
|
| + /// The other [Rule]s that this rule places immediate constraints on.
|
| + Set<Rule> get constrainedRules {
|
| + // Lazy initialize this on first use. Note: Assumes this is only called
|
| + // after the chunks have been written and any constraints have been wired
|
| + // up.
|
| + if (_constrainedRules == null) {
|
| + _constrainedRules = _outerRules.toSet();
|
| + addConstrainedRules(_constrainedRules);
|
| + }
|
| +
|
| + return _constrainedRules;
|
| + }
|
|
|
| - String toString() => "Hard";
|
| -}
|
| + Set<Rule> _constrainedRules;
|
|
|
| -/// A basic rule that has two states: unsplit or split.
|
| -class SimpleRule extends Rule {
|
| - /// Two values: 0 is unsplit, 1 is split.
|
| - int get numValues => 2;
|
| + /// The transitive closure of all of the rules this rule places constraints
|
| + /// on, directly or indirectly, including itself.
|
| + Set<Rule> get allConstrainedRules {
|
| + if (_allConstrainedRules == null) {
|
| + visit(Rule rule) {
|
| + if (_allConstrainedRules.contains(rule)) return;
|
|
|
| - final int cost;
|
| + _allConstrainedRules.add(rule);
|
| + rule.constrainedRules.forEach(visit);
|
| + }
|
|
|
| - final bool splitsOnInnerRules;
|
| + _allConstrainedRules = new Set();
|
| + visit(this);
|
| + }
|
|
|
| - SimpleRule({int cost, bool splitsOnInnerRules})
|
| - : cost = cost != null ? cost : Cost.normal,
|
| - splitsOnInnerRules =
|
| - splitsOnInnerRules != null ? splitsOnInnerRules : true;
|
| + return _allConstrainedRules;
|
| + }
|
|
|
| - bool isSplit(int value, Chunk chunk) => value == 1;
|
| + Set<Rule> _allConstrainedRules;
|
|
|
| - String toString() => "Simple${super.toString()}";
|
| + String toString() => "$id";
|
| }
|
|
|