| 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.
|
| 31 | 2 | 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.
|
| 36 | 6 | int get fullySplitValue => numValues - 1;
|
| 37 | |
|
| 38 | 4 | 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.
|
| 47 | 6 | 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.
|
| 67 | 2 | void imply(Rule other) {
|
| 68 | 4 | _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.
|
| 76 | 2 | bool get splitsOnInnerRules => true;
|
| 77 | |
|
| 78 | 3 | Rule([int cost]) : _cost = cost ?? Cost.normal;
|
| 79 | |
|
| 80 | | /// Creates a new rule that is already fully split.
|
| 81 | 3 | 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.
|
| 88 | 3 | harden();
|
| 89 | | }
|
| 90 | |
|
| 91 | | /// Fixes this rule into a "fully split" state.
|
| 92 | 3 | void harden() {
|
| 93 | 3 | _isHardened = true;
|
| 94 | | }
|
| 95 | |
|
| 96 | | /// Returns `true` if [chunk] should split when this rule has [value].
|
| 97 | 3 | bool isSplit(int value, Chunk chunk) {
|
| 98 | 3 | if (_isHardened) return true;
|
| 99 | |
|
| 100 | 3 | if (value == Rule.unsplit) return false;
|
| 101 | |
|
| 102 | | // Let the subclass decide.
|
| 103 | 2 | 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.
|
| 109 | 2 | 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.
|
| 118 | 2 | int constrain(int value, Rule other) {
|
| 119 | | // By default, any containing rule will be fully split if this one is split.
|
| 120 | 2 | if (value == Rule.unsplit) return null;
|
| 121 | 6 | 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.
|
| 130 | 2 | 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.
|
| 141 | 3 | void forgetUnusedRules() {
|
| 142 | 10 | _implied.retainWhere((rule) => rule.index != null);
|
| 143 | |
|
| 144 | | // Clear the cached ones too.
|
| 145 | 3 | _constrainedRules = null;
|
| 146 | 3 | _allConstrainedRules = null;
|
| 147 | | }
|
| 148 | |
|
| 149 | | /// The other [Rule]s that this rule places immediate constraints on.
|
| 150 | 2 | 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.
|
| 154 | 2 | if (_constrainedRules == null) {
|
| 155 | 6 | _constrainedRules = _implied.toSet();
|
| 156 | 4 | addConstrainedRules(_constrainedRules);
|
| 157 | | }
|
| 158 | |
|
| 159 | 2 | 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.
|
| 166 | 2 | Set<Rule> get allConstrainedRules {
|
| 167 | 2 | if (_allConstrainedRules == null) {
|
| 168 | 2 | visit(Rule rule) {
|
| 169 | 4 | if (_allConstrainedRules.contains(rule)) return;
|
| 170 | |
|
| 171 | 4 | _allConstrainedRules.add(rule);
|
| 172 | 4 | rule.constrainedRules.forEach(visit);
|
| 173 | | }
|
| 174 | |
|
| 175 | 4 | _allConstrainedRules = new Set();
|
| 176 | 2 | visit(this);
|
| 177 | | }
|
| 178 | |
|
| 179 | 2 | return _allConstrainedRules;
|
| 180 | | }
|
| 181 | |
|
| 182 | | Set<Rule> _allConstrainedRules;
|
| 183 | |
|
| 184 | 0 | String toString() => "$id";
|
| 185 | | }
|