blob: 369b8321e458d72acf0ade8f689939401312a236 [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, ElementKind;
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/rename.dart';
import 'package:analysis_server/src/services/search/hierarchy.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/element/ast_provider.dart';
import 'package:analyzer/src/generated/source.dart';
/**
* A [Refactoring] for renaming [LocalElement]s.
*/
class RenameLocalRefactoringImpl extends RenameRefactoringImpl {
final AstProvider astProvider;
final ResolvedUnitCache unitCache;
List<LocalElement> elements = [];
RenameLocalRefactoringImpl(
RefactoringWorkspace workspace, this.astProvider, LocalElement element)
: unitCache = new ResolvedUnitCache(astProvider),
super(workspace, element);
@override
LocalElement get element => super.element as LocalElement;
@override
String get refactoringName {
if (element is ParameterElement) {
return "Rename Parameter";
}
if (element is FunctionElement) {
return "Rename Local Function";
}
return "Rename Local Variable";
}
@override
Future<RefactoringStatus> checkFinalConditions() async {
// TODO(brianwilkerson) Determine whether this await is necessary.
await null;
RefactoringStatus result = new RefactoringStatus();
await _prepareElements();
for (LocalElement element in elements) {
CompilationUnit unit = await unitCache.getUnit(element);
if (unit != null) {
unit.accept(new _ConflictValidatorVisitor(result, newName, element));
}
}
return result;
}
@override
RefactoringStatus checkNewName() {
RefactoringStatus result = super.checkNewName();
if (element is LocalVariableElement) {
result.addStatus(validateVariableName(newName));
} else if (element is ParameterElement) {
result.addStatus(validateParameterName(newName));
} else if (element is FunctionElement) {
result.addStatus(validateFunctionName(newName));
}
return result;
}
@override
Future<void> fillChange() async {
var processor = new RenameProcessor(searchEngine, change, newName);
for (Element element in elements) {
processor.addDeclarationEdit(element);
var references = await searchEngine.searchReferences(element);
// Exclude "implicit" references to optional positional parameters.
if (element is ParameterElement && element.isOptionalPositional) {
references.removeWhere((match) => match.sourceRange.length == 0);
}
processor.addReferenceEdits(references);
}
}
/**
* Fills [elements] with [Element]s to rename.
*/
Future _prepareElements() async {
// TODO(brianwilkerson) Determine whether this await is necessary.
await null;
Element element = this.element;
if (element is ParameterElement && element.isNamed) {
elements = await getHierarchyNamedParameters(searchEngine, element);
} else {
elements = [element];
}
}
}
class _ConflictValidatorVisitor extends RecursiveAstVisitor {
final RefactoringStatus result;
final String newName;
final LocalElement target;
final Set<Element> conflictingLocals = new Set<Element>();
_ConflictValidatorVisitor(this.result, this.newName, this.target);
@override
visitSimpleIdentifier(SimpleIdentifier node) {
Element nodeElement = node.staticElement;
if (nodeElement != null && nodeElement.name == newName) {
// Duplicate declaration.
if (node.inDeclarationContext() && _isVisibleWithTarget(nodeElement)) {
conflictingLocals.add(nodeElement);
String nodeKind = nodeElement.kind.displayName;
String message = "Duplicate $nodeKind '$newName'.";
result.addError(message, newLocation_fromElement(nodeElement));
return;
}
if (conflictingLocals.contains(nodeElement)) {
return;
}
// Shadowing by the target element.
SourceRange targetRange = target.visibleRange;
if (targetRange != null &&
targetRange.contains(node.offset) &&
!node.isQualified &&
!_isNamedExpressionName(node)) {
nodeElement = getSyntheticAccessorVariable(nodeElement);
String nodeKind = nodeElement.kind.displayName;
String nodeName = getElementQualifiedName(nodeElement);
String nameElementSourceName = nodeElement.source.shortName;
String refKind = target.kind.displayName;
String message = 'Usage of $nodeKind "$nodeName" declared in '
'"$nameElementSourceName" will be shadowed by renamed $refKind.';
result.addError(message, newLocation_fromNode(node));
}
}
}
/**
* Returns whether [element] and [target] are visible together.
*/
bool _isVisibleWithTarget(Element element) {
if (element is LocalElement) {
SourceRange targetRange = target.visibleRange;
SourceRange elementRange = element.visibleRange;
return targetRange != null &&
elementRange != null &&
elementRange.intersects(targetRange);
}
return false;
}
static bool _isNamedExpressionName(SimpleIdentifier node) {
return node.parent is Label && node.parent.parent is NamedExpression;
}
}