blob: efe388003939a3264f7879380bc6b8dfe20c5c56 [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 'package:analysis_server/src/services/correction/status.dart';
import 'package:analysis_server/src/services/refactoring/convert_getter_to_method.dart';
import 'package:analysis_server/src/services/refactoring/convert_method_to_getter.dart';
import 'package:analysis_server/src/services/refactoring/extract_local.dart';
import 'package:analysis_server/src/services/refactoring/extract_method.dart';
import 'package:analysis_server/src/services/refactoring/extract_widget.dart';
import 'package:analysis_server/src/services/refactoring/inline_local.dart';
import 'package:analysis_server/src/services/refactoring/inline_method.dart';
import 'package:analysis_server/src/services/refactoring/move_file.dart';
import 'package:analysis_server/src/services/refactoring/rename_class_member.dart';
import 'package:analysis_server/src/services/refactoring/rename_constructor.dart';
import 'package:analysis_server/src/services/refactoring/rename_extension_member.dart';
import 'package:analysis_server/src/services/refactoring/rename_import.dart';
import 'package:analysis_server/src/services/refactoring/rename_label.dart';
import 'package:analysis_server/src/services/refactoring/rename_library.dart';
import 'package:analysis_server/src/services/refactoring/rename_local.dart';
import 'package:analysis_server/src/services/refactoring/rename_unit_member.dart';
import 'package:analysis_server/src/services/search/search_engine.dart';
import 'package:analysis_server/src/utilities/progress.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/dart/analysis/index.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart'
show RefactoringMethodParameter, SourceChange;
/// [Refactoring] to convert getters into normal [MethodDeclaration]s.
abstract class ConvertGetterToMethodRefactoring implements Refactoring {
/// Returns a new [ConvertMethodToGetterRefactoring] instance for converting
/// [element] and all the corresponding hierarchy elements.
factory ConvertGetterToMethodRefactoring(SearchEngine searchEngine,
AnalysisSession session, PropertyAccessorElement element) {
return ConvertGetterToMethodRefactoringImpl(searchEngine, session, element);
}
}
/// [Refactoring] to convert normal [MethodDeclaration]s into getters.
abstract class ConvertMethodToGetterRefactoring implements Refactoring {
/// Returns a new [ConvertMethodToGetterRefactoring] instance for converting
/// [element] and all the corresponding hierarchy elements.
factory ConvertMethodToGetterRefactoring(SearchEngine searchEngine,
AnalysisSession session, ExecutableElement element) {
return ConvertMethodToGetterRefactoringImpl(searchEngine, session, element);
}
}
/// [Refactoring] to extract an expression into a local variable declaration.
abstract class ExtractLocalRefactoring implements Refactoring {
/// Returns a new [ExtractLocalRefactoring] instance.
factory ExtractLocalRefactoring(ResolvedUnitResult resolveResult,
int selectionOffset, int selectionLength) = ExtractLocalRefactoringImpl;
/// The lengths of the expressions that cover the specified selection,
/// from the down most to the up most.
List<int> get coveringExpressionLengths;
/// The offsets of the expressions that cover the specified selection,
/// from the down most to the up most.
List<int> get coveringExpressionOffsets;
/// True if all occurrences of the expression within the scope in which the
/// variable will be defined should be replaced by a reference to the local
/// variable. The expression used to initiate the refactoring will always be
/// replaced.
set extractAll(bool extractAll);
/// The lengths of the expressions that would be replaced by a reference to
/// the variable. The lengths correspond to the offsets. In other words, for a
/// given expression, if the offset of that expression is offsets[i], then the
/// length of that expression is lengths[i].
List<int> get lengths;
/// The name that the local variable should be given.
set name(String name);
/// The proposed names for the local variable.
///
/// The first proposal should be used as the "best guess" (if it exists).
List<String> get names;
/// The offsets of the expressions that would be replaced by a reference to
/// the variable.
List<int> get offsets;
/// Validates that the [name] is a valid identifier and is appropriate for
/// local variable.
///
/// It does not perform all the checks (such as checking for conflicts with
/// any existing names in any of the scopes containing the current name), as
/// many of these checks require search engine. Use [checkFinalConditions] for
/// this level of checking.
RefactoringStatus checkName();
/// Return `true` if refactoring is available, possibly without checking all
/// initial conditions.
bool isAvailable();
}
/// [Refactoring] to extract an [Expression] or [Statement]s into a new method.
abstract class ExtractMethodRefactoring implements Refactoring {
/// Returns a new [ExtractMethodRefactoring] instance.
factory ExtractMethodRefactoring(
SearchEngine searchEngine,
ResolvedUnitResult resolveResult,
int selectionOffset,
int selectionLength) {
return ExtractMethodRefactoringImpl(
searchEngine, resolveResult, selectionOffset, selectionLength);
}
/// True if a getter could be created rather than a method.
bool get canCreateGetter;
/// True if a getter should be created rather than a method.
set createGetter(bool createGetter);
/// True if all occurrences of the expression or statements should be replaced
/// by an invocation of the method. The expression or statements used to
/// initiate the refactoring will always be replaced.
set extractAll(bool extractAll);
/// The lengths of the expressions or statements that would be replaced by an
/// invocation of the method. The lengths correspond to the offsets.
/// In other words, for a given expression (or block of statements), if the
/// offset of that expression is offsets[i], then the length of that
/// expression is lengths[i].
List<int> get lengths;
/// The name that the method should be given.
set name(String name);
/// The proposed names for the method.
///
/// The first proposal should be used as the "best guess" (if it exists).
List<String> get names;
/// The offsets of the expressions or statements that would be replaced by an
/// invocation of the method.
List<int> get offsets;
/// The proposed parameters for the method.
List<RefactoringMethodParameter> get parameters;
/// The parameters that should be defined for the method.
set parameters(List<RefactoringMethodParameter> parameters);
/// The proposed return type for the method.
String get returnType;
/// The return type that should be defined for the method.
set returnType(String returnType);
/// Validates that the [name] is a valid identifier and is appropriate for a
/// method.
///
/// It does not perform all the checks (such as checking for conflicts with
/// any existing names in any of the scopes containing the current name), as
/// many of these checks require search engine. Use [checkFinalConditions] for
/// this level of checking.
RefactoringStatus checkName();
/// Return `true` if refactoring is available, possibly without checking all
/// initial conditions.
bool isAvailable();
}
/// [Refactoring] to extract a widget creation expression or a method returning
/// a widget, into a new stateless or stateful widget.
abstract class ExtractWidgetRefactoring implements Refactoring {
/// Returns a new [ExtractWidgetRefactoring] instance.
factory ExtractWidgetRefactoring(SearchEngine searchEngine,
ResolvedUnitResult resolveResult, int offset, int length) {
return ExtractWidgetRefactoringImpl(
searchEngine, resolveResult, offset, length);
}
/// The name that the class should be given.
set name(String name);
/// Validates that the [name] is a valid identifier and is appropriate for a
/// class.
///
/// It does not perform all the checks (such as checking for conflicts with
/// any existing names in any of the scopes containing the current name), as
/// many of these checks require search engine. Use [checkFinalConditions] for
/// this level of checking.
RefactoringStatus checkName();
/// Return `true` if refactoring is available, possibly without checking all
/// initial conditions.
bool isAvailable();
}
/// [Refactoring] to inline a local [VariableElement].
abstract class InlineLocalRefactoring implements Refactoring {
/// Returns a new [InlineLocalRefactoring] instance.
factory InlineLocalRefactoring(
SearchEngine searchEngine, ResolvedUnitResult resolveResult, int offset) {
return InlineLocalRefactoringImpl(searchEngine, resolveResult, offset);
}
/// Returns the number of references to the [VariableElement].
int get referenceCount;
/// Returns the name of the variable being inlined.
String? get variableName;
/// Return `true` if refactoring is available, possibly without checking all
/// initial conditions.
bool isAvailable();
}
/// [Refactoring] to inline an [ExecutableElement].
abstract class InlineMethodRefactoring implements Refactoring {
/// Returns a new [InlineMethodRefactoring] instance.
factory InlineMethodRefactoring(
SearchEngine searchEngine, ResolvedUnitResult resolveResult, int offset) {
return InlineMethodRefactoringImpl(searchEngine, resolveResult, offset);
}
/// The name of the class enclosing the method being inlined.
/// If not a class member is being inlined, then `null`.
String? get className;
/// True if the method being inlined should be removed.
/// It is an error if this field is `true` and [inlineAll] is `false`.
set deleteSource(bool deleteSource);
/// True if all invocations of the method should be inlined, or false if only
/// the invocation site used to create this refactoring should be inlined.
set inlineAll(bool inlineAll);
/// True if the declaration of the method is selected.
/// So, all references should be inlined.
bool get isDeclaration;
/// The name of the method (or function) being inlined.
String? get methodName;
/// Return `true` if refactoring is available, possibly without checking all
/// initial conditions.
bool isAvailable();
}
/// [Refactoring] to move/rename a file or folder.
abstract class MoveFileRefactoring implements Refactoring {
/// Returns a new [MoveFileRefactoring] instance.
factory MoveFileRefactoring(ResourceProvider resourceProvider,
RefactoringWorkspace workspace, String oldFilePath) {
return MoveFileRefactoringImpl(resourceProvider, workspace, oldFilePath);
}
/// The new file path to which the given file is being moved.
set newFile(String newName);
}
/// Abstract interface for all refactorings.
abstract class Refactoring {
set cancellationToken(CancellationToken token);
/// The ids of source edits that are not known to be valid.
///
/// An edit is not known to be valid if there was insufficient type
/// information for the server to be able to determine whether or not the code
/// needs to be modified, such as when a member is being renamed and there is
/// a reference to a member from an unknown type. This field will be omitted
/// if the change field is omitted or if there are no potential edits for the
/// refactoring.
List<String> get potentialEditIds;
/// Returns the human readable name of this [Refactoring].
String get refactoringName;
/// Checks all conditions - [checkInitialConditions] and
/// [checkFinalConditions] to decide if refactoring can be performed.
Future<RefactoringStatus> checkAllConditions();
/// Validates environment to check if this refactoring can be performed.
///
/// This check may be slow, because many refactorings use search engine.
Future<RefactoringStatus> checkFinalConditions();
/// Validates arguments to check if this refactoring can be performed.
///
/// This check should be quick because it is used often as arguments change.
Future<RefactoringStatus> checkInitialConditions();
/// Returns the [Change] to apply to perform this refactoring.
Future<SourceChange> createChange();
}
/// Information about the workspace refactorings operate it.
class RefactoringWorkspace {
final Iterable<AnalysisDriver> drivers;
final SearchEngine searchEngine;
RefactoringWorkspace(this.drivers, this.searchEngine);
/// Whether the [element] is defined in a file that is in a context root.
bool containsElement(Element element) {
return containsFile(element.source!.fullName);
}
/// Whether the file with the given [path] is in a context root.
bool containsFile(String path) {
return drivers.any((driver) {
return driver.analysisContext!.contextRoot.isAnalyzed(path);
});
}
/// Returns the drivers that have [path] in a context root.
Iterable<AnalysisDriver> driversContaining(String path) {
return drivers.where((driver) {
return driver.analysisContext!.contextRoot.isAnalyzed(path);
});
}
}
/// Abstract [Refactoring] for renaming some [Element].
abstract class RenameRefactoring implements Refactoring {
/// Returns the human-readable description of the kind of element being
/// renamed (such as “class” or “function type alias”).
String get elementKindName;
/// Sets the new name for the [Element].
set newName(String newName);
/// Returns the old name of the [Element] being renamed.
String get oldName;
/// Validates that the [newName] is a valid identifier and is appropriate for
/// the type of the [Element] being renamed.
///
/// It does not perform all the checks (such as checking for conflicts with
/// any existing names in any of the scopes containing the current name), as
/// many of these checks require search engine. Use [checkFinalConditions] for
/// this level of checking.
RefactoringStatus checkNewName();
/// Returns a new [RenameRefactoring] instance for renaming [element],
/// maybe `null` if there is no support for renaming [Element]s of the given
/// type.
static RenameRefactoring? create(RefactoringWorkspace workspace,
ResolvedUnitResult resolvedUnit, Element? element) {
var session = resolvedUnit.session;
if (element == null) {
return null;
}
if (element is PropertyAccessorElement) {
element = element.variable;
}
var enclosingElement = element.enclosingElement;
if (enclosingElement is CompilationUnitElement) {
return RenameUnitMemberRefactoringImpl(workspace, resolvedUnit, element);
}
if (element is ConstructorElement) {
return RenameConstructorRefactoringImpl(workspace, session, element);
}
if (element is ImportElement) {
return RenameImportRefactoringImpl(workspace, session, element);
}
if (element is LabelElement) {
return RenameLabelRefactoringImpl(workspace, element);
}
if (element is LibraryElement) {
return RenameLibraryRefactoringImpl(workspace, element);
}
if (element is LocalElement) {
return RenameLocalRefactoringImpl(workspace, session, element);
}
if (enclosingElement is ClassElement) {
return RenameClassMemberRefactoringImpl(
workspace, session, enclosingElement, element);
}
if (enclosingElement is ExtensionElement) {
return RenameExtensionMemberRefactoringImpl(
workspace, session, enclosingElement, element);
}
return null;
}
/// Given a node/element, finds the best element to rename (for example
/// the class when on the `new` keyword).
static RenameRefactoringElement? getElementToRename(
AstNode node, Element? element) {
var offset = node.offset;
var length = node.length;
if (node is SimpleIdentifier && element is ParameterElement) {
element = declaredParameterElement(node, element);
}
if (element is FieldFormalParameterElement) {
element = element.field;
}
// Use the prefix offset/length when renaming an import directive.
if (node is ImportDirective && element is ImportElement) {
var prefix = node.prefix;
if (prefix != null) {
offset = prefix.offset;
length = prefix.length;
} else {
// -1 means the name does not exist yet.
offset = -1;
length = 0;
}
}
// Rename the class when on `new` in an instance creation.
if (node is InstanceCreationExpression) {
var creation = node;
var typeIdentifier = creation.constructorName.type.name;
element = typeIdentifier.staticElement;
offset = typeIdentifier.offset;
length = typeIdentifier.length;
}
if (element == null) {
return null;
}
return RenameRefactoringElement(element, offset, length);
}
}
class RenameRefactoringElement {
final Element element;
final int offset;
final int length;
RenameRefactoringElement(this.element, this.offset, this.length);
}