diff --git a/pkg/analysis_server/lib/src/services/correction/dart/create_method.dart b/pkg/analysis_server/lib/src/services/correction/dart/create_method.dart index 5350ca74771..ee7a864e9e9 100644 --- a/pkg/analysis_server/lib/src/services/correction/dart/create_method.dart +++ b/pkg/analysis_server/lib/src/services/correction/dart/create_method.dart @@ -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 _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 _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 _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 _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 _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'); diff --git a/pkg/analysis_server/test/src/services/correction/fix/create_method_test.dart b/pkg/analysis_server/test/src/services/correction/fix/create_method_test.dart index 23c698429af..b18f46a11c2 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/create_method_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/create_method_test.dart @@ -450,6 +450,25 @@ void f() { '''); } + Future 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 test_createQualified_fromClass() async { await resolveTestCode(''' class A { @@ -468,6 +487,26 @@ void f() { '''); } + Future 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 test_createQualified_fromClass_hasOtherMember() async { await resolveTestCode(''' class A { @@ -489,6 +528,30 @@ void f() { '''); } + Future + 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 test_createQualified_fromExtensionType() async { await resolveTestCode(''' extension type A(String s) { @@ -507,6 +570,26 @@ void f() { '''); } + Future 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 test_createQualified_fromInstance() async { await resolveTestCode(''' class A { @@ -553,6 +636,17 @@ void f() { await assertNoFix(); } + Future test_createQualified_targetIsFunctionType_dotShorthand() async { + await resolveTestCode(''' +typedef A(); +void f() { + A a = .myUndefinedMethod(); + print(a); +} +'''); + await assertNoFix(); + } + Future test_createQualified_targetIsUnresolved() async { await resolveTestCode(''' void f() { @@ -867,6 +961,33 @@ void test() { '''); } + Future 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 test_enum_tearoff() async { await resolveTestCode(''' enum E { @@ -1469,6 +1590,15 @@ void f() { await assertNoFix(); } + Future test_inSDK_dotShorthand() async { + await resolveTestCode(''' +List f() { + return .foo(); +} +'''); + await assertNoFix(); + } + Future test_internal_instance_extension() async { await resolveTestCode(''' extension E on String { @@ -1558,6 +1688,40 @@ class D { ''', target: '$testPackageLibPath/test2.dart'); } + Future + 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 test_parameterType_inTargetUnit() async { newFile('$testPackageLibPath/test2.dart', r''' class D {