[Analyzer] Fix error in LSP go-to-definition when target is in a "part"
Plus unrelated navigation regions (for example in comments, references
outside of the required range would be returned).
Fixes https://github.com/dart-lang/sdk/issues/28799.
Change-Id: I2fce33a3bd2678abfb40e5e9b3e3c5662e4f5608
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/161948
Commit-Queue: Danny Tuppeny <danny@tuppeny.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/test/analysis/get_navigation_test.dart b/pkg/analysis_server/test/analysis/get_navigation_test.dart
index f2f2cf1..4dbe557 100644
--- a/pkg/analysis_server/test/analysis/get_navigation_test.dart
+++ b/pkg/analysis_server/test/analysis/get_navigation_test.dart
@@ -43,6 +43,29 @@
assertHasTarget('test = 0');
}
+ Future<void> test_comment_outsideReference() async {
+ addTestFile('''
+/// Returns a [String].
+String main() {
+}''');
+ await waitForTasksFinished();
+ var search = 'Returns';
+ await _getNavigation(testFile, testCode.indexOf(search), 1);
+ expect(regions, hasLength(0));
+ }
+
+ Future<void> test_comment_reference() async {
+ addTestFile('''
+/// Returns a [String].
+String main() {
+}''');
+ await waitForTasksFinished();
+ var search = '[String';
+ await _getNavigation(testFile, testCode.indexOf(search), 1);
+ expect(regions, hasLength(1));
+ assertHasRegion('String]');
+ }
+
Future<void> test_fieldType() async {
// This test mirrors test_navigation() from
// test/integration/analysis/get_navigation_test.dart
@@ -101,20 +124,6 @@
expect(testTargets[0].kind, ElementKind.LIBRARY);
}
- Future<void> test_importKeyword() async {
- addTestFile('''
-import 'dart:math';
-
-main() {
-}''');
- await waitForTasksFinished();
- await _getNavigation(testFile, 0, 1);
- expect(regions, hasLength(1));
- assertHasRegionString("'dart:math'");
- expect(testTargets, hasLength(1));
- expect(testTargets[0].kind, ElementKind.LIBRARY);
- }
-
Future<void> test_importUri() async {
addTestFile('''
import 'dart:math';
diff --git a/pkg/analysis_server/test/integration/analysis/get_navigation_test.dart b/pkg/analysis_server/test/integration/analysis/get_navigation_test.dart
index bd69280..1288adb 100644
--- a/pkg/analysis_server/test/integration/analysis/get_navigation_test.dart
+++ b/pkg/analysis_server/test/integration/analysis/get_navigation_test.dart
@@ -41,9 +41,7 @@
expect(target.startColumn, 7);
}
- @failingTest
Future<void> test_navigation_no_result() async {
- // This fails - it returns navigation results for a whitespace area (#28799).
var pathname = sourcePath('test.dart');
var text = r'''
//
diff --git a/pkg/analysis_server/test/lsp/definition_test.dart b/pkg/analysis_server/test/lsp/definition_test.dart
index a243579..d64412c 100644
--- a/pkg/analysis_server/test/lsp/definition_test.dart
+++ b/pkg/analysis_server/test/lsp/definition_test.dart
@@ -55,6 +55,26 @@
expect(loc.uri, equals(referencedFileUri.toString()));
}
+ Future<void> test_comment_adjacentReference() async {
+ /// Computing Dart navigation locates a node at the provided offset then
+ /// returns all navigation regions inside it. This test ensures we filter
+ /// out any regions that are in the same target node (the comment) but do
+ /// not span the requested offset.
+ final contents = '''
+ /// Te^st
+ ///
+ /// References [String].
+ main() {}
+ ''';
+
+ await initialize();
+ await openFile(mainFileUri, withoutMarkers(contents));
+ final res = await getDefinitionAsLocation(
+ mainFileUri, positionFromMarker(contents));
+
+ expect(res, hasLength(0));
+ }
+
Future<void> test_fromPlugins() async {
final pluginAnalyzedFilePath = join(projectFolderPath, 'lib', 'foo.foo');
final pluginAnalyzedFileUri = Uri.file(pluginAnalyzedFilePath);
@@ -176,6 +196,59 @@
expect(res, isEmpty);
}
+ /// Failing due to incorrect range because _DartNavigationCollector._getCodeLocation
+ /// does not handle parts.
+ @failingTest
+ Future<void> test_part() async {
+ final mainContents = '''
+ import 'lib.dart';
+
+ main() {
+ Icons.[[ad^d]]();
+ }
+ ''';
+
+ final libContents = '''
+ part 'part.dart';
+ ''';
+
+ final partContents = '''
+ part of 'lib.dart';
+
+ void unrelatedFunction() {}
+
+ class Icons {
+ /// `targetRange` should not include the dartDoc but should include the full
+ /// function body. `targetSelectionRange` will be just the name.
+ [[String add = "Test"]];
+ }
+
+ void otherUnrelatedFunction() {}
+ ''';
+
+ final libFileUri = Uri.file(join(projectFolderPath, 'lib', 'lib.dart'));
+ final partFileUri = Uri.file(join(projectFolderPath, 'lib', 'part.dart'));
+
+ await initialize(
+ textDocumentCapabilities:
+ withLocationLinkSupport(emptyTextDocumentClientCapabilities));
+ await openFile(mainFileUri, withoutMarkers(mainContents));
+ await openFile(libFileUri, withoutMarkers(libContents));
+ await openFile(partFileUri, withoutMarkers(partContents));
+ final res = await getDefinitionAsLocationLinks(
+ mainFileUri, positionFromMarker(mainContents));
+
+ expect(res, hasLength(1));
+ var loc = res.single;
+ expect(loc.originSelectionRange, equals(rangeFromMarkers(mainContents)));
+ expect(loc.targetUri, equals(partFileUri.toString()));
+ expect(loc.targetRange, equals(rangeFromMarkers(partContents)));
+ expect(
+ loc.targetSelectionRange,
+ equals(rangeOfString(partContents, 'add')),
+ );
+ }
+
Future<void> test_sameLine() async {
final contents = '''
int plusOne(int [[value]]) => 1 + val^ue;
diff --git a/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart b/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
index efa84ae..cd05675 100644
--- a/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
+++ b/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
@@ -19,7 +19,7 @@
CompilationUnit unit,
int offset,
int length) {
- var dartCollector = _DartNavigationCollector(collector);
+ var dartCollector = _DartNavigationCollector(collector, offset, length);
var visitor = _DartNavigationComputerVisitor(resourceProvider, dartCollector);
if (offset == null || length == null) {
unit.accept(visitor);
@@ -43,8 +43,11 @@
/// A Dart specific wrapper around [NavigationCollector].
class _DartNavigationCollector {
final NavigationCollector collector;
+ final int requestedOffset;
+ final int requestedLength;
- _DartNavigationCollector(this.collector);
+ _DartNavigationCollector(
+ this.collector, this.requestedOffset, this.requestedLength);
void _addRegion(int offset, int length, Element element) {
if (element is FieldFormalParameterElement) {
@@ -56,6 +59,12 @@
if (element.location == null) {
return;
}
+ // Discard elements that don't span the offset/range given (if provided).
+ if (requestedOffset != null &&
+ (offset > requestedOffset + (requestedLength ?? 0) ||
+ offset + length < requestedOffset)) {
+ return;
+ }
var converter = AnalyzerConverter();
var kind = converter.convertElementKind(element.kind);
var location = converter.locationFromElement(element);
@@ -118,9 +127,13 @@
}
// Read the declaration so we can get the offset after the doc comments.
- final declaration = codeElement.session
- .getParsedLibrary(location.file)
- .getElementDeclaration(codeElement);
+ // TODO(dantup): Skip this for parts (getParsedLibrary will throw), but find
+ // a better solution.
+ final declaration = !codeElement.session.getFile(location.file).isPart
+ ? codeElement.session
+ .getParsedLibrary(location.file)
+ .getElementDeclaration(codeElement)
+ : null;
var node = declaration?.node;
if (node is VariableDeclaration) {
node = node.parent;