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"; |
} |