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 |