| 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.refactoring.extract_method; |
| 6 |
| 7 import 'dart:async'; |
| 8 |
| 9 import 'package:analysis_server/src/protocol2.dart' show Location, |
| 10 RefactoringMethodParameter, RefactoringMethodParameterKind, SourceChange, |
| 11 SourceEdit; |
| 12 import 'package:analysis_server/src/services/correction/selection_analyzer.dart'
; |
| 13 import 'package:analysis_server/src/services/correction/source_range.dart'; |
| 14 import 'package:analysis_server/src/services/correction/statement_analyzer.dart'
; |
| 15 import 'package:analysis_server/src/services/correction/status.dart'; |
| 16 import 'package:analysis_server/src/services/correction/util.dart'; |
| 17 import 'package:analysis_server/src/services/refactoring/naming_conventions.dart
'; |
| 18 import 'package:analysis_server/src/services/refactoring/refactoring.dart'; |
| 19 import 'package:analysis_server/src/services/refactoring/refactoring_internal.da
rt'; |
| 20 import 'package:analysis_server/src/services/search/search_engine.dart'; |
| 21 import 'package:analyzer/src/generated/ast.dart'; |
| 22 import 'package:analyzer/src/generated/element.dart'; |
| 23 import 'package:analyzer/src/generated/java_core.dart'; |
| 24 import 'package:analyzer/src/generated/scanner.dart'; |
| 25 import 'package:analyzer/src/generated/source.dart'; |
| 26 |
| 27 |
| 28 const String _TOKEN_SEPARATOR = "\uFFFF"; |
| 29 |
| 30 |
| 31 /** |
| 32 * Returns the "normalized" version of the given source, which is reconstructed |
| 33 * from tokens, so ignores all the comments and spaces. |
| 34 */ |
| 35 String _getNormalizedSource(String src) { |
| 36 List<Token> selectionTokens = TokenUtils.getTokens(src); |
| 37 return StringUtils.join(selectionTokens, _TOKEN_SEPARATOR); |
| 38 } |
| 39 |
| 40 |
| 41 /** |
| 42 * Returns the [Map] which maps [map] values to their keys. |
| 43 */ |
| 44 Map<String, String> _inverseMap(Map map) { |
| 45 Map result = {}; |
| 46 map.forEach((key, value) { |
| 47 result[value] = key; |
| 48 }); |
| 49 return result; |
| 50 } |
| 51 |
| 52 |
| 53 /** |
| 54 * [ExtractMethodRefactoring] implementation. |
| 55 */ |
| 56 class ExtractMethodRefactoringImpl extends RefactoringImpl implements |
| 57 ExtractMethodRefactoring { |
| 58 final SearchEngine searchEngine; |
| 59 final CompilationUnit unit; |
| 60 final int selectionOffset; |
| 61 final int selectionLength; |
| 62 String file; |
| 63 SourceRange selectionRange; |
| 64 CorrectionUtils utils; |
| 65 |
| 66 String returnType; |
| 67 String name; |
| 68 bool extractAll = true; |
| 69 bool createGetter = false; |
| 70 final List<String> names = <String>[]; |
| 71 final List<int> offsets = <int>[]; |
| 72 final List<int> lengths = <int>[]; |
| 73 |
| 74 Set<String> _usedNames = new Set<String>(); |
| 75 List<RefactoringMethodParameter> _parameters = <RefactoringMethodParameter>[]; |
| 76 Map<String, RefactoringMethodParameter> _parametersMap = <String, |
| 77 RefactoringMethodParameter>{}; |
| 78 Map<String, List<SourceRange>> _parameterReferencesMap = <String, |
| 79 List<SourceRange>>{}; |
| 80 DartType _returnType; |
| 81 String _returnVariableName; |
| 82 AstNode _parentMember; |
| 83 Expression _selectionExpression; |
| 84 FunctionExpression _selectionFunctionExpression; |
| 85 List<Statement> _selectionStatements; |
| 86 List<_Occurrence> _occurrences = []; |
| 87 bool _staticContext = false; |
| 88 |
| 89 ExtractMethodRefactoringImpl(this.searchEngine, this.unit, |
| 90 this.selectionOffset, this.selectionLength) { |
| 91 file = unit.element.source.fullName; |
| 92 selectionRange = new SourceRange(selectionOffset, selectionLength); |
| 93 utils = new CorrectionUtils(unit); |
| 94 } |
| 95 |
| 96 bool get canCreateGetter { |
| 97 if (!parameters.isEmpty) { |
| 98 return false; |
| 99 } |
| 100 if (_selectionExpression != null) { |
| 101 if (_selectionExpression is AssignmentExpression) { |
| 102 return false; |
| 103 } |
| 104 } |
| 105 if (_selectionStatements != null) { |
| 106 return returnType != null; |
| 107 } |
| 108 return true; |
| 109 } |
| 110 |
| 111 /** |
| 112 * @return the selected [DartExpression] source, with applying new parameter n
ames. |
| 113 */ |
| 114 String get methodBodySource { |
| 115 String source = utils.getRangeText(selectionRange); |
| 116 // prepare ReplaceEdit operations to replace variables with parameters |
| 117 // TODO: implement parameters |
| 118 List<SourceEdit> replaceEdits = []; |
| 119 // for (Parameter parameter in _parametersMap.values) { |
| 120 // List<SourceRange> ranges = _parameterReferencesMap[parameter.oldName]; |
| 121 // if (ranges != null) { |
| 122 // for (SourceRange range in ranges) { |
| 123 // replaceEdits.add(new SourceEdit(range.offset - selectionRange.offset
, range.length, parameter.newName)); |
| 124 // } |
| 125 // } |
| 126 // } |
| 127 // apply replacements |
| 128 source = SourceEdit.applySequence(source, replaceEdits); |
| 129 // change indentation |
| 130 if (_selectionFunctionExpression != null) { |
| 131 AstNode baseNode = |
| 132 _selectionFunctionExpression.getAncestor((node) => node is Statement); |
| 133 if (baseNode != null) { |
| 134 String baseIndent = utils.getNodePrefix(baseNode); |
| 135 String targetIndent = utils.getNodePrefix(_parentMember); |
| 136 source = utils.replaceSourceIndent(source, baseIndent, targetIndent); |
| 137 source = source.trim(); |
| 138 } |
| 139 } |
| 140 if (_selectionStatements != null) { |
| 141 String selectionIndent = utils.getNodePrefix(_selectionStatements[0]); |
| 142 String targetIndent = utils.getNodePrefix(_parentMember) + ' '; |
| 143 source = utils.replaceSourceIndent(source, selectionIndent, targetIndent); |
| 144 } |
| 145 // done |
| 146 return source; |
| 147 } |
| 148 |
| 149 @override |
| 150 List<RefactoringMethodParameter> get parameters => _parameters; |
| 151 |
| 152 @override |
| 153 void set parameters(List<RefactoringMethodParameter> parameters) { |
| 154 // TODO: implement parameters |
| 155 } |
| 156 |
| 157 @override |
| 158 String get refactoringName { |
| 159 AstNode node = new NodeLocator.con1(selectionOffset).searchWithin(unit); |
| 160 if (node != null && |
| 161 node.getAncestor((node) => node is ClassDeclaration) != null) { |
| 162 return 'Extract Method'; |
| 163 } |
| 164 return 'Extract Function'; |
| 165 } |
| 166 |
| 167 String get signature { |
| 168 StringBuffer sb = new StringBuffer(); |
| 169 if (createGetter) { |
| 170 sb.write("get "); |
| 171 sb.write(name); |
| 172 } else { |
| 173 sb.write(name); |
| 174 sb.write("("); |
| 175 // add all parameters |
| 176 bool firstParameter = true; |
| 177 for (RefactoringMethodParameter parameter in _parameters) { |
| 178 // may be comma |
| 179 if (firstParameter) { |
| 180 firstParameter = false; |
| 181 } else { |
| 182 sb.write(', '); |
| 183 } |
| 184 // type |
| 185 { |
| 186 String typeSource = parameter.type; |
| 187 if ('dynamic' != typeSource && '' != typeSource) { |
| 188 sb.write(typeSource); |
| 189 sb.write(" "); |
| 190 } |
| 191 } |
| 192 // name |
| 193 sb.write(parameter.name); |
| 194 } |
| 195 sb.write(")"); |
| 196 } |
| 197 // done |
| 198 return sb.toString(); |
| 199 } |
| 200 |
| 201 @override |
| 202 Future<RefactoringStatus> checkFinalConditions() { |
| 203 RefactoringStatus result = new RefactoringStatus(); |
| 204 return new Future.value(result); |
| 205 // TODO: implement checkFinalConditions |
| 206 } |
| 207 |
| 208 @override |
| 209 Future<RefactoringStatus> checkInitialConditions() { |
| 210 RefactoringStatus result = new RefactoringStatus(); |
| 211 // selection |
| 212 result.addStatus(_checkSelection()); |
| 213 if (result.hasFatalError) { |
| 214 return new Future.value(result); |
| 215 } |
| 216 // prepare parts |
| 217 result.addStatus(_initializeParameters()); |
| 218 _initializeReturnType(); |
| 219 _initializeOccurrences(); |
| 220 _initializeGetter(); |
| 221 // closure cannot have parameters |
| 222 if (_selectionFunctionExpression != null && !_parameters.isEmpty) { |
| 223 String message = format( |
| 224 'Cannot extract closure as method, it references {0} external variable
(s).', |
| 225 _parameters.length); |
| 226 RefactoringStatus result = new RefactoringStatus.fatal(message); |
| 227 return new Future.value(result); |
| 228 } |
| 229 return new Future.value(result); |
| 230 } |
| 231 |
| 232 @override |
| 233 RefactoringStatus checkName() { |
| 234 return validateMethodName(name); |
| 235 } |
| 236 |
| 237 @override |
| 238 Future<SourceChange> createChange() { |
| 239 SourceChange change = new SourceChange(refactoringName); |
| 240 // replace occurrences with method invocation |
| 241 for (_Occurrence occurence in _occurrences) { |
| 242 SourceRange range = occurence.range; |
| 243 // may be replacement of duplicates disabled |
| 244 if (!extractAll && !occurence.isSelection) { |
| 245 continue; |
| 246 } |
| 247 // prepare invocation source |
| 248 String invocationSource; |
| 249 if (_selectionFunctionExpression != null) { |
| 250 invocationSource = name; |
| 251 } else { |
| 252 StringBuffer sb = new StringBuffer(); |
| 253 // may be returns value |
| 254 if (returnType != null) { |
| 255 // single variable assignment / return statement |
| 256 if (_returnVariableName != null) { |
| 257 String occurrenceName = |
| 258 occurence._parameterOldToOccurrenceName[_returnVariableName]; |
| 259 // may be declare variable |
| 260 if (!_parametersMap.containsKey(_returnVariableName)) { |
| 261 if (returnType.isEmpty) { |
| 262 sb.write('var '); |
| 263 } else { |
| 264 sb.write(returnType); |
| 265 sb.write(' '); |
| 266 } |
| 267 } |
| 268 // assign the return value |
| 269 sb.write(occurrenceName); |
| 270 sb.write(' = '); |
| 271 } else { |
| 272 sb.write('return '); |
| 273 } |
| 274 } |
| 275 // invocation itself |
| 276 sb.write(name); |
| 277 if (!createGetter) { |
| 278 sb.write("("); |
| 279 bool firstParameter = true; |
| 280 for (RefactoringMethodParameter parameter in _parameters) { |
| 281 // may be comma |
| 282 if (firstParameter) { |
| 283 firstParameter = false; |
| 284 } else { |
| 285 sb.write(', '); |
| 286 } |
| 287 // argument name |
| 288 { |
| 289 String argumentName = |
| 290 occurence._parameterOldToOccurrenceName[parameter.id]; |
| 291 sb.write(argumentName); |
| 292 } |
| 293 } |
| 294 sb.write(')'); |
| 295 } |
| 296 invocationSource = sb.toString(); |
| 297 // statements as extracted with their ";", so add new after invocation |
| 298 if (_selectionStatements != null) { |
| 299 invocationSource += ';'; |
| 300 } |
| 301 } |
| 302 // add replace edit |
| 303 SourceEdit edit = new SourceEdit.range(range, invocationSource); |
| 304 change.addEdit(file, edit); |
| 305 } |
| 306 // add method declaration |
| 307 { |
| 308 // prepare environment |
| 309 String prefix = utils.getNodePrefix(_parentMember); |
| 310 String eol = utils.endOfLine; |
| 311 // prepare annotations |
| 312 String annotations = ""; |
| 313 { |
| 314 // may be "static" |
| 315 if (_staticContext) { |
| 316 annotations = 'static '; |
| 317 } |
| 318 } |
| 319 // prepare declaration source |
| 320 String declarationSource = null; |
| 321 { |
| 322 String returnExpressionSource = methodBodySource; |
| 323 // closure |
| 324 if (_selectionFunctionExpression != null) { |
| 325 declarationSource = "${name}${returnExpressionSource}"; |
| 326 if (_selectionFunctionExpression.body is ExpressionFunctionBody) { |
| 327 declarationSource += ';'; |
| 328 } |
| 329 } |
| 330 // expression |
| 331 if (_selectionExpression != null) { |
| 332 // add return type |
| 333 String returnTypeName = |
| 334 utils.getExpressionTypeSource(_selectionExpression); |
| 335 if (returnTypeName != null && returnTypeName != "dynamic") { |
| 336 annotations += "${returnTypeName} "; |
| 337 } |
| 338 // just return expression |
| 339 declarationSource = |
| 340 "${annotations}${signature} => ${returnExpressionSource};"; |
| 341 } |
| 342 // statements |
| 343 if (_selectionStatements != null) { |
| 344 if (returnType != null) { |
| 345 if (returnType.isNotEmpty) { |
| 346 annotations += returnType + ' '; |
| 347 } |
| 348 } else { |
| 349 annotations += 'void '; |
| 350 } |
| 351 declarationSource = "${annotations}${signature} {${eol}"; |
| 352 declarationSource += returnExpressionSource; |
| 353 if (_returnVariableName != null) { |
| 354 declarationSource += |
| 355 '${prefix} return ${_returnVariableName};$eol'; |
| 356 } |
| 357 declarationSource += '${prefix}}'; |
| 358 } |
| 359 } |
| 360 // insert declaration |
| 361 if (declarationSource != null) { |
| 362 int offset = _parentMember.end; |
| 363 SourceEdit edit = |
| 364 new SourceEdit(offset, 0, '${eol}${eol}${prefix}${declarationSource}
'); |
| 365 change.addEdit(file, edit); |
| 366 } |
| 367 } |
| 368 // done |
| 369 return new Future.value(change); |
| 370 } |
| 371 |
| 372 @override |
| 373 bool requiresPreview() => false; |
| 374 |
| 375 /** |
| 376 * Adds a new reference to the parameter with the given name. |
| 377 */ |
| 378 void _addParameterReference(String name, SourceRange range) { |
| 379 List<SourceRange> references = _parameterReferencesMap[name]; |
| 380 if (references == null) { |
| 381 references = []; |
| 382 _parameterReferencesMap[name] = references; |
| 383 } |
| 384 references.add(range); |
| 385 } |
| 386 |
| 387 /** |
| 388 * Checks if [selectionRange] selects [Expression] which can be extracted, and |
| 389 * location of this [DartExpression] in AST allows extracting. |
| 390 */ |
| 391 RefactoringStatus _checkSelection() { |
| 392 _ExtractMethodAnalyzer selectionAnalyzer = |
| 393 new _ExtractMethodAnalyzer(unit, selectionRange); |
| 394 unit.accept(selectionAnalyzer); |
| 395 // may be fatal error |
| 396 { |
| 397 RefactoringStatus status = selectionAnalyzer.status; |
| 398 if (status.hasFatalError) { |
| 399 return status; |
| 400 } |
| 401 } |
| 402 // check selected nodes |
| 403 List<AstNode> selectedNodes = selectionAnalyzer.selectedNodes; |
| 404 if (!selectedNodes.isEmpty) { |
| 405 AstNode coveringNode = selectionAnalyzer.coveringNode; |
| 406 _parentMember = getEnclosingClassOrUnitMember(coveringNode); |
| 407 // single expression selected |
| 408 if (selectedNodes.length == 1 && |
| 409 !utils.selectionIncludesNonWhitespaceOutsideNode( |
| 410 selectionRange, |
| 411 selectionAnalyzer.firstSelectedNode)) { |
| 412 AstNode selectedNode = selectionAnalyzer.firstSelectedNode; |
| 413 if (selectedNode is Expression) { |
| 414 _selectionExpression = selectedNode; |
| 415 // additional check for closure |
| 416 if (_selectionExpression is FunctionExpression) { |
| 417 _selectionFunctionExpression = |
| 418 _selectionExpression as FunctionExpression; |
| 419 _selectionExpression = null; |
| 420 } |
| 421 // OK |
| 422 return new RefactoringStatus(); |
| 423 } |
| 424 } |
| 425 // statements selected |
| 426 { |
| 427 List<Statement> selectedStatements = []; |
| 428 for (AstNode selectedNode in selectedNodes) { |
| 429 if (selectedNode is Statement) { |
| 430 selectedStatements.add(selectedNode); |
| 431 } |
| 432 } |
| 433 if (selectedStatements.length == selectedNodes.length) { |
| 434 _selectionStatements = selectedStatements; |
| 435 return new RefactoringStatus(); |
| 436 } |
| 437 } |
| 438 } |
| 439 // invalid selection |
| 440 return new RefactoringStatus.fatal( |
| 441 'Can only extract a single expression or a set of statements.'); |
| 442 } |
| 443 |
| 444 _SourcePattern _getSourcePattern(SourceRange range) { |
| 445 String originalSource = utils.getText(range.offset, range.length); |
| 446 _SourcePattern pattern = new _SourcePattern(); |
| 447 List<SourceEdit> replaceEdits = <SourceEdit>[]; |
| 448 unit.accept(new _GetSourcePatternVisitor(range, pattern, replaceEdits)); |
| 449 replaceEdits = replaceEdits.reversed.toList(); |
| 450 pattern.patternSource = |
| 451 SourceEdit.applySequence(originalSource, replaceEdits); |
| 452 return pattern; |
| 453 } |
| 454 |
| 455 /** |
| 456 * Initializes [createGetter] flag. |
| 457 */ |
| 458 void _initializeGetter() { |
| 459 createGetter = false; |
| 460 // maybe we cannot at all |
| 461 if (!canCreateGetter) { |
| 462 return; |
| 463 } |
| 464 // OK, just expression |
| 465 if (_selectionExpression != null) { |
| 466 createGetter = !_hasMethodInvocation(_selectionExpression); |
| 467 return; |
| 468 } |
| 469 // allow code blocks without cycles |
| 470 if (_selectionStatements != null) { |
| 471 createGetter = true; |
| 472 for (Statement statement in _selectionStatements) { |
| 473 // method invocation is something heavy, |
| 474 // so we don't want to extract it as a part of a getter |
| 475 if (_hasMethodInvocation(statement)) { |
| 476 createGetter = false; |
| 477 return; |
| 478 } |
| 479 // don't allow cycles |
| 480 statement.accept(new _ResetCanCreateGetterVisitor(this)); |
| 481 } |
| 482 } |
| 483 } |
| 484 |
| 485 /** |
| 486 * Fills [_occurrences] field. |
| 487 */ |
| 488 void _initializeOccurrences() { |
| 489 // prepare selection |
| 490 _SourcePattern selectionPattern = _getSourcePattern(selectionRange); |
| 491 String selectionSource = |
| 492 _getNormalizedSource(selectionPattern.patternSource); |
| 493 Map<String, String> patternToSelectionName = |
| 494 _inverseMap(selectionPattern.originalToPatternNames); |
| 495 // prepare an enclosing parent - class or unit |
| 496 AstNode enclosingMemberParent = _parentMember.parent; |
| 497 // visit nodes which will able to access extracted method |
| 498 enclosingMemberParent.accept( |
| 499 new _InitializeOccurrencesVisitor( |
| 500 this, |
| 501 selectionSource, |
| 502 patternToSelectionName)); |
| 503 } |
| 504 |
| 505 /** |
| 506 * Prepares information about used variables, which should be turned into |
| 507 * parameters. |
| 508 */ |
| 509 RefactoringStatus _initializeParameters() { |
| 510 _parameters.clear(); |
| 511 _parametersMap.clear(); |
| 512 _parameterReferencesMap.clear(); |
| 513 RefactoringStatus result = new RefactoringStatus(); |
| 514 List<VariableElement> assignedUsedVariables = []; |
| 515 unit.accept(new _InitializeParametersVisitor(this, assignedUsedVariables)); |
| 516 // may be ends with "return" statement |
| 517 if (_selectionStatements != null) { |
| 518 Statement lastStatement = |
| 519 _selectionStatements[_selectionStatements.length - 1]; |
| 520 if (lastStatement is ReturnStatement) { |
| 521 Expression expression = lastStatement.expression; |
| 522 if (expression != null) { |
| 523 _returnType = expression.bestType; |
| 524 } |
| 525 } |
| 526 } |
| 527 // may be single variable to return |
| 528 if (assignedUsedVariables.length == 1) { |
| 529 // we cannot both return variable and have explicit return statement |
| 530 if (_returnType != null) { |
| 531 result.addFatalError( |
| 532 "Ambiguous return value: Selected block contains assignment(s) to " |
| 533 "local variables and return statement."); |
| 534 return result; |
| 535 } |
| 536 // prepare to return an assigned variable |
| 537 VariableElement returnVariable = assignedUsedVariables[0]; |
| 538 _returnType = returnVariable.type; |
| 539 _returnVariableName = returnVariable.displayName; |
| 540 } |
| 541 // fatal, if multiple variables assigned and used after selection |
| 542 if (assignedUsedVariables.length > 1) { |
| 543 StringBuffer sb = new StringBuffer(); |
| 544 for (VariableElement variable in assignedUsedVariables) { |
| 545 sb.write(variable.displayName); |
| 546 sb.write("\n"); |
| 547 } |
| 548 result.addFatalError( |
| 549 format( |
| 550 "Ambiguous return value: Selected block contains more than one " |
| 551 "assignment to local variables. Affected variables are:\n\n{0}
", |
| 552 sb.toString().trim())); |
| 553 } |
| 554 // done |
| 555 return result; |
| 556 } |
| 557 |
| 558 void _initializeReturnType() { |
| 559 if (_returnType == null) { |
| 560 returnType = null; |
| 561 } else { |
| 562 returnType = utils.getTypeSource(_returnType); |
| 563 if (returnType == 'dynamic') { |
| 564 returnType = ''; |
| 565 } |
| 566 } |
| 567 } |
| 568 |
| 569 /** |
| 570 * Checks if the given [VariableElement] is declared in [selectionRange]. |
| 571 */ |
| 572 bool _isDeclaredInSelection(VariableElement element) { |
| 573 return selectionRange.contains(element.nameOffset); |
| 574 } |
| 575 |
| 576 /** |
| 577 * Checks if it is OK to extract the node with the given [SourceRange]. |
| 578 */ |
| 579 bool _isExtractable(SourceRange range) { |
| 580 _ExtractMethodAnalyzer analyzer = new _ExtractMethodAnalyzer(unit, range); |
| 581 utils.unit.accept(analyzer); |
| 582 return analyzer.status.isOK; |
| 583 } |
| 584 |
| 585 /** |
| 586 * Checks if [element] is referenced after [selectionRange]. |
| 587 */ |
| 588 bool _isUsedAfterSelection(VariableElement element) { |
| 589 var visitor = new _IsUsedAfterSelectionVisitor(this, element); |
| 590 _parentMember.accept(visitor); |
| 591 return visitor.result; |
| 592 } |
| 593 |
| 594 /** |
| 595 * Checks if [node] has a [MethodInvocation]. |
| 596 */ |
| 597 static bool _hasMethodInvocation(AstNode node) { |
| 598 var visitor = new _HasMethodInvocationVisitor(); |
| 599 node.accept(visitor); |
| 600 return visitor.result; |
| 601 } |
| 602 } |
| 603 |
| 604 |
| 605 /** |
| 606 * [SelectionAnalyzer] for [ExtractMethodRefactoringImpl]. |
| 607 */ |
| 608 class _ExtractMethodAnalyzer extends StatementAnalyzer { |
| 609 _ExtractMethodAnalyzer(CompilationUnit unit, SourceRange selection) |
| 610 : super(unit, selection); |
| 611 |
| 612 @override |
| 613 void handleNextSelectedNode(AstNode node) { |
| 614 super.handleNextSelectedNode(node); |
| 615 _checkParent(node); |
| 616 } |
| 617 |
| 618 @override |
| 619 void handleSelectionEndsIn(AstNode node) { |
| 620 super.handleSelectionEndsIn(node); |
| 621 invalidSelection( |
| 622 "The selection does not cover a set of statements or an expression. " |
| 623 "Extend selection to a valid range."); |
| 624 } |
| 625 |
| 626 @override |
| 627 Object visitAssignmentExpression(AssignmentExpression node) { |
| 628 super.visitAssignmentExpression(node); |
| 629 Expression lhs = node.leftHandSide; |
| 630 if (_isFirstSelectedNode(lhs)) { |
| 631 invalidSelection( |
| 632 'Cannot extract the left-hand side of an assignment.', |
| 633 new Location.fromNode(lhs)); |
| 634 } |
| 635 return null; |
| 636 } |
| 637 |
| 638 @override |
| 639 Object visitConstructorInitializer(ConstructorInitializer node) { |
| 640 super.visitConstructorInitializer(node); |
| 641 if (_isFirstSelectedNode(node)) { |
| 642 invalidSelection( |
| 643 'Cannot extract a constructor initializer. ' |
| 644 'Select expression part of initializer.', |
| 645 new Location.fromNode(node)); |
| 646 } |
| 647 return null; |
| 648 } |
| 649 |
| 650 @override |
| 651 Object visitForStatement(ForStatement node) { |
| 652 super.visitForStatement(node); |
| 653 if (identical(node.variables, firstSelectedNode)) { |
| 654 invalidSelection( |
| 655 "Cannot extract initialization part of a 'for' statement."); |
| 656 } else if (node.updaters.contains(lastSelectedNode)) { |
| 657 invalidSelection("Cannot extract increment part of a 'for' statement."); |
| 658 } |
| 659 return null; |
| 660 } |
| 661 |
| 662 @override |
| 663 Object visitSimpleIdentifier(SimpleIdentifier node) { |
| 664 super.visitSimpleIdentifier(node); |
| 665 if (_isFirstSelectedNode(node)) { |
| 666 // name of declaration |
| 667 if (node.inDeclarationContext()) { |
| 668 invalidSelection("Cannot extract the name part of a declaration."); |
| 669 } |
| 670 // method name |
| 671 Element element = node.bestElement; |
| 672 if (element is FunctionElement || element is MethodElement) { |
| 673 invalidSelection("Cannot extract a single method name."); |
| 674 } |
| 675 // name in property access |
| 676 if (node.parent is PrefixedIdentifier && |
| 677 (node.parent as PrefixedIdentifier).identifier == node) { |
| 678 invalidSelection("Can not extract name part of a property access."); |
| 679 } |
| 680 } |
| 681 return null; |
| 682 } |
| 683 |
| 684 @override |
| 685 Object visitTypeName(TypeName node) { |
| 686 super.visitTypeName(node); |
| 687 if (_isFirstSelectedNode(node)) { |
| 688 invalidSelection("Cannot extract a single type reference."); |
| 689 } |
| 690 return null; |
| 691 } |
| 692 |
| 693 @override |
| 694 Object visitVariableDeclaration(VariableDeclaration node) { |
| 695 super.visitVariableDeclaration(node); |
| 696 if (_isFirstSelectedNode(node)) { |
| 697 invalidSelection( |
| 698 "Cannot extract a variable declaration fragment. " |
| 699 "Select whole declaration statement.", |
| 700 new Location.fromNode(node)); |
| 701 } |
| 702 return null; |
| 703 } |
| 704 |
| 705 void _checkParent(AstNode node) { |
| 706 AstNode firstParent = firstSelectedNode.parent; |
| 707 do { |
| 708 node = node.parent; |
| 709 if (identical(node, firstParent)) { |
| 710 return; |
| 711 } |
| 712 } while (node != null); |
| 713 invalidSelection( |
| 714 "Not all selected statements are enclosed by the same parent statement."
); |
| 715 } |
| 716 |
| 717 bool _isFirstSelectedNode(AstNode node) => identical(firstSelectedNode, node); |
| 718 } |
| 719 |
| 720 |
| 721 class _GetSourcePatternVisitor extends GeneralizingAstVisitor { |
| 722 final SourceRange partRange; |
| 723 final _SourcePattern pattern; |
| 724 final List<SourceEdit> replaceEdits; |
| 725 |
| 726 _GetSourcePatternVisitor(this.partRange, this.pattern, this.replaceEdits); |
| 727 |
| 728 @override |
| 729 visitSimpleIdentifier(SimpleIdentifier node) { |
| 730 SourceRange nodeRange = rangeNode(node); |
| 731 if (partRange.covers(nodeRange)) { |
| 732 VariableElement variableElement = |
| 733 getLocalOrParameterVariableElement(node); |
| 734 if (variableElement != null) { |
| 735 // name of a named expression |
| 736 if (isNamedExpressionName(node)) { |
| 737 return; |
| 738 } |
| 739 // continue |
| 740 String originalName = variableElement.displayName; |
| 741 String patternName = pattern.originalToPatternNames[originalName]; |
| 742 if (patternName == null) { |
| 743 patternName = '__refVar${pattern.originalToPatternNames.length}'; |
| 744 pattern.originalToPatternNames[originalName] = patternName; |
| 745 } |
| 746 replaceEdits.add( |
| 747 new SourceEdit( |
| 748 nodeRange.offset - partRange.offset, |
| 749 nodeRange.length, |
| 750 patternName)); |
| 751 } |
| 752 } |
| 753 } |
| 754 } |
| 755 |
| 756 |
| 757 |
| 758 class _HasMethodInvocationVisitor extends RecursiveAstVisitor { |
| 759 bool result = false; |
| 760 |
| 761 @override |
| 762 visitMethodInvocation(MethodInvocation node) { |
| 763 result = true; |
| 764 } |
| 765 } |
| 766 |
| 767 |
| 768 class _InitializeOccurrencesVisitor extends GeneralizingAstVisitor<Object> { |
| 769 final ExtractMethodRefactoringImpl ref; |
| 770 final String selectionSource; |
| 771 final Map<String, String> patternToSelectionName; |
| 772 |
| 773 bool forceStatic = false; |
| 774 |
| 775 _InitializeOccurrencesVisitor(this.ref, this.selectionSource, |
| 776 this.patternToSelectionName); |
| 777 |
| 778 @override |
| 779 Object visitBlock(Block node) { |
| 780 if (ref._selectionStatements != null) { |
| 781 _visitStatements(node.statements); |
| 782 } |
| 783 return super.visitBlock(node); |
| 784 } |
| 785 |
| 786 @override |
| 787 Object visitConstructorInitializer(ConstructorInitializer node) { |
| 788 forceStatic = true; |
| 789 try { |
| 790 return super.visitConstructorInitializer(node); |
| 791 } finally { |
| 792 forceStatic = false; |
| 793 } |
| 794 } |
| 795 |
| 796 @override |
| 797 Object visitExpression(Expression node) { |
| 798 if (ref._selectionFunctionExpression != null || |
| 799 ref._selectionExpression != null && |
| 800 node.runtimeType == ref._selectionExpression.runtimeType) { |
| 801 SourceRange nodeRange = rangeNode(node); |
| 802 _tryToFindOccurrence(nodeRange); |
| 803 } |
| 804 return super.visitExpression(node); |
| 805 } |
| 806 |
| 807 @override |
| 808 Object visitMethodDeclaration(MethodDeclaration node) { |
| 809 forceStatic = node.isStatic; |
| 810 try { |
| 811 return super.visitMethodDeclaration(node); |
| 812 } finally { |
| 813 forceStatic = false; |
| 814 } |
| 815 } |
| 816 |
| 817 @override |
| 818 Object visitSwitchMember(SwitchMember node) { |
| 819 if (ref._selectionStatements != null) { |
| 820 _visitStatements(node.statements); |
| 821 } |
| 822 return super.visitSwitchMember(node); |
| 823 } |
| 824 |
| 825 /** |
| 826 * Checks if given [SourceRange] matched selection source and adds [_Occurrenc
e]. |
| 827 */ |
| 828 bool _tryToFindOccurrence(SourceRange nodeRange) { |
| 829 // check if can be extracted |
| 830 if (!ref._isExtractable(nodeRange)) { |
| 831 return false; |
| 832 } |
| 833 // prepare normalized node source |
| 834 _SourcePattern nodePattern = ref._getSourcePattern(nodeRange); |
| 835 String nodeSource = _getNormalizedSource(nodePattern.patternSource); |
| 836 // if matches normalized node source, then add as occurrence |
| 837 if (nodeSource == selectionSource) { |
| 838 _Occurrence occurrence = |
| 839 new _Occurrence(nodeRange, ref.selectionRange.intersects(nodeRange)); |
| 840 ref._occurrences.add(occurrence); |
| 841 // prepare mapping of parameter names to the occurrence variables |
| 842 for (MapEntry<String, String> entry in getMapEntrySet( |
| 843 nodePattern.originalToPatternNames)) { |
| 844 String patternName = entry.getValue(); |
| 845 String originalName = entry.getKey(); |
| 846 String selectionName = patternToSelectionName[patternName]; |
| 847 occurrence._parameterOldToOccurrenceName[selectionName] = originalName; |
| 848 } |
| 849 // update static |
| 850 if (forceStatic) { |
| 851 ref._staticContext = true; |
| 852 } |
| 853 // we have match |
| 854 return true; |
| 855 } |
| 856 // no match |
| 857 return false; |
| 858 } |
| 859 |
| 860 void _visitStatements(List<Statement> statements) { |
| 861 int beginStatementIndex = 0; |
| 862 int selectionCount = ref._selectionStatements.length; |
| 863 while (beginStatementIndex + selectionCount <= statements.length) { |
| 864 SourceRange nodeRange = rangeStartEnd( |
| 865 statements[beginStatementIndex], |
| 866 statements[beginStatementIndex + selectionCount - 1]); |
| 867 bool found = _tryToFindOccurrence(nodeRange); |
| 868 // next statement |
| 869 if (found) { |
| 870 beginStatementIndex += selectionCount; |
| 871 } else { |
| 872 beginStatementIndex++; |
| 873 } |
| 874 } |
| 875 } |
| 876 } |
| 877 |
| 878 |
| 879 class _InitializeParametersVisitor extends GeneralizingAstVisitor<Object> { |
| 880 final ExtractMethodRefactoringImpl ref; |
| 881 final List<VariableElement> assignedUsedVariables; |
| 882 |
| 883 _InitializeParametersVisitor(this.ref, this.assignedUsedVariables); |
| 884 |
| 885 @override |
| 886 Object visitSimpleIdentifier(SimpleIdentifier node) { |
| 887 SourceRange nodeRange = rangeNode(node); |
| 888 if (ref.selectionRange.covers(nodeRange)) { |
| 889 // analyze local variable |
| 890 VariableElement variableElement = |
| 891 getLocalOrParameterVariableElement(node); |
| 892 if (variableElement != null) { |
| 893 // name of the named expression |
| 894 if (isNamedExpressionName(node)) { |
| 895 return null; |
| 896 } |
| 897 // if declared outside, add parameter |
| 898 if (!ref._isDeclaredInSelection(variableElement)) { |
| 899 String variableName = variableElement.displayName; |
| 900 // add parameter |
| 901 RefactoringMethodParameter parameter = |
| 902 ref._parametersMap[variableName]; |
| 903 if (parameter == null) { |
| 904 DartType parameterType = node.bestType; |
| 905 String parameterTypeName = ref.utils.getTypeSource(parameterType); |
| 906 parameter = new RefactoringMethodParameter( |
| 907 RefactoringMethodParameterKind.REQUIRED, |
| 908 parameterTypeName, |
| 909 variableName, |
| 910 id: variableName); |
| 911 ref._parameters.add(parameter); |
| 912 ref._parametersMap[variableName] = parameter; |
| 913 } |
| 914 // add reference to parameter |
| 915 ref._addParameterReference(variableName, nodeRange); |
| 916 } |
| 917 // remember, if assigned and used after selection |
| 918 if (isLeftHandOfAssignment(node) && |
| 919 ref._isUsedAfterSelection(variableElement)) { |
| 920 if (!assignedUsedVariables.contains(variableElement)) { |
| 921 assignedUsedVariables.add(variableElement); |
| 922 } |
| 923 } |
| 924 } |
| 925 // remember declaration names |
| 926 if (node.inDeclarationContext()) { |
| 927 ref._usedNames.add(node.name); |
| 928 } |
| 929 } |
| 930 return null; |
| 931 } |
| 932 } |
| 933 |
| 934 class _IsUsedAfterSelectionVisitor extends GeneralizingAstVisitor { |
| 935 final ExtractMethodRefactoringImpl ref; |
| 936 final VariableElement element; |
| 937 bool result = false; |
| 938 |
| 939 _IsUsedAfterSelectionVisitor(this.ref, this.element); |
| 940 |
| 941 @override |
| 942 visitSimpleIdentifier(SimpleIdentifier node) { |
| 943 VariableElement nodeElement = getLocalVariableElement(node); |
| 944 if (identical(nodeElement, element)) { |
| 945 int nodeOffset = node.offset; |
| 946 if (nodeOffset > ref.selectionRange.end) { |
| 947 result = true; |
| 948 } |
| 949 } |
| 950 } |
| 951 } |
| 952 |
| 953 |
| 954 /** |
| 955 * Description of a single occurrence of the selected expression or set of |
| 956 * statements. |
| 957 */ |
| 958 class _Occurrence { |
| 959 final SourceRange range; |
| 960 final bool isSelection; |
| 961 |
| 962 Map<String, String> _parameterOldToOccurrenceName = <String, String>{}; |
| 963 |
| 964 _Occurrence(this.range, this.isSelection); |
| 965 } |
| 966 |
| 967 |
| 968 class _ResetCanCreateGetterVisitor extends RecursiveAstVisitor { |
| 969 final ExtractMethodRefactoringImpl ref; |
| 970 |
| 971 _ResetCanCreateGetterVisitor(this.ref); |
| 972 |
| 973 @override |
| 974 visitDoStatement(DoStatement node) { |
| 975 ref.createGetter = false; |
| 976 super.visitDoStatement(node); |
| 977 } |
| 978 |
| 979 @override |
| 980 visitForEachStatement(ForEachStatement node) { |
| 981 ref.createGetter = false; |
| 982 super.visitForEachStatement(node); |
| 983 } |
| 984 |
| 985 @override |
| 986 visitForStatement(ForStatement node) { |
| 987 ref.createGetter = false; |
| 988 super.visitForStatement(node); |
| 989 } |
| 990 |
| 991 @override |
| 992 visitWhileStatement(WhileStatement node) { |
| 993 ref.createGetter = false; |
| 994 super.visitWhileStatement(node); |
| 995 } |
| 996 } |
| 997 |
| 998 |
| 999 /** |
| 1000 * Generalized version of some source, in which references to the specific |
| 1001 * variables are replaced with pattern variables, with back mapping from the |
| 1002 * pattern to the original variable names. |
| 1003 */ |
| 1004 class _SourcePattern { |
| 1005 String patternSource; |
| 1006 Map<String, String> originalToPatternNames = {}; |
| 1007 } |
| OLD | NEW |