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:
Keerti Parthasarathy 2021-12-08 17:21:30 +00:00 committed by Commit Bot
parent 80119cb900
commit c7de515ac9
2 changed files with 264 additions and 69 deletions

View file

@ -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});
}

View file

@ -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);