|
| 1 | +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file |
| 2 | +// for details. All rights reserved. Use of this source code is governed by a |
| 3 | +// BSD-style license that can be found in the LICENSE file. |
| 4 | + |
| 5 | +import 'package:analysis_server/src/services/completion/dart/feature_computer.dart'; |
| 6 | +import 'package:analysis_server/src/services/correction/assist.dart'; |
| 7 | +import 'package:analysis_server_plugin/edit/dart/correction_producer.dart'; |
| 8 | +import 'package:analyzer/dart/analysis/features.dart'; |
| 9 | +import 'package:analyzer/dart/ast/ast.dart'; |
| 10 | +import 'package:analyzer/dart/element/element.dart'; |
| 11 | +import 'package:analyzer/dart/element/type.dart'; |
| 12 | +import 'package:analyzer_plugin/utilities/assist/assist.dart'; |
| 13 | +import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; |
| 14 | +import 'package:analyzer_plugin/utilities/range_factory.dart'; |
| 15 | + |
| 16 | +class ConvertToDotShorthand extends ResolvedCorrectionProducer { |
| 17 | + ConvertToDotShorthand({required super.context}); |
| 18 | + |
| 19 | + @override |
| 20 | + CorrectionApplicability get applicability => |
| 21 | + CorrectionApplicability.automatically; |
| 22 | + |
| 23 | + @override |
| 24 | + AssistKind get assistKind => DartAssistKind.convertToDotShorthand; |
| 25 | + |
| 26 | + @override |
| 27 | + Future<void> compute(ChangeBuilder builder) async { |
| 28 | + if (!isEnabled(Feature.dot_shorthands)) return; |
| 29 | + await computeAssistForNode(builder, node.parent); |
| 30 | + } |
| 31 | + |
| 32 | + Future<void> computeAssistForNode( |
| 33 | + ChangeBuilder builder, |
| 34 | + AstNode? node, |
| 35 | + ) async { |
| 36 | + switch (node) { |
| 37 | + // e.g. `A.nam^ed()` |
| 38 | + case ConstructorName constructorName: |
| 39 | + // e.g. `pref^ix.A.named()` |
| 40 | + case NamedType(parent: ConstructorName constructorName): |
| 41 | + await convertFromConstructorName(builder, constructorName); |
| 42 | + // e.g. `A.meth^od()` |
| 43 | + case MethodInvocation(): |
| 44 | + await convertFromMethodInvocation(builder, node); |
| 45 | + // e.g. `A.gett^er` |
| 46 | + case PrefixedIdentifier(): |
| 47 | + await convertFromPrefixedIdentifier(builder, node); |
| 48 | + } |
| 49 | + } |
| 50 | + |
| 51 | + /// Whether the element of [node] matches the [typeElement], allowing us to |
| 52 | + /// convert the typed identifier to a dot shorthand. |
| 53 | + /// |
| 54 | + /// In the following example, the node `B.getter` is unable to be converted |
| 55 | + /// to a valid dot shorthand, and this method would return `false`. |
| 56 | + /// |
| 57 | + /// ```dart |
| 58 | + /// class A {} |
| 59 | + /// class B { |
| 60 | + /// static A get getter => A(); |
| 61 | + /// } |
| 62 | + /// |
| 63 | + /// A f() { |
| 64 | + /// return B.getter; |
| 65 | + /// } |
| 66 | + /// ``` |
| 67 | + bool contextTypeMatchesTypeElement(AstNode node, Element? typeElement) { |
| 68 | + var featureComputer = FeatureComputer( |
| 69 | + unitResult.libraryElement.typeSystem, |
| 70 | + unitResult.libraryElement.typeProvider, |
| 71 | + ); |
| 72 | + var contextType = featureComputer.computeContextType(node, node.offset); |
| 73 | + if (contextType is! InterfaceType) return false; |
| 74 | + return contextType.element == typeElement; |
| 75 | + } |
| 76 | + |
| 77 | + /// Converts a constructor to a dot shorthand. |
| 78 | + /// (e.g. `E.named()` to `.named()` or `E()` to `.new()`) |
| 79 | + Future<void> convertFromConstructorName( |
| 80 | + ChangeBuilder builder, |
| 81 | + ConstructorName node, |
| 82 | + ) async { |
| 83 | + if (!contextTypeMatchesTypeElement(node, node.type.element)) return; |
| 84 | + |
| 85 | + // Dot shorthand constructors don't have explicit type arguments. |
| 86 | + // Disallow the assist if the user provided explicit arguments. |
| 87 | + if (node.type.typeArguments != null) return; |
| 88 | + |
| 89 | + if (node.name != null) { |
| 90 | + // Converts a named constructor e.g. `E.named()` to `.named()`. |
| 91 | + await builder.addDartFileEdit(file, (builder) { |
| 92 | + builder.addDeletion(range.node(node.type)); |
| 93 | + }); |
| 94 | + } else { |
| 95 | + // Converts an unnamed constructor e.g. `E()` to `.new()`. |
| 96 | + await builder.addDartFileEdit(file, (builder) { |
| 97 | + builder.addSimpleReplacement(range.node(node.type), '.new'); |
| 98 | + }); |
| 99 | + } |
| 100 | + } |
| 101 | + |
| 102 | + /// Converts a method invocation to a dot shorthand. |
| 103 | + /// (e.g. `E.id()` to `.id()`) |
| 104 | + Future<void> convertFromMethodInvocation( |
| 105 | + ChangeBuilder builder, |
| 106 | + MethodInvocation node, |
| 107 | + ) async { |
| 108 | + var target = node.target; |
| 109 | + if (target is SimpleIdentifier) { |
| 110 | + if (!contextTypeMatchesTypeElement(node, target.element)) return; |
| 111 | + await builder.addDartFileEdit(file, (builder) { |
| 112 | + builder.addDeletion(range.node(target)); |
| 113 | + }); |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + /// Converts a prefix identifier to a dot shorthand. (e.g. `E.id` to `.id`) |
| 118 | + Future<void> convertFromPrefixedIdentifier( |
| 119 | + ChangeBuilder builder, |
| 120 | + PrefixedIdentifier node, |
| 121 | + ) async { |
| 122 | + Identifier prefix; |
| 123 | + if (node.prefix.element is PrefixElement) { |
| 124 | + prefix = node; |
| 125 | + } else { |
| 126 | + prefix = node.prefix; |
| 127 | + } |
| 128 | + if (!contextTypeMatchesTypeElement(node, prefix.element)) return; |
| 129 | + await builder.addDartFileEdit(file, (builder) { |
| 130 | + builder.addDeletion(range.node(prefix)); |
| 131 | + }); |
| 132 | + } |
| 133 | +} |
0 commit comments