Skip to content

Commit aa2cd45

Browse files
scheglovCommit Queue
authored and
Commit Queue
committed
Completion. Issue 35449. Don't suggest @VisibleForTesting instance members when not available.
Bug: #35449 Change-Id: I7e02d7fbbc9477ad98dd87327adf3b562fc0fde8 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/358328 Commit-Queue: Konstantin Shcheglov <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]>
1 parent 2a7fc06 commit aa2cd45

File tree

8 files changed

+240
-28
lines changed

8 files changed

+240
-28
lines changed

pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart

-8
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import 'package:analysis_server/src/services/completion/dart/record_literal_cont
1818
import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
1919
import 'package:analysis_server/src/services/completion/dart/suggestion_collector.dart';
2020
import 'package:analysis_server/src/services/completion/dart/uri_contributor.dart';
21-
import 'package:analysis_server/src/utilities/extensions/object.dart';
2221
import 'package:analysis_server/src/utilities/selection.dart';
2322
import 'package:analyzer/dart/analysis/features.dart';
2423
import 'package:analyzer/dart/analysis/results.dart';
@@ -39,7 +38,6 @@ import 'package:analyzer/src/dartdoc/dartdoc_directive_info.dart';
3938
import 'package:analyzer/src/generated/source.dart' show SourceFactory;
4039
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
4140
import 'package:analyzer/src/util/performance/operation_performance.dart';
42-
import 'package:analyzer/src/workspace/pub.dart';
4341
import 'package:analyzer_plugin/protocol/protocol_common.dart' as protocol;
4442
import 'package:analyzer_plugin/src/utilities/completion/completion_target.dart';
4543
import 'package:analyzer_plugin/src/utilities/completion/optype.dart';
@@ -412,12 +410,6 @@ class DartCompletionRequest {
412410
/// Answer the [DartType] for Object in dart:core
413411
InterfaceType get objectType => libraryElement.typeProvider.objectType;
414412

415-
/// Returns the [PubPackage] of the file where completion is requested.
416-
/// Or `null` if the package is not [PubPackage].
417-
PubPackage? get pubPackage {
418-
return fileState.workspacePackage.ifTypeOrNull();
419-
}
420-
421413
/// The length of the text to be replaced if the remainder of the identifier
422414
/// containing the cursor is to be replaced when the suggestion is applied
423415
/// (that is, the number of characters in the existing identifier).

pkg/analysis_server/lib/src/services/completion/dart/declaration_helper.dart

+23-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import 'package:analyzer/src/dart/element/member.dart';
1717
import 'package:analyzer/src/dart/element/type_algebra.dart';
1818
import 'package:analyzer/src/dart/resolver/scope.dart';
1919
import 'package:analyzer/src/utilities/extensions/element.dart';
20+
import 'package:analyzer/src/workspace/pub.dart';
2021

2122
/// A helper class that produces candidate suggestions for all of the
2223
/// declarations that are in scope at the completion location.
@@ -1031,10 +1032,11 @@ class DeclarationHelper {
10311032
}
10321033

10331034
if (element.isInternal) {
1034-
if (request.pubPackage case var pubPackage?) {
1035-
if (!pubPackage.contains(element.librarySource)) {
1036-
return false;
1037-
}
1035+
switch (request.fileState.workspacePackage) {
1036+
case PubPackage pubPackage:
1037+
if (!pubPackage.contains(element.librarySource)) {
1038+
return false;
1039+
}
10381040
}
10391041
}
10401042

@@ -1057,6 +1059,23 @@ class DeclarationHelper {
10571059
}
10581060
}
10591061

1062+
if (element.isVisibleForTesting) {
1063+
if (element.library != requestLibrary) {
1064+
var fileState = request.fileState;
1065+
switch (fileState.workspacePackage) {
1066+
case PubPackage pubPackage:
1067+
// Must be in the same package.
1068+
if (!pubPackage.contains(element.librarySource)) {
1069+
return false;
1070+
}
1071+
// Must be in the `test` directory.
1072+
if (!pubPackage.isInTestDirectory(fileState.resource)) {
1073+
return false;
1074+
}
1075+
}
1076+
}
1077+
}
1078+
10601079
return true;
10611080
}
10621081

pkg/analysis_server/test/analysis_server_base.dart

+3-2
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,9 @@ abstract class ContextResolutionTest with ResourceProviderMixin {
207207

208208
class PubPackageAnalysisServerTest extends ContextResolutionTest
209209
with MockPackagesMixin, ConfigurationFilesMixin, TestMacros {
210+
// TODO(scheglov): Consider turning it back into a getter.
211+
late String testFilePath = '$testPackageLibPath/test.dart';
212+
210213
// If experiments are needed,
211214
// add `import 'package:analyzer/dart/analysis/features.dart';`
212215
// and list the necessary experiments here.
@@ -227,8 +230,6 @@ class PubPackageAnalysisServerTest extends ContextResolutionTest
227230

228231
String get testFileContent => testFile.readAsStringSync();
229232

230-
String get testFilePath => '$testPackageLibPath/test.dart';
231-
232233
String get testPackageLibPath => '$testPackageRootPath/lib';
233234

234235
Folder get testPackageRoot => getFolder(testPackageRootPath);

pkg/analysis_server/test/services/completion/dart/location/property_access_expression_test.dart

+171
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,177 @@ suggestions
550550
kind: setter
551551
f02
552552
kind: setter
553+
''');
554+
}
555+
556+
Future<void> test_isVisibleForTesting_method_otherPackage() async {
557+
var otherRoot = getFolder('$packagesRootPath/other');
558+
newFile('${otherRoot.path}/lib/a.dart', r'''
559+
import 'package:meta/meta.dart';
560+
561+
class A {
562+
void f01() {}
563+
564+
@visibleForTesting
565+
void f02() {}
566+
}
567+
''');
568+
569+
writeTestPackageConfig(
570+
config: PackageConfigFileBuilder()
571+
..add(
572+
name: 'other',
573+
rootPath: otherRoot.path,
574+
),
575+
meta: true,
576+
);
577+
578+
await computeSuggestions('''
579+
import 'package:other/a.dart''
580+
581+
void f() {
582+
A().^
583+
}
584+
''');
585+
586+
assertResponse(r'''
587+
suggestions
588+
f01
589+
kind: methodInvocation
590+
''');
591+
}
592+
593+
Future<void> test_isVisibleForTesting_method_otherPackage_test() async {
594+
var otherRoot = getFolder('$packagesRootPath/other');
595+
newFile('${otherRoot.path}/lib/a.dart', r'''
596+
import 'package:meta/meta.dart';
597+
598+
class A {
599+
void f01() {}
600+
601+
@visibleForTesting
602+
void f02() {}
603+
}
604+
''');
605+
606+
writeTestPackageConfig(
607+
config: PackageConfigFileBuilder()
608+
..add(
609+
name: 'other',
610+
rootPath: otherRoot.path,
611+
),
612+
meta: true,
613+
);
614+
615+
testFilePath = '$testPackageTestPath/test.dart';
616+
await computeSuggestions('''
617+
import 'package:other/a.dart''
618+
619+
void f() {
620+
A().^
621+
}
622+
''');
623+
624+
assertResponse(r'''
625+
suggestions
626+
f01
627+
kind: methodInvocation
628+
''');
629+
}
630+
631+
Future<void> test_isVisibleForTesting_method_sameLibrary() async {
632+
writeTestPackageConfig(
633+
meta: true,
634+
);
635+
636+
await computeSuggestions('''
637+
import 'package:meta/meta.dart';
638+
639+
class A {
640+
void f01() {}
641+
642+
@visibleForTesting
643+
void f02() {}
644+
}
645+
646+
void f(A a) {
647+
a.^
648+
}
649+
''');
650+
651+
assertResponse(r'''
652+
suggestions
653+
f01
654+
kind: methodInvocation
655+
f02
656+
kind: methodInvocation
657+
''');
658+
}
659+
660+
Future<void>
661+
test_isVisibleForTesting_method_samePackage_otherLibrary() async {
662+
writeTestPackageConfig(
663+
meta: true,
664+
);
665+
666+
newFile('$testPackageLibPath/a.dart', r'''
667+
import 'package:meta/meta.dart';
668+
669+
class A {
670+
void f01() {}
671+
672+
@visibleForTesting
673+
void f02() {}
674+
}
675+
''');
676+
677+
await computeSuggestions('''
678+
import 'a.dart';
679+
680+
void f(A a) {
681+
a.^
682+
}
683+
''');
684+
685+
assertResponse(r'''
686+
suggestions
687+
f01
688+
kind: methodInvocation
689+
''');
690+
}
691+
692+
Future<void>
693+
test_isVisibleForTesting_method_samePackage_otherLibrary_test() async {
694+
writeTestPackageConfig(
695+
meta: true,
696+
);
697+
698+
newFile('$testPackageLibPath/a.dart', r'''
699+
import 'package:meta/meta.dart';
700+
701+
class A {
702+
void f01() {}
703+
704+
@visibleForTesting
705+
void f02() {}
706+
}
707+
''');
708+
709+
testFilePath = '$testPackageTestPath/test.dart';
710+
await computeSuggestions('''
711+
import 'package:test/a.dart';
712+
713+
void f(A a) {
714+
a.^
715+
}
716+
''');
717+
718+
assertResponse(r'''
719+
suggestions
720+
f01
721+
kind: methodInvocation
722+
f02
723+
kind: methodInvocation
553724
''');
554725
}
555726
}

pkg/analyzer/lib/src/error/best_practices_verifier.dart

+1-12
Original file line numberDiff line numberDiff line change
@@ -1800,7 +1800,7 @@ class _InvalidAccessVerifier {
18001800
}
18011801
}
18021802

1803-
bool hasVisibleForTesting = _hasVisibleForTesting(element);
1803+
var hasVisibleForTesting = element.isVisibleForTesting;
18041804
if (hasVisibleForTesting) {
18051805
if (_inTestDirectory || _inExportDirective(node)) {
18061806
return;
@@ -1935,17 +1935,6 @@ class _InvalidAccessVerifier {
19351935
return false;
19361936
}
19371937

1938-
bool _hasVisibleForTesting(Element element) {
1939-
if (element.hasVisibleForTesting) {
1940-
return true;
1941-
}
1942-
if (element is PropertyAccessorElement) {
1943-
var variable = element.variable2;
1944-
return variable != null && variable.hasVisibleForTesting;
1945-
}
1946-
return false;
1947-
}
1948-
19491938
bool _hasVisibleOutsideTemplate(Element element) {
19501939
if (element.hasVisibleOutsideTemplate) {
19511940
return true;

pkg/analyzer/lib/src/utilities/extensions/element.dart

+17-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:analyzer/dart/element/nullability_suffix.dart';
77
import 'package:analyzer/dart/element/type.dart';
88
import 'package:analyzer/src/dart/ast/ast.dart';
99
import 'package:analyzer/src/dart/element/element.dart';
10+
import 'package:meta/meta.dart';
1011

1112
extension ElementExtension on Element {
1213
// TODO(scheglov): Maybe just add to `Element`?
@@ -17,7 +18,7 @@ extension ElementExtension on Element {
1718
return null;
1819
}
1920

20-
/// Whether the element is effectively internal.
21+
/// Whether the element is effectively [internal].
2122
bool get isInternal {
2223
if (hasInternal) {
2324
return true;
@@ -31,7 +32,7 @@ extension ElementExtension on Element {
3132
return false;
3233
}
3334

34-
/// Whether the element is effectively protected.
35+
/// Whether the element is effectively [protected].
3536
bool get isProtected {
3637
final self = this;
3738
if (self is PropertyAccessorElement &&
@@ -52,6 +53,20 @@ extension ElementExtension on Element {
5253
return false;
5354
}
5455

56+
/// Whether the element is effectively [visibleForTesting].
57+
bool get isVisibleForTesting {
58+
if (hasVisibleForTesting) {
59+
return true;
60+
}
61+
if (this case PropertyAccessorElement accessor) {
62+
var variable = accessor.variable2;
63+
if (variable != null && variable.hasVisibleForTesting) {
64+
return true;
65+
}
66+
}
67+
return false;
68+
}
69+
5570
List<Element> get withAugmentations {
5671
final result = <Element>[];
5772
Element? current = this;

pkg/analyzer/lib/src/workspace/pub.dart

+7
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,13 @@ class PubPackage extends WorkspacePackage {
456456
return false;
457457
}
458458

459+
/// Whether [file] is in the `test` directory of this package.
460+
bool isInTestDirectory(File file) {
461+
var resourceProvider = workspace.provider;
462+
var packageRoot = resourceProvider.getFolder(root);
463+
return packageRoot.getChildAssumingFolder('test').contains(file.path);
464+
}
465+
459466
@override
460467
Packages packagesAvailableTo(String libraryPath) {
461468
// TODO(brianwilkerson): Consider differentiating based on whether the

pkg/analyzer/test/src/workspace/pub_test.dart

+18
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,24 @@ class PubPackageTest extends WorkspacePackageTest {
724724
expect(package, isNull);
725725
}
726726

727+
test_isInTestDirectory() {
728+
var pubPackage = myPackage as PubPackage;
729+
730+
expect(
731+
pubPackage.isInTestDirectory(
732+
getFile('$myPackageRootPath/lib/a.dart'),
733+
),
734+
isFalse,
735+
);
736+
737+
expect(
738+
pubPackage.isInTestDirectory(
739+
getFile('$myPackageRootPath/test/a.dart'),
740+
),
741+
isTrue,
742+
);
743+
}
744+
727745
void test_packagesAvailableTo() {
728746
var libraryPath = convertPath('/workspace/lib/test.dart');
729747
var package = findPackage(libraryPath)!;

0 commit comments

Comments
 (0)