OLD | NEW |
| (Empty) |
1 // Copyright (c) 2014, 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.call_chain_visitor; | |
6 | |
7 import 'package:analyzer/analyzer.dart'; | |
8 | |
9 import 'argument_list_visitor.dart'; | |
10 import 'rule/argument.dart'; | |
11 import 'source_visitor.dart'; | |
12 | |
13 /// Helper class for [SourceVisitor] that handles visiting and writing a | |
14 /// chained series of method invocations, property accesses, and/or prefix | |
15 /// expressions. In other words, anything using the "." operator. | |
16 class CallChainVisitor { | |
17 final SourceVisitor _visitor; | |
18 | |
19 /// The initial target of the call chain. | |
20 /// | |
21 /// This may be any expression except [MethodInvocation], [PropertyAccess] or | |
22 /// [PrefixedIdentifier]. | |
23 final Expression _target; | |
24 | |
25 /// The list of dotted names ([PropertyAccess] and [PrefixedIdentifier] at | |
26 /// the start of the call chain. | |
27 /// | |
28 /// This will be empty if the [_target] is not a [SimpleIdentifier]. | |
29 final List<Expression> _properties; | |
30 | |
31 /// The mixed method calls and property accesses in the call chain in the | |
32 /// order that they appear in the source. | |
33 final List<Expression> _calls; | |
34 | |
35 /// Whether or not a [Rule] is currently active for the call chain. | |
36 bool _ruleEnabled = false; | |
37 | |
38 /// Whether or not the span wrapping the call chain is currently active. | |
39 bool _spanEnded = false; | |
40 | |
41 /// Creates a new call chain visitor for [visitor] starting with [node]. | |
42 /// | |
43 /// The [node] is the outermost expression containing the chained "." | |
44 /// operators and must be a [MethodInvocation], [PropertyAccess] or | |
45 /// [PrefixedIdentifier]. | |
46 factory CallChainVisitor(SourceVisitor visitor, Expression node) { | |
47 var target; | |
48 | |
49 // Recursively walk the chain of calls and turn the tree into a list. | |
50 var calls = []; | |
51 flatten(expression) { | |
52 target = expression; | |
53 | |
54 if (expression is MethodInvocation && expression.target != null) { | |
55 flatten(expression.target); | |
56 calls.add(expression); | |
57 } else if (expression is PropertyAccess && expression.target != null) { | |
58 flatten(expression.target); | |
59 calls.add(expression); | |
60 } else if (expression is PrefixedIdentifier) { | |
61 flatten(expression.prefix); | |
62 calls.add(expression); | |
63 } | |
64 } | |
65 | |
66 flatten(node); | |
67 | |
68 // An expression that starts with a series of dotted names gets treated a | |
69 // little specially. We don't force leading properties to split with the | |
70 // rest of the chain. Allows code like: | |
71 // | |
72 // address.street.number | |
73 // .toString() | |
74 // .length; | |
75 var properties = []; | |
76 if (target is SimpleIdentifier) { | |
77 properties = | |
78 calls.takeWhile((call) => call is! MethodInvocation).toList(); | |
79 } | |
80 | |
81 calls.removeRange(0, properties.length); | |
82 | |
83 return new CallChainVisitor._(visitor, target, properties, calls); | |
84 } | |
85 | |
86 CallChainVisitor._( | |
87 this._visitor, this._target, this._properties, this._calls); | |
88 | |
89 /// Builds chunks for the call chain. | |
90 /// | |
91 /// If [unnest] is `false` than this will not close the expression nesting | |
92 /// created for the call chain and the caller must end it. Used by cascades | |
93 /// to force a cascade after a method chain to be more deeply nested than | |
94 /// the methods. | |
95 void visit({bool unnest}) { | |
96 if (unnest == null) unnest = true; | |
97 | |
98 _visitor.builder.nestExpression(); | |
99 | |
100 // Try to keep the entire method invocation one line. | |
101 _visitor.builder.startSpan(); | |
102 | |
103 _visitor.visit(_target); | |
104 | |
105 // Leading properties split like positional arguments: either not at all, | |
106 // before one ".", or before all of them. | |
107 if (_properties.length == 1) { | |
108 _visitor.soloZeroSplit(); | |
109 _writeCall(_properties.single); | |
110 } else if (_properties.length > 1) { | |
111 var argRule = new MultiplePositionalRule(null, 0, 0); | |
112 _visitor.builder.startRule(argRule); | |
113 | |
114 for (var property in _properties) { | |
115 argRule.beforeArgument(_visitor.zeroSplit()); | |
116 _writeCall(property); | |
117 } | |
118 | |
119 _visitor.builder.endRule(); | |
120 } | |
121 | |
122 // The remaining chain of calls generally split atomically (either all or | |
123 // none), except that block arguments may split a chain into two parts. | |
124 for (var call in _calls) { | |
125 _enableRule(); | |
126 _visitor.zeroSplit(); | |
127 _writeCall(call); | |
128 } | |
129 | |
130 _disableRule(); | |
131 _endSpan(); | |
132 | |
133 if (unnest) _visitor.builder.unnest(); | |
134 } | |
135 | |
136 /// Writes [call], which must be one of the supported expression types. | |
137 void _writeCall(Expression call) { | |
138 if (call is MethodInvocation) { | |
139 _writeInvocation(call); | |
140 } else if (call is PropertyAccess) { | |
141 _writePropertyAccess(call); | |
142 } else if (call is PrefixedIdentifier) { | |
143 _writePrefixedIdentifier(call); | |
144 } else { | |
145 // Unexpected type. | |
146 assert(false); | |
147 } | |
148 } | |
149 | |
150 void _writeInvocation(MethodInvocation invocation) { | |
151 _visitor.token(invocation.operator); | |
152 _visitor.token(invocation.methodName.token); | |
153 | |
154 // If a method's argument list includes any block arguments, there's a | |
155 // good chance it will split. Treat the chains before and after that as | |
156 // separate unrelated method chains. | |
157 // | |
158 // This is kind of a hack since it treats methods before and after a | |
159 // collection literal argument differently even when the collection | |
160 // doesn't split, but it works out OK in practice. | |
161 // | |
162 // Doing something more precise would require setting up a bunch of complex | |
163 // constraints between various rules. You'd basically have to say "if the | |
164 // block argument splits then allow the chain after it to split | |
165 // independently, otherwise force it to follow the previous chain". | |
166 var args = new ArgumentListVisitor(_visitor, invocation.argumentList); | |
167 | |
168 // Stop the rule after the last call, but before its arguments. This | |
169 // allows unsplit chains where the last argument list wraps, like: | |
170 // | |
171 // foo().bar().baz( | |
172 // argument, list); | |
173 // | |
174 // Also stop the rule to split the argument list at any call with | |
175 // block arguments. This makes for nicer chains of higher-order method | |
176 // calls, like: | |
177 // | |
178 // items.map((element) { | |
179 // ... | |
180 // }).where((element) { | |
181 // ... | |
182 // }); | |
183 if (invocation == _calls.last || args.hasBlockArguments) _disableRule(); | |
184 | |
185 if (args.nestMethodArguments) _visitor.builder.startBlockArgumentNesting(); | |
186 | |
187 // For a single method call on an identifier, stop the span before the | |
188 // arguments to make it easier to keep the call name with the target. In | |
189 // other words, prefer: | |
190 // | |
191 // target.method( | |
192 // argument, list); | |
193 // | |
194 // Over: | |
195 // | |
196 // target | |
197 // .method(argument, list); | |
198 // | |
199 // Alternatively, the way to think of this is try to avoid splitting on the | |
200 // "." when calling a single method on a single name. This is especially | |
201 // important because the identifier is often a library prefix, and splitting | |
202 // there looks really odd. | |
203 if (_properties.isEmpty && | |
204 _calls.length == 1 && | |
205 _target is SimpleIdentifier) { | |
206 _endSpan(); | |
207 } | |
208 | |
209 _visitor.visit(invocation.argumentList); | |
210 | |
211 if (args.nestMethodArguments) _visitor.builder.endBlockArgumentNesting(); | |
212 } | |
213 | |
214 void _writePropertyAccess(PropertyAccess property) { | |
215 _visitor.token(property.operator); | |
216 _visitor.visit(property.propertyName); | |
217 } | |
218 | |
219 void _writePrefixedIdentifier(PrefixedIdentifier prefix) { | |
220 _visitor.token(prefix.period); | |
221 _visitor.visit(prefix.identifier); | |
222 } | |
223 | |
224 /// If a [Rule] for the method chain is currently active, ends it. | |
225 void _disableRule() { | |
226 if (_ruleEnabled == false) return; | |
227 | |
228 _visitor.builder.endRule(); | |
229 _ruleEnabled = false; | |
230 } | |
231 | |
232 /// Creates a new method chain [Rule] if one is not already active. | |
233 void _enableRule() { | |
234 if (_ruleEnabled) return; | |
235 | |
236 _visitor.builder.startRule(); | |
237 _ruleEnabled = true; | |
238 } | |
239 | |
240 /// Ends the span wrapping the call chain if it hasn't ended already. | |
241 void _endSpan() { | |
242 if (_spanEnded) return; | |
243 | |
244 _visitor.builder.endSpan(); | |
245 _spanEnded = true; | |
246 } | |
247 } | |
OLD | NEW |