mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
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:
parent
d156344a08
commit
d45080d365
|
@ -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 ||
|
||||
|
|
|
@ -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
|
@ -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
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(''));
|
||||
}
|
||||
}
|
|
@ -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('')));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>());
|
||||
}
|
||||
|
|
|
@ -93,6 +93,7 @@ class CompletionTestCase extends AbstractCompletionDomainTest {
|
|||
await getSuggestions(
|
||||
path: testFile.path,
|
||||
completionOffset: completionOffset,
|
||||
maxResults: 1 << 10,
|
||||
);
|
||||
|
||||
filterResults(spec.source);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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].
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)});
|
||||
|
|
|
@ -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}
|
||||
*
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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";
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue