| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library services.src.refactoring.extract_local; | 5 library services.src.refactoring.extract_local; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | 8 import 'dart:collection'; |
| 9 | 9 |
| 10 import 'package:analysis_server/src/protocol_server.dart' hide Element; | 10 import 'package:analysis_server/src/protocol_server.dart' hide Element; |
| (...skipping 22 matching lines...) Expand all Loading... |
| 33 final CompilationUnit unit; | 33 final CompilationUnit unit; |
| 34 final int selectionOffset; | 34 final int selectionOffset; |
| 35 final int selectionLength; | 35 final int selectionLength; |
| 36 CompilationUnitElement unitElement; | 36 CompilationUnitElement unitElement; |
| 37 String file; | 37 String file; |
| 38 SourceRange selectionRange; | 38 SourceRange selectionRange; |
| 39 CorrectionUtils utils; | 39 CorrectionUtils utils; |
| 40 | 40 |
| 41 String name; | 41 String name; |
| 42 bool extractAll = true; | 42 bool extractAll = true; |
| 43 final List<int> coveringExpressionOffsets = <int>[]; |
| 44 final List<int> coveringExpressionLengths = <int>[]; |
| 43 final List<String> names = <String>[]; | 45 final List<String> names = <String>[]; |
| 44 final List<int> offsets = <int>[]; | 46 final List<int> offsets = <int>[]; |
| 45 final List<int> lengths = <int>[]; | 47 final List<int> lengths = <int>[]; |
| 46 | 48 |
| 47 Expression rootExpression; | 49 Expression rootExpression; |
| 48 Expression singleExpression; | 50 Expression singleExpression; |
| 49 bool wholeStatementExpression = false; | 51 bool wholeStatementExpression = false; |
| 50 String stringLiteralPart; | 52 String stringLiteralPart; |
| 51 final List<SourceRange> occurrences = <SourceRange>[]; | 53 final List<SourceRange> occurrences = <SourceRange>[]; |
| 52 final Map<Element, int> elementIds = <Element, int>{}; | 54 final Map<Element, int> elementIds = <Element, int>{}; |
| (...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 171 } | 173 } |
| 172 // done | 174 // done |
| 173 return new Future.value(change); | 175 return new Future.value(change); |
| 174 } | 176 } |
| 175 | 177 |
| 176 @override | 178 @override |
| 177 bool requiresPreview() => false; | 179 bool requiresPreview() => false; |
| 178 | 180 |
| 179 /** | 181 /** |
| 180 * Checks if [selectionRange] selects [Expression] which can be extracted, and | 182 * Checks if [selectionRange] selects [Expression] which can be extracted, and |
| 181 * location of this [DartExpression] in AST allows extracting. | 183 * location of this [Expression] in AST allows extracting. |
| 182 */ | 184 */ |
| 183 RefactoringStatus _checkSelection() { | 185 RefactoringStatus _checkSelection() { |
| 184 _ExtractExpressionAnalyzer _selectionAnalyzer = | 186 String selectionStr; |
| 185 new _ExtractExpressionAnalyzer(selectionRange); | 187 // exclude whitespaces |
| 186 unit.accept(_selectionAnalyzer); | |
| 187 AstNode coveringNode = _selectionAnalyzer.coveringNode; | |
| 188 // may be fatal error | |
| 189 { | 188 { |
| 190 RefactoringStatus status = _selectionAnalyzer.status; | 189 selectionStr = utils.getRangeText(selectionRange); |
| 191 if (status.hasFatalError) { | 190 int numLeading = countLeadingWhitespaces(selectionStr); |
| 192 return status; | 191 int numTrailing = countTrailingWhitespaces(selectionStr); |
| 192 int offset = selectionRange.offset + numLeading; |
| 193 int end = selectionRange.end - numTrailing; |
| 194 selectionRange = new SourceRange(offset, end - offset); |
| 195 } |
| 196 // get covering node |
| 197 AstNode coveringNode = new NodeLocator( |
| 198 selectionRange.offset, selectionRange.end).searchWithin(unit); |
| 199 // compute covering expressions |
| 200 for (AstNode node = coveringNode; node is Expression; node = node.parent) { |
| 201 AstNode parent = node.parent; |
| 202 // cannot extract the name part of a property access |
| 203 if (parent is PrefixedIdentifier && parent.identifier == node || |
| 204 parent is PropertyAccess && parent.propertyName == node) { |
| 205 continue; |
| 193 } | 206 } |
| 207 // fatal selection problems |
| 208 if (coveringExpressionOffsets.isEmpty) { |
| 209 if (node is SimpleIdentifier) { |
| 210 Element element = node.bestElement; |
| 211 if (element is FunctionElement || element is MethodElement) { |
| 212 return new RefactoringStatus.fatal( |
| 213 'Cannot extract a single method name.', |
| 214 newLocation_fromNode(node)); |
| 215 } |
| 216 if (node.inDeclarationContext()) { |
| 217 return new RefactoringStatus.fatal( |
| 218 'Cannot extract the name part of a declaration.', |
| 219 newLocation_fromNode(node)); |
| 220 } |
| 221 } |
| 222 if (parent is AssignmentExpression && parent.leftHandSide == node) { |
| 223 return new RefactoringStatus.fatal( |
| 224 'Cannot extract the left-hand side of an assignment.', |
| 225 newLocation_fromNode(node)); |
| 226 } |
| 227 } |
| 228 // set selected expression |
| 229 if (coveringExpressionOffsets.isEmpty) { |
| 230 rootExpression = node; |
| 231 } |
| 232 // add the expression range |
| 233 coveringExpressionOffsets.add(node.offset); |
| 234 coveringExpressionLengths.add(node.length); |
| 194 } | 235 } |
| 195 // we need enclosing block to add variable declaration statement | 236 // we need enclosing block to add variable declaration statement |
| 196 if (coveringNode == null || | 237 if (coveringNode == null || |
| 197 coveringNode.getAncestor((node) => node is Block) == null) { | 238 coveringNode.getAncestor((node) => node is Block) == null) { |
| 198 return new RefactoringStatus.fatal( | 239 return new RefactoringStatus.fatal( |
| 199 'Expression inside of function must be selected ' | 240 'Expression inside of function must be selected ' |
| 200 'to activate this refactoring.'); | 241 'to activate this refactoring.'); |
| 201 } | 242 } |
| 202 // part of string literal | 243 // part of string literal |
| 203 if (coveringNode is StringLiteral) { | 244 if (coveringNode is StringLiteral) { |
| 204 stringLiteralPart = utils.getRangeText(selectionRange); | 245 if (selectionRange.offset > coveringNode.offset && |
| 205 if (stringLiteralPart.startsWith("'") || | 246 selectionRange.end < coveringNode.end) { |
| 206 stringLiteralPart.startsWith('"') || | 247 stringLiteralPart = selectionStr; |
| 207 stringLiteralPart.endsWith("'") || | |
| 208 stringLiteralPart.endsWith('"')) { | |
| 209 return new RefactoringStatus.fatal( | |
| 210 'Cannot extract only leading or trailing quote of string literal.'); | |
| 211 } | |
| 212 return new RefactoringStatus(); | |
| 213 } | |
| 214 // single node selected | |
| 215 if (_selectionAnalyzer.selectedNodes.length == 1 && | |
| 216 !utils.selectionIncludesNonWhitespaceOutsideNode( | |
| 217 selectionRange, _selectionAnalyzer.firstSelectedNode)) { | |
| 218 AstNode selectedNode = _selectionAnalyzer.firstSelectedNode; | |
| 219 if (selectedNode is Expression) { | |
| 220 rootExpression = selectedNode; | |
| 221 singleExpression = rootExpression; | |
| 222 wholeStatementExpression = | |
| 223 singleExpression.parent is ExpressionStatement; | |
| 224 return new RefactoringStatus(); | 248 return new RefactoringStatus(); |
| 225 } | 249 } |
| 226 } | 250 } |
| 227 // fragment of binary expression selected | 251 // single node selected |
| 228 if (coveringNode is BinaryExpression) { | 252 if (rootExpression != null) { |
| 229 BinaryExpression binaryExpression = coveringNode; | 253 singleExpression = rootExpression; |
| 230 if (utils.validateBinaryExpressionRange( | 254 selectionRange = rangeNode(singleExpression); |
| 231 binaryExpression, selectionRange)) { | 255 wholeStatementExpression = singleExpression.parent is ExpressionStatement; |
| 232 rootExpression = binaryExpression; | 256 return new RefactoringStatus(); |
| 233 singleExpression = null; | |
| 234 return new RefactoringStatus(); | |
| 235 } | |
| 236 } | 257 } |
| 237 // invalid selection | 258 // invalid selection |
| 238 return new RefactoringStatus.fatal( | 259 return new RefactoringStatus.fatal( |
| 239 'Expression must be selected to activate this refactoring.'); | 260 'Expression must be selected to activate this refactoring.'); |
| 240 } | 261 } |
| 241 | 262 |
| 242 /** | 263 /** |
| 243 * Return an unique identifier for the given [Element], or `null` if [element] | 264 * Return an unique identifier for the given [Element], or `null` if [element] |
| 244 * is `null`. | 265 * is `null`. |
| 245 */ | 266 */ |
| 246 int _encodeElement(Element element) { | 267 int _encodeElement(Element element) { |
| 247 if (element == null) { | 268 if (element == null) { |
| 248 return null; | 269 return null; |
| 249 } | 270 } |
| 250 int id = elementIds[element]; | 271 int id = elementIds[element]; |
| 251 if (id == null) { | 272 if (id == null) { |
| 252 id = elementIds.length; | 273 id = elementIds.length; |
| 253 elementIds[element] = id; | 274 elementIds[element] = id; |
| 254 } | 275 } |
| 255 return id; | 276 return id; |
| 256 } | 277 } |
| 257 | 278 |
| 258 /** | 279 /** |
| 259 * Returns an [Element]-sensitive encoding of [tokens]. | 280 * Returns an [Element]-sensitive encoding of [tokens]. |
| 260 * Each [Token] with a [LocalVariableElement] has a suffix of the element id. | 281 * Each [Token] with a [LocalVariableElement] has a suffix of the element id. |
| 261 * | 282 * |
| 262 * So, we can distingush different local variables with the same name, if | 283 * So, we can distinguish different local variables with the same name, if |
| 263 * there are multiple variables with the same name are declared in the | 284 * there are multiple variables with the same name are declared in the |
| 264 * function we are searching occurrences in. | 285 * function we are searching occurrences in. |
| 265 */ | 286 */ |
| 266 String _encodeExpressionTokens(Expression expr, List<Token> tokens) { | 287 String _encodeExpressionTokens(Expression expr, List<Token> tokens) { |
| 267 // no expression, i.e. a part of a string | 288 // no expression, i.e. a part of a string |
| 268 if (expr == null) { | 289 if (expr == null) { |
| 269 return tokens.join(_TOKEN_SEPARATOR); | 290 return tokens.join(_TOKEN_SEPARATOR); |
| 270 } | 291 } |
| 271 // prepare Token -> LocalElement map | 292 // prepare Token -> LocalElement map |
| 272 Map<Token, Element> map = new HashMap<Token, Element>( | 293 Map<Token, Element> map = new HashMap<Token, Element>( |
| (...skipping 195 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 468 invalidSelection('Cannot extract name part of a property access.'); | 489 invalidSelection('Cannot extract name part of a property access.'); |
| 469 } | 490 } |
| 470 if (parent is PropertyAccess && identical(parent.propertyName, node)) { | 491 if (parent is PropertyAccess && identical(parent.propertyName, node)) { |
| 471 invalidSelection('Cannot extract name part of a property access.'); | 492 invalidSelection('Cannot extract name part of a property access.'); |
| 472 } | 493 } |
| 473 } | 494 } |
| 474 return null; | 495 return null; |
| 475 } | 496 } |
| 476 | 497 |
| 477 /** | 498 /** |
| 478 * Records fatal error with given message and [Locatiom]. | 499 * Records fatal error with given [message] and [location]. |
| 479 */ | 500 */ |
| 480 void _invalidSelection(String message, Location location) { | 501 void _invalidSelection(String message, Location location) { |
| 481 status.addFatalError(message, location); | 502 status.addFatalError(message, location); |
| 482 reset(); | 503 reset(); |
| 483 } | 504 } |
| 484 | 505 |
| 485 bool _isFirstSelectedNode(AstNode node) => node == firstSelectedNode; | 506 bool _isFirstSelectedNode(AstNode node) => node == firstSelectedNode; |
| 486 } | 507 } |
| 487 | 508 |
| 488 class _HasStatementVisitor extends GeneralizingAstVisitor { | 509 class _HasStatementVisitor extends GeneralizingAstVisitor { |
| (...skipping 27 matching lines...) Expand all Loading... |
| 516 Object visitExpression(Expression node) { | 537 Object visitExpression(Expression node) { |
| 517 if (ref._isExtractable(rangeNode(node))) { | 538 if (ref._isExtractable(rangeNode(node))) { |
| 518 _tryToFindOccurrence(node); | 539 _tryToFindOccurrence(node); |
| 519 } | 540 } |
| 520 return super.visitExpression(node); | 541 return super.visitExpression(node); |
| 521 } | 542 } |
| 522 | 543 |
| 523 @override | 544 @override |
| 524 Object visitStringLiteral(StringLiteral node) { | 545 Object visitStringLiteral(StringLiteral node) { |
| 525 if (ref.stringLiteralPart != null) { | 546 if (ref.stringLiteralPart != null) { |
| 526 int occuLength = ref.stringLiteralPart.length; | 547 int length = ref.stringLiteralPart.length; |
| 527 String value = ref.utils.getNodeText(node); | 548 String value = ref.utils.getNodeText(node); |
| 528 int lastIndex = 0; | 549 int lastIndex = 0; |
| 529 while (true) { | 550 while (true) { |
| 530 int index = value.indexOf(ref.stringLiteralPart, lastIndex); | 551 int index = value.indexOf(ref.stringLiteralPart, lastIndex); |
| 531 if (index == -1) { | 552 if (index == -1) { |
| 532 break; | 553 break; |
| 533 } | 554 } |
| 534 lastIndex = index + occuLength; | 555 lastIndex = index + length; |
| 535 int occuStart = node.offset + index; | 556 int start = node.offset + index; |
| 536 SourceRange occuRange = rangeStartLength(occuStart, occuLength); | 557 SourceRange range = rangeStartLength(start, length); |
| 537 occurrences.add(occuRange); | 558 occurrences.add(range); |
| 538 } | 559 } |
| 539 return null; | 560 return null; |
| 540 } | 561 } |
| 541 return visitExpression(node); | 562 return visitExpression(node); |
| 542 } | 563 } |
| 543 | 564 |
| 544 void _addOccurrence(SourceRange range) { | 565 void _addOccurrence(SourceRange range) { |
| 545 if (range.intersects(ref.selectionRange)) { | 566 if (range.intersects(ref.selectionRange)) { |
| 546 occurrences.add(ref.selectionRange); | 567 occurrences.add(ref.selectionRange); |
| 547 } else { | 568 } else { |
| 548 occurrences.add(range); | 569 occurrences.add(range); |
| 549 } | 570 } |
| 550 } | 571 } |
| 551 | 572 |
| 552 bool _hasStatements(AstNode root) { | 573 bool _hasStatements(AstNode root) { |
| 553 _HasStatementVisitor visitor = new _HasStatementVisitor(); | 574 _HasStatementVisitor visitor = new _HasStatementVisitor(); |
| 554 root.accept(visitor); | 575 root.accept(visitor); |
| 555 return visitor.result; | 576 return visitor.result; |
| 556 } | 577 } |
| 557 | 578 |
| 558 void _tryToFindOccurrence(Expression node) { | 579 void _tryToFindOccurrence(Expression node) { |
| 559 String nodeSource = ref.utils.getNodeText(node); | 580 String nodeSource = ref.utils.getNodeText(node); |
| 560 List<Token> nodeTokens = TokenUtils.getTokens(nodeSource); | 581 List<Token> nodeTokens = TokenUtils.getTokens(nodeSource); |
| 561 nodeSource = ref._encodeExpressionTokens(node, nodeTokens); | 582 nodeSource = ref._encodeExpressionTokens(node, nodeTokens); |
| 562 if (nodeSource == selectionSource) { | 583 if (nodeSource == selectionSource) { |
| 563 SourceRange occuRange = rangeNode(node); | 584 SourceRange range = rangeNode(node); |
| 564 _addOccurrence(occuRange); | 585 _addOccurrence(range); |
| 565 } | 586 } |
| 566 } | 587 } |
| 567 | 588 |
| 568 void _tryToFindOccurrenceFragments(Expression node) { | 589 void _tryToFindOccurrenceFragments(Expression node) { |
| 569 int nodeOffset = node.offset; | 590 int nodeOffset = node.offset; |
| 570 String nodeSource = ref.utils.getNodeText(node); | 591 String nodeSource = ref.utils.getNodeText(node); |
| 571 List<Token> nodeTokens = TokenUtils.getTokens(nodeSource); | 592 List<Token> nodeTokens = TokenUtils.getTokens(nodeSource); |
| 572 nodeSource = ref._encodeExpressionTokens(node, nodeTokens); | 593 nodeSource = ref._encodeExpressionTokens(node, nodeTokens); |
| 573 // find "selection" in "node" tokens | 594 // find "selection" in "node" tokens |
| 574 int lastIndex = 0; | 595 int lastIndex = 0; |
| 575 while (true) { | 596 while (true) { |
| 576 // find next occurrence | 597 // find next occurrence |
| 577 int index = nodeSource.indexOf(selectionSource, lastIndex); | 598 int index = nodeSource.indexOf(selectionSource, lastIndex); |
| 578 if (index == -1) { | 599 if (index == -1) { |
| 579 break; | 600 break; |
| 580 } | 601 } |
| 581 lastIndex = index + selectionSource.length; | 602 lastIndex = index + selectionSource.length; |
| 582 // find start/end tokens | 603 // find start/end tokens |
| 583 int startTokenIndex = | 604 int startTokenIndex = |
| 584 countMatches(nodeSource.substring(0, index), _TOKEN_SEPARATOR); | 605 countMatches(nodeSource.substring(0, index), _TOKEN_SEPARATOR); |
| 585 int endTokenIndex = | 606 int endTokenIndex = |
| 586 countMatches(nodeSource.substring(0, lastIndex), _TOKEN_SEPARATOR); | 607 countMatches(nodeSource.substring(0, lastIndex), _TOKEN_SEPARATOR); |
| 587 Token startToken = nodeTokens[startTokenIndex]; | 608 Token startToken = nodeTokens[startTokenIndex]; |
| 588 Token endToken = nodeTokens[endTokenIndex]; | 609 Token endToken = nodeTokens[endTokenIndex]; |
| 589 // add occurrence range | 610 // add occurrence range |
| 590 int occuStart = nodeOffset + startToken.offset; | 611 int start = nodeOffset + startToken.offset; |
| 591 int occuEnd = nodeOffset + endToken.end; | 612 int end = nodeOffset + endToken.end; |
| 592 SourceRange occuRange = rangeStartEnd(occuStart, occuEnd); | 613 SourceRange range = rangeStartEnd(start, end); |
| 593 _addOccurrence(occuRange); | 614 _addOccurrence(range); |
| 594 } | 615 } |
| 595 } | 616 } |
| 596 } | 617 } |
| 597 | 618 |
| 598 class _TokenLocalElementVisitor extends RecursiveAstVisitor { | 619 class _TokenLocalElementVisitor extends RecursiveAstVisitor { |
| 599 final Map<Token, Element> map; | 620 final Map<Token, Element> map; |
| 600 | 621 |
| 601 _TokenLocalElementVisitor(this.map); | 622 _TokenLocalElementVisitor(this.map); |
| 602 | 623 |
| 603 visitSimpleIdentifier(SimpleIdentifier node) { | 624 visitSimpleIdentifier(SimpleIdentifier node) { |
| 604 Element element = node.staticElement; | 625 Element element = node.staticElement; |
| 605 if (element is LocalVariableElement) { | 626 if (element is LocalVariableElement) { |
| 606 map[node.token] = element; | 627 map[node.token] = element; |
| 607 } | 628 } |
| 608 } | 629 } |
| 609 } | 630 } |
| OLD | NEW |