blob: f8b7eb203dfdb7c6d7274a9388590db7040387e5 [file] [log] [blame]
// Copyright (c) 2019, 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/refactoring/visible_ranges_computer.dart';
import 'package:analysis_server/src/services/search/hierarchy.dart';
import 'package:analysis_server/src/services/search/search_engine.dart';
import 'package:analyzer/dart/analysis/session.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/analysis/session_helper.dart';
import 'package:analyzer/src/generated/java_core.dart';
import 'package:analyzer/src/generated/source.dart';
/**
* A [Refactoring] for renaming extension member [Element]s.
*/
class RenameExtensionMemberRefactoringImpl extends RenameRefactoringImpl {
final AnalysisSessionHelper sessionHelper;
_ExtensionMemberValidator _validator;
RenameExtensionMemberRefactoringImpl(
RefactoringWorkspace workspace, AnalysisSession session, Element element)
: sessionHelper = AnalysisSessionHelper(session),
super(workspace, element);
@override
String get refactoringName {
if (element is TypeParameterElement) {
return "Rename Type Parameter";
}
if (element is FieldElement) {
return "Rename Field";
}
return "Rename Method";
}
@override
Future<RefactoringStatus> checkFinalConditions() {
_validator = _ExtensionMemberValidator.forRename(
searchEngine, sessionHelper, element, newName);
return _validator.validate();
}
@override
Future<RefactoringStatus> checkInitialConditions() async {
RefactoringStatus result = await super.checkInitialConditions();
if (element is MethodElement && (element as MethodElement).isOperator) {
result.addFatalError('Cannot rename operator.');
}
return Future<RefactoringStatus>.value(result);
}
@override
RefactoringStatus checkNewName() {
RefactoringStatus result = super.checkNewName();
if (element is FieldElement) {
result.addStatus(validateFieldName(newName));
}
if (element is MethodElement) {
result.addStatus(validateMethodName(newName));
}
return result;
}
@override
Future<void> fillChange() async {
var processor = RenameProcessor(workspace, change, newName);
// Update the declaration.
var renameElement = element;
if (renameElement.isSynthetic && renameElement is FieldElement) {
processor.addDeclarationEdit(renameElement.getter);
processor.addDeclarationEdit(renameElement.setter);
} else {
processor.addDeclarationEdit(renameElement);
}
// Update references.
processor.addReferenceEdits(_validator.references);
}
}
/**
* Helper to check if the created or renamed [Element] will cause any conflicts.
*/
class _ExtensionMemberValidator {
final SearchEngine searchEngine;
final AnalysisSessionHelper sessionHelper;
final LibraryElement library;
final Element element;
final ExtensionElement elementExtension;
final ElementKind elementKind;
final String name;
final bool isRename;
final RefactoringStatus result = RefactoringStatus();
final List<SearchMatch> references = <SearchMatch>[];
_ExtensionMemberValidator.forRename(
this.searchEngine, this.sessionHelper, Element element, this.name)
: isRename = true,
library = element.library,
element = element,
elementExtension = element.enclosingElement,
elementKind = element.kind;
Future<RefactoringStatus> validate() async {
// Check if there is a member with "newName" in the extension.
for (Element newNameMember in getChildren(elementExtension, name)) {
result.addError(
format(
"Extension '{0}' already declares {1} with name '{2}'.",
elementExtension.displayName,
getElementKindName(newNameMember),
name,
),
newLocation_fromElement(newNameMember),
);
}
await _prepareReferences();
// usage of the renamed Element is shadowed by a local element
{
_MatchShadowedByLocal conflict = await _getShadowingLocalElement();
if (conflict != null) {
LocalElement localElement = conflict.localElement;
result.addError(
format(
"Usage of renamed {0} will be shadowed by {1} '{2}'.",
elementKind.displayName,
getElementKindName(localElement),
localElement.displayName,
),
newLocation_fromMatch(conflict.match),
);
}
}
return result;
}
Future<_MatchShadowedByLocal> _getShadowingLocalElement() async {
var localElementMap = <CompilationUnitElement, List<LocalElement>>{};
var visibleRangeMap = <LocalElement, SourceRange>{};
Future<List<LocalElement>> getLocalElements(Element element) async {
var unitElement = element.getAncestor((e) => e is CompilationUnitElement);
var localElements = localElementMap[unitElement];
if (localElements == null) {
var result = await sessionHelper.getResolvedUnitByElement(element);
var unit = result.unit;
var collector = _LocalElementsCollector(name);
unit.accept(collector);
localElements = collector.elements;
localElementMap[unitElement] = localElements;
visibleRangeMap.addAll(VisibleRangesComputer.forNode(unit));
}
return localElements;
}
for (SearchMatch match in references) {
// Qualified reference cannot be shadowed by local elements.
if (match.isQualified) {
continue;
}
// Check local elements that might shadow the reference.
var localElements = await getLocalElements(match.element);
for (LocalElement localElement in localElements) {
SourceRange elementRange = visibleRangeMap[localElement];
if (elementRange != null &&
elementRange.intersects(match.sourceRange)) {
return _MatchShadowedByLocal(match, localElement);
}
}
}
return null;
}
/**
* Fills [references] with references to the [element].
*/
Future<void> _prepareReferences() async {
if (!isRename) return;
references.addAll(
await searchEngine.searchReferences(element),
);
}
}
class _LocalElementsCollector extends GeneralizingAstVisitor<void> {
final String name;
final List<LocalElement> elements = [];
_LocalElementsCollector(this.name);
visitSimpleIdentifier(SimpleIdentifier node) {
Element element = node.staticElement;
if (element is LocalElement && element.name == name) {
elements.add(element);
}
}
}
class _MatchShadowedByLocal {
final SearchMatch match;
final LocalElement localElement;
_MatchShadowedByLocal(this.match, this.localElement);
}