| 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 | 8 |
| 9 import 'package:analysis_server/src/protocol2.dart' show SourceEdit; | 9 import 'package:analysis_server/src/protocol2.dart' show SourceEdit; |
| 10 import 'package:analysis_server/src/services/correction/change.dart'; | 10 import 'package:analysis_server/src/services/correction/change.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'; |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 52 final List<SourceRange> occurrences = <SourceRange>[]; | 53 final List<SourceRange> occurrences = <SourceRange>[]; |
| 53 final Set<String> excludedVariableNames = new Set<String>(); | 54 final Set<String> excludedVariableNames = new Set<String>(); |
| 54 | 55 |
| 55 ExtractLocalRefactoringImpl(this.unit, this.selectionOffset, | 56 ExtractLocalRefactoringImpl(this.unit, this.selectionOffset, |
| 56 this.selectionLength) { | 57 this.selectionLength) { |
| 57 file = unit.element.source.fullName; | 58 file = unit.element.source.fullName; |
| 58 selectionRange = new SourceRange(selectionOffset, selectionLength); | 59 selectionRange = new SourceRange(selectionOffset, selectionLength); |
| 59 utils = new CorrectionUtils(unit); | 60 utils = new CorrectionUtils(unit); |
| 60 } | 61 } |
| 61 | 62 |
| 62 String get declarationKeyword { | 63 @override |
| 64 String get refactoringName => 'Extract Local Variable'; |
| 65 |
| 66 String get _declarationKeyword { |
| 63 if (_isPartOfConstantExpression(rootExpression)) { | 67 if (_isPartOfConstantExpression(rootExpression)) { |
| 64 return "const"; | 68 return "const"; |
| 65 } else { | 69 } else { |
| 66 return "var"; | 70 return "var"; |
| 67 } | 71 } |
| 68 } | 72 } |
| 69 | 73 |
| 70 @override | 74 @override |
| 71 String get refactoringName => 'Extract Local Variable'; | |
| 72 | |
| 73 @override | |
| 74 Future<RefactoringStatus> checkFinalConditions() { | 75 Future<RefactoringStatus> checkFinalConditions() { |
| 75 RefactoringStatus result = new RefactoringStatus(); | 76 RefactoringStatus result = new RefactoringStatus(); |
| 76 if (excludedVariableNames.contains(name)) { | 77 if (excludedVariableNames.contains(name)) { |
| 77 result.addWarning( | 78 result.addWarning( |
| 78 format( | 79 format( |
| 79 "A variable with name '{0}' is already defined in the visible scop
e.", | 80 "A variable with name '{0}' is already defined in the visible scop
e.", |
| 80 name)); | 81 name)); |
| 81 } | 82 } |
| 82 return new Future.value(result); | 83 return new Future.value(result); |
| 83 } | 84 } |
| 84 | 85 |
| 85 @override | 86 @override |
| 86 Future<RefactoringStatus> checkInitialConditions() { | 87 Future<RefactoringStatus> checkInitialConditions() { |
| 87 RefactoringStatus result = new RefactoringStatus(); | 88 RefactoringStatus result = new RefactoringStatus(); |
| 88 // selection | 89 // selection |
| 89 result.addStatus(_checkSelection()); | 90 result.addStatus(_checkSelection()); |
| 91 if (result.hasFatalError) { |
| 92 return new Future.value(result); |
| 93 } |
| 90 // occurrences | 94 // occurrences |
| 91 if (!result.hasFatalError) { | 95 _prepareOccurrences(); |
| 92 _prepareOccurrences(); | 96 _prepareOffsetsLengths(); |
| 93 _prepareExcludedNames(); | 97 // names |
| 94 } | 98 _prepareExcludedNames(); |
| 95 // suggested names | |
| 96 _prepareNames(); | 99 _prepareNames(); |
| 97 // done | 100 // done |
| 98 return new Future.value(result); | 101 return new Future.value(result); |
| 99 } | 102 } |
| 100 | 103 |
| 101 @override | 104 @override |
| 102 RefactoringStatus checkName() { | 105 RefactoringStatus checkName() { |
| 103 return validateVariableName(name); | 106 return validateVariableName(name); |
| 104 } | 107 } |
| 105 | 108 |
| 106 @override | 109 @override |
| 107 Future<Change> createChange() { | 110 Future<Change> createChange() { |
| 108 Change change = new Change(refactoringName); | 111 Change change = new Change(refactoringName); |
| 109 // prepare occurrences | 112 // prepare occurrences |
| 110 List<SourceRange> occurrences; | 113 List<SourceRange> occurrences; |
| 111 if (extractAll) { | 114 if (extractAll) { |
| 112 occurrences = this.occurrences; | 115 occurrences = this.occurrences; |
| 113 } else { | 116 } else { |
| 114 occurrences = [selectionRange]; | 117 occurrences = [selectionRange]; |
| 115 } | 118 } |
| 116 // If the whole expression of a statement is selected, like '1 + 2', | 119 // If the whole expression of a statement is selected, like '1 + 2', |
| 117 // then convert it into a variable declaration statement. | 120 // then convert it into a variable declaration statement. |
| 118 if (wholeStatementExpression && occurrences.length == 1) { | 121 if (wholeStatementExpression && occurrences.length == 1) { |
| 119 String keyword = declarationKeyword; | 122 String keyword = _declarationKeyword; |
| 120 String declarationSource = '$keyword $name = '; | 123 String declarationSource = '$keyword $name = '; |
| 121 SourceEdit edit = | 124 SourceEdit edit = |
| 122 new SourceEdit(singleExpression.offset, 0, declarationSource); | 125 new SourceEdit(singleExpression.offset, 0, declarationSource); |
| 123 change.addEdit(file, edit); | 126 change.addEdit(file, edit); |
| 124 return new Future.value(change); | 127 return new Future.value(change); |
| 125 } | 128 } |
| 126 // add variable declaration | 129 // add variable declaration |
| 127 { | 130 { |
| 128 String declarationSource; | 131 String declarationSource; |
| 129 if (stringLiteralPart != null) { | 132 if (stringLiteralPart != null) { |
| 130 declarationSource = "var ${name} = '${stringLiteralPart}';"; | 133 declarationSource = "var ${name} = '${stringLiteralPart}';"; |
| 131 } else { | 134 } else { |
| 132 String keyword = declarationKeyword; | 135 String keyword = _declarationKeyword; |
| 133 String initializerSource = utils.getRangeText(selectionRange); | 136 String initializerSource = utils.getRangeText(selectionRange); |
| 134 declarationSource = "${keyword} ${name} = ${initializerSource};"; | 137 declarationSource = "${keyword} ${name} = ${initializerSource};"; |
| 135 } | 138 } |
| 136 // prepare location for declaration | 139 // prepare location for declaration |
| 137 Statement targetStatement = _findTargetStatement(occurrences); | 140 Statement targetStatement = _findTargetStatement(occurrences); |
| 138 String prefix = utils.getNodePrefix(targetStatement); | 141 String prefix = utils.getNodePrefix(targetStatement); |
| 139 // insert variable declaration | 142 // insert variable declaration |
| 140 String eol = utils.endOfLine; | 143 String eol = utils.endOfLine; |
| 141 SourceEdit edit = new SourceEdit( | 144 SourceEdit edit = new SourceEdit( |
| 142 targetStatement.offset, | 145 targetStatement.offset, |
| (...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 232 List<AstNode> _findNodes(List<SourceRange> ranges) { | 235 List<AstNode> _findNodes(List<SourceRange> ranges) { |
| 233 List<AstNode> nodes = <AstNode>[]; | 236 List<AstNode> nodes = <AstNode>[]; |
| 234 for (SourceRange range in ranges) { | 237 for (SourceRange range in ranges) { |
| 235 AstNode node = new NodeLocator.con1(range.offset).searchWithin(unit); | 238 AstNode node = new NodeLocator.con1(range.offset).searchWithin(unit); |
| 236 nodes.add(node); | 239 nodes.add(node); |
| 237 } | 240 } |
| 238 return nodes; | 241 return nodes; |
| 239 } | 242 } |
| 240 | 243 |
| 241 /** | 244 /** |
| 242 * @return the [Statement] such that variable declaration added before it will
be visible in | 245 * Returns the [Statement] such that variable declaration added before it is |
| 243 * all given occurrences. | 246 * visible at all given occurrences. |
| 244 */ | 247 */ |
| 245 Statement _findTargetStatement(List<SourceRange> occurrences) { | 248 Statement _findTargetStatement(List<SourceRange> occurrences) { |
| 246 List<AstNode> nodes = _findNodes(occurrences); | 249 List<AstNode> nodes = _findNodes(occurrences); |
| 247 List<AstNode> firstParents = getParents(nodes[0]); | 250 List<AstNode> firstParents = getParents(nodes[0]); |
| 248 AstNode commonParent = getNearestCommonAncestor(nodes); | 251 AstNode commonParent = getNearestCommonAncestor(nodes); |
| 249 if (commonParent is Block) { | 252 if (commonParent is Block) { |
| 250 int commonIndex = firstParents.indexOf(commonParent); | 253 int commonIndex = firstParents.indexOf(commonParent); |
| 251 return firstParents[commonIndex + 1] as Statement; | 254 return firstParents[commonIndex + 1] as Statement; |
| 252 } else { | 255 } else { |
| 253 return commonParent.getAncestor((node) => node is Statement); | 256 return commonParent.getAncestor((node) => node is Statement); |
| 254 } | 257 } |
| 255 } | 258 } |
| 256 | 259 |
| 257 /** | 260 /** |
| 258 * @return `true` if it is OK to extract the node with the given [SourceRange]
. | 261 * Checks if it is OK to extract the node with the given [SourceRange]. |
| 259 */ | 262 */ |
| 260 bool _isExtractable(SourceRange range) { | 263 bool _isExtractable(SourceRange range) { |
| 261 _ExtractExpressionAnalyzer analyzer = new _ExtractExpressionAnalyzer(range); | 264 _ExtractExpressionAnalyzer analyzer = new _ExtractExpressionAnalyzer(range); |
| 262 utils.unit.accept(analyzer); | 265 utils.unit.accept(analyzer); |
| 263 return analyzer.status.isOK; | 266 return analyzer.status.isOK; |
| 264 } | 267 } |
| 265 | 268 |
| 266 bool _isPartOfConstantExpression(AstNode node) { | 269 bool _isPartOfConstantExpression(AstNode node) { |
| 267 if (node is TypedLiteral) { | 270 if (node is TypedLiteral) { |
| 268 return node.constKeyword != null; | 271 return node.constKeyword != null; |
| 269 } | 272 } |
| 270 if (node is InstanceCreationExpression) { | 273 if (node is InstanceCreationExpression) { |
| 271 InstanceCreationExpression creation = node; | 274 InstanceCreationExpression creation = node; |
| 272 return creation.isConst; | 275 return creation.isConst; |
| 273 } | 276 } |
| 274 if (node is ArgumentList || | 277 if (node is ArgumentList || |
| 275 node is ConditionalExpression || | 278 node is ConditionalExpression || |
| 276 node is BinaryExpression || | 279 node is BinaryExpression || |
| 277 node is ParenthesizedExpression || | 280 node is ParenthesizedExpression || |
| 278 node is PrefixExpression || | 281 node is PrefixExpression || |
| 279 node is Literal || | 282 node is Literal || |
| 280 node is MapLiteralEntry) { | 283 node is MapLiteralEntry) { |
| 281 return _isPartOfConstantExpression(node.parent); | 284 return _isPartOfConstantExpression(node.parent); |
| 282 } | 285 } |
| 283 return false; | 286 return false; |
| 284 } | 287 } |
| 285 | 288 |
| 286 void _prepareExcludedNames() { | 289 void _prepareExcludedNames() { |
| 287 excludedVariableNames.clear(); | 290 excludedVariableNames.clear(); |
| 288 // TODO(scheglov) clean up? | |
| 289 AstNode enclosingNode = | 291 AstNode enclosingNode = |
| 290 new NodeLocator.con1(selectionOffset).searchWithin(unit); | 292 new NodeLocator.con1(selectionOffset).searchWithin(unit); |
| 291 Block enclosingBlock = enclosingNode.getAncestor((node) => node is Block); | 293 Block enclosingBlock = enclosingNode.getAncestor((node) => node is Block); |
| 292 if (enclosingBlock != null) { | 294 if (enclosingBlock != null) { |
| 293 SourceRange newVariableVisibleRange = | 295 SourceRange newVariableVisibleRange = |
| 294 rangeStartEnd(selectionRange, enclosingBlock.end); | 296 rangeStartEnd(selectionRange, enclosingBlock.end); |
| 295 ExecutableElement enclosingExecutable = | 297 ExecutableElement enclosingExecutable = |
| 296 getEnclosingExecutableElement(enclosingNode); | 298 getEnclosingExecutableElement(enclosingNode); |
| 297 if (enclosingExecutable != null) { | 299 if (enclosingExecutable != null) { |
| 298 visitChildren(enclosingExecutable, (Element element) { | 300 visitChildren(enclosingExecutable, (Element element) { |
| 299 if (element is LocalElement) { | 301 if (element is LocalElement) { |
| 300 SourceRange elementRange = element.visibleRange; | 302 SourceRange elementRange = element.visibleRange; |
| 301 if (elementRange != null && | 303 if (elementRange != null && |
| 302 elementRange.intersects(newVariableVisibleRange)) { | 304 elementRange.intersects(newVariableVisibleRange)) { |
| 303 excludedVariableNames.add(element.displayName); | 305 excludedVariableNames.add(element.displayName); |
| 304 } | 306 } |
| 305 } | 307 } |
| 306 return true; | 308 return true; |
| 307 }); | 309 }); |
| 308 } | 310 } |
| 309 } | 311 } |
| 310 } | 312 } |
| 311 | 313 |
| 312 void _prepareNames() { | 314 void _prepareNames() { |
| 313 names.clear(); | 315 names.clear(); |
| 314 // TODO(scheglov) implement | 316 if (stringLiteralPart != null) { |
| 315 // Set<String> excluded = excludedVariableNames; | 317 names.addAll( |
| 316 // if (_stringLiteralPart != null) { | 318 getVariableNameSuggestionsForText(stringLiteralPart, excludedVariableN
ames)); |
| 317 // return getVariableNameSuggestions(_stringLiteralPart, excluded); | 319 } else if (singleExpression != null) { |
| 318 // } else if (_singleExpression != null) { | 320 names.addAll( |
| 319 // _guessedNames = CorrectionUtils.getVariableNameSuggestions2(_singleExpre
ssion.staticType, _singleExpression, excluded); | 321 getVariableNameSuggestionsForExpression( |
| 320 // } else { | 322 singleExpression.staticType, |
| 321 // _guessedNames = ArrayUtils.EMPTY_STRING_ARRAY; | 323 singleExpression, |
| 322 // } | 324 excludedVariableNames)); |
| 325 } |
| 323 } | 326 } |
| 324 | 327 |
| 325 /** | 328 /** |
| 326 * @return all occurrences of the source which matches given selection, sorted
by offset. First | 329 * Prepares all occurrences of the source which matches given selection, |
| 327 * [SourceRange] is same as the given selection. May be empty, but not | 330 * sorted by offsets. |
| 328 * <code>null</code>. | |
| 329 */ | 331 */ |
| 330 List<SourceRange> _prepareOccurrences() { | 332 void _prepareOccurrences() { |
| 333 occurrences.clear(); |
| 331 // prepare selection | 334 // prepare selection |
| 332 String selectionSource; | 335 String selectionSource; |
| 333 { | 336 { |
| 334 String rawSelectionSource = utils.getRangeText(selectionRange); | 337 String rawSelectionSource = utils.getRangeText(selectionRange); |
| 335 List<Token> selectionTokens = TokenUtils.getTokens(rawSelectionSource); | 338 List<Token> selectionTokens = TokenUtils.getTokens(rawSelectionSource); |
| 336 selectionSource = selectionTokens.join(_TOKEN_SEPARATOR); | 339 selectionSource = selectionTokens.join(_TOKEN_SEPARATOR); |
| 337 } | 340 } |
| 338 // prepare enclosing function | 341 // prepare enclosing function |
| 339 AstNode enclosingFunction; | 342 AstNode enclosingFunction; |
| 340 { | 343 { |
| 341 AstNode selectionNode = | 344 AstNode selectionNode = |
| 342 new NodeLocator.con1(selectionOffset).searchWithin(unit); | 345 new NodeLocator.con1(selectionOffset).searchWithin(unit); |
| 343 enclosingFunction = getEnclosingExecutableNode(selectionNode); | 346 enclosingFunction = getEnclosingExecutableNode(selectionNode); |
| 344 } | 347 } |
| 345 // visit function | 348 // visit function |
| 346 enclosingFunction.accept( | 349 enclosingFunction.accept( |
| 347 new _OccurrencesVisitor(this, occurrences, selectionSource)); | 350 new _OccurrencesVisitor(this, occurrences, selectionSource)); |
| 348 // done | 351 } |
| 349 return occurrences; | 352 |
| 353 void _prepareOffsetsLengths() { |
| 354 offsets.clear(); |
| 355 lengths.clear(); |
| 356 for (SourceRange occurrence in occurrences) { |
| 357 offsets.add(occurrence.offset); |
| 358 lengths.add(occurrence.length); |
| 359 } |
| 350 } | 360 } |
| 351 } | 361 } |
| 352 | 362 |
| 353 | 363 |
| 354 /** | 364 /** |
| 355 * [SelectionAnalyzer] for [ExtractLocalRefactoringImpl]. | 365 * [SelectionAnalyzer] for [ExtractLocalRefactoringImpl]. |
| 356 */ | 366 */ |
| 357 class _ExtractExpressionAnalyzer extends SelectionAnalyzer { | 367 class _ExtractExpressionAnalyzer extends SelectionAnalyzer { |
| 358 final RefactoringStatus status = new RefactoringStatus(); | 368 final RefactoringStatus status = new RefactoringStatus(); |
| 359 | 369 |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 404 } | 414 } |
| 405 | 415 |
| 406 /** | 416 /** |
| 407 * Records fatal error with given message and [RefactoringStatusContext]. | 417 * Records fatal error with given message and [RefactoringStatusContext]. |
| 408 */ | 418 */ |
| 409 void _invalidSelection(String message, RefactoringStatusContext context) { | 419 void _invalidSelection(String message, RefactoringStatusContext context) { |
| 410 status.addFatalError(message, context); | 420 status.addFatalError(message, context); |
| 411 reset(); | 421 reset(); |
| 412 } | 422 } |
| 413 | 423 |
| 414 bool _isFirstSelectedNode(AstNode node) => identical(firstSelectedNode, node); | 424 bool _isFirstSelectedNode(AstNode node) => node == firstSelectedNode; |
| 415 } | 425 } |
| 416 | 426 |
| 417 | 427 |
| 418 class _HasStatementVisitor extends GeneralizingAstVisitor { | 428 class _HasStatementVisitor extends GeneralizingAstVisitor { |
| 419 final List<bool> result; | 429 final List<bool> result; |
| 420 | 430 |
| 421 _HasStatementVisitor(this.result); | 431 _HasStatementVisitor(this.result); |
| 422 | 432 |
| 423 @override | 433 @override |
| 424 visitStatement(Statement node) { | 434 visitStatement(Statement node) { |
| 425 result[0] = true; | 435 result[0] = true; |
| 426 } | 436 } |
| 427 } | 437 } |
| 428 | 438 |
| 429 | 439 |
| 430 class _OccurrencesVisitor extends GeneralizingAstVisitor<Object> { | 440 class _OccurrencesVisitor extends GeneralizingAstVisitor<Object> { |
| 431 final ExtractLocalRefactoringImpl ref; | 441 final ExtractLocalRefactoringImpl ref; |
| 432 | 442 final List<SourceRange> occurrences; |
| 433 List<SourceRange> occurrences; | 443 final String selectionSource; |
| 434 | |
| 435 String selectionSource; | |
| 436 | 444 |
| 437 _OccurrencesVisitor(this.ref, this.occurrences, this.selectionSource); | 445 _OccurrencesVisitor(this.ref, this.occurrences, this.selectionSource); |
| 438 | 446 |
| 439 @override | 447 @override |
| 440 Object visitBinaryExpression(BinaryExpression node) { | 448 Object visitBinaryExpression(BinaryExpression node) { |
| 441 if (!_hasStatements(node)) { | 449 if (!_hasStatements(node)) { |
| 442 _tryToFindOccurrenceFragment(node); | 450 _tryToFindOccurrenceFragment(node); |
| 443 return null; | 451 return null; |
| 444 } | 452 } |
| 445 return super.visitBinaryExpression(node); | 453 return super.visitBinaryExpression(node); |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 521 Token startToken = nodeTokens[startTokenIndex]; | 529 Token startToken = nodeTokens[startTokenIndex]; |
| 522 Token endToken = nodeTokens[endTokenIndex]; | 530 Token endToken = nodeTokens[endTokenIndex]; |
| 523 // add occurrence range | 531 // add occurrence range |
| 524 int occuStart = nodeOffset + startToken.offset; | 532 int occuStart = nodeOffset + startToken.offset; |
| 525 int occuEnd = nodeOffset + endToken.end; | 533 int occuEnd = nodeOffset + endToken.end; |
| 526 SourceRange occuRange = rangeStartEnd(occuStart, occuEnd); | 534 SourceRange occuRange = rangeStartEnd(occuStart, occuEnd); |
| 527 _addOccurrence(occuRange); | 535 _addOccurrence(occuRange); |
| 528 } | 536 } |
| 529 } | 537 } |
| 530 } | 538 } |
| OLD | NEW |