[analysis_server] Dot shorthands: Update CreateMethod fix.
Refactored `CreateMethod` to handle both method invocations and dot shorthand invocations. The code overlaps so a lot of the CL is just re-organizing them into helpers. Added unit tests. Bug: https://github.com/dart-lang/sdk/issues/60994 Change-Id: Ie06005b67f0fe5f066ab57a2f0dbc06095597897 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/442729 Commit-Queue: Kallen Tu <kallentu@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
@@ -3,8 +3,8 @@
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:analysis_server/src/services/correction/fix.dart';
|
||||
import 'package:analysis_server/src/services/correction/util.dart';
|
||||
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
|
||||
import 'package:analyzer/dart/analysis/results.dart';
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
|
||||
@@ -88,14 +88,48 @@ class CreateMethod extends ResolvedCorrectionProducer {
|
||||
}
|
||||
|
||||
Future<void> _createMethod(ChangeBuilder builder) async {
|
||||
if (node is! SimpleIdentifier || node.parent is! MethodInvocation) {
|
||||
return;
|
||||
}
|
||||
if (node is! SimpleIdentifier) return;
|
||||
_memberName = (node as SimpleIdentifier).name;
|
||||
var invocation = node.parent as MethodInvocation;
|
||||
|
||||
var invocation = node.parent;
|
||||
switch (invocation) {
|
||||
case MethodInvocation():
|
||||
await _createMethodFromMethodInvocation(builder, invocation);
|
||||
case DotShorthandInvocation():
|
||||
await _createMethodFromDotShorthandInvocation(builder, invocation);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _createMethodFromDotShorthandInvocation(
|
||||
ChangeBuilder builder,
|
||||
DotShorthandInvocation invocation,
|
||||
) async {
|
||||
var targetClassElement = computeDotShorthandContextTypeElement(
|
||||
invocation,
|
||||
unitResult.libraryElement,
|
||||
);
|
||||
if (targetClassElement == null) return;
|
||||
|
||||
var targetNode = await _declarationNodeFromElement(targetClassElement);
|
||||
if (targetNode is! CompilationUnitMember) return;
|
||||
|
||||
await _writeMethod(
|
||||
builder,
|
||||
invocation,
|
||||
invocation.argumentList,
|
||||
targetClassElement.firstFragment,
|
||||
targetNode,
|
||||
hasStaticModifier: true,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _createMethodFromMethodInvocation(
|
||||
ChangeBuilder builder,
|
||||
MethodInvocation invocation,
|
||||
) async {
|
||||
// Prepare environment.
|
||||
Fragment? targetFragment;
|
||||
var staticModifier = false;
|
||||
var hasStaticModifier = false;
|
||||
|
||||
CompilationUnitMember? targetNode;
|
||||
var target = invocation.realTarget;
|
||||
@@ -117,7 +151,7 @@ class CreateMethod extends ResolvedCorrectionProducer {
|
||||
if (enclosingMemberParent is CompilationUnitMember &&
|
||||
enclosingMemberParent is! ExtensionDeclaration) {
|
||||
targetNode = enclosingMemberParent;
|
||||
staticModifier = switch (enclosingMember) {
|
||||
hasStaticModifier = switch (enclosingMember) {
|
||||
ConstructorDeclaration(:var factoryKeyword) => factoryKeyword != null,
|
||||
MethodDeclaration(:var isStatic) => isStatic,
|
||||
FieldDeclaration(
|
||||
@@ -129,64 +163,72 @@ class CreateMethod extends ResolvedCorrectionProducer {
|
||||
}
|
||||
} else {
|
||||
var targetClassElement = getTargetInterfaceElement(target);
|
||||
if (targetClassElement == null) {
|
||||
return;
|
||||
}
|
||||
if (targetClassElement == null) return;
|
||||
targetFragment = targetClassElement.firstFragment;
|
||||
if (targetClassElement.library.isInSdk) {
|
||||
return;
|
||||
}
|
||||
// Prepare target ClassDeclaration.
|
||||
if (targetClassElement is MixinElement) {
|
||||
var fragment = targetClassElement.firstFragment;
|
||||
targetNode = await getMixinDeclaration(fragment);
|
||||
} else if (targetClassElement is ClassElement) {
|
||||
var fragment = targetClassElement.firstFragment;
|
||||
targetNode = await getClassDeclaration(fragment);
|
||||
} else if (targetClassElement is ExtensionTypeElement) {
|
||||
var fragment = targetClassElement.firstFragment;
|
||||
targetNode = await getExtensionTypeDeclaration(fragment);
|
||||
} else if (targetClassElement is EnumElement) {
|
||||
var fragment = targetClassElement.firstFragment;
|
||||
targetNode = await getEnumDeclaration(fragment);
|
||||
}
|
||||
if (targetNode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
targetNode = await _declarationNodeFromElement(targetClassElement);
|
||||
if (targetNode == null) return;
|
||||
|
||||
// Maybe static.
|
||||
if (target is Identifier) {
|
||||
staticModifier =
|
||||
hasStaticModifier =
|
||||
target.element?.kind == ElementKind.CLASS ||
|
||||
target.element?.kind == ElementKind.ENUM ||
|
||||
target.element?.kind == ElementKind.EXTENSION_TYPE ||
|
||||
target.element?.kind == ElementKind.MIXIN;
|
||||
}
|
||||
// Use different utils.
|
||||
var targetPath = targetFragment.libraryFragment!.source.fullName;
|
||||
var targetResolveResult = await unitResult.session.getResolvedUnit(
|
||||
targetPath,
|
||||
);
|
||||
if (targetResolveResult is! ResolvedUnitResult) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await _writeMethod(
|
||||
builder,
|
||||
invocation,
|
||||
invocation.argumentList,
|
||||
targetFragment,
|
||||
targetNode,
|
||||
hasStaticModifier: hasStaticModifier,
|
||||
);
|
||||
}
|
||||
|
||||
Future<CompilationUnitMember?> _declarationNodeFromElement(
|
||||
InterfaceElement element,
|
||||
) async {
|
||||
if (element.library.isInSdk) return null;
|
||||
if (element is MixinElement) {
|
||||
var fragment = element.firstFragment;
|
||||
return await getMixinDeclaration(fragment);
|
||||
} else if (element is ClassElement) {
|
||||
var fragment = element.firstFragment;
|
||||
return await getClassDeclaration(fragment);
|
||||
} else if (element is ExtensionTypeElement) {
|
||||
var fragment = element.firstFragment;
|
||||
return await getExtensionTypeDeclaration(fragment);
|
||||
} else if (element is EnumElement) {
|
||||
var fragment = element.firstFragment;
|
||||
return await getEnumDeclaration(fragment);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Inserts the new method into the source code.
|
||||
Future<void> _writeMethod(
|
||||
ChangeBuilder builder,
|
||||
Expression invocation,
|
||||
ArgumentList argumentList,
|
||||
Fragment? targetFragment,
|
||||
CompilationUnitMember? targetNode, {
|
||||
required bool hasStaticModifier,
|
||||
}) async {
|
||||
var targetSource = targetFragment?.libraryFragment!.source;
|
||||
if (targetSource == null) {
|
||||
return;
|
||||
}
|
||||
if (targetSource == null) return;
|
||||
|
||||
var targetFile = targetSource.fullName;
|
||||
// Build method source.
|
||||
await builder.addDartFileEdit(targetFile, (builder) {
|
||||
if (targetNode == null) {
|
||||
return;
|
||||
}
|
||||
if (targetNode == null) return;
|
||||
builder.insertMethod(targetNode, (builder) {
|
||||
// Maybe 'static'.
|
||||
if (staticModifier) {
|
||||
if (hasStaticModifier) {
|
||||
builder.write('static ');
|
||||
}
|
||||
// Append return type.
|
||||
|
||||
var type = inferUndefinedExpressionType(invocation);
|
||||
if (builder.writeType(type, groupName: 'RETURN_TYPE')) {
|
||||
builder.write(' ');
|
||||
@@ -197,7 +239,7 @@ class CreateMethod extends ResolvedCorrectionProducer {
|
||||
builder.write(_memberName);
|
||||
});
|
||||
builder.write('(');
|
||||
builder.writeParametersMatchingArguments(invocation.argumentList);
|
||||
builder.writeParametersMatchingArguments(argumentList);
|
||||
builder.write(')');
|
||||
if (type?.isDartAsyncFuture == true) {
|
||||
builder.write(' async');
|
||||
|
||||
@@ -450,6 +450,25 @@ void f() {
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_createQualified_emptyClassBody_dotShorthand() async {
|
||||
await resolveTestCode('''
|
||||
class A {}
|
||||
void f() {
|
||||
A a = .myUndefinedMethod();
|
||||
print(a);
|
||||
}
|
||||
''');
|
||||
await assertHasFix('''
|
||||
class A {
|
||||
static A myUndefinedMethod() {}
|
||||
}
|
||||
void f() {
|
||||
A a = .myUndefinedMethod();
|
||||
print(a);
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_createQualified_fromClass() async {
|
||||
await resolveTestCode('''
|
||||
class A {
|
||||
@@ -468,6 +487,26 @@ void f() {
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_createQualified_fromClass_dotShorthand() async {
|
||||
await resolveTestCode('''
|
||||
class A {
|
||||
}
|
||||
void f() {
|
||||
A a = .myUndefinedMethod();
|
||||
print(a);
|
||||
}
|
||||
''');
|
||||
await assertHasFix('''
|
||||
class A {
|
||||
static A myUndefinedMethod() {}
|
||||
}
|
||||
void f() {
|
||||
A a = .myUndefinedMethod();
|
||||
print(a);
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_createQualified_fromClass_hasOtherMember() async {
|
||||
await resolveTestCode('''
|
||||
class A {
|
||||
@@ -489,6 +528,30 @@ void f() {
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void>
|
||||
test_createQualified_fromClass_hasOtherMember_dotShorthand() async {
|
||||
await resolveTestCode('''
|
||||
class A {
|
||||
foo() {}
|
||||
}
|
||||
void f() {
|
||||
A a = .myUndefinedMethod();
|
||||
print(a);
|
||||
}
|
||||
''');
|
||||
await assertHasFix('''
|
||||
class A {
|
||||
foo() {}
|
||||
|
||||
static A myUndefinedMethod() {}
|
||||
}
|
||||
void f() {
|
||||
A a = .myUndefinedMethod();
|
||||
print(a);
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_createQualified_fromExtensionType() async {
|
||||
await resolveTestCode('''
|
||||
extension type A(String s) {
|
||||
@@ -507,6 +570,26 @@ void f() {
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_createQualified_fromExtensionType_dotShorthand() async {
|
||||
await resolveTestCode('''
|
||||
extension type A(String s) {
|
||||
}
|
||||
void f() {
|
||||
A a = .myUndefinedMethod();
|
||||
print(a);
|
||||
}
|
||||
''');
|
||||
await assertHasFix('''
|
||||
extension type A(String s) {
|
||||
static A myUndefinedMethod() {}
|
||||
}
|
||||
void f() {
|
||||
A a = .myUndefinedMethod();
|
||||
print(a);
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_createQualified_fromInstance() async {
|
||||
await resolveTestCode('''
|
||||
class A {
|
||||
@@ -553,6 +636,17 @@ void f() {
|
||||
await assertNoFix();
|
||||
}
|
||||
|
||||
Future<void> test_createQualified_targetIsFunctionType_dotShorthand() async {
|
||||
await resolveTestCode('''
|
||||
typedef A();
|
||||
void f() {
|
||||
A a = .myUndefinedMethod();
|
||||
print(a);
|
||||
}
|
||||
''');
|
||||
await assertNoFix();
|
||||
}
|
||||
|
||||
Future<void> test_createQualified_targetIsUnresolved() async {
|
||||
await resolveTestCode('''
|
||||
void f() {
|
||||
@@ -867,6 +961,33 @@ void test() {
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_enum_invocation_static_dotShorthand() async {
|
||||
await resolveTestCode('''
|
||||
enum E {
|
||||
e1,
|
||||
e2;
|
||||
}
|
||||
|
||||
void test() {
|
||||
E e = .bar();
|
||||
print(e);
|
||||
}
|
||||
''');
|
||||
await assertHasFix('''
|
||||
enum E {
|
||||
e1,
|
||||
e2;
|
||||
|
||||
static E bar() {}
|
||||
}
|
||||
|
||||
void test() {
|
||||
E e = .bar();
|
||||
print(e);
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_enum_tearoff() async {
|
||||
await resolveTestCode('''
|
||||
enum E {
|
||||
@@ -1469,6 +1590,15 @@ void f() {
|
||||
await assertNoFix();
|
||||
}
|
||||
|
||||
Future<void> test_inSDK_dotShorthand() async {
|
||||
await resolveTestCode('''
|
||||
List f() {
|
||||
return .foo();
|
||||
}
|
||||
''');
|
||||
await assertNoFix();
|
||||
}
|
||||
|
||||
Future<void> test_internal_instance_extension() async {
|
||||
await resolveTestCode('''
|
||||
extension E on String {
|
||||
@@ -1558,6 +1688,40 @@ class D {
|
||||
''', target: '$testPackageLibPath/test2.dart');
|
||||
}
|
||||
|
||||
Future<void>
|
||||
test_parameterType_differentPrefixInTargetUnit_dotShorthand() async {
|
||||
var code2 = r'''
|
||||
import 'test3.dart' as bbb;
|
||||
export 'test3.dart';
|
||||
|
||||
class D {
|
||||
}
|
||||
''';
|
||||
|
||||
newFile('$testPackageLibPath/test2.dart', code2);
|
||||
newFile('$testPackageLibPath/test3.dart', r'''
|
||||
library test3;
|
||||
class E {}
|
||||
''');
|
||||
|
||||
await resolveTestCode('''
|
||||
import 'test2.dart' as aaa;
|
||||
|
||||
aaa.D f(aaa.E e) {
|
||||
return .foo(e);
|
||||
}
|
||||
''');
|
||||
|
||||
await assertHasFix('''
|
||||
import 'test3.dart' as bbb;
|
||||
export 'test3.dart';
|
||||
|
||||
class D {
|
||||
static D foo(bbb.E e) {}
|
||||
}
|
||||
''', target: '$testPackageLibPath/test2.dart');
|
||||
}
|
||||
|
||||
Future<void> test_parameterType_inTargetUnit() async {
|
||||
newFile('$testPackageLibPath/test2.dart', r'''
|
||||
class D {
|
||||
|
||||
Reference in New Issue
Block a user