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