mirror of
https://github.com/dart-lang/sdk
synced 2024-07-24 20:05:13 +00:00
Add support to rename compilation unit members.
Change-Id: I81d50c06126b9ad552f978e6ce298f9797c05866 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/222340 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Keerti Parthasarathy <keertip@google.com>
This commit is contained in:
parent
80119cb900
commit
c7de515ac9
|
@ -5,11 +5,13 @@
|
|||
import 'package:analysis_server/src/services/correction/status.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/utilities/flutter.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/source/line_info.dart';
|
||||
import 'package:analyzer/src/dart/ast/utilities.dart';
|
||||
import 'package:analyzer/src/dart/micro/resolve_file.dart';
|
||||
import 'package:analyzer/src/dart/micro/utils.dart';
|
||||
import 'package:analyzer/src/utilities/extensions/collection.dart';
|
||||
|
||||
class CanRenameResponse {
|
||||
final LineInfo lineInfo;
|
||||
|
@ -17,6 +19,8 @@ class CanRenameResponse {
|
|||
final FileResolver _fileResolver;
|
||||
final String filePath;
|
||||
|
||||
FlutterWidgetState? _flutterWidgetState;
|
||||
|
||||
CanRenameResponse(this.lineInfo, this.refactoringElement, this._fileResolver,
|
||||
this.filePath);
|
||||
|
||||
|
@ -24,6 +28,8 @@ class CanRenameResponse {
|
|||
|
||||
CheckNameResponse? checkNewName(String name) {
|
||||
var element = refactoringElement.element;
|
||||
_flutterWidgetState = _findFlutterStateClass(element, name);
|
||||
|
||||
RefactoringStatus? status;
|
||||
if (element is LocalVariableElement) {
|
||||
status = validateVariableName(name);
|
||||
|
@ -31,12 +37,36 @@ class CanRenameResponse {
|
|||
status = validateParameterName(name);
|
||||
} else if (element is FunctionElement) {
|
||||
status = validateFunctionName(name);
|
||||
}
|
||||
if (status == null) {
|
||||
} else if (element is TopLevelVariableElement) {
|
||||
status = validateVariableName(name);
|
||||
} else if (element is TypeAliasElement) {
|
||||
status = validateTypeAliasName(name);
|
||||
} else if (element is ClassElement) {
|
||||
status = validateClassName(name);
|
||||
} else if (status == null) {
|
||||
return null;
|
||||
}
|
||||
return CheckNameResponse(status, this);
|
||||
}
|
||||
|
||||
FlutterWidgetState? _findFlutterStateClass(Element element, String newName) {
|
||||
if (Flutter.instance.isStatefulWidgetDeclaration(element)) {
|
||||
var oldStateName = element.displayName + 'State';
|
||||
var library = element.library!;
|
||||
var state =
|
||||
library.getType(oldStateName) ?? library.getType('_' + oldStateName);
|
||||
if (state != null) {
|
||||
var flutterWidgetStateNewName = newName + 'State';
|
||||
// If the State was private, ensure that it stays private.
|
||||
if (state.name.startsWith('_') &&
|
||||
!flutterWidgetStateNewName.startsWith('_')) {
|
||||
flutterWidgetStateNewName = '_' + flutterWidgetStateNewName;
|
||||
}
|
||||
return FlutterWidgetState(state, flutterWidgetStateNewName);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class CheckNameResponse {
|
||||
|
@ -50,9 +80,29 @@ class CheckNameResponse {
|
|||
String get oldName => canRename.refactoringElement.element.displayName;
|
||||
|
||||
RenameResponse? computeRenameRanges() {
|
||||
var matches = canRename._fileResolver
|
||||
.findReferences(canRename.refactoringElement.element);
|
||||
return RenameResponse(matches, this);
|
||||
var elements = <Element>[];
|
||||
var element = canRename.refactoringElement.element;
|
||||
if (element is PropertyInducingElement && element.isSynthetic) {
|
||||
var property = element;
|
||||
var getter = property.getter;
|
||||
var setter = property.setter;
|
||||
elements.addIfNotNull(getter);
|
||||
elements.addIfNotNull(setter);
|
||||
} else {
|
||||
elements.add(element);
|
||||
}
|
||||
var matches = <CiderSearchMatch>[];
|
||||
for (var element in elements) {
|
||||
matches.addAll(canRename._fileResolver.findReferences(element));
|
||||
}
|
||||
|
||||
FlutterWidgetRename? flutterRename;
|
||||
if (canRename._flutterWidgetState != null) {
|
||||
var stateWidget = canRename._flutterWidgetState!;
|
||||
var match = canRename._fileResolver.findReferences(stateWidget.state);
|
||||
flutterRename = FlutterWidgetRename(stateWidget.newName, match);
|
||||
}
|
||||
return RenameResponse(matches, this, flutterWidgetRename: flutterRename);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,6 +130,9 @@ class CiderRenameComputer {
|
|||
if (element is MethodElement && element.isOperator) {
|
||||
return null;
|
||||
}
|
||||
if (element is PropertyAccessorElement) {
|
||||
element = element.variable;
|
||||
}
|
||||
if (!_canRenameElement(element)) {
|
||||
return null;
|
||||
}
|
||||
|
@ -90,59 +143,43 @@ class CiderRenameComputer {
|
|||
return null;
|
||||
}
|
||||
|
||||
@deprecated
|
||||
CheckNameResponse? checkNewName(
|
||||
String filePath, int line, int column, String name) {
|
||||
var resolvedUnit = _fileResolver.resolve(path: filePath);
|
||||
var lineInfo = resolvedUnit.lineInfo;
|
||||
var offset = lineInfo.getOffsetOfLine(line) + column;
|
||||
|
||||
var node = NodeLocator(offset).searchWithin(resolvedUnit.unit);
|
||||
var element = getElementOfNode(node);
|
||||
|
||||
if (node == null || element == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var refactoring = RenameRefactoring.getElementToRename(node, element);
|
||||
if (refactoring == null) {
|
||||
return null;
|
||||
}
|
||||
RefactoringStatus? status;
|
||||
if (element is LocalVariableElement) {
|
||||
status = validateVariableName(name);
|
||||
} else if (element is ParameterElement) {
|
||||
status = validateParameterName(name);
|
||||
} else if (element is FunctionElement) {
|
||||
status = validateFunctionName(name);
|
||||
}
|
||||
if (status == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return CheckNameResponse(status,
|
||||
CanRenameResponse(lineInfo, refactoring, _fileResolver, filePath));
|
||||
}
|
||||
|
||||
bool _canRenameElement(Element element) {
|
||||
if (element is PropertyAccessorElement) {
|
||||
element = element.variable;
|
||||
}
|
||||
var enclosingElement = element.enclosingElement;
|
||||
if (element is ConstructorElement) {
|
||||
return false;
|
||||
}
|
||||
if (element is LabelElement || element is LocalElement) {
|
||||
return true;
|
||||
}
|
||||
if (enclosingElement is ClassElement ||
|
||||
enclosingElement is ExtensionElement) {
|
||||
enclosingElement is ExtensionElement ||
|
||||
enclosingElement is CompilationUnitElement) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class FlutterWidgetRename {
|
||||
final String name;
|
||||
final List<CiderSearchMatch> matches;
|
||||
|
||||
FlutterWidgetRename(this.name, this.matches);
|
||||
}
|
||||
|
||||
/// The corresponding `State` declaration of a Flutter `StatefulWidget`.
|
||||
class FlutterWidgetState {
|
||||
ClassElement state;
|
||||
String newName;
|
||||
|
||||
FlutterWidgetState(this.state, this.newName);
|
||||
}
|
||||
|
||||
class RenameResponse {
|
||||
final List<CiderSearchMatch> matches;
|
||||
final CheckNameResponse checkName;
|
||||
FlutterWidgetRename? flutterWidgetRename;
|
||||
|
||||
RenameResponse(this.matches, this.checkName);
|
||||
RenameResponse(this.matches, this.checkName, {this.flutterWidgetRename});
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import 'package:analyzer/src/dart/micro/resolve_file.dart';
|
|||
import 'package:test/test.dart';
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
|
||||
import '../utilities/mock_packages.dart';
|
||||
import 'cider_service.dart';
|
||||
|
||||
void main() {
|
||||
|
@ -20,6 +21,22 @@ void main() {
|
|||
class CiderRenameComputerTest extends CiderServiceTest {
|
||||
late _CorrectionContext _correctionContext;
|
||||
|
||||
@override
|
||||
void setUp() {
|
||||
super.setUp();
|
||||
BazelMockPackages.instance.addFlutter(resourceProvider);
|
||||
}
|
||||
|
||||
void test_canRename_class() {
|
||||
var refactor = _compute(r'''
|
||||
class ^Old {}
|
||||
}
|
||||
''');
|
||||
|
||||
expect(refactor!.refactoringElement.element.name, 'Old');
|
||||
expect(refactor.refactoringElement.offset, _correctionContext.offset);
|
||||
}
|
||||
|
||||
void test_canRename_field() {
|
||||
var refactor = _compute(r'''
|
||||
class A {
|
||||
|
@ -104,6 +121,15 @@ void foo(int ^bar) {
|
|||
expect(refactor.refactoringElement.offset, _correctionContext.offset);
|
||||
}
|
||||
|
||||
void test_checkName_class() {
|
||||
var result = _checkName(r'''
|
||||
class ^Old {}
|
||||
''', 'New');
|
||||
|
||||
expect(result!.status.problems.length, 0);
|
||||
expect(result.oldName, 'Old');
|
||||
}
|
||||
|
||||
void test_checkName_function() {
|
||||
var result = _checkName(r'''
|
||||
int ^foo() => 2;
|
||||
|
@ -146,6 +172,131 @@ void foo(String ^a) {
|
|||
expect(result.oldName, 'a');
|
||||
}
|
||||
|
||||
void test_checkName_topLevelVariable() {
|
||||
var result = _checkName(r'''
|
||||
var ^foo;
|
||||
''', 'bar');
|
||||
|
||||
expect(result!.status.problems.length, 0);
|
||||
expect(result.oldName, 'foo');
|
||||
}
|
||||
|
||||
void test_checkName_TypeAlias() {
|
||||
var result = _checkName(r'''
|
||||
typedef ^Foo = void Function();
|
||||
''', 'Bar');
|
||||
|
||||
expect(result!.status.problems.length, 0);
|
||||
expect(result.oldName, 'Foo');
|
||||
}
|
||||
|
||||
void test_rename_class() {
|
||||
var result = _rename(r'''
|
||||
class ^Old implements Other {
|
||||
Old() {}
|
||||
Old.named() {}
|
||||
}
|
||||
class Other {
|
||||
factory Other.a() = Old;
|
||||
factory Other.b() = Old.named;
|
||||
}
|
||||
void f() {
|
||||
Old t1 = new Old();
|
||||
Old t2 = new Old.named();
|
||||
}
|
||||
''', 'New');
|
||||
|
||||
expect(result!.matches.length, 1);
|
||||
expect(result.matches, [
|
||||
CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'), [
|
||||
CharacterLocation(1, 7),
|
||||
CharacterLocation(2, 3),
|
||||
CharacterLocation(3, 3),
|
||||
CharacterLocation(6, 23),
|
||||
CharacterLocation(7, 23),
|
||||
CharacterLocation(10, 3),
|
||||
CharacterLocation(10, 16),
|
||||
CharacterLocation(11, 3),
|
||||
CharacterLocation(11, 16)
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
void test_rename_class_flutterWidget() {
|
||||
var result = _rename(r'''
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ^TestPage extends StatefulWidget {
|
||||
const TestPage();
|
||||
|
||||
@override
|
||||
State<TestPage> createState() => TestPageState();
|
||||
}
|
||||
|
||||
class TestPageState extends State<TestPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) => throw 0;
|
||||
}
|
||||
''', 'NewPage');
|
||||
|
||||
expect(result!.matches.length, 1);
|
||||
expect(result.matches, [
|
||||
CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'), [
|
||||
CharacterLocation(3, 7),
|
||||
CharacterLocation(4, 9),
|
||||
CharacterLocation(7, 9),
|
||||
CharacterLocation(10, 35)
|
||||
])
|
||||
]);
|
||||
expect(result.flutterWidgetRename != null, isTrue);
|
||||
expect(result.flutterWidgetRename!.name, 'NewPageState');
|
||||
expect(result.flutterWidgetRename!.matches, [
|
||||
CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'),
|
||||
[CharacterLocation(7, 36), CharacterLocation(10, 7)])
|
||||
]);
|
||||
}
|
||||
|
||||
void test_rename_function() {
|
||||
var result = _rename(r'''
|
||||
test() {}
|
||||
^foo() {}
|
||||
void f() {
|
||||
print(test);
|
||||
print(test());
|
||||
foo();
|
||||
}
|
||||
''', 'bar');
|
||||
|
||||
expect(result!.matches.length, 1);
|
||||
expect(result.matches, [
|
||||
CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'), [
|
||||
CharacterLocation(2, 1),
|
||||
CharacterLocation(6, 3),
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
void test_rename_function_imported() {
|
||||
var a = newFile('/workspace/dart/test/lib/a.dart', content: r'''
|
||||
foo() {}
|
||||
''');
|
||||
fileResolver.resolve(path: a.path);
|
||||
var result = _rename(r'''
|
||||
import 'a.dart';
|
||||
void f() {
|
||||
^foo();
|
||||
}
|
||||
''', 'bar');
|
||||
expect(result!.matches.length, 2);
|
||||
expect(result.matches, [
|
||||
CiderSearchMatch(convertPath('/workspace/dart/test/lib/a.dart'), [
|
||||
CharacterLocation(1, 1),
|
||||
]),
|
||||
CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'),
|
||||
[CharacterLocation(3, 3)])
|
||||
]);
|
||||
}
|
||||
|
||||
void test_rename_local() {
|
||||
var result = _rename(r'''
|
||||
void foo() {
|
||||
|
@ -160,31 +311,6 @@ void foo() {
|
|||
[CharacterLocation(2, 7), CharacterLocation(2, 22)]));
|
||||
}
|
||||
|
||||
void test_rename_method() {
|
||||
var a = newFile('/workspace/dart/test/lib/a.dart', content: r'''
|
||||
void foo() {
|
||||
a;
|
||||
}
|
||||
''');
|
||||
fileResolver.resolve(path: a.path);
|
||||
|
||||
var result = _rename(r'''
|
||||
import 'a.dart';
|
||||
|
||||
main() {
|
||||
^foo();
|
||||
}
|
||||
''', 'bar');
|
||||
|
||||
expect(result!.matches.length, 2);
|
||||
expect(result.matches, [
|
||||
CiderSearchMatch(convertPath('/workspace/dart/test/lib/a.dart'),
|
||||
[CharacterLocation(1, 6)]),
|
||||
CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'),
|
||||
[CharacterLocation(4, 1)])
|
||||
]);
|
||||
}
|
||||
|
||||
void test_rename_parameter() {
|
||||
var result = _rename(r'''
|
||||
void foo(String ^a) {
|
||||
|
@ -195,6 +321,38 @@ void foo(String ^a) {
|
|||
expect(result.checkName.oldName, 'a');
|
||||
}
|
||||
|
||||
void test_rename_propertyAccessor() {
|
||||
var result = _rename(r'''
|
||||
get foo {}
|
||||
set foo(x) {}
|
||||
void f() {
|
||||
print(foo);
|
||||
^foo = 1;
|
||||
foo += 2;
|
||||
''', 'bar');
|
||||
expect(result!.matches, [
|
||||
CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'),
|
||||
[CharacterLocation(1, 5), CharacterLocation(4, 9)]),
|
||||
CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'), [
|
||||
CharacterLocation(2, 5),
|
||||
CharacterLocation(5, 3),
|
||||
CharacterLocation(6, 3)
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
void test_typeAlias_functionType() {
|
||||
var result = _rename(r'''
|
||||
typedef ^F = void Function();
|
||||
void f(F a) {}
|
||||
''', 'bar');
|
||||
|
||||
expect(result!.matches, [
|
||||
CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'),
|
||||
[CharacterLocation(1, 9), CharacterLocation(2, 8)])
|
||||
]);
|
||||
}
|
||||
|
||||
CheckNameResponse? _checkName(String content, String newName) {
|
||||
_updateFile(content);
|
||||
|
||||
|
|
Loading…
Reference in a new issue