| // 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.inline_local; |
| |
| import 'dart:async'; |
| |
| import 'package:analysis_server/src/protocol.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/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"; |
| |
| |
| /** |
| * [InlineLocalRefactoring] implementation. |
| */ |
| class InlineLocalRefactoringImpl extends RefactoringImpl implements |
| InlineLocalRefactoring { |
| final SearchEngine searchEngine; |
| final CompilationUnit unit; |
| final LocalVariableElement element; |
| String file; |
| CorrectionUtils utils; |
| |
| VariableDeclaration _variableNode; |
| List<SearchMatch> _references; |
| |
| InlineLocalRefactoringImpl(this.searchEngine, this.unit, this.element) { |
| file = unit.element.source.fullName; |
| utils = new CorrectionUtils(unit); |
| } |
| |
| @override |
| String get refactoringName => 'Inline Local Variable'; |
| |
| @override |
| int get referenceCount { |
| return _references.length; |
| } |
| |
| @override |
| Future<RefactoringStatus> checkFinalConditions() { |
| RefactoringStatus result = new RefactoringStatus(); |
| return new Future.value(result); |
| } |
| |
| @override |
| Future<RefactoringStatus> checkInitialConditions() { |
| RefactoringStatus result = new RefactoringStatus(); |
| // prepare variable |
| { |
| AstNode elementNode = utils.findNode(element.nameOffset); |
| _variableNode = elementNode != null ? |
| elementNode.getAncestor((node) => node is VariableDeclaration) : |
| null; |
| } |
| // should be normal variable declaration statement |
| if (_variableNode.parent is! VariableDeclarationList || |
| _variableNode.parent.parent is! VariableDeclarationStatement || |
| _variableNode.parent.parent.parent is! Block) { |
| result = new RefactoringStatus.fatal( |
| 'Local variable declared in ' |
| 'statement should be selected to activate this refactoring.'); |
| return new Future.value(result); |
| } |
| // should have initializer at declaration |
| if (_variableNode.initializer == null) { |
| String message = format( |
| "Local variable '{0}' is not initialized at declaration.", |
| element.displayName); |
| result = |
| new RefactoringStatus.fatal(message, new Location.fromNode(_variableNode)); |
| return new Future.value(result); |
| } |
| // prepare references |
| return searchEngine.searchReferences(element).then((references) { |
| this._references = references; |
| // 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.", |
| [element.displayName]); |
| return new RefactoringStatus.fatal( |
| message, |
| new Location.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]); |
| change.addEdit(file, new SourceEdit.range(range, '')); |
| } |
| // prepare initializer |
| Expression initializer = _variableNode.initializer; |
| String initializerSource = utils.getNodeText(initializer); |
| int initializerPrecedence = getExpressionPrecedence(initializer); |
| // replace references |
| for (SearchMatch reference in _references) { |
| SourceRange range = reference.sourceRange; |
| String sourceForReference = |
| _getSourceForReference(range, initializerSource, initializerPrecedence); |
| change.addEdit(file, new SourceEdit.range(range, sourceForReference)); |
| } |
| // done |
| return new Future.value(change); |
| } |
| |
| @override |
| bool requiresPreview() => false; |
| |
| /** |
| * Returns the source which should be used to replace the reference with the |
| * given [SourceRange]. |
| * |
| * [range] - the [SourceRange] of the reference. |
| * [source] - the source of the initializer, to be inserted at [range]. |
| * [precedence] - the precedence of the initializer [source]. |
| */ |
| String _getSourceForReference(SourceRange range, String source, |
| int precedence) { |
| int offset = range.offset; |
| AstNode node = utils.findNode(offset); |
| AstNode parent = node.parent; |
| if (_isIdentifierStringInterpolation(parent)) { |
| return '{${source}}'; |
| } |
| if (precedence < getExpressionParentPrecedence(node)) { |
| return '(${source})'; |
| } |
| return source; |
| } |
| |
| /** |
| * Checks if the given node is a string interpolation in form `$name`. |
| */ |
| bool _isIdentifierStringInterpolation(AstNode parent) { |
| if (parent is InterpolationExpression) { |
| InterpolationExpression element = parent; |
| return element.beginToken.type == |
| TokenType.STRING_INTERPOLATION_IDENTIFIER; |
| } |
| return false; |
| } |
| } |