Remove support for the older legacy completion protocol

This does not remove the computation of available declarations. This CL
seemed big enough without that, so I (or someone else) can get that in
the next CL.

Change-Id: I67ab49b75c8a415ccfaef16c4e49a00026a6091f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/341160
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Brian Wilkerson 2023-12-12 00:54:48 +00:00 committed by Commit Queue
parent d156344a08
commit d45080d365
45 changed files with 78 additions and 8332 deletions

View file

@ -120,10 +120,6 @@ abstract class CommonInputConverter extends Converter<String, Operation?> {
});
return RequestOperation(this, json);
}
// Track performance for completion notifications
if (method == COMPLETION_REQUEST_GET_SUGGESTIONS) {
return CompletionRequestOperation(this, json);
}
// TODO(danrubel): replace this with code
// that just forwards the translated request
if (method == ANALYSIS_REQUEST_GET_HOVER ||

View file

@ -10,49 +10,6 @@ import 'package:logging/logging.dart';
import 'driver.dart';
import 'input_converter.dart';
/// A [CompletionRequestOperation] tracks response time along with
/// the first and last completion notifications.
class CompletionRequestOperation extends RequestOperation {
late Driver driver;
late StreamSubscription<CompletionResultsParams> subscription;
late String notificationId;
late Stopwatch stopwatch;
bool firstNotification = true;
CompletionRequestOperation(super.converter, super.json);
@override
Future<void>? perform(Driver driver) {
this.driver = driver;
subscription = driver.onCompletionResults.listen(processNotification);
return super.perform(driver);
}
void processNotification(CompletionResultsParams event) {
if (event.id == notificationId) {
var elapsed = stopwatch.elapsed;
if (firstNotification) {
firstNotification = false;
driver.results.record('completion notification first', elapsed,
notification: true);
}
if (event.isLast) {
subscription.cancel();
driver.results.record('completion notification last', elapsed,
notification: true);
}
}
}
@override
void processResult(
String id, Map<String, Object?> result, Stopwatch stopwatch) {
notificationId = result['id'] as String;
this.stopwatch = stopwatch;
super.processResult(id, result, stopwatch);
}
}
/// An [Operation] represents an action such as sending a request to the server.
abstract class Operation {
Future<void>? perform(Driver driver);

File diff suppressed because one or more lines are too long

View file

@ -6,7 +6,7 @@
// To regenerate the file, use the script
// "pkg/analysis_server/tool/spec/generate_files".
const String PROTOCOL_VERSION = '1.34.0';
const String PROTOCOL_VERSION = '1.35.0';
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES = 'analysis.analyzedFiles';
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES_DIRECTORIES = 'directories';
@ -110,33 +110,10 @@ const String ANALYTICS_REQUEST_SEND_TIMING = 'analytics.sendTiming';
const String ANALYTICS_REQUEST_SEND_TIMING_EVENT = 'event';
const String ANALYTICS_REQUEST_SEND_TIMING_MILLIS = 'millis';
const String ANALYTICS_RESPONSE_IS_ENABLED_ENABLED = 'enabled';
const String COMPLETION_NOTIFICATION_AVAILABLE_SUGGESTIONS =
'completion.availableSuggestions';
const String COMPLETION_NOTIFICATION_AVAILABLE_SUGGESTIONS_CHANGED_LIBRARIES =
'changedLibraries';
const String COMPLETION_NOTIFICATION_AVAILABLE_SUGGESTIONS_REMOVED_LIBRARIES =
'removedLibraries';
const String COMPLETION_NOTIFICATION_EXISTING_IMPORTS =
'completion.existingImports';
const String COMPLETION_NOTIFICATION_EXISTING_IMPORTS_FILE = 'file';
const String COMPLETION_NOTIFICATION_EXISTING_IMPORTS_IMPORTS = 'imports';
const String COMPLETION_NOTIFICATION_RESULTS = 'completion.results';
const String COMPLETION_NOTIFICATION_RESULTS_ID = 'id';
const String COMPLETION_NOTIFICATION_RESULTS_INCLUDED_ELEMENT_KINDS =
'includedElementKinds';
const String
COMPLETION_NOTIFICATION_RESULTS_INCLUDED_SUGGESTION_RELEVANCE_TAGS =
'includedSuggestionRelevanceTags';
const String COMPLETION_NOTIFICATION_RESULTS_INCLUDED_SUGGESTION_SETS =
'includedSuggestionSets';
const String COMPLETION_NOTIFICATION_RESULTS_IS_LAST = 'isLast';
const String COMPLETION_NOTIFICATION_RESULTS_LIBRARY_FILE = 'libraryFile';
const String COMPLETION_NOTIFICATION_RESULTS_REPLACEMENT_LENGTH =
'replacementLength';
const String COMPLETION_NOTIFICATION_RESULTS_REPLACEMENT_OFFSET =
'replacementOffset';
const String COMPLETION_NOTIFICATION_RESULTS_RESULTS = 'results';
const String COMPLETION_REQUEST_GET_SUGGESTIONS = 'completion.getSuggestions';
const String COMPLETION_REQUEST_GET_SUGGESTIONS2 = 'completion.getSuggestions2';
const String COMPLETION_REQUEST_GET_SUGGESTIONS2_COMPLETION_CASE_MATCHING_MODE =
'completionCaseMatchingMode';
@ -148,10 +125,6 @@ const String COMPLETION_REQUEST_GET_SUGGESTIONS2_INVOCATION_COUNT =
const String COMPLETION_REQUEST_GET_SUGGESTIONS2_MAX_RESULTS = 'maxResults';
const String COMPLETION_REQUEST_GET_SUGGESTIONS2_OFFSET = 'offset';
const String COMPLETION_REQUEST_GET_SUGGESTIONS2_TIMEOUT = 'timeout';
const String COMPLETION_REQUEST_GET_SUGGESTIONS_FILE = 'file';
const String COMPLETION_REQUEST_GET_SUGGESTIONS_OFFSET = 'offset';
const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS =
'completion.getSuggestionDetails';
const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS2 =
'completion.getSuggestionDetails2';
const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS2_COMPLETION =
@ -160,17 +133,9 @@ const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS2_FILE = 'file';
const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS2_LIBRARY_URI =
'libraryUri';
const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS2_OFFSET = 'offset';
const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS_FILE = 'file';
const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS_ID = 'id';
const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS_LABEL = 'label';
const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS_OFFSET = 'offset';
const String COMPLETION_REQUEST_REGISTER_LIBRARY_PATHS =
'completion.registerLibraryPaths';
const String COMPLETION_REQUEST_REGISTER_LIBRARY_PATHS_PATHS = 'paths';
const String COMPLETION_REQUEST_SET_SUBSCRIPTIONS =
'completion.setSubscriptions';
const String COMPLETION_REQUEST_SET_SUBSCRIPTIONS_SUBSCRIPTIONS =
'subscriptions';
const String COMPLETION_RESPONSE_GET_SUGGESTIONS2_IS_INCOMPLETE =
'isIncomplete';
const String COMPLETION_RESPONSE_GET_SUGGESTIONS2_REPLACEMENT_LENGTH =
@ -178,13 +143,9 @@ const String COMPLETION_RESPONSE_GET_SUGGESTIONS2_REPLACEMENT_LENGTH =
const String COMPLETION_RESPONSE_GET_SUGGESTIONS2_REPLACEMENT_OFFSET =
'replacementOffset';
const String COMPLETION_RESPONSE_GET_SUGGESTIONS2_SUGGESTIONS = 'suggestions';
const String COMPLETION_RESPONSE_GET_SUGGESTIONS_ID = 'id';
const String COMPLETION_RESPONSE_GET_SUGGESTION_DETAILS2_CHANGE = 'change';
const String COMPLETION_RESPONSE_GET_SUGGESTION_DETAILS2_COMPLETION =
'completion';
const String COMPLETION_RESPONSE_GET_SUGGESTION_DETAILS_CHANGE = 'change';
const String COMPLETION_RESPONSE_GET_SUGGESTION_DETAILS_COMPLETION =
'completion';
const String DIAGNOSTIC_REQUEST_GET_DIAGNOSTICS = 'diagnostic.getDiagnostics';
const String DIAGNOSTIC_REQUEST_GET_SERVER_PORT = 'diagnostic.getServerPort';
const String DIAGNOSTIC_RESPONSE_GET_DIAGNOSTICS_CONTEXTS = 'contexts';

File diff suppressed because it is too large Load diff

View file

@ -137,8 +137,6 @@ abstract class AnalysisServer {
DeclarationsTracker? declarationsTracker;
DeclarationsTrackerData? declarationsTrackerData;
/// The DiagnosticServer for this AnalysisServer. If available, it can be used
/// to start an http diagnostics server or return the port for an existing
/// server.
@ -281,9 +279,12 @@ abstract class AnalysisServer {
driverWatcher: pluginWatcher);
if (options.featureSet.completion) {
// TODO(brianwilkerson): The DeclarationsTracker is used to find Dartdoc
// templates for substitution in doc comments, and probably shouldn't be
// gated on completion support being enabled, especially given that it
// will no longer used for available suggestions.
var tracker = declarationsTracker =
DeclarationsTracker(byteStore, resourceProvider);
declarationsTrackerData = DeclarationsTrackerData(tracker);
analysisDriverScheduler.outOfBandWorker =
CompletionLibrariesWorker(tracker);
}

View file

@ -94,14 +94,11 @@ class CiderCompletionComputer {
var result = await _logger.runAsync('Compute suggestions', () async {
var includedElementKinds = <ElementKind>{};
var includedElementNames = <String>{};
var includedSuggestionRelevanceTags =
<IncludedSuggestionRelevanceTag>[];
var manager = DartCompletionManager(
budget: CompletionBudget(CompletionBudget.defaultDuration),
includedElementKinds: includedElementKinds,
includedElementNames: includedElementNames,
includedSuggestionRelevanceTags: includedSuggestionRelevanceTags,
);
return await manager.computeSuggestions(

View file

@ -2,264 +2,12 @@
// 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:analysis_server/src/protocol_server.dart' as protocol;
import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/services/available_declarations.dart';
/// Compute which suggestion sets should be included into completion for
/// the given completion [request]. Depending on the file path, it might
/// include different sets, e.g. inside the `lib/` directory of a `Pub` package
/// only regular dependencies can be referenced, but `test/` can reference
/// both regular and "dev" dependencies.
void computeIncludedSetList(
DeclarationsTracker tracker,
DartCompletionRequest request,
List<protocol.IncludedSuggestionSet> includedSetList,
Set<String> includedElementNames,
) {
var context = tracker.getContext(request.analysisContext);
if (context == null) return;
var librariesObject = context.getLibraries(request.path);
var importedUriSet = request.libraryElement.importedLibraries
.map((importedLibrary) => importedLibrary.source.uri)
.toSet();
void includeLibrary(
Library library,
int importedRelevance,
int deprecatedRelevance,
int otherwiseRelevance,
) {
int relevance;
if (importedUriSet.contains(library.uri)) {
relevance = importedRelevance;
} else if (library.isDeprecated) {
relevance = deprecatedRelevance;
} else {
relevance = otherwiseRelevance;
}
includedSetList.add(
protocol.IncludedSuggestionSet(
library.id,
relevance,
displayUri: _getRelativeFileUri(request, library.uri),
),
);
for (var declaration in library.declarations) {
includedElementNames.add(declaration.name);
}
}
for (var library in librariesObject.context) {
includeLibrary(library, 8, 2, 5);
}
for (var library in librariesObject.dependencies) {
includeLibrary(library, 7, 1, 4);
}
for (var library in librariesObject.sdk) {
includeLibrary(library, 6, 0, 3);
}
}
protocol.CompletionAvailableSuggestionsParams
createCompletionAvailableSuggestions(
List<Library> changed,
List<int> removed,
) =>
protocol.CompletionAvailableSuggestionsParams(
changedLibraries: changed.map((library) {
return _protocolAvailableSuggestionSet(library);
}).toList(),
removedLibraries: removed,
);
/// Convert the [LibraryChange] into the corresponding protocol notification.
protocol.Notification createCompletionAvailableSuggestionsNotification(
List<Library> changed,
List<int> removed,
) =>
createCompletionAvailableSuggestions(changed, removed).toNotification();
/// Compute existing imports and elements that they provide.
protocol.Notification createExistingImportsNotification(
ResolvedUnitResult resolvedUnit,
) {
var uniqueStrings = _UniqueImportedStrings();
var uniqueElements = _UniqueImportedElements();
var existingImports = <protocol.ExistingImport>[];
var importElementList = resolvedUnit.libraryElement.libraryImports;
for (var import in importElementList) {
var importedLibrary = import.importedLibrary;
if (importedLibrary == null) continue;
var importedUriStr = '${importedLibrary.librarySource.uri}';
var existingImportElements = <int>[];
for (var element in import.namespace.definedNames.values) {
if (element.librarySource != null) {
var index = uniqueElements.indexOf(uniqueStrings, element);
existingImportElements.add(index);
}
}
existingImports.add(protocol.ExistingImport(
uniqueStrings.indexOf(importedUriStr),
existingImportElements,
));
}
return protocol.CompletionExistingImportsParams(
resolvedUnit.libraryElement.source.fullName,
protocol.ExistingImports(
protocol.ImportedElementSet(
uniqueStrings.values,
uniqueElements.uriList,
uniqueElements.nameList,
),
existingImports,
),
).toNotification();
}
// TODO(dantup): We need to expose this because the Declarations code currently
// returns declarations with DeclarationKinds but the DartCompletionManager
// gives us a list of "included ElementKinds". Maybe it would be better to expose
// includedDeclarationKinds and then just map that list to ElementKinds once in
// domain_completion for the original protocol?
protocol.ElementKind protocolElementKind(DeclarationKind kind) {
return switch (kind) {
DeclarationKind.CLASS => protocol.ElementKind.CLASS,
DeclarationKind.CLASS_TYPE_ALIAS => protocol.ElementKind.CLASS_TYPE_ALIAS,
DeclarationKind.CONSTRUCTOR => protocol.ElementKind.CONSTRUCTOR,
DeclarationKind.ENUM => protocol.ElementKind.ENUM,
DeclarationKind.ENUM_CONSTANT => protocol.ElementKind.ENUM_CONSTANT,
DeclarationKind.EXTENSION => protocol.ElementKind.EXTENSION,
DeclarationKind.FIELD => protocol.ElementKind.FIELD,
DeclarationKind.FUNCTION => protocol.ElementKind.FUNCTION,
DeclarationKind.FUNCTION_TYPE_ALIAS => protocol.ElementKind.TYPE_ALIAS,
DeclarationKind.GETTER => protocol.ElementKind.GETTER,
DeclarationKind.METHOD => protocol.ElementKind.METHOD,
DeclarationKind.MIXIN => protocol.ElementKind.MIXIN,
DeclarationKind.SETTER => protocol.ElementKind.SETTER,
DeclarationKind.TYPE_ALIAS => protocol.ElementKind.TYPE_ALIAS,
DeclarationKind.VARIABLE => protocol.ElementKind.TOP_LEVEL_VARIABLE
};
}
/// Computes the best URI to import [what] into the [unit] library.
String? _getRelativeFileUri(DartCompletionRequest request, Uri what) {
if (what.isScheme('file')) {
var pathContext = request.analysisSession.resourceProvider.pathContext;
var libraryPath = request.libraryElement.source.fullName;
var libraryFolder = pathContext.dirname(libraryPath);
var whatPath = pathContext.fromUri(what);
var relativePath = pathContext.relative(whatPath, from: libraryFolder);
return pathContext.split(relativePath).join('/');
}
return null;
}
protocol.AvailableSuggestion? _protocolAvailableSuggestion(
Declaration declaration) {
var label = declaration.name;
var parent = declaration.parent;
if (parent != null) {
if (declaration.kind == DeclarationKind.CONSTRUCTOR) {
label = parent.name;
if (declaration.name.isNotEmpty) {
label += '.${declaration.name}';
}
} else if (declaration.kind == DeclarationKind.ENUM_CONSTANT) {
label = '${parent.name}.${declaration.name}';
} else {
return null;
}
}
String declaringLibraryUri;
if (parent == null) {
declaringLibraryUri = '${declaration.locationLibraryUri}';
} else {
declaringLibraryUri = '${parent.locationLibraryUri}';
}
var relevanceTags = declaration.relevanceTags.toList()..add(declaration.name);
return protocol.AvailableSuggestion(
label,
declaringLibraryUri,
_protocolElement(declaration),
defaultArgumentListString: declaration.defaultArgumentListString,
defaultArgumentListTextRanges: declaration.defaultArgumentListTextRanges,
parameterNames: declaration.parameterNames,
parameterTypes: declaration.parameterTypes,
requiredParameterCount: declaration.requiredParameterCount,
relevanceTags: relevanceTags,
);
}
protocol.AvailableSuggestionSet _protocolAvailableSuggestionSet(
Library library) {
var items = <protocol.AvailableSuggestion>[];
void addItem(Declaration declaration) {
var suggestion = _protocolAvailableSuggestion(declaration);
if (suggestion != null) {
items.add(suggestion);
}
declaration.children.forEach(addItem);
}
for (var declaration in library.declarations) {
addItem(declaration);
}
return protocol.AvailableSuggestionSet(library.id, library.uriStr, items);
}
protocol.Element _protocolElement(Declaration declaration) {
return protocol.Element(
protocolElementKind(declaration.kind),
declaration.name,
_protocolElementFlags(declaration),
location: protocol.Location(
declaration.locationPath,
declaration.locationOffset,
0, // length
declaration.locationStartLine,
declaration.locationStartColumn,
endLine: declaration.locationStartLine,
endColumn: declaration.locationStartColumn,
),
parameters: declaration.parameters,
returnType: declaration.returnType,
typeParameters: declaration.typeParameters,
);
}
int _protocolElementFlags(Declaration declaration) {
return protocol.Element.makeFlags(
isAbstract: declaration.isAbstract,
isConst: declaration.isConst,
isDeprecated: declaration.isDeprecated,
isFinal: declaration.isFinal,
isStatic: declaration.isStatic,
);
}
class CompletionLibrariesWorker implements SchedulerWorker {
// TODO(brianwilkerson): Rename this class. It's used for gathering dartdoc
// information, but isn't used for completion data.
final DeclarationsTracker tracker;
CompletionLibrariesWorker(this.tracker);
@ -278,108 +26,3 @@ class CompletionLibrariesWorker implements SchedulerWorker {
tracker.doWork();
}
}
class DeclarationsTrackerData {
final DeclarationsTracker _tracker;
/// The set of libraries reported by [_tracker] so far.
///
/// We create [_tracker] at the server start, but the completion domain
/// should send available declarations only when the corresponding
/// subscription is done. OTOH, we don't want the changes stream grow
/// infinitely as the same libraries are changed multiple times. So, we drain
/// the changes stream in this map, and send it at subscription.
final Map<int, Library> _idToLibrary = {};
/// When the completion domain subscribes for changes, we start redirecting
/// changes to this listener.
void Function(LibraryChange)? _listener;
DeclarationsTrackerData(this._tracker) {
_tracker.changes.listen((change) {
var listener = _listener;
if (listener != null) {
listener(change);
} else {
for (var library in change.changed) {
_idToLibrary[library.id] = library;
}
for (var id in change.removed) {
_idToLibrary.remove(id);
}
}
});
}
/// Start listening for available libraries, and return the libraries that
/// were accumulated so far.
List<Library> startListening(void Function(LibraryChange) listener) {
if (_listener != null) {
throw StateError('Already listening.');
}
_listener = listener;
var accumulatedLibraries = _idToLibrary.values.toList();
_idToLibrary.clear();
return accumulatedLibraries;
}
void stopListening() {
if (_listener == null) {
throw StateError('Not listening.');
}
_listener = null;
}
}
class _ImportedElement {
final int uri;
final int name;
@override
final int hashCode;
_ImportedElement(this.uri, this.name) : hashCode = Object.hash(uri, name);
@override
bool operator ==(other) {
return other is _ImportedElement && other.uri == uri && other.name == name;
}
}
class _UniqueImportedElements {
final map = <_ImportedElement, int>{};
List<int> get nameList => map.keys.map((e) => e.name).toList();
List<int> get uriList => map.keys.map((e) => e.uri).toList();
int indexOf(_UniqueImportedStrings strings, Element element) {
var uriStr = '${element.librarySource!.uri}';
var wrapper = _ImportedElement(
strings.indexOf(uriStr),
strings.indexOf(element.name!),
);
var index = map[wrapper];
if (index == null) {
index = map.length;
map[wrapper] = index;
}
return index;
}
}
class _UniqueImportedStrings {
final map = <String, int>{};
List<String> get values => map.keys.toList();
int indexOf(String str) {
var index = map[str];
if (index == null) {
index = map.length;
map[str] = index;
}
return index;
}
}

View file

@ -1,94 +0,0 @@
// 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 'dart:async';
import 'package:analysis_server/protocol/protocol.dart';
import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
/// The handler for the `completion.getSuggestionDetails` request.
class CompletionGetSuggestionDetailsHandler extends CompletionHandler {
/// The identifiers of the latest `getSuggestionDetails` request.
/// We use it to abort previous requests.
int _latestGetSuggestionDetailsId = 0;
/// Initialize a newly created handler to be able to service requests for the
/// [server].
CompletionGetSuggestionDetailsHandler(
super.server, super.request, super.cancellationToken, super.performance);
@override
Future<void> handle() async {
if (completionIsDisabled) {
return;
}
var params = CompletionGetSuggestionDetailsParams.fromRequest(request);
var file = params.file;
if (server.sendResponseErrorIfInvalidFilePath(request, file)) {
return;
}
var libraryId = params.id;
var declarationsTracker = server.declarationsTracker;
if (declarationsTracker == null) {
sendResponse(Response.unsupportedFeature(
request.id, 'Completion is not enabled.'));
return;
}
var library = declarationsTracker.getLibrary(libraryId);
if (library == null) {
sendResponse(Response.invalidParameter(
request,
'libraryId',
'No such library: $libraryId',
));
return;
}
// The label might be `MyEnum.myValue`, but we import only `MyEnum`.
var requestedName = params.label;
if (requestedName.contains('.')) {
requestedName = requestedName.substring(
0,
requestedName.indexOf('.'),
);
}
const timeout = Duration(milliseconds: 1000);
var timer = Stopwatch()..start();
var id = ++_latestGetSuggestionDetailsId;
while (id == _latestGetSuggestionDetailsId && timer.elapsed < timeout) {
try {
var session = await server.getAnalysisSession(file);
if (session == null) {
sendResponse(Response.fileNotAnalyzed(request, 'libraryId'));
return;
}
var completion = params.label;
var builder = ChangeBuilder(session: session);
await builder.addDartFileEdit(file, (builder) {
var result = builder.importLibraryElement(library.uri);
if (result.prefix != null) {
completion = '${result.prefix}.$completion';
}
});
sendResult(CompletionGetSuggestionDetailsResult(completion,
change: builder.sourceChange));
return;
} on InconsistentAnalysisException {
// Loop around to try again.
}
}
// Timeout or abort, send the empty response.
sendResult(CompletionGetSuggestionDetailsResult(''));
}
}

View file

@ -4,9 +4,8 @@
import 'dart:async';
import 'package:analysis_server/protocol/protocol.dart';
import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
import 'package:analysis_server/src/protocol_server.dart';
import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
@ -75,6 +74,6 @@ class CompletionGetSuggestionDetails2Handler extends CompletionHandler {
}
// Timeout or abort, send the empty response.
sendResult(CompletionGetSuggestionDetailsResult(''));
sendResult(CompletionGetSuggestionDetails2Result('', SourceChange('')));
}
}

View file

@ -1,203 +0,0 @@
// 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 'dart:async';
import 'package:analysis_server/protocol/protocol.dart';
import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analysis_server/src/domains/completion/available_suggestions.dart';
import 'package:analysis_server/src/handler/legacy/completion_get_suggestions2.dart';
import 'package:analysis_server/src/provisional/completion/completion_core.dart';
import 'package:analysis_server/src/services/completion/completion_performance.dart';
import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
import 'package:analyzer/src/util/performance/operation_performance.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
/// The handler for the `completion.getSuggestions` request.
class CompletionGetSuggestionsHandler extends CompletionGetSuggestions2Handler {
/// Initialize a newly created handler to be able to service requests for the
/// [server].
CompletionGetSuggestionsHandler(
super.server, super.request, super.cancellationToken, super.performance);
@override
Future<void> handle() async {
if (completionIsDisabled) {
return;
}
var requestLatency = request.timeSinceRequest;
var budget = CompletionBudget(server.completionState.budgetDuration);
// extract and validate params
var params = CompletionGetSuggestionsParams.fromRequest(request);
var file = params.file;
var offset = params.offset;
if (server.sendResponseErrorIfInvalidFilePath(request, file)) {
return;
}
var performance = OperationPerformanceImpl('<root>');
await performance.runAsync(
'request',
(performance) async {
if (file.endsWith('.yaml')) {
// Return the response without results.
var completionId =
(server.completionState.nextCompletionId++).toString();
server.sendResponse(CompletionGetSuggestionsResult(completionId)
.toResponse(request.id));
// Send a notification with results.
final suggestions = computeYamlSuggestions(file, offset);
sendCompletionNotification(
completionId,
suggestions.replacementOffset,
suggestions.replacementLength,
suggestions.suggestions,
null,
null,
null,
null,
);
return;
} else if (!file.endsWith('.dart')) {
// Return the response without results.
var completionId =
(server.completionState.nextCompletionId++).toString();
server.sendResponse(CompletionGetSuggestionsResult(completionId)
.toResponse(request.id));
// Send a notification with results.
sendCompletionNotification(
completionId, offset, 0, [], null, null, null, null);
return;
}
var resolvedUnit = await server.getResolvedUnit(file);
if (resolvedUnit == null) {
server.sendResponse(Response.fileNotAnalyzed(request, file));
return;
}
server.requestStatistics?.addItemTimeNow(request, 'resolvedUnit');
if (offset < 0 || offset > resolvedUnit.content.length) {
server.sendResponse(Response.invalidParameter(
request,
'params.offset',
'Expected offset between 0 and source length inclusive,'
' but found $offset'));
return;
}
final completionPerformance = CompletionPerformance(
performance: performance,
path: file,
requestLatency: requestLatency,
content: resolvedUnit.content,
offset: offset,
);
server.recentPerformance.completion.add(completionPerformance);
var declarationsTracker = server.declarationsTracker;
if (declarationsTracker == null) {
server.sendResponse(Response.unsupportedFeature(
request.id, 'Completion is not enabled.'));
return;
}
var completionRequest = DartCompletionRequest.forResolvedUnit(
resolvedUnit: resolvedUnit,
offset: offset,
dartdocDirectiveInfo: server.getDartdocDirectiveInfoFor(
resolvedUnit,
),
);
var completionId =
(server.completionState.nextCompletionId++).toString();
setNewRequest(completionRequest);
// initial response without results
server.sendResponse(CompletionGetSuggestionsResult(completionId)
.toResponse(request.id));
// If the client opted into using available suggestion sets,
// create the kinds set, so signal the completion manager about opt-in.
Set<ElementKind>? includedElementKinds;
Set<String>? includedElementNames;
List<IncludedSuggestionRelevanceTag>? includedSuggestionRelevanceTags;
if (server.completionState.subscriptions
.contains(CompletionService.AVAILABLE_SUGGESTION_SETS)) {
includedElementKinds = <ElementKind>{};
includedElementNames = <String>{};
includedSuggestionRelevanceTags = <IncludedSuggestionRelevanceTag>[];
}
// Compute suggestions in the background
try {
var suggestionBuilders = <CompletionSuggestionBuilder>[];
try {
suggestionBuilders = await computeSuggestions(
budget: budget,
performance: performance,
request: completionRequest,
includedElementKinds: includedElementKinds,
includedElementNames: includedElementNames,
includedSuggestionRelevanceTags: includedSuggestionRelevanceTags,
// There's no filtering done afterwards,
// so don't filter here either.
useFilter: false,
);
} on AbortCompletion {
// Continue with empty suggestions list.
}
String? libraryFile;
var includedSuggestionSets = <IncludedSuggestionSet>[];
if (includedElementKinds != null && includedElementNames != null) {
libraryFile = resolvedUnit.libraryElement.source.fullName;
server.sendNotification(
createExistingImportsNotification(resolvedUnit),
);
computeIncludedSetList(
declarationsTracker,
completionRequest,
includedSuggestionSets,
includedElementNames,
);
}
const SEND_NOTIFICATION_TAG = 'send notification';
performance.run(SEND_NOTIFICATION_TAG, (_) {
sendCompletionNotification(
completionId,
completionRequest.replacementOffset,
completionRequest.replacementLength,
suggestionBuilders.map((e) => e.build()).toList(),
libraryFile,
includedSuggestionSets,
includedElementKinds?.toList(),
includedSuggestionRelevanceTags,
);
});
completionPerformance.computedSuggestionCount =
suggestionBuilders.length;
completionPerformance.transmittedSuggestionCount =
suggestionBuilders.length;
} finally {
_ifMatchesRequestClear(completionRequest);
}
},
);
}
void _ifMatchesRequestClear(DartCompletionRequest request) {
if (server.completionState.currentRequest == request) {
server.completionState.currentRequest = null;
}
}
}

View file

@ -45,7 +45,6 @@ class CompletionGetSuggestions2Handler extends CompletionHandler
required DartCompletionRequest request,
Set<ElementKind>? includedElementKinds,
Set<String>? includedElementNames,
List<IncludedSuggestionRelevanceTag>? includedSuggestionRelevanceTags,
NotImportedSuggestions? notImportedSuggestions,
required bool useFilter,
}) async {
@ -65,7 +64,6 @@ class CompletionGetSuggestions2Handler extends CompletionHandler
budget: budget,
includedElementKinds: includedElementKinds,
includedElementNames: includedElementNames,
includedSuggestionRelevanceTags: includedSuggestionRelevanceTags,
notImportedSuggestions: notImportedSuggestions,
);
@ -258,32 +256,6 @@ class CompletionGetSuggestions2Handler extends CompletionHandler
);
}
/// Send completion notification results.
void sendCompletionNotification(
String completionId,
int replacementOffset,
int replacementLength,
List<CompletionSuggestion> results,
String? libraryFile,
List<IncludedSuggestionSet>? includedSuggestionSets,
List<ElementKind>? includedElementKinds,
List<IncludedSuggestionRelevanceTag>? includedSuggestionRelevanceTags,
) {
server.sendNotification(
CompletionResultsParams(
completionId,
replacementOffset,
replacementLength,
results,
true,
libraryFile: libraryFile,
includedSuggestionSets: includedSuggestionSets,
includedElementKinds: includedElementKinds,
includedSuggestionRelevanceTags: includedSuggestionRelevanceTags,
).toNotification(),
);
}
void setNewRequest(DartCompletionRequest request) {
_abortCurrentRequest();
server.completionState.currentRequest = request;

View file

@ -1,53 +0,0 @@
// 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 'dart:async';
import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analysis_server/src/domains/completion/available_suggestions.dart';
import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
/// The handler for the `completion.setSubscriptions` request.
class CompletionSetSubscriptionsHandler extends CompletionHandler {
/// Initialize a newly created handler to be able to service requests for the
/// [server].
CompletionSetSubscriptionsHandler(
super.server, super.request, super.cancellationToken, super.performance);
@override
Future<void> handle() async {
if (completionIsDisabled) {
return;
}
var params = CompletionSetSubscriptionsParams.fromRequest(request);
var subscriptions = server.completionState.subscriptions;
subscriptions.clear();
subscriptions.addAll(params.subscriptions);
var data = server.declarationsTrackerData;
if (data != null) {
if (subscriptions.contains(CompletionService.AVAILABLE_SUGGESTION_SETS)) {
var soFarLibraries = data.startListening((change) {
server.sendNotification(
createCompletionAvailableSuggestionsNotification(
change.changed,
change.removed,
),
);
});
server.sendNotification(
createCompletionAvailableSuggestionsNotification(
soFarLibraries,
[],
),
);
} else {
data.stopListening();
}
}
sendResult(CompletionSetSubscriptionsResult());
}
}

View file

@ -34,11 +34,8 @@ import 'package:analysis_server/src/handler/legacy/analytics_enable.dart';
import 'package:analysis_server/src/handler/legacy/analytics_is_enabled.dart';
import 'package:analysis_server/src/handler/legacy/analytics_send_event.dart';
import 'package:analysis_server/src/handler/legacy/analytics_send_timing.dart';
import 'package:analysis_server/src/handler/legacy/completion_get_suggestion_details.dart';
import 'package:analysis_server/src/handler/legacy/completion_get_suggestion_details2.dart';
import 'package:analysis_server/src/handler/legacy/completion_get_suggestions.dart';
import 'package:analysis_server/src/handler/legacy/completion_get_suggestions2.dart';
import 'package:analysis_server/src/handler/legacy/completion_set_subscriptions.dart';
import 'package:analysis_server/src/handler/legacy/diagnostic_get_diagnostics.dart';
import 'package:analysis_server/src/handler/legacy/diagnostic_get_server_port.dart';
import 'package:analysis_server/src/handler/legacy/edit_bulk_fixes.dart';
@ -198,13 +195,9 @@ class LegacyAnalysisServer extends AnalysisServer {
ANALYTICS_REQUEST_SEND_EVENT: AnalyticsSendEventHandler.new,
ANALYTICS_REQUEST_SEND_TIMING: AnalyticsSendTimingHandler.new,
//
COMPLETION_REQUEST_GET_SUGGESTION_DETAILS:
CompletionGetSuggestionDetailsHandler.new,
COMPLETION_REQUEST_GET_SUGGESTION_DETAILS2:
CompletionGetSuggestionDetails2Handler.new,
COMPLETION_REQUEST_GET_SUGGESTIONS: CompletionGetSuggestionsHandler.new,
COMPLETION_REQUEST_GET_SUGGESTIONS2: CompletionGetSuggestions2Handler.new,
COMPLETION_REQUEST_SET_SUBSCRIPTIONS: CompletionSetSubscriptionsHandler.new,
//
DIAGNOSTIC_REQUEST_GET_DIAGNOSTICS: DiagnosticGetDiagnosticsHandler.new,
DIAGNOSTIC_REQUEST_GET_SERVER_PORT: DiagnosticGetServerPortHandler.new,

View file

@ -2,16 +2,12 @@
// 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:analysis_server/protocol/protocol_generated.dart';
import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
class CompletionState {
/// The time budget for a completion request.
Duration budgetDuration = CompletionBudget.defaultDuration;
/// The completion services that the client is currently subscribed to.
final Set<CompletionService> subscriptions = <CompletionService>{};
/// The next completion response id.
int nextCompletionId = 0;

View file

@ -23,7 +23,6 @@ import 'package:analysis_server/src/services/completion/dart/not_imported_contri
import 'package:analysis_server/src/services/completion/dart/override_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/record_literal_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/redirecting_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/relevance_tables.g.dart';
import 'package:analysis_server/src/services/completion/dart/static_member_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
import 'package:analysis_server/src/services/completion/dart/suggestion_collector.dart';
@ -90,11 +89,6 @@ class DartCompletionManager {
/// included suggestion sets.
final Set<String>? includedElementNames;
/// If [includedElementKinds] is not null, must be also not `null`, and
/// will be filled with tags for suggestions that should be given higher
/// relevance than other included suggestions.
final List<IncludedSuggestionRelevanceTag>? includedSuggestionRelevanceTags;
/// The listener to be notified at certain points in the process of building
/// suggestions, or `null` if no notification should occur.
final SuggestionListener? listener;
@ -113,15 +107,10 @@ class DartCompletionManager {
required this.budget,
this.includedElementKinds,
this.includedElementNames,
this.includedSuggestionRelevanceTags,
this.listener,
this.notImportedSuggestions,
}) : assert((includedElementKinds != null &&
includedElementNames != null &&
includedSuggestionRelevanceTags != null) ||
(includedElementKinds == null &&
includedElementNames == null &&
includedSuggestionRelevanceTags == null));
}) : assert((includedElementKinds != null && includedElementNames != null) ||
(includedElementKinds == null && includedElementNames == null));
Future<List<CompletionSuggestionBuilder>> computeSuggestions(
DartCompletionRequest request,
@ -170,7 +159,6 @@ class DartCompletionManager {
if (includedElementKinds != null) {
_addIncludedElementKinds(request);
_addIncludedSuggestionRelevanceTags(request);
}
final notImportedSuggestions = this.notImportedSuggestions;
@ -250,55 +238,6 @@ class DartCompletionManager {
}
}
void _addIncludedSuggestionRelevanceTags(DartCompletionRequest request) {
final includedSuggestionRelevanceTags =
this.includedSuggestionRelevanceTags!;
var location = request.opType.completionLocation;
if (location != null) {
var locationTable = elementKindRelevance[location];
if (locationTable != null) {
var inConstantContext = request.inConstantContext;
for (var entry in locationTable.entries) {
var kind = entry.key.toString();
var elementBoost = (entry.value.upper * 100).floor();
includedSuggestionRelevanceTags
.add(IncludedSuggestionRelevanceTag(kind, elementBoost));
if (inConstantContext) {
includedSuggestionRelevanceTags.add(IncludedSuggestionRelevanceTag(
'$kind+const', elementBoost + 100));
}
}
}
}
var type = request.contextType;
if (type is InterfaceType) {
var element = type.element;
var tag = '${element.librarySource.uri}::${element.name}';
if (element is EnumElement) {
includedSuggestionRelevanceTags.add(
IncludedSuggestionRelevanceTag(
tag,
RelevanceBoost.availableEnumConstant,
),
);
} else {
// TODO(brianwilkerson): This was previously used to boost exact type
// matches. For example, if the context type was `Foo`, then the class
// `Foo` and it's constructors would be given this boost. Now this
// boost will almost always be ignored because the element boost will
// be bigger. Find a way to use this boost without negating the element
// boost, which is how we get constructors to come before classes.
includedSuggestionRelevanceTags.add(
IncludedSuggestionRelevanceTag(
tag,
RelevanceBoost.availableDeclaration,
),
);
}
}
}
// Run the first pass of the code completion algorithm.
VisibilityTracker? _runFirstPass(DartCompletionRequest request,
SuggestionBuilder builder, bool skipImports) {

View file

@ -1544,16 +1544,6 @@ class SubscriptionsPage extends DiagnosticPageWithNav {
buf.write('$item');
});
}
// completion domain
h3('Completion domain subscriptions');
ul(CompletionService.VALUES, (service) {
if (server.completionState.subscriptions.contains(service)) {
buf.write('$service (has subscriptions)');
} else {
buf.write('$service (no subscriptions)');
}
});
}
}

View file

@ -175,10 +175,7 @@ $actual
name: test
''');
driver = CompletionDriver(
supportsAvailableSuggestions: false,
server: this,
);
driver = CompletionDriver(server: this);
await driver.createProject();
// TODO(pq): add logic (possibly to driver) that waits for SDK suggestions

View file

@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:math' as math;
import 'package:analysis_server/protocol/protocol.dart';
import 'package:analysis_server/protocol/protocol_constants.dart';
@ -14,63 +13,15 @@ import 'package:matcher/matcher.dart';
import 'package:meta/meta.dart';
import '../../analysis_server_base.dart';
import '../../constants.dart';
import 'expect_mixin.dart';
CompletionSuggestion _createCompletionSuggestionFromAvailableSuggestion(
AvailableSuggestion suggestion,
int suggestionSetRelevance,
Map<String, IncludedSuggestionRelevanceTag>
includedSuggestionRelevanceTags) {
// https://github.com/JetBrains/intellij-plugins/blob/59018828753973324ea0500fa4bae93563f1aacf/Dart/src/com/jetbrains/lang/dart/ide/completion/DartServerCompletionContributor.java#L568
// https://github.com/Dart-Code/Dart-Code/blob/d4e98d2ca2636be5da7334760d73face12414e70/src/extension/providers/dart_completion_item_provider.ts#L187
var relevanceBoost = 0;
var relevanceTags = suggestion.relevanceTags;
if (relevanceTags != null) {
for (var tag in relevanceTags) {
var relevanceTag = includedSuggestionRelevanceTags[tag];
if (relevanceTag != null) {
relevanceBoost = math.max(relevanceBoost, relevanceTag.relevanceBoost);
}
}
}
// TODO(pq): in IDEA, this is "UNKNOWN" but here we need a value; figure out what's up.
var suggestionKind = CompletionSuggestionKind.IDENTIFIER;
if (suggestion.element.kind == ElementKind.CONSTRUCTOR ||
suggestion.element.kind == ElementKind.FUNCTION ||
suggestion.element.kind == ElementKind.METHOD) {
suggestionKind = CompletionSuggestionKind.INVOCATION;
}
return CompletionSuggestion(
suggestionKind,
suggestionSetRelevance + relevanceBoost,
suggestion.label,
suggestion.label.length,
0,
suggestion.element.isDeprecated,
false,
element: suggestion.element,
returnType: suggestion.element.returnType,
defaultArgumentListString: suggestion.defaultArgumentListString,
defaultArgumentListTextRanges: suggestion.defaultArgumentListTextRanges,
parameterNames: suggestion.parameterNames,
);
}
class CompletionDriver with ExpectMixin {
final PubPackageAnalysisServerTest server;
final bool supportsAvailableSuggestions;
Map<String, Completer<void>> receivedSuggestionsCompleters = {};
List<CompletionSuggestion> suggestions = [];
Map<String, List<CompletionSuggestion>> allSuggestions = {};
final Map<int, AvailableSuggestionSet> idToSetMap = {};
final Map<String, AvailableSuggestionSet> uriToSetMap = {};
final Map<String, CompletionResultsParams> idToSuggestions = {};
final Map<String, ExistingImports> fileToExistingImports = {};
final Map<String, List<AnalysisError>> filesErrors = {};
@ -80,10 +31,7 @@ class CompletionDriver with ExpectMixin {
late int replacementOffset;
late int replacementLength;
CompletionDriver({
required this.supportsAvailableSuggestions,
required this.server,
}) {
CompletionDriver({required this.server}) {
server.serverChannel.notifications.listen(processNotification);
}
@ -112,14 +60,6 @@ class CompletionDriver with ExpectMixin {
Future<void> createProject() async {
await server.setRoots(included: [server.workspaceRootPath], excluded: []);
if (supportsAvailableSuggestions) {
await server.handleRequest(
CompletionSetSubscriptionsParams([
CompletionService.AVAILABLE_SUGGESTION_SETS,
]).toRequest('0'),
);
}
}
Future<List<CompletionSuggestion>> getSuggestions() async {
@ -138,124 +78,7 @@ class CompletionDriver with ExpectMixin {
@mustCallSuper
Future<void> processNotification(Notification notification) async {
if (notification.event == COMPLETION_RESULTS) {
var params = CompletionResultsParams.fromNotification(notification);
var id = params.id;
assertValidId(id);
idToSuggestions[id] = params;
replacementOffset = params.replacementOffset;
replacementLength = params.replacementLength;
suggestions = params.results;
expect(allSuggestions.containsKey(id), isFalse);
allSuggestions[id] = params.results;
var includedKinds = params.includedElementKinds;
//
// Collect relevance information.
//
// https://github.com/JetBrains/intellij-plugins/blob/59018828753973324ea0500fa4bae93563f1aacf/Dart/src/com/jetbrains/lang/dart/analyzer/DartAnalysisServerService.java#L467
var includedRelevanceTags = <String, IncludedSuggestionRelevanceTag>{};
var includedSuggestionRelevanceTags =
params.includedSuggestionRelevanceTags;
if (includedSuggestionRelevanceTags != null) {
for (var includedRelevanceTag in includedSuggestionRelevanceTags) {
includedRelevanceTags[includedRelevanceTag.tag] =
includedRelevanceTag;
}
}
//
// Identify imported libraries.
//
var importedLibraryUris = <String>{};
var existingImports = fileToExistingImports[params.libraryFile];
if (existingImports != null) {
for (var existingImport in existingImports.imports) {
var uri = existingImports.elements.strings[existingImport.uri];
importedLibraryUris.add(uri);
}
}
//
// Partition included suggestion sets into imported and not-imported groups.
//
var importedSets = <IncludedSuggestionSet>[];
var notImportedSets = <IncludedSuggestionSet>[];
for (var set in params.includedSuggestionSets!) {
var id = set.id;
while (!idToSetMap.containsKey(id)) {
await Future.delayed(const Duration(milliseconds: 1));
}
var suggestionSet = idToSetMap[id]!;
if (importedLibraryUris.contains(suggestionSet.uri)) {
importedSets.add(set);
} else {
notImportedSets.add(set);
}
}
//
// Add suggestions.
//
// First from imported then from not-imported sets.
//
void addSuggestion(
AvailableSuggestion suggestion, IncludedSuggestionSet includeSet) {
var kind = suggestion.element.kind;
if (!includedKinds!.contains(kind)) {
return;
}
var completionSuggestion =
_createCompletionSuggestionFromAvailableSuggestion(
suggestion, includeSet.relevance, includedRelevanceTags);
suggestions.add(completionSuggestion);
}
// Track seen elements to ensure they are not duplicated.
var seenElements = <String>{};
// Suggestions can be uniquely identified by kind, label and uri.
String suggestionId(AvailableSuggestion s) =>
'${s.declaringLibraryUri}:${s.element.kind}:${s.label}';
for (var includeSet in importedSets) {
var set = idToSetMap[includeSet.id]!;
for (var suggestion in set.items) {
if (seenElements.add(suggestionId(suggestion))) {
addSuggestion(suggestion, includeSet);
}
}
}
for (var includeSet in notImportedSets) {
var set = idToSetMap[includeSet.id]!;
for (var suggestion in set.items) {
if (!seenElements.contains(suggestionId(suggestion))) {
addSuggestion(suggestion, includeSet);
}
}
}
_getResultsCompleter(id).complete(null);
} else if (notification.event ==
COMPLETION_NOTIFICATION_AVAILABLE_SUGGESTIONS) {
var params = CompletionAvailableSuggestionsParams.fromNotification(
notification,
);
for (var set in params.changedLibraries!) {
idToSetMap[set.id] = set;
uriToSetMap[set.uri] = set;
}
for (var id in params.removedLibraries!) {
var set = idToSetMap.remove(id);
uriToSetMap.remove(set?.uri);
}
} else if (notification.event == COMPLETION_NOTIFICATION_EXISTING_IMPORTS) {
if (notification.event == COMPLETION_NOTIFICATION_EXISTING_IMPORTS) {
var params = CompletionExistingImportsParams.fromNotification(
notification,
);
@ -273,13 +96,4 @@ class CompletionDriver with ExpectMixin {
print('Unhandled notification: ${notification.event}');
}
}
Future<void> waitForSetWithUri(String uri) async {
while (uriToSetMap[uri] == null) {
await Future.delayed(const Duration(milliseconds: 1));
}
}
Completer<void> _getResultsCompleter(String id) =>
receivedSuggestionsCompleters.putIfAbsent(id, () => Completer<void>());
}

View file

@ -93,6 +93,7 @@ class CompletionTestCase extends AbstractCompletionDomainTest {
await getSuggestions(
path: testFile.path,
completionOffset: completionOffset,
maxResults: 1 << 10,
);
filterResults(spec.source);

View file

@ -4,31 +4,22 @@
import 'dart:async';
import 'package:analysis_server/src/analysis_server.dart';
import 'package:analysis_server/src/plugin/plugin_manager.dart';
import 'package:analysis_server/src/protocol_server.dart';
import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/instrumentation/service.dart';
import 'package:analyzer/src/test_utilities/package_config_file_builder.dart';
import 'package:analyzer_plugin/protocol/protocol.dart' as plugin;
import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'analysis_server_base.dart';
import 'domain_completion_util.dart';
import 'mocks.dart';
import 'services/completion/dart/completion_check.dart';
import 'services/completion/dart/completion_printer.dart' as printer;
import 'services/completion/dart/text_expectations.dart';
import 'src/plugin/plugin_manager_test.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(CompletionDomainHandlerGetSuggestionDetails2Test);
defineReflectiveTests(CompletionDomainHandlerGetSuggestions2Test);
defineReflectiveTests(CompletionDomainHandlerGetSuggestionsTest);
});
}
@ -2097,902 +2088,6 @@ suggestions
}
}
@reflectiveTest
class CompletionDomainHandlerGetSuggestionsTest
extends AbstractCompletionDomainTest {
Future<void> test_ArgumentList_constructor_named_fieldFormalParam() async {
// https://github.com/dart-lang/sdk/issues/31023
await getTestCodeSuggestions('''
void f() { new A(field: ^);}
class A {
A({this.field: -1}) {}
}
''');
}
Future<void> test_ArgumentList_constructor_named_param_label() async {
await getTestCodeSuggestions('void f() { new A(^);}'
'class A { A({one, two}) {} }');
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'one: ');
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'two: ');
expect(suggestions, hasLength(2));
}
Future<void> test_ArgumentList_factory_named_param_label() async {
await getTestCodeSuggestions('void f() { new A(^);}'
'class A { factory A({one, two}) => throw 0; }');
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'one: ');
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'two: ');
expect(suggestions, hasLength(2));
}
Future<void>
test_ArgumentList_function_named_fromPositionalNumeric_withoutSpace() async {
await getTestCodeSuggestions('void f(int a, {int b = 0}) {}'
'void g() { f(2, ^3); }');
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'b: ');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'const');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'false');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'null');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'true');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'switch');
// Ensure we don't try to replace the following arg.
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
}
Future<void>
test_ArgumentList_function_named_fromPositionalNumeric_withSpace() async {
await getTestCodeSuggestions('void f(int a, {int b = 0}) {}'
'void g() { f(2, ^ 3); }');
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'b: ');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'const');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'false');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'null');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'true');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'switch');
// Ensure we don't try to replace the following arg.
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
}
Future<void>
test_ArgumentList_function_named_fromPositionalVariable_withoutSpace() async {
await getTestCodeSuggestions('void f(int a, {int b = 0}) {}'
'var foo = 1;'
'void g() { f(2, ^foo); }');
// The named arg "b: " should not replace anything.
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'b: ',
replacementOffset: null, replacementLength: 0);
assertHasResult(CompletionSuggestionKind.KEYWORD, 'const');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'false');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'null');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'true');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'switch');
}
Future<void>
test_ArgumentList_function_named_fromPositionalVariable_withSpace() async {
await getTestCodeSuggestions('void f(int a, {int b = 0}) {}'
'var foo = 1;'
'void g() { f(2, ^ foo); }');
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'b: ');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'const');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'false');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'null');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'true');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'switch');
// Ensure we don't try to replace the following arg.
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
}
Future<void> test_ArgumentList_function_named_partiallyTyped() async {
await getTestCodeSuggestions('''
class C {
void m(String firstString, {String secondString}) {}
void n() {
m('a', se^'b');
}
}
''');
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'secondString: ');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'const');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'false');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'null');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'this');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'true');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'super');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'switch');
// Ensure we replace the correct section.
expect(replacementOffset, equals(completionOffset - 2));
expect(replacementLength, equals(2));
}
Future<void> test_ArgumentList_imported_function_named_param() async {
await getTestCodeSuggestions('void f() { int.parse("16", ^);}');
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'radix: ');
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'onError: ');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'const');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'false');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'null');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'true');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'switch');
}
Future<void> test_ArgumentList_imported_function_named_param1() async {
await getTestCodeSuggestions('void f() { foo(o^);} foo({one, two}) {}');
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'one: ');
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'two: ');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'const');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'false');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'null');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'true');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'switch');
}
Future<void> test_ArgumentList_imported_function_named_param2() async {
await getTestCodeSuggestions('void f() {A a = new A(); a.foo(one: 7, ^);}'
'class A { foo({one, two}) {} }');
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'two: ');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'const');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'false');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'null');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'true');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'switch');
}
Future<void> test_ArgumentList_imported_function_named_param_label1() async {
await getTestCodeSuggestions('void f() { int.parse("16", r^: 16);}');
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'radix');
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'onError');
expect(suggestions, hasLength(2));
}
Future<void> test_ArgumentList_imported_function_named_param_label3() async {
await getTestCodeSuggestions('void f() { int.parse("16", ^: 16);}');
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'radix');
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'onError');
expect(suggestions, hasLength(2));
}
Future<void> test_catch() async {
await getTestCodeSuggestions('void f() {try {} ^}');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'on');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'catch');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'finally');
expect(suggestions, hasLength(3));
}
Future<void> test_catch2() async {
await getTestCodeSuggestions('void f() {try {} on Foo {} ^}');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'on');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'catch');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'finally');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'for');
suggestions.firstWhere(
(CompletionSuggestion suggestion) =>
suggestion.kind != CompletionSuggestionKind.KEYWORD, orElse: () {
fail('Expected suggestions other than keyword suggestions');
});
}
Future<void> test_catch3() async {
await getTestCodeSuggestions('void f() {try {} catch (e) {} finally {} ^}');
assertNoResult('on');
assertNoResult('catch');
assertNoResult('finally');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'for');
suggestions.firstWhere(
(CompletionSuggestion suggestion) =>
suggestion.kind != CompletionSuggestionKind.KEYWORD, orElse: () {
fail('Expected suggestions other than keyword suggestions');
});
}
Future<void> test_catch4() async {
await getTestCodeSuggestions('void f() {try {} finally {} ^}');
assertNoResult('on');
assertNoResult('catch');
assertNoResult('finally');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'for');
suggestions.firstWhere(
(CompletionSuggestion suggestion) =>
suggestion.kind != CompletionSuggestionKind.KEYWORD, orElse: () {
fail('Expected suggestions other than keyword suggestions');
});
}
Future<void> test_catch5() async {
await getTestCodeSuggestions('void f() {try {} ^ finally {}}');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'on');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'catch');
expect(suggestions, hasLength(2));
}
Future<void> test_constructor() async {
await getTestCodeSuggestions('class A {bool foo; A() : ^;}');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'super');
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'foo');
}
Future<void> test_constructor2() async {
await getTestCodeSuggestions('class A {bool foo; A() : s^;}');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'super');
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'foo');
}
Future<void> test_constructor3() async {
await getTestCodeSuggestions('class A {bool foo; A() : a=7,^;}');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'super');
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'foo');
}
Future<void> test_constructor4() async {
await getTestCodeSuggestions('class A {bool foo; A() : a=7,s^;}');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'super');
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'foo');
}
Future<void> test_constructor5() async {
await getTestCodeSuggestions('class A {bool foo; A() : a=7,s^}');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'super');
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'foo');
}
Future<void> test_constructor6() async {
await getTestCodeSuggestions(
'class A {bool foo; A() : a=7,^ void bar() {}}');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'super');
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'foo');
}
Future<void> test_extension() async {
await getTestCodeSuggestions('''
class MyClass {
void foo() {
ba^
}
}
extension MyClassExtension on MyClass {
void bar() {}
}
''');
assertHasResult(CompletionSuggestionKind.INVOCATION, 'bar');
}
Future<void> test_html() async {
//
// We no longer support the analysis of non-dart files.
//
await getCodeSuggestions(
path: convertPath('$testPackageLibPath/test.html'),
content: '<html>^</html>',
);
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
expect(suggestions, hasLength(0));
}
Future<void> test_import_uri_with_trailing() async {
final filePath = '/project/bin/testA.dart';
final incompleteImportText = toUriStr('/project/bin/t');
newFile(filePath, 'library libA;');
await getTestCodeSuggestions('''
import "$incompleteImportText^.dart";
void f() {}''');
expect(replacementOffset,
equals(completionOffset - incompleteImportText.length));
expect(replacementLength, equals(5 + incompleteImportText.length));
assertHasResult(CompletionSuggestionKind.IMPORT, toUriStr(filePath));
assertNoResult('test');
}
Future<void> test_imports() async {
await getTestCodeSuggestions('''
import 'dart:html';
void f() {^}
''');
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'Object',
elementKind: ElementKind.CLASS);
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'HtmlElement',
elementKind: ElementKind.CLASS);
assertNoResult('test');
}
Future<void> test_imports_aborted_new_request() async {
addTestFile('''
class foo { }
c''');
completionOffset = 31;
// Make a request for suggestions
var request1 =
CompletionGetSuggestionsParams(testFile.path, completionOffset)
.toRequest('7');
var responseFuture1 = serverChannel.simulateRequestFromClient(request1);
// Make another request before the first request completes
var request2 =
CompletionGetSuggestionsParams(testFile.path, completionOffset)
.toRequest('8');
var responseFuture2 = serverChannel.simulateRequestFromClient(request2);
// Await first response
var response1 = await responseFuture1;
var result1 = CompletionGetSuggestionsResult.fromResponse(response1);
assertValidId(result1.id);
// Await second response
var response2 = await responseFuture2;
var result2 = CompletionGetSuggestionsResult.fromResponse(response2);
assertValidId(result2.id);
// Wait for all processing to be complete
await server.onAnalysisComplete;
await pumpEventQueue();
// Assert that first request has been aborted
expect(allSuggestions[result1.id], hasLength(0));
// Assert valid results for the second request
expect(allSuggestions[result2.id], same(suggestions));
assertHasResult(CompletionSuggestionKind.KEYWORD, 'class');
}
@failingTest
Future<void> test_imports_aborted_source_changed() async {
// TODO(brianwilkerson): Figure out whether this test makes sense when
// running the new driver. It waits for an initial empty notification then
// waits for a new notification. But I think that under the driver we only
// ever send one notification.
addTestFile('''
class foo { }
c^''');
// Make a request for suggestions
var request =
CompletionGetSuggestionsParams(testFile.path, completionOffset)
.toRequest('0');
var responseFuture = handleSuccessfulRequest(request);
// Simulate user deleting text after request but before suggestions returned
server.updateContent(
'uc1', {testFile.path: AddContentOverlay(testFileContent)});
server.updateContent('uc2', {
testFile.path:
ChangeContentOverlay([SourceEdit(completionOffset - 1, 1, '')])
});
// Await a response
var response = await responseFuture;
completionId = response.id;
assertValidId(completionId);
// Wait for all processing to be complete
await server.onAnalysisComplete;
await pumpEventQueue();
// Assert that request has been aborted
expect(suggestionsDone, isTrue);
expect(suggestions, hasLength(0));
}
Future<void> test_imports_incremental() async {
addTestFile('''library foo;
e^
import "dart:async";
import "package:foo/foo.dart";
class foo { }''');
completionOffset = 20;
await waitForTasksFinished();
server.updateContent(
'uc1', {testFile.path: AddContentOverlay(testFileContent)});
server.updateContent('uc2', {
testFile.path:
ChangeContentOverlay([SourceEdit(completionOffset, 0, 'xp')])
});
completionOffset += 2;
await getSuggestions(
path: testFile.path,
completionOffset: completionOffset,
);
expect(replacementOffset, completionOffset - 3);
expect(replacementLength, 3);
assertHasResult(CompletionSuggestionKind.KEYWORD, 'export \'\';',
selectionOffset: 8);
assertHasResult(CompletionSuggestionKind.KEYWORD, 'import \'\';',
selectionOffset: 8);
assertNoResult('extends');
assertNoResult('library');
}
Future<void> test_imports_partial() async {
addTestFile('''
import "package:foo/foo.dart";
import "package:bar/bar.dart";
class Baz { }''');
completionOffset = 0;
// Wait for analysis then edit the content
await waitForTasksFinished();
var precedingContent = testFileContent.substring(0, 0);
var trailingContent = testFileContent.substring(completionOffset);
var revisedContent = '${precedingContent}i$trailingContent';
++completionOffset;
server.handleRequest(AnalysisUpdateContentParams(
{testFile.path: AddContentOverlay(revisedContent)}).toRequest('add1'));
// Request code completion immediately after edit
var response = await handleSuccessfulRequest(
CompletionGetSuggestionsParams(testFile.path, completionOffset)
.toRequest('0'));
completionId = response.id;
assertValidId(completionId);
await waitForTasksFinished();
// wait for response to arrive
// because although the analysis is complete (waitForTasksFinished)
// the response may not yet have been processed
while (replacementOffset == null) {
await Future.delayed(Duration(milliseconds: 5));
}
expect(replacementOffset, completionOffset - 1);
expect(replacementLength, 1);
assertHasResult(CompletionSuggestionKind.KEYWORD, 'library');
assertHasResult(CompletionSuggestionKind.KEYWORD, 'import \'\';',
selectionOffset: 8);
assertHasResult(CompletionSuggestionKind.KEYWORD, 'export \'\';',
selectionOffset: 8);
assertHasResult(CompletionSuggestionKind.KEYWORD, 'part \'\';',
selectionOffset: 6);
assertNoResult('extends');
}
Future<void> test_imports_prefixed() async {
await getTestCodeSuggestions('''
import 'dart:html' as foo;
void f() {^}
''');
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'Object',
elementKind: ElementKind.CLASS);
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'foo');
assertNoResult('HtmlElement');
assertNoResult('test');
}
Future<void> test_imports_prefixed2() async {
await getTestCodeSuggestions('''
import 'dart:html' as foo;
void f() {foo.^}
''');
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'HtmlElement');
assertNoResult('test');
}
Future<void> test_inComment_block_beforeDartDoc() async {
await getTestCodeSuggestions('''
/* text ^ */
/// some doc comments
class SomeClass {}
''');
expect(suggestions, isEmpty);
}
Future<void> test_inComment_block_beforeNode() async {
await getTestCodeSuggestions('''
void f(aaa, bbb) {
/* text ^ */
print(42);
}
''');
expect(suggestions, isEmpty);
}
Future<void> test_inComment_endOfFile_withNewline() async {
await getTestCodeSuggestions('''
// text ^
''');
expect(suggestions, isEmpty);
}
Future<void> test_inComment_endOfFile_withoutNewline() async {
await getTestCodeSuggestions('// text ^');
expect(suggestions, isEmpty);
}
Future<void> test_inComment_endOfLine_beforeDartDoc() async {
await getTestCodeSuggestions('''
// text ^
/// some doc comments
class SomeClass {}
''');
expect(suggestions, isEmpty);
}
Future<void> test_inComment_endOfLine_beforeNode() async {
await getTestCodeSuggestions('''
void f(aaa, bbb) {
// text ^
print(42);
}
''');
expect(suggestions, isEmpty);
}
Future<void> test_inComment_endOfLine_beforeToken() async {
await getTestCodeSuggestions('''
void f(aaa, bbb) {
// text ^
}
''');
expect(suggestions, isEmpty);
}
Future<void> test_inDartDoc1() async {
await getTestCodeSuggestions('''
/// ^
void f(aaa, bbb) {}
''');
expect(suggestions, isEmpty);
}
Future<void> test_inDartDoc2() async {
await getTestCodeSuggestions('''
/// Some text^
void f(aaa, bbb) {}
''');
expect(suggestions, isEmpty);
}
Future<void> test_inDartDoc3() async {
await getTestCodeSuggestions('''
class MyClass {
/// ^
void foo() {}
void bar() {}
}
extension MyClassExtension on MyClass {
void baz() {}
}
''');
expect(suggestions, isEmpty);
}
Future<void> test_inDartDoc_reference1() async {
newFile('/testA.dart', '''
part of libA;
foo(bar) => 0;''');
await getTestCodeSuggestions('''
library libA;
part "${toUriStr('/testA.dart')}";
import "dart:math";
/// The [^]
void f(aaa, bbb) {}
''');
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'f');
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'foo');
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'min');
}
Future<void> test_inDartDoc_reference2() async {
await getTestCodeSuggestions('''
/// The [m^]
void f(aaa, bbb) {}
''');
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'f');
}
Future<void> test_inDartDoc_reference3() async {
await getTestCodeSuggestions('''
class MyClass {
/// [^]
void foo() {}
void bar() {}
}
extension MyClassExtension on MyClass {
void baz() {}
}
''');
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'bar');
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'baz');
}
Future<void> test_inherited() async {
await getTestCodeSuggestions('''
class A {
m() {}
}
class B extends A {
x() {^}
}
''');
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
assertHasResult(CompletionSuggestionKind.INVOCATION, 'm');
}
Future<void> test_invalidFilePathFormat_notAbsolute() async {
var request = CompletionGetSuggestionsParams('test.dart', 0).toRequest('0');
var response = await handleRequest(request);
assertResponseFailure(
response,
requestId: '0',
errorCode: RequestErrorCode.INVALID_FILE_PATH_FORMAT,
);
}
Future<void> test_invalidFilePathFormat_notNormalized() async {
var request =
CompletionGetSuggestionsParams(convertPath('/foo/../bar/test.dart'), 0)
.toRequest('0');
var response = await handleRequest(request);
assertResponseFailure(
response,
requestId: '0',
errorCode: RequestErrorCode.INVALID_FILE_PATH_FORMAT,
);
}
Future<void> test_invocation() async {
await getTestCodeSuggestions('class A {b() {}} void f() {A a; a.^}');
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
assertHasResult(CompletionSuggestionKind.INVOCATION, 'b');
}
Future<void> test_invocation_withTrailingStmt() async {
await getTestCodeSuggestions(
'class A {b() {}} void f() {A a; a.^ int x = 7;}');
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
assertHasResult(CompletionSuggestionKind.INVOCATION, 'b');
}
Future<void> test_is_asPrefixedIdentifierStart() async {
await getTestCodeSuggestions('''
class A { var isVisible;}
void f(A p) { var v1 = p.is^; }''');
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'isVisible');
}
Future<void> test_keyword() async {
await getTestCodeSuggestions('library A; cl^');
expect(replacementOffset, equals(completionOffset - 2));
expect(replacementLength, equals(2));
assertHasResult(CompletionSuggestionKind.KEYWORD, 'export \'\';',
selectionOffset: 8);
assertHasResult(CompletionSuggestionKind.KEYWORD, 'class');
}
Future<void> test_local_implicitCreation() async {
await getTestCodeSuggestions('''
class A {
A();
A.named();
}
void f() {
^
}
''');
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
// The class is suggested.
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'A',
elementKind: ElementKind.CLASS);
// Both constructors - default and named, are suggested.
assertHasResult(CompletionSuggestionKind.INVOCATION, 'A',
elementKind: ElementKind.CONSTRUCTOR);
assertHasResult(CompletionSuggestionKind.INVOCATION, 'A.named',
elementKind: ElementKind.CONSTRUCTOR);
}
Future<void> test_local_named_constructor() async {
await getTestCodeSuggestions('class A {A.c(); x() {new A.^}}');
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
assertHasResult(CompletionSuggestionKind.INVOCATION, 'c');
assertNoResult('A');
}
Future<void> test_local_override() async {
newFile('/project/bin/a.dart', 'class A {m() {}}');
await getTestCodeSuggestions('''
import 'a.dart';
class B extends A {
m() {}
x() {^}
}
''');
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
assertHasResult(CompletionSuggestionKind.INVOCATION, 'm');
}
Future<void> test_local_shadowClass() async {
await getTestCodeSuggestions('''
class A {
A();
A.named();
}
void f() {
int A = 0;
^
}
''');
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
// The class is suggested.
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'A');
// Class and all its constructors are shadowed by the local variable.
assertNoResult('A', elementKind: ElementKind.CLASS);
assertNoResult('A', elementKind: ElementKind.CONSTRUCTOR);
assertNoResult('A.named', elementKind: ElementKind.CONSTRUCTOR);
}
Future<void> test_locals() async {
await getTestCodeSuggestions(
'class A {var a; x() {var b;^}} class DateTime { }');
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'A',
elementKind: ElementKind.CLASS);
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'a');
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'b');
assertHasResult(CompletionSuggestionKind.INVOCATION, 'x');
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'DateTime',
elementKind: ElementKind.CLASS);
}
Future<void> test_offset_past_eof() async {
addTestFile('void f() { }');
var request =
CompletionGetSuggestionsParams(testFile.path, 300).toRequest('0');
var response = await handleRequest(request);
assertResponseFailure(
response,
requestId: '0',
errorCode: RequestErrorCode.INVALID_PARAMETER,
);
}
Future<void> test_overrides() async {
newFile('/project/bin/a.dart', 'class A {m() {}}');
await getTestCodeSuggestions('''
import 'a.dart';
class B extends A {m() {^}}
''');
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
assertHasResult(CompletionSuggestionKind.INVOCATION, 'm');
}
Future<void> test_partFile() async {
newFile('$testPackageLibPath/a.dart', '''
library libA;
import 'dart:html';
part 'test.dart';
class A { }
''');
await getTestCodeSuggestions('''
part of libA;
void f() {^}''');
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'Object',
elementKind: ElementKind.CLASS);
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'HtmlElement',
elementKind: ElementKind.CLASS);
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'A',
elementKind: ElementKind.CLASS);
assertNoResult('test');
}
Future<void> test_partFile2() async {
newFile('$testPackageLibPath/a.dart', '''
part of libA;
class A { }''');
await getTestCodeSuggestions('''
library libA;
part "a.dart";
import 'dart:html';
void f() {^}
''');
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'Object',
elementKind: ElementKind.CLASS);
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'HtmlElement',
elementKind: ElementKind.CLASS);
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'A',
elementKind: ElementKind.CLASS);
assertNoResult('test');
}
Future<void> test_sentToPlugins() async {
if (!AnalysisServer.supportsPlugins) return;
addTestFile('''
void f() {
}
''');
PluginInfo info = DiscoveredPluginInfo('a', 'b', 'c',
TestNotificationManager(), InstrumentationService.NULL_SERVICE);
var result = plugin.CompletionGetSuggestionsResult(
testFileContent.indexOf('^'), 0, <CompletionSuggestion>[
CompletionSuggestion(CompletionSuggestionKind.IDENTIFIER,
DART_RELEVANCE_DEFAULT, 'plugin completion', 3, 0, false, false)
]);
pluginManager.broadcastResults = <PluginInfo, Future<plugin.Response>>{
info: Future.value(result.toResponse('-', 1))
};
await getTestCodeSuggestions('''
void f() {
^
}
''');
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'plugin completion',
selectionOffset: 3);
}
Future<void> test_simple() async {
await getTestCodeSuggestions('''
void f() {
^
}
''');
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'Object',
elementKind: ElementKind.CLASS);
assertNoResult('HtmlElement');
assertNoResult('test');
}
Future<void> test_static() async {
await getTestCodeSuggestions(
'class A {static b() {} c() {}} void f() {A.^}');
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
assertHasResult(CompletionSuggestionKind.INVOCATION, 'b');
assertNoResult('c');
}
Future<void> test_topLevel() async {
await getTestCodeSuggestions('''
typedef foo();
var test = '';
void f() {tes^t}
''');
expect(replacementOffset, equals(completionOffset - 3));
expect(replacementLength, equals(4));
// Suggestions based upon imported elements are partially filtered
//assertHasResult(CompletionSuggestionKind.INVOCATION, 'Object');
assertHasResult(CompletionSuggestionKind.IDENTIFIER, 'test');
assertNoResult('HtmlElement');
}
}
class RequestWithFutureResponse {
final int offset;
final Request request;

View file

@ -11,17 +11,13 @@ import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:test/test.dart';
import 'analysis_server_base.dart';
import 'constants.dart';
class AbstractCompletionDomainTest extends PubPackageAnalysisServerTest {
late String completionId;
// TODO(brianwilkerson): Merge this class and `CompletionTestCase`.
late int completionOffset; // TODO(scheglov): remove it
int? replacementOffset;
late int replacementLength;
Map<String, Completer<void>> receivedSuggestionsCompleters = {};
List<CompletionSuggestion> suggestions = [];
bool suggestionsDone = false;
Map<String, List<CompletionSuggestion>> allSuggestions = {};
void assertHasResult(CompletionSuggestionKind kind, String completion,
{bool isDeprecated = false,
@ -79,6 +75,7 @@ class AbstractCompletionDomainTest extends PubPackageAnalysisServerTest {
Future<void> getCodeSuggestions({
required String path,
required String content,
int maxResults = 1 << 10,
}) async {
completionOffset = content.indexOf('^');
expect(completionOffset, isNot(equals(-1)), reason: 'missing ^');
@ -95,21 +92,26 @@ class AbstractCompletionDomainTest extends PubPackageAnalysisServerTest {
return await getSuggestions(
path: path,
completionOffset: completionOffset,
maxResults: maxResults,
);
}
Future<void> getSuggestions({
required String path,
required int completionOffset,
required int maxResults,
}) async {
var request =
CompletionGetSuggestionsParams(path, completionOffset).toRequest('0');
var request = CompletionGetSuggestions2Params(
path,
completionOffset,
maxResults,
).toRequest('0');
var response = await handleSuccessfulRequest(request);
var result = CompletionGetSuggestionsResult.fromResponse(response);
completionId = result.id;
assertValidId(completionId);
await _getResultsCompleter(completionId).future;
expect(suggestionsDone, isTrue);
var result = CompletionGetSuggestions2Result.fromResponse(response);
replacementOffset = result.replacementOffset;
replacementLength = result.replacementLength;
suggestions = result.suggestions;
}
Future<void> getTestCodeSuggestions(String content) {
@ -121,19 +123,7 @@ class AbstractCompletionDomainTest extends PubPackageAnalysisServerTest {
@override
Future<void> processNotification(Notification notification) async {
if (notification.event == COMPLETION_RESULTS) {
var params = CompletionResultsParams.fromNotification(notification);
var id = params.id;
assertValidId(id);
replacementOffset = params.replacementOffset;
replacementLength = params.replacementLength;
suggestionsDone = params.isLast;
expect(suggestionsDone, isNotNull);
suggestions = params.results;
expect(allSuggestions.containsKey(id), isFalse);
allSuggestions[id] = params.results;
_getResultsCompleter(id).complete(null);
} else if (notification.event == SERVER_NOTIFICATION_ERROR) {
if (notification.event == SERVER_NOTIFICATION_ERROR) {
fail('server error: ${notification.toJson()}');
}
}
@ -143,9 +133,4 @@ class AbstractCompletionDomainTest extends PubPackageAnalysisServerTest {
super.setUp();
await setRoots(included: [workspaceRootPath], excluded: []);
}
Completer<void> _getResultsCompleter(String id) {
return receivedSuggestionsCompleters.putIfAbsent(
id, () => Completer<void>());
}
}

View file

@ -11,12 +11,12 @@ import '../support/integration_tests.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(GetSuggestionsTest);
defineReflectiveTests(GetSuggestions2Test);
});
}
@reflectiveTest
class GetSuggestionsTest extends AbstractAnalysisServerIntegrationTest {
class GetSuggestions2Test extends AbstractAnalysisServerIntegrationTest {
bool initialized = false;
late String path;
late String content;
@ -49,14 +49,11 @@ void f() {
writeFile(path, content);
await standardAnalysisSetup();
await analysisFinished;
var result = await sendCompletionGetSuggestions(path, completionOffset);
var completionId = result.id;
var param = await onCompletionResults.firstWhere(
(CompletionResultsParams param) =>
param.id == completionId && param.isLast);
expect(param.replacementOffset, completionOffset);
expect(param.replacementLength, 0);
param.results.firstWhere(
var result =
await sendCompletionGetSuggestions2(path, completionOffset, 100);
expect(result.replacementOffset, completionOffset);
expect(result.replacementLength, 0);
result.suggestions.firstWhere(
(CompletionSuggestion suggestion) => suggestion.completion == 'length');
}
@ -72,14 +69,11 @@ void f() {
await standardAnalysisSetup();
await sendAnalysisUpdateContent({path: AddContentOverlay(content)});
await analysisFinished;
var result = await sendCompletionGetSuggestions(path, completionOffset);
var completionId = result.id;
var param = await onCompletionResults.firstWhere(
(CompletionResultsParams param) =>
param.id == completionId && param.isLast);
expect(param.replacementOffset, completionOffset);
expect(param.replacementLength, 0);
param.results.firstWhere(
var result =
await sendCompletionGetSuggestions2(path, completionOffset, 100);
expect(result.replacementOffset, completionOffset);
expect(result.replacementLength, 0);
result.suggestions.firstWhere(
(CompletionSuggestion suggestion) => suggestion.completion == 'length');
}
@ -96,12 +90,11 @@ void f() {
// writeFile(pathname, text);
// Don't wait for any results except the completion notifications
await sendAnalysisUpdateContent({path: AddContentOverlay(content)});
await sendCompletionGetSuggestions(path, completionOffset);
var param = await onCompletionResults
.firstWhere((CompletionResultsParams param) => param.isLast);
expect(param.replacementOffset, completionOffset);
expect(param.replacementLength, 0);
param.results.firstWhere(
var result =
await sendCompletionGetSuggestions2(path, completionOffset, 100);
expect(result.replacementOffset, completionOffset);
expect(result.replacementLength, 0);
result.suggestions.firstWhere(
(CompletionSuggestion suggestion) => suggestion.completion == 'length');
}
@ -114,7 +107,7 @@ void f() {
await analysisFinished;
// Missing file and no overlay
//sendAnalysisUpdateContent({path: new AddContentOverlay(content)});
var result = await sendCompletionGetSuggestions(path, 0);
expect(result, const TypeMatcher<CompletionGetSuggestionsResult>());
var result = await sendCompletionGetSuggestions2(path, 0, 100);
expect(result, const TypeMatcher<CompletionGetSuggestions2Result>());
}
}

View file

@ -1067,32 +1067,6 @@ abstract class IntegrationTest {
final _onAnalysisOverrides =
StreamController<AnalysisOverridesParams>(sync: true);
/// Request that completion suggestions for the given offset in the given
/// file be returned.
///
/// Parameters
///
/// file: FilePath
///
/// The file containing the point at which suggestions are to be made.
///
/// offset: int
///
/// The offset within the file at which suggestions are to be made.
///
/// Returns
///
/// id: CompletionId
///
/// The identifier used to associate results with this completion request.
Future<CompletionGetSuggestionsResult> sendCompletionGetSuggestions(
String file, int offset) async {
var params = CompletionGetSuggestionsParams(file, offset).toJson();
var result = await server.send('completion.getSuggestions', params);
var decoder = ResponseDecoder(null);
return CompletionGetSuggestionsResult.fromJson(decoder, 'result', result);
}
/// Request that completion suggestions for the given offset in the given
/// file be returned. The suggestions will be filtered using fuzzy matching
/// with the already existing prefix.
@ -1169,25 +1143,6 @@ abstract class IntegrationTest {
return CompletionGetSuggestions2Result.fromJson(decoder, 'result', result);
}
/// Subscribe for completion services. All previous subscriptions are
/// replaced by the given set of services.
///
/// It is an error if any of the elements in the list are not valid services.
/// If there is an error, then the current subscriptions will remain
/// unchanged.
///
/// Parameters
///
/// subscriptions: List<CompletionService>
///
/// A list of the services being subscribed to.
Future<void> sendCompletionSetSubscriptions(
List<CompletionService> subscriptions) async {
var params = CompletionSetSubscriptionsParams(subscriptions).toJson();
var result = await server.send('completion.setSubscriptions', params);
outOfTestExpect(result, isNull);
}
/// The client can make this request to express interest in certain libraries
/// to receive completion suggestions from based on the client path. If this
/// request is received before the client has used
@ -1214,55 +1169,6 @@ abstract class IntegrationTest {
outOfTestExpect(result, isNull);
}
/// Clients must make this request when the user has selected a completion
/// suggestion from an AvailableSuggestionSet. Analysis server will respond
/// with the text to insert as well as any SourceChange that needs to be
/// applied in case the completion requires an additional import to be added.
/// It is an error if the id is no longer valid, for instance if the library
/// has been removed after the completion suggestion is accepted.
///
/// Parameters
///
/// file: FilePath
///
/// The path of the file into which this completion is being inserted.
///
/// id: int
///
/// The identifier of the AvailableSuggestionSet containing the selected
/// label.
///
/// label: String
///
/// The label from the AvailableSuggestionSet with the `id` for which
/// insertion information is requested.
///
/// offset: int
///
/// The offset in the file where the completion will be inserted.
///
/// Returns
///
/// completion: String
///
/// The full text to insert, including any optional import prefix.
///
/// change: SourceChange (optional)
///
/// A change for the client to apply in case the library containing the
/// accepted completion suggestion needs to be imported. The field will be
/// omitted if there are no additional changes that need to be made.
Future<CompletionGetSuggestionDetailsResult>
sendCompletionGetSuggestionDetails(
String file, int id, String label, int offset) async {
var params =
CompletionGetSuggestionDetailsParams(file, id, label, offset).toJson();
var result = await server.send('completion.getSuggestionDetails', params);
var decoder = ResponseDecoder(null);
return CompletionGetSuggestionDetailsResult.fromJson(
decoder, 'result', result);
}
/// Clients must make this request when the user has selected a completion
/// suggestion with the isNotImported field set to true. The server will
/// respond with the text to insert, as well as any SourceChange that needs
@ -1318,108 +1224,6 @@ abstract class IntegrationTest {
decoder, 'result', result);
}
/// Reports the completion suggestions that should be presented to the user.
/// The set of suggestions included in the notification is always a complete
/// list that supersedes any previously reported suggestions.
///
/// Parameters
///
/// id: CompletionId
///
/// The id associated with the completion.
///
/// replacementOffset: int
///
/// The offset of the start of the text to be replaced. This will be
/// different than the offset used to request the completion suggestions if
/// there was a portion of an identifier before the original offset. In
/// particular, the replacementOffset will be the offset of the beginning
/// of said identifier.
///
/// replacementLength: int
///
/// The length of the text to be replaced if the remainder of the
/// identifier containing the cursor is to be replaced when the suggestion
/// is applied (that is, the number of characters in the existing
/// identifier).
///
/// results: List<CompletionSuggestion>
///
/// The completion suggestions being reported. The notification contains
/// all possible completions at the requested cursor position, even those
/// that do not match the characters the user has already typed. This
/// allows the client to respond to further keystrokes from the user
/// without having to make additional requests.
///
/// isLast: bool
///
/// True if this is that last set of results that will be returned for the
/// indicated completion.
///
/// libraryFile: FilePath (optional)
///
/// The library file that contains the file where completion was requested.
/// The client might use it for example together with the existingImports
/// notification to filter out available suggestions. If there were changes
/// to existing imports in the library, the corresponding existingImports
/// notification will be sent before the completion notification.
///
/// includedSuggestionSets: List<IncludedSuggestionSet> (optional)
///
/// References to AvailableSuggestionSet objects previously sent to the
/// client. The client can include applicable names from the referenced
/// library in code completion suggestions.
///
/// includedElementKinds: List<ElementKind> (optional)
///
/// The client is expected to check this list against the ElementKind sent
/// in IncludedSuggestionSet to decide whether or not these symbols should
/// be presented to the user.
///
/// includedSuggestionRelevanceTags: List<IncludedSuggestionRelevanceTag>
/// (optional)
///
/// The client is expected to check this list against the values of the
/// field relevanceTags of AvailableSuggestion to decide if the suggestion
/// should be given a different relevance than the IncludedSuggestionSet
/// that contains it. This might be used for example to give higher
/// relevance to suggestions of matching types.
///
/// If an AvailableSuggestion has relevance tags that match more than one
/// IncludedSuggestionRelevanceTag, the maximum relevance boost is used.
late final Stream<CompletionResultsParams> onCompletionResults =
_onCompletionResults.stream.asBroadcastStream();
/// Stream controller for [onCompletionResults].
final _onCompletionResults =
StreamController<CompletionResultsParams>(sync: true);
/// Reports the pre-computed, candidate completions from symbols defined in a
/// corresponding library. This notification may be sent multiple times. When
/// a notification is processed, clients should replace any previous
/// information about the libraries in the list of changedLibraries, discard
/// any information about the libraries in the list of removedLibraries, and
/// preserve any previously received information about any libraries that are
/// not included in either list.
///
/// Parameters
///
/// changedLibraries: List<AvailableSuggestionSet> (optional)
///
/// A list of pre-computed, potential completions coming from this set of
/// completion suggestions.
///
/// removedLibraries: List<int> (optional)
///
/// A list of library ids that no longer apply.
late final Stream<CompletionAvailableSuggestionsParams>
onCompletionAvailableSuggestions =
_onCompletionAvailableSuggestions.stream.asBroadcastStream();
/// Stream controller for [onCompletionAvailableSuggestions].
final _onCompletionAvailableSuggestions =
StreamController<CompletionAvailableSuggestionsParams>(sync: true);
/// Reports existing imports in a library. This notification may be sent
/// multiple times for a library. When a notification is processed, clients
/// should replace any previous information for the library.
@ -2829,15 +2633,6 @@ abstract class IntegrationTest {
outOfTestExpect(params, isAnalysisOverridesParams);
_onAnalysisOverrides
.add(AnalysisOverridesParams.fromJson(decoder, 'params', params));
case 'completion.results':
outOfTestExpect(params, isCompletionResultsParams);
_onCompletionResults
.add(CompletionResultsParams.fromJson(decoder, 'params', params));
case 'completion.availableSuggestions':
outOfTestExpect(params, isCompletionAvailableSuggestionsParams);
_onCompletionAvailableSuggestions.add(
CompletionAvailableSuggestionsParams.fromJson(
decoder, 'params', params));
case 'completion.existingImports':
outOfTestExpect(params, isCompletionExistingImportsParams);
_onCompletionExistingImports.add(

View file

@ -151,49 +151,6 @@ final Matcher isAnalysisStatus = LazyMatcher(() => MatchesJsonObject(
'AnalysisStatus', {'isAnalyzing': isBool},
optionalFields: {'analysisTarget': isString}));
/// AvailableSuggestion
///
/// {
/// "label": String
/// "declaringLibraryUri": String
/// "element": Element
/// "defaultArgumentListString": optional String
/// "defaultArgumentListTextRanges": optional List<int>
/// "parameterNames": optional List<String>
/// "parameterTypes": optional List<String>
/// "relevanceTags": optional List<AvailableSuggestionRelevanceTag>
/// "requiredParameterCount": optional int
/// }
final Matcher isAvailableSuggestion =
LazyMatcher(() => MatchesJsonObject('AvailableSuggestion', {
'label': isString,
'declaringLibraryUri': isString,
'element': isElement
}, optionalFields: {
'defaultArgumentListString': isString,
'defaultArgumentListTextRanges': isListOf(isInt),
'parameterNames': isListOf(isString),
'parameterTypes': isListOf(isString),
'relevanceTags': isListOf(isAvailableSuggestionRelevanceTag),
'requiredParameterCount': isInt
}));
/// AvailableSuggestionRelevanceTag
///
/// String
final Matcher isAvailableSuggestionRelevanceTag = isString;
/// AvailableSuggestionSet
///
/// {
/// "id": int
/// "uri": String
/// "items": List<AvailableSuggestion>
/// }
final Matcher isAvailableSuggestionSet = LazyMatcher(() => MatchesJsonObject(
'AvailableSuggestionSet',
{'id': isInt, 'uri': isString, 'items': isListOf(isAvailableSuggestion)}));
/// BulkFix
///
/// {
@ -242,11 +199,6 @@ final Matcher isClosingLabel = LazyMatcher(() => MatchesJsonObject(
final Matcher isCompletionCaseMatchingMode = MatchesEnum(
'CompletionCaseMatchingMode', ['FIRST_CHAR', 'ALL_CHARS', 'NONE']);
/// CompletionId
///
/// String
final Matcher isCompletionId = isString;
/// CompletionMode
///
/// enum {
@ -256,14 +208,6 @@ final Matcher isCompletionId = isString;
final Matcher isCompletionMode =
MatchesEnum('CompletionMode', ['BASIC', 'SMART']);
/// CompletionService
///
/// enum {
/// AVAILABLE_SUGGESTION_SETS
/// }
final Matcher isCompletionService =
MatchesEnum('CompletionService', ['AVAILABLE_SUGGESTION_SETS']);
/// CompletionSuggestion
///
/// {
@ -1040,27 +984,6 @@ final Matcher isImportedElements = LazyMatcher(() => MatchesJsonObject(
'ImportedElements',
{'path': isFilePath, 'prefix': isString, 'elements': isListOf(isString)}));
/// IncludedSuggestionRelevanceTag
///
/// {
/// "tag": AvailableSuggestionRelevanceTag
/// "relevanceBoost": int
/// }
final Matcher isIncludedSuggestionRelevanceTag = LazyMatcher(() =>
MatchesJsonObject('IncludedSuggestionRelevanceTag',
{'tag': isAvailableSuggestionRelevanceTag, 'relevanceBoost': isInt}));
/// IncludedSuggestionSet
///
/// {
/// "id": int
/// "relevance": int
/// "displayUri": optional String
/// }
final Matcher isIncludedSuggestionSet = LazyMatcher(() => MatchesJsonObject(
'IncludedSuggestionSet', {'id': isInt, 'relevance': isInt},
optionalFields: {'displayUri': isString}));
/// LibraryPathSet
///
/// {
@ -2076,19 +1999,6 @@ final Matcher isAnalyticsSendTimingParams = LazyMatcher(() => MatchesJsonObject(
/// analytics.sendTiming result
final Matcher isAnalyticsSendTimingResult = isNull;
/// completion.availableSuggestions params
///
/// {
/// "changedLibraries": optional List<AvailableSuggestionSet>
/// "removedLibraries": optional List<int>
/// }
final Matcher isCompletionAvailableSuggestionsParams = LazyMatcher(() =>
MatchesJsonObject('completion.availableSuggestions params', null,
optionalFields: {
'changedLibraries': isListOf(isAvailableSuggestionSet),
'removedLibraries': isListOf(isInt)
}));
/// completion.existingImports params
///
/// {
@ -2125,29 +2035,6 @@ final Matcher isCompletionGetSuggestionDetails2Result = LazyMatcher(() =>
MatchesJsonObject('completion.getSuggestionDetails2 result',
{'completion': isString, 'change': isSourceChange}));
/// completion.getSuggestionDetails params
///
/// {
/// "file": FilePath
/// "id": int
/// "label": String
/// "offset": int
/// }
final Matcher isCompletionGetSuggestionDetailsParams = LazyMatcher(() =>
MatchesJsonObject('completion.getSuggestionDetails params',
{'file': isFilePath, 'id': isInt, 'label': isString, 'offset': isInt}));
/// completion.getSuggestionDetails result
///
/// {
/// "completion": String
/// "change": optional SourceChange
/// }
final Matcher isCompletionGetSuggestionDetailsResult = LazyMatcher(() =>
MatchesJsonObject(
'completion.getSuggestionDetails result', {'completion': isString},
optionalFields: {'change': isSourceChange}));
/// completion.getSuggestions2 params
///
/// {
@ -2184,25 +2071,6 @@ final Matcher isCompletionGetSuggestions2Result =
'isIncomplete': isBool
}));
/// completion.getSuggestions params
///
/// {
/// "file": FilePath
/// "offset": int
/// }
final Matcher isCompletionGetSuggestionsParams = LazyMatcher(() =>
MatchesJsonObject('completion.getSuggestions params',
{'file': isFilePath, 'offset': isInt}));
/// completion.getSuggestions result
///
/// {
/// "id": CompletionId
/// }
final Matcher isCompletionGetSuggestionsResult = LazyMatcher(() =>
MatchesJsonObject(
'completion.getSuggestions result', {'id': isCompletionId}));
/// completion.registerLibraryPaths params
///
/// {
@ -2215,46 +2083,6 @@ final Matcher isCompletionRegisterLibraryPathsParams = LazyMatcher(() =>
/// completion.registerLibraryPaths result
final Matcher isCompletionRegisterLibraryPathsResult = isNull;
/// completion.results params
///
/// {
/// "id": CompletionId
/// "replacementOffset": int
/// "replacementLength": int
/// "results": List<CompletionSuggestion>
/// "isLast": bool
/// "libraryFile": optional FilePath
/// "includedSuggestionSets": optional List<IncludedSuggestionSet>
/// "includedElementKinds": optional List<ElementKind>
/// "includedSuggestionRelevanceTags": optional List<IncludedSuggestionRelevanceTag>
/// }
final Matcher isCompletionResultsParams =
LazyMatcher(() => MatchesJsonObject('completion.results params', {
'id': isCompletionId,
'replacementOffset': isInt,
'replacementLength': isInt,
'results': isListOf(isCompletionSuggestion),
'isLast': isBool
}, optionalFields: {
'libraryFile': isFilePath,
'includedSuggestionSets': isListOf(isIncludedSuggestionSet),
'includedElementKinds': isListOf(isElementKind),
'includedSuggestionRelevanceTags':
isListOf(isIncludedSuggestionRelevanceTag)
}));
/// completion.setSubscriptions params
///
/// {
/// "subscriptions": List<CompletionService>
/// }
final Matcher isCompletionSetSubscriptionsParams = LazyMatcher(() =>
MatchesJsonObject('completion.setSubscriptions params',
{'subscriptions': isListOf(isCompletionService)}));
/// completion.setSubscriptions result
final Matcher isCompletionSetSubscriptionsResult = isNull;
/// convertGetterToMethod feedback
final Matcher isConvertGetterToMethodFeedback = isNull;

View file

@ -18,17 +18,4 @@ class CompletionResponseForTesting {
required this.isIncomplete,
required this.suggestions,
});
factory CompletionResponseForTesting.legacy(
int requestOffset,
CompletionResultsParams parameters,
) {
return CompletionResponseForTesting(
requestOffset: requestOffset,
replacementOffset: parameters.replacementOffset,
replacementLength: parameters.replacementLength,
isIncomplete: false,
suggestions: parameters.results,
);
}
}

View file

@ -1,562 +0,0 @@
// Copyright (c) 2019, 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:analysis_server/src/protocol_server.dart';
import 'package:collection/collection.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'available_suggestions_base.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(AvailableSuggestionSetsTest);
});
}
@reflectiveTest
class AvailableSuggestionSetsTest extends AvailableSuggestionsBase {
Future<void> test_notifications_whenFileChanges() async {
var path = convertPath('/home/test/lib/a.dart');
var uriStr = 'package:test/a.dart';
// No file initially, so no set.
expect(uriToSetMap.keys, isNot(contains(uriStr)));
// Create the file, should get the set.
{
newFile(path, r'''
class A {}
''');
var set = await waitForSetWithUri(uriStr);
expect(set.items.map((d) => d.label), contains('A'));
}
// Update the file, should get the updated set.
{
newFile(path, r'''
class B {}
''');
removeSet(uriStr);
var set = await waitForSetWithUri(uriStr);
expect(set.items.map((d) => d.label), contains('B'));
}
// Delete the file, the set should be removed.
deleteFile(path);
await waitForSetWithUriRemoved(uriStr);
}
Future<void> test_suggestion_class() async {
var path = convertPath('/home/test/lib/a.dart');
var uriStr = 'package:test/a.dart';
newFile(path, r'''
class A {
A.a();
}
''');
var set = await waitForSetWithUri(uriStr);
assertJsonText(_getSuggestion(set, 'A'), '''
{
"label": "A",
"declaringLibraryUri": "package:test/a.dart",
"element": {
"kind": "CLASS",
"name": "A",
"location": {
"file": ${jsonOfPath(path)},
"offset": 6,
"length": 0,
"startLine": 1,
"startColumn": 7,
"endLine": 1,
"endColumn": 7
},
"flags": 0
},
"relevanceTags": [
"ElementKind.CLASS",
"package:test/a.dart::A",
"A"
]
}
''');
assertJsonText(_getSuggestion(set, 'A.a'), '''
{
"label": "A.a",
"declaringLibraryUri": "package:test/a.dart",
"element": {
"kind": "CONSTRUCTOR",
"name": "a",
"location": {
"file": ${jsonOfPath(path)},
"offset": 14,
"length": 0,
"startLine": 2,
"startColumn": 5,
"endLine": 2,
"endColumn": 5
},
"flags": 0,
"parameters": "()",
"returnType": "A"
},
"parameterNames": [],
"parameterTypes": [],
"relevanceTags": [
"ElementKind.CONSTRUCTOR",
"package:test/a.dart::A",
"a"
],
"requiredParameterCount": 0
}
''');
}
Future<void> test_suggestion_class_abstract() async {
var path = convertPath('/home/test/lib/a.dart');
var uriStr = 'package:test/a.dart';
newFile(path, r'''
abstract class A {
A.a();
factory A.b() => _B();
}
class _B extends A {
_B() : super.a();
}
''');
var set = await waitForSetWithUri(uriStr);
assertNoSuggestion(set, 'A.a');
assertNoSuggestion(set, '_B');
assertJsonText(_getSuggestion(set, 'A'), '''
{
"label": "A",
"declaringLibraryUri": "package:test/a.dart",
"element": {
"kind": "CLASS",
"name": "A",
"location": {
"file": ${jsonOfPath(path)},
"offset": 15,
"length": 0,
"startLine": 1,
"startColumn": 16,
"endLine": 1,
"endColumn": 16
},
"flags": 1
},
"relevanceTags": [
"ElementKind.CLASS",
"package:test/a.dart::A",
"A"
]
}
''');
assertJsonText(_getSuggestion(set, 'A.b'), '''
{
"label": "A.b",
"declaringLibraryUri": "package:test/a.dart",
"element": {
"kind": "CONSTRUCTOR",
"name": "b",
"location": {
"file": ${jsonOfPath(path)},
"offset": 40,
"length": 0,
"startLine": 3,
"startColumn": 13,
"endLine": 3,
"endColumn": 13
},
"flags": 0,
"parameters": "()",
"returnType": "A"
},
"parameterNames": [],
"parameterTypes": [],
"relevanceTags": [
"ElementKind.CONSTRUCTOR",
"package:test/a.dart::A",
"b"
],
"requiredParameterCount": 0
}
''');
}
Future<void> test_suggestion_class_part() async {
var a_path = convertPath('/home/test/lib/a.dart');
var b_path = convertPath('/home/test/lib/b.dart');
var a_uriStr = 'package:test/a.dart';
newFile(a_path, r'''
part 'b.dart';
class A {}
''');
newFile(b_path, r'''
part of 'a.dart';
class B {}
''');
var set = await waitForSetWithUri(a_uriStr);
assertJsonText(_getSuggestion(set, 'A', kind: ElementKind.CLASS), '''
{
"label": "A",
"declaringLibraryUri": "package:test/a.dart",
"element": {
"kind": "CLASS",
"name": "A",
"location": {
"file": ${jsonOfPath(a_path)},
"offset": 21,
"length": 0,
"startLine": 2,
"startColumn": 7,
"endLine": 2,
"endColumn": 7
},
"flags": 0
},
"relevanceTags": [
"ElementKind.CLASS",
"package:test/a.dart::A",
"A"
]
}
''');
// We should not get duplicate relevance tags.
assertJsonText(_getSuggestion(set, 'B', kind: ElementKind.CLASS), '''
{
"label": "B",
"declaringLibraryUri": "package:test/a.dart",
"element": {
"kind": "CLASS",
"name": "B",
"location": {
"file": ${jsonOfPath(b_path)},
"offset": 24,
"length": 0,
"startLine": 2,
"startColumn": 7,
"endLine": 2,
"endColumn": 7
},
"flags": 0
},
"relevanceTags": [
"ElementKind.CLASS",
"package:test/a.dart::B",
"B"
]
}
''');
}
Future<void> test_suggestion_enum() async {
var path = convertPath('/home/test/lib/a.dart');
var uriStr = 'package:test/a.dart';
newFile(path, r'''
enum MyEnum {
aaa,
bbb,
}
''');
var set = await waitForSetWithUri(uriStr);
assertJsonText(_getSuggestion(set, 'MyEnum'), '''
{
"label": "MyEnum",
"declaringLibraryUri": "package:test/a.dart",
"element": {
"kind": "ENUM",
"name": "MyEnum",
"location": {
"file": ${jsonOfPath(path)},
"offset": 5,
"length": 0,
"startLine": 1,
"startColumn": 6,
"endLine": 1,
"endColumn": 6
},
"flags": 0
},
"relevanceTags": [
"ElementKind.ENUM",
"package:test/a.dart::MyEnum",
"MyEnum"
]
}
''');
assertJsonText(_getSuggestion(set, 'MyEnum.aaa'), '''
{
"label": "MyEnum.aaa",
"declaringLibraryUri": "package:test/a.dart",
"element": {
"kind": "ENUM_CONSTANT",
"name": "aaa",
"location": {
"file": ${jsonOfPath(path)},
"offset": 16,
"length": 0,
"startLine": 2,
"startColumn": 3,
"endLine": 2,
"endColumn": 3
},
"flags": 0
},
"relevanceTags": [
"ElementKind.ENUM_CONSTANT",
"ElementKind.ENUM_CONSTANT+const",
"package:test/a.dart::MyEnum",
"aaa"
]
}
''');
assertJsonText(_getSuggestion(set, 'MyEnum.bbb'), '''
{
"label": "MyEnum.bbb",
"declaringLibraryUri": "package:test/a.dart",
"element": {
"kind": "ENUM_CONSTANT",
"name": "bbb",
"location": {
"file": ${jsonOfPath(path)},
"offset": 23,
"length": 0,
"startLine": 3,
"startColumn": 3,
"endLine": 3,
"endColumn": 3
},
"flags": 0
},
"relevanceTags": [
"ElementKind.ENUM_CONSTANT",
"ElementKind.ENUM_CONSTANT+const",
"package:test/a.dart::MyEnum",
"bbb"
]
}
''');
}
Future<void> test_suggestion_topLevelVariable() async {
var path = convertPath('/home/test/lib/a.dart');
var uriStr = 'package:test/a.dart';
newFile(path, r'''
var boolV = false;
var intV = 0;
var doubleV = 0.1;
var stringV = 'hi';
''');
var set = await waitForSetWithUri(uriStr);
assertJsonText(_getSuggestion(set, 'boolV'), '''
{
"label": "boolV",
"declaringLibraryUri": "package:test/a.dart",
"element": {
"kind": "TOP_LEVEL_VARIABLE",
"name": "boolV",
"location": {
"file": ${jsonOfPath(path)},
"offset": 4,
"length": 0,
"startLine": 1,
"startColumn": 5,
"endLine": 1,
"endColumn": 5
},
"flags": 0,
"returnType": ""
},
"relevanceTags": [
"ElementKind.TOP_LEVEL_VARIABLE",
"dart:core::bool",
"boolV"
]
}
''');
assertJsonText(_getSuggestion(set, 'intV'), '''
{
"label": "intV",
"declaringLibraryUri": "package:test/a.dart",
"element": {
"kind": "TOP_LEVEL_VARIABLE",
"name": "intV",
"location": {
"file": ${jsonOfPath(path)},
"offset": 23,
"length": 0,
"startLine": 2,
"startColumn": 5,
"endLine": 2,
"endColumn": 5
},
"flags": 0,
"returnType": ""
},
"relevanceTags": [
"ElementKind.TOP_LEVEL_VARIABLE",
"dart:core::int",
"intV"
]
}
''');
assertJsonText(_getSuggestion(set, 'doubleV'), '''
{
"label": "doubleV",
"declaringLibraryUri": "package:test/a.dart",
"element": {
"kind": "TOP_LEVEL_VARIABLE",
"name": "doubleV",
"location": {
"file": ${jsonOfPath(path)},
"offset": 37,
"length": 0,
"startLine": 3,
"startColumn": 5,
"endLine": 3,
"endColumn": 5
},
"flags": 0,
"returnType": ""
},
"relevanceTags": [
"ElementKind.TOP_LEVEL_VARIABLE",
"dart:core::double",
"doubleV"
]
}
''');
assertJsonText(_getSuggestion(set, 'stringV'), '''
{
"label": "stringV",
"declaringLibraryUri": "package:test/a.dart",
"element": {
"kind": "TOP_LEVEL_VARIABLE",
"name": "stringV",
"location": {
"file": ${jsonOfPath(path)},
"offset": 56,
"length": 0,
"startLine": 4,
"startColumn": 5,
"endLine": 4,
"endColumn": 5
},
"flags": 0,
"returnType": ""
},
"relevanceTags": [
"ElementKind.TOP_LEVEL_VARIABLE",
"dart:core::String",
"stringV"
]
}
''');
}
Future<void> test_suggestion_typedef() async {
var path = convertPath('/home/test/lib/a.dart');
var uriStr = 'package:test/a.dart';
newFile(path, r'''
typedef MyAlias = double;
''');
var set = await waitForSetWithUri(uriStr);
assertJsonText(_getSuggestion(set, 'MyAlias'), '''
{
"label": "MyAlias",
"declaringLibraryUri": "package:test/a.dart",
"element": {
"kind": "TYPE_ALIAS",
"name": "MyAlias",
"location": {
"file": ${jsonOfPath(path)},
"offset": 8,
"length": 0,
"startLine": 1,
"startColumn": 9,
"endLine": 1,
"endColumn": 9
},
"flags": 0
},
"relevanceTags": [
"ElementKind.TYPE_ALIAS",
"package:test/a.dart::MyAlias",
"MyAlias"
]
}
''');
}
Future<void> test_suggestion_typedef_function() async {
var path = convertPath('/home/test/lib/a.dart');
var uriStr = 'package:test/a.dart';
newFile(path, r'''
typedef MyAlias = void Function();
''');
var set = await waitForSetWithUri(uriStr);
assertJsonText(_getSuggestion(set, 'MyAlias'), '''
{
"label": "MyAlias",
"declaringLibraryUri": "package:test/a.dart",
"element": {
"kind": "TYPE_ALIAS",
"name": "MyAlias",
"location": {
"file": ${jsonOfPath(path)},
"offset": 8,
"length": 0,
"startLine": 1,
"startColumn": 9,
"endLine": 1,
"endColumn": 9
},
"flags": 0,
"parameters": "()",
"returnType": "void"
},
"parameterNames": [],
"parameterTypes": [],
"relevanceTags": [
"ElementKind.FUNCTION_TYPE_ALIAS",
"package:test/a.dart::MyAlias",
"MyAlias"
],
"requiredParameterCount": 0
}
''');
}
static void assertNoSuggestion(AvailableSuggestionSet set, String label,
{ElementKind? kind}) {
var suggestion = set.items.singleWhereOrNull(
(s) => s.label == label && (kind == null || s.element.kind == kind));
expect(suggestion, null);
}
static AvailableSuggestion _getSuggestion(
AvailableSuggestionSet set, String label,
{ElementKind? kind}) {
return set.items.singleWhere(
(s) => s.label == label && (kind == null || s.element.kind == kind));
}
}

View file

@ -1,118 +0,0 @@
// Copyright (c) 2019, 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 'dart:convert';
import 'package:analysis_server/protocol/protocol_constants.dart';
import 'package:analysis_server/src/protocol_server.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../analysis_server_base.dart';
import '../../../constants.dart';
@reflectiveTest
class AvailableSuggestionsBase extends PubPackageAnalysisServerTest {
final Map<int, AvailableSuggestionSet> idToSetMap = {};
final Map<String, AvailableSuggestionSet> uriToSetMap = {};
final Map<String, CompletionResultsParams> idToSuggestions = {};
final Map<File, ExistingImports> fileToExistingImports = {};
void assertJsonText(Object object, String expected) {
expected = expected.trimRight();
var actual = JsonEncoder.withIndent(' ').convert(object);
if (actual != expected) {
print('-----');
print(actual);
print('-----');
}
expect(actual, expected);
}
String jsonOfPath(String path) {
path = convertPath(path);
return json.encode(path);
}
@override
void processNotification(Notification notification) {
super.processNotification(notification);
if (notification.event == COMPLETION_NOTIFICATION_AVAILABLE_SUGGESTIONS) {
var params = CompletionAvailableSuggestionsParams.fromNotification(
notification,
);
for (var set in params.changedLibraries!) {
idToSetMap[set.id] = set;
uriToSetMap[set.uri] = set;
}
for (var id in params.removedLibraries!) {
var set = idToSetMap.remove(id);
uriToSetMap.remove(set?.uri);
}
} else if (notification.event == COMPLETION_RESULTS) {
var params = CompletionResultsParams.fromNotification(notification);
idToSuggestions[params.id] = params;
} else if (notification.event == COMPLETION_NOTIFICATION_EXISTING_IMPORTS) {
var params = CompletionExistingImportsParams.fromNotification(
notification,
);
fileToExistingImports[getFile(params.file)] = params.imports;
} else if (notification.event == SERVER_NOTIFICATION_ERROR) {
fail('${notification.toJson()}');
}
}
/// Remove the set with the given [uri].
/// The set must be already received.
void removeSet(String uri) {
var set = uriToSetMap.remove(uri)!;
idToSetMap.remove(set.id);
}
@override
Future<void> setUp() async {
super.setUp();
newPubspecYamlFile(testPackageRootPath, '');
writeTestPackageConfig();
await setRoots(included: [workspaceRootPath], excluded: []);
await handleSuccessfulRequest(
CompletionSetSubscriptionsParams([
CompletionService.AVAILABLE_SUGGESTION_SETS,
]).toRequest('0'),
);
}
Future<CompletionResultsParams> waitForGetSuggestions(String id) async {
while (true) {
var result = idToSuggestions[id];
if (result != null) {
return result;
}
await Future.delayed(const Duration(milliseconds: 1));
}
}
Future<AvailableSuggestionSet> waitForSetWithUri(String uri) async {
while (true) {
var result = uriToSetMap[uri];
if (result != null) {
return result;
}
await Future.delayed(const Duration(milliseconds: 1));
}
}
Future<void> waitForSetWithUriRemoved(String uri) async {
while (true) {
var result = uriToSetMap[uri];
if (result == null) {
return;
}
await Future.delayed(const Duration(milliseconds: 1));
}
}
}

View file

@ -1,283 +0,0 @@
// Copyright (c) 2019, 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:analysis_server/src/protocol_server.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'available_suggestions_base.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(GetSuggestionDetailsTest);
});
}
@reflectiveTest
class GetSuggestionDetailsTest extends AvailableSuggestionsBase {
Future<void> test_enum() async {
newFile('/home/test/lib/a.dart', r'''
enum MyEnum {
aaa, bbb
}
''');
addTestFile(r'''
void f() {} // ref
''');
var set = await waitForSetWithUri('package:test/a.dart');
var result = await _getSuggestionDetails(
_buildRequest(
id: set.id,
label: 'MyEnum.aaa',
offset: testFileContent.indexOf('} // ref'),
),
);
expect(result.completion, 'MyEnum.aaa');
_assertTestFileChange(result.change!, r'''
import 'package:test/a.dart';
void f() {} // ref
''');
}
Future<void> test_existingImport() async {
addTestFile(r'''
import 'dart:math';
void f() {} // ref
''');
var mathSet = await waitForSetWithUri('dart:math');
var result = await _getSuggestionDetails(
_buildRequest(
id: mathSet.id,
label: 'sin',
offset: testFileContent.indexOf('} // ref'),
),
);
expect(result.completion, 'sin');
_assertEmptyChange(result.change!);
}
Future<void> test_existingImport_prefixed() async {
addTestFile(r'''
import 'dart:math' as math;
void f() {} // ref
''');
var mathSet = await waitForSetWithUri('dart:math');
var result = await _getSuggestionDetails(
_buildRequest(
id: mathSet.id,
label: 'sin',
offset: testFileContent.indexOf('} // ref'),
),
);
expect(result.completion, 'math.sin');
_assertEmptyChange(result.change!);
}
Future<void> test_invalid_library() async {
addTestFile('');
var response = await handleRequest(
_buildRequest(id: -1, label: 'foo', offset: 0),
);
assertResponseFailure(
response,
requestId: '0',
errorCode: RequestErrorCode.INVALID_PARAMETER,
);
}
Future<void> test_newImport() async {
addTestFile(r'''
void f() {} // ref
''');
var mathSet = await waitForSetWithUri('dart:math');
var result = await _getSuggestionDetails(
_buildRequest(
id: mathSet.id,
label: 'sin',
offset: testFileContent.indexOf('} // ref'),
),
);
expect(result.completion, 'sin');
_assertTestFileChange(result.change!, r'''
import 'dart:math';
void f() {} // ref
''');
}
Future<void> test_newImport_afterLibraryBeforeFirstImportsAnnotation() async {
// Annotations are only treated as being for the file if they are on the first
// directive of the file.
addTestFile(r'''
library foo;
@myAnnotation
import 'package:zzz';
void f() {} // ref
''');
var mathSet = await waitForSetWithUri('dart:math');
var result = await _getSuggestionDetails(
_buildRequest(
id: mathSet.id,
label: 'sin',
offset: testFileContent.indexOf('} // ref'),
),
);
expect(result.completion, 'sin');
_assertTestFileChange(result.change!, r'''
library foo;
import 'dart:math';
@myAnnotation
import 'package:zzz';
void f() {} // ref
''');
}
Future<void> test_newImport_betweenAnnotationAndFirstImport() async {
// Annotations attached to the first import in a file are considered
// to be for the file, so if an import is inserted in the top position, it
// should go after the annotation.
addTestFile(r'''
@myAnnotation
import 'package:zzz';
void f() {} // ref
''');
var mathSet = await waitForSetWithUri('dart:math');
var result = await _getSuggestionDetails(
_buildRequest(
id: mathSet.id,
label: 'sin',
offset: testFileContent.indexOf('} // ref'),
),
);
expect(result.completion, 'sin');
_assertTestFileChange(result.change!, r'''
@myAnnotation
import 'dart:math';
import 'package:zzz';
void f() {} // ref
''');
}
Future<void> test_newImport_notBetweenAnnotationAndNonFirstImport() async {
// Annotations on non-first directives should not be kept above the newly
// imported imports (opposite of test_newImport_betweenAnnotationAndFirstImport).
addTestFile(r'''
import 'dart:async';
@myAnnotation
import 'package:zzz';
void f() {} // ref
''');
var mathSet = await waitForSetWithUri('dart:math');
var result = await _getSuggestionDetails(
_buildRequest(
id: mathSet.id,
label: 'sin',
offset: testFileContent.indexOf('} // ref'),
),
);
expect(result.completion, 'sin');
_assertTestFileChange(result.change!, r'''
import 'dart:async';
import 'dart:math';
@myAnnotation
import 'package:zzz';
void f() {} // ref
''');
}
Future<void> test_newImport_part() async {
var partCode = r'''
part of 'test.dart';
void f() {} // ref
''';
var partFile = newFile('/home/test/lib/a.dart', partCode);
addTestFile(r'''
part 'a.dart';
''');
var mathSet = await waitForSetWithUri('dart:math');
var result = await _getSuggestionDetails(
_buildRequest(
file: partFile,
id: mathSet.id,
label: 'sin',
offset: partCode.indexOf('} // ref'),
),
);
expect(result.completion, 'sin');
_assertTestFileChange(result.change!, r'''
import 'dart:math';
part 'a.dart';
''');
}
void _assertEmptyChange(SourceChange change) {
expect(change.edits, isEmpty);
}
void _assertTestFileChange(SourceChange change, String expected) {
var fileEdits = change.edits;
expect(fileEdits, hasLength(1));
var fileEdit = fileEdits[0];
expect(fileEdit.file, testFile.path);
var edits = fileEdit.edits;
expect(SourceEdit.applySequence(testFileContent, edits), expected);
}
Request _buildRequest({
File? file,
required int id,
required String label,
required int offset,
}) {
return CompletionGetSuggestionDetailsParams(
(file ?? testFile).path,
id,
label,
offset,
).toRequest('0');
}
Future<CompletionGetSuggestionDetailsResult> _getSuggestionDetails(
Request request) async {
var response = await handleSuccessfulRequest(request);
return CompletionGetSuggestionDetailsResult.fromResponse(response);
}
}

View file

@ -1,628 +0,0 @@
// Copyright (c) 2019, 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:analysis_server/src/protocol_server.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'available_suggestions_base.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(ExistingImportsNotification);
defineReflectiveTests(GetSuggestionAvailableTest);
});
}
@reflectiveTest
class ExistingImportsNotification extends GetSuggestionsBase {
Future<void> test_dart() async {
addTestFile(r'''
import 'dart:math';
''');
await _getSuggestions(testFile, 0);
_assertHasImport('dart:math', 'dart:math', 'Random');
}
Future<void> test_invalidUri() async {
addTestFile(r'''
import 'ht:';
''');
await _getSuggestions(testFile, 0);
// We should not get 'server.error' notification.
}
void _assertHasImport(String exportingUri, String declaringUri, String name) {
var existingImports = fileToExistingImports[testFile]!;
var existingImport = existingImports.imports.singleWhere((import) =>
existingImports.elements.strings[import.uri] == exportingUri);
var elements = existingImport.elements.map((index) {
var uriIndex = existingImports.elements.uris[index];
var nameIndex = existingImports.elements.names[index];
var uri = existingImports.elements.strings[uriIndex];
var name = existingImports.elements.strings[nameIndex];
return '$uri::$name';
}).toList();
expect(elements, contains('$declaringUri::$name'));
}
}
@reflectiveTest
class GetSuggestionAvailableTest extends GetSuggestionsBase {
Future<void> test_dart() async {
addTestFile('');
var mathSet = await waitForSetWithUri('dart:math');
var asyncSet = await waitForSetWithUri('dart:async');
var results = await _getSuggestions(testFile, 0);
expect(results.includedElementKinds, isNotEmpty);
var includedIdSet = results.includedSuggestionSets!.map((set) => set.id);
expect(includedIdSet, contains(mathSet.id));
expect(includedIdSet, contains(asyncSet.id));
}
Future<void> test_dart_afterRecovery() async {
addTestFile('');
// Wait for a known set to be available.
await waitForSetWithUri('dart:math');
// Ensure the set is returned in the results.
var results = await _getSuggestions(testFile, 0);
expect(results.includedSuggestionSets, isNotEmpty);
// Force the server to rebuild all contexts, as happens when the file watcher
// fails on Windows.
// https://github.com/dart-lang/sdk/issues/44650
await server.contextManager.refresh();
// Give it time to process the newly scheduled files.
await pumpEventQueue(times: 5000);
// Ensure the set is still returned after the rebuild.
results = await _getSuggestions(testFile, 0);
expect(results.includedSuggestionSets, isNotEmpty);
}
Future<void> test_dart_instanceCreationExpression() async {
addTestFile(r'''
void f() {
new ; // ref
}
''');
var mathSet = await waitForSetWithUri('dart:math');
var asyncSet = await waitForSetWithUri('dart:async');
var results = await _getSuggestions(testFile, findOffset('; // ref'));
expect(
results.includedElementKinds,
unorderedEquals([ElementKind.CONSTRUCTOR]),
);
var includedIdSet = results.includedSuggestionSets!.map((set) => set.id);
expect(includedIdSet, contains(mathSet.id));
expect(includedIdSet, contains(asyncSet.id));
}
Future<void> test_defaultArgumentListString() async {
newFile('$testPackageLibPath/a.dart', r'''
void fff(int aaa, int bbb) {}
void ggg({int aaa, @required int bbb, @required int ccc}) {}
''');
var aSet = await waitForSetWithUri('package:test/a.dart');
var fff = aSet.items.singleWhere((e) => e.label == 'fff');
expect(fff.defaultArgumentListString, 'aaa, bbb');
expect(fff.defaultArgumentListTextRanges, [0, 3, 5, 3]);
var ggg = aSet.items.singleWhere((e) => e.label == 'ggg');
expect(ggg.defaultArgumentListString, 'bbb: bbb, ccc: ccc');
expect(ggg.defaultArgumentListTextRanges, [5, 3, 15, 3]);
}
Future<void> test_displayUri_file() async {
var aPath = '$testPackageRootPath/test/a.dart';
newFile(aPath, 'class A {}');
var aSet = await waitForSetWithUri(toUriStr(aPath));
var file = newFile('$testPackageRootPath/test/sub/test.dart', '');
var results = await _getSuggestions(file, 0);
expect(
results.includedSuggestionSets!.singleWhere((set) {
return set.id == aSet.id;
}).displayUri,
'../a.dart',
);
}
Future<void> test_displayUri_package() async {
newFile('$testPackageLibPath/a.dart', 'class A {}');
var aSet = await waitForSetWithUri('package:test/a.dart');
var file = newFile('$testPackageLibPath/test.dart', '');
var results = await _getSuggestions(file, 0);
expect(
results.includedSuggestionSets!.singleWhere((set) {
return set.id == aSet.id;
}).displayUri,
isNull,
);
}
Future<void> test_includedElementKinds_type() async {
addTestFile(r'''
class X extends {} // ref
''');
var results = await _getSuggestions(
testFile,
testFileContent.indexOf('{} // ref'),
);
expect(
results.includedElementKinds,
unorderedEquals([
ElementKind.CLASS,
ElementKind.CLASS_TYPE_ALIAS,
ElementKind.ENUM,
ElementKind.FUNCTION_TYPE_ALIAS,
ElementKind.MIXIN,
ElementKind.TYPE_ALIAS,
]),
);
}
Future<void> test_includedElementKinds_value() async {
addTestFile(r'''
void f() {
print(); // ref
}
''');
var results = await _getSuggestions(
testFile,
testFileContent.indexOf('); // ref'),
);
expect(
results.includedElementKinds,
unorderedEquals([
ElementKind.CLASS,
ElementKind.CLASS_TYPE_ALIAS,
ElementKind.CONSTRUCTOR,
ElementKind.ENUM,
ElementKind.ENUM_CONSTANT,
ElementKind.EXTENSION,
ElementKind.FUNCTION,
ElementKind.FUNCTION_TYPE_ALIAS,
ElementKind.GETTER,
ElementKind.MIXIN,
ElementKind.SETTER,
ElementKind.TOP_LEVEL_VARIABLE,
ElementKind.TYPE_ALIAS,
]),
);
}
Future<void> test_inHtml() async {
newFile('$testPackageLibPath/a.dart', 'class A {}');
var file = newFile('$testPackageRoot/doc/a.html', '<html></html>');
await handleSuccessfulRequest(
CompletionGetSuggestionsParams(file.path, 0).toRequest('0'),
);
}
Future<void> test_relevanceTags_constructorBeforeClass() async {
addTestFile(r'''
void foo(List<int> a) {}
void f() {
foo(); // ref
}
''');
var results = await _getSuggestions(
testFile,
testFileContent.indexOf('); // ref'),
);
var includedTags = results.includedSuggestionRelevanceTags!;
int findBoost(String tag) {
for (var includedTag in includedTags) {
if (includedTag.tag == tag) {
return includedTag.relevanceBoost;
}
}
fail('Missing relevance boost for tag $tag');
}
var classBoost = findBoost('ElementKind.CLASS');
var constructorBoost = findBoost('ElementKind.CONSTRUCTOR');
expect(constructorBoost, greaterThan(classBoost));
}
Future<void> test_relevanceTags_enum() async {
newFile('/home/test/lib/a.dart', r'''
enum MyEnum {
aaa, bbb
}
''');
addTestFile(r'''
import 'a.dart';
void f(MyEnum e) {
e = // ref;
}
''');
var results = await _getSuggestions(
testFile,
testFileContent.indexOf(' // ref'),
);
assertJsonText(results.includedSuggestionRelevanceTags!, r'''
[
{
"tag": "ElementKind.PREFIX",
"relevanceBoost": 0
},
{
"tag": "ElementKind.TOP_LEVEL_VARIABLE",
"relevanceBoost": 1
},
{
"tag": "ElementKind.FUNCTION",
"relevanceBoost": 2
},
{
"tag": "ElementKind.METHOD",
"relevanceBoost": 4
},
{
"tag": "ElementKind.ENUM",
"relevanceBoost": 9
},
{
"tag": "ElementKind.CLASS",
"relevanceBoost": 28
},
{
"tag": "ElementKind.LOCAL_VARIABLE",
"relevanceBoost": 40
},
{
"tag": "ElementKind.CONSTRUCTOR",
"relevanceBoost": 53
},
{
"tag": "ElementKind.FIELD",
"relevanceBoost": 68
},
{
"tag": "ElementKind.PARAMETER",
"relevanceBoost": 100
},
{
"tag": "package:test/a.dart::MyEnum",
"relevanceBoost": 250
}
]
''');
}
Future<void> test_relevanceTags_location_argumentList_named() async {
addTestFile(r'''
void foo({int a, String b}) {}
void f() {
foo(b: ); // ref
}
''');
var results = await _getSuggestions(
testFile,
testFileContent.indexOf('); // ref'),
);
assertJsonText(results.includedSuggestionRelevanceTags!, r'''
[
{
"tag": "ElementKind.PREFIX",
"relevanceBoost": 0
},
{
"tag": "ElementKind.FUNCTION",
"relevanceBoost": 1
},
{
"tag": "ElementKind.METHOD",
"relevanceBoost": 1
},
{
"tag": "ElementKind.TOP_LEVEL_VARIABLE",
"relevanceBoost": 3
},
{
"tag": "ElementKind.ENUM",
"relevanceBoost": 5
},
{
"tag": "ElementKind.CLASS",
"relevanceBoost": 20
},
{
"tag": "ElementKind.LOCAL_VARIABLE",
"relevanceBoost": 30
},
{
"tag": "ElementKind.FIELD",
"relevanceBoost": 41
},
{
"tag": "ElementKind.PARAMETER",
"relevanceBoost": 56
},
{
"tag": "ElementKind.CONSTRUCTOR",
"relevanceBoost": 100
},
{
"tag": "dart:core::String",
"relevanceBoost": 10
}
]
''');
}
Future<void> test_relevanceTags_location_argumentList_positional() async {
addTestFile(r'''
void foo(double a) {}
void f() {
foo(); // ref
}
''');
var results = await _getSuggestions(
testFile,
testFileContent.indexOf('); // ref'),
);
assertJsonText(results.includedSuggestionRelevanceTags!, r'''
[
{
"tag": "ElementKind.MIXIN",
"relevanceBoost": 0
},
{
"tag": "ElementKind.TYPE_PARAMETER",
"relevanceBoost": 0
},
{
"tag": "ElementKind.PREFIX",
"relevanceBoost": 0
},
{
"tag": "ElementKind.ENUM",
"relevanceBoost": 3
},
{
"tag": "ElementKind.METHOD",
"relevanceBoost": 4
},
{
"tag": "ElementKind.FUNCTION",
"relevanceBoost": 9
},
{
"tag": "ElementKind.CLASS",
"relevanceBoost": 13
},
{
"tag": "ElementKind.TOP_LEVEL_VARIABLE",
"relevanceBoost": 18
},
{
"tag": "ElementKind.CONSTRUCTOR",
"relevanceBoost": 27
},
{
"tag": "ElementKind.FIELD",
"relevanceBoost": 42
},
{
"tag": "ElementKind.LOCAL_VARIABLE",
"relevanceBoost": 60
},
{
"tag": "ElementKind.PARAMETER",
"relevanceBoost": 100
},
{
"tag": "dart:core::double",
"relevanceBoost": 10
}
]
''');
}
Future<void> test_relevanceTags_location_assignment() async {
addTestFile(r'''
void f() {
int v;
v = // ref;
}
''');
var results = await _getSuggestions(
testFile,
testFileContent.indexOf(' // ref'),
);
assertJsonText(results.includedSuggestionRelevanceTags!, r'''
[
{
"tag": "ElementKind.PREFIX",
"relevanceBoost": 0
},
{
"tag": "ElementKind.TOP_LEVEL_VARIABLE",
"relevanceBoost": 1
},
{
"tag": "ElementKind.FUNCTION",
"relevanceBoost": 2
},
{
"tag": "ElementKind.METHOD",
"relevanceBoost": 4
},
{
"tag": "ElementKind.ENUM",
"relevanceBoost": 9
},
{
"tag": "ElementKind.CLASS",
"relevanceBoost": 28
},
{
"tag": "ElementKind.LOCAL_VARIABLE",
"relevanceBoost": 40
},
{
"tag": "ElementKind.CONSTRUCTOR",
"relevanceBoost": 53
},
{
"tag": "ElementKind.FIELD",
"relevanceBoost": 68
},
{
"tag": "ElementKind.PARAMETER",
"relevanceBoost": 100
},
{
"tag": "dart:core::int",
"relevanceBoost": 10
}
]
''');
}
Future<void> test_relevanceTags_location_initializer() async {
addTestFile(r'''
int v = // ref;
''');
var results = await _getSuggestions(
testFile,
testFileContent.indexOf(' // ref'),
);
assertJsonText(results.includedSuggestionRelevanceTags!, r'''
[
{
"tag": "ElementKind.MIXIN",
"relevanceBoost": 0
},
{
"tag": "ElementKind.TYPE_PARAMETER",
"relevanceBoost": 0
},
{
"tag": "ElementKind.PREFIX",
"relevanceBoost": 1
},
{
"tag": "ElementKind.ENUM",
"relevanceBoost": 1
},
{
"tag": "ElementKind.METHOD",
"relevanceBoost": 4
},
{
"tag": "ElementKind.TOP_LEVEL_VARIABLE",
"relevanceBoost": 6
},
{
"tag": "ElementKind.FUNCTION",
"relevanceBoost": 16
},
{
"tag": "ElementKind.PARAMETER",
"relevanceBoost": 26
},
{
"tag": "ElementKind.FIELD",
"relevanceBoost": 35
},
{
"tag": "ElementKind.CLASS",
"relevanceBoost": 56
},
{
"tag": "ElementKind.LOCAL_VARIABLE",
"relevanceBoost": 68
},
{
"tag": "ElementKind.CONSTRUCTOR",
"relevanceBoost": 100
},
{
"tag": "dart:core::int",
"relevanceBoost": 10
}
]
''');
}
Future<void> test_relevanceTags_location_listLiteral() async {
addTestFile(r'''
void f() {
var v = [0, ]; // ref
}
''');
var results = await _getSuggestions(
testFile,
testFileContent.indexOf(']; // ref'),
);
assertJsonText(results.includedSuggestionRelevanceTags!, r'''
[
{
"tag": "dart:core::int",
"relevanceBoost": 10
}
]
''');
}
}
abstract class GetSuggestionsBase extends AvailableSuggestionsBase {
Future<CompletionResultsParams> _getSuggestions(
File file,
int offset,
) async {
var response = CompletionGetSuggestionsResult.fromResponse(
await handleSuccessfulRequest(
CompletionGetSuggestionsParams(file.path, offset).toRequest('0'),
),
);
return await waitForGetSuggestions(response.id);
}
}

View file

@ -1,17 +0,0 @@
// Copyright (c) 2019, 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 'available_suggestion_sets_test.dart' as available_suggestion_sets;
import 'get_suggestion_details_test.dart' as get_suggestion_details;
import 'get_suggestions_available_test.dart' as get_suggestions_available;
void main() {
defineReflectiveSuite(() {
available_suggestion_sets.main();
get_suggestion_details.main();
get_suggestions_available.main();
});
}

View file

@ -4,13 +4,11 @@
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'completion/test_all.dart' as completion;
import 'execution/test_all.dart' as execution;
import 'flutter/test_all.dart' as flutter;
void main() {
defineReflectiveSuite(() {
completion.main();
execution.main();
flutter.main();
});

View file

@ -35,24 +35,6 @@ class Analysis_UpdateContent extends ServerOperation {
}
}
/// An operation that will send a 'completion.getSuggestions' request.
class Completion_GetSuggestions extends ServerOperation {
/// The path of the file in which completions are being requested.
final String filePath;
/// The offset at which completions are being requested.
final int offset;
/// Initialize an operation to send a 'completion.getSuggestions' request with
/// the given [filePath] and [offset] as parameters.
Completion_GetSuggestions(this.filePath, this.offset);
@override
void perform(Server server) {
server.sendCompletionGetSuggestions(filePath, offset);
}
}
/// An operation to be performed during the simulation.
abstract class ServerOperation {
/// Perform this operation by communicating with the given [server].

View file

@ -390,11 +390,6 @@ class Server {
_send('analysis.updateOptions', params);
}
void sendCompletionGetSuggestions(String file, int offset) {
var params = CompletionGetSuggestionsParams(file, offset).toJson();
_send('completion.getSuggestions', params);
}
RequestData sendDiagnosticGetDiagnostics() {
return _send('diagnostic.getDiagnostics', null);
}

View file

@ -37,10 +37,6 @@ class SimpleTest extends TimingTest {
/// The offset of the cursor when requesting code completion.
late int cursorOffset;
/// A completer that will be completed when code completion results have been
/// received from the server.
late Completer<void> completionReceived;
/// Initialize a newly created test.
SimpleTest();
@ -69,19 +65,11 @@ f(C c) {
sendAnalysisUpdateContent({
mainFilePath: ChangeContentOverlay([SourceEdit(cursorOffset, 0, '.')])
});
sendCompletionGetSuggestions(mainFilePath, cursorOffset + 1);
return completionReceived.future;
return sendCompletionGetSuggestions2(mainFilePath, cursorOffset + 1, 1000);
}
@override
Future<void> setUp() {
completionReceived = Completer();
onCompletionResults.listen((_) {
// We only care about the time to the first response.
if (!completionReceived.isCompleted) {
completionReceived.complete();
}
});
sendAnalysisSetAnalysisRoots([dirname(mainFilePath)], []);
sendAnalysisUpdateContent(
{mainFilePath: AddContentOverlay(originalContent)});

View file

@ -384,23 +384,6 @@ public interface AnalysisServer {
*/
public void analytics_sendTiming(String event, int millis);
/**
* {@code completion.getSuggestionDetails}
*
* Clients must make this request when the user has selected a completion suggestion from an
* AvailableSuggestionSet. Analysis server will respond with the text to insert as well as any
* SourceChange that needs to be applied in case the completion requires an additional import to be
* added. It is an error if the id is no longer valid, for instance if the library has been removed
* after the completion suggestion is accepted.
*
* @param file The path of the file into which this completion is being inserted.
* @param id The identifier of the AvailableSuggestionSet containing the selected label.
* @param label The label from the AvailableSuggestionSet with the `id` for which insertion
* information is requested.
* @param offset The offset in the file where the completion will be inserted.
*/
public void completion_getSuggestionDetails(String file, int id, String label, int offset, GetSuggestionDetailsConsumer consumer);
/**
* {@code completion.getSuggestionDetails2}
*
@ -420,16 +403,6 @@ public interface AnalysisServer {
*/
public void completion_getSuggestionDetails2(String file, int offset, String completion, String libraryUri, GetSuggestionDetails2Consumer consumer);
/**
* {@code completion.getSuggestions}
*
* Request that completion suggestions for the given offset in the given file be returned.
*
* @param file The file containing the point at which suggestions are to be made.
* @param offset The offset within the file at which suggestions are to be made.
*/
public void completion_getSuggestions(String file, int offset, GetSuggestionsConsumer consumer);
/**
* {@code completion.getSuggestions2}
*
@ -471,19 +444,6 @@ public interface AnalysisServer {
*/
public void completion_registerLibraryPaths(List<LibraryPathSet> paths);
/**
* {@code completion.setSubscriptions}
*
* Subscribe for completion services. All previous subscriptions are replaced by the given set of
* services.
*
* It is an error if any of the elements in the list are not valid services. If there is an error,
* then the current subscriptions will remain unchanged.
*
* @param subscriptions A list of the services being subscribed to.
*/
public void completion_setSubscriptions(List<String> subscriptions);
/**
* {@code diagnostic.getDiagnostics}
*

View file

@ -1,300 +0,0 @@
/*
* Copyright (c) 2019, 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.
*
* This file has been automatically generated. Please do not edit it manually.
* To regenerate the file, use the script "pkg/analysis_server/tool/spec/generate_files".
*/
package org.dartlang.analysis.server.protocol;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.google.dart.server.utilities.general.JsonUtilities;
import com.google.dart.server.utilities.general.ObjectUtilities;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.commons.lang3.StringUtils;
/**
* A partial completion suggestion that can be used in combination with info from
* completion.results to build completion suggestions for not yet imported library tokens.
*
* @coverage dart.server.generated.types
*/
@SuppressWarnings("unused")
public class AvailableSuggestion {
public static final AvailableSuggestion[] EMPTY_ARRAY = new AvailableSuggestion[0];
public static final List<AvailableSuggestion> EMPTY_LIST = Lists.newArrayList();
/**
* The identifier to present to the user for code completion.
*/
private final String label;
/**
* The URI of the library that declares the element being suggested, not the URI of the library
* associated with the enclosing AvailableSuggestionSet.
*/
private final String declaringLibraryUri;
/**
* Information about the element reference being suggested.
*/
private final Element element;
/**
* A default String for use in generating argument list source contents on the client side.
*/
private final String defaultArgumentListString;
/**
* Pairs of offsets and lengths describing 'defaultArgumentListString' text ranges suitable for use
* by clients to set up linked edits of default argument source contents. For example, given an
* argument list string 'x, y', the corresponding text range [0, 1, 3, 1], indicates two text
* ranges of length 1, starting at offsets 0 and 3. Clients can use these ranges to treat the 'x'
* and 'y' values specially for linked edits.
*/
private final int[] defaultArgumentListTextRanges;
/**
* If the element is an executable, the names of the formal parameters of all kinds - required,
* optional positional, and optional named. The names of positional parameters are empty strings.
* Omitted if the element is not an executable.
*/
private final List<String> parameterNames;
/**
* If the element is an executable, the declared types of the formal parameters of all kinds -
* required, optional positional, and optional named. Omitted if the element is not an executable.
*/
private final List<String> parameterTypes;
/**
* This field is set if the relevance of this suggestion might be changed depending on where
* completion is requested.
*/
private final List<String> relevanceTags;
private final Integer requiredParameterCount;
/**
* Constructor for {@link AvailableSuggestion}.
*/
public AvailableSuggestion(String label, String declaringLibraryUri, Element element, String defaultArgumentListString, int[] defaultArgumentListTextRanges, List<String> parameterNames, List<String> parameterTypes, List<String> relevanceTags, Integer requiredParameterCount) {
this.label = label;
this.declaringLibraryUri = declaringLibraryUri;
this.element = element;
this.defaultArgumentListString = defaultArgumentListString;
this.defaultArgumentListTextRanges = defaultArgumentListTextRanges;
this.parameterNames = parameterNames;
this.parameterTypes = parameterTypes;
this.relevanceTags = relevanceTags;
this.requiredParameterCount = requiredParameterCount;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof AvailableSuggestion) {
AvailableSuggestion other = (AvailableSuggestion) obj;
return
ObjectUtilities.equals(other.label, label) &&
ObjectUtilities.equals(other.declaringLibraryUri, declaringLibraryUri) &&
ObjectUtilities.equals(other.element, element) &&
ObjectUtilities.equals(other.defaultArgumentListString, defaultArgumentListString) &&
Arrays.equals(other.defaultArgumentListTextRanges, defaultArgumentListTextRanges) &&
ObjectUtilities.equals(other.parameterNames, parameterNames) &&
ObjectUtilities.equals(other.parameterTypes, parameterTypes) &&
ObjectUtilities.equals(other.relevanceTags, relevanceTags) &&
ObjectUtilities.equals(other.requiredParameterCount, requiredParameterCount);
}
return false;
}
public static AvailableSuggestion fromJson(JsonObject jsonObject) {
String label = jsonObject.get("label").getAsString();
String declaringLibraryUri = jsonObject.get("declaringLibraryUri").getAsString();
Element element = Element.fromJson(jsonObject.get("element").getAsJsonObject());
String defaultArgumentListString = jsonObject.get("defaultArgumentListString") == null ? null : jsonObject.get("defaultArgumentListString").getAsString();
int[] defaultArgumentListTextRanges = jsonObject.get("defaultArgumentListTextRanges") == null ? null : JsonUtilities.decodeIntArray(jsonObject.get("defaultArgumentListTextRanges").getAsJsonArray());
List<String> parameterNames = jsonObject.get("parameterNames") == null ? null : JsonUtilities.decodeStringList(jsonObject.get("parameterNames").getAsJsonArray());
List<String> parameterTypes = jsonObject.get("parameterTypes") == null ? null : JsonUtilities.decodeStringList(jsonObject.get("parameterTypes").getAsJsonArray());
List<String> relevanceTags = jsonObject.get("relevanceTags") == null ? null : JsonUtilities.decodeStringList(jsonObject.get("relevanceTags").getAsJsonArray());
Integer requiredParameterCount = jsonObject.get("requiredParameterCount") == null ? null : jsonObject.get("requiredParameterCount").getAsInt();
return new AvailableSuggestion(label, declaringLibraryUri, element, defaultArgumentListString, defaultArgumentListTextRanges, parameterNames, parameterTypes, relevanceTags, requiredParameterCount);
}
public static List<AvailableSuggestion> fromJsonArray(JsonArray jsonArray) {
if (jsonArray == null) {
return EMPTY_LIST;
}
ArrayList<AvailableSuggestion> list = new ArrayList<AvailableSuggestion>(jsonArray.size());
Iterator<JsonElement> iterator = jsonArray.iterator();
while (iterator.hasNext()) {
list.add(fromJson(iterator.next().getAsJsonObject()));
}
return list;
}
/**
* The URI of the library that declares the element being suggested, not the URI of the library
* associated with the enclosing AvailableSuggestionSet.
*/
public String getDeclaringLibraryUri() {
return declaringLibraryUri;
}
/**
* A default String for use in generating argument list source contents on the client side.
*/
public String getDefaultArgumentListString() {
return defaultArgumentListString;
}
/**
* Pairs of offsets and lengths describing 'defaultArgumentListString' text ranges suitable for use
* by clients to set up linked edits of default argument source contents. For example, given an
* argument list string 'x, y', the corresponding text range [0, 1, 3, 1], indicates two text
* ranges of length 1, starting at offsets 0 and 3. Clients can use these ranges to treat the 'x'
* and 'y' values specially for linked edits.
*/
public int[] getDefaultArgumentListTextRanges() {
return defaultArgumentListTextRanges;
}
/**
* Information about the element reference being suggested.
*/
public Element getElement() {
return element;
}
/**
* The identifier to present to the user for code completion.
*/
public String getLabel() {
return label;
}
/**
* If the element is an executable, the names of the formal parameters of all kinds - required,
* optional positional, and optional named. The names of positional parameters are empty strings.
* Omitted if the element is not an executable.
*/
public List<String> getParameterNames() {
return parameterNames;
}
/**
* If the element is an executable, the declared types of the formal parameters of all kinds -
* required, optional positional, and optional named. Omitted if the element is not an executable.
*/
public List<String> getParameterTypes() {
return parameterTypes;
}
/**
* This field is set if the relevance of this suggestion might be changed depending on where
* completion is requested.
*/
public List<String> getRelevanceTags() {
return relevanceTags;
}
public Integer getRequiredParameterCount() {
return requiredParameterCount;
}
@Override
public int hashCode() {
HashCodeBuilder builder = new HashCodeBuilder();
builder.append(label);
builder.append(declaringLibraryUri);
builder.append(element);
builder.append(defaultArgumentListString);
builder.append(defaultArgumentListTextRanges);
builder.append(parameterNames);
builder.append(parameterTypes);
builder.append(relevanceTags);
builder.append(requiredParameterCount);
return builder.toHashCode();
}
public JsonObject toJson() {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("label", label);
jsonObject.addProperty("declaringLibraryUri", declaringLibraryUri);
jsonObject.add("element", element.toJson());
if (defaultArgumentListString != null) {
jsonObject.addProperty("defaultArgumentListString", defaultArgumentListString);
}
if (defaultArgumentListTextRanges != null) {
JsonArray jsonArrayDefaultArgumentListTextRanges = new JsonArray();
for (int elt : defaultArgumentListTextRanges) {
jsonArrayDefaultArgumentListTextRanges.add(new JsonPrimitive(elt));
}
jsonObject.add("defaultArgumentListTextRanges", jsonArrayDefaultArgumentListTextRanges);
}
if (parameterNames != null) {
JsonArray jsonArrayParameterNames = new JsonArray();
for (String elt : parameterNames) {
jsonArrayParameterNames.add(new JsonPrimitive(elt));
}
jsonObject.add("parameterNames", jsonArrayParameterNames);
}
if (parameterTypes != null) {
JsonArray jsonArrayParameterTypes = new JsonArray();
for (String elt : parameterTypes) {
jsonArrayParameterTypes.add(new JsonPrimitive(elt));
}
jsonObject.add("parameterTypes", jsonArrayParameterTypes);
}
if (relevanceTags != null) {
JsonArray jsonArrayRelevanceTags = new JsonArray();
for (String elt : relevanceTags) {
jsonArrayRelevanceTags.add(new JsonPrimitive(elt));
}
jsonObject.add("relevanceTags", jsonArrayRelevanceTags);
}
if (requiredParameterCount != null) {
jsonObject.addProperty("requiredParameterCount", requiredParameterCount);
}
return jsonObject;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[");
builder.append("label=");
builder.append(label + ", ");
builder.append("declaringLibraryUri=");
builder.append(declaringLibraryUri + ", ");
builder.append("element=");
builder.append(element + ", ");
builder.append("defaultArgumentListString=");
builder.append(defaultArgumentListString + ", ");
builder.append("defaultArgumentListTextRanges=");
builder.append(StringUtils.join(defaultArgumentListTextRanges, ", ") + ", ");
builder.append("parameterNames=");
builder.append(StringUtils.join(parameterNames, ", ") + ", ");
builder.append("parameterTypes=");
builder.append(StringUtils.join(parameterTypes, ", ") + ", ");
builder.append("relevanceTags=");
builder.append(StringUtils.join(relevanceTags, ", ") + ", ");
builder.append("requiredParameterCount=");
builder.append(requiredParameterCount);
builder.append("]");
return builder.toString();
}
}

View file

@ -1,141 +0,0 @@
/*
* Copyright (c) 2019, 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.
*
* This file has been automatically generated. Please do not edit it manually.
* To regenerate the file, use the script "pkg/analysis_server/tool/spec/generate_files".
*/
package org.dartlang.analysis.server.protocol;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.google.dart.server.utilities.general.JsonUtilities;
import com.google.dart.server.utilities.general.ObjectUtilities;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.commons.lang3.StringUtils;
/**
* @coverage dart.server.generated.types
*/
@SuppressWarnings("unused")
public class AvailableSuggestionSet {
public static final AvailableSuggestionSet[] EMPTY_ARRAY = new AvailableSuggestionSet[0];
public static final List<AvailableSuggestionSet> EMPTY_LIST = Lists.newArrayList();
/**
* The id associated with the library.
*/
private final int id;
/**
* The URI of the library.
*/
private final String uri;
private final List<AvailableSuggestion> items;
/**
* Constructor for {@link AvailableSuggestionSet}.
*/
public AvailableSuggestionSet(int id, String uri, List<AvailableSuggestion> items) {
this.id = id;
this.uri = uri;
this.items = items;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof AvailableSuggestionSet) {
AvailableSuggestionSet other = (AvailableSuggestionSet) obj;
return
other.id == id &&
ObjectUtilities.equals(other.uri, uri) &&
ObjectUtilities.equals(other.items, items);
}
return false;
}
public static AvailableSuggestionSet fromJson(JsonObject jsonObject) {
int id = jsonObject.get("id").getAsInt();
String uri = jsonObject.get("uri").getAsString();
List<AvailableSuggestion> items = AvailableSuggestion.fromJsonArray(jsonObject.get("items").getAsJsonArray());
return new AvailableSuggestionSet(id, uri, items);
}
public static List<AvailableSuggestionSet> fromJsonArray(JsonArray jsonArray) {
if (jsonArray == null) {
return EMPTY_LIST;
}
ArrayList<AvailableSuggestionSet> list = new ArrayList<AvailableSuggestionSet>(jsonArray.size());
Iterator<JsonElement> iterator = jsonArray.iterator();
while (iterator.hasNext()) {
list.add(fromJson(iterator.next().getAsJsonObject()));
}
return list;
}
/**
* The id associated with the library.
*/
public int getId() {
return id;
}
public List<AvailableSuggestion> getItems() {
return items;
}
/**
* The URI of the library.
*/
public String getUri() {
return uri;
}
@Override
public int hashCode() {
HashCodeBuilder builder = new HashCodeBuilder();
builder.append(id);
builder.append(uri);
builder.append(items);
return builder.toHashCode();
}
public JsonObject toJson() {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("id", id);
jsonObject.addProperty("uri", uri);
JsonArray jsonArrayItems = new JsonArray();
for (AvailableSuggestion elt : items) {
jsonArrayItems.add(elt.toJson());
}
jsonObject.add("items", jsonArrayItems);
return jsonObject;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[");
builder.append("id=");
builder.append(id + ", ");
builder.append("uri=");
builder.append(uri + ", ");
builder.append("items=");
builder.append(StringUtils.join(items, ", "));
builder.append("]");
return builder.toString();
}
}

View file

@ -1,28 +0,0 @@
/*
* Copyright (c) 2019, 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.
*
* This file has been automatically generated. Please do not edit it manually.
* To regenerate the file, use the script "pkg/analysis_server/tool/spec/generate_files".
*/
package org.dartlang.analysis.server.protocol;
/**
* An enumeration of the completion services to which a client can subscribe.
*
* @coverage dart.server.generated.types
*/
public class CompletionService {
/**
* The client will receive availableSuggestions notifications once subscribed with completion
* suggestion sets from the libraries of interest. The client should keep an up-to-date record of
* these in memory so that it will be able to union these candidates with other completion
* suggestions when applicable at completion time.
*
* The client will also receive existingImports notifications.
*/
public static final String AVAILABLE_SUGGESTION_SETS = "AVAILABLE_SUGGESTION_SETS";
}

View file

@ -1,130 +0,0 @@
/*
* Copyright (c) 2019, 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.
*
* This file has been automatically generated. Please do not edit it manually.
* To regenerate the file, use the script "pkg/analysis_server/tool/spec/generate_files".
*/
package org.dartlang.analysis.server.protocol;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.google.dart.server.utilities.general.JsonUtilities;
import com.google.dart.server.utilities.general.ObjectUtilities;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.commons.lang3.StringUtils;
/**
* Each AvailableSuggestion can specify zero or more tags in the field relevanceTags, so that when
* the included tag is equal to one of the relevanceTags, the suggestion is given higher relevance
* than the whole IncludedSuggestionSet.
*
* @coverage dart.server.generated.types
*/
@SuppressWarnings("unused")
public class IncludedSuggestionRelevanceTag {
public static final IncludedSuggestionRelevanceTag[] EMPTY_ARRAY = new IncludedSuggestionRelevanceTag[0];
public static final List<IncludedSuggestionRelevanceTag> EMPTY_LIST = Lists.newArrayList();
/**
* The opaque value of the tag.
*/
private final String tag;
/**
* The boost to the relevance of the completion suggestions that match this tag, which is added to
* the relevance of the containing IncludedSuggestionSet.
*/
private final int relevanceBoost;
/**
* Constructor for {@link IncludedSuggestionRelevanceTag}.
*/
public IncludedSuggestionRelevanceTag(String tag, int relevanceBoost) {
this.tag = tag;
this.relevanceBoost = relevanceBoost;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof IncludedSuggestionRelevanceTag) {
IncludedSuggestionRelevanceTag other = (IncludedSuggestionRelevanceTag) obj;
return
ObjectUtilities.equals(other.tag, tag) &&
other.relevanceBoost == relevanceBoost;
}
return false;
}
public static IncludedSuggestionRelevanceTag fromJson(JsonObject jsonObject) {
String tag = jsonObject.get("tag").getAsString();
int relevanceBoost = jsonObject.get("relevanceBoost").getAsInt();
return new IncludedSuggestionRelevanceTag(tag, relevanceBoost);
}
public static List<IncludedSuggestionRelevanceTag> fromJsonArray(JsonArray jsonArray) {
if (jsonArray == null) {
return EMPTY_LIST;
}
ArrayList<IncludedSuggestionRelevanceTag> list = new ArrayList<IncludedSuggestionRelevanceTag>(jsonArray.size());
Iterator<JsonElement> iterator = jsonArray.iterator();
while (iterator.hasNext()) {
list.add(fromJson(iterator.next().getAsJsonObject()));
}
return list;
}
/**
* The boost to the relevance of the completion suggestions that match this tag, which is added to
* the relevance of the containing IncludedSuggestionSet.
*/
public int getRelevanceBoost() {
return relevanceBoost;
}
/**
* The opaque value of the tag.
*/
public String getTag() {
return tag;
}
@Override
public int hashCode() {
HashCodeBuilder builder = new HashCodeBuilder();
builder.append(tag);
builder.append(relevanceBoost);
return builder.toHashCode();
}
public JsonObject toJson() {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("tag", tag);
jsonObject.addProperty("relevanceBoost", relevanceBoost);
return jsonObject;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[");
builder.append("tag=");
builder.append(tag + ", ");
builder.append("relevanceBoost=");
builder.append(relevanceBoost);
builder.append("]");
return builder.toString();
}
}

View file

@ -1,160 +0,0 @@
/*
* Copyright (c) 2019, 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.
*
* This file has been automatically generated. Please do not edit it manually.
* To regenerate the file, use the script "pkg/analysis_server/tool/spec/generate_files".
*/
package org.dartlang.analysis.server.protocol;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.google.dart.server.utilities.general.JsonUtilities;
import com.google.dart.server.utilities.general.ObjectUtilities;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.commons.lang3.StringUtils;
/**
* A reference to an AvailableSuggestionSet noting that the library's members which match the kind
* of this ref should be presented to the user.
*
* @coverage dart.server.generated.types
*/
@SuppressWarnings("unused")
public class IncludedSuggestionSet {
public static final IncludedSuggestionSet[] EMPTY_ARRAY = new IncludedSuggestionSet[0];
public static final List<IncludedSuggestionSet> EMPTY_LIST = Lists.newArrayList();
/**
* Clients should use it to access the set of precomputed completions to be displayed to the user.
*/
private final int id;
/**
* The relevance of completion suggestions from this library where a higher number indicates a
* higher relevance.
*/
private final int relevance;
/**
* The optional string that should be displayed instead of the uri of the referenced
* AvailableSuggestionSet.
*
* For example libraries in the "test" directory of a package have only "file://" URIs, so are
* usually long, and don't look nice, but actual import directives will use relative URIs, which
* are short, so we probably want to display such relative URIs to the user.
*/
private final String displayUri;
/**
* Constructor for {@link IncludedSuggestionSet}.
*/
public IncludedSuggestionSet(int id, int relevance, String displayUri) {
this.id = id;
this.relevance = relevance;
this.displayUri = displayUri;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof IncludedSuggestionSet) {
IncludedSuggestionSet other = (IncludedSuggestionSet) obj;
return
other.id == id &&
other.relevance == relevance &&
ObjectUtilities.equals(other.displayUri, displayUri);
}
return false;
}
public static IncludedSuggestionSet fromJson(JsonObject jsonObject) {
int id = jsonObject.get("id").getAsInt();
int relevance = jsonObject.get("relevance").getAsInt();
String displayUri = jsonObject.get("displayUri") == null ? null : jsonObject.get("displayUri").getAsString();
return new IncludedSuggestionSet(id, relevance, displayUri);
}
public static List<IncludedSuggestionSet> fromJsonArray(JsonArray jsonArray) {
if (jsonArray == null) {
return EMPTY_LIST;
}
ArrayList<IncludedSuggestionSet> list = new ArrayList<IncludedSuggestionSet>(jsonArray.size());
Iterator<JsonElement> iterator = jsonArray.iterator();
while (iterator.hasNext()) {
list.add(fromJson(iterator.next().getAsJsonObject()));
}
return list;
}
/**
* The optional string that should be displayed instead of the uri of the referenced
* AvailableSuggestionSet.
*
* For example libraries in the "test" directory of a package have only "file://" URIs, so are
* usually long, and don't look nice, but actual import directives will use relative URIs, which
* are short, so we probably want to display such relative URIs to the user.
*/
public String getDisplayUri() {
return displayUri;
}
/**
* Clients should use it to access the set of precomputed completions to be displayed to the user.
*/
public int getId() {
return id;
}
/**
* The relevance of completion suggestions from this library where a higher number indicates a
* higher relevance.
*/
public int getRelevance() {
return relevance;
}
@Override
public int hashCode() {
HashCodeBuilder builder = new HashCodeBuilder();
builder.append(id);
builder.append(relevance);
builder.append(displayUri);
return builder.toHashCode();
}
public JsonObject toJson() {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("id", id);
jsonObject.addProperty("relevance", relevance);
if (displayUri != null) {
jsonObject.addProperty("displayUri", displayUri);
}
return jsonObject;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[");
builder.append("id=");
builder.append(id + ", ");
builder.append("relevance=");
builder.append(relevance + ", ");
builder.append("displayUri=");
builder.append(displayUri);
builder.append("]");
return builder.toString();
}
}

View file

@ -7,7 +7,7 @@
<body>
<h1>Analysis Server API Specification</h1>
<h1 style="color:#999999">Version
<version>1.34.0</version>
<version>1.35.0</version>
</h1>
<p>
This document contains a specification of the API provided by the
@ -142,6 +142,16 @@
ignoring the item or treating it with some default/fallback handling.
</p>
<h3>Changelog</h3>
<h4>1.35.0</h4>
<ul>
<li>
Removed the requests <tt>completion.getSuggestions</tt>,
<tt>completion.setSubscriptions</tt>, and
<tt>completion.getSuggestionDetails</tt>. These requests are no longer
supported and the server will return an error if they are invoked. The
associated object structures and notifications have also been removed.
</li>
</ul>
<h4>1.34.0</h4>
<ul>
<li>
@ -1592,37 +1602,6 @@
The code completion domain contains commands related to
getting code completion suggestions.
</p>
<request method="getSuggestions">
<p>
Request that completion suggestions for the given offset in
the given file be returned.
</p>
<params>
<field name="file">
<ref>FilePath</ref>
<p>
The file containing the point at which suggestions are
to be made.
</p>
</field>
<field name="offset">
<ref>int</ref>
<p>
The offset within the file at which suggestions are to
be made.
</p>
</field>
</params>
<result>
<field name="id">
<ref>CompletionId</ref>
<p>
The identifier used to associate results with this
completion request.
</p>
</field>
</result>
</request>
<request method="getSuggestions2">
<p>
Request that completion suggestions for the given offset in the given
@ -1732,25 +1711,6 @@
</field>
</result>
</request>
<request method="setSubscriptions">
<p>
Subscribe for completion services. All previous subscriptions are
replaced by the given set of services.
</p>
<p>
It is an error if any of the elements in the list are not valid
services. If there is an error, then the current subscriptions will
remain unchanged.
</p>
<params>
<field name="subscriptions">
<list>
<ref>CompletionService</ref>
</list>
<p>A list of the services being subscribed to.</p>
</field>
</params>
</request>
<request method="registerLibraryPaths" deprecated="true">
<p>
The client can make this request to express interest in certain
@ -1774,60 +1734,6 @@
</field>
</params>
</request>
<request method="getSuggestionDetails">
<p>
Clients must make this request when the user has selected a completion
suggestion from an <tt>AvailableSuggestionSet</tt>. Analysis server will respond with
the text to insert as well as any <tt>SourceChange</tt> that needs to be applied
in case the completion requires an additional import to be added. It is an error
if the id is no longer valid, for instance if the library has been removed after
the completion suggestion is accepted.
</p>
<params>
<field name="file">
<ref>FilePath</ref>
<p>
The path of the file into which this completion is being inserted.
</p>
</field>
<field name="id">
<ref>int</ref>
<p>
The identifier of the <tt>AvailableSuggestionSet</tt> containing
the selected label.
</p>
</field>
<field name="label">
<ref>String</ref>
<p>
The label from the <tt>AvailableSuggestionSet</tt> with the `id`
for which insertion information is requested.
</p>
</field>
<field name="offset">
<ref>int</ref>
<p>
The offset in the file where the completion will be inserted.
</p>
</field>
</params>
<result>
<field name="completion">
<ref>String</ref>
<p>
The full text to insert, including any optional import prefix.
</p>
</field>
<field name="change" optional="true">
<ref>SourceChange</ref>
<p>
A change for the client to apply in case the library containing
the accepted completion suggestion needs to be imported. The field
will be omitted if there are no additional changes that need to be made.
</p>
</field>
</result>
</request>
<request method="getSuggestionDetails2">
<p>
Clients must make this request when the user has selected a completion
@ -1888,141 +1794,6 @@
</field>
</result>
</request>
<notification event="results">
<p>
Reports the completion suggestions that should be presented
to the user. The set of suggestions included in the
notification is always a complete list that supersedes any
previously reported suggestions.
</p>
<params>
<field name="id">
<ref>CompletionId</ref>
<p>
The id associated with the completion.
</p>
</field>
<field name="replacementOffset">
<ref>int</ref>
<p>
The offset of the start of the text to be
replaced. This will be different than the offset used
to request the completion suggestions if there was a
portion of an identifier before the original
offset. In particular, the replacementOffset will be
the offset of the beginning of said identifier.
</p>
</field>
<field name="replacementLength">
<ref>int</ref>
<p>
The length of the text to be replaced if the remainder
of the identifier containing the cursor is to be
replaced when the suggestion is applied (that is, the
number of characters in the existing identifier).
</p>
</field>
<field name="results">
<list>
<ref>CompletionSuggestion</ref>
</list>
<p>
The completion suggestions being reported. The
notification contains all possible completions at the
requested cursor position, even those that do not match
the characters the user has already typed. This allows
the client to respond to further keystrokes from the
user without having to make additional requests.
</p>
</field>
<field name="isLast">
<ref>bool</ref>
<p>
True if this is that last set of results that will be
returned for the indicated completion.
</p>
</field>
<field name="libraryFile" optional="true">
<ref>FilePath</ref>
<p>
The library file that contains the file where completion was
requested. The client might use it for example together with the
<tt>existingImports</tt> notification to filter out available
suggestions. If there were changes to existing imports in the library,
the corresponding <tt>existingImports</tt> notification will be sent
before the completion notification.
</p>
</field>
<field name="includedSuggestionSets" optional="true">
<list>
<ref>IncludedSuggestionSet</ref>
</list>
<p>
References to <tt>AvailableSuggestionSet</tt> objects previously sent
to the client. The client can include applicable names from the
referenced library in code completion suggestions.
</p>
</field>
<field name="includedElementKinds" optional="true">
<list>
<ref>ElementKind</ref>
</list>
<p>
The client is expected to check this list against the
<tt>ElementKind</tt> sent in <tt>IncludedSuggestionSet</tt> to decide
whether or not these symbols should be presented to the user.
</p>
</field>
<field name="includedSuggestionRelevanceTags" optional="true">
<list>
<ref>IncludedSuggestionRelevanceTag</ref>
</list>
<p>
The client is expected to check this list against the values of the
field <tt>relevanceTags</tt> of <tt>AvailableSuggestion</tt> to
decide if the suggestion should be given a different relevance than
the <tt>IncludedSuggestionSet</tt> that contains it. This might be
used for example to give higher relevance to suggestions of matching
types.
</p>
<p>
If an <tt>AvailableSuggestion</tt> has relevance tags that match more
than one <tt>IncludedSuggestionRelevanceTag</tt>, the maximum
relevance boost is used.
</p>
</field>
</params>
</notification>
<notification event="availableSuggestions">
<p>
Reports the pre-computed, candidate completions from symbols defined
in a corresponding library. This notification may be sent multiple times.
When a notification is processed, clients should replace any previous
information about the libraries in the list of changedLibraries, discard
any information about the libraries in the list of removedLibraries, and
preserve any previously received information about any libraries that are
not included in either list.
</p>
<params>
<field name="changedLibraries" optional="true">
<list>
<ref>AvailableSuggestionSet</ref>
</list>
<p>
A list of pre-computed, potential completions coming from
this set of completion suggestions.
</p>
</field>
<field name="removedLibraries" optional="true">
<list>
<ref>int</ref>
</list>
<p>
A list of library ids that no longer apply.
</p>
</field>
</params>
</notification>
<notification event="existingImports">
<p>
Reports existing imports in a library. This notification may be sent
@ -3850,13 +3621,6 @@
</field>
</object>
</type>
<type name="CompletionId">
<ref>String</ref>
<p>
An identifier used to associate completion results with a
completion request.
</p>
</type>
<type name="ContextData">
<p>
Information about an analysis context.
@ -4014,116 +3778,6 @@
The identifier for a execution context.
</p>
</type>
<type name="AvailableSuggestion">
<p>
A partial completion suggestion that can be used in combination with
info from <tt>completion.results</tt> to build completion suggestions
for not yet imported library tokens.
</p>
<object>
<field name="label">
<ref>String</ref>
<p>
The identifier to present to the user for code completion.
</p>
</field>
<field name="declaringLibraryUri">
<ref>String</ref>
<p>
The URI of the library that declares the element being suggested,
not the URI of the library associated with the enclosing
<tt>AvailableSuggestionSet</tt>.
</p>
</field>
<field name="element">
<ref>Element</ref>
<p>
Information about the element reference being suggested.
</p>
</field>
<field name="defaultArgumentListString" optional="true">
<ref>String</ref>
<p>
A default String for use in generating argument list source contents
on the client side.
</p>
</field>
<field name="defaultArgumentListTextRanges" optional="true">
<list>
<ref>int</ref>
</list>
<p>
Pairs of offsets and lengths describing 'defaultArgumentListString'
text ranges suitable for use by clients to set up linked edits of
default argument source contents. For example, given an argument list
string 'x, y', the corresponding text range [0, 1, 3, 1], indicates
two text ranges of length 1, starting at offsets 0 and 3. Clients can
use these ranges to treat the 'x' and 'y' values specially for linked
edits.
</p>
</field>
<field name="parameterNames" optional="true">
<list>
<ref>String</ref>
</list>
<p>
If the element is an executable, the names of the formal parameters of
all kinds - required, optional positional, and optional named. The
names of positional parameters are empty strings. Omitted if the element
is not an executable.
</p>
</field>
<field name="parameterTypes" optional="true">
<list>
<ref>String</ref>
</list>
<p>
If the element is an executable, the declared types of the formal parameters
of all kinds - required, optional positional, and optional named.
Omitted if the element is not an executable.
</p>
</field>
<field name="relevanceTags" optional="true">
<list>
<ref>AvailableSuggestionRelevanceTag</ref>
</list>
<p>
This field is set if the relevance of this suggestion might be
changed depending on where completion is requested.
</p>
</field>
<field name="requiredParameterCount" optional="true">
<ref>int</ref>
</field>
</object>
</type>
<type name="AvailableSuggestionRelevanceTag">
<ref>String</ref>
<p>
The opaque tag value.
</p>
</type>
<type name="AvailableSuggestionSet">
<object>
<field name="id">
<ref>int</ref>
<p>
The id associated with the library.
</p>
</field>
<field name="uri">
<ref>String</ref>
<p>
The URI of the library.
</p>
</field>
<field name="items">
<list>
<ref>AvailableSuggestion</ref>
</list>
</field>
</object>
</type>
<type name="ExistingImport">
<p>
Information about an existing import, with elements that it provides.
@ -4204,86 +3858,6 @@
</field>
</object>
</type>
<type name="IncludedSuggestionSet">
<p>
A reference to an <tt>AvailableSuggestionSet</tt> noting
that the library's members which match the kind of this ref
should be presented to the user.
</p>
<object>
<field name="id">
<ref>int</ref>
<p>
Clients should use it to access the set of precomputed completions
to be displayed to the user.
</p>
</field>
<field name="relevance">
<ref>int</ref>
<p>
The relevance of completion suggestions from this
library where a higher number indicates a higher relevance.
</p>
</field>
<field name="displayUri" optional="true">
<ref>String</ref>
<p>
The optional string that should be displayed instead of the
<tt>uri</tt> of the referenced <tt>AvailableSuggestionSet</tt>.
</p>
<p>
For example libraries in the "test" directory of a package have only
"file://" URIs, so are usually long, and don't look nice, but actual
import directives will use relative URIs, which are short, so we
probably want to display such relative URIs to the user.
</p>
</field>
</object>
</type>
<type name="IncludedSuggestionRelevanceTag">
<p>
Each <tt>AvailableSuggestion</tt> can specify zero or more tags in the
field <tt>relevanceTags</tt>, so that when the included tag is equal to
one of the <tt>relevanceTags</tt>, the suggestion is given higher
relevance than the whole <tt>IncludedSuggestionSet</tt>.
</p>
<object>
<field name="tag">
<ref>AvailableSuggestionRelevanceTag</ref>
<p>
The opaque value of the tag.
</p>
</field>
<field name="relevanceBoost">
<ref>int</ref>
<p>
The boost to the relevance of the completion suggestions that match
this tag, which is added to the relevance of the containing
<tt>IncludedSuggestionSet</tt>.
</p>
</field>
</object>
</type>
<type name="CompletionService">
<p>
An enumeration of the completion services to which a client can subscribe.
</p>
<enum>
<value>
<code>AVAILABLE_SUGGESTION_SETS</code>
<p>
The client will receive <tt>availableSuggestions</tt> notifications
once subscribed with completion suggestion sets from the libraries of
interest. The client should keep an up-to-date record of these in
memory so that it will be able to union these candidates with other
completion suggestions when applicable at completion time.
</p>
<p>
The client will also receive <tt>existingImports</tt> notifications.
</p>
</value>
</enum>
</type>
<type name="LibraryPathSet">
<p>
A list of associations between paths and the libraries that should be

View file

@ -67,19 +67,10 @@ mixin NotificationHandler {
onAnalysisOverrides(
AnalysisOverridesParams.fromJson(decoder, 'params', params));
break;
case COMPLETION_NOTIFICATION_AVAILABLE_SUGGESTIONS:
onCompletionAvailableSuggestions(
CompletionAvailableSuggestionsParams.fromJson(
decoder, 'params', params));
break;
case COMPLETION_NOTIFICATION_EXISTING_IMPORTS:
onCompletionExistingImports(CompletionExistingImportsParams.fromJson(
decoder, 'params', params));
break;
case COMPLETION_NOTIFICATION_RESULTS:
onCompletionResults(
CompletionResultsParams.fromJson(decoder, 'params', params));
break;
case EXECUTION_NOTIFICATION_LAUNCH_DATA:
onExecutionLaunchData(
ExecutionLaunchDataParams.fromJson(decoder, 'params', params));
@ -216,27 +207,11 @@ mixin NotificationHandler {
/// request.
void onAnalysisOverrides(AnalysisOverridesParams params) {}
/// Reports the pre-computed, candidate completions from symbols defined
/// in a corresponding library. This notification may be sent multiple times.
/// When a notification is processed, clients should replace any previous
/// information about the libraries in the list of changedLibraries, discard
/// any information about the libraries in the list of removedLibraries, and
/// preserve any previously received information about any libraries that are
/// not included in either list.
void onCompletionAvailableSuggestions(
CompletionAvailableSuggestionsParams params) {}
/// Reports existing imports in a library. This notification may be sent
/// multiple times for a library. When a notification is processed, clients
/// should replace any previous information for the library.
void onCompletionExistingImports(CompletionExistingImportsParams params) {}
/// Reports the completion suggestions that should be presented
/// to the user. The set of suggestions included in the
/// notification is always a complete list that supersedes any
/// previously reported suggestions.
void onCompletionResults(CompletionResultsParams params) {}
/// Reports information needed to allow a single file to be launched.
///
/// This notification is not subscribed to by default. Clients can

View file

@ -6,7 +6,7 @@
// To regenerate the file, use the script
// "pkg/analysis_server/tool/spec/generate_files".
const String PROTOCOL_VERSION = '1.34.0';
const String PROTOCOL_VERSION = '1.35.0';
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES = 'analysis.analyzedFiles';
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES_DIRECTORIES = 'directories';
@ -110,33 +110,10 @@ const String ANALYTICS_REQUEST_SEND_TIMING = 'analytics.sendTiming';
const String ANALYTICS_REQUEST_SEND_TIMING_EVENT = 'event';
const String ANALYTICS_REQUEST_SEND_TIMING_MILLIS = 'millis';
const String ANALYTICS_RESPONSE_IS_ENABLED_ENABLED = 'enabled';
const String COMPLETION_NOTIFICATION_AVAILABLE_SUGGESTIONS =
'completion.availableSuggestions';
const String COMPLETION_NOTIFICATION_AVAILABLE_SUGGESTIONS_CHANGED_LIBRARIES =
'changedLibraries';
const String COMPLETION_NOTIFICATION_AVAILABLE_SUGGESTIONS_REMOVED_LIBRARIES =
'removedLibraries';
const String COMPLETION_NOTIFICATION_EXISTING_IMPORTS =
'completion.existingImports';
const String COMPLETION_NOTIFICATION_EXISTING_IMPORTS_FILE = 'file';
const String COMPLETION_NOTIFICATION_EXISTING_IMPORTS_IMPORTS = 'imports';
const String COMPLETION_NOTIFICATION_RESULTS = 'completion.results';
const String COMPLETION_NOTIFICATION_RESULTS_ID = 'id';
const String COMPLETION_NOTIFICATION_RESULTS_INCLUDED_ELEMENT_KINDS =
'includedElementKinds';
const String
COMPLETION_NOTIFICATION_RESULTS_INCLUDED_SUGGESTION_RELEVANCE_TAGS =
'includedSuggestionRelevanceTags';
const String COMPLETION_NOTIFICATION_RESULTS_INCLUDED_SUGGESTION_SETS =
'includedSuggestionSets';
const String COMPLETION_NOTIFICATION_RESULTS_IS_LAST = 'isLast';
const String COMPLETION_NOTIFICATION_RESULTS_LIBRARY_FILE = 'libraryFile';
const String COMPLETION_NOTIFICATION_RESULTS_REPLACEMENT_LENGTH =
'replacementLength';
const String COMPLETION_NOTIFICATION_RESULTS_REPLACEMENT_OFFSET =
'replacementOffset';
const String COMPLETION_NOTIFICATION_RESULTS_RESULTS = 'results';
const String COMPLETION_REQUEST_GET_SUGGESTIONS = 'completion.getSuggestions';
const String COMPLETION_REQUEST_GET_SUGGESTIONS2 = 'completion.getSuggestions2';
const String COMPLETION_REQUEST_GET_SUGGESTIONS2_COMPLETION_CASE_MATCHING_MODE =
'completionCaseMatchingMode';
@ -148,10 +125,6 @@ const String COMPLETION_REQUEST_GET_SUGGESTIONS2_INVOCATION_COUNT =
const String COMPLETION_REQUEST_GET_SUGGESTIONS2_MAX_RESULTS = 'maxResults';
const String COMPLETION_REQUEST_GET_SUGGESTIONS2_OFFSET = 'offset';
const String COMPLETION_REQUEST_GET_SUGGESTIONS2_TIMEOUT = 'timeout';
const String COMPLETION_REQUEST_GET_SUGGESTIONS_FILE = 'file';
const String COMPLETION_REQUEST_GET_SUGGESTIONS_OFFSET = 'offset';
const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS =
'completion.getSuggestionDetails';
const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS2 =
'completion.getSuggestionDetails2';
const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS2_COMPLETION =
@ -160,17 +133,9 @@ const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS2_FILE = 'file';
const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS2_LIBRARY_URI =
'libraryUri';
const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS2_OFFSET = 'offset';
const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS_FILE = 'file';
const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS_ID = 'id';
const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS_LABEL = 'label';
const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS_OFFSET = 'offset';
const String COMPLETION_REQUEST_REGISTER_LIBRARY_PATHS =
'completion.registerLibraryPaths';
const String COMPLETION_REQUEST_REGISTER_LIBRARY_PATHS_PATHS = 'paths';
const String COMPLETION_REQUEST_SET_SUBSCRIPTIONS =
'completion.setSubscriptions';
const String COMPLETION_REQUEST_SET_SUBSCRIPTIONS_SUBSCRIPTIONS =
'subscriptions';
const String COMPLETION_RESPONSE_GET_SUGGESTIONS2_IS_INCOMPLETE =
'isIncomplete';
const String COMPLETION_RESPONSE_GET_SUGGESTIONS2_REPLACEMENT_LENGTH =
@ -178,13 +143,9 @@ const String COMPLETION_RESPONSE_GET_SUGGESTIONS2_REPLACEMENT_LENGTH =
const String COMPLETION_RESPONSE_GET_SUGGESTIONS2_REPLACEMENT_OFFSET =
'replacementOffset';
const String COMPLETION_RESPONSE_GET_SUGGESTIONS2_SUGGESTIONS = 'suggestions';
const String COMPLETION_RESPONSE_GET_SUGGESTIONS_ID = 'id';
const String COMPLETION_RESPONSE_GET_SUGGESTION_DETAILS2_CHANGE = 'change';
const String COMPLETION_RESPONSE_GET_SUGGESTION_DETAILS2_COMPLETION =
'completion';
const String COMPLETION_RESPONSE_GET_SUGGESTION_DETAILS_CHANGE = 'change';
const String COMPLETION_RESPONSE_GET_SUGGESTION_DETAILS_COMPLETION =
'completion';
const String DIAGNOSTIC_REQUEST_GET_DIAGNOSTICS = 'diagnostic.getDiagnostics';
const String DIAGNOSTIC_REQUEST_GET_SERVER_PORT = 'diagnostic.getServerPort';
const String DIAGNOSTIC_RESPONSE_GET_DIAGNOSTICS_CONTEXTS = 'contexts';