Issue 52177. Add rename refactoring for type parameters.

Bug: https://github.com/dart-lang/sdk/issues/52177
Change-Id: I4ae5bb7d72c8956af6dcd91ad28fb1d7397bf424
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/299280
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Phil Quitslund <pquitslund@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Konstantin Shcheglov 2023-04-27 21:43:48 +00:00 committed by Commit Queue
parent f193d91ba5
commit 310f854c24
5 changed files with 326 additions and 0 deletions

View file

@ -116,6 +116,14 @@ RefactoringStatus validateTypeAliasName(String name) {
return _validateUpperCamelCase(name, 'Type alias');
}
/// Returns the [RefactoringStatus] with severity:
/// - OK if the name is valid;
/// - WARNING if the name is discouraged;
/// - FATAL if the name is illegal.
RefactoringStatus validateTypeParameter(String name) {
return _validateUpperCamelCase(name, 'Type parameter');
}
/// Returns the [RefactoringStatus] with severity:
/// - OK if the name is valid;
/// - WARNING if the name is discouraged;

View file

@ -19,6 +19,7 @@ import 'package:analysis_server/src/services/refactoring/legacy/rename_label.dar
import 'package:analysis_server/src/services/refactoring/legacy/rename_library.dart';
import 'package:analysis_server/src/services/refactoring/legacy/rename_local.dart';
import 'package:analysis_server/src/services/refactoring/legacy/rename_parameter.dart';
import 'package:analysis_server/src/services/refactoring/legacy/rename_type_parameter.dart';
import 'package:analysis_server/src/services/refactoring/legacy/rename_unit_member.dart';
import 'package:analysis_server/src/services/search/search_engine.dart';
import 'package:analyzer/dart/analysis/results.dart';
@ -441,6 +442,10 @@ abstract class RenameRefactoring implements Refactoring {
if (element is LocalElement) {
return RenameLocalRefactoringImpl(workspace, sessionHelper, element);
}
if (element is TypeParameterElement) {
return RenameTypeParameterRefactoringImpl(
workspace, sessionHelper, element);
}
if (enclosingElement is InterfaceElement) {
return RenameClassMemberRefactoringImpl(
workspace, sessionHelper, enclosingElement, element);

View file

@ -0,0 +1,60 @@
// Copyright (c) 2023, 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/protocol_server.dart';
import 'package:analysis_server/src/services/correction/status.dart';
import 'package:analysis_server/src/services/refactoring/legacy/naming_conventions.dart';
import 'package:analysis_server/src/services/refactoring/legacy/rename.dart';
import 'package:analyzer/dart/element/element.dart';
class RenameTypeParameterRefactoringImpl extends RenameRefactoringImpl {
RenameTypeParameterRefactoringImpl(
super.workspace,
super.sessionHelper,
TypeParameterElement super.element,
);
@override
TypeParameterElement get element => super.element as TypeParameterElement;
@override
String get refactoringName {
return 'Rename Type Parameter';
}
@override
Future<RefactoringStatus> checkFinalConditions() async {
final result = RefactoringStatus();
final enclosing = element.enclosingElement;
if (enclosing is TypeParameterizedElement) {
for (final sibling in enclosing.typeParameters) {
if (sibling.name == newName) {
final nodeKind = sibling.kind.displayName;
final message = "Duplicate $nodeKind '$newName'.";
result.addError(message, newLocation_fromElement(sibling));
}
}
}
return result;
}
@override
RefactoringStatus checkNewName() {
final result = super.checkNewName();
result.addStatus(validateTypeParameter(newName));
return result;
}
@override
Future<void> fillChange() async {
final processor =
RenameProcessor(workspace, sessionHelper, change, newName);
processor.addDeclarationEdit(element);
final references = await searchEngine.searchReferences(element);
processor.addReferenceEdits(references);
}
}

View file

@ -0,0 +1,251 @@
// Copyright (c) 2023, 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/protocol_server.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'abstract_rename.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(RenameTypeParameterTest);
});
}
@reflectiveTest
class RenameTypeParameterTest extends RenameRefactoringTest {
Future<void> test_checkFinalConditions_duplicateName() async {
await indexTestUnit('''
void f<T, U>() {}
''');
createRenameRefactoringAtString('T, U');
_assertKindName();
refactoring.newName = 'U';
final status = await refactoring.checkFinalConditions();
assertRefactoringStatus(
status,
RefactoringProblemSeverity.ERROR,
expectedMessage: "Duplicate type parameter 'U'.",
expectedContextSearch: 'U>()',
);
}
Future<void> test_checkNewName_empty() async {
await indexTestUnit('''
class A<T> {}
''');
createRenameRefactoringAtString('T>');
_assertKindName();
refactoring.newName = '';
assertRefactoringStatus(
refactoring.checkNewName(),
RefactoringProblemSeverity.FATAL,
expectedMessage: 'Type parameter name must not be empty.',
);
}
Future<void> test_createChange_class() async {
await indexTestUnit('''
class A<T> {
void foo(T a) {}
}
''');
createRenameRefactoringAtString('T> {');
_assertKindName();
refactoring.newName = 'U';
await assertSuccessfulRefactoring('''
class A<U> {
void foo(U a) {}
}
''');
}
Future<void> test_createChange_class2() async {
await indexTestUnit('''
class A<T extends U, U, V extends List<U>> {}
''');
createRenameRefactoringAtString('U, V');
_assertKindName();
refactoring.newName = 'Z';
await assertSuccessfulRefactoring('''
class A<T extends Z, Z, V extends List<Z>> {}
''');
}
Future<void> test_createChange_class_method() async {
await indexTestUnit('''
class A {
void f<T>(T a) {}
}
''');
createRenameRefactoringAtString('T>(');
_assertKindName();
refactoring.newName = 'U';
await assertSuccessfulRefactoring('''
class A {
void f<U>(U a) {}
}
''');
}
Future<void> test_createChange_enum() async {
await indexTestUnit('''
enum E<T> {
v;
void foo(T a) {}
}
''');
createRenameRefactoringAtString('T> {');
_assertKindName();
refactoring.newName = 'U';
await assertSuccessfulRefactoring('''
enum E<U> {
v;
void foo(U a) {}
}
''');
}
Future<void> test_createChange_extension() async {
await indexTestUnit('''
extension E<T> on int {
void foo(T a) {}
}
''');
createRenameRefactoringAtString('T> on');
_assertKindName();
refactoring.newName = 'U';
await assertSuccessfulRefactoring('''
extension E<U> on int {
void foo(U a) {}
}
''');
}
Future<void> test_createChange_functionTypeAlias() async {
await indexTestUnit('''
typedef void F<T>(T a);
''');
createRenameRefactoringAtString('T>(');
_assertKindName();
refactoring.newName = 'U';
await assertSuccessfulRefactoring('''
typedef void F<U>(U a);
''');
}
Future<void> test_createChange_functionTypeFormalParameter() async {
await indexTestUnit('''
void f(a<T>(T b)) {}
''');
createRenameRefactoringAtString('T>(');
_assertKindName();
refactoring.newName = 'U';
await assertSuccessfulRefactoring('''
void f(a<U>(U b)) {}
''');
}
Future<void> test_createChange_genericFunctionType() async {
await indexTestUnit('''
void f(void Function<T>(T a) b) {}
''');
createRenameRefactoringAtString('T>(');
_assertKindName();
refactoring.newName = 'U';
await assertSuccessfulRefactoring('''
void f(void Function<U>(U a) b) {}
''');
}
Future<void> test_createChange_localFunction() async {
await indexTestUnit('''
void f() {
void g<T>(T a) {}
}
''');
createRenameRefactoringAtString('T>(');
_assertKindName();
refactoring.newName = 'U';
await assertSuccessfulRefactoring('''
void f() {
void g<U>(U a) {}
}
''');
}
Future<void> test_createChange_mixin() async {
await indexTestUnit('''
mixin M<T> {
void foo(T a) {}
}
''');
createRenameRefactoringAtString('T> {');
_assertKindName();
refactoring.newName = 'U';
await assertSuccessfulRefactoring('''
mixin M<U> {
void foo(U a) {}
}
''');
}
Future<void> test_createChange_topLevelFunction() async {
await indexTestUnit('''
void f<T>(T a) {}
''');
createRenameRefactoringAtString('T>(');
_assertKindName();
refactoring.newName = 'U';
await assertSuccessfulRefactoring('''
void f<U>(U a) {}
''');
}
Future<void> test_createChange_typeAlias() async {
await indexTestUnit('''
typedef A<T> = void Function(T);
''');
createRenameRefactoringAtString('T> =');
_assertKindName();
refactoring.newName = 'U';
await assertSuccessfulRefactoring('''
typedef A<U> = void Function(U);
''');
}
void _assertKindName() {
expect(refactoring.refactoringName, 'Rename Type Parameter');
expect(refactoring.elementKindName, 'type parameter');
}
}

View file

@ -20,6 +20,7 @@ import 'rename_import_test.dart' as rename_import_test;
import 'rename_label_test.dart' as rename_label_test;
import 'rename_library_test.dart' as rename_library_test;
import 'rename_local_test.dart' as rename_local_test;
import 'rename_type_parameter_test.dart' as rename_type_parameter;
import 'rename_unit_member_test.dart' as rename_unit_member_test;
void main() {
@ -40,6 +41,7 @@ void main() {
rename_label_test.main();
rename_library_test.main();
rename_local_test.main();
rename_type_parameter.main();
rename_unit_member_test.main();
}, name: 'legacy');
}