Add rename constructor
Change-Id: Ie4815ed4b4db93cfad9307702fc86172685cfb17
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/242160
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Keerti Parthasarathy <keertip@google.com>
diff --git a/pkg/analysis_server/lib/src/cider/rename.dart b/pkg/analysis_server/lib/src/cider/rename.dart
index 33ba10e..a7ce3f3 100644
--- a/pkg/analysis_server/lib/src/cider/rename.dart
+++ b/pkg/analysis_server/lib/src/cider/rename.dart
@@ -2,15 +2,20 @@
// 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' hide Element;
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/naming_conventions.dart';
import 'package:analysis_server/src/services/refactoring/refactoring.dart';
+import 'package:analysis_server/src/services/search/hierarchy.dart';
import 'package:analysis_server/src/utilities/flutter.dart';
+import 'package:analyzer/dart/ast/ast.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/generated/java_core.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
class CanRenameResponse {
@@ -45,12 +50,31 @@
status = validateTypeAliasName(name);
} else if (element is ClassElement) {
status = validateClassName(name);
+ } else if (element is ConstructorElement) {
+ status = validateConstructorName(name);
+ _analyzePossibleConflicts(element, status, name);
}
if (status == null) {
return null;
}
- return CheckNameResponse(status, this);
+ return CheckNameResponse(status, this, name);
+ }
+
+ void _analyzePossibleConflicts(
+ ConstructorElement element, RefactoringStatus result, String newName) {
+ var parentClass = element.enclosingElement;
+ // Check if the "newName" is the name of the enclosing class.
+ if (parentClass.name == newName) {
+ result.addError('The constructor should not have the same name '
+ 'as the name of the enclosing class.');
+ }
+ // check if there are members with "newName" in the same ClassElement
+ for (var newNameMember in getChildren(parentClass, newName)) {
+ var message = format("Class '{0}' already declares {1} with name '{2}'.",
+ parentClass.displayName, getElementKindName(newNameMember), newName);
+ result.addError(message, newLocation_fromElement(newNameMember));
+ }
}
FlutterWidgetState? _findFlutterStateClass(Element element, String newName) {
@@ -76,8 +100,9 @@
class CheckNameResponse {
final RefactoringStatus status;
final CanRenameResponse canRename;
+ final String newName;
- CheckNameResponse(this.status, this.canRename);
+ CheckNameResponse(this.status, this.canRename, this.newName);
LineInfo get lineInfo => canRename.lineInfo;
@@ -100,13 +125,148 @@
for (var element in elements) {
matches.addAll(await fileResolver.findReferences2(element));
}
+
FlutterWidgetRename? flutterRename;
- if (canRename._flutterWidgetState != null) {
- var stateWidget = canRename._flutterWidgetState!;
- var match = await fileResolver.findReferences2(stateWidget.state);
- flutterRename = FlutterWidgetRename(stateWidget.newName, match);
+ var flutterState = canRename._flutterWidgetState;
+ if (flutterState != null) {
+ var stateClass = flutterState.state;
+ var stateName = flutterState.newName;
+ var match = await fileResolver.findReferences2(stateClass);
+ var sourcePath = stateClass.source.fullName;
+ var location = stateClass.enclosingElement.lineInfo
+ .getLocation(stateClass.nameOffset);
+ CiderSearchMatch ciderMatch;
+ var searchInfo = CiderSearchInfo(
+ location, stateClass.nameLength, MatchKind.DECLARATION);
+ try {
+ ciderMatch = match.firstWhere((m) => m.path == sourcePath);
+ ciderMatch.references.add(searchInfo);
+ } catch (_) {
+ match.add(CiderSearchMatch(sourcePath, [], [searchInfo]));
+ }
+ var replacements = match
+ .map((m) => CiderReplaceMatch(
+ m.path,
+ m.references
+ .map((p) => ReplaceInfo(
+ stateName, p.startPosition, stateClass.nameLength))
+ .toList()))
+ .toList();
+ flutterRename = FlutterWidgetRename(stateName, match, replacements);
}
- return RenameResponse(matches, this, flutterWidgetRename: flutterRename);
+ var replaceMatches = <CiderReplaceMatch>[];
+ if (element is ConstructorElement) {
+ for (var match in matches) {
+ var replaceInfo = <ReplaceInfo>[];
+ for (var ref in match.references) {
+ String replacement = newName.isNotEmpty ? '.$newName' : '';
+ if (replacement.isEmpty &&
+ ref.kind == MatchKind.REFERENCE_BY_CONSTRUCTOR_TEAR_OFF) {
+ replacement = '.new';
+ }
+ if (ref.kind ==
+ MatchKind.INVOCATION_BY_ENUM_CONSTANT_WITHOUT_ARGUMENTS) {
+ replacement += '()';
+ }
+ replaceInfo
+ .add(ReplaceInfo(replacement, ref.startPosition, ref.length));
+ }
+ replaceMatches.addMatch(match.path, replaceInfo);
+ }
+ if (element.isSynthetic) {
+ var result = await _replaceSyntheticConstructor();
+ if (result != null) {
+ replaceMatches.addMatch(result.path, result.matches.toList());
+ }
+ }
+ } else {
+ for (var match in matches) {
+ replaceMatches.addMatch(
+ match.path,
+ match.references
+ .map((info) =>
+ ReplaceInfo(newName, info.startPosition, info.length))
+ .toList());
+ }
+ // add element declaration
+ var sourcePath = element.source!.fullName;
+ var infos = await _addElementDeclaration(element, sourcePath);
+ replaceMatches.addMatch(sourcePath, infos);
+ }
+ return RenameResponse(matches, this, replaceMatches,
+ flutterWidgetRename: flutterRename);
+ }
+
+ Future<List<ReplaceInfo>> _addElementDeclaration(
+ Element element, String sourcePath) async {
+ var infos = <ReplaceInfo>[];
+ if (element is PropertyInducingElement && element.isSynthetic) {
+ if (element.getter != null) {
+ infos.add(ReplaceInfo(
+ newName,
+ lineInfo.getLocation(element.getter!.nameOffset),
+ element.getter!.nameLength));
+ }
+ if (element.setter != null) {
+ infos.add(ReplaceInfo(
+ newName,
+ lineInfo.getLocation(element.setter!.nameOffset),
+ element.setter!.nameLength));
+ }
+ } else {
+ var location = (await canRename._fileResolver.resolve2(path: sourcePath))
+ .lineInfo
+ .getLocation(element.nameOffset);
+ infos.add(ReplaceInfo(newName, location, element.nameLength));
+ }
+ return infos;
+ }
+
+ Future<CiderReplaceMatch?> _replaceSyntheticConstructor() async {
+ var element = canRename.refactoringElement.element;
+ var classElement = element.enclosingElement;
+
+ var fileResolver = canRename._fileResolver;
+ var libraryPath = classElement!.library!.source.fullName;
+ var resolvedLibrary = await fileResolver.resolveLibrary2(path: libraryPath);
+ var result = resolvedLibrary.getElementDeclaration(classElement);
+ if (result == null) {
+ return null;
+ }
+
+ var resolvedUnit = result.resolvedUnit;
+ if (resolvedUnit == null) {
+ return null;
+ }
+
+ var node = result.node;
+ if (node is ClassDeclaration) {
+ var utils = CorrectionUtils(resolvedUnit);
+ var location = utils.prepareNewConstructorLocation(
+ fileResolver.contextObjects!.analysisSession, node);
+ if (location == null) {
+ return null;
+ }
+
+ var header = '${classElement.name}.$newName();';
+ return CiderReplaceMatch(libraryPath, [
+ ReplaceInfo(location.prefix + header + location.suffix,
+ resolvedUnit.lineInfo.getLocation(location.offset), 0)
+ ]);
+ } else if (node is EnumDeclaration) {
+ var utils = CorrectionUtils(resolvedUnit);
+ var location = utils.prepareEnumNewConstructorLocation(node);
+ if (location == null) {
+ return null;
+ }
+
+ var header = 'const ${classElement.name}.$newName();';
+ return CiderReplaceMatch(libraryPath, [
+ ReplaceInfo(location.prefix + header + location.suffix,
+ resolvedUnit.lineInfo.getLocation(location.offset), 0)
+ ]);
+ }
+ return null;
}
}
@@ -151,7 +311,7 @@
bool _canRenameElement(Element element) {
var enclosingElement = element.enclosingElement;
if (element is ConstructorElement) {
- return false;
+ return true;
}
if (element is LabelElement || element is LocalElement) {
return true;
@@ -161,16 +321,24 @@
enclosingElement is CompilationUnitElement) {
return true;
}
-
return false;
}
}
+class CiderReplaceMatch {
+ final String path;
+ List<ReplaceInfo> matches;
+
+ CiderReplaceMatch(this.path, this.matches);
+}
+
class FlutterWidgetRename {
final String name;
+ @deprecated
final List<CiderSearchMatch> matches;
+ final List<CiderReplaceMatch> replacements;
- FlutterWidgetRename(this.name, this.matches);
+ FlutterWidgetRename(this.name, this.matches, this.replacements);
}
/// The corresponding `State` declaration of a Flutter `StatefulWidget`.
@@ -182,9 +350,39 @@
}
class RenameResponse {
+ @deprecated
final List<CiderSearchMatch> matches;
final CheckNameResponse checkName;
+ final List<CiderReplaceMatch> replaceMatches;
FlutterWidgetRename? flutterWidgetRename;
- RenameResponse(this.matches, this.checkName, {this.flutterWidgetRename});
+ RenameResponse(this.matches, this.checkName, this.replaceMatches,
+ {this.flutterWidgetRename});
+}
+
+class ReplaceInfo {
+ final String replacementText;
+ final CharacterLocation startPosition;
+ final int length;
+
+ ReplaceInfo(this.replacementText, this.startPosition, this.length);
+
+ @override
+ bool operator ==(Object other) =>
+ other is ReplaceInfo &&
+ replacementText == other.replacementText &&
+ startPosition == other.startPosition &&
+ length == other.length;
+}
+
+extension on List<CiderReplaceMatch> {
+ void addMatch(String path, List<ReplaceInfo> infos) {
+ for (var m in this) {
+ if (m.path == path) {
+ m.matches.addAll(infos);
+ return;
+ }
+ }
+ add(CiderReplaceMatch(path, infos));
+ }
}
diff --git a/pkg/analysis_server/test/src/cider/rename_test.dart b/pkg/analysis_server/test/src/cider/rename_test.dart
index 4adefd9..3a3ec88 100644
--- a/pkg/analysis_server/test/src/cider/rename_test.dart
+++ b/pkg/analysis_server/test/src/cider/rename_test.dart
@@ -4,7 +4,7 @@
import 'package:analysis_server/src/cider/rename.dart';
import 'package:analyzer/source/line_info.dart';
-import 'package:analyzer/src/dart/micro/resolve_file.dart';
+import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -20,6 +20,8 @@
@reflectiveTest
class CiderRenameComputerTest extends CiderServiceTest {
late _CorrectionContext _correctionContext;
+ late LineInfo? _lineInfo;
+ late String _testCode;
@override
void setUp() {
@@ -27,6 +29,16 @@
BazelMockPackages.instance.addFlutter(resourceProvider);
}
+ void test_cannotRename_inSdk() async {
+ var refactor = await _compute(r'''
+main() {
+ new String.^fromCharCodes([]);
+}
+''');
+
+ expect(refactor, isNull);
+ }
+
void test_canRename_class() async {
var refactor = await _compute(r'''
class ^Old {}
@@ -173,6 +185,17 @@
expect(result.oldName, 'a');
}
+ void test_checkName_newName() async {
+ var result = await _checkName(r'''
+class A {
+ A.^test() {}
+}
+''', 'test');
+
+ expect(result!.status.problems.length, 1);
+ expect(result.status.hasError, isTrue);
+ }
+
void test_checkName_parameter() async {
var result = await _checkName(r'''
void foo(String ^a) {
@@ -203,7 +226,7 @@
}
void test_rename_class() async {
- var result = await _rename(r'''
+ var testCode = '''
class ^Old implements Other {
Old() {}
Old.named() {}
@@ -216,26 +239,26 @@
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)
- ])
- ]);
+''';
+ var result = await _rename(testCode, 'New');
+ _assertTestChangeResult('''
+class New implements Other {
+ New() {}
+ New.named() {}
+}
+class Other {
+ factory Other.a() = New;
+ factory Other.b() = New.named;
+}
+void f() {
+ New t1 = new New();
+ New t2 = new New.named();
+}
+''', result!.replaceMatches.first.matches);
}
void test_rename_class_flutterWidget() async {
- var result = await _rename(r'''
+ var testCode = '''
import 'package:flutter/material.dart';
class ^TestPage extends StatefulWidget {
@@ -249,27 +272,299 @@
@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)
- ])
+ var result = await _rename(testCode, 'NewPage');
+ expect(result!.replaceMatches.length, 1);
+ expect(result.replaceMatches.first.matches, [
+ ReplaceInfo('NewPage', CharacterLocation(4, 9), 8),
+ ReplaceInfo('NewPage', CharacterLocation(7, 9), 8),
+ ReplaceInfo('NewPage', CharacterLocation(10, 35), 8),
+ ReplaceInfo('NewPage', CharacterLocation(3, 7), 8)
]);
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)])
- ]);
+ expect(
+ result.flutterWidgetRename!.replacements.first.matches
+ .map((m) => m.startPosition)
+ .toList(),
+ [CharacterLocation(7, 36), CharacterLocation(10, 7)]);
+ }
+
+ void test_rename_constructor_add() async {
+ var testCode = '''
+// ignore: deprecated_new_in_comment_reference
+/// Documentation for [new A] and [A.new]
+class A {
+ ^A() {} // marker
+ factory A._() = A;
+}
+class B extends A {
+ B() : super() {}
+}
+main() {
+ new A();
+ A.new;
+}
+''';
+
+ var result = await _rename(testCode, 'newName');
+ _assertTestChangeResult('''
+// ignore: deprecated_new_in_comment_reference
+/// Documentation for [new A.newName] and [A.newName]
+class A {
+ A.newName() {} // marker
+ factory A._() = A.newName;
+}
+class B extends A {
+ B() : super.newName() {}
+}
+main() {
+ new A.newName();
+ A.newName;
+}
+''', result!.replaceMatches.first.matches);
+ }
+
+ void test_rename_constructor_enum() async {
+ var testCode = '''
+/// [E.new]
+enum E {
+ v1(), v2.new(), v3, v4.other();
+ const ^E(); // 0
+ const E.other() : this();
+}
+''';
+
+ var result = await _rename(testCode, 'newName');
+ _assertTestChangeResult('''
+/// [E.newName]
+enum E {
+ v1.newName(), v2.newName(), v3.newName(), v4.other();
+ const E.newName(); // 0
+ const E.other() : this.newName();
+}
+''', result!.replaceMatches.first.matches);
+ }
+
+ void test_rename_constructor_enum_hasConstructor() async {
+ var testCode = '''
+/// [E.new]
+enum E {
+ v1(), v2.^new(), v3;
+
+ factory E.other() => throw 0;
+}
+''';
+
+ var result = await _rename(testCode, 'newName');
+ _assertTestChangeResult('''
+/// [E.newName]
+enum E {
+ v1.newName(), v2.newName(), v3.newName();
+
+ factory E.other() => throw 0;
+
+ const E.newName();
+}
+''', result!.replaceMatches.first.matches);
+ }
+
+ void test_rename_constructor_enum_hasField() async {
+ var testCode = '''
+/// [E.new]
+enum E {
+ v1(), v2.^new(), v3;
+
+ final int foo = 0;
+}
+''';
+
+ var result = await _rename(testCode, 'newName');
+ _assertTestChangeResult('''
+/// [E.newName]
+enum E {
+ v1.newName(), v2.newName(), v3.newName();
+
+ final int foo = 0;
+
+ const E.newName();
+}
+''', result!.replaceMatches.first.matches);
+ }
+
+ void test_rename_constructor_enum_hasMethod() async {
+ var testCode = '''
+/// [E.new]
+enum E {
+ v1(), v2.^new(), v3;
+
+ void foo() {}
+}
+''';
+
+ var result = await _rename(testCode, 'newName');
+ _assertTestChangeResult('''
+/// [E.newName]
+enum E {
+ v1.newName(), v2.newName(), v3.newName();
+
+ const E.newName();
+
+ void foo() {}
+}
+''', result!.replaceMatches.first.matches);
+ }
+
+ void test_rename_constructor_enum_named() async {
+ var testCode = '''
+/// [E.test]
+enum E {
+ v1.^test(), v2.other();
+ const E.test(); // 0
+ const E.other() : this.test();
+}
+''';
+
+ var result = await _rename(testCode, 'newName');
+ _assertTestChangeResult('''
+/// [E.newName]
+enum E {
+ v1.newName(), v2.other();
+ const E.newName(); // 0
+ const E.other() : this.newName();
+}
+''', result!.replaceMatches.first.matches);
+ }
+
+ void test_rename_constructor_enum_remove() async {
+ var testCode = '''
+/// [E]
+enum E {
+ v1.test(), v2.other();
+ const E.^test(); // 0
+ const E.other() : this.test();
+}
+''';
+
+ var result = await _rename(testCode, '');
+ _assertTestChangeResult('''
+/// [E]
+enum E {
+ v1(), v2.other();
+ const E(); // 0
+ const E.other() : this();
+}
+''', result!.replaceMatches.first.matches);
+ }
+
+ void test_rename_constructor_named() async {
+ var testCode = '''
+// ignore: deprecated_new_in_comment_reference
+/// Documentation for [A.test] and [new A.test]
+class A {
+ A.^test() {} // marker
+ factory A._() = A.test;
+}
+class B extends A {
+ B() : super.test() {}
+}
+main() {
+ new A.test();
+ A.test;
+}
+''';
+
+ var result = await _rename(testCode, 'newName');
+ _assertTestChangeResult('''
+// ignore: deprecated_new_in_comment_reference
+/// Documentation for [A.newName] and [new A.newName]
+class A {
+ A.newName() {} // marker
+ factory A._() = A.newName;
+}
+class B extends A {
+ B() : super.newName() {}
+}
+main() {
+ new A.newName();
+ A.newName;
+}
+''', result!.replaceMatches.first.matches);
+ }
+
+ void test_rename_constructor_remove() async {
+ var testCode = '''
+// ignore: deprecated_new_in_comment_reference
+/// Documentation for [A.test] and [new A.test]
+class A {
+ A.^test() {} // marker
+ factory A._() = A.test;
+}
+class B extends A {
+ B() : super.test() {}
+}
+main() {
+ new A.test();
+ A.test;
+}
+''';
+
+ var result = await _rename(testCode, '');
+ _assertTestChangeResult('''
+// ignore: deprecated_new_in_comment_reference
+/// Documentation for [A] and [new A]
+class A {
+ A() {} // marker
+ factory A._() = A;
+}
+class B extends A {
+ B() : super() {}
+}
+main() {
+ new A();
+ A.new;
+}
+''', result!.replaceMatches.first.matches);
+ }
+
+ void test_rename_constructor_synthetic() async {
+ var testCode = '''
+// ignore: deprecated_new_in_comment_reference
+/// Documentation for [new A] and [A.new]
+class A {
+ int field = 0;
+}
+class B extends A {
+ B() : super() {}
+}
+main() {
+ new A();
+ A.^new;
+}
+''';
+
+ var result = await _rename(testCode, 'newName');
+ _assertTestChangeResult('''
+// ignore: deprecated_new_in_comment_reference
+/// Documentation for [new A.newName] and [A.newName]
+class A {
+ int field = 0;
+
+ A.newName();
+}
+class B extends A {
+ B() : super.newName() {}
+}
+main() {
+ new A.newName();
+ A.newName;
+}
+''', result!.replaceMatches.first.matches);
}
void test_rename_field() async {
- var result = await _rename(r'''
+ var testCode = '''
class A{
int get ^x => 5;
}
@@ -277,17 +572,22 @@
void foo() {
var m = A().x;
}
-''', 'y');
+''';
- expect(result, isNotNull);
- expect(result!.matches, [
- CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'),
- [CharacterLocation(2, 11), CharacterLocation(6, 15)]),
- ]);
+ var result = await _rename(testCode, 'y');
+ _assertTestChangeResult('''
+class A{
+ int get y => 5;
+}
+
+void foo() {
+ var m = A().y;
+}
+''', result!.replaceMatches.first.matches);
}
void test_rename_field_static_private() async {
- var result = await _rename(r'''
+ var testCode = '''
class A{
static const ^_val = 1234;
}
@@ -295,17 +595,22 @@
void foo() {
print(A._val);
}
-''', '_newVal');
+''';
- expect(result, isNotNull);
- expect(result!.matches, [
- CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'),
- [CharacterLocation(2, 16), CharacterLocation(6, 11)]),
- ]);
+ var result = await _rename(testCode, '_newVal');
+ _assertTestChangeResult('''
+class A{
+ static const _newVal = 1234;
+}
+
+void foo() {
+ print(A._newVal);
+}
+''', result!.replaceMatches.first.matches);
}
void test_rename_function() async {
- var result = await _rename(r'''
+ var testCode = '''
test() {}
^foo() {}
void f() {
@@ -313,15 +618,18 @@
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),
- ])
- ]);
+ var result = await _rename(testCode, 'bar');
+ _assertTestChangeResult('''
+test() {}
+bar() {}
+void f() {
+ print(test);
+ print(test());
+ bar();
+}
+''', result!.replaceMatches.first.matches);
}
void test_rename_function_imported() async {
@@ -335,28 +643,27 @@
^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)])
- ]);
+
+ expect(result!.replaceMatches.length, 2);
+ expect(result.replaceMatches.first.matches,
+ [ReplaceInfo('bar', CharacterLocation(3, 3), 3)]);
+ expect(result.replaceMatches[1].matches,
+ [ReplaceInfo('bar', CharacterLocation(1, 1), 3)]);
}
void test_rename_local() async {
- var result = await _rename(r'''
+ var testCode = '''
void foo() {
var ^a = 0; var b = a + 1;
}
-''', 'bar');
+''';
- expect(result!.matches.length, 1);
- expect(
- result.matches[0],
- CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'),
- [CharacterLocation(2, 7), CharacterLocation(2, 22)]));
+ var result = await _rename(testCode, 'bar');
+ _assertTestChangeResult('''
+void foo() {
+ var bar = 0; var b = bar + 1;
+}
+''', result!.replaceMatches.first.matches);
}
void test_rename_method_imported() async {
@@ -372,56 +679,76 @@
var a = A().^foo();
}
''', 'bar');
- expect(result!.matches.length, 2);
- expect(result.matches, [
- CiderSearchMatch(convertPath('/workspace/dart/test/lib/a.dart'), [
- CharacterLocation(2, 3),
- ]),
- CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'),
- [CharacterLocation(3, 15)])
- ]);
+ expect(result!.replaceMatches.length, 2);
+ expect(result.replaceMatches.first.matches,
+ [ReplaceInfo('bar', CharacterLocation(3, 15), 3)]);
+ expect(result.replaceMatches[1].matches,
+ [ReplaceInfo('bar', CharacterLocation(2, 3), 3)]);
}
void test_rename_parameter() async {
- var result = await _rename(r'''
+ var testCode = '''
void foo(String ^a) {
var b = a + 1;
}
-''', 'bar');
- expect(result!.matches.length, 1);
- expect(result.checkName.oldName, 'a');
+''';
+ var result = await _rename(testCode, 'bar');
+ _assertTestChangeResult('''
+void foo(String bar) {
+ var b = bar + 1;
+}
+''', result!.replaceMatches.first.matches);
}
void test_rename_propertyAccessor() async {
- var result = await _rename(r'''
+ var testCode = '''
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)
- ])
- ]);
+''';
+ var result = await _rename(testCode, 'bar');
+ _assertTestChangeResult('''
+get bar {}
+set bar(x) {}
+void f() {
+ print(bar);
+ bar = 1;
+ bar += 2;
+''', result!.replaceMatches.first.matches);
}
void test_rename_typeAlias_functionType() async {
- var result = await _rename(r'''
+ var testCode = '''
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)])
- ]);
+ var result = await _rename(testCode, 'bar');
+ _assertTestChangeResult('''
+typedef bar = void Function();
+void f(bar a) {}
+''', result!.replaceMatches.first.matches);
+ }
+
+ // Asserts that the results of the rename is the [expectedCode].
+ void _assertTestChangeResult(
+ String expectedCode, List<ReplaceInfo> changes) async {
+ var edits = <SourceEdit>[];
+ for (var change in changes) {
+ var offset =
+ _lineInfo!.getOffsetOfLine(change.startPosition.lineNumber - 1) +
+ change.startPosition.columnNumber -
+ 1;
+ edits.add(SourceEdit(offset, change.length, change.replacementText));
+ }
+ edits.sort((a, b) => a.offset.compareTo(b.offset));
+ edits = edits.reversed.toList();
+ // validate resulting code
+ var actualCode = SourceEdit.applySequence(_testCode, edits);
+ expect(actualCode, expectedCode);
}
Future<CheckNameResponse?> _checkName(String content, String newName) async {
@@ -459,6 +786,7 @@
_correctionContext.line,
_correctionContext.character,
);
+ _lineInfo = canRename?.lineInfo;
return canRename?.checkNewName(newName)?.computeRenameRanges2();
}
@@ -470,8 +798,8 @@
var lineInfo = LineInfo.fromContent(content);
var location = lineInfo.getLocation(offset);
- content = content.substring(0, offset) + content.substring(offset + 1);
- newFile(testPath, content);
+ _testCode = content.substring(0, offset) + content.substring(offset + 1);
+ newFile(testPath, _testCode);
_correctionContext = _CorrectionContext(
content,
diff --git a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
index 989c73c..e333fc8 100644
--- a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
+++ b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
@@ -44,21 +44,42 @@
const M = 1024 * 1024 /*1 MiB*/;
const memoryCacheSize = 200 * M;
+class CiderSearchInfo {
+ final CharacterLocation startPosition;
+ final int length;
+ final MatchKind kind;
+
+ CiderSearchInfo(this.startPosition, this.length, this.kind);
+
+ @override
+ bool operator ==(Object other) =>
+ other is CiderSearchInfo &&
+ startPosition == other.startPosition &&
+ length == other.length &&
+ kind == other.kind;
+}
+
class CiderSearchMatch {
final String path;
+ @deprecated
final List<CharacterLocation?> startPositions;
+ final List<CiderSearchInfo> references;
- CiderSearchMatch(this.path, this.startPositions);
+ CiderSearchMatch(this.path, this.startPositions, this.references);
@override
bool operator ==(Object other) =>
other is CiderSearchMatch &&
path == other.path &&
const ListEquality<CharacterLocation?>()
- .equals(startPositions, other.startPositions);
+ // ignore: deprecated_member_use_from_same_package
+ .equals(startPositions, other.startPositions) &&
+ const ListEquality<CiderSearchInfo>()
+ .equals(references, other.references);
@override
String toString() {
+ // ignore: deprecated_member_use_from_same_package
return '($path, $startPositions)';
}
}
@@ -191,13 +212,19 @@
var resolved = await resolve2(path: path);
var collector = ReferencesCollector(element);
resolved.unit.accept(collector);
- var offsets = collector.offsets;
- if (offsets.isNotEmpty) {
+ var matches = collector.references;
+ if (matches.isNotEmpty) {
var lineInfo = resolved.unit.lineInfo;
references.add(CiderSearchMatch(
path,
- offsets
- .map((offset) => lineInfo.getLocation(offset))
+ matches
+ .map((match) => lineInfo.getLocation(match.offset))
+ .toList(),
+ matches
+ .map((match) => CiderSearchInfo(
+ lineInfo.getLocation(match.offset),
+ match.length,
+ match.matchKind))
.toList()));
}
});
diff --git a/pkg/analyzer/lib/src/dart/micro/utils.dart b/pkg/analyzer/lib/src/dart/micro/utils.dart
index 9afec39b..6908497 100644
--- a/pkg/analyzer/lib/src/dart/micro/utils.dart
+++ b/pkg/analyzer/lib/src/dart/micro/utils.dart
@@ -6,6 +6,8 @@
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/ast/element_locator.dart';
+import 'package:analyzer/src/dart/element/element.dart';
+import 'package:collection/src/iterable_extensions.dart';
/// Return the [Element] of the given [node], or `null` if [node] is `null` or
/// does not have an element.
@@ -34,6 +36,32 @@
return element;
}
+/// If the given [constructor] is a synthetic constructor created for a
+/// [ClassTypeAlias], return the actual constructor of a [ClassDeclaration]
+/// which is invoked. Return `null` if a redirection cycle is detected.
+ConstructorElement? _getActualConstructorElement(
+ ConstructorElement? constructor) {
+ var seenConstructors = <ConstructorElement?>{};
+ while (constructor is ConstructorElementImpl && constructor.isSynthetic) {
+ var enclosing = constructor.enclosingElement;
+ if (enclosing.isMixinApplication) {
+ var superInvocation = constructor.constantInitializers
+ .whereType<SuperConstructorInvocation>()
+ .singleOrNull;
+ if (superInvocation != null) {
+ constructor = superInvocation.staticElement;
+ }
+ } else {
+ break;
+ }
+ // fail if a cycle is detected
+ if (!seenConstructors.add(constructor)) {
+ return null;
+ }
+ }
+ return constructor;
+}
+
/// Return the [ImportElement] that declared [prefix] and imports [element].
///
/// [libraryElement] - the [LibraryElement] where reference is.
@@ -132,9 +160,55 @@
libraryElement, prefix, usedElement, importElementsMap);
}
+class MatchInfo {
+ final int offset;
+ final int length;
+ final MatchKind matchKind;
+
+ MatchInfo(this.offset, this.length, this.matchKind);
+}
+
+/// Instances of the enum [MatchKind] represent the kind of reference that was
+/// found when a match represents a reference to an element.
+class MatchKind {
+ /// A declaration of an element.
+ static const MatchKind DECLARATION = MatchKind('DECLARATION');
+
+ /// A reference to an element in which it is being read.
+ static const MatchKind READ = MatchKind('READ');
+
+ /// A reference to an element in which it is being both read and written.
+ static const MatchKind READ_WRITE = MatchKind('READ_WRITE');
+
+ /// A reference to an element in which it is being written.
+ static const MatchKind WRITE = MatchKind('WRITE');
+
+ /// A reference to an element in which it is being invoked.
+ static const MatchKind INVOCATION = MatchKind('INVOCATION');
+
+ /// An invocation of an enum constructor from an enum constant without
+ /// arguments.
+ static const MatchKind INVOCATION_BY_ENUM_CONSTANT_WITHOUT_ARGUMENTS =
+ MatchKind('INVOCATION_BY_ENUM_CONSTANT_WITHOUT_ARGUMENTS');
+
+ /// A reference to an element in which it is referenced.
+ static const MatchKind REFERENCE = MatchKind('REFERENCE');
+
+ /// A tear-off reference to a constructor.
+ static const MatchKind REFERENCE_BY_CONSTRUCTOR_TEAR_OFF =
+ MatchKind('REFERENCE_BY_CONSTRUCTOR_TEAR_OFF');
+
+ final String name;
+
+ const MatchKind(this.name);
+
+ @override
+ String toString() => name;
+}
+
class ReferencesCollector extends GeneralizingAstVisitor<void> {
final Element element;
- final List<int> offsets = [];
+ final List<MatchInfo> references = [];
ReferencesCollector(this.element);
@@ -142,16 +216,20 @@
void visitAssignmentExpression(AssignmentExpression node) {
if (node.writeElement != null &&
node.writeElement is PropertyAccessorElement) {
+ var kind = MatchKind.WRITE;
var property = node.writeElement as PropertyAccessorElement;
if (property.variable == element || property == element) {
if (node.leftHandSide is SimpleIdentifier) {
- offsets.add(node.leftHandSide.offset);
+ references.add(MatchInfo(
+ node.leftHandSide.offset, node.leftHandSide.length, kind));
} else if (node.leftHandSide is PrefixedIdentifier) {
var prefixIdentifier = node.leftHandSide as PrefixedIdentifier;
- offsets.add(prefixIdentifier.identifier.offset);
+ references.add(MatchInfo(prefixIdentifier.identifier.offset,
+ prefixIdentifier.identifier.length, kind));
} else if (node.leftHandSide is PropertyAccess) {
var accessor = node.leftHandSide as PropertyAccess;
- offsets.add(accessor.propertyName.offset);
+ references.add(
+ MatchInfo(accessor.propertyName.offset, accessor.length, kind));
}
}
}
@@ -159,18 +237,157 @@
node.readElement is PropertyAccessorElement) {
var property = node.readElement as PropertyAccessorElement;
if (property.variable == element) {
- offsets.add(node.rightHandSide.offset);
+ references.add(MatchInfo(node.rightHandSide.offset,
+ node.rightHandSide.length, MatchKind.READ));
+ }
+ }
+ }
+
+ @override
+ visitCommentReference(CommentReference node) {
+ var expression = node.expression;
+ if (expression is Identifier) {
+ var element = expression.staticElement;
+ if (element is ConstructorElement) {
+ if (expression is PrefixedIdentifier) {
+ var offset = expression.prefix.end;
+ var length = expression.end - offset;
+ references.add(MatchInfo(offset, length, MatchKind.REFERENCE));
+ return;
+ } else {
+ var offset = expression.end;
+ references.add(MatchInfo(offset, 0, MatchKind.REFERENCE));
+ return;
+ }
+ }
+ } else if (expression is PropertyAccess) {
+ // Nothing to do?
+ } else {
+ throw UnimplementedError('Unhandled CommentReference expression type: '
+ '${expression.runtimeType}');
+ }
+ }
+
+ @override
+ visitConstructorDeclaration(ConstructorDeclaration node) {
+ var e = node.declaredElement;
+ if (e == element) {
+ if (e!.name.isEmpty) {
+ references.add(
+ MatchInfo(e.nameOffset + e.nameLength, 0, MatchKind.DECLARATION));
+ } else {
+ var offset = node.period!.offset;
+ var length = node.name!.end - offset;
+ references.add(MatchInfo(offset, length, MatchKind.DECLARATION));
+ }
+ }
+ super.visitConstructorDeclaration(node);
+ }
+
+ @override
+ void visitConstructorName(ConstructorName node) {
+ var e = node.staticElement?.declaration;
+ e = _getActualConstructorElement(e);
+ MatchKind kind;
+ int offset;
+ int length;
+ if (e == element) {
+ if (node.parent is ConstructorReference) {
+ kind = MatchKind.REFERENCE_BY_CONSTRUCTOR_TEAR_OFF;
+ } else if (node.parent is InstanceCreationExpression) {
+ kind = MatchKind.INVOCATION;
+ } else {
+ kind = MatchKind.REFERENCE;
+ }
+ if (node.name != null) {
+ offset = node.period!.offset;
+ length = node.name!.end - offset;
+ } else {
+ offset = node.type.end;
+ length = 0;
+ }
+ references.add(MatchInfo(offset, length, kind));
+ }
+ if (e!.enclosingElement == element) {
+ kind = MatchKind.REFERENCE;
+ offset = node.offset;
+ length = element.nameLength;
+ references.add(MatchInfo(offset, length, kind));
+ }
+ }
+
+ @override
+ void visitEnumConstantDeclaration(EnumConstantDeclaration node) {
+ var constructorElement = node.constructorElement;
+ if (constructorElement != null && constructorElement == element) {
+ int offset;
+ int length;
+ var constructorSelector = node.arguments?.constructorSelector;
+ if (constructorSelector != null) {
+ offset = constructorSelector.period.offset;
+ length = constructorSelector.name.end - offset;
+ } else {
+ offset = node.name.end;
+ length = 0;
+ }
+ var kind = node.arguments == null
+ ? MatchKind.INVOCATION_BY_ENUM_CONSTANT_WITHOUT_ARGUMENTS
+ : MatchKind.INVOCATION;
+ references.add(MatchInfo(offset, length, kind));
+ }
+ }
+
+ @override
+ void visitRedirectingConstructorInvocation(
+ RedirectingConstructorInvocation node) {
+ var e = node.staticElement;
+ if (e == element) {
+ if (node.constructorName != null) {
+ int offset = node.period!.offset;
+ int length = node.constructorName!.end - offset;
+ references.add(MatchInfo(offset, length, MatchKind.INVOCATION));
+ } else {
+ int offset = node.thisKeyword.end;
+ references.add(MatchInfo(offset, 0, MatchKind.INVOCATION));
}
}
}
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
+ if (node.inDeclarationContext()) {
+ return;
+ }
var e = node.staticElement;
if (e == element) {
- offsets.add(node.offset);
+ references.add(MatchInfo(node.offset, node.length, MatchKind.REFERENCE));
} else if (e is PropertyAccessorElement && e.variable == element) {
- offsets.add(node.offset);
+ bool inGetterContext = node.inGetterContext();
+ bool inSetterContext = node.inSetterContext();
+ MatchKind kind;
+ if (inGetterContext && inSetterContext) {
+ kind = MatchKind.READ_WRITE;
+ } else if (inGetterContext) {
+ kind = MatchKind.READ;
+ } else {
+ kind = MatchKind.WRITE;
+ }
+ references.add(MatchInfo(node.offset, node.length, kind));
+ }
+ }
+
+ @override
+ void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
+ var e = node.staticElement;
+ if (e == element) {
+ if (node.constructorName != null) {
+ int offset = node.period!.offset;
+ int length = node.constructorName!.end - offset;
+ references.add(MatchInfo(offset, length, MatchKind.INVOCATION));
+ } else {
+ int offset = node.superKeyword.end;
+ references.add(MatchInfo(offset, 0, MatchKind.INVOCATION));
+ }
}
}
}
diff --git a/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart b/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
index f0b0c235..934b7bd 100644
--- a/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
+++ b/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
@@ -396,8 +396,8 @@
var element = await _findElement(6, aPath);
var result = await fileResolver.findReferences2(element);
var expected = <CiderSearchMatch>[
- CiderSearchMatch(bPath, [CharacterLocation(4, 11)]),
- CiderSearchMatch(aPath, [CharacterLocation(1, 7)])
+ CiderSearchMatch(bPath, [CharacterLocation(4, 11)],
+ [CiderSearchInfo(CharacterLocation(4, 11), 1, MatchKind.REFERENCE)])
];
expect(result, unorderedEquals(expected));
}
@@ -418,10 +418,10 @@
var element = await _findElement(16, aPath);
var result = await fileResolver.findReferences2(element);
var expected = <CiderSearchMatch>[
- CiderSearchMatch(
- aPath, [CharacterLocation(2, 7), CharacterLocation(5, 5)])
+ CiderSearchMatch(aPath, [CharacterLocation(5, 5)],
+ [CiderSearchInfo(CharacterLocation(5, 5), 3, MatchKind.WRITE)])
];
- expect(result, unorderedEquals(expected));
+ expect(result, expected);
}
test_findReferences_function() async {
@@ -438,8 +438,8 @@
var element = await _findElement(11, aPath);
var result = await fileResolver.findReferences2(element);
var expected = <CiderSearchMatch>[
- CiderSearchMatch(
- aPath, [CharacterLocation(2, 3), CharacterLocation(5, 1)])
+ CiderSearchMatch(aPath, [CharacterLocation(2, 3)],
+ [CiderSearchInfo(CharacterLocation(2, 3), 3, MatchKind.REFERENCE)])
];
expect(result, unorderedEquals(expected));
}
@@ -465,8 +465,8 @@
var element = await _findElement(20, aPath);
var result = await fileResolver.findReferences2(element);
var expected = <CiderSearchMatch>[
- CiderSearchMatch(bPath, [CharacterLocation(5, 15)]),
- CiderSearchMatch(aPath, [CharacterLocation(2, 11)])
+ CiderSearchMatch(bPath, [CharacterLocation(5, 15)],
+ [CiderSearchInfo(CharacterLocation(5, 15), 3, MatchKind.REFERENCE)])
];
expect(result, unorderedEquals(expected));
}
@@ -485,8 +485,8 @@
var element = await _findElement(39, aPath);
var result = await fileResolver.findReferences2(element);
var expected = <CiderSearchMatch>[
- CiderSearchMatch(
- aPath, [CharacterLocation(3, 9), CharacterLocation(4, 11)])
+ CiderSearchMatch(aPath, [CharacterLocation(4, 11)],
+ [CiderSearchInfo(CharacterLocation(4, 11), 3, MatchKind.REFERENCE)])
];
expect(result, unorderedEquals(expected));
}
@@ -519,9 +519,10 @@
var element = await _findElement(17, aPath);
var result = await fileResolver.findReferences2(element);
var expected = <CiderSearchMatch>[
- CiderSearchMatch(bPath, [CharacterLocation(5, 5)]),
- CiderSearchMatch(
- aPath, [CharacterLocation(2, 8), CharacterLocation(7, 4)])
+ CiderSearchMatch(bPath, [CharacterLocation(5, 5)],
+ [CiderSearchInfo(CharacterLocation(5, 5), 4, MatchKind.REFERENCE)]),
+ CiderSearchMatch(aPath, [CharacterLocation(7, 4)],
+ [CiderSearchInfo(CharacterLocation(7, 4), 4, MatchKind.REFERENCE)])
];
expect(result, unorderedEquals(expected));
}
@@ -547,8 +548,8 @@
var element = await _findElement(21, aPath);
var result = await fileResolver.findReferences2(element);
var expected = <CiderSearchMatch>[
- CiderSearchMatch(bPath, [CharacterLocation(5, 5)]),
- CiderSearchMatch(aPath, [CharacterLocation(2, 12)])
+ CiderSearchMatch(bPath, [CharacterLocation(5, 5)],
+ [CiderSearchInfo(CharacterLocation(5, 5), 5, MatchKind.WRITE)])
];
expect(result, unorderedEquals(expected));
}
@@ -575,8 +576,8 @@
var element = await _findElement(19, aPath);
var result = await fileResolver.findReferences2(element);
var expected = <CiderSearchMatch>[
- CiderSearchMatch(bPath, [CharacterLocation(4, 13)]),
- CiderSearchMatch(aPath, [CharacterLocation(3, 9)])
+ CiderSearchMatch(bPath, [CharacterLocation(4, 13)],
+ [CiderSearchInfo(CharacterLocation(4, 13), 3, MatchKind.REFERENCE)])
];
expect(result, unorderedEquals(expected));
}
@@ -603,8 +604,8 @@
var element = await _findElement(20, aPath);
var result = await fileResolver.findReferences2(element);
var expected = <CiderSearchMatch>[
- CiderSearchMatch(bPath, [CharacterLocation(4, 3)]),
- CiderSearchMatch(aPath, [CharacterLocation(3, 10)])
+ CiderSearchMatch(bPath, [CharacterLocation(4, 3)],
+ [CiderSearchInfo(CharacterLocation(4, 3), 3, MatchKind.WRITE)]),
];
expect(result, unorderedEquals(expected));
}
@@ -624,8 +625,8 @@
var element = await _findElement(10, aPath);
var result = await fileResolver.findReferences2(element);
var expected = <CiderSearchMatch>[
- CiderSearchMatch(
- aPath, [CharacterLocation(1, 11), CharacterLocation(4, 11)])
+ CiderSearchMatch(aPath, [CharacterLocation(4, 11)],
+ [CiderSearchInfo(CharacterLocation(4, 11), 1, MatchKind.READ)])
];
expect(result, unorderedEquals(expected));
}
@@ -644,14 +645,20 @@
var result = await fileResolver.findReferences2(element);
var expected = <CiderSearchMatch>[
CiderSearchMatch(aPath, [
- CharacterLocation(1, 11),
CharacterLocation(2, 8),
CharacterLocation(4, 12)
+ ], [
+ CiderSearchInfo(CharacterLocation(2, 8), 5, MatchKind.WRITE),
+ CiderSearchInfo(CharacterLocation(4, 12), 5, MatchKind.WRITE)
])
];
expect(result.map((e) => e.path),
unorderedEquals(expected.map((e) => e.path)));
- expect(result.map((e) => e.startPositions),
+ // ignore: deprecated_member_use_from_same_package
+ expect(
+ // ignore: deprecated_member_use_from_same_package
+ result.map((e) => e.startPositions),
+ // ignore: deprecated_member_use_from_same_package
unorderedEquals(expected.map((e) => e.startPositions)));
}
@@ -672,8 +679,8 @@
var element = await _findElement(8, aPath);
var result = await fileResolver.findReferences2(element);
var expected = <CiderSearchMatch>[
- CiderSearchMatch(bPath, [CharacterLocation(3, 8)]),
- CiderSearchMatch(aPath, [CharacterLocation(1, 9)])
+ CiderSearchMatch(bPath, [CharacterLocation(3, 8)],
+ [CiderSearchInfo(CharacterLocation(3, 8), 4, MatchKind.REFERENCE)])
];
expect(result, unorderedEquals(expected));
}