blob: a9bdb749b5d46b5294981b231ff2d06fadba6312 [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.
library services.src.refactoring.rename;
import 'dart:async';
import 'package:analysis_server/src/protocol_server.dart' hide Element;
import 'package:analysis_server/src/services/correction/source_range.dart';
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/element.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/java_core.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:path/path.dart' as pathos;
/**
* Returns `true` if two given [Element]s are [LocalElement]s and have
* intersecting with visibility ranges.
*/
bool haveIntersectingRanges(LocalElement localElement, Element element) {
if (element is! LocalElement) {
return false;
}
LocalElement localElement2 = element as LocalElement;
Source localSource = localElement.source;
Source localSource2 = localElement2.source;
SourceRange localRange = localElement.visibleRange;
SourceRange localRange2 = localElement2.visibleRange;
return localSource2 == localSource &&
localRange != null &&
localRange2 != null &&
localRange2.intersects(localRange);
}
/**
* Checks if [element] is defined in the library containing [source].
*/
bool isDefinedInLibrary(
Element element, AnalysisContext context, Source source) {
// should be the same AnalysisContext
if (!isInContext(element, context)) {
return false;
}
// private elements are visible only in their library
List<Source> librarySourcesOfSource = context.getLibrariesContaining(source);
Source librarySourceOfElement = element.library.source;
return librarySourcesOfSource.contains(librarySourceOfElement);
}
bool isElementInPubCache(Element element) {
Source source = element.source;
String path = source.fullName;
return isPathInPubCache(path);
}
bool isElementInSdkOrPubCache(Element element) {
Source source = element.source;
String path = source.fullName;
return source.isInSystemLibrary || isPathInPubCache(path);
}
/**
* Checks if the given [Element] is in the given [AnalysisContext].
*/
bool isInContext(Element element, AnalysisContext context) {
return element.context == context;
}
bool isPathInPubCache(String path) {
List<String> parts = pathos.split(path);
if (parts.contains('.pub-cache')) {
return true;
}
for (int i = 0; i < parts.length - 1; i++) {
if (parts[i] == 'Pub' && parts[i + 1] == 'Cache') {
return true;
}
if (parts[i] == 'third_party' &&
(parts[i + 1] == 'pkg' || parts[i + 1] == 'pkg_tested')) {
return true;
}
}
return false;
}
/**
* Checks if the given unqualified [SearchMatch] intersects with visibility
* range of [localElement].
*/
bool isReferenceInLocalRange(LocalElement localElement, SearchMatch reference) {
if (reference.isQualified) {
return false;
}
Source localSource = localElement.source;
Source referenceSource = reference.element.source;
SourceRange localRange = localElement.visibleRange;
SourceRange referenceRange = reference.sourceRange;
return referenceSource == localSource &&
referenceRange.intersects(localRange);
}
/**
* Checks if [element] is visible in the library containing [source].
*/
bool isVisibleInLibrary(
Element element, AnalysisContext context, Source source) {
// should be the same AnalysisContext
if (!isInContext(element, context)) {
return false;
}
// public elements are always visible
if (element.isPublic) {
return true;
}
// private elements are visible only in their library
return isDefinedInLibrary(element, context, source);
}
/**
* An abstract implementation of [RenameRefactoring].
*/
abstract class RenameRefactoringImpl extends RefactoringImpl
implements RenameRefactoring {
final SearchEngine searchEngine;
final Element element;
final AnalysisContext context;
final String elementKindName;
final String oldName;
SourceChange change;
String newName;
RenameRefactoringImpl(SearchEngine searchEngine, Element element)
: searchEngine = searchEngine,
element = element,
context = element.context,
elementKindName = element.kind.displayName,
oldName = _getDisplayName(element);
/**
* Adds a [SourceEdit] to update [element] name to [change].
*/
void addDeclarationEdit(Element element) {
if (element != null) {
SourceRange range = rangeElementName(element);
SourceEdit edit = newSourceEdit_range(range, newName);
doSourceChange_addElementEdit(change, element, edit);
}
}
/**
* Adds [SourceEdit]s to update [matches] to [change].
*/
void addReferenceEdits(List<SearchMatch> matches) {
List<SourceReference> references = getSourceReferences(matches);
for (SourceReference reference in references) {
reference.addEdit(change, newName);
}
}
@override
Future<RefactoringStatus> checkInitialConditions() {
RefactoringStatus result = new RefactoringStatus();
if (element.source.isInSystemLibrary) {
String message = format(
"The {0} '{1}' is defined in the SDK, so cannot be renamed.",
getElementKindName(element),
getElementQualifiedName(element));
result.addFatalError(message);
}
if (isElementInPubCache(element)) {
String message = format(
"The {0} '{1}' is defined in a pub package, so cannot be renamed.",
getElementKindName(element),
getElementQualifiedName(element));
result.addFatalError(message);
}
return new Future.value(result);
}
@override
RefactoringStatus checkNewName() {
RefactoringStatus result = new RefactoringStatus();
if (newName == oldName) {
result.addFatalError(
"The new name must be different than the current name.");
}
return result;
}
@override
Future<SourceChange> createChange() async {
String changeName = "$refactoringName '$oldName' to '$newName'";
change = new SourceChange(changeName);
await fillChange();
return change;
}
/**
* Adds individual edits to [change].
*/
Future fillChange();
@override
bool requiresPreview() {
return false;
}
static String _getDisplayName(Element element) {
if (element is ImportElement) {
PrefixElement prefix = element.prefix;
if (prefix != null) {
return prefix.displayName;
}
return '';
}
return element.displayName;
}
}