[analysis_server] Dot shorthands: Update ChangeTo fix.
Updates the `ChangeTo` fix to handle miswritten dot shorthands.
We use the calculated context type to determine the closest element that
the user might be attempting to write.
Bug: https://github.com/dart-lang/sdk/issues/60994
Change-Id: If69b6b30fa70a9cd047a3f2946ae1e0883f322e8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/442168
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Kallen Tu <kallentu@google.com>
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart b/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart
index 0049b2e..3fc5fda 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart
@@ -698,6 +698,23 @@
}
@override
+ DartType? visitDotShorthandConstructorInvocation(
+ DotShorthandConstructorInvocation node,
+ ) {
+ return _visitParent(node);
+ }
+
+ @override
+ DartType? visitDotShorthandInvocation(DotShorthandInvocation node) {
+ return _visitParent(node);
+ }
+
+ @override
+ DartType? visitDotShorthandPropertyAccess(DotShorthandPropertyAccess node) {
+ return _visitParent(node);
+ }
+
+ @override
DartType? visitExpressionFunctionBody(ExpressionFunctionBody node) {
if (range.endEnd(node.functionDefinition, node).contains(offset)) {
var parent = node.parent;
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/change_to.dart b/pkg/analysis_server/lib/src/services/correction/dart/change_to.dart
index 2b41c66..83d7215 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/change_to.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/change_to.dart
@@ -176,6 +176,28 @@
await _suggest(builder, node, finder._element?.displayName);
}
+ /// Using the context type of the [dotShorthandNode], we'll propose the
+ /// closest element that we believe the user was attempting to write.
+ Future<void> _proposeClassOrMixinMemberForDotShorthand(
+ ChangeBuilder builder,
+ Expression dotShorthandNode,
+ Token dotShorthandIdentifier,
+ _ElementPredicate predicate,
+ ) async {
+ var contextElement = computeDotShorthandContextTypeElement(
+ dotShorthandNode,
+ unitResult.libraryElement,
+ );
+ if (contextElement == null) return;
+
+ var finder = _ClosestElementFinder(
+ dotShorthandIdentifier.lexeme,
+ predicate,
+ );
+ _updateFinderWithClassMembers(finder, contextElement);
+ await _suggest(builder, node, finder._element?.displayName);
+ }
+
Future<void> _proposeField(ChangeBuilder builder) async {
var node = this.node;
if (node is! FieldFormalParameter) return;
@@ -257,9 +279,18 @@
Future<void> _proposeGetterOrSetter(ChangeBuilder builder) async {
var node = this.node;
if (node is SimpleIdentifier) {
+ var parent = node.parent;
+ if (parent is DotShorthandPropertyAccess) {
+ await _proposeClassOrMixinMemberForDotShorthand(
+ builder,
+ parent,
+ node.token,
+ (element) => element is GetterElement || element is FieldElement,
+ );
+ }
+
// prepare target
Expression? target;
- var parent = node.parent;
if (parent is PrefixedIdentifier) {
target = parent.prefix;
} else if (parent is PropertyAccess) {
@@ -285,13 +316,22 @@
Future<void> _proposeMethod(ChangeBuilder builder) async {
var node = this.node;
var parent = node.parent;
- if (parent is MethodInvocation && node is SimpleIdentifier) {
- await _proposeClassOrMixinMember(
- builder,
- node.token,
- parent.realTarget,
- (element) => element is MethodElement && !element.isOperator,
- );
+ if (node is SimpleIdentifier) {
+ if (parent is DotShorthandInvocation) {
+ await _proposeClassOrMixinMemberForDotShorthand(
+ builder,
+ parent,
+ node.token,
+ (element) => element is MethodElement,
+ );
+ } else if (parent is MethodInvocation) {
+ await _proposeClassOrMixinMember(
+ builder,
+ node.token,
+ parent.realTarget,
+ (element) => element is MethodElement && !element.isOperator,
+ );
+ }
}
}
diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
index 72b9ac0..f866a11 100644
--- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
+++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
@@ -517,9 +517,9 @@
CompileTimeErrorCode.DOT_SHORTHAND_MISSING_CONTEXT:
status: needsEvaluation
CompileTimeErrorCode.DOT_SHORTHAND_UNDEFINED_GETTER:
- status: needsEvaluation
+ status: hasFix
CompileTimeErrorCode.DOT_SHORTHAND_UNDEFINED_INVOCATION:
- status: needsEvaluation
+ status: hasFix
CompileTimeErrorCode.DUPLICATE_CONSTRUCTOR_DEFAULT:
status: noFix
notes: |-
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
index dfdbe2a..29b6888 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -626,6 +626,10 @@
RemoveDefaultValue.new,
RemoveRequired.new,
],
+ CompileTimeErrorCode.DOT_SHORTHAND_UNDEFINED_GETTER: [
+ ChangeTo.getterOrSetter,
+ ],
+ CompileTimeErrorCode.DOT_SHORTHAND_UNDEFINED_INVOCATION: [ChangeTo.method],
CompileTimeErrorCode.EMPTY_MAP_PATTERN: [
ReplaceEmptyMapPattern.any,
ReplaceEmptyMapPattern.empty,
diff --git a/pkg/analysis_server/lib/src/services/correction/util.dart b/pkg/analysis_server/lib/src/services/correction/util.dart
index b6bc71e..1597259 100644
--- a/pkg/analysis_server/lib/src/services/correction/util.dart
+++ b/pkg/analysis_server/lib/src/services/correction/util.dart
@@ -5,6 +5,7 @@
import 'dart:math';
import 'package:_fe_analyzer_shared/src/scanner/token.dart';
+import 'package:analysis_server/src/services/completion/dart/feature_computer.dart';
import 'package:analyzer/dart/ast/precedence.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
@@ -34,6 +35,26 @@
}
}
+/// Computes the [InterfaceElement] of the context type of a given
+/// [dotShorthandNode].
+///
+/// Returns `null` if no context type exists or the context type isn't an
+/// [InterfaceType].
+InterfaceElement? computeDotShorthandContextTypeElement(
+ AstNode dotShorthandNode,
+ LibraryElement libraryElement,
+) {
+ var featureComputer = FeatureComputer(
+ libraryElement.typeSystem,
+ libraryElement.typeProvider,
+ );
+ var contextType = featureComputer.computeContextType(
+ dotShorthandNode,
+ dotShorthandNode.offset,
+ );
+ return contextType is InterfaceType ? contextType.element : null;
+}
+
/// Return references to the [element] inside the [root] node.
List<AstNode> findImportPrefixElementReferences(
AstNode root,
diff --git a/pkg/analysis_server/test/src/services/correction/fix/change_to_test.dart b/pkg/analysis_server/test/src/services/correction/fix/change_to_test.dart
index 1fee748..f37e60a 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/change_to_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/change_to_test.dart
@@ -160,6 +160,23 @@
''');
}
+ Future<void> test_enum_constant_dotShorthand() async {
+ await resolveTestCode('''
+enum E { ONE }
+
+E e() {
+ return .OEN;
+}
+''');
+ await assertHasFix('''
+enum E { ONE }
+
+E e() {
+ return .ONE;
+}
+''');
+ }
+
Future<void> test_enum_getter() async {
await resolveTestCode('''
enum E { ONE }
@@ -397,6 +414,25 @@
''');
}
+ Future<void> test_getter_qualified_static_dotShorthand() async {
+ await resolveTestCode('''
+class A {
+ static int MY_NAME = 1;
+}
+A f() {
+ return .MY_NAM;
+}
+''');
+ await assertHasFix('''
+class A {
+ static int MY_NAME = 1;
+}
+A f() {
+ return .MY_NAME;
+}
+''');
+ }
+
Future<void> test_getter_static() async {
await resolveTestCode('''
extension E on int {
@@ -580,6 +616,44 @@
''');
}
+ Future<void> test_method_static_dotShorthand_class() async {
+ await resolveTestCode('''
+class E {
+ static E myMethod() => E();
+}
+E f() {
+ return .myMehod();
+}
+''');
+ await assertHasFix('''
+class E {
+ static E myMethod() => E();
+}
+E f() {
+ return .myMethod();
+}
+''');
+ }
+
+ Future<void> test_method_static_dotShorthand_extensionType() async {
+ await resolveTestCode('''
+extension type E(int x) {
+ static E myMethod() => E(1);
+}
+E f() {
+ return .myMehod();
+}
+''');
+ await assertHasFix('''
+extension type E(int x) {
+ static E myMethod() => E(1);
+}
+E f() {
+ return .myMethod();
+}
+''');
+ }
+
Future<void> test_method_unqualified_superClass() async {
await resolveTestCode('''
class A {