| Index: pkg/analysis_server/lib/src/services/refactoring/extract_method.dart
|
| diff --git a/pkg/analysis_server/lib/src/services/refactoring/extract_method.dart b/pkg/analysis_server/lib/src/services/refactoring/extract_method.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..030e933b4f5f47b6f3a530e8a4d63aa39489e658
|
| --- /dev/null
|
| +++ b/pkg/analysis_server/lib/src/services/refactoring/extract_method.dart
|
| @@ -0,0 +1,1007 @@
|
| +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
|
| +// for details. All rights reserved. Use of this source code is governed by a
|
| +// BSD-style license that can be found in the LICENSE file.
|
| +
|
| +library services.src.refactoring.extract_method;
|
| +
|
| +import 'dart:async';
|
| +
|
| +import 'package:analysis_server/src/protocol2.dart' show Location,
|
| + RefactoringMethodParameter, RefactoringMethodParameterKind, SourceChange,
|
| + SourceEdit;
|
| +import 'package:analysis_server/src/services/correction/selection_analyzer.dart';
|
| +import 'package:analysis_server/src/services/correction/source_range.dart';
|
| +import 'package:analysis_server/src/services/correction/statement_analyzer.dart';
|
| +import 'package:analysis_server/src/services/correction/status.dart';
|
| +import 'package:analysis_server/src/services/correction/util.dart';
|
| +import 'package:analysis_server/src/services/refactoring/naming_conventions.dart';
|
| +import 'package:analysis_server/src/services/refactoring/refactoring.dart';
|
| +import 'package:analysis_server/src/services/refactoring/refactoring_internal.dart';
|
| +import 'package:analysis_server/src/services/search/search_engine.dart';
|
| +import 'package:analyzer/src/generated/ast.dart';
|
| +import 'package:analyzer/src/generated/element.dart';
|
| +import 'package:analyzer/src/generated/java_core.dart';
|
| +import 'package:analyzer/src/generated/scanner.dart';
|
| +import 'package:analyzer/src/generated/source.dart';
|
| +
|
| +
|
| +const String _TOKEN_SEPARATOR = "\uFFFF";
|
| +
|
| +
|
| +/**
|
| + * Returns the "normalized" version of the given source, which is reconstructed
|
| + * from tokens, so ignores all the comments and spaces.
|
| + */
|
| +String _getNormalizedSource(String src) {
|
| + List<Token> selectionTokens = TokenUtils.getTokens(src);
|
| + return StringUtils.join(selectionTokens, _TOKEN_SEPARATOR);
|
| +}
|
| +
|
| +
|
| +/**
|
| + * Returns the [Map] which maps [map] values to their keys.
|
| + */
|
| +Map<String, String> _inverseMap(Map map) {
|
| + Map result = {};
|
| + map.forEach((key, value) {
|
| + result[value] = key;
|
| + });
|
| + return result;
|
| +}
|
| +
|
| +
|
| +/**
|
| + * [ExtractMethodRefactoring] implementation.
|
| + */
|
| +class ExtractMethodRefactoringImpl extends RefactoringImpl implements
|
| + ExtractMethodRefactoring {
|
| + final SearchEngine searchEngine;
|
| + final CompilationUnit unit;
|
| + final int selectionOffset;
|
| + final int selectionLength;
|
| + String file;
|
| + SourceRange selectionRange;
|
| + CorrectionUtils utils;
|
| +
|
| + String returnType;
|
| + String name;
|
| + bool extractAll = true;
|
| + bool createGetter = false;
|
| + final List<String> names = <String>[];
|
| + final List<int> offsets = <int>[];
|
| + final List<int> lengths = <int>[];
|
| +
|
| + Set<String> _usedNames = new Set<String>();
|
| + List<RefactoringMethodParameter> _parameters = <RefactoringMethodParameter>[];
|
| + Map<String, RefactoringMethodParameter> _parametersMap = <String,
|
| + RefactoringMethodParameter>{};
|
| + Map<String, List<SourceRange>> _parameterReferencesMap = <String,
|
| + List<SourceRange>>{};
|
| + DartType _returnType;
|
| + String _returnVariableName;
|
| + AstNode _parentMember;
|
| + Expression _selectionExpression;
|
| + FunctionExpression _selectionFunctionExpression;
|
| + List<Statement> _selectionStatements;
|
| + List<_Occurrence> _occurrences = [];
|
| + bool _staticContext = false;
|
| +
|
| + ExtractMethodRefactoringImpl(this.searchEngine, this.unit,
|
| + this.selectionOffset, this.selectionLength) {
|
| + file = unit.element.source.fullName;
|
| + selectionRange = new SourceRange(selectionOffset, selectionLength);
|
| + utils = new CorrectionUtils(unit);
|
| + }
|
| +
|
| + bool get canCreateGetter {
|
| + if (!parameters.isEmpty) {
|
| + return false;
|
| + }
|
| + if (_selectionExpression != null) {
|
| + if (_selectionExpression is AssignmentExpression) {
|
| + return false;
|
| + }
|
| + }
|
| + if (_selectionStatements != null) {
|
| + return returnType != null;
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * @return the selected [DartExpression] source, with applying new parameter names.
|
| + */
|
| + String get methodBodySource {
|
| + String source = utils.getRangeText(selectionRange);
|
| + // prepare ReplaceEdit operations to replace variables with parameters
|
| + // TODO: implement parameters
|
| + List<SourceEdit> replaceEdits = [];
|
| +// for (Parameter parameter in _parametersMap.values) {
|
| +// List<SourceRange> ranges = _parameterReferencesMap[parameter.oldName];
|
| +// if (ranges != null) {
|
| +// for (SourceRange range in ranges) {
|
| +// replaceEdits.add(new SourceEdit(range.offset - selectionRange.offset, range.length, parameter.newName));
|
| +// }
|
| +// }
|
| +// }
|
| + // apply replacements
|
| + source = SourceEdit.applySequence(source, replaceEdits);
|
| + // change indentation
|
| + if (_selectionFunctionExpression != null) {
|
| + AstNode baseNode =
|
| + _selectionFunctionExpression.getAncestor((node) => node is Statement);
|
| + if (baseNode != null) {
|
| + String baseIndent = utils.getNodePrefix(baseNode);
|
| + String targetIndent = utils.getNodePrefix(_parentMember);
|
| + source = utils.replaceSourceIndent(source, baseIndent, targetIndent);
|
| + source = source.trim();
|
| + }
|
| + }
|
| + if (_selectionStatements != null) {
|
| + String selectionIndent = utils.getNodePrefix(_selectionStatements[0]);
|
| + String targetIndent = utils.getNodePrefix(_parentMember) + ' ';
|
| + source = utils.replaceSourceIndent(source, selectionIndent, targetIndent);
|
| + }
|
| + // done
|
| + return source;
|
| + }
|
| +
|
| + @override
|
| + List<RefactoringMethodParameter> get parameters => _parameters;
|
| +
|
| + @override
|
| + void set parameters(List<RefactoringMethodParameter> parameters) {
|
| + // TODO: implement parameters
|
| + }
|
| +
|
| + @override
|
| + String get refactoringName {
|
| + AstNode node = new NodeLocator.con1(selectionOffset).searchWithin(unit);
|
| + if (node != null &&
|
| + node.getAncestor((node) => node is ClassDeclaration) != null) {
|
| + return 'Extract Method';
|
| + }
|
| + return 'Extract Function';
|
| + }
|
| +
|
| + String get signature {
|
| + StringBuffer sb = new StringBuffer();
|
| + if (createGetter) {
|
| + sb.write("get ");
|
| + sb.write(name);
|
| + } else {
|
| + sb.write(name);
|
| + sb.write("(");
|
| + // add all parameters
|
| + bool firstParameter = true;
|
| + for (RefactoringMethodParameter parameter in _parameters) {
|
| + // may be comma
|
| + if (firstParameter) {
|
| + firstParameter = false;
|
| + } else {
|
| + sb.write(', ');
|
| + }
|
| + // type
|
| + {
|
| + String typeSource = parameter.type;
|
| + if ('dynamic' != typeSource && '' != typeSource) {
|
| + sb.write(typeSource);
|
| + sb.write(" ");
|
| + }
|
| + }
|
| + // name
|
| + sb.write(parameter.name);
|
| + }
|
| + sb.write(")");
|
| + }
|
| + // done
|
| + return sb.toString();
|
| + }
|
| +
|
| + @override
|
| + Future<RefactoringStatus> checkFinalConditions() {
|
| + RefactoringStatus result = new RefactoringStatus();
|
| + return new Future.value(result);
|
| + // TODO: implement checkFinalConditions
|
| + }
|
| +
|
| + @override
|
| + Future<RefactoringStatus> checkInitialConditions() {
|
| + RefactoringStatus result = new RefactoringStatus();
|
| + // selection
|
| + result.addStatus(_checkSelection());
|
| + if (result.hasFatalError) {
|
| + return new Future.value(result);
|
| + }
|
| + // prepare parts
|
| + result.addStatus(_initializeParameters());
|
| + _initializeReturnType();
|
| + _initializeOccurrences();
|
| + _initializeGetter();
|
| + // closure cannot have parameters
|
| + if (_selectionFunctionExpression != null && !_parameters.isEmpty) {
|
| + String message = format(
|
| + 'Cannot extract closure as method, it references {0} external variable(s).',
|
| + _parameters.length);
|
| + RefactoringStatus result = new RefactoringStatus.fatal(message);
|
| + return new Future.value(result);
|
| + }
|
| + return new Future.value(result);
|
| + }
|
| +
|
| + @override
|
| + RefactoringStatus checkName() {
|
| + return validateMethodName(name);
|
| + }
|
| +
|
| + @override
|
| + Future<SourceChange> createChange() {
|
| + SourceChange change = new SourceChange(refactoringName);
|
| + // replace occurrences with method invocation
|
| + for (_Occurrence occurence in _occurrences) {
|
| + SourceRange range = occurence.range;
|
| + // may be replacement of duplicates disabled
|
| + if (!extractAll && !occurence.isSelection) {
|
| + continue;
|
| + }
|
| + // prepare invocation source
|
| + String invocationSource;
|
| + if (_selectionFunctionExpression != null) {
|
| + invocationSource = name;
|
| + } else {
|
| + StringBuffer sb = new StringBuffer();
|
| + // may be returns value
|
| + if (returnType != null) {
|
| + // single variable assignment / return statement
|
| + if (_returnVariableName != null) {
|
| + String occurrenceName =
|
| + occurence._parameterOldToOccurrenceName[_returnVariableName];
|
| + // may be declare variable
|
| + if (!_parametersMap.containsKey(_returnVariableName)) {
|
| + if (returnType.isEmpty) {
|
| + sb.write('var ');
|
| + } else {
|
| + sb.write(returnType);
|
| + sb.write(' ');
|
| + }
|
| + }
|
| + // assign the return value
|
| + sb.write(occurrenceName);
|
| + sb.write(' = ');
|
| + } else {
|
| + sb.write('return ');
|
| + }
|
| + }
|
| + // invocation itself
|
| + sb.write(name);
|
| + if (!createGetter) {
|
| + sb.write("(");
|
| + bool firstParameter = true;
|
| + for (RefactoringMethodParameter parameter in _parameters) {
|
| + // may be comma
|
| + if (firstParameter) {
|
| + firstParameter = false;
|
| + } else {
|
| + sb.write(', ');
|
| + }
|
| + // argument name
|
| + {
|
| + String argumentName =
|
| + occurence._parameterOldToOccurrenceName[parameter.id];
|
| + sb.write(argumentName);
|
| + }
|
| + }
|
| + sb.write(')');
|
| + }
|
| + invocationSource = sb.toString();
|
| + // statements as extracted with their ";", so add new after invocation
|
| + if (_selectionStatements != null) {
|
| + invocationSource += ';';
|
| + }
|
| + }
|
| + // add replace edit
|
| + SourceEdit edit = new SourceEdit.range(range, invocationSource);
|
| + change.addEdit(file, edit);
|
| + }
|
| + // add method declaration
|
| + {
|
| + // prepare environment
|
| + String prefix = utils.getNodePrefix(_parentMember);
|
| + String eol = utils.endOfLine;
|
| + // prepare annotations
|
| + String annotations = "";
|
| + {
|
| + // may be "static"
|
| + if (_staticContext) {
|
| + annotations = 'static ';
|
| + }
|
| + }
|
| + // prepare declaration source
|
| + String declarationSource = null;
|
| + {
|
| + String returnExpressionSource = methodBodySource;
|
| + // closure
|
| + if (_selectionFunctionExpression != null) {
|
| + declarationSource = "${name}${returnExpressionSource}";
|
| + if (_selectionFunctionExpression.body is ExpressionFunctionBody) {
|
| + declarationSource += ';';
|
| + }
|
| + }
|
| + // expression
|
| + if (_selectionExpression != null) {
|
| + // add return type
|
| + String returnTypeName =
|
| + utils.getExpressionTypeSource(_selectionExpression);
|
| + if (returnTypeName != null && returnTypeName != "dynamic") {
|
| + annotations += "${returnTypeName} ";
|
| + }
|
| + // just return expression
|
| + declarationSource =
|
| + "${annotations}${signature} => ${returnExpressionSource};";
|
| + }
|
| + // statements
|
| + if (_selectionStatements != null) {
|
| + if (returnType != null) {
|
| + if (returnType.isNotEmpty) {
|
| + annotations += returnType + ' ';
|
| + }
|
| + } else {
|
| + annotations += 'void ';
|
| + }
|
| + declarationSource = "${annotations}${signature} {${eol}";
|
| + declarationSource += returnExpressionSource;
|
| + if (_returnVariableName != null) {
|
| + declarationSource +=
|
| + '${prefix} return ${_returnVariableName};$eol';
|
| + }
|
| + declarationSource += '${prefix}}';
|
| + }
|
| + }
|
| + // insert declaration
|
| + if (declarationSource != null) {
|
| + int offset = _parentMember.end;
|
| + SourceEdit edit =
|
| + new SourceEdit(offset, 0, '${eol}${eol}${prefix}${declarationSource}');
|
| + change.addEdit(file, edit);
|
| + }
|
| + }
|
| + // done
|
| + return new Future.value(change);
|
| + }
|
| +
|
| + @override
|
| + bool requiresPreview() => false;
|
| +
|
| + /**
|
| + * Adds a new reference to the parameter with the given name.
|
| + */
|
| + void _addParameterReference(String name, SourceRange range) {
|
| + List<SourceRange> references = _parameterReferencesMap[name];
|
| + if (references == null) {
|
| + references = [];
|
| + _parameterReferencesMap[name] = references;
|
| + }
|
| + references.add(range);
|
| + }
|
| +
|
| + /**
|
| + * Checks if [selectionRange] selects [Expression] which can be extracted, and
|
| + * location of this [DartExpression] in AST allows extracting.
|
| + */
|
| + RefactoringStatus _checkSelection() {
|
| + _ExtractMethodAnalyzer selectionAnalyzer =
|
| + new _ExtractMethodAnalyzer(unit, selectionRange);
|
| + unit.accept(selectionAnalyzer);
|
| + // may be fatal error
|
| + {
|
| + RefactoringStatus status = selectionAnalyzer.status;
|
| + if (status.hasFatalError) {
|
| + return status;
|
| + }
|
| + }
|
| + // check selected nodes
|
| + List<AstNode> selectedNodes = selectionAnalyzer.selectedNodes;
|
| + if (!selectedNodes.isEmpty) {
|
| + AstNode coveringNode = selectionAnalyzer.coveringNode;
|
| + _parentMember = getEnclosingClassOrUnitMember(coveringNode);
|
| + // single expression selected
|
| + if (selectedNodes.length == 1 &&
|
| + !utils.selectionIncludesNonWhitespaceOutsideNode(
|
| + selectionRange,
|
| + selectionAnalyzer.firstSelectedNode)) {
|
| + AstNode selectedNode = selectionAnalyzer.firstSelectedNode;
|
| + if (selectedNode is Expression) {
|
| + _selectionExpression = selectedNode;
|
| + // additional check for closure
|
| + if (_selectionExpression is FunctionExpression) {
|
| + _selectionFunctionExpression =
|
| + _selectionExpression as FunctionExpression;
|
| + _selectionExpression = null;
|
| + }
|
| + // OK
|
| + return new RefactoringStatus();
|
| + }
|
| + }
|
| + // statements selected
|
| + {
|
| + List<Statement> selectedStatements = [];
|
| + for (AstNode selectedNode in selectedNodes) {
|
| + if (selectedNode is Statement) {
|
| + selectedStatements.add(selectedNode);
|
| + }
|
| + }
|
| + if (selectedStatements.length == selectedNodes.length) {
|
| + _selectionStatements = selectedStatements;
|
| + return new RefactoringStatus();
|
| + }
|
| + }
|
| + }
|
| + // invalid selection
|
| + return new RefactoringStatus.fatal(
|
| + 'Can only extract a single expression or a set of statements.');
|
| + }
|
| +
|
| + _SourcePattern _getSourcePattern(SourceRange range) {
|
| + String originalSource = utils.getText(range.offset, range.length);
|
| + _SourcePattern pattern = new _SourcePattern();
|
| + List<SourceEdit> replaceEdits = <SourceEdit>[];
|
| + unit.accept(new _GetSourcePatternVisitor(range, pattern, replaceEdits));
|
| + replaceEdits = replaceEdits.reversed.toList();
|
| + pattern.patternSource =
|
| + SourceEdit.applySequence(originalSource, replaceEdits);
|
| + return pattern;
|
| + }
|
| +
|
| + /**
|
| + * Initializes [createGetter] flag.
|
| + */
|
| + void _initializeGetter() {
|
| + createGetter = false;
|
| + // maybe we cannot at all
|
| + if (!canCreateGetter) {
|
| + return;
|
| + }
|
| + // OK, just expression
|
| + if (_selectionExpression != null) {
|
| + createGetter = !_hasMethodInvocation(_selectionExpression);
|
| + return;
|
| + }
|
| + // allow code blocks without cycles
|
| + if (_selectionStatements != null) {
|
| + createGetter = true;
|
| + for (Statement statement in _selectionStatements) {
|
| + // method invocation is something heavy,
|
| + // so we don't want to extract it as a part of a getter
|
| + if (_hasMethodInvocation(statement)) {
|
| + createGetter = false;
|
| + return;
|
| + }
|
| + // don't allow cycles
|
| + statement.accept(new _ResetCanCreateGetterVisitor(this));
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Fills [_occurrences] field.
|
| + */
|
| + void _initializeOccurrences() {
|
| + // prepare selection
|
| + _SourcePattern selectionPattern = _getSourcePattern(selectionRange);
|
| + String selectionSource =
|
| + _getNormalizedSource(selectionPattern.patternSource);
|
| + Map<String, String> patternToSelectionName =
|
| + _inverseMap(selectionPattern.originalToPatternNames);
|
| + // prepare an enclosing parent - class or unit
|
| + AstNode enclosingMemberParent = _parentMember.parent;
|
| + // visit nodes which will able to access extracted method
|
| + enclosingMemberParent.accept(
|
| + new _InitializeOccurrencesVisitor(
|
| + this,
|
| + selectionSource,
|
| + patternToSelectionName));
|
| + }
|
| +
|
| + /**
|
| + * Prepares information about used variables, which should be turned into
|
| + * parameters.
|
| + */
|
| + RefactoringStatus _initializeParameters() {
|
| + _parameters.clear();
|
| + _parametersMap.clear();
|
| + _parameterReferencesMap.clear();
|
| + RefactoringStatus result = new RefactoringStatus();
|
| + List<VariableElement> assignedUsedVariables = [];
|
| + unit.accept(new _InitializeParametersVisitor(this, assignedUsedVariables));
|
| + // may be ends with "return" statement
|
| + if (_selectionStatements != null) {
|
| + Statement lastStatement =
|
| + _selectionStatements[_selectionStatements.length - 1];
|
| + if (lastStatement is ReturnStatement) {
|
| + Expression expression = lastStatement.expression;
|
| + if (expression != null) {
|
| + _returnType = expression.bestType;
|
| + }
|
| + }
|
| + }
|
| + // may be single variable to return
|
| + if (assignedUsedVariables.length == 1) {
|
| + // we cannot both return variable and have explicit return statement
|
| + if (_returnType != null) {
|
| + result.addFatalError(
|
| + "Ambiguous return value: Selected block contains assignment(s) to "
|
| + "local variables and return statement.");
|
| + return result;
|
| + }
|
| + // prepare to return an assigned variable
|
| + VariableElement returnVariable = assignedUsedVariables[0];
|
| + _returnType = returnVariable.type;
|
| + _returnVariableName = returnVariable.displayName;
|
| + }
|
| + // fatal, if multiple variables assigned and used after selection
|
| + if (assignedUsedVariables.length > 1) {
|
| + StringBuffer sb = new StringBuffer();
|
| + for (VariableElement variable in assignedUsedVariables) {
|
| + sb.write(variable.displayName);
|
| + sb.write("\n");
|
| + }
|
| + result.addFatalError(
|
| + format(
|
| + "Ambiguous return value: Selected block contains more than one "
|
| + "assignment to local variables. Affected variables are:\n\n{0}",
|
| + sb.toString().trim()));
|
| + }
|
| + // done
|
| + return result;
|
| + }
|
| +
|
| + void _initializeReturnType() {
|
| + if (_returnType == null) {
|
| + returnType = null;
|
| + } else {
|
| + returnType = utils.getTypeSource(_returnType);
|
| + if (returnType == 'dynamic') {
|
| + returnType = '';
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Checks if the given [VariableElement] is declared in [selectionRange].
|
| + */
|
| + bool _isDeclaredInSelection(VariableElement element) {
|
| + return selectionRange.contains(element.nameOffset);
|
| + }
|
| +
|
| + /**
|
| + * Checks if it is OK to extract the node with the given [SourceRange].
|
| + */
|
| + bool _isExtractable(SourceRange range) {
|
| + _ExtractMethodAnalyzer analyzer = new _ExtractMethodAnalyzer(unit, range);
|
| + utils.unit.accept(analyzer);
|
| + return analyzer.status.isOK;
|
| + }
|
| +
|
| + /**
|
| + * Checks if [element] is referenced after [selectionRange].
|
| + */
|
| + bool _isUsedAfterSelection(VariableElement element) {
|
| + var visitor = new _IsUsedAfterSelectionVisitor(this, element);
|
| + _parentMember.accept(visitor);
|
| + return visitor.result;
|
| + }
|
| +
|
| + /**
|
| + * Checks if [node] has a [MethodInvocation].
|
| + */
|
| + static bool _hasMethodInvocation(AstNode node) {
|
| + var visitor = new _HasMethodInvocationVisitor();
|
| + node.accept(visitor);
|
| + return visitor.result;
|
| + }
|
| +}
|
| +
|
| +
|
| +/**
|
| + * [SelectionAnalyzer] for [ExtractMethodRefactoringImpl].
|
| + */
|
| +class _ExtractMethodAnalyzer extends StatementAnalyzer {
|
| + _ExtractMethodAnalyzer(CompilationUnit unit, SourceRange selection)
|
| + : super(unit, selection);
|
| +
|
| + @override
|
| + void handleNextSelectedNode(AstNode node) {
|
| + super.handleNextSelectedNode(node);
|
| + _checkParent(node);
|
| + }
|
| +
|
| + @override
|
| + void handleSelectionEndsIn(AstNode node) {
|
| + super.handleSelectionEndsIn(node);
|
| + invalidSelection(
|
| + "The selection does not cover a set of statements or an expression. "
|
| + "Extend selection to a valid range.");
|
| + }
|
| +
|
| + @override
|
| + Object visitAssignmentExpression(AssignmentExpression node) {
|
| + super.visitAssignmentExpression(node);
|
| + Expression lhs = node.leftHandSide;
|
| + if (_isFirstSelectedNode(lhs)) {
|
| + invalidSelection(
|
| + 'Cannot extract the left-hand side of an assignment.',
|
| + new Location.fromNode(lhs));
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + @override
|
| + Object visitConstructorInitializer(ConstructorInitializer node) {
|
| + super.visitConstructorInitializer(node);
|
| + if (_isFirstSelectedNode(node)) {
|
| + invalidSelection(
|
| + 'Cannot extract a constructor initializer. '
|
| + 'Select expression part of initializer.',
|
| + new Location.fromNode(node));
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + @override
|
| + Object visitForStatement(ForStatement node) {
|
| + super.visitForStatement(node);
|
| + if (identical(node.variables, firstSelectedNode)) {
|
| + invalidSelection(
|
| + "Cannot extract initialization part of a 'for' statement.");
|
| + } else if (node.updaters.contains(lastSelectedNode)) {
|
| + invalidSelection("Cannot extract increment part of a 'for' statement.");
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + @override
|
| + Object visitSimpleIdentifier(SimpleIdentifier node) {
|
| + super.visitSimpleIdentifier(node);
|
| + if (_isFirstSelectedNode(node)) {
|
| + // name of declaration
|
| + if (node.inDeclarationContext()) {
|
| + invalidSelection("Cannot extract the name part of a declaration.");
|
| + }
|
| + // method name
|
| + Element element = node.bestElement;
|
| + if (element is FunctionElement || element is MethodElement) {
|
| + invalidSelection("Cannot extract a single method name.");
|
| + }
|
| + // name in property access
|
| + if (node.parent is PrefixedIdentifier &&
|
| + (node.parent as PrefixedIdentifier).identifier == node) {
|
| + invalidSelection("Can not extract name part of a property access.");
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + @override
|
| + Object visitTypeName(TypeName node) {
|
| + super.visitTypeName(node);
|
| + if (_isFirstSelectedNode(node)) {
|
| + invalidSelection("Cannot extract a single type reference.");
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + @override
|
| + Object visitVariableDeclaration(VariableDeclaration node) {
|
| + super.visitVariableDeclaration(node);
|
| + if (_isFirstSelectedNode(node)) {
|
| + invalidSelection(
|
| + "Cannot extract a variable declaration fragment. "
|
| + "Select whole declaration statement.",
|
| + new Location.fromNode(node));
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + void _checkParent(AstNode node) {
|
| + AstNode firstParent = firstSelectedNode.parent;
|
| + do {
|
| + node = node.parent;
|
| + if (identical(node, firstParent)) {
|
| + return;
|
| + }
|
| + } while (node != null);
|
| + invalidSelection(
|
| + "Not all selected statements are enclosed by the same parent statement.");
|
| + }
|
| +
|
| + bool _isFirstSelectedNode(AstNode node) => identical(firstSelectedNode, node);
|
| +}
|
| +
|
| +
|
| +class _GetSourcePatternVisitor extends GeneralizingAstVisitor {
|
| + final SourceRange partRange;
|
| + final _SourcePattern pattern;
|
| + final List<SourceEdit> replaceEdits;
|
| +
|
| + _GetSourcePatternVisitor(this.partRange, this.pattern, this.replaceEdits);
|
| +
|
| + @override
|
| + visitSimpleIdentifier(SimpleIdentifier node) {
|
| + SourceRange nodeRange = rangeNode(node);
|
| + if (partRange.covers(nodeRange)) {
|
| + VariableElement variableElement =
|
| + getLocalOrParameterVariableElement(node);
|
| + if (variableElement != null) {
|
| + // name of a named expression
|
| + if (isNamedExpressionName(node)) {
|
| + return;
|
| + }
|
| + // continue
|
| + String originalName = variableElement.displayName;
|
| + String patternName = pattern.originalToPatternNames[originalName];
|
| + if (patternName == null) {
|
| + patternName = '__refVar${pattern.originalToPatternNames.length}';
|
| + pattern.originalToPatternNames[originalName] = patternName;
|
| + }
|
| + replaceEdits.add(
|
| + new SourceEdit(
|
| + nodeRange.offset - partRange.offset,
|
| + nodeRange.length,
|
| + patternName));
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +
|
| +
|
| +class _HasMethodInvocationVisitor extends RecursiveAstVisitor {
|
| + bool result = false;
|
| +
|
| + @override
|
| + visitMethodInvocation(MethodInvocation node) {
|
| + result = true;
|
| + }
|
| +}
|
| +
|
| +
|
| +class _InitializeOccurrencesVisitor extends GeneralizingAstVisitor<Object> {
|
| + final ExtractMethodRefactoringImpl ref;
|
| + final String selectionSource;
|
| + final Map<String, String> patternToSelectionName;
|
| +
|
| + bool forceStatic = false;
|
| +
|
| + _InitializeOccurrencesVisitor(this.ref, this.selectionSource,
|
| + this.patternToSelectionName);
|
| +
|
| + @override
|
| + Object visitBlock(Block node) {
|
| + if (ref._selectionStatements != null) {
|
| + _visitStatements(node.statements);
|
| + }
|
| + return super.visitBlock(node);
|
| + }
|
| +
|
| + @override
|
| + Object visitConstructorInitializer(ConstructorInitializer node) {
|
| + forceStatic = true;
|
| + try {
|
| + return super.visitConstructorInitializer(node);
|
| + } finally {
|
| + forceStatic = false;
|
| + }
|
| + }
|
| +
|
| + @override
|
| + Object visitExpression(Expression node) {
|
| + if (ref._selectionFunctionExpression != null ||
|
| + ref._selectionExpression != null &&
|
| + node.runtimeType == ref._selectionExpression.runtimeType) {
|
| + SourceRange nodeRange = rangeNode(node);
|
| + _tryToFindOccurrence(nodeRange);
|
| + }
|
| + return super.visitExpression(node);
|
| + }
|
| +
|
| + @override
|
| + Object visitMethodDeclaration(MethodDeclaration node) {
|
| + forceStatic = node.isStatic;
|
| + try {
|
| + return super.visitMethodDeclaration(node);
|
| + } finally {
|
| + forceStatic = false;
|
| + }
|
| + }
|
| +
|
| + @override
|
| + Object visitSwitchMember(SwitchMember node) {
|
| + if (ref._selectionStatements != null) {
|
| + _visitStatements(node.statements);
|
| + }
|
| + return super.visitSwitchMember(node);
|
| + }
|
| +
|
| + /**
|
| + * Checks if given [SourceRange] matched selection source and adds [_Occurrence].
|
| + */
|
| + bool _tryToFindOccurrence(SourceRange nodeRange) {
|
| + // check if can be extracted
|
| + if (!ref._isExtractable(nodeRange)) {
|
| + return false;
|
| + }
|
| + // prepare normalized node source
|
| + _SourcePattern nodePattern = ref._getSourcePattern(nodeRange);
|
| + String nodeSource = _getNormalizedSource(nodePattern.patternSource);
|
| + // if matches normalized node source, then add as occurrence
|
| + if (nodeSource == selectionSource) {
|
| + _Occurrence occurrence =
|
| + new _Occurrence(nodeRange, ref.selectionRange.intersects(nodeRange));
|
| + ref._occurrences.add(occurrence);
|
| + // prepare mapping of parameter names to the occurrence variables
|
| + for (MapEntry<String, String> entry in getMapEntrySet(
|
| + nodePattern.originalToPatternNames)) {
|
| + String patternName = entry.getValue();
|
| + String originalName = entry.getKey();
|
| + String selectionName = patternToSelectionName[patternName];
|
| + occurrence._parameterOldToOccurrenceName[selectionName] = originalName;
|
| + }
|
| + // update static
|
| + if (forceStatic) {
|
| + ref._staticContext = true;
|
| + }
|
| + // we have match
|
| + return true;
|
| + }
|
| + // no match
|
| + return false;
|
| + }
|
| +
|
| + void _visitStatements(List<Statement> statements) {
|
| + int beginStatementIndex = 0;
|
| + int selectionCount = ref._selectionStatements.length;
|
| + while (beginStatementIndex + selectionCount <= statements.length) {
|
| + SourceRange nodeRange = rangeStartEnd(
|
| + statements[beginStatementIndex],
|
| + statements[beginStatementIndex + selectionCount - 1]);
|
| + bool found = _tryToFindOccurrence(nodeRange);
|
| + // next statement
|
| + if (found) {
|
| + beginStatementIndex += selectionCount;
|
| + } else {
|
| + beginStatementIndex++;
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +
|
| +class _InitializeParametersVisitor extends GeneralizingAstVisitor<Object> {
|
| + final ExtractMethodRefactoringImpl ref;
|
| + final List<VariableElement> assignedUsedVariables;
|
| +
|
| + _InitializeParametersVisitor(this.ref, this.assignedUsedVariables);
|
| +
|
| + @override
|
| + Object visitSimpleIdentifier(SimpleIdentifier node) {
|
| + SourceRange nodeRange = rangeNode(node);
|
| + if (ref.selectionRange.covers(nodeRange)) {
|
| + // analyze local variable
|
| + VariableElement variableElement =
|
| + getLocalOrParameterVariableElement(node);
|
| + if (variableElement != null) {
|
| + // name of the named expression
|
| + if (isNamedExpressionName(node)) {
|
| + return null;
|
| + }
|
| + // if declared outside, add parameter
|
| + if (!ref._isDeclaredInSelection(variableElement)) {
|
| + String variableName = variableElement.displayName;
|
| + // add parameter
|
| + RefactoringMethodParameter parameter =
|
| + ref._parametersMap[variableName];
|
| + if (parameter == null) {
|
| + DartType parameterType = node.bestType;
|
| + String parameterTypeName = ref.utils.getTypeSource(parameterType);
|
| + parameter = new RefactoringMethodParameter(
|
| + RefactoringMethodParameterKind.REQUIRED,
|
| + parameterTypeName,
|
| + variableName,
|
| + id: variableName);
|
| + ref._parameters.add(parameter);
|
| + ref._parametersMap[variableName] = parameter;
|
| + }
|
| + // add reference to parameter
|
| + ref._addParameterReference(variableName, nodeRange);
|
| + }
|
| + // remember, if assigned and used after selection
|
| + if (isLeftHandOfAssignment(node) &&
|
| + ref._isUsedAfterSelection(variableElement)) {
|
| + if (!assignedUsedVariables.contains(variableElement)) {
|
| + assignedUsedVariables.add(variableElement);
|
| + }
|
| + }
|
| + }
|
| + // remember declaration names
|
| + if (node.inDeclarationContext()) {
|
| + ref._usedNames.add(node.name);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +}
|
| +
|
| +class _IsUsedAfterSelectionVisitor extends GeneralizingAstVisitor {
|
| + final ExtractMethodRefactoringImpl ref;
|
| + final VariableElement element;
|
| + bool result = false;
|
| +
|
| + _IsUsedAfterSelectionVisitor(this.ref, this.element);
|
| +
|
| + @override
|
| + visitSimpleIdentifier(SimpleIdentifier node) {
|
| + VariableElement nodeElement = getLocalVariableElement(node);
|
| + if (identical(nodeElement, element)) {
|
| + int nodeOffset = node.offset;
|
| + if (nodeOffset > ref.selectionRange.end) {
|
| + result = true;
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +
|
| +/**
|
| + * Description of a single occurrence of the selected expression or set of
|
| + * statements.
|
| + */
|
| +class _Occurrence {
|
| + final SourceRange range;
|
| + final bool isSelection;
|
| +
|
| + Map<String, String> _parameterOldToOccurrenceName = <String, String>{};
|
| +
|
| + _Occurrence(this.range, this.isSelection);
|
| +}
|
| +
|
| +
|
| +class _ResetCanCreateGetterVisitor extends RecursiveAstVisitor {
|
| + final ExtractMethodRefactoringImpl ref;
|
| +
|
| + _ResetCanCreateGetterVisitor(this.ref);
|
| +
|
| + @override
|
| + visitDoStatement(DoStatement node) {
|
| + ref.createGetter = false;
|
| + super.visitDoStatement(node);
|
| + }
|
| +
|
| + @override
|
| + visitForEachStatement(ForEachStatement node) {
|
| + ref.createGetter = false;
|
| + super.visitForEachStatement(node);
|
| + }
|
| +
|
| + @override
|
| + visitForStatement(ForStatement node) {
|
| + ref.createGetter = false;
|
| + super.visitForStatement(node);
|
| + }
|
| +
|
| + @override
|
| + visitWhileStatement(WhileStatement node) {
|
| + ref.createGetter = false;
|
| + super.visitWhileStatement(node);
|
| + }
|
| +}
|
| +
|
| +
|
| +/**
|
| + * Generalized version of some source, in which references to the specific
|
| + * variables are replaced with pattern variables, with back mapping from the
|
| + * pattern to the original variable names.
|
| + */
|
| +class _SourcePattern {
|
| + String patternSource;
|
| + Map<String, String> originalToPatternNames = {};
|
| +}
|
|
|