[analysis_server] Take prefixes into account when determining the visibility of completions
A prefixed identifier like `a.Foo` should not hide a prefixed identifier like `b.Foo` not an unprefixed identifier (and vice-versa).
Fixes https://github.com/Dart-Code/Dart-Code/issues/5242
Change-Id: I8be8b3260f2a9f4333d38bba7ffe309cd3817932
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/383641
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Keerti Parthasarathy <keertip@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/visibility_tracker.dart b/pkg/analysis_server/lib/src/services/completion/dart/visibility_tracker.dart
index 6a71a35..eca3029 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/visibility_tracker.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/visibility_tracker.dart
@@ -25,9 +25,15 @@
var name = element?.displayName;
if (name == null) {
return false;
- } else if (importData?.isNotImported ?? false) {
- return !_declaredNames.contains(name);
}
- return _declaredNames.add(name);
+
+ var isNotImported = importData?.isNotImported ?? false;
+ var prefix = importData?.prefix;
+ var qualifiedName = prefix != null ? '$prefix.$name' : name;
+
+ if (isNotImported) {
+ return !_declaredNames.contains(qualifiedName);
+ }
+ return _declaredNames.add(qualifiedName);
}
}
diff --git a/pkg/analysis_server/test/completion_test_support.dart b/pkg/analysis_server/test/completion_test_support.dart
index 5bed7c2..2777810 100644
--- a/pkg/analysis_server/test/completion_test_support.dart
+++ b/pkg/analysis_server/test/completion_test_support.dart
@@ -18,7 +18,7 @@
.toList();
void assertHasCompletion(String completion,
- {ElementKind? elementKind, bool? isDeprecated}) {
+ {ElementKind? elementKind, bool? isDeprecated, Matcher? libraryUri}) {
var expectedOffset = completion.indexOf(CURSOR_MARKER);
if (expectedOffset >= 0) {
if (completion.contains(CURSOR_MARKER, expectedOffset + 1)) {
@@ -58,6 +58,9 @@
if (isDeprecated != null) {
expect(matchingSuggestion.isDeprecated, isDeprecated);
}
+ if (libraryUri != null) {
+ expect(matchingSuggestion.libraryUri, libraryUri);
+ }
}
void assertHasNoCompletion(String completion) {
diff --git a/pkg/analysis_server/test/services/completion/dart/test_all.dart b/pkg/analysis_server/test/services/completion/dart/test_all.dart
index f8b9911..c669869 100644
--- a/pkg/analysis_server/test/services/completion/dart/test_all.dart
+++ b/pkg/analysis_server/test/services/completion/dart/test_all.dart
@@ -10,6 +10,7 @@
import 'relevance/test_all.dart' as relevance_tests;
import 'shadowing_test.dart' as shadowing_test;
import 'text_expectations.dart';
+import 'visibility/test_all.dart' as visibility;
void main() {
defineReflectiveSuite(() {
@@ -18,6 +19,7 @@
location.main();
relevance_tests.main();
shadowing_test.main();
+ visibility.main();
defineReflectiveTests(UpdateTextExpectations);
}, name: 'dart');
}
diff --git a/pkg/analysis_server/test/services/completion/dart/visibility/test_all.dart b/pkg/analysis_server/test/services/completion/dart/visibility/test_all.dart
new file mode 100644
index 0000000..b69a5db
--- /dev/null
+++ b/pkg/analysis_server/test/services/completion/dart/visibility/test_all.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'types_test.dart' as types;
+
+void main() {
+ defineReflectiveSuite(() {
+ types.main();
+ }, name: 'visibility');
+}
diff --git a/pkg/analysis_server/test/services/completion/dart/visibility/types_test.dart b/pkg/analysis_server/test/services/completion/dart/visibility/types_test.dart
new file mode 100644
index 0000000..4df3e5c
--- /dev/null
+++ b/pkg/analysis_server/test/services/completion/dart/visibility/types_test.dart
@@ -0,0 +1,120 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../../../../completion_test_support.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(CompletionVisibilityTest);
+ });
+}
+
+@reflectiveTest
+class CompletionVisibilityTest extends CompletionTestCase {
+ Future<void> test_imported() async {
+ newFile(convertPath('$testPackageLibPath/lib.dart'), '''
+class C {}
+''');
+ await getTestCodeSuggestions('''
+import 'lib.dart';
+
+C^
+''');
+ assertHasCompletion('C');
+ }
+
+ Future<void> test_imported_localAndMultiplePrefixes() async {
+ newFile(convertPath('$testPackageLibPath/lib.dart'), '''
+class C {}
+''');
+ await getTestCodeSuggestions('''
+import 'lib.dart' as l1;
+import 'lib.dart' as l2;
+
+class C {}
+
+C^
+''');
+ assertHasCompletion('C');
+ assertHasCompletion('l1.C');
+ assertHasCompletion('l2.C');
+ }
+
+ Future<void> test_imported_localAndUnprefixed() async {
+ newFile(convertPath('$testPackageLibPath/lib.dart'), '''
+class C {}
+''');
+ await getTestCodeSuggestions('''
+import 'lib.dart';
+
+class C {}
+
+C^
+''');
+ // Verify exactly one, and it was the local one.
+ assertHasCompletion('C', libraryUri: isNull);
+ }
+
+ Future<void> test_imported_multiplePrefixes() async {
+ newFile(convertPath('$testPackageLibPath/lib.dart'), '''
+class C {}
+''');
+ await getTestCodeSuggestions('''
+import 'lib.dart' as l1;
+import 'lib.dart' as l2;
+
+C^
+''');
+ assertHasCompletion('l1.C');
+ assertHasCompletion('l2.C');
+ }
+
+ Future<void> test_imported_prefix() async {
+ newFile(convertPath('$testPackageLibPath/lib.dart'), '''
+class C {}
+''');
+ await getTestCodeSuggestions('''
+import 'lib.dart' as l;
+
+C^
+''');
+ assertHasCompletion('l.C');
+ }
+
+ Future<void> test_local() async {
+ await getTestCodeSuggestions('''
+class C {}
+
+C^
+''');
+ assertHasCompletion('C');
+ }
+
+ Future<void> test_localAndNotImported() async {
+ newFile(convertPath('$testPackageLibPath/lib.dart'), '''
+class C {}
+''');
+ await getTestCodeSuggestions('''
+class C {}
+
+C^
+''');
+ // This verifies exactly one, since we won't suggest the not-imported
+ // when it matches a local name.
+ assertHasCompletion('C');
+ }
+
+ Future<void> test_notImported() async {
+ newFile(convertPath('$testPackageLibPath/lib.dart'), '''
+class C {}
+''');
+ await getTestCodeSuggestions('''
+C^
+''');
+ assertHasCompletion('C');
+ }
+}