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