diff --git a/pkg/analysis_server/lib/src/domain_completion.dart b/pkg/analysis_server/lib/src/domain_completion.dart index 3db886233d13..a35a7589c2d3 100644 --- a/pkg/analysis_server/lib/src/domain_completion.dart +++ b/pkg/analysis_server/lib/src/domain_completion.dart @@ -31,7 +31,6 @@ import 'package:analyzer/src/util/performance/operation_performance.dart'; import 'package:analyzer_plugin/protocol/protocol.dart' as plugin; import 'package:analyzer_plugin/protocol/protocol_common.dart'; import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin; -import 'package:collection/collection.dart'; /// Instances of the class [CompletionDomainHandler] implement a /// [RequestHandler] that handles requests in the completion domain. @@ -208,8 +207,7 @@ class CompletionDomainHandler extends AbstractRequestHandler { performanceList.add(completionPerformance); var analysisSession = resolvedUnit.analysisSession; - var enclosingNode = - resolvedUnit.resolvedNodes.lastOrNull ?? resolvedUnit.parsedUnit; + var enclosingNode = resolvedUnit.parsedUnit; var completionRequest = DartCompletionRequest( analysisSession: analysisSession, diff --git a/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart b/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart index 4fbeca3aec25..80b21282fd30 100644 --- a/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart +++ b/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart @@ -475,6 +475,19 @@ class DartCompletionRequest { } } + if (entity is Token && + entity.type == TokenType.STRING && + entity.offset < offset && + offset < entity.end) { + final uriNode = target.containingNode; + if (uriNode is SimpleStringLiteral && uriNode.literal == entity) { + final directive = uriNode.parent; + if (directive is UriBasedDirective && directive.uri == uriNode) { + return uriNode.value.substring(0, offset - uriNode.contentsOffset); + } + } + } + while (entity is AstNode) { if (entity is SimpleIdentifier) { var identifier = entity.name; 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 983ff5200353..09966598d2bd 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 @@ -540,10 +540,12 @@ class _ContextTypeVisitor extends SimpleAstVisitor { if (offset <= argument.offset) { return typeOfIndexPositionalParameter(); } - if (argument.contains(offset) && offset >= argument.name.end) { - return argument.staticParameterElement?.type; + if (argument.contains(offset)) { + if (offset >= argument.name.end) { + return argument.staticParameterElement?.type; + } + return null; } - return null; } else { if (previousArgument == null || previousArgument.end < offset) { if (offset <= argument.end) { diff --git a/pkg/analysis_server/lib/src/services/completion/dart/fuzzy_filter_sort.dart b/pkg/analysis_server/lib/src/services/completion/dart/fuzzy_filter_sort.dart index 1fbda622ada4..f410bf193a2d 100644 --- a/pkg/analysis_server/lib/src/services/completion/dart/fuzzy_filter_sort.dart +++ b/pkg/analysis_server/lib/src/services/completion/dart/fuzzy_filter_sort.dart @@ -5,6 +5,7 @@ import 'package:analysis_server/src/protocol_server.dart'; import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart'; import 'package:analysis_server/src/services/completion/filtering/fuzzy_matcher.dart'; +import 'package:collection/collection.dart'; final _identifierPattern = RegExp(r'([_a-zA-Z][_a-zA-Z0-9]*)'); @@ -14,7 +15,11 @@ List fuzzyFilterSort({ required String pattern, required List suggestions, }) { - var matcher = FuzzyMatcher(pattern, matchStyle: MatchStyle.SYMBOL); + final matchStyle = + suggestions.firstOrNull?.kind == CompletionSuggestionKind.IMPORT + ? MatchStyle.FILENAME + : MatchStyle.SYMBOL; + final matcher = FuzzyMatcher(pattern, matchStyle: matchStyle); double score(CompletionSuggestionBuilder suggestion) { var textToMatch = suggestion.textToMatch; diff --git a/pkg/analysis_server/lib/src/services/completion/dart/local_library_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/local_library_contributor.dart index 7659c35e1ea4..1b2a1ddb63c5 100644 --- a/pkg/analysis_server/lib/src/services/completion/dart/local_library_contributor.dart +++ b/pkg/analysis_server/lib/src/services/completion/dart/local_library_contributor.dart @@ -9,6 +9,7 @@ import 'package:analysis_server/src/services/completion/dart/completion_manager. import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart' show SuggestionBuilder; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/type.dart'; import 'package:analyzer/dart/element/visitor.dart'; import 'package:analyzer_plugin/src/utilities/completion/optype.dart'; @@ -52,10 +53,14 @@ class LibraryElementSuggestionBuilder extends GeneralizingElementVisitor { _addConstructorSuggestions(element); } if (opType.includeReturnValueSuggestions) { - if (element.isEnum) { - for (var field in element.fields) { - if (field.isEnumConstant) { - builder.suggestEnumConstant(field, prefix: prefix); + final typeSystem = request.libraryElement.typeSystem; + final contextType = request.contextType; + if (contextType is InterfaceType) { + // TODO(scheglov) This looks not ideal - we should suggest getters. + for (final field in element.fields) { + if (field.isStatic && + typeSystem.isSubtypeOf(field.type, contextType)) { + builder.suggestStaticField(field, prefix: prefix); } } } diff --git a/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart index b3a14996de61..d3f7a95d2189 100644 --- a/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart +++ b/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart @@ -210,24 +210,7 @@ class _LocalVisitor extends LocalDeclarationVisitor { @override void declaredClass(ClassDeclaration declaration) { - var classElt = declaration.declaredElement; - if (classElt != null && visibilityTracker._isVisible(classElt)) { - if (opType.includeTypeNameSuggestions) { - builder.suggestClass(classElt); - } - - // Generate the suggestions for the constructors. We are required to loop - // through elements here instead of using declaredConstructor() due to - // implicit constructors (i.e. there is no AstNode for an implicit - // constructor) - if (!opType.isPrefixed && opType.includeConstructorSuggestions) { - for (var constructor in classElt.constructors) { - if (!classElt.isAbstract || constructor.isFactory) { - builder.suggestConstructor(constructor); - } - } - } - } + _declaredClassElement(declaration.declaredElement); } @override @@ -245,20 +228,7 @@ class _LocalVisitor extends LocalDeclarationVisitor { @override void declaredEnum(EnumDeclaration declaration) { - var declaredElement = declaration.declaredElement; - if (declaredElement != null && - visibilityTracker._isVisible(declaredElement) && - opType.includeTypeNameSuggestions) { - builder.suggestClass(declaredElement); - for (var enumConstant in declaration.constants) { - if (!enumConstant.isSynthetic) { - var constantElement = enumConstant.declaredElement; - if (constantElement is FieldElement) { - builder.suggestEnumConstant(constantElement); - } - } - } - } + _declaredClassElement(declaration.declaredElement); } @override @@ -434,6 +404,38 @@ class _LocalVisitor extends LocalDeclarationVisitor { super.visitExtendsClause(node); } + void _declaredClassElement(ClassElement? class_) { + if (class_ != null && visibilityTracker._isVisible(class_)) { + if (opType.includeTypeNameSuggestions) { + builder.suggestClass(class_); + } + + if (!opType.isPrefixed && + opType.includeConstructorSuggestions && + !class_.isEnum) { + for (final constructor in class_.constructors) { + if (!class_.isAbstract || constructor.isFactory) { + builder.suggestConstructor(constructor); + } + } + } + + if (!opType.isPrefixed && opType.includeReturnValueSuggestions) { + final typeSystem = request.libraryElement.typeSystem; + final contextType = request.contextType; + if (contextType is InterfaceType) { + // TODO(scheglov) This looks not ideal - we should suggest getters. + for (final field in class_.fields) { + if (field.isStatic && + typeSystem.isSubtypeOf(field.type, contextType)) { + builder.suggestStaticField(field); + } + } + } + } + } + } + /// Return `true` if the [identifier] is composed of one or more underscore /// characters and nothing else. bool _isUnused(String identifier) => RegExp(r'^_+$').hasMatch(identifier); diff --git a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart index a784e8a2e8d8..e401ba44a7f2 100644 --- a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart +++ b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart @@ -335,10 +335,17 @@ class SuggestionBuilder { /// invocation. The [inheritanceDistance] is the value of the inheritance /// distance feature computed for the accessor or `-1.0` if the accessor is a /// static accessor. - void suggestAccessor(PropertyAccessorElement accessor, - {required double inheritanceDistance}) { - assert(accessor.enclosingElement is ClassElement || - accessor.enclosingElement is ExtensionElement); + void suggestAccessor( + PropertyAccessorElement accessor, { + required double inheritanceDistance, + bool withEnclosingName = false, + }) { + var enclosingPrefix = ''; + var enclosingName = _enclosingClassOrExtensionName(accessor); + if (withEnclosingName && enclosingName != null) { + enclosingPrefix = '$enclosingName.'; + } + if (accessor.isSynthetic) { // Avoid visiting a field twice. All fields induce a getter, but only // non-final fields induce a setter, so we don't add a suggestion for a @@ -378,6 +385,7 @@ class SuggestionBuilder { _addBuilder( _createCompletionSuggestionBuilder( accessor, + completion: enclosingPrefix + accessor.displayName, kind: CompletionSuggestionKind.IDENTIFIER, relevance: relevance, isNotImported: isNotImportedLibrary, @@ -825,6 +833,7 @@ class SuggestionBuilder { // Let the user know that we are going to insert a complete statement. displayText: 'setState(() {});', ), + textToMatchOverride: 'setState', ); return; } @@ -1025,6 +1034,43 @@ class SuggestionBuilder { ); } + /// Add a suggestion for a static field declared within a class or extension. + /// If the field is synthetic, add the corresponding getter instead. + /// + /// If the enclosing element can only be referenced using a prefix, then + /// the [prefix] should be provided. + void suggestStaticField(FieldElement element, {String? prefix}) { + assert(element.isStatic); + if (element.isSynthetic) { + var getter = element.getter; + if (getter != null) { + suggestAccessor( + getter, + inheritanceDistance: 0.0, + withEnclosingName: true, + ); + } + } else { + var enclosingPrefix = ''; + var enclosingName = _enclosingClassOrExtensionName(element); + if (enclosingName != null) { + enclosingPrefix = '$enclosingName.'; + } + var relevance = + _computeTopLevelRelevance(element, elementType: element.type); + _addBuilder( + _createCompletionSuggestionBuilder( + element, + completion: enclosingPrefix + element.name, + kind: CompletionSuggestionKind.IDENTIFIER, + prefix: prefix, + relevance: relevance, + isNotImported: isNotImportedLibrary, + ), + ); + } + } + /// Add a suggestion to reference a [parameter] in a super formal parameter. void suggestSuperFormalParameter(ParameterElement parameter) { _addBuilder( @@ -1392,6 +1438,22 @@ class SuggestionBuilder { ); } + /// Return the name of the enclosing class or extension. + /// + /// The enclosing element must be either a class, or extension; otherwise + /// we either fail with assertion, or return `null`. + String? _enclosingClassOrExtensionName(Element element) { + var enclosing = element.enclosingElement; + if (enclosing is ClassElement) { + return enclosing.name; + } else if (enclosing is ExtensionElement) { + return enclosing.name; + } else { + assert(false, 'Expected ClassElement or ExtensionElement'); + return null; + } + } + /// If the [element] has a documentation comment, return it. _ElementDocumentation? _getDocumentation(Element element) { var documentationCache = request.documentationCache; diff --git a/pkg/analysis_server/test/analysis_server_base.dart b/pkg/analysis_server/test/analysis_server_base.dart index 804f0794b4e7..43f7da3c4b80 100644 --- a/pkg/analysis_server/test/analysis_server_base.dart +++ b/pkg/analysis_server/test/analysis_server_base.dart @@ -22,6 +22,7 @@ import 'package:meta/meta.dart'; import 'package:test/test.dart'; import 'mocks.dart'; +import 'src/utilities/mock_packages.dart'; /// TODO(scheglov) this is duplicate class AnalysisOptionsFileConfig { @@ -91,6 +92,9 @@ class PubPackageAnalysisServerTest with ResourceProviderMixin { EnableString.super_parameters, ]; + /// The path that is not in [workspaceRootPath], contains external packages. + String get packagesRootPath => '/packages'; + Folder get sdkRoot => newFolder('/sdk'); File get testFile => getFile(testFilePath); @@ -280,6 +284,8 @@ class PubPackageAnalysisServerTest with ResourceProviderMixin { void writeTestPackageConfig({ PackageConfigFileBuilder? config, String? languageVersion, + bool flutter = false, + bool meta = false, }) { if (config == null) { config = PackageConfigFileBuilder(); @@ -293,6 +299,22 @@ class PubPackageAnalysisServerTest with ResourceProviderMixin { languageVersion: languageVersion, ); + if (meta || flutter) { + var libFolder = MockPackages.instance.addMeta(resourceProvider); + config.add(name: 'meta', rootPath: libFolder.parent.path); + } + + if (flutter) { + { + var libFolder = MockPackages.instance.addUI(resourceProvider); + config.add(name: 'ui', rootPath: libFolder.parent.path); + } + { + var libFolder = MockPackages.instance.addFlutter(resourceProvider); + config.add(name: 'flutter', rootPath: libFolder.parent.path); + } + } + writePackageConfig(testPackageRoot, config); } diff --git a/pkg/analysis_server/test/client/completion_driver_test.dart b/pkg/analysis_server/test/client/completion_driver_test.dart index c8ee38be3d8e..1b470b3d4d1c 100644 --- a/pkg/analysis_server/test/client/completion_driver_test.dart +++ b/pkg/analysis_server/test/client/completion_driver_test.dart @@ -267,7 +267,7 @@ class CompletionWithSuggestionsTest1 extends AbstractCompletionDriverTest @override TestingCompletionProtocol get protocol => TestingCompletionProtocol.version1; - @failingTest + @FailingTest(reason: 'This test fails with available suggestions') @override Future test_project_lib_multipleExports() async { return super.test_project_lib_multipleExports(); @@ -343,7 +343,7 @@ export 'a.dart'; await addTestFile(''' import 'a.dart'; void f() { - ^ + E v = ^ } '''); assertSuggestion( @@ -665,7 +665,7 @@ void f() { Future test_project_lib_setters_static() async { newFile2('$testPackageLibPath/a.dart', r''' class A { - static set g(int g) {} + static set foo(int _) {} } '''); @@ -679,7 +679,7 @@ void f() { } '''); - assertNoSuggestion(completion: 'A.g'); + assertNoSuggestion(completion: 'A.foo'); } /// See: https://github.com/dart-lang/sdk/issues/40626 diff --git a/pkg/analysis_server/test/client/impl/completion_driver.dart b/pkg/analysis_server/test/client/impl/completion_driver.dart index df8faf3a518b..c29265ee5ca0 100644 --- a/pkg/analysis_server/test/client/impl/completion_driver.dart +++ b/pkg/analysis_server/test/client/impl/completion_driver.dart @@ -276,6 +276,8 @@ class CompletionDriver with ExpectMixin { } else if (notification.event == ANALYSIS_NOTIFICATION_ERRORS) { var decoded = AnalysisErrorsParams.fromNotification(notification); filesErrors[decoded.file] = decoded.errors; + } else if (notification.event == ANALYSIS_NOTIFICATION_FLUSH_RESULTS) { + // Ignored. } else if (notification.event == SERVER_NOTIFICATION_ERROR) { throw Exception('server error: ${notification.toJson()}'); } else if (notification.event == SERVER_NOTIFICATION_CONNECTED) { diff --git a/pkg/analysis_server/test/domain_completion_test.dart b/pkg/analysis_server/test/domain_completion_test.dart index 03ec19a02e87..2fa55a90b08d 100644 --- a/pkg/analysis_server/test/domain_completion_test.dart +++ b/pkg/analysis_server/test/domain_completion_test.dart @@ -391,44 +391,6 @@ void f() { ..suggestions.withElementClass.isEmpty; } - Future test_notImported_lowerRelevance_enumConstant() async { - newFile2('$testPackageLibPath/a.dart', ''' -enum E1 { - foo01 -} -'''); - - newFile2('$testPackageLibPath/b.dart', ''' -enum E2 { - foo02 -} -'''); - - await _configureWithWorkspaceRoot(); - - var response = await _getTestCodeSuggestions(''' -import 'b.dart'; - -void f() { - foo0^ -} -'''); - - check(response) - ..assertComplete() - ..hasReplacement(left: 4); - - // `foo01` relevance is decreased because it is not yet imported. - check(response).suggestions.matches([ - (suggestion) => suggestion - ..completion.isEqualTo('E2.foo02') - ..libraryUriToImport.isNull, - (suggestion) => suggestion - ..completion.isEqualTo('E1.foo01') - ..libraryUriToImport.isEqualTo('package:test/a.dart'), - ]); - } - Future test_notImported_lowerRelevance_extension_getter() async { await _configureWithWorkspaceRoot(); diff --git a/pkg/analysis_server/test/services/completion/dart/completion_check.dart b/pkg/analysis_server/test/services/completion/dart/completion_check.dart index ff987940984e..313959ecf839 100644 --- a/pkg/analysis_server/test/services/completion/dart/completion_check.dart +++ b/pkg/analysis_server/test/services/completion/dart/completion_check.dart @@ -185,6 +185,14 @@ extension CompletionSuggestionExtension ); } + @useResult + CheckTarget get hasNamedParameters { + return nest( + value.suggestion.hasNamedParameters, + (selected) => 'has hasNamedParameters ${valueStr(selected)}', + ); + } + void get isClass { kind.isIdentifier; element.isNotNull.kind.isClass; @@ -220,6 +228,10 @@ extension CompletionSuggestionExtension element.isNotNull.kind.isGetter; } + void get isImport { + kind.isImport; + } + void get isImportPrefix { kind.isIdentifier; element.isNotNull.kind.isPrefix; @@ -262,6 +274,20 @@ extension CompletionSuggestionExtension element.isNotNull.kind.isSetter; } + void get isStatic { + element.isNotNull.isStatic.isTrue; + } + + void get isStaticField { + isStatic; + isField; + } + + void get isStaticGetter { + isStatic; + isGetter; + } + void get isTopLevelVariable { kind.isIdentifier; element.isNotNull.kind.isTopLevelVariable; @@ -293,6 +319,14 @@ extension CompletionSuggestionExtension ); } + @useResult + CheckTarget?> get parameterNames { + return nest( + value.suggestion.parameterNames, + (selected) => 'has parameterNames ${valueStr(selected)}', + ); + } + @useResult CheckTarget get parameterType { return nest( @@ -301,6 +335,14 @@ extension CompletionSuggestionExtension ); } + @useResult + CheckTarget?> get parameterTypes { + return nest( + value.suggestion.parameterTypes, + (selected) => 'has parameterTypes ${valueStr(selected)}', + ); + } + /// Return the effective replacement length. @useResult CheckTarget get replacementLength { @@ -319,6 +361,14 @@ extension CompletionSuggestionExtension ); } + @useResult + CheckTarget get requiredParameterCount { + return nest( + value.suggestion.requiredParameterCount, + (selected) => 'has requiredParameterCount ${valueStr(selected)}', + ); + } + @useResult CheckTarget get returnType { return nest( @@ -373,6 +423,10 @@ extension CompletionSuggestionKindExtension isEqualTo(CompletionSuggestionKind.IDENTIFIER); } + void get isImport { + isEqualTo(CompletionSuggestionKind.IMPORT); + } + void get isInvocation { isEqualTo(CompletionSuggestionKind.INVOCATION); } @@ -396,6 +450,45 @@ extension CompletionSuggestionsExtension ); } + @useResult + CheckTarget> get fields { + var result = value + .where((suggestion) => + suggestion.suggestion.kind == CompletionSuggestionKind.IDENTIFIER && + suggestion.suggestion.element?.kind == ElementKind.FIELD) + .toList(); + return nest( + result, + (selected) => 'fields ${valueStr(selected)}', + ); + } + + @useResult + CheckTarget> get getters { + var result = value + .where((suggestion) => + suggestion.suggestion.kind == CompletionSuggestionKind.IDENTIFIER && + suggestion.suggestion.element?.kind == ElementKind.GETTER) + .toList(); + return nest( + result, + (selected) => 'getters ${valueStr(selected)}', + ); + } + + @useResult + CheckTarget> get methods { + var result = value + .where((suggestion) => + suggestion.suggestion.kind == CompletionSuggestionKind.IDENTIFIER && + suggestion.suggestion.element?.kind == ElementKind.METHOD) + .toList(); + return nest( + result, + (selected) => 'setters ${valueStr(selected)}', + ); + } + @useResult CheckTarget> get namedArguments { var result = value @@ -421,6 +514,19 @@ extension CompletionSuggestionsExtension ); } + @useResult + CheckTarget> get setters { + var result = value + .where((suggestion) => + suggestion.suggestion.kind == CompletionSuggestionKind.IDENTIFIER && + suggestion.suggestion.element?.kind == ElementKind.SETTER) + .toList(); + return nest( + result, + (selected) => 'setters ${valueStr(selected)}', + ); + } + @useResult CheckTarget> get withElementClass { return nest( @@ -445,6 +551,14 @@ extension CompletionSuggestionsExtension } extension ElementExtension on CheckTarget { + @useResult + CheckTarget get isStatic { + return nest( + value.isStatic, + (selected) => 'isStatic ${valueStr(selected)}', + ); + } + @useResult CheckTarget get kind { return nest( diff --git a/pkg/analysis_server/test/services/completion/dart/declaration/class_test.dart b/pkg/analysis_server/test/services/completion/dart/declaration/class_test.dart new file mode 100644 index 000000000000..83f7e105a1af --- /dev/null +++ b/pkg/analysis_server/test/services/completion/dart/declaration/class_test.dart @@ -0,0 +1,249 @@ +// Copyright (c) 2022, 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:analyzer_utilities/check/check.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import '../../../../client/completion_driver_test.dart'; +import '../completion_check.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(ClassStaticMembersWithoutClassNameTest); + }); +} + +@reflectiveTest +class ClassStaticMembersWithoutClassNameTest + extends AbstractCompletionDriverTest with _Helpers { + @override + TestingCompletionProtocol get protocol => TestingCompletionProtocol.version2; + + Future test_field_hasContextType_exact() async { + await _checkLocations( + classCode: r''' +class A { + static final int foo01 = 0; + static final num foo02 = 0; + static final double foo03 = 0; + final int foo04 = 0; +} +''', + contextCode: r''' +void f() { + int a = foo0^ +} +''', + validator: (response) { + check(response).suggestions.fields.completions.matchesInAnyOrder([ + (e) => e.isEqualTo('A.foo01'), + ]); + }, + ); + } + + Future test_field_hasContextType_subtypes() async { + await _checkLocations( + classCode: r''' +class A { + static final int foo01 = 0; + static final double foo02 = 0; + static final num foo03 = 0; + static final Object foo04 = ''; +} +''', + contextCode: r''' +void f() { + num a = foo0^ +} +''', + validator: (response) { + check(response).suggestions.fields.completions.matchesInAnyOrder([ + (e) => e.isEqualTo('A.foo01'), + (e) => e.isEqualTo('A.foo02'), + (e) => e.isEqualTo('A.foo03'), + ]); + }, + ); + } + + Future test_field_noContextType() async { + await _checkLocations( + classCode: r''' +class A { + static final foo01 = 0; + static final foo02 = 0; + final foo03 = 0; +} +''', + contextCode: r''' +void f() { + foo0^ +} +''', + validator: (response) { + check(response).suggestions.fields.isEmpty; + }, + ); + } + + Future test_getter_hasContextType_exact() async { + await _checkLocations( + classCode: r''' +class A { + static int get foo01 => 0; + static num get foo02 => 0; + static double get foo03 => 0; +} +''', + contextCode: r''' +void f() { + int a = foo0^ +} +''', + validator: (response) { + check(response).suggestions.getters.completions.matchesInAnyOrder([ + (e) => e.isEqualTo('A.foo01'), + ]); + }, + ); + } + + Future test_getter_hasContextType_subtypes() async { + await _checkLocations( + classCode: r''' +class A { + static int get foo01 => 0; + static double get foo02 => 0; + static num get foo03 => 0; + static Object get foo04 => ''; +} +''', + contextCode: r''' +void f() { + num a = foo0^ +} +''', + validator: (response) { + check(response).suggestions.getters.completions.matchesInAnyOrder([ + (e) => e.isEqualTo('A.foo01'), + (e) => e.isEqualTo('A.foo02'), + (e) => e.isEqualTo('A.foo03'), + ]); + }, + ); + } + + Future test_getter_noContextType() async { + await _checkLocations( + classCode: r''' +class A { + static int get foo01 => 0; +} +''', + contextCode: r''' +void f() { + foo0^ +} +''', + validator: (response) { + check(response).suggestions.getters.isEmpty; + }, + ); + } + + Future test_method() async { + await _checkLocations( + classCode: r''' +class A { + static void foo01() {} +} +''', + contextCode: r''' +void f() { + foo0^ +} +''', + validator: (response) { + check(response).suggestions.methods.isEmpty; + }, + ); + } + + Future test_setter_hasContextType() async { + await _checkLocations( + classCode: r''' +class A { + static set foo01(int _) {} + static set foo02(num _) {} + static set foo03(double _) {} +} +''', + contextCode: r''' +void f() { + int a = foo0^ +} +''', + validator: (response) { + check(response).suggestions.setters.isEmpty; + }, + ); + } + + Future test_setter_noContextType() async { + await _checkLocations( + classCode: r''' +class A { + static set foo01(int _) {} +} +''', + contextCode: r''' +void f() { + foo0^ +} +''', + validator: (response) { + check(response).suggestions.setters.isEmpty; + }, + ); + } +} + +mixin _Helpers on AbstractCompletionDriverTest { + Future _checkLocations({ + required String classCode, + required String contextCode, + required void Function(CompletionResponseForTesting response) validator, + }) async { + // local + { + final response = await getTestCodeSuggestions(''' +$classCode + +$contextCode +'''); + validator(response); + } + + // imported, without prefix + { + newFile2('$testPackageLibPath/a.dart', classCode); + final response = await getTestCodeSuggestions(''' +import 'a.dart'; + +$contextCode +'''); + validator(response); + } + + // not imported + { + newFile2('$testPackageLibPath/a.dart', classCode); + final response = await getTestCodeSuggestions(''' +$contextCode +'''); + validator(response); + } + } +} diff --git a/pkg/analysis_server/test/services/completion/dart/declaration/enum_test.dart b/pkg/analysis_server/test/services/completion/dart/declaration/enum_test.dart index e06f7648c2d6..af01adde2395 100644 --- a/pkg/analysis_server/test/services/completion/dart/declaration/enum_test.dart +++ b/pkg/analysis_server/test/services/completion/dart/declaration/enum_test.dart @@ -30,8 +30,12 @@ class EnumTest2 extends AbstractCompletionDriverTest with EnumTestCases { mixin EnumTestCases on AbstractCompletionDriverTest { Future test_enumConstantName() async { await _check_locations( - declaration: 'enum MyEnum { foo01 }', - codeAtCompletion: 'foo0^', + declaration: ''' +enum MyEnum { foo01 } +enum OtherEnum { foo02 } +''', + declarationForContextType: 'void useMyEnum(MyEnum _) {}', + codeAtCompletion: 'useMyEnum(foo0^);', validator: (response) { check(response).hasReplacement(left: 4); @@ -61,6 +65,7 @@ mixin EnumTestCases on AbstractCompletionDriverTest { Future test_enumConstantName_imported_withPrefix() async { newFile2('$testPackageLibPath/a.dart', r''' enum MyEnum { foo01 } +enum OtherEnum { foo02 } '''); if (isProtocolVersion1) { @@ -70,8 +75,10 @@ enum MyEnum { foo01 } var response = await getTestCodeSuggestions(''' import 'a.dart' as prefix; +void useMyEnum(prefix.MyEnum _) {} + void f() { - foo0^ + useMyEnum(foo0^); } '''); @@ -223,7 +230,8 @@ void f() { Future test_nothing() async { await _check_locations( declaration: 'enum MyEnum { v }', - codeAtCompletion: '^', + declarationForContextType: 'void useMyEnum(MyEnum _) {}', + codeAtCompletion: 'useMyEnum(^);', validator: (response) { check(response).hasEmptyReplacement(); @@ -257,8 +265,10 @@ enum MyEnum { v } var response = await getTestCodeSuggestions(''' import 'a.dart' as prefix; +void useMyEnum(prefix.MyEnum _) {} + void f() { - ^ + useMyEnum(^); } '''); @@ -288,6 +298,7 @@ void f() { Future _check_locations({ required String declaration, + String declarationForContextType = '', required String codeAtCompletion, required void Function(CompletionResponseForTesting response) validator, }) async { @@ -295,6 +306,7 @@ void f() { { var response = await getTestCodeSuggestions(''' $declaration +$declarationForContextType void f() { $codeAtCompletion } @@ -312,6 +324,7 @@ $declaration } var response = await getTestCodeSuggestions(''' import 'a.dart'; +$declarationForContextType void f() { $codeAtCompletion } @@ -323,11 +336,16 @@ void f() { { newFile2('$testPackageLibPath/a.dart', ''' $declaration +'''); + newFile2('$testPackageLibPath/context_type.dart', ''' +import 'a.dart'; // ignore: unused_import +$declarationForContextType '''); if (isProtocolVersion1) { await waitForSetWithUri('package:test/a.dart'); } var response = await getTestCodeSuggestions(''' +import 'context_type.dart'; void f() { $codeAtCompletion } diff --git a/pkg/analysis_server/test/services/completion/dart/declaration/test_all.dart b/pkg/analysis_server/test/services/completion/dart/declaration/test_all.dart index 12cf69339da7..c74d1a030347 100644 --- a/pkg/analysis_server/test/services/completion/dart/declaration/test_all.dart +++ b/pkg/analysis_server/test/services/completion/dart/declaration/test_all.dart @@ -4,12 +4,14 @@ import 'package:test_reflective_loader/test_reflective_loader.dart'; +import 'class_test.dart' as class_; import 'enum_test.dart' as enum_; import 'library_test.dart' as library_; /// Tests suggestions produced for various kinds of declarations. void main() { defineReflectiveSuite(() { + class_.main(); enum_.main(); library_.main(); }); diff --git a/pkg/analysis_server/test/services/completion/dart/imported_reference_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/imported_reference_contributor_test.dart index 4305c1355a50..91d59b2ccc24 100644 --- a/pkg/analysis_server/test/services/completion/dart/imported_reference_contributor_test.dart +++ b/pkg/analysis_server/test/services/completion/dart/imported_reference_contributor_test.dart @@ -1923,8 +1923,8 @@ void f() { assertSuggestEnumConst('E.two'); assertSuggestEnum('F'); - assertSuggestEnumConst('F.three'); - assertSuggestEnumConst('F.four'); + assertNotSuggested('F.three'); + assertNotSuggested('F.four'); } Future test_ExpressionStatement_identifier() async { diff --git a/pkg/analysis_server/test/services/completion/dart/local_reference_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/local_reference_contributor_test.dart index 45e5c5d6584f..a95020e7faaa 100644 --- a/pkg/analysis_server/test/services/completion/dart/local_reference_contributor_test.dart +++ b/pkg/analysis_server/test/services/completion/dart/local_reference_contributor_test.dart @@ -2312,7 +2312,7 @@ void f() {^} } Future test_enum() async { - addTestSource('enum E { one, two } void f() {^}'); + addTestSource('enum E { one, two } void f() {E v = ^}'); await computeSuggestions(); assertSuggestEnum('E'); assertSuggestEnumConst('E.one'); @@ -2322,7 +2322,7 @@ void f() {^} } Future test_enum_deprecated() async { - addTestSource('@deprecated enum E { one, two } void f() {^}'); + addTestSource('@deprecated enum E { one, two } void f() {E v = ^}'); await computeSuggestions(); assertSuggestEnum('E', isDeprecated: true); assertSuggestEnumConst('E.one', isDeprecated: true); @@ -2349,8 +2349,8 @@ void f() { assertSuggestEnumConst('E.two'); assertSuggestEnum('F'); - assertSuggestEnumConst('F.three'); - assertSuggestEnumConst('F.four'); + assertNotSuggested('F.three'); + assertNotSuggested('F.four'); } Future test_enum_filter_assignment() async { @@ -2370,8 +2370,8 @@ void f() { assertSuggestEnumConst('E.two'); assertSuggestEnum('F'); - assertSuggestEnumConst('F.three'); - assertSuggestEnumConst('F.four'); + assertNotSuggested('F.three'); + assertNotSuggested('F.four'); } Future test_enum_filter_binaryEquals() async { @@ -2412,8 +2412,8 @@ void f(E e) { assertSuggestEnumConst('E.two'); assertSuggestEnum('F'); - assertSuggestEnumConst('F.three'); - assertSuggestEnumConst('F.four'); + assertNotSuggested('F.three'); + assertNotSuggested('F.four'); } Future test_enum_filter_variableDeclaration() async { @@ -2432,8 +2432,8 @@ void f() { assertSuggestEnumConst('E.two'); assertSuggestEnum('F'); - assertSuggestEnumConst('F.three'); - assertSuggestEnumConst('F.four'); + assertNotSuggested('F.three'); + assertNotSuggested('F.four'); } Future test_enum_shadowed() async { @@ -2771,42 +2771,6 @@ class A { assertNoSuggestions(); } - Future test_flutter_setState_hasPrefix() async { - var spaces_4 = ' ' * 4; - var spaces_6 = ' ' * 6; - await _check_flutter_setState( - ' setSt', - ''' -setState(() { -$spaces_6 -$spaces_4});''', - 20); - } - - Future test_flutter_setState_longPrefix() async { - var spaces_6 = ' ' * 6; - var spaces_8 = ' ' * 8; - await _check_flutter_setState( - ' setSt', - ''' -setState(() { -$spaces_8 -$spaces_6});''', - 22); - } - - Future test_flutter_setState_noPrefix() async { - var spaces_4 = ' ' * 4; - var spaces_6 = ' ' * 6; - await _check_flutter_setState( - ' ', - ''' -setState(() { -$spaces_6 -$spaces_4});''', - 20); - } - Future test_forEachPartsWithIdentifier_class() async { addTestSource(''' class C {} @@ -6322,36 +6286,4 @@ void f() async* { assertSuggestLocalVariable('value', null); } - - Future _check_flutter_setState( - String line, String completion, int selectionOffset) async { - writeTestPackageConfig(flutter: true); - addTestSource(''' -import 'package:flutter/widgets.dart'; - -class TestWidget extends StatefulWidget { - @override - State createState() { - return new TestWidgetState(); - } -} - -class TestWidgetState extends State { - @override - Widget build(BuildContext context) { -$line^ - } -} -'''); - await computeSuggestions(); - var cs = assertSuggest(completion, selectionOffset: selectionOffset); - expect(cs.selectionLength, 0); - - // It is an invocation, but we don't need any additional info for it. - // So, all parameter information is absent. - expect(cs.parameterNames, isNull); - expect(cs.parameterTypes, isNull); - expect(cs.requiredParameterCount, isNull); - expect(cs.hasNamedParameters, isNull); - } } diff --git a/pkg/analysis_server/test/services/completion/dart/location/block_test.dart b/pkg/analysis_server/test/services/completion/dart/location/block_test.dart new file mode 100644 index 000000000000..bf80f1b78a79 --- /dev/null +++ b/pkg/analysis_server/test/services/completion/dart/location/block_test.dart @@ -0,0 +1,107 @@ +// Copyright (c) 2022, 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:analyzer_utilities/check/check.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import '../../../../client/completion_driver_test.dart'; +import '../completion_check.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(BlockTest1); + defineReflectiveTests(BlockTest2); + }); +} + +@reflectiveTest +class BlockTest1 extends AbstractCompletionDriverTest with BlockTestCases { + @override + TestingCompletionProtocol get protocol => TestingCompletionProtocol.version1; +} + +@reflectiveTest +class BlockTest2 extends AbstractCompletionDriverTest with BlockTestCases { + @override + TestingCompletionProtocol get protocol => TestingCompletionProtocol.version2; +} + +mixin BlockTestCases on AbstractCompletionDriverTest { + static final spaces_4 = ' ' * 4; + static final spaces_6 = ' ' * 6; + static final spaces_8 = ' ' * 8; + + Future test_flutter_setState_indent6_hasPrefix() async { + await _check_flutter_setState( + line: '${spaces_6}setSt^', + completion: ''' +setState(() { +$spaces_8 +$spaces_6});''', + selectionOffset: 22, + ); + } + + Future test_flutter_setState_indent_hasPrefix() async { + await _check_flutter_setState( + line: '${spaces_4}setSt^', + completion: ''' +setState(() { +$spaces_6 +$spaces_4});''', + selectionOffset: 20, + ); + } + + Future test_flutter_setState_indent_noPrefix() async { + await _check_flutter_setState( + line: '$spaces_4^', + completion: ''' +setState(() { +$spaces_6 +$spaces_4});''', + selectionOffset: 20, + ); + } + + Future _check_flutter_setState({ + required String line, + required String completion, + required int selectionOffset, + }) async { + writeTestPackageConfig(flutter: true); + + var response = await getTestCodeSuggestions(''' +import 'package:flutter/widgets.dart'; + +class TestWidget extends StatefulWidget { + @override + State createState() { + return TestWidgetState(); + } +} + +class TestWidgetState extends State { + @override + Widget build(BuildContext context) { +$line + } +} +'''); + + check(response).suggestions.includesAll([ + (suggestion) => suggestion + ..completion.startsWith('setState') + ..completion.isEqualTo(completion) + ..hasSelection(offset: selectionOffset) + // It is an invocation, but we don't need any additional info for it. + // So, all parameter information is absent. + ..kind.isInvocation + ..parameterNames.isNull + ..parameterTypes.isNull + ..requiredParameterCount.isNull + ..hasNamedParameters.isNull, + ]); + } +} diff --git a/pkg/analysis_server/test/services/completion/dart/location/directive_uri_test.dart b/pkg/analysis_server/test/services/completion/dart/location/directive_uri_test.dart new file mode 100644 index 000000000000..bb87a74165d9 --- /dev/null +++ b/pkg/analysis_server/test/services/completion/dart/location/directive_uri_test.dart @@ -0,0 +1,95 @@ +// Copyright (c) 2022, 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:analyzer/src/test_utilities/package_config_file_builder.dart'; +import 'package:analyzer_utilities/check/check.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import '../../../../client/completion_driver_test.dart'; +import '../completion_check.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(DirectiveUriTest); + }); +} + +@reflectiveTest +class DirectiveUriTest extends AbstractCompletionDriverTest { + @override + TestingCompletionProtocol get protocol => TestingCompletionProtocol.version2; + + Future test_uri_end() async { + await _checkDirectives( + uriContent: 'foo0^', + validator: (response) { + // We have both `foo0x`, but no `bar`. + check(response).suggestions.matchesInAnyOrder([ + (suggestion) => suggestion + ..isImport + ..completion.isEqualTo('package:foo/foo01.dart'), + (suggestion) => suggestion + ..isImport + ..completion.isEqualTo('package:foo/foo02.dart'), + ]); + }, + ); + } + + Future test_uri_notEnd() async { + await _checkDirectives( + uriContent: 'foo0^xyz', + validator: (response) { + // We ignore 'xyz' after the caret. + check(response).suggestions.matchesInAnyOrder([ + (suggestion) => suggestion + ..isImport + ..completion.isEqualTo('package:foo/foo01.dart'), + (suggestion) => suggestion + ..isImport + ..completion.isEqualTo('package:foo/foo02.dart'), + ]); + }, + ); + } + + Future _checkDirectives({ + required String uriContent, + required void Function(CompletionResponseForTesting response) validator, + }) async { + _configurePackagesFooBar(); + + { + var response = await getTestCodeSuggestions(''' +export '$uriContent'; +'''); + validator(response); + } + + { + var response = await getTestCodeSuggestions(''' +import '$uriContent'; +'''); + validator(response); + } + } + + void _configurePackagesFooBar() { + final fooPackageRoot = getFolder('$packagesRootPath/foo'); + newFile2('$packagesRootPath/foo/lib/foo01.dart', ''); + newFile2('$packagesRootPath/foo/lib/foo02.dart', ''); + // We use this file to check that exactly `foo0` is used as prefix. + // So, we don't have one-off and don't use just `foo`. + newFile2('$packagesRootPath/foo/lib/foo11.dart', ''); + + final barPackageRoot = getFolder('$packagesRootPath/bar'); + newFile2('$packagesRootPath/bar/lib/bar01.dart', ''); + + writeTestPackageConfig( + config: PackageConfigFileBuilder() + ..add(name: 'foo', rootPath: fooPackageRoot.path) + ..add(name: 'bar', rootPath: barPackageRoot.path), + ); + } +} diff --git a/pkg/analysis_server/test/services/completion/dart/location/test_all.dart b/pkg/analysis_server/test/services/completion/dart/location/test_all.dart index b0fe4882fd01..139de2f39bb9 100644 --- a/pkg/analysis_server/test/services/completion/dart/location/test_all.dart +++ b/pkg/analysis_server/test/services/completion/dart/location/test_all.dart @@ -4,8 +4,10 @@ import 'package:test_reflective_loader/test_reflective_loader.dart'; +import 'block_test.dart' as block; import 'class_body_test.dart' as class_body; import 'compilation_unit_test.dart' as compilation_unit; +import 'directive_uri_test.dart' as directive_uri; import 'enum_constant_test.dart' as enum_constant; import 'enum_test.dart' as enum_; import 'field_formal_parameter_test.dart' as field_formal_parameter; @@ -15,8 +17,10 @@ import 'super_formal_parameter_test.dart' as super_formal_parameter; /// Tests suggestions produced at specific locations. void main() { defineReflectiveSuite(() { + block.main(); class_body.main(); compilation_unit.main(); + directive_uri.main(); enum_constant.main(); enum_.main(); field_formal_parameter.main(); diff --git a/pkg/analysis_server/test/src/cider/completion_test.dart b/pkg/analysis_server/test/src/cider/completion_test.dart index 06ee178e3b27..4f3945bc26f8 100644 --- a/pkg/analysis_server/test/src/cider/completion_test.dart +++ b/pkg/analysis_server/test/src/cider/completion_test.dart @@ -401,7 +401,7 @@ enum E { e } _assertHasClass(text: 'String'); _assertHasConstructor(text: 'A'); _assertHasConstructor(text: 'B'); - _assertHasEnumConstant(text: 'E.e'); + _assertHasEnum(text: 'E'); _assertHasMethod(text: 'foo'); _assertHasMethod(text: 'bar'); _assertHasParameter(text: 'a'); @@ -661,10 +661,10 @@ import 'a.dart'; return matching.single; } - CompletionSuggestion _assertHasEnumConstant({required String text}) { + CompletionSuggestion _assertHasEnum({required String text}) { var matching = _matchingCompletions( text: text, - elementKind: ElementKind.ENUM_CONSTANT, + elementKind: ElementKind.ENUM, ); expect(matching, hasLength(1), reason: 'Expected exactly one completion'); return matching.single; diff --git a/pkg/analysis_server/test/src/services/completion/dart/feature_computer_test.dart b/pkg/analysis_server/test/src/services/completion/dart/feature_computer_test.dart index bfa63e0343cc..f866620ec596 100644 --- a/pkg/analysis_server/test/src/services/completion/dart/feature_computer_test.dart +++ b/pkg/analysis_server/test/src/services/completion/dart/feature_computer_test.dart @@ -109,6 +109,16 @@ void f(C c) { ''', 'int'); } + Future test_argumentList_named_second() async { + await assertContextType(''' +void f({String p1, int p2}) {} + +void g() { + f(p1: '', p2: ^); +} +''', 'int'); + } + Future test_argumentList_named_unresolved_hasNamedParameters() async { await assertContextType(''' void f({int i}) {}