Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(89)

Side by Side Diff: pkg/analysis_server/lib/src/services/refactoring/extract_method.dart

Issue 498763003: Initial 'Extract Method' refactoring implementation. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698