Coverage report for lib/src/rule/rule.dart

Line coverage: 39 / 40 (97.5%)

All files > lib/src/rule/rule.dart

1
// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
2
// for details. All rights reserved. Use of this source code is governed by a
3
// BSD-style license that can be found in the LICENSE file.
4
5
library dart_style.src.rule.rule;
6
7
import '../chunk.dart';
8
import '../fast_hash.dart';
9
10
/// A constraint that determines the different ways a related set of chunks may
11
/// be split.
12
class Rule extends FastHash {
13
  /// Rule value that splits no chunks.
14
  ///
15
  /// Every rule is required to treat this value as fully unsplit.
16
  static const unsplit = 0;
17
18
  /// Rule constraint value that means "any value as long as something splits".
19
  ///
20
  /// It disallows [unsplit] but allows any other value.
21
  static const mustSplit = -1;
22
23
  /// The number of different states this rule can be in.
24
  ///
25
  /// Each state determines which set of chunks using this rule are split and
26
  /// which aren't. Values range from zero to one minus this. Value zero
27
  /// always means "no chunks are split" and increasing values by convention
28
  /// mean increasingly undesirable splits.
29
  ///
30
  /// By default, a rule has two values: fully unsplit and fully split.
312
  int get numValues => 2;
32
33
  /// The rule value that forces this rule into its maximally split state.
34
  ///
35
  /// By convention, this is the highest of the range of allowed values.
366
  int get fullySplitValue => numValues - 1;
37
384
  int get cost => _cost;
39
  final int _cost;
40
41
  /// During line splitting [LineSplitter] sets this to the index of this
42
  /// rule in its list of rules.
43
  int index;
44
45
  /// If `true`, the rule has been "hardened" meaning it's been placed into a
46
  /// permanent "must fully split" state.
476
  bool get isHardened => _isHardened;
48
  bool _isHardened = false;
49
50
  /// The other [Rule]s that are implied this one.
51
  ///
52
  /// In many cases, if a split occurs inside an expression, surrounding rules
53
  /// also want to split too. For example, a split in the middle of an argument
54
  /// forces the entire argument list to also split.
55
  ///
56
  /// This tracks those relationships. If this rule splits, (sets its value to
57
  /// [fullySplitValue]) then all of the surrounding implied rules are also set
58
  /// to their fully split value.
59
  ///
60
  /// This contains all direct as well as transitive relationships. If A
61
  /// contains B which contains C, C's outerRules contains both B and A.
62
  final Set<Rule> _implied = new Set<Rule>();
63
64
  /// Marks [other] as implied by this one.
65
  ///
66
  /// That means that if this rule splits, then [other] is force to split too.
672
  void imply(Rule other) {
684
    _implied.add(other);
69
  }
70
71
  /// Whether this rule cares about rules that it contains.
72
  ///
73
  /// If `true` then inner rules will constrain this one and force it to split
74
  /// when they split. Otherwise, it can split independently of any contained
75
  /// rules.
762
  bool get splitsOnInnerRules => true;
77
783
  Rule([int cost]) : _cost = cost ?? Cost.normal;
79
80
  /// Creates a new rule that is already fully split.
813
  Rule.hard() : _cost = 0 {
82
    // Set the cost to zero since it will always be applied, so there's no
83
    // point in penalizing it.
84
    //
85
    // Also, this avoids doubled counting in literal blocks where there is both
86
    // a split in the outer chunk containing the block and the inner hard split
87
    // between the elements or statements.
883
    harden();
89
  }
90
91
  /// Fixes this rule into a "fully split" state.
923
  void harden() {
933
    _isHardened = true;
94
  }
95
96
  /// Returns `true` if [chunk] should split when this rule has [value].
973
  bool isSplit(int value, Chunk chunk) {
983
    if (_isHardened) return true;
99
1003
    if (value == Rule.unsplit) return false;
101
102
    // Let the subclass decide.
1032
    return isSplitAtValue(value, chunk);
104
  }
105
106
  /// Subclasses can override this to determine which values split which chunks.
107
  ///
108
  /// By default, this assumes every chunk splits.
1092
  bool isSplitAtValue(int value, Chunk chunk) => true;
110
111
  /// Given that this rule has [value], determine if [other]'s value should be
112
  /// constrained.
113
  ///
114
  /// Allows relationships between rules like "if I split, then this should
115
  /// split too". Returns a non-negative value to force [other] to take that
116
  /// value. Returns -1 to allow [other] to take any non-zero value. Returns
117
  /// null to not constrain other.
1182
  int constrain(int value, Rule other) {
119
    // By default, any containing rule will be fully split if this one is split.
1202
    if (value == Rule.unsplit) return null;
1216
    if (_implied.contains(other)) return other.fullySplitValue;
122
123
    return null;
124
  }
125
126
  /// A protected method for subclasses to add the rules that they constrain
127
  /// to [rules].
128
  ///
129
  /// Called by [Rule] the first time [constrainedRules] is accessed.
1302
  void addConstrainedRules(Set<Rule> rules) {}
131
132
  /// Discards constraints on any rule that doesn't have an index.
133
  ///
134
  /// This is called by [LineSplitter] after it has indexed all of the in-use
135
  /// rules. A rule may end up with a constraint on a rule that's no longer
136
  /// used by any chunk. This can happen if the rule gets hardened, or if it
137
  /// simply never got used by a chunk. For example, a rule for splitting an
138
  /// empty list of metadata annotations.
139
  ///
140
  /// This removes all of those.
1413
  void forgetUnusedRules() {
14210
    _implied.retainWhere((rule) => rule.index != null);
143
144
    // Clear the cached ones too.
1453
    _constrainedRules = null;
1463
    _allConstrainedRules = null;
147
  }
148
149
  /// The other [Rule]s that this rule places immediate constraints on.
1502
  Set<Rule> get constrainedRules {
151
    // Lazy initialize this on first use. Note: Assumes this is only called
152
    // after the chunks have been written and any constraints have been wired
153
    // up.
1542
    if (_constrainedRules == null) {
1556
      _constrainedRules = _implied.toSet();
1564
      addConstrainedRules(_constrainedRules);
157
    }
158
1592
    return _constrainedRules;
160
  }
161
162
  Set<Rule> _constrainedRules;
163
164
  /// The transitive closure of all of the rules this rule places constraints
165
  /// on, directly or indirectly, including itself.
1662
  Set<Rule> get allConstrainedRules {
1672
    if (_allConstrainedRules == null) {
1682
      visit(Rule rule) {
1694
        if (_allConstrainedRules.contains(rule)) return;
170
1714
        _allConstrainedRules.add(rule);
1724
        rule.constrainedRules.forEach(visit);
173
      }
174
1754
      _allConstrainedRules = new Set();
1762
      visit(this);
177
    }
178
1792
    return _allConstrainedRules;
180
  }
181
182
  Set<Rule> _allConstrainedRules;
183
1840
  String toString() => "$id";
185
}