| 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 | 9 |
| 9 import 'package:analysis_server/src/protocol.dart' hide Element; | 10 import 'package:analysis_server/src/protocol_server.dart' hide Element; |
| 10 import 'package:analysis_server/src/services/correction/name_suggestion.dart'; | 11 import 'package:analysis_server/src/services/correction/name_suggestion.dart'; |
| 11 import 'package:analysis_server/src/services/correction/selection_analyzer.dart'
; | 12 import 'package:analysis_server/src/services/correction/selection_analyzer.dart'
; |
| 12 import 'package:analysis_server/src/services/correction/source_range.dart'; | 13 import 'package:analysis_server/src/services/correction/source_range.dart'; |
| 13 import 'package:analysis_server/src/services/correction/status.dart'; | 14 import 'package:analysis_server/src/services/correction/status.dart'; |
| 14 import 'package:analysis_server/src/services/correction/strings.dart'; | 15 import 'package:analysis_server/src/services/correction/strings.dart'; |
| 15 import 'package:analysis_server/src/services/correction/util.dart'; | 16 import 'package:analysis_server/src/services/correction/util.dart'; |
| 16 import 'package:analysis_server/src/services/refactoring/naming_conventions.dart
'; | 17 import 'package:analysis_server/src/services/refactoring/naming_conventions.dart
'; |
| 17 import 'package:analysis_server/src/services/refactoring/refactoring.dart'; | 18 import 'package:analysis_server/src/services/refactoring/refactoring.dart'; |
| 18 import 'package:analysis_server/src/services/refactoring/refactoring_internal.da
rt'; | 19 import 'package:analysis_server/src/services/refactoring/refactoring_internal.da
rt'; |
| 19 import 'package:analysis_server/src/services/search/element_visitors.dart'; | 20 import 'package:analysis_server/src/services/search/element_visitors.dart'; |
| 20 import 'package:analyzer/src/generated/ast.dart'; | 21 import 'package:analyzer/src/generated/ast.dart'; |
| 21 import 'package:analyzer/src/generated/element.dart'; | 22 import 'package:analyzer/src/generated/element.dart'; |
| 22 import 'package:analyzer/src/generated/java_core.dart'; | 23 import 'package:analyzer/src/generated/java_core.dart'; |
| 23 import 'package:analyzer/src/generated/scanner.dart'; | 24 import 'package:analyzer/src/generated/scanner.dart'; |
| 24 import 'package:analyzer/src/generated/source.dart'; | 25 import 'package:analyzer/src/generated/source.dart'; |
| 25 import 'dart:collection'; | |
| 26 | 26 |
| 27 | 27 |
| 28 const String _TOKEN_SEPARATOR = "\uFFFF"; | 28 const String _TOKEN_SEPARATOR = "\uFFFF"; |
| 29 | 29 |
| 30 | 30 |
| 31 /** | 31 /** |
| 32 * [ExtractLocalRefactoring] implementation. | 32 * [ExtractLocalRefactoring] implementation. |
| 33 */ | 33 */ |
| 34 class ExtractLocalRefactoringImpl extends RefactoringImpl implements | 34 class ExtractLocalRefactoringImpl extends RefactoringImpl implements |
| 35 ExtractLocalRefactoring { | 35 ExtractLocalRefactoring { |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 118 } else { | 118 } else { |
| 119 occurrences = [selectionRange]; | 119 occurrences = [selectionRange]; |
| 120 } | 120 } |
| 121 // If the whole expression of a statement is selected, like '1 + 2', | 121 // If the whole expression of a statement is selected, like '1 + 2', |
| 122 // then convert it into a variable declaration statement. | 122 // then convert it into a variable declaration statement. |
| 123 if (wholeStatementExpression && occurrences.length == 1) { | 123 if (wholeStatementExpression && occurrences.length == 1) { |
| 124 String keyword = _declarationKeyword; | 124 String keyword = _declarationKeyword; |
| 125 String declarationSource = '$keyword $name = '; | 125 String declarationSource = '$keyword $name = '; |
| 126 SourceEdit edit = | 126 SourceEdit edit = |
| 127 new SourceEdit(singleExpression.offset, 0, declarationSource); | 127 new SourceEdit(singleExpression.offset, 0, declarationSource); |
| 128 change.addElementEdit(unitElement, edit); | 128 doSourceChange_addElementEdit(change, unitElement, edit); |
| 129 return new Future.value(change); | 129 return new Future.value(change); |
| 130 } | 130 } |
| 131 // add variable declaration | 131 // add variable declaration |
| 132 { | 132 { |
| 133 String declarationSource; | 133 String declarationSource; |
| 134 if (stringLiteralPart != null) { | 134 if (stringLiteralPart != null) { |
| 135 declarationSource = "var $name = '$stringLiteralPart';"; | 135 declarationSource = "var $name = '$stringLiteralPart';"; |
| 136 } else { | 136 } else { |
| 137 String keyword = _declarationKeyword; | 137 String keyword = _declarationKeyword; |
| 138 String initializerSource = utils.getRangeText(selectionRange); | 138 String initializerSource = utils.getRangeText(selectionRange); |
| (...skipping 15 matching lines...) Expand all Loading... |
| 154 target_ = commonParent.getAncestor((node) => node is Statement); | 154 target_ = commonParent.getAncestor((node) => node is Statement); |
| 155 } | 155 } |
| 156 } | 156 } |
| 157 } | 157 } |
| 158 AstNode target = target_; | 158 AstNode target = target_; |
| 159 // insert variable declaration | 159 // insert variable declaration |
| 160 if (target is Statement) { | 160 if (target is Statement) { |
| 161 String prefix = utils.getNodePrefix(target); | 161 String prefix = utils.getNodePrefix(target); |
| 162 SourceEdit edit = | 162 SourceEdit edit = |
| 163 new SourceEdit(target.offset, 0, declarationSource + eol + prefix); | 163 new SourceEdit(target.offset, 0, declarationSource + eol + prefix); |
| 164 change.addElementEdit(unitElement, edit); | 164 doSourceChange_addElementEdit(change, unitElement, edit); |
| 165 } else if (target is ExpressionFunctionBody) { | 165 } else if (target is ExpressionFunctionBody) { |
| 166 String prefix = utils.getNodePrefix(target.parent); | 166 String prefix = utils.getNodePrefix(target.parent); |
| 167 String indent = utils.getIndent(1); | 167 String indent = utils.getIndent(1); |
| 168 String declStatement = prefix + indent + declarationSource + eol; | 168 String declStatement = prefix + indent + declarationSource + eol; |
| 169 String exprStatement = prefix + indent + 'return '; | 169 String exprStatement = prefix + indent + 'return '; |
| 170 Expression expr = target.expression; | 170 Expression expr = target.expression; |
| 171 change.addElementEdit( | 171 doSourceChange_addElementEdit( |
| 172 change, |
| 172 unitElement, | 173 unitElement, |
| 173 new SourceEdit( | 174 new SourceEdit( |
| 174 target.offset, | 175 target.offset, |
| 175 expr.offset - target.offset, | 176 expr.offset - target.offset, |
| 176 '{' + eol + declStatement + exprStatement)); | 177 '{' + eol + declStatement + exprStatement)); |
| 177 change.addElementEdit( | 178 doSourceChange_addElementEdit( |
| 179 change, |
| 178 unitElement, | 180 unitElement, |
| 179 new SourceEdit(expr.end, 0, ';' + eol + prefix + '}')); | 181 new SourceEdit(expr.end, 0, ';' + eol + prefix + '}')); |
| 180 } | 182 } |
| 181 } | 183 } |
| 182 // prepare replacement | 184 // prepare replacement |
| 183 String occurrenceReplacement = name; | 185 String occurrenceReplacement = name; |
| 184 if (stringLiteralPart != null) { | 186 if (stringLiteralPart != null) { |
| 185 occurrenceReplacement = "\${$name}"; | 187 occurrenceReplacement = "\${$name}"; |
| 186 } | 188 } |
| 187 // replace occurrences with variable reference | 189 // replace occurrences with variable reference |
| 188 for (SourceRange range in occurrences) { | 190 for (SourceRange range in occurrences) { |
| 189 SourceEdit edit = new SourceEdit.range(range, occurrenceReplacement); | 191 SourceEdit edit = newSourceEdit_range(range, occurrenceReplacement); |
| 190 change.addElementEdit(unitElement, edit); | 192 doSourceChange_addElementEdit(change, unitElement, edit); |
| 191 } | 193 } |
| 192 // done | 194 // done |
| 193 return new Future.value(change); | 195 return new Future.value(change); |
| 194 } | 196 } |
| 195 | 197 |
| 196 @override | 198 @override |
| 197 bool requiresPreview() => false; | 199 bool requiresPreview() => false; |
| 198 | 200 |
| 199 /** | 201 /** |
| 200 * Checks if [selectionRange] selects [Expression] which can be extracted, and | 202 * Checks if [selectionRange] selects [Expression] which can be extracted, and |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 255 singleExpression = null; | 257 singleExpression = null; |
| 256 return new RefactoringStatus(); | 258 return new RefactoringStatus(); |
| 257 } | 259 } |
| 258 } | 260 } |
| 259 // invalid selection | 261 // invalid selection |
| 260 return new RefactoringStatus.fatal( | 262 return new RefactoringStatus.fatal( |
| 261 'Expression must be selected to activate this refactoring.'); | 263 'Expression must be selected to activate this refactoring.'); |
| 262 } | 264 } |
| 263 | 265 |
| 264 /** | 266 /** |
| 267 * Return an unique identifier for the given [Element], or `null` if [element] |
| 268 * is `null`. |
| 269 */ |
| 270 int _encodeElement(Element element) { |
| 271 if (element == null) { |
| 272 return null; |
| 273 } |
| 274 int id = elementIds[element]; |
| 275 if (id == null) { |
| 276 id = elementIds.length; |
| 277 elementIds[element] = id; |
| 278 } |
| 279 return id; |
| 280 } |
| 281 |
| 282 /** |
| 283 * Returns an [Element]-sensitive encoding of [tokens]. |
| 284 * Each [Token] with a [LocalVariableElement] has a suffix of the element id. |
| 285 * |
| 286 * So, we can distingush different local variables with the same name, if |
| 287 * there are multiple variables with the same name are declared in the |
| 288 * function we are searching occurrences in. |
| 289 */ |
| 290 String _encodeExpressionTokens(Expression expr, List<Token> tokens) { |
| 291 // no expression, i.e. a part of a string |
| 292 if (expr == null) { |
| 293 return tokens.join(_TOKEN_SEPARATOR); |
| 294 } |
| 295 // prepare Token -> LocalElement map |
| 296 Map<Token, Element> map = new HashMap<Token, Element>( |
| 297 equals: (Token a, Token b) => a.lexeme == b.lexeme, |
| 298 hashCode: (Token t) => t.lexeme.hashCode); |
| 299 expr.accept(new _TokenLocalElementVisitor(map)); |
| 300 // map and join tokens |
| 301 return tokens.map((Token token) { |
| 302 String tokenString = token.lexeme; |
| 303 // append token's Element id |
| 304 Element element = map[token]; |
| 305 if (element != null) { |
| 306 int elementId = _encodeElement(element); |
| 307 if (elementId != null) { |
| 308 tokenString += '-$elementId'; |
| 309 } |
| 310 } |
| 311 // done |
| 312 return tokenString; |
| 313 }).join(_TOKEN_SEPARATOR); |
| 314 } |
| 315 |
| 316 /** |
| 265 * Returns [AstNode]s at the offsets of the given [SourceRange]s. | 317 * Returns [AstNode]s at the offsets of the given [SourceRange]s. |
| 266 */ | 318 */ |
| 267 List<AstNode> _findNodes(List<SourceRange> ranges) { | 319 List<AstNode> _findNodes(List<SourceRange> ranges) { |
| 268 List<AstNode> nodes = <AstNode>[]; | 320 List<AstNode> nodes = <AstNode>[]; |
| 269 for (SourceRange range in ranges) { | 321 for (SourceRange range in ranges) { |
| 270 AstNode node = new NodeLocator.con1(range.offset).searchWithin(unit); | 322 AstNode node = new NodeLocator.con1(range.offset).searchWithin(unit); |
| 271 nodes.add(node); | 323 nodes.add(node); |
| 272 } | 324 } |
| 273 return nodes; | 325 return nodes; |
| 274 } | 326 } |
| (...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 386 } | 438 } |
| 387 | 439 |
| 388 void _prepareOffsetsLengths() { | 440 void _prepareOffsetsLengths() { |
| 389 offsets.clear(); | 441 offsets.clear(); |
| 390 lengths.clear(); | 442 lengths.clear(); |
| 391 for (SourceRange occurrence in occurrences) { | 443 for (SourceRange occurrence in occurrences) { |
| 392 offsets.add(occurrence.offset); | 444 offsets.add(occurrence.offset); |
| 393 lengths.add(occurrence.length); | 445 lengths.add(occurrence.length); |
| 394 } | 446 } |
| 395 } | 447 } |
| 396 | |
| 397 /** | |
| 398 * Return an unique identifier for the given [Element], or `null` if [element] | |
| 399 * is `null`. | |
| 400 */ | |
| 401 int _encodeElement(Element element) { | |
| 402 if (element == null) { | |
| 403 return null; | |
| 404 } | |
| 405 int id = elementIds[element]; | |
| 406 if (id == null) { | |
| 407 id = elementIds.length; | |
| 408 elementIds[element] = id; | |
| 409 } | |
| 410 return id; | |
| 411 } | |
| 412 | |
| 413 /** | |
| 414 * Returns an [Element]-sensitive encoding of [tokens]. | |
| 415 * Each [Token] with a [LocalVariableElement] has a suffix of the element id. | |
| 416 * | |
| 417 * So, we can distingush different local variables with the same name, if | |
| 418 * there are multiple variables with the same name are declared in the | |
| 419 * function we are searching occurrences in. | |
| 420 */ | |
| 421 String _encodeExpressionTokens(Expression expr, List<Token> tokens) { | |
| 422 // no expression, i.e. a part of a string | |
| 423 if (expr == null) { | |
| 424 return tokens.join(_TOKEN_SEPARATOR); | |
| 425 } | |
| 426 // prepare Token -> LocalElement map | |
| 427 Map<Token, Element> map = new HashMap<Token, Element>( | |
| 428 equals: (Token a, Token b) => a.lexeme == b.lexeme, | |
| 429 hashCode: (Token t) => t.lexeme.hashCode); | |
| 430 expr.accept(new _TokenLocalElementVisitor(map)); | |
| 431 // map and join tokens | |
| 432 return tokens.map((Token token) { | |
| 433 String tokenString = token.lexeme; | |
| 434 // append token's Element id | |
| 435 Element element = map[token]; | |
| 436 if (element != null) { | |
| 437 int elementId = _encodeElement(element); | |
| 438 if (elementId != null) { | |
| 439 tokenString += '-$elementId'; | |
| 440 } | |
| 441 } | |
| 442 // done | |
| 443 return tokenString; | |
| 444 }).join(_TOKEN_SEPARATOR); | |
| 445 } | |
| 446 } | |
| 447 | |
| 448 | |
| 449 class _TokenLocalElementVisitor extends RecursiveAstVisitor { | |
| 450 final Map<Token, Element> map; | |
| 451 | |
| 452 _TokenLocalElementVisitor(this.map); | |
| 453 | |
| 454 visitSimpleIdentifier(SimpleIdentifier node) { | |
| 455 Element element = node.staticElement; | |
| 456 if (element is LocalVariableElement) { | |
| 457 map[node.token] = element; | |
| 458 } | |
| 459 } | |
| 460 } | 448 } |
| 461 | 449 |
| 462 | 450 |
| 463 /** | 451 /** |
| 464 * [SelectionAnalyzer] for [ExtractLocalRefactoringImpl]. | 452 * [SelectionAnalyzer] for [ExtractLocalRefactoringImpl]. |
| 465 */ | 453 */ |
| 466 class _ExtractExpressionAnalyzer extends SelectionAnalyzer { | 454 class _ExtractExpressionAnalyzer extends SelectionAnalyzer { |
| 467 final RefactoringStatus status = new RefactoringStatus(); | 455 final RefactoringStatus status = new RefactoringStatus(); |
| 468 | 456 |
| 469 _ExtractExpressionAnalyzer(SourceRange selection) : super(selection); | 457 _ExtractExpressionAnalyzer(SourceRange selection) : super(selection); |
| 470 | 458 |
| 471 /** | 459 /** |
| 472 * Records fatal error with given message. | 460 * Records fatal error with given message. |
| 473 */ | 461 */ |
| 474 void invalidSelection(String message) { | 462 void invalidSelection(String message) { |
| 475 _invalidSelection(message, null); | 463 _invalidSelection(message, null); |
| 476 } | 464 } |
| 477 | 465 |
| 478 @override | 466 @override |
| 479 Object visitAssignmentExpression(AssignmentExpression node) { | 467 Object visitAssignmentExpression(AssignmentExpression node) { |
| 480 super.visitAssignmentExpression(node); | 468 super.visitAssignmentExpression(node); |
| 481 Expression lhs = node.leftHandSide; | 469 Expression lhs = node.leftHandSide; |
| 482 if (_isFirstSelectedNode(lhs)) { | 470 if (_isFirstSelectedNode(lhs)) { |
| 483 _invalidSelection( | 471 _invalidSelection( |
| 484 'Cannot extract the left-hand side of an assignment.', | 472 'Cannot extract the left-hand side of an assignment.', |
| 485 new Location.fromNode(lhs)); | 473 newLocation_fromNode(lhs)); |
| 486 } | 474 } |
| 487 return null; | 475 return null; |
| 488 } | 476 } |
| 489 | 477 |
| 490 @override | 478 @override |
| 491 Object visitSimpleIdentifier(SimpleIdentifier node) { | 479 Object visitSimpleIdentifier(SimpleIdentifier node) { |
| 492 super.visitSimpleIdentifier(node); | 480 super.visitSimpleIdentifier(node); |
| 493 if (_isFirstSelectedNode(node)) { | 481 if (_isFirstSelectedNode(node)) { |
| 494 // name of declaration | 482 // name of declaration |
| 495 if (node.inDeclarationContext()) { | 483 if (node.inDeclarationContext()) { |
| (...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 628 Token startToken = nodeTokens[startTokenIndex]; | 616 Token startToken = nodeTokens[startTokenIndex]; |
| 629 Token endToken = nodeTokens[endTokenIndex]; | 617 Token endToken = nodeTokens[endTokenIndex]; |
| 630 // add occurrence range | 618 // add occurrence range |
| 631 int occuStart = nodeOffset + startToken.offset; | 619 int occuStart = nodeOffset + startToken.offset; |
| 632 int occuEnd = nodeOffset + endToken.end; | 620 int occuEnd = nodeOffset + endToken.end; |
| 633 SourceRange occuRange = rangeStartEnd(occuStart, occuEnd); | 621 SourceRange occuRange = rangeStartEnd(occuStart, occuEnd); |
| 634 _addOccurrence(occuRange); | 622 _addOccurrence(occuRange); |
| 635 } | 623 } |
| 636 } | 624 } |
| 637 } | 625 } |
| 626 |
| 627 |
| 628 class _TokenLocalElementVisitor extends RecursiveAstVisitor { |
| 629 final Map<Token, Element> map; |
| 630 |
| 631 _TokenLocalElementVisitor(this.map); |
| 632 |
| 633 visitSimpleIdentifier(SimpleIdentifier node) { |
| 634 Element element = node.staticElement; |
| 635 if (element is LocalVariableElement) { |
| 636 map[node.token] = element; |
| 637 } |
| 638 } |
| 639 } |
| OLD | NEW |