| // 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; |
| } |
| } |