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 services.src.correction.assist; | |
6 | |
7 import 'dart:collection'; | |
8 | |
9 import 'package:analysis_services/correction/assist.dart'; | |
10 import 'package:analysis_services/correction/change.dart'; | |
11 import 'package:analysis_services/search/hierarchy.dart'; | |
12 import 'package:analysis_services/search/search_engine.dart'; | |
13 import 'package:analysis_services/src/correction/name_suggestion.dart'; | |
14 import 'package:analysis_services/src/correction/source_buffer.dart'; | |
15 import 'package:analysis_services/src/correction/source_range.dart'; | |
16 import 'package:analysis_services/src/correction/statement_analyzer.dart'; | |
17 import 'package:analysis_services/src/correction/util.dart'; | |
18 import 'package:analyzer/src/generated/ast.dart'; | |
19 import 'package:analyzer/src/generated/element.dart'; | |
20 import 'package:analyzer/src/generated/java_core.dart'; | |
21 import 'package:analyzer/src/generated/scanner.dart'; | |
22 import 'package:analyzer/src/generated/source.dart'; | |
23 import 'package:path/path.dart'; | |
24 | |
25 | |
26 | |
27 typedef _SimpleIdentifierVisitor(SimpleIdentifier node); | |
28 | |
29 | |
30 /** | |
31 * The computer for Dart assists. | |
32 */ | |
33 class AssistProcessor { | |
34 final SearchEngine searchEngine; | |
35 final Source source; | |
36 final String file; | |
37 final CompilationUnit unit; | |
38 final int selectionOffset; | |
39 final int selectionLength; | |
40 CompilationUnitElement unitElement; | |
41 LibraryElement unitLibraryElement; | |
42 String unitLibraryFile; | |
43 String unitLibraryFolder; | |
44 | |
45 final List<Edit> edits = <Edit>[]; | |
46 final Map<String, LinkedEditGroup> linkedPositionGroups = <String, | |
47 LinkedEditGroup>{}; | |
48 Position exitPosition = null; | |
49 final List<Assist> assists = <Assist>[]; | |
50 | |
51 int selectionEnd; | |
52 CorrectionUtils utils; | |
53 AstNode node; | |
54 | |
55 AssistProcessor(this.searchEngine, this.source, this.file, this.unit, | |
56 this.selectionOffset, this.selectionLength) { | |
57 unitElement = unit.element; | |
58 unitLibraryElement = unitElement.library; | |
59 unitLibraryFile = unitLibraryElement.source.fullName; | |
60 unitLibraryFolder = dirname(unitLibraryFile); | |
61 selectionEnd = selectionOffset + selectionLength; | |
62 } | |
63 | |
64 /** | |
65 * Returns the EOL to use for this [CompilationUnit]. | |
66 */ | |
67 String get eol => utils.endOfLine; | |
68 | |
69 List<Assist> compute() { | |
70 utils = new CorrectionUtils(unit); | |
71 node = new NodeLocator.con2( | |
72 selectionOffset, | |
73 selectionEnd).searchWithin(unit); | |
74 // try to add proposals | |
75 _addProposal_addTypeAnnotation(); | |
76 _addProposal_assignToLocalVariable(); | |
77 _addProposal_convertToBlockFunctionBody(); | |
78 _addProposal_convertToExpressionFunctionBody(); | |
79 _addProposal_convertToIsNot_onIs(); | |
80 _addProposal_convertToIsNot_onNot(); | |
81 _addProposal_convertToIsNotEmpty(); | |
82 _addProposal_exchangeOperands(); | |
83 _addProposal_importAddShow(); | |
84 _addProposal_invertIf(); | |
85 _addProposal_joinIfStatementInner(); | |
86 _addProposal_joinIfStatementOuter(); | |
87 _addProposal_joinVariableDeclaration_onAssignment(); | |
88 _addProposal_joinVariableDeclaration_onDeclaration(); | |
89 _addProposal_removeTypeAnnotation(); | |
90 _addProposal_replaceConditionalWithIfElse(); | |
91 _addProposal_replaceIfElseWithConditional(); | |
92 _addProposal_splitAndCondition(); | |
93 _addProposal_splitVariableDeclaration(); | |
94 _addProposal_surroundWith(); | |
95 // done | |
96 return assists; | |
97 } | |
98 | |
99 FunctionBody getEnclosingFunctionBody() { | |
100 { | |
101 FunctionExpression function = | |
102 node.getAncestor((node) => node is FunctionExpression); | |
103 if (function != null) { | |
104 return function.body; | |
105 } | |
106 } | |
107 { | |
108 FunctionDeclaration function = | |
109 node.getAncestor((node) => node is FunctionDeclaration); | |
110 if (function != null) { | |
111 return function.functionExpression.body; | |
112 } | |
113 } | |
114 { | |
115 MethodDeclaration method = | |
116 node.getAncestor((node) => node is MethodDeclaration); | |
117 if (method != null) { | |
118 return method.body; | |
119 } | |
120 } | |
121 return null; | |
122 } | |
123 | |
124 void _addAssist(AssistKind kind, List args, {String assistFile}) { | |
125 if (assistFile == null) { | |
126 assistFile = file; | |
127 } | |
128 FileEdit fileEdit = new FileEdit(file); | |
129 fileEdit.addAll(edits); | |
130 // prepare Change | |
131 String message = formatList(kind.message, args); | |
132 Change change = new Change(message); | |
133 change.addFileEdit(fileEdit); | |
134 linkedPositionGroups.values.forEach( | |
135 (group) => change.addLinkedEditGroup(group)); | |
136 change.selection = exitPosition; | |
137 // add Assist | |
138 Assist assist = new Assist(kind, change); | |
139 assists.add(assist); | |
140 // clear | |
141 edits.clear(); | |
142 linkedPositionGroups.clear(); | |
143 exitPosition = null; | |
144 } | |
145 | |
146 /** | |
147 * Adds a new [Edit] to [edits]. | |
148 */ | |
149 void _addInsertEdit(int offset, String text) { | |
150 Edit edit = new Edit(offset, 0, text); | |
151 edits.add(edit); | |
152 } | |
153 | |
154 void _addProposal_addTypeAnnotation() { | |
155 // prepare VariableDeclarationList | |
156 VariableDeclarationList declarationList = | |
157 node.getAncestor((node) => node is VariableDeclarationList); | |
158 if (declarationList == null) { | |
159 _coverageMarker(); | |
160 return; | |
161 } | |
162 // may be has type annotation already | |
163 if (declarationList.type != null) { | |
164 _coverageMarker(); | |
165 return; | |
166 } | |
167 // prepare single VariableDeclaration | |
168 List<VariableDeclaration> variables = declarationList.variables; | |
169 if (variables.length != 1) { | |
170 _coverageMarker(); | |
171 return; | |
172 } | |
173 VariableDeclaration variable = variables[0]; | |
174 // we need an initializer to get the type from | |
175 Expression initializer = variable.initializer; | |
176 if (initializer == null) { | |
177 _coverageMarker(); | |
178 return; | |
179 } | |
180 DartType type = initializer.bestType; | |
181 // prepare type source | |
182 String typeSource; | |
183 if (type is InterfaceType || type is FunctionType) { | |
184 typeSource = utils.getTypeSource(type); | |
185 } else { | |
186 _coverageMarker(); | |
187 return; | |
188 } | |
189 // add edit | |
190 Token keyword = declarationList.keyword; | |
191 if (keyword is KeywordToken && keyword.keyword == Keyword.VAR) { | |
192 SourceRange range = rangeToken(keyword); | |
193 _addReplaceEdit(range, typeSource); | |
194 } else { | |
195 _addInsertEdit(variable.offset, '$typeSource '); | |
196 } | |
197 // add proposal | |
198 _addAssist(AssistKind.ADD_TYPE_ANNOTATION, []); | |
199 } | |
200 | |
201 void _addProposal_assignToLocalVariable() { | |
202 // prepare enclosing ExpressionStatement | |
203 Statement statement = node.getAncestor((node) => node is Statement); | |
204 if (statement is! ExpressionStatement) { | |
205 _coverageMarker(); | |
206 return; | |
207 } | |
208 ExpressionStatement expressionStatement = statement as ExpressionStatement; | |
209 // prepare expression | |
210 Expression expression = expressionStatement.expression; | |
211 int offset = expression.offset; | |
212 // ignore if already assignment | |
213 if (expression is AssignmentExpression) { | |
214 _coverageMarker(); | |
215 return; | |
216 } | |
217 // ignore "throw" | |
218 if (expression is ThrowExpression) { | |
219 _coverageMarker(); | |
220 return; | |
221 } | |
222 // prepare expression type | |
223 DartType type = expression.bestType; | |
224 if (type.isVoid) { | |
225 _coverageMarker(); | |
226 return; | |
227 } | |
228 // prepare source | |
229 SourceBuilder builder = new SourceBuilder(file, offset); | |
230 builder.append("var "); | |
231 // prepare excluded names | |
232 Set<String> excluded = new Set<String>(); | |
233 { | |
234 ScopedNameFinder scopedNameFinder = new ScopedNameFinder(offset); | |
235 expression.accept(scopedNameFinder); | |
236 excluded.addAll(scopedNameFinder.locals.keys.toSet()); | |
237 } | |
238 // name(s) | |
239 { | |
240 List<String> suggestions = | |
241 getVariableNameSuggestionsForExpression(type, expression, excluded); | |
242 builder.startPosition("NAME"); | |
243 for (int i = 0; i < suggestions.length; i++) { | |
244 String name = suggestions[i]; | |
245 if (i == 0) { | |
246 builder.append(name); | |
247 } | |
248 builder.addSuggestion(LinkedEditSuggestionKind.VARIABLE, name); | |
249 } | |
250 builder.endPosition(); | |
251 } | |
252 builder.append(" = "); | |
253 // add proposal | |
254 _insertBuilder(builder); | |
255 _addAssist(AssistKind.ASSIGN_TO_LOCAL_VARIABLE, []); | |
256 } | |
257 | |
258 void _addProposal_convertToBlockFunctionBody() { | |
259 FunctionBody body = getEnclosingFunctionBody(); | |
260 // prepare expression body | |
261 if (body is! ExpressionFunctionBody) { | |
262 _coverageMarker(); | |
263 return; | |
264 } | |
265 Expression returnValue = (body as ExpressionFunctionBody).expression; | |
266 // prepare prefix | |
267 String prefix = utils.getNodePrefix(body.parent); | |
268 // add change | |
269 String indent = utils.getIndent(1); | |
270 String returnSource = 'return ' + _getNodeText(returnValue); | |
271 String newBodySource = "{$eol$prefix${indent}$returnSource;$eol$prefix}"; | |
272 _addReplaceEdit(rangeNode(body), newBodySource); | |
273 // add proposal | |
274 _addAssist(AssistKind.CONVERT_INTO_BLOCK_BODY, []); | |
275 } | |
276 | |
277 void _addProposal_convertToExpressionFunctionBody() { | |
278 // prepare current body | |
279 FunctionBody body = getEnclosingFunctionBody(); | |
280 if (body is! BlockFunctionBody) { | |
281 _coverageMarker(); | |
282 return; | |
283 } | |
284 // prepare return statement | |
285 List<Statement> statements = (body as BlockFunctionBody).block.statements; | |
286 if (statements.length != 1) { | |
287 _coverageMarker(); | |
288 return; | |
289 } | |
290 if (statements[0] is! ReturnStatement) { | |
291 _coverageMarker(); | |
292 return; | |
293 } | |
294 ReturnStatement returnStatement = statements[0] as ReturnStatement; | |
295 // prepare returned expression | |
296 Expression returnExpression = returnStatement.expression; | |
297 if (returnExpression == null) { | |
298 _coverageMarker(); | |
299 return; | |
300 } | |
301 // add change | |
302 String newBodySource = "=> ${_getNodeText(returnExpression)}"; | |
303 if (body.parent is! FunctionExpression || | |
304 body.parent.parent is FunctionDeclaration) { | |
305 newBodySource += ";"; | |
306 } | |
307 _addReplaceEdit(rangeNode(body), newBodySource); | |
308 // add proposal | |
309 _addAssist(AssistKind.CONVERT_INTO_EXPRESSION_BODY, []); | |
310 } | |
311 | |
312 /** | |
313 * Converts "!isEmpty" -> "isNotEmpty" if possible. | |
314 */ | |
315 void _addProposal_convertToIsNotEmpty() { | |
316 // prepare "expr.isEmpty" | |
317 AstNode isEmptyAccess = null; | |
318 SimpleIdentifier isEmptyIdentifier = null; | |
319 if (node is SimpleIdentifier) { | |
320 SimpleIdentifier identifier = node as SimpleIdentifier; | |
321 AstNode parent = identifier.parent; | |
322 // normal case (but rare) | |
323 if (parent is PropertyAccess) { | |
324 isEmptyIdentifier = parent.propertyName; | |
325 isEmptyAccess = parent; | |
326 } | |
327 // usual case | |
328 if (parent is PrefixedIdentifier) { | |
329 isEmptyIdentifier = parent.identifier; | |
330 isEmptyAccess = parent; | |
331 } | |
332 } | |
333 if (isEmptyIdentifier == null) { | |
334 _coverageMarker(); | |
335 return; | |
336 } | |
337 // should be "isEmpty" | |
338 Element propertyElement = isEmptyIdentifier.bestElement; | |
339 if (propertyElement == null || "isEmpty" != propertyElement.name) { | |
340 _coverageMarker(); | |
341 return; | |
342 } | |
343 // should have "isNotEmpty" | |
344 Element propertyTarget = propertyElement.enclosingElement; | |
345 if (propertyTarget == null || | |
346 getChildren(propertyTarget, "isNotEmpty").isEmpty) { | |
347 _coverageMarker(); | |
348 return; | |
349 } | |
350 // should be in PrefixExpression | |
351 if (isEmptyAccess.parent is! PrefixExpression) { | |
352 _coverageMarker(); | |
353 return; | |
354 } | |
355 PrefixExpression prefixExpression = | |
356 isEmptyAccess.parent as PrefixExpression; | |
357 // should be ! | |
358 if (prefixExpression.operator.type != TokenType.BANG) { | |
359 return; | |
360 } | |
361 // do replace | |
362 _addRemoveEdit(rangeStartStart(prefixExpression, prefixExpression.operand)); | |
363 _addReplaceEdit(rangeNode(isEmptyIdentifier), "isNotEmpty"); | |
364 // add proposal | |
365 _addAssist(AssistKind.CONVERT_INTO_IS_NOT_EMPTY, []); | |
366 } | |
367 | |
368 void _addProposal_convertToIsNot_onIs() { | |
369 // may be child of "is" | |
370 AstNode node = this.node; | |
371 while (node != null && node is! IsExpression) { | |
372 node = node.parent; | |
373 } | |
374 // prepare "is" | |
375 if (node is! IsExpression) { | |
376 _coverageMarker(); | |
377 return; | |
378 } | |
379 IsExpression isExpression = node as IsExpression; | |
380 if (isExpression.notOperator != null) { | |
381 _coverageMarker(); | |
382 return; | |
383 } | |
384 // prepare enclosing () | |
385 AstNode parent = isExpression.parent; | |
386 if (parent is! ParenthesizedExpression) { | |
387 _coverageMarker(); | |
388 return; | |
389 } | |
390 ParenthesizedExpression parExpression = parent as ParenthesizedExpression; | |
391 // prepare enclosing !() | |
392 AstNode parent2 = parent.parent; | |
393 if (parent2 is! PrefixExpression) { | |
394 _coverageMarker(); | |
395 return; | |
396 } | |
397 PrefixExpression prefExpression = parent2 as PrefixExpression; | |
398 if (prefExpression.operator.type != TokenType.BANG) { | |
399 _coverageMarker(); | |
400 return; | |
401 } | |
402 // strip !() | |
403 if (getExpressionParentPrecedence(prefExpression) >= | |
404 TokenType.IS.precedence) { | |
405 _addRemoveEdit(rangeToken(prefExpression.operator)); | |
406 } else { | |
407 _addRemoveEdit( | |
408 rangeStartEnd(prefExpression, parExpression.leftParenthesis)); | |
409 _addRemoveEdit( | |
410 rangeStartEnd(parExpression.rightParenthesis, prefExpression)); | |
411 } | |
412 _addInsertEdit(isExpression.isOperator.end, "!"); | |
413 // add proposal | |
414 _addAssist(AssistKind.CONVERT_INTO_IS_NOT, []); | |
415 } | |
416 | |
417 void _addProposal_convertToIsNot_onNot() { | |
418 // may be () in prefix expression | |
419 if (node is ParenthesizedExpression && node.parent is PrefixExpression) { | |
420 node = node.parent; | |
421 } | |
422 // prepare !() | |
423 if (node is! PrefixExpression) { | |
424 _coverageMarker(); | |
425 return; | |
426 } | |
427 PrefixExpression prefExpression = node as PrefixExpression; | |
428 // should be ! operator | |
429 if (prefExpression.operator.type != TokenType.BANG) { | |
430 _coverageMarker(); | |
431 return; | |
432 } | |
433 // prepare !() | |
434 Expression operand = prefExpression.operand; | |
435 if (operand is! ParenthesizedExpression) { | |
436 _coverageMarker(); | |
437 return; | |
438 } | |
439 ParenthesizedExpression parExpression = operand as ParenthesizedExpression; | |
440 operand = parExpression.expression; | |
441 // prepare "is" | |
442 if (operand is! IsExpression) { | |
443 _coverageMarker(); | |
444 return; | |
445 } | |
446 IsExpression isExpression = operand as IsExpression; | |
447 if (isExpression.notOperator != null) { | |
448 _coverageMarker(); | |
449 return; | |
450 } | |
451 // strip !() | |
452 if (getExpressionParentPrecedence(prefExpression) >= | |
453 TokenType.IS.precedence) { | |
454 _addRemoveEdit(rangeToken(prefExpression.operator)); | |
455 } else { | |
456 _addRemoveEdit( | |
457 rangeStartEnd(prefExpression, parExpression.leftParenthesis)); | |
458 _addRemoveEdit( | |
459 rangeStartEnd(parExpression.rightParenthesis, prefExpression)); | |
460 } | |
461 _addInsertEdit(isExpression.isOperator.end, "!"); | |
462 // add proposal | |
463 _addAssist(AssistKind.CONVERT_INTO_IS_NOT, []); | |
464 } | |
465 | |
466 void _addProposal_exchangeOperands() { | |
467 // check that user invokes quick assist on binary expression | |
468 if (node is! BinaryExpression) { | |
469 _coverageMarker(); | |
470 return; | |
471 } | |
472 BinaryExpression binaryExpression = node as BinaryExpression; | |
473 // prepare operator position | |
474 if (!_isOperatorSelected( | |
475 binaryExpression, | |
476 selectionOffset, | |
477 selectionLength)) { | |
478 _coverageMarker(); | |
479 return; | |
480 } | |
481 // add edits | |
482 { | |
483 Expression leftOperand = binaryExpression.leftOperand; | |
484 Expression rightOperand = binaryExpression.rightOperand; | |
485 // find "wide" enclosing binary expression with same operator | |
486 while (binaryExpression.parent is BinaryExpression) { | |
487 BinaryExpression newBinaryExpression = | |
488 binaryExpression.parent as BinaryExpression; | |
489 if (newBinaryExpression.operator.type != | |
490 binaryExpression.operator.type) { | |
491 _coverageMarker(); | |
492 break; | |
493 } | |
494 binaryExpression = newBinaryExpression; | |
495 } | |
496 // exchange parts of "wide" expression parts | |
497 SourceRange leftRange = rangeStartEnd(binaryExpression, leftOperand); | |
498 SourceRange rightRange = rangeStartEnd(rightOperand, binaryExpression); | |
499 _addReplaceEdit(leftRange, _getRangeText(rightRange)); | |
500 _addReplaceEdit(rightRange, _getRangeText(leftRange)); | |
501 } | |
502 // add proposal | |
503 _addAssist(AssistKind.EXCHANGE_OPERANDS, []); | |
504 } | |
505 | |
506 void _addProposal_importAddShow() { | |
507 // prepare ImportDirective | |
508 ImportDirective importDirective = | |
509 node.getAncestor((node) => node is ImportDirective); | |
510 if (importDirective == null) { | |
511 _coverageMarker(); | |
512 return; | |
513 } | |
514 // there should be no existing combinators | |
515 if (importDirective.combinators.isNotEmpty) { | |
516 _coverageMarker(); | |
517 return; | |
518 } | |
519 // prepare whole import namespace | |
520 ImportElement importElement = importDirective.element; | |
521 Map<String, Element> namespace = getImportNamespace(importElement); | |
522 // prepare names of referenced elements (from this import) | |
523 SplayTreeSet<String> referencedNames = new SplayTreeSet<String>(); | |
524 _SimpleIdentifierRecursiveAstVisitor visitor = | |
525 new _SimpleIdentifierRecursiveAstVisitor((SimpleIdentifier node) { | |
526 Element element = node.staticElement; | |
527 if (namespace[node.name] == element) { | |
528 referencedNames.add(element.displayName); | |
529 } | |
530 }); | |
531 unit.accept(visitor); | |
532 // ignore if unused | |
533 if (referencedNames.isEmpty) { | |
534 _coverageMarker(); | |
535 return; | |
536 } | |
537 // prepare change | |
538 String showCombinator = " show ${StringUtils.join(referencedNames, ", ")}"; | |
539 _addInsertEdit(importDirective.end - 1, showCombinator); | |
540 // add proposal | |
541 _addAssist(AssistKind.IMPORT_ADD_SHOW, []); | |
542 } | |
543 | |
544 void _addProposal_invertIf() { | |
545 if (node is! IfStatement) { | |
546 return; | |
547 } | |
548 IfStatement ifStatement = node as IfStatement; | |
549 Expression condition = ifStatement.condition; | |
550 // should have both "then" and "else" | |
551 Statement thenStatement = ifStatement.thenStatement; | |
552 Statement elseStatement = ifStatement.elseStatement; | |
553 if (thenStatement == null || elseStatement == null) { | |
554 return; | |
555 } | |
556 // prepare source | |
557 String invertedCondition = utils.invertCondition(condition); | |
558 String thenSource = _getNodeText(thenStatement); | |
559 String elseSource = _getNodeText(elseStatement); | |
560 // do replacements | |
561 _addReplaceEdit(rangeNode(condition), invertedCondition); | |
562 _addReplaceEdit(rangeNode(thenStatement), elseSource); | |
563 _addReplaceEdit(rangeNode(elseStatement), thenSource); | |
564 // add proposal | |
565 _addAssist(AssistKind.INVERT_IF_STATEMENT, []); | |
566 } | |
567 | |
568 void _addProposal_joinIfStatementInner() { | |
569 // climb up condition to the (supposedly) "if" statement | |
570 AstNode node = this.node; | |
571 while (node is Expression) { | |
572 node = node.parent; | |
573 } | |
574 // prepare target "if" statement | |
575 if (node is! IfStatement) { | |
576 _coverageMarker(); | |
577 return; | |
578 } | |
579 IfStatement targetIfStatement = node as IfStatement; | |
580 if (targetIfStatement.elseStatement != null) { | |
581 _coverageMarker(); | |
582 return; | |
583 } | |
584 // prepare inner "if" statement | |
585 Statement targetThenStatement = targetIfStatement.thenStatement; | |
586 Statement innerStatement = getSingleStatement(targetThenStatement); | |
587 if (innerStatement is! IfStatement) { | |
588 _coverageMarker(); | |
589 return; | |
590 } | |
591 IfStatement innerIfStatement = innerStatement as IfStatement; | |
592 if (innerIfStatement.elseStatement != null) { | |
593 _coverageMarker(); | |
594 return; | |
595 } | |
596 // prepare environment | |
597 String prefix = utils.getNodePrefix(targetIfStatement); | |
598 // merge conditions | |
599 String condition; | |
600 { | |
601 Expression targetCondition = targetIfStatement.condition; | |
602 Expression innerCondition = innerIfStatement.condition; | |
603 String targetConditionSource = _getNodeText(targetCondition); | |
604 String innerConditionSource = _getNodeText(innerCondition); | |
605 if (_shouldWrapParenthesisBeforeAnd(targetCondition)) { | |
606 targetConditionSource = "(${targetConditionSource})"; | |
607 } | |
608 if (_shouldWrapParenthesisBeforeAnd(innerCondition)) { | |
609 innerConditionSource = "(${innerConditionSource})"; | |
610 } | |
611 condition = "${targetConditionSource} && ${innerConditionSource}"; | |
612 } | |
613 // replace target "if" statement | |
614 { | |
615 Statement innerThenStatement = innerIfStatement.thenStatement; | |
616 List<Statement> innerThenStatements = getStatements(innerThenStatement); | |
617 SourceRange lineRanges = | |
618 utils.getLinesRangeStatements(innerThenStatements); | |
619 String oldSource = utils.getRangeText(lineRanges); | |
620 String newSource = utils.indentSourceLeftRight(oldSource, false); | |
621 _addReplaceEdit( | |
622 rangeNode(targetIfStatement), | |
623 "if ($condition) {${eol}${newSource}${prefix}}"); | |
624 } | |
625 // done | |
626 _addAssist(AssistKind.JOIN_IF_WITH_INNER, []); | |
627 } | |
628 | |
629 void _addProposal_joinIfStatementOuter() { | |
630 // climb up condition to the (supposedly) "if" statement | |
631 AstNode node = this.node; | |
632 while (node is Expression) { | |
633 node = node.parent; | |
634 } | |
635 // prepare target "if" statement | |
636 if (node is! IfStatement) { | |
637 _coverageMarker(); | |
638 return; | |
639 } | |
640 IfStatement targetIfStatement = node as IfStatement; | |
641 if (targetIfStatement.elseStatement != null) { | |
642 _coverageMarker(); | |
643 return; | |
644 } | |
645 // prepare outer "if" statement | |
646 AstNode parent = targetIfStatement.parent; | |
647 if (parent is Block) { | |
648 parent = parent.parent; | |
649 } | |
650 if (parent is! IfStatement) { | |
651 _coverageMarker(); | |
652 return; | |
653 } | |
654 IfStatement outerIfStatement = parent as IfStatement; | |
655 if (outerIfStatement.elseStatement != null) { | |
656 _coverageMarker(); | |
657 return; | |
658 } | |
659 // prepare environment | |
660 String prefix = utils.getNodePrefix(outerIfStatement); | |
661 // merge conditions | |
662 String condition; | |
663 { | |
664 Expression targetCondition = targetIfStatement.condition; | |
665 Expression outerCondition = outerIfStatement.condition; | |
666 String targetConditionSource = _getNodeText(targetCondition); | |
667 String outerConditionSource = _getNodeText(outerCondition); | |
668 if (_shouldWrapParenthesisBeforeAnd(targetCondition)) { | |
669 targetConditionSource = "(${targetConditionSource})"; | |
670 } | |
671 if (_shouldWrapParenthesisBeforeAnd(outerCondition)) { | |
672 outerConditionSource = "(${outerConditionSource})"; | |
673 } | |
674 condition = "${outerConditionSource} && ${targetConditionSource}"; | |
675 } | |
676 // replace outer "if" statement | |
677 { | |
678 Statement targetThenStatement = targetIfStatement.thenStatement; | |
679 List<Statement> targetThenStatements = getStatements(targetThenStatement); | |
680 SourceRange lineRanges = | |
681 utils.getLinesRangeStatements(targetThenStatements); | |
682 String oldSource = utils.getRangeText(lineRanges); | |
683 String newSource = utils.indentSourceLeftRight(oldSource, false); | |
684 _addReplaceEdit( | |
685 rangeNode(outerIfStatement), | |
686 "if ($condition) {${eol}${newSource}${prefix}}"); | |
687 } | |
688 // done | |
689 _addAssist(AssistKind.JOIN_IF_WITH_OUTER, []); | |
690 } | |
691 | |
692 void _addProposal_joinVariableDeclaration_onAssignment() { | |
693 // check that node is LHS in assignment | |
694 if (node is SimpleIdentifier && | |
695 node.parent is AssignmentExpression && | |
696 (node.parent as AssignmentExpression).leftHandSide == node && | |
697 node.parent.parent is ExpressionStatement) { | |
698 } else { | |
699 _coverageMarker(); | |
700 return; | |
701 } | |
702 AssignmentExpression assignExpression = node.parent as AssignmentExpression; | |
703 // check that binary expression is assignment | |
704 if (assignExpression.operator.type != TokenType.EQ) { | |
705 _coverageMarker(); | |
706 return; | |
707 } | |
708 // prepare "declaration" statement | |
709 Element element = (node as SimpleIdentifier).staticElement; | |
710 if (element == null) { | |
711 _coverageMarker(); | |
712 return; | |
713 } | |
714 int declOffset = element.nameOffset; | |
715 AstNode declNode = new NodeLocator.con1(declOffset).searchWithin(unit); | |
716 if (declNode != null && | |
717 declNode.parent is VariableDeclaration && | |
718 (declNode.parent as VariableDeclaration).name == declNode && | |
719 declNode.parent.parent is VariableDeclarationList && | |
720 declNode.parent.parent.parent is VariableDeclarationStatement) { | |
721 } else { | |
722 _coverageMarker(); | |
723 return; | |
724 } | |
725 VariableDeclaration decl = declNode.parent as VariableDeclaration; | |
726 VariableDeclarationStatement declStatement = | |
727 decl.parent.parent as VariableDeclarationStatement; | |
728 // may be has initializer | |
729 if (decl.initializer != null) { | |
730 _coverageMarker(); | |
731 return; | |
732 } | |
733 // check that "declaration" statement declared only one variable | |
734 if (declStatement.variables.variables.length != 1) { | |
735 _coverageMarker(); | |
736 return; | |
737 } | |
738 // check that the "declaration" and "assignment" statements are | |
739 // parts of the same Block | |
740 ExpressionStatement assignStatement = | |
741 node.parent.parent as ExpressionStatement; | |
742 if (assignStatement.parent is Block && | |
743 assignStatement.parent == declStatement.parent) { | |
744 } else { | |
745 _coverageMarker(); | |
746 return; | |
747 } | |
748 Block block = assignStatement.parent as Block; | |
749 // check that "declaration" and "assignment" statements are adjacent | |
750 List<Statement> statements = block.statements; | |
751 if (statements.indexOf(assignStatement) == | |
752 statements.indexOf(declStatement) + 1) { | |
753 } else { | |
754 _coverageMarker(); | |
755 return; | |
756 } | |
757 // add edits | |
758 { | |
759 int assignOffset = assignExpression.operator.offset; | |
760 _addReplaceEdit(rangeEndStart(declNode, assignOffset), " "); | |
761 } | |
762 // add proposal | |
763 _addAssist(AssistKind.JOIN_VARIABLE_DECLARATION, []); | |
764 } | |
765 | |
766 void _addProposal_joinVariableDeclaration_onDeclaration() { | |
767 // prepare enclosing VariableDeclarationList | |
768 VariableDeclarationList declList = | |
769 node.getAncestor((node) => node is VariableDeclarationList); | |
770 if (declList != null && declList.variables.length == 1) { | |
771 } else { | |
772 _coverageMarker(); | |
773 return; | |
774 } | |
775 VariableDeclaration decl = declList.variables[0]; | |
776 // already initialized | |
777 if (decl.initializer != null) { | |
778 _coverageMarker(); | |
779 return; | |
780 } | |
781 // prepare VariableDeclarationStatement in Block | |
782 if (declList.parent is VariableDeclarationStatement && | |
783 declList.parent.parent is Block) { | |
784 } else { | |
785 _coverageMarker(); | |
786 return; | |
787 } | |
788 VariableDeclarationStatement declStatement = | |
789 declList.parent as VariableDeclarationStatement; | |
790 Block block = declStatement.parent as Block; | |
791 List<Statement> statements = block.statements; | |
792 // prepare assignment | |
793 AssignmentExpression assignExpression; | |
794 { | |
795 // declaration should not be last Statement | |
796 int declIndex = statements.indexOf(declStatement); | |
797 if (declIndex < statements.length - 1) { | |
798 } else { | |
799 _coverageMarker(); | |
800 return; | |
801 } | |
802 // next Statement should be assignment | |
803 Statement assignStatement = statements[declIndex + 1]; | |
804 if (assignStatement is ExpressionStatement) { | |
805 } else { | |
806 _coverageMarker(); | |
807 return; | |
808 } | |
809 ExpressionStatement expressionStatement = | |
810 assignStatement as ExpressionStatement; | |
811 // expression should be assignment | |
812 if (expressionStatement.expression is AssignmentExpression) { | |
813 } else { | |
814 _coverageMarker(); | |
815 return; | |
816 } | |
817 assignExpression = expressionStatement.expression as AssignmentExpression; | |
818 } | |
819 // check that pure assignment | |
820 if (assignExpression.operator.type != TokenType.EQ) { | |
821 _coverageMarker(); | |
822 return; | |
823 } | |
824 // add edits | |
825 { | |
826 int assignOffset = assignExpression.operator.offset; | |
827 _addReplaceEdit(rangeEndStart(decl.name, assignOffset), " "); | |
828 } | |
829 // add proposal | |
830 _addAssist(AssistKind.JOIN_VARIABLE_DECLARATION, []); | |
831 } | |
832 | |
833 void _addProposal_removeTypeAnnotation() { | |
834 AstNode typeStart = null; | |
835 AstNode typeEnd = null; | |
836 // try top-level variable | |
837 { | |
838 TopLevelVariableDeclaration declaration = | |
839 node.getAncestor((node) => node is TopLevelVariableDeclaration); | |
840 if (declaration != null) { | |
841 TypeName typeNode = declaration.variables.type; | |
842 if (typeNode != null) { | |
843 VariableDeclaration field = declaration.variables.variables[0]; | |
844 typeStart = declaration; | |
845 typeEnd = field; | |
846 } | |
847 } | |
848 } | |
849 // try class field | |
850 { | |
851 FieldDeclaration fieldDeclaration = | |
852 node.getAncestor((node) => node is FieldDeclaration); | |
853 if (fieldDeclaration != null) { | |
854 TypeName typeNode = fieldDeclaration.fields.type; | |
855 if (typeNode != null) { | |
856 VariableDeclaration field = fieldDeclaration.fields.variables[0]; | |
857 typeStart = fieldDeclaration; | |
858 typeEnd = field; | |
859 } | |
860 } | |
861 } | |
862 // try local variable | |
863 { | |
864 VariableDeclarationStatement statement = | |
865 node.getAncestor((node) => node is VariableDeclarationStatement); | |
866 if (statement != null) { | |
867 TypeName typeNode = statement.variables.type; | |
868 if (typeNode != null) { | |
869 VariableDeclaration variable = statement.variables.variables[0]; | |
870 typeStart = typeNode; | |
871 typeEnd = variable; | |
872 } | |
873 } | |
874 } | |
875 // add edit | |
876 if (typeStart != null && typeEnd != null) { | |
877 SourceRange typeRange = rangeStartStart(typeStart, typeEnd); | |
878 _addReplaceEdit(typeRange, "var "); | |
879 } | |
880 // add proposal | |
881 _addAssist(AssistKind.REMOVE_TYPE_ANNOTATION, []); | |
882 } | |
883 | |
884 void _addProposal_replaceConditionalWithIfElse() { | |
885 ConditionalExpression conditional = null; | |
886 // may be on Statement with Conditional | |
887 Statement statement = node.getAncestor((node) => node is Statement); | |
888 if (statement == null) { | |
889 _coverageMarker(); | |
890 return; | |
891 } | |
892 // variable declaration | |
893 bool inVariable = false; | |
894 if (statement is VariableDeclarationStatement) { | |
895 VariableDeclarationStatement variableStatement = statement; | |
896 for (VariableDeclaration variable in | |
897 variableStatement.variables.variables) { | |
898 if (variable.initializer is ConditionalExpression) { | |
899 conditional = variable.initializer as ConditionalExpression; | |
900 inVariable = true; | |
901 break; | |
902 } | |
903 } | |
904 } | |
905 // assignment | |
906 bool inAssignment = false; | |
907 if (statement is ExpressionStatement) { | |
908 ExpressionStatement exprStmt = statement; | |
909 if (exprStmt.expression is AssignmentExpression) { | |
910 AssignmentExpression assignment = | |
911 exprStmt.expression as AssignmentExpression; | |
912 if (assignment.operator.type == TokenType.EQ && | |
913 assignment.rightHandSide is ConditionalExpression) { | |
914 conditional = assignment.rightHandSide as ConditionalExpression; | |
915 inAssignment = true; | |
916 } | |
917 } | |
918 } | |
919 // return | |
920 bool inReturn = false; | |
921 if (statement is ReturnStatement) { | |
922 ReturnStatement returnStatement = statement; | |
923 if (returnStatement.expression is ConditionalExpression) { | |
924 conditional = returnStatement.expression as ConditionalExpression; | |
925 inReturn = true; | |
926 } | |
927 } | |
928 // prepare environment | |
929 String indent = utils.getIndent(1); | |
930 String prefix = utils.getNodePrefix(statement); | |
931 // Type v = Conditional; | |
932 if (inVariable) { | |
933 VariableDeclaration variable = conditional.parent as VariableDeclaration; | |
934 _addRemoveEdit(rangeEndEnd(variable.name, conditional)); | |
935 String conditionSrc = _getNodeText(conditional.condition); | |
936 String thenSrc = _getNodeText(conditional.thenExpression); | |
937 String elseSrc = _getNodeText(conditional.elseExpression); | |
938 String name = variable.name.name; | |
939 String src = eol; | |
940 src += prefix + 'if ($conditionSrc) {' + eol; | |
941 src += prefix + indent + '$name = $thenSrc;' + eol; | |
942 src += prefix + '} else {' + eol; | |
943 src += prefix + indent + '$name = $elseSrc;' + eol; | |
944 src += prefix + '}'; | |
945 _addReplaceEdit(rangeEndLength(statement, 0), src); | |
946 } | |
947 // v = Conditional; | |
948 if (inAssignment) { | |
949 AssignmentExpression assignment = | |
950 conditional.parent as AssignmentExpression; | |
951 Expression leftSide = assignment.leftHandSide; | |
952 String conditionSrc = _getNodeText(conditional.condition); | |
953 String thenSrc = _getNodeText(conditional.thenExpression); | |
954 String elseSrc = _getNodeText(conditional.elseExpression); | |
955 String name = _getNodeText(leftSide); | |
956 String src = ''; | |
957 src += 'if ($conditionSrc) {' + eol; | |
958 src += prefix + indent + '$name = $thenSrc;' + eol; | |
959 src += prefix + '} else {' + eol; | |
960 src += prefix + indent + '$name = $elseSrc;' + eol; | |
961 src += prefix + '}'; | |
962 _addReplaceEdit(rangeNode(statement), src); | |
963 } | |
964 // return Conditional; | |
965 if (inReturn) { | |
966 String conditionSrc = _getNodeText(conditional.condition); | |
967 String thenSrc = _getNodeText(conditional.thenExpression); | |
968 String elseSrc = _getNodeText(conditional.elseExpression); | |
969 String src = ''; | |
970 src += 'if ($conditionSrc) {' + eol; | |
971 src += prefix + indent + 'return $thenSrc;' + eol; | |
972 src += prefix + '} else {' + eol; | |
973 src += prefix + indent + 'return $elseSrc;' + eol; | |
974 src += prefix + '}'; | |
975 _addReplaceEdit(rangeNode(statement), src); | |
976 } | |
977 // add proposal | |
978 _addAssist(AssistKind.REPLACE_CONDITIONAL_WITH_IF_ELSE, []); | |
979 } | |
980 | |
981 void _addProposal_replaceIfElseWithConditional() { | |
982 // should be "if" | |
983 if (node is! IfStatement) { | |
984 _coverageMarker(); | |
985 return; | |
986 } | |
987 IfStatement ifStatement = node as IfStatement; | |
988 // single then/else statements | |
989 Statement thenStatement = getSingleStatement(ifStatement.thenStatement); | |
990 Statement elseStatement = getSingleStatement(ifStatement.elseStatement); | |
991 if (thenStatement == null || elseStatement == null) { | |
992 _coverageMarker(); | |
993 return; | |
994 } | |
995 // returns | |
996 if (thenStatement is ReturnStatement || elseStatement is ReturnStatement) { | |
997 ReturnStatement thenReturn = thenStatement as ReturnStatement; | |
998 ReturnStatement elseReturn = elseStatement as ReturnStatement; | |
999 String conditionSrc = _getNodeText(ifStatement.condition); | |
1000 String theSrc = _getNodeText(thenReturn.expression); | |
1001 String elseSrc = _getNodeText(elseReturn.expression); | |
1002 _addReplaceEdit( | |
1003 rangeNode(ifStatement), | |
1004 'return $conditionSrc ? $theSrc : $elseSrc;'); | |
1005 } | |
1006 // assignments -> v = Conditional; | |
1007 if (thenStatement is ExpressionStatement && | |
1008 elseStatement is ExpressionStatement) { | |
1009 Expression thenExpression = thenStatement.expression; | |
1010 Expression elseExpression = elseStatement.expression; | |
1011 if (thenExpression is AssignmentExpression && | |
1012 elseExpression is AssignmentExpression) { | |
1013 AssignmentExpression thenAssignment = thenExpression; | |
1014 AssignmentExpression elseAssignment = elseExpression; | |
1015 String thenTarget = _getNodeText(thenAssignment.leftHandSide); | |
1016 String elseTarget = _getNodeText(elseAssignment.leftHandSide); | |
1017 if (thenAssignment.operator.type == TokenType.EQ && | |
1018 elseAssignment.operator.type == TokenType.EQ && | |
1019 StringUtils.equals(thenTarget, elseTarget)) { | |
1020 String conditionSrc = _getNodeText(ifStatement.condition); | |
1021 String theSrc = _getNodeText(thenAssignment.rightHandSide); | |
1022 String elseSrc = _getNodeText(elseAssignment.rightHandSide); | |
1023 _addReplaceEdit( | |
1024 rangeNode(ifStatement), | |
1025 '$thenTarget = $conditionSrc ? $theSrc : $elseSrc;'); | |
1026 } | |
1027 } | |
1028 } | |
1029 // add proposal | |
1030 _addAssist(AssistKind.REPLACE_IF_ELSE_WITH_CONDITIONAL, []); | |
1031 } | |
1032 | |
1033 void _addProposal_splitAndCondition() { | |
1034 // check that user invokes quick assist on binary expression | |
1035 if (node is! BinaryExpression) { | |
1036 _coverageMarker(); | |
1037 return; | |
1038 } | |
1039 BinaryExpression binaryExpression = node as BinaryExpression; | |
1040 // prepare operator position | |
1041 if (!_isOperatorSelected( | |
1042 binaryExpression, | |
1043 selectionOffset, | |
1044 selectionLength)) { | |
1045 _coverageMarker(); | |
1046 return; | |
1047 } | |
1048 // should be && | |
1049 if (binaryExpression.operator.type != TokenType.AMPERSAND_AMPERSAND) { | |
1050 _coverageMarker(); | |
1051 return; | |
1052 } | |
1053 // prepare "if" | |
1054 Statement statement = node.getAncestor((node) => node is Statement); | |
1055 if (statement is! IfStatement) { | |
1056 _coverageMarker(); | |
1057 return; | |
1058 } | |
1059 IfStatement ifStatement = statement as IfStatement; | |
1060 // check that binary expression is part of first level && condition of "if" | |
1061 BinaryExpression condition = binaryExpression; | |
1062 while (condition.parent is BinaryExpression && | |
1063 (condition.parent as BinaryExpression).operator.type == | |
1064 TokenType.AMPERSAND_AMPERSAND) { | |
1065 condition = condition.parent as BinaryExpression; | |
1066 } | |
1067 if (ifStatement.condition != condition) { | |
1068 _coverageMarker(); | |
1069 return; | |
1070 } | |
1071 // prepare environment | |
1072 String prefix = utils.getNodePrefix(ifStatement); | |
1073 String indent = utils.getIndent(1); | |
1074 // prepare "rightCondition" | |
1075 String rightConditionSource; | |
1076 { | |
1077 SourceRange rightConditionRange = | |
1078 rangeStartEnd(binaryExpression.rightOperand, condition); | |
1079 rightConditionSource = _getRangeText(rightConditionRange); | |
1080 } | |
1081 // remove "&& rightCondition" | |
1082 _addRemoveEdit(rangeEndEnd(binaryExpression.leftOperand, condition)); | |
1083 // update "then" statement | |
1084 Statement thenStatement = ifStatement.thenStatement; | |
1085 Statement elseStatement = ifStatement.elseStatement; | |
1086 if (thenStatement is Block) { | |
1087 Block thenBlock = thenStatement; | |
1088 SourceRange thenBlockRange = rangeNode(thenBlock); | |
1089 // insert inner "if" with right part of "condition" | |
1090 { | |
1091 String source = | |
1092 "${eol}${prefix}${indent}if (${rightConditionSource}) {"; | |
1093 int thenBlockInsideOffset = thenBlockRange.offset + 1; | |
1094 _addInsertEdit(thenBlockInsideOffset, source); | |
1095 } | |
1096 // insert closing "}" for inner "if" | |
1097 { | |
1098 int thenBlockEnd = thenBlockRange.end; | |
1099 String source = "${indent}}"; | |
1100 // may be move "else" statements | |
1101 if (elseStatement != null) { | |
1102 List<Statement> elseStatements = getStatements(elseStatement); | |
1103 SourceRange elseLinesRange = | |
1104 utils.getLinesRangeStatements(elseStatements); | |
1105 String elseIndentOld = "${prefix}${indent}"; | |
1106 String elseIndentNew = "${elseIndentOld}${indent}"; | |
1107 String newElseSource = | |
1108 utils.replaceSourceRangeIndent(elseLinesRange, elseIndentOld, else
IndentNew); | |
1109 // append "else" block | |
1110 source += " else {${eol}"; | |
1111 source += newElseSource; | |
1112 source += "${prefix}${indent}}"; | |
1113 // remove old "else" range | |
1114 _addRemoveEdit(rangeStartEnd(thenBlockEnd, elseStatement)); | |
1115 } | |
1116 // insert before outer "then" block "}" | |
1117 source += "${eol}${prefix}"; | |
1118 _addInsertEdit(thenBlockEnd - 1, source); | |
1119 } | |
1120 } else { | |
1121 // insert inner "if" with right part of "condition" | |
1122 { | |
1123 String source = "${eol}${prefix}${indent}if (${rightConditionSource})"; | |
1124 _addInsertEdit(ifStatement.rightParenthesis.offset + 1, source); | |
1125 } | |
1126 // indent "else" statements to correspond inner "if" | |
1127 if (elseStatement != null) { | |
1128 SourceRange elseRange = | |
1129 rangeStartEnd(ifStatement.elseKeyword.offset, elseStatement); | |
1130 SourceRange elseLinesRange = utils.getLinesRange(elseRange); | |
1131 String elseIndentOld = prefix; | |
1132 String elseIndentNew = "${elseIndentOld}${indent}"; | |
1133 edits.add( | |
1134 utils.createIndentEdit(elseLinesRange, elseIndentOld, elseIndentNew)
); | |
1135 } | |
1136 } | |
1137 // indent "then" statements to correspond inner "if" | |
1138 { | |
1139 List<Statement> thenStatements = getStatements(thenStatement); | |
1140 SourceRange linesRange = utils.getLinesRangeStatements(thenStatements); | |
1141 String thenIndentOld = "${prefix}${indent}"; | |
1142 String thenIndentNew = "${thenIndentOld}${indent}"; | |
1143 edits.add( | |
1144 utils.createIndentEdit(linesRange, thenIndentOld, thenIndentNew)); | |
1145 } | |
1146 // add proposal | |
1147 _addAssist(AssistKind.SPLIT_AND_CONDITION, []); | |
1148 } | |
1149 | |
1150 void _addProposal_splitVariableDeclaration() { | |
1151 // prepare DartVariableStatement, should be part of Block | |
1152 VariableDeclarationStatement statement = | |
1153 node.getAncestor((node) => node is VariableDeclarationStatement); | |
1154 if (statement != null && statement.parent is Block) { | |
1155 } else { | |
1156 _coverageMarker(); | |
1157 return; | |
1158 } | |
1159 // check that statement declares single variable | |
1160 List<VariableDeclaration> variables = statement.variables.variables; | |
1161 if (variables.length != 1) { | |
1162 _coverageMarker(); | |
1163 return; | |
1164 } | |
1165 VariableDeclaration variable = variables[0]; | |
1166 // prepare initializer | |
1167 Expression initializer = variable.initializer; | |
1168 if (initializer == null) { | |
1169 _coverageMarker(); | |
1170 return; | |
1171 } | |
1172 // remove initializer value | |
1173 _addRemoveEdit(rangeEndStart(variable.name, statement.semicolon)); | |
1174 // add assignment statement | |
1175 String indent = utils.getNodePrefix(statement); | |
1176 String name = variable.name.name; | |
1177 String initSrc = _getNodeText(initializer); | |
1178 SourceRange assignRange = rangeEndLength(statement, 0); | |
1179 _addReplaceEdit(assignRange, eol + indent + name + ' = ' + initSrc + ';'); | |
1180 // add proposal | |
1181 _addAssist(AssistKind.SPLIT_VARIABLE_DECLARATION, []); | |
1182 } | |
1183 | |
1184 void _addProposal_surroundWith() { | |
1185 // prepare selected statements | |
1186 List<Statement> selectedStatements; | |
1187 { | |
1188 SourceRange selection = | |
1189 rangeStartLength(selectionOffset, selectionLength); | |
1190 StatementAnalyzer selectionAnalyzer = | |
1191 new StatementAnalyzer(unit, selection); | |
1192 unit.accept(selectionAnalyzer); | |
1193 List<AstNode> selectedNodes = selectionAnalyzer.selectedNodes; | |
1194 // convert nodes to statements | |
1195 selectedStatements = []; | |
1196 for (AstNode selectedNode in selectedNodes) { | |
1197 if (selectedNode is Statement) { | |
1198 selectedStatements.add(selectedNode); | |
1199 } | |
1200 } | |
1201 // we want only statements | |
1202 if (selectedStatements.isEmpty || | |
1203 selectedStatements.length != selectedNodes.length) { | |
1204 return; | |
1205 } | |
1206 } | |
1207 // prepare statement information | |
1208 Statement firstStatement = selectedStatements[0]; | |
1209 Statement lastStatement = selectedStatements[selectedStatements.length - 1]; | |
1210 SourceRange statementsRange = | |
1211 utils.getLinesRangeStatements(selectedStatements); | |
1212 // prepare environment | |
1213 String indentOld = utils.getNodePrefix(firstStatement); | |
1214 String indentNew = "${indentOld}${utils.getIndent(1)}"; | |
1215 // "block" | |
1216 { | |
1217 _addInsertEdit(statementsRange.offset, "${indentOld}{${eol}"); | |
1218 { | |
1219 Edit edit = | |
1220 utils.createIndentEdit(statementsRange, indentOld, indentNew); | |
1221 edits.add(edit); | |
1222 } | |
1223 _addInsertEdit(statementsRange.end, "${indentOld}}${eol}"); | |
1224 exitPosition = _newPosition(lastStatement.end); | |
1225 // add proposal | |
1226 _addAssist(AssistKind.SURROUND_WITH_BLOCK, []); | |
1227 } | |
1228 // "if" | |
1229 { | |
1230 { | |
1231 int offset = statementsRange.offset; | |
1232 SourceBuilder sb = new SourceBuilder(file, offset); | |
1233 sb.append(indentOld); | |
1234 sb.append("if ("); | |
1235 { | |
1236 sb.startPosition("CONDITION"); | |
1237 sb.append("condition"); | |
1238 sb.endPosition(); | |
1239 } | |
1240 sb.append(") {"); | |
1241 sb.append(eol); | |
1242 _insertBuilder(sb); | |
1243 } | |
1244 { | |
1245 Edit edit = | |
1246 utils.createIndentEdit(statementsRange, indentOld, indentNew); | |
1247 edits.add(edit); | |
1248 } | |
1249 _addInsertEdit(statementsRange.end, "${indentOld}}${eol}"); | |
1250 exitPosition = _newPosition(lastStatement.end); | |
1251 // add proposal | |
1252 _addAssist(AssistKind.SURROUND_WITH_IF, []); | |
1253 } | |
1254 // "while" | |
1255 { | |
1256 { | |
1257 int offset = statementsRange.offset; | |
1258 SourceBuilder sb = new SourceBuilder(file, offset); | |
1259 sb.append(indentOld); | |
1260 sb.append("while ("); | |
1261 { | |
1262 sb.startPosition("CONDITION"); | |
1263 sb.append("condition"); | |
1264 sb.endPosition(); | |
1265 } | |
1266 sb.append(") {"); | |
1267 sb.append(eol); | |
1268 _insertBuilder(sb); | |
1269 } | |
1270 { | |
1271 Edit edit = | |
1272 utils.createIndentEdit(statementsRange, indentOld, indentNew); | |
1273 edits.add(edit); | |
1274 } | |
1275 _addInsertEdit(statementsRange.end, "${indentOld}}${eol}"); | |
1276 exitPosition = _newPosition(lastStatement.end); | |
1277 // add proposal | |
1278 _addAssist(AssistKind.SURROUND_WITH_WHILE, []); | |
1279 } | |
1280 // "for-in" | |
1281 { | |
1282 { | |
1283 int offset = statementsRange.offset; | |
1284 SourceBuilder sb = new SourceBuilder(file, offset); | |
1285 sb.append(indentOld); | |
1286 sb.append("for (var "); | |
1287 { | |
1288 sb.startPosition("NAME"); | |
1289 sb.append("item"); | |
1290 sb.endPosition(); | |
1291 } | |
1292 sb.append(" in "); | |
1293 { | |
1294 sb.startPosition("ITERABLE"); | |
1295 sb.append("iterable"); | |
1296 sb.endPosition(); | |
1297 } | |
1298 sb.append(") {"); | |
1299 sb.append(eol); | |
1300 _insertBuilder(sb); | |
1301 } | |
1302 { | |
1303 Edit edit = | |
1304 utils.createIndentEdit(statementsRange, indentOld, indentNew); | |
1305 edits.add(edit); | |
1306 } | |
1307 _addInsertEdit(statementsRange.end, "${indentOld}}${eol}"); | |
1308 exitPosition = _newPosition(lastStatement.end); | |
1309 // add proposal | |
1310 _addAssist(AssistKind.SURROUND_WITH_FOR_IN, []); | |
1311 } | |
1312 // "for" | |
1313 { | |
1314 { | |
1315 int offset = statementsRange.offset; | |
1316 SourceBuilder sb = new SourceBuilder(file, offset); | |
1317 sb.append(indentOld); | |
1318 sb.append("for (var "); | |
1319 { | |
1320 sb.startPosition("VAR"); | |
1321 sb.append("v"); | |
1322 sb.endPosition(); | |
1323 } | |
1324 sb.append(" = "); | |
1325 { | |
1326 sb.startPosition("INIT"); | |
1327 sb.append("init"); | |
1328 sb.endPosition(); | |
1329 } | |
1330 sb.append("; "); | |
1331 { | |
1332 sb.startPosition("CONDITION"); | |
1333 sb.append("condition"); | |
1334 sb.endPosition(); | |
1335 } | |
1336 sb.append("; "); | |
1337 { | |
1338 sb.startPosition("INCREMENT"); | |
1339 sb.append("increment"); | |
1340 sb.endPosition(); | |
1341 } | |
1342 sb.append(") {"); | |
1343 sb.append(eol); | |
1344 _insertBuilder(sb); | |
1345 } | |
1346 { | |
1347 Edit edit = | |
1348 utils.createIndentEdit(statementsRange, indentOld, indentNew); | |
1349 edits.add(edit); | |
1350 } | |
1351 _addInsertEdit(statementsRange.end, "${indentOld}}${eol}"); | |
1352 exitPosition = _newPosition(lastStatement.end); | |
1353 // add proposal | |
1354 _addAssist(AssistKind.SURROUND_WITH_FOR, []); | |
1355 } | |
1356 // "do-while" | |
1357 { | |
1358 _addInsertEdit(statementsRange.offset, "${indentOld}do {${eol}"); | |
1359 { | |
1360 Edit edit = | |
1361 utils.createIndentEdit(statementsRange, indentOld, indentNew); | |
1362 edits.add(edit); | |
1363 } | |
1364 { | |
1365 int offset = statementsRange.end; | |
1366 SourceBuilder sb = new SourceBuilder(file, offset); | |
1367 sb.append(indentOld); | |
1368 sb.append("} while ("); | |
1369 { | |
1370 sb.startPosition("CONDITION"); | |
1371 sb.append("condition"); | |
1372 sb.endPosition(); | |
1373 } | |
1374 sb.append(");"); | |
1375 sb.append(eol); | |
1376 _insertBuilder(sb); | |
1377 } | |
1378 exitPosition = _newPosition(lastStatement.end); | |
1379 // add proposal | |
1380 _addAssist(AssistKind.SURROUND_WITH_DO_WHILE, []); | |
1381 } | |
1382 // "try-catch" | |
1383 { | |
1384 _addInsertEdit(statementsRange.offset, "${indentOld}try {${eol}"); | |
1385 { | |
1386 Edit edit = | |
1387 utils.createIndentEdit(statementsRange, indentOld, indentNew); | |
1388 edits.add(edit); | |
1389 } | |
1390 { | |
1391 int offset = statementsRange.end; | |
1392 SourceBuilder sb = new SourceBuilder(file, offset); | |
1393 sb.append(indentOld); | |
1394 sb.append("} on "); | |
1395 { | |
1396 sb.startPosition("EXCEPTION_TYPE"); | |
1397 sb.append("Exception"); | |
1398 sb.endPosition(); | |
1399 } | |
1400 sb.append(" catch ("); | |
1401 { | |
1402 sb.startPosition("EXCEPTION_VAR"); | |
1403 sb.append("e"); | |
1404 sb.endPosition(); | |
1405 } | |
1406 sb.append(") {"); | |
1407 sb.append(eol); | |
1408 // | |
1409 sb.append(indentNew); | |
1410 { | |
1411 sb.startPosition("CATCH"); | |
1412 sb.append("// TODO"); | |
1413 sb.endPosition(); | |
1414 sb.setExitOffset(); | |
1415 } | |
1416 sb.append(eol); | |
1417 // | |
1418 sb.append(indentOld); | |
1419 sb.append("}"); | |
1420 sb.append(eol); | |
1421 // | |
1422 _insertBuilder(sb); | |
1423 exitPosition = _newPosition(sb.exitOffset); | |
1424 } | |
1425 // add proposal | |
1426 _addAssist(AssistKind.SURROUND_WITH_TRY_CATCH, []); | |
1427 } | |
1428 // "try-finally" | |
1429 { | |
1430 _addInsertEdit(statementsRange.offset, "${indentOld}try {${eol}"); | |
1431 { | |
1432 Edit edit = | |
1433 utils.createIndentEdit(statementsRange, indentOld, indentNew); | |
1434 edits.add(edit); | |
1435 } | |
1436 { | |
1437 int offset = statementsRange.end; | |
1438 SourceBuilder sb = new SourceBuilder(file, offset); | |
1439 // | |
1440 sb.append(indentOld); | |
1441 sb.append("} finally {"); | |
1442 sb.append(eol); | |
1443 // | |
1444 sb.append(indentNew); | |
1445 { | |
1446 sb.startPosition("FINALLY"); | |
1447 sb.append("// TODO"); | |
1448 sb.endPosition(); | |
1449 } | |
1450 sb.setExitOffset(); | |
1451 sb.append(eol); | |
1452 // | |
1453 sb.append(indentOld); | |
1454 sb.append("}"); | |
1455 sb.append(eol); | |
1456 // | |
1457 _insertBuilder(sb); | |
1458 exitPosition = _newPosition(sb.exitOffset); | |
1459 } | |
1460 // add proposal | |
1461 _addAssist(AssistKind.SURROUND_WITH_TRY_FINALLY, []); | |
1462 } | |
1463 } | |
1464 | |
1465 /** | |
1466 * Adds a new [Edit] to [edits]. | |
1467 */ | |
1468 void _addRemoveEdit(SourceRange range) { | |
1469 _addReplaceEdit(range, ''); | |
1470 } | |
1471 | |
1472 /** | |
1473 * Adds a new [Edit] to [edits]. | |
1474 */ | |
1475 void _addReplaceEdit(SourceRange range, String text) { | |
1476 Edit edit = new Edit(range.offset, range.length, text); | |
1477 edits.add(edit); | |
1478 } | |
1479 | |
1480 /** | |
1481 * Returns an existing or just added [LinkedEditGroup] with [groupId]. | |
1482 */ | |
1483 LinkedEditGroup _getLinkedPosition(String groupId) { | |
1484 LinkedEditGroup group = linkedPositionGroups[groupId]; | |
1485 if (group == null) { | |
1486 group = new LinkedEditGroup(groupId); | |
1487 linkedPositionGroups[groupId] = group; | |
1488 } | |
1489 return group; | |
1490 } | |
1491 | |
1492 /** | |
1493 * Returns the text of the given node in the unit. | |
1494 */ | |
1495 String _getNodeText(AstNode node) { | |
1496 return utils.getNodeText(node); | |
1497 } | |
1498 | |
1499 /** | |
1500 * Returns the text of the given range in the unit. | |
1501 */ | |
1502 String _getRangeText(SourceRange range) { | |
1503 return utils.getRangeText(range); | |
1504 } | |
1505 | |
1506 /** | |
1507 * Inserts the given [SourceBuilder] at its offset. | |
1508 */ | |
1509 void _insertBuilder(SourceBuilder builder) { | |
1510 String text = builder.toString(); | |
1511 _addInsertEdit(builder.offset, text); | |
1512 // add linked positions | |
1513 builder.linkedPositionGroups.forEach((LinkedEditGroup group) { | |
1514 LinkedEditGroup fixGroup = _getLinkedPosition(group.id); | |
1515 group.positions.forEach((Position position) { | |
1516 fixGroup.addPosition(position, group.length); | |
1517 }); | |
1518 group.suggestions.forEach((LinkedEditSuggestion suggestion) { | |
1519 fixGroup.addSuggestion(suggestion); | |
1520 }); | |
1521 }); | |
1522 } | |
1523 | |
1524 Position _newPosition(int offset) { | |
1525 return new Position(file, offset); | |
1526 } | |
1527 | |
1528 /** | |
1529 * This method does nothing, but we invoke it in places where Dart VM | |
1530 * coverage agent fails to provide coverage information - such as almost | |
1531 * all "return" statements. | |
1532 * | |
1533 * https://code.google.com/p/dart/issues/detail?id=19912 | |
1534 */ | |
1535 static void _coverageMarker() { | |
1536 } | |
1537 | |
1538 /** | |
1539 * Returns `true` if the selection covers an operator of the given | |
1540 * [BinaryExpression]. | |
1541 */ | |
1542 static bool _isOperatorSelected(BinaryExpression binaryExpression, int offset, | |
1543 int length) { | |
1544 AstNode left = binaryExpression.leftOperand; | |
1545 AstNode right = binaryExpression.rightOperand; | |
1546 // between the nodes | |
1547 if (offset >= left.endToken.end && offset + length <= right.offset) { | |
1548 _coverageMarker(); | |
1549 return true; | |
1550 } | |
1551 // or exactly select the node (but not with infix expressions) | |
1552 if (offset == left.offset && offset + length == right.endToken.end) { | |
1553 if (left is BinaryExpression || right is BinaryExpression) { | |
1554 _coverageMarker(); | |
1555 return false; | |
1556 } | |
1557 _coverageMarker(); | |
1558 return true; | |
1559 } | |
1560 // invalid selection (part of node, etc) | |
1561 _coverageMarker(); | |
1562 return false; | |
1563 } | |
1564 | |
1565 /** | |
1566 * Checks if the given [Expression] should be wrapped with parenthesis when we | |
1567 * want to use it as operand of a logical `and` expression. | |
1568 */ | |
1569 static bool _shouldWrapParenthesisBeforeAnd(Expression expr) { | |
1570 if (expr is BinaryExpression) { | |
1571 BinaryExpression binary = expr; | |
1572 int precedence = binary.operator.type.precedence; | |
1573 return precedence < TokenClass.LOGICAL_AND_OPERATOR.precedence; | |
1574 } | |
1575 return false; | |
1576 } | |
1577 } | |
1578 | |
1579 | |
1580 class _SimpleIdentifierRecursiveAstVisitor extends RecursiveAstVisitor { | |
1581 final _SimpleIdentifierVisitor visitor; | |
1582 | |
1583 _SimpleIdentifierRecursiveAstVisitor(this.visitor); | |
1584 | |
1585 @override | |
1586 visitSimpleIdentifier(SimpleIdentifier node) { | |
1587 visitor(node); | |
1588 } | |
1589 } | |
OLD | NEW |