diff --git a/pkg/analysis_server/lib/src/services/completion/dart/override_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/override_contributor.dart index 28c0d6fbc50..cb5c05eb9b5 100644 --- a/pkg/analysis_server/lib/src/services/completion/dart/override_contributor.dart +++ b/pkg/analysis_server/lib/src/services/completion/dart/override_contributor.dart @@ -15,6 +15,7 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart'; +import 'package:analyzer/source/source_range.dart'; import 'package:analyzer/src/dart/element/inheritance_manager3.dart'; import 'package:analyzer_plugin/src/utilities/completion/completion_target.dart'; import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart'; @@ -26,14 +27,19 @@ class OverrideContributor implements DartCompletionContributor { @override Future> computeSuggestions( DartCompletionRequest request, SuggestionBuilder builder) async { - var targetId = _getTargetId(request.target); - if (targetId == null) { - return const []; - } - var classDecl = targetId.thisOrAncestorOfType(); + var target = request.target; + var containingNode = target.containingNode; + var classDecl = + containingNode.thisOrAncestorOfType(); if (classDecl == null) { return const []; } + if (_inClassMemberBody(containingNode)) { + return const []; + } + + var sourceRange = _getTargetSourceRange(target); + sourceRange ??= range.startOffsetEndOffset(request.offset, 0); var inheritance = InheritanceManager3(); @@ -53,7 +59,7 @@ class OverrideContributor implements DartCompletionContributor { if (element.returnType != null) { var invokeSuper = interface.isSuperImplemented(name); var suggestion = - await _buildSuggestion(request, targetId, element, invokeSuper); + await _buildSuggestion(request, sourceRange, element, invokeSuper); if (suggestion != null) { suggestions.add(suggestion); } @@ -62,17 +68,17 @@ class OverrideContributor implements DartCompletionContributor { return suggestions; } - /// Build a suggestion to replace [targetId] in the given [request] with an + /// Build a suggestion to replace [sourceRange] in the given [request] with an /// override of the given [element]. Future _buildSuggestion( DartCompletionRequest request, - SimpleIdentifier targetId, + SourceRange sourceRange, ExecutableElement element, bool invokeSuper) async { var displayTextBuffer = StringBuffer(); var builder = DartChangeBuilder(request.result.session); await builder.addFileEdit(request.result.path, (builder) { - builder.addReplacement(range.node(targetId), (builder) { + builder.addReplacement(sourceRange, (builder) { builder.writeOverride( element, displayTextBuffer: displayTextBuffer, @@ -102,7 +108,7 @@ class OverrideContributor implements DartCompletionContributor { if (selectionRange == null) { return null; } - var offsetDelta = targetId.offset + replacement.indexOf(completion); + var offsetDelta = sourceRange.offset + replacement.indexOf(completion); var displayText = displayTextBuffer.isNotEmpty ? displayTextBuffer.toString() : null; var suggestion = CompletionSuggestion( @@ -118,24 +124,6 @@ class OverrideContributor implements DartCompletionContributor { return suggestion; } - /// If the target looks like a partial identifier inside a class declaration - /// then return that identifier, otherwise return `null`. - SimpleIdentifier _getTargetId(CompletionTarget target) { - var node = target.containingNode; - if (node is ClassOrMixinDeclaration) { - var entity = target.entity; - if (entity is FieldDeclaration) { - return _getTargetIdFromVarList(entity.fields); - } - } else if (node is FieldDeclaration) { - var entity = target.entity; - if (entity is VariableDeclarationList) { - return _getTargetIdFromVarList(entity); - } - } - return null; - } - SimpleIdentifier _getTargetIdFromVarList(VariableDeclarationList fields) { var variables = fields.variables; if (variables.length == 1) { @@ -157,6 +145,29 @@ class OverrideContributor implements DartCompletionContributor { return null; } + /// If the target looks like a partial identifier inside a class declaration + /// then return that identifier [SourceRange], otherwise return `null`. + SourceRange _getTargetSourceRange(CompletionTarget target) { + var containingNode = target.containingNode; + if (containingNode is ClassOrMixinDeclaration) { + if (target.entity is FieldDeclaration) { + var fieldDecl = target.entity as FieldDeclaration; + var simpleIdentifier = _getTargetIdFromVarList(fieldDecl.fields); + if (simpleIdentifier != null) { + return range.node(simpleIdentifier); + } + } + } else if (containingNode is FieldDeclaration) { + if (target.entity is VariableDeclarationList) { + var simpleIdentifier = _getTargetIdFromVarList(target.entity); + if (simpleIdentifier != null) { + return range.node(simpleIdentifier); + } + } + } + return null; + } + /// Return `true` if the given [node] has an `override` annotation. bool _hasOverride(AstNode node) { if (node is AnnotatedNode) { @@ -205,4 +216,20 @@ class OverrideContributor implements DartCompletionContributor { nullabilitySuffix: NullabilitySuffix.none, ); } + + static bool _inClassMemberBody(AstNode node) { + /// TODO(jwren) this method was copied from keyword_contributor.dart, all + /// related methods should be moved into a superclass or mixin + while (true) { + var body = node.thisOrAncestorOfType(); + if (body == null) { + return false; + } + var parent = body.parent; + if (parent is ConstructorDeclaration || parent is MethodDeclaration) { + return true; + } + node = parent; + } + } } diff --git a/pkg/analysis_server/test/services/completion/dart/override_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/override_contributor_test.dart index ff816ccc366..f0de9a8396e 100644 --- a/pkg/analysis_server/test/services/completion/dart/override_contributor_test.dart +++ b/pkg/analysis_server/test/services/completion/dart/override_contributor_test.dart @@ -285,6 +285,24 @@ method() { selectionLength: 22); } + Future test_no_target_inClass_of_interface() async { + addTestSource(''' +class A { + void foo() {} +} + +class B implements A { + ^ +} +'''); + await computeSuggestions(); + _assertOverride(''' +@override + void foo() { + // TODO: implement foo + }''', displayText: 'foo() { … }', selectionOffset: 51, selectionLength: 0); + } + Future test_outsideOfWorkspace() async { testFile = convertPath('/home/other/lib/a.dart'); addTestSource('''