blob: eafbcaf8c2edd8efaaa3a9de25ced592c7afe83a [file] [log] [blame]
// 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.
import 'dart:async';
import 'package:analysis_server/src/protocol_server.dart' hide Element;
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/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/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/dart/element/ast_provider.dart';
import 'package:analyzer/src/generated/java_core.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer_plugin/utilities/range_factory.dart';
/**
* [InlineLocalRefactoring] implementation.
*/
class InlineLocalRefactoringImpl extends RefactoringImpl
implements InlineLocalRefactoring {
final SearchEngine searchEngine;
final AstProvider astProvider;
final ResolveResult resolveResult;
final int offset;
CorrectionUtils utils;
Element _variableElement;
VariableDeclaration _variableNode;
List<SearchMatch> _references;
InlineLocalRefactoringImpl(
this.searchEngine, this.astProvider, this.resolveResult, this.offset) {
utils =
new CorrectionUtils(resolveResult.unit, buffer: resolveResult.content);
}
@override
String get refactoringName => 'Inline Local Variable';
@override
int get referenceCount {
if (_references == null) {
return 0;
}
return _references.length;
}
@override
String get variableName {
if (_variableElement == null) {
return null;
}
return _variableElement.name;
}
@override
Future<RefactoringStatus> checkFinalConditions() {
RefactoringStatus result = new RefactoringStatus();
return new Future.value(result);
}
@override
Future<RefactoringStatus> checkInitialConditions() async {
// TODO(brianwilkerson) Determine whether this await is necessary.
await null;
RefactoringStatus result = new RefactoringStatus();
// prepare variable
{
AstNode offsetNode =
new NodeLocator(offset).searchWithin(resolveResult.unit);
if (offsetNode is SimpleIdentifier) {
Element element = offsetNode.staticElement;
if (element is LocalVariableElement) {
_variableElement = element;
AstNode name = await astProvider.getResolvedNameForElement(element);
_variableNode = name.parent as VariableDeclaration;
}
}
}
// validate node declaration
if (!_isVariableDeclaredInStatement()) {
result = new RefactoringStatus.fatal(
'Local variable declaration or reference must be selected '
'to activate this refactoring.');
return new Future<RefactoringStatus>.value(result);
}
// should have initializer at declaration
if (_variableNode.initializer == null) {
String message = format(
"Local variable '{0}' is not initialized at declaration.",
_variableElement.displayName);
result = new RefactoringStatus.fatal(
message, newLocation_fromNode(_variableNode));
return new Future<RefactoringStatus>.value(result);
}
// prepare references
_references = await searchEngine.searchReferences(_variableElement);
// should not have assignments
for (SearchMatch reference in _references) {
if (reference.kind != MatchKind.READ) {
String message = format(
"Local variable '{0}' is assigned more than once.",
[_variableElement.displayName]);
return new RefactoringStatus.fatal(
message, newLocation_fromMatch(reference));
}
}
// done
return result;
}
@override
Future<SourceChange> createChange() {
SourceChange change = new SourceChange(refactoringName);
// remove declaration
{
Statement declarationStatement = _variableNode
.getAncestor((node) => node is VariableDeclarationStatement);
SourceRange range = utils.getLinesRangeStatements([declarationStatement]);
doSourceChange_addElementEdit(change, resolveResult.unit.declaredElement,
newSourceEdit_range(range, ''));
}
// prepare initializer
Expression initializer = _variableNode.initializer;
String initializerCode = utils.getNodeText(initializer);
// replace references
for (SearchMatch reference in _references) {
SourceRange editRange = reference.sourceRange;
// prepare context
int offset = editRange.offset;
AstNode node = utils.findNode(offset);
AstNode parent = node.parent;
// prepare code
String codeForReference;
if (parent is InterpolationExpression) {
StringInterpolation target = parent.parent;
if (initializer is SingleStringLiteral &&
!initializer.isRaw &&
initializer.isSingleQuoted == target.isSingleQuoted &&
(!initializer.isMultiline || target.isMultiline)) {
editRange = range.node(parent);
// unwrap the literal being inlined
int initOffset = initializer.contentsOffset;
int initLength = initializer.contentsEnd - initOffset;
codeForReference = utils.getText(initOffset, initLength);
} else if (_shouldBeExpressionInterpolation(parent, initializer)) {
codeForReference = '{$initializerCode}';
} else {
codeForReference = initializerCode;
}
} else if (_shouldUseParenthesis(initializer, node)) {
codeForReference = '($initializerCode)';
} else {
codeForReference = initializerCode;
}
// do replace
doSourceChange_addElementEdit(change, resolveResult.unit.declaredElement,
newSourceEdit_range(editRange, codeForReference));
}
// done
return new Future.value(change);
}
bool _isVariableDeclaredInStatement() {
if (_variableNode == null) {
return false;
}
AstNode parent = _variableNode.parent;
if (parent is VariableDeclarationList) {
parent = parent.parent;
if (parent is VariableDeclarationStatement) {
parent = parent.parent;
return parent is Block || parent is SwitchCase;
}
}
return false;
}
static bool _shouldBeExpressionInterpolation(
InterpolationExpression target, Expression expression) {
TokenType targetType = target.beginToken.type;
return targetType == TokenType.STRING_INTERPOLATION_IDENTIFIER &&
expression is! SimpleIdentifier;
}
static bool _shouldUseParenthesis(Expression init, AstNode node) {
// check precedence
int initPrecedence = getExpressionPrecedence(init);
if (initPrecedence < getExpressionParentPrecedence(node)) {
return true;
}
// special case for '-'
AstNode parent = node.parent;
if (init is PrefixExpression && parent is PrefixExpression) {
if (parent.operator.type == TokenType.MINUS) {
TokenType initializerOperator = init.operator.type;
if (initializerOperator == TokenType.MINUS ||
initializerOperator == TokenType.MINUS_MINUS) {
return true;
}
}
}
// no () is needed
return false;
}
}