afcfbbeba8
(Part of https://github.com/dart-lang/sdk/issues/63288) This change migrates the packages owned by the developer experience team to use the new constructor declaration syntax, described in https://github.com/dart-lang/language/blob/main/accepted/future-releases/primary-constructors/feature-specification.md#abbreviations-of-in-body-constructor-declarations. This change was performed in an automated fashion, by (a) bumping the packages' SDK constraints to `3.13.0-0`, (b) enabling the lints `unnecessary_type_name_in_constructor` and `unnecessary_const_in_enum_constructor`, (c) fixing the resulting lint failures using `dart fix`, and then (d) reformatting the affected files. To ease code review, I've reverted unrelated formatting changes. Since this change requires bumping SDK constaints to `3.13.0-0`, it was only performed on packages that are *not* published on pub. (Packages that *are* published on pub should remain on lower language versions until at least after the stable version of 3.13 is released, so that we don't block users on the stable channel from receiving updates to those packages.) Change-Id: Ibb4daebafd239da58251e838ea6a3f336a6a6964 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/505046 Commit-Queue: Paul Berry <paulberry@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com>
607 lines
20 KiB
Dart
607 lines
20 KiB
Dart
// Copyright (c) 2014, 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.
|
|
|
|
/// Support for client code that needs to interact with the requests, responses
|
|
/// and notifications that are part of the analysis server's wire protocol.
|
|
library;
|
|
|
|
import 'package:analysis_server/protocol/protocol_generated.dart';
|
|
import 'package:analysis_server/src/protocol/protocol_internal.dart';
|
|
import 'package:analyzer/src/utilities/cancellation.dart';
|
|
|
|
export 'package:analyzer_plugin/protocol/protocol.dart' show Enum;
|
|
|
|
/// A notification that can be sent from the server about an event that
|
|
/// occurred.
|
|
///
|
|
/// Clients may not extend, implement or mix-in this class.
|
|
class Notification {
|
|
/// The name of the JSON attribute containing the name of the event that
|
|
/// triggered the notification.
|
|
static const String eventAttributeName = 'event';
|
|
|
|
/// The name of the JSON attribute containing the result values.
|
|
static const String paramsAttributeName = 'params';
|
|
|
|
/// The name of the event that triggered the notification.
|
|
final String event;
|
|
|
|
/// A table mapping the names of notification parameters to their values, or
|
|
/// `null` if there are no notification parameters.
|
|
final Map<String, Object?>? params;
|
|
|
|
/// Initialize a newly created [Notification] to have the given [event] name.
|
|
/// If [params] is provided, it will be used as the params; otherwise no
|
|
/// params will be used.
|
|
new(this.event, [this.params]);
|
|
|
|
/// Initialize a newly created instance based on the given JSON data.
|
|
factory fromJson(Map<Object?, Object?> json) {
|
|
return Notification(
|
|
json[Notification.eventAttributeName] as String,
|
|
json[Notification.paramsAttributeName] as Map<String, Object?>?,
|
|
);
|
|
}
|
|
|
|
/// Returns a table representing the structure of the JSON object that will be
|
|
/// sent to the client to represent this response.
|
|
Map<String, Object> toJson() => {
|
|
eventAttributeName: event,
|
|
paramsAttributeName: ?params,
|
|
};
|
|
}
|
|
|
|
/// A request that was received from the client.
|
|
///
|
|
/// Clients may not extend, implement or mix-in this class.
|
|
class Request extends RequestOrResponse {
|
|
/// The name of the JSON attribute containing the id of the request.
|
|
static const String idAttributeName = 'id';
|
|
|
|
/// The name of the JSON attribute containing the name of the request.
|
|
static const String methodAttributeName = 'method';
|
|
|
|
/// The name of the JSON attribute containing the request parameters.
|
|
static const String paramsAttributeName = 'params';
|
|
|
|
/// The name of the optional JSON attribute indicating the time (milliseconds
|
|
/// since epoch) at which the client made the request.
|
|
static const String clientRequestTimeAttributeName = 'clientRequestTime';
|
|
|
|
/// The unique identifier used to identify this request.
|
|
@override
|
|
final String id;
|
|
|
|
/// The method being requested.
|
|
final String method;
|
|
|
|
/// A table mapping the names of request parameters to their values.
|
|
final Map<String, Object?> params;
|
|
|
|
/// The time (milliseconds since epoch) at which the client made the request
|
|
/// or `null` if this information is not provided by the client.
|
|
final int? clientRequestTime;
|
|
|
|
/// Initialize a newly created [Request] to have the given [id] and [method]
|
|
/// name. If [params] is supplied, it is used as the "params" map for the
|
|
/// request. Otherwise an empty "params" map is allocated.
|
|
new(
|
|
this.id,
|
|
this.method, [
|
|
Map<String, Object?>? params,
|
|
this.clientRequestTime,
|
|
]) : params = params ?? <String, Object?>{};
|
|
|
|
@override
|
|
int get hashCode {
|
|
return id.hashCode;
|
|
}
|
|
|
|
/// Returns the amount of time (in milliseconds) since the client sent this
|
|
/// request or `null` if the client did not provide [clientRequestTime].
|
|
int? get timeSinceRequest {
|
|
var clientRequestTime = this.clientRequestTime;
|
|
return clientRequestTime != null
|
|
? DateTime.now().millisecondsSinceEpoch - clientRequestTime
|
|
: null;
|
|
}
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
return other is Request &&
|
|
id == other.id &&
|
|
method == other.method &&
|
|
clientRequestTime == other.clientRequestTime &&
|
|
_equalMaps(params, other.params);
|
|
}
|
|
|
|
/// Returns a table representing the structure of the JSON object that will be
|
|
/// sent to the client to represent this response.
|
|
Map<String, Object> toJson() => {
|
|
idAttributeName: id,
|
|
methodAttributeName: method,
|
|
if (params.isNotEmpty) paramsAttributeName: params,
|
|
clientRequestTimeAttributeName: ?clientRequestTime,
|
|
};
|
|
|
|
bool _equalLists(List<Object?> first, List<Object?> second) {
|
|
var length = first.length;
|
|
if (length != second.length) {
|
|
return false;
|
|
}
|
|
for (var i = 0; i < length; i++) {
|
|
if (!_equalObjects(first[i], second[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool _equalMaps(Map<Object?, Object?> first, Map<Object?, Object?> second) {
|
|
if (first.length != second.length) {
|
|
return false;
|
|
}
|
|
for (var key in first.keys) {
|
|
if (!second.containsKey(key)) {
|
|
return false;
|
|
}
|
|
if (!_equalObjects(first[key], second[key])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool _equalObjects(Object? first, Object? second) {
|
|
if (first == null) {
|
|
return second == null;
|
|
}
|
|
if (second == null) {
|
|
return false;
|
|
}
|
|
if (first is Map) {
|
|
if (second is Map) {
|
|
return _equalMaps(first, second);
|
|
}
|
|
return false;
|
|
}
|
|
if (first is List) {
|
|
if (second is List) {
|
|
return _equalLists(first, second);
|
|
}
|
|
return false;
|
|
}
|
|
return first == second;
|
|
}
|
|
|
|
/// Returns a request parsed from the given JSON [result], or `null` if the
|
|
/// data is not a valid JSON representation of a request. The data is expected
|
|
/// to have the following format:
|
|
///
|
|
/// {
|
|
/// 'clientRequestTime': millisecondsSinceEpoch
|
|
/// 'id': String,
|
|
/// 'method': methodName,
|
|
/// 'params': {
|
|
/// parameter_name: value
|
|
/// }
|
|
/// }
|
|
///
|
|
/// where both the parameters and `clientRequestTime` are optional.
|
|
///
|
|
/// The parameters can contain any number of name/value pairs. The
|
|
/// `clientRequestTime` must be an int representing the time at which the
|
|
/// client issued the request (milliseconds since epoch).
|
|
static Request? fromJson(Map<String, Object?> result) {
|
|
var id = result[Request.idAttributeName];
|
|
var method = result[Request.methodAttributeName];
|
|
if (id is! String || method is! String) {
|
|
return null;
|
|
}
|
|
var time = result[Request.clientRequestTimeAttributeName];
|
|
if (time is! int?) {
|
|
return null;
|
|
}
|
|
var params = result[Request.paramsAttributeName];
|
|
if (params is Map<String, Object?>?) {
|
|
return Request(id, method, params, time);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An exception that occurred during the handling of a request that requires
|
|
/// that an error be returned to the client.
|
|
///
|
|
/// Clients may not extend, implement or mix-in this class.
|
|
class RequestFailure implements Exception {
|
|
/// The response to be returned as a result of the failure.
|
|
final Response response;
|
|
|
|
/// Initialize a newly created exception to return the given response.
|
|
new(this.response);
|
|
}
|
|
|
|
/// An object that can handle requests and produce responses for them.
|
|
///
|
|
/// Clients may not extend, implement or mix-in this class.
|
|
abstract class RequestHandler {
|
|
/// Attempt to handle the given [request]. If the request is not recognized by
|
|
/// this handler, return `null` so that other handlers will be given a chance
|
|
/// to handle it. Otherwise, return the response that should be passed back to
|
|
/// the client.
|
|
Response? handleRequest(Request request, CancellationToken cancellationToken);
|
|
}
|
|
|
|
/// A request or response that was received from the client.
|
|
///
|
|
/// Clients may not extend, implement or mix-in this class.
|
|
abstract class RequestOrResponse {
|
|
/// The unique identifier associated with this request or response.
|
|
String get id;
|
|
}
|
|
|
|
/// A response to a request.
|
|
///
|
|
/// Clients may not extend, implement or mix-in this class.
|
|
class Response extends RequestOrResponse {
|
|
/// The name of the JSON attribute containing the id of the request for which
|
|
/// this is a response.
|
|
static const String idAttributeName = 'id';
|
|
|
|
/// The name of the JSON attribute containing the error message.
|
|
static const String errorAttributeName = 'error';
|
|
|
|
/// The name of the JSON attribute containing the result values.
|
|
static const String resultAttributeName = 'result';
|
|
|
|
/// The unique identifier used to identify the request that this response is
|
|
/// associated with.
|
|
@override
|
|
final String id;
|
|
|
|
/// The error that was caused by attempting to handle the request, or `null`
|
|
/// if there was no error.
|
|
final RequestError? error;
|
|
|
|
/// A table mapping the names of result fields to their values. Should be
|
|
/// `null` if there is no result to send.
|
|
Map<String, Object?>? result;
|
|
|
|
/// Initialize a newly created instance to represent a response to a request
|
|
/// with the given [id]. If [result] is provided, it will be used as the
|
|
/// result; otherwise an empty result will be used. If an [error] is provided
|
|
/// then the response will represent an error condition.
|
|
new(this.id, {this.result, this.error});
|
|
|
|
/// Initialize a newly created instance to represent the CONTENT_MODIFIED
|
|
/// error condition.
|
|
new contentModified(Request request)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.CONTENT_MODIFIED,
|
|
'File was modified before the operation completed.',
|
|
),
|
|
);
|
|
|
|
/// Create and return the `DEBUG_PORT_COULD_NOT_BE_OPENED` error response.
|
|
new debugPortCouldNotBeOpened(Request request, Object? error)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.DEBUG_PORT_COULD_NOT_BE_OPENED,
|
|
'$error',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent the FILE_NOT_ANALYZED
|
|
/// error condition.
|
|
new fileNotAnalyzed(Request request, String file)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.FILE_NOT_ANALYZED,
|
|
'File is not analyzed: $file.',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent the FORMAT_INVALID_FILE
|
|
/// error condition.
|
|
new formatInvalidFile(Request request)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.FORMAT_INVALID_FILE,
|
|
'Error during `${request.method}`: invalid file.',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent the FORMAT_WITH_ERROR
|
|
/// error condition.
|
|
new formatWithErrors(Request request)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.FORMAT_WITH_ERRORS,
|
|
'Error during `edit.format`: source contains syntax errors.',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent the
|
|
/// GET_ERRORS_INVALID_FILE error condition.
|
|
new getErrorsInvalidFile(Request request)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.GET_ERRORS_INVALID_FILE,
|
|
'Error during `analysis.getErrors`: invalid file.',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent the
|
|
/// GET_FIXES_INVALID_FILE error condition.
|
|
new getFixesInvalidFile(Request request)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.GET_FIXES_INVALID_FILE,
|
|
'Error during `edit.getFixes`: invalid file.',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent the
|
|
/// GET_IMPORTED_ELEMENTS_INVALID_FILE error condition.
|
|
new getImportedElementsInvalidFile(Request request)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.GET_IMPORTED_ELEMENTS_INVALID_FILE,
|
|
'Error during `analysis.getImportedElements`: invalid file.',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent the
|
|
/// GET_NAVIGATION_INVALID_FILE error condition.
|
|
new getNavigationInvalidFile(Request request)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.GET_NAVIGATION_INVALID_FILE,
|
|
'Error during `analysis.getNavigation`: invalid file.',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent the
|
|
/// GET_REACHABLE_SOURCES_INVALID_FILE error condition.
|
|
new getReachableSourcesInvalidFile(Request request)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.GET_REACHABLE_SOURCES_INVALID_FILE,
|
|
'Error during `analysis.getReachableSources`: invalid file.',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent the
|
|
/// GET_SIGNATURE_INVALID_FILE error condition.
|
|
new getSignatureInvalidFile(Request request)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.GET_SIGNATURE_INVALID_FILE,
|
|
'Error during `analysis.getSignature`: invalid file.',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent the
|
|
/// GET_SIGNATURE_INVALID_OFFSET error condition.
|
|
new getSignatureInvalidOffset(Request request)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.GET_SIGNATURE_INVALID_OFFSET,
|
|
'Error during `analysis.getSignature`: invalid offset.',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent the
|
|
/// GET_SIGNATURE_UNKNOWN_FUNCTION error condition.
|
|
new getSignatureUnknownFunction(Request request)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.GET_SIGNATURE_UNKNOWN_FUNCTION,
|
|
'Error during `analysis.getSignature`: unknown function.',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent the
|
|
/// IMPORT_ELEMENTS_INVALID_FILE error condition.
|
|
new importElementsInvalidFile(Request request)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.IMPORT_ELEMENTS_INVALID_FILE,
|
|
'Error during `edit.importElements`: invalid file.',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent an error condition caused
|
|
/// by an analysis.reanalyze [request] that specifies an analysis root that is
|
|
/// not in the current list of analysis roots.
|
|
new invalidAnalysisRoot(Request request, String rootPath)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.INVALID_ANALYSIS_ROOT,
|
|
'Invalid analysis root: $rootPath',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent an error condition caused
|
|
/// by a [request] that specifies an execution context whose context root does
|
|
/// not exist.
|
|
new invalidExecutionContext(Request request, String contextId)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.INVALID_EXECUTION_CONTEXT,
|
|
'Invalid execution context: $contextId',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent the
|
|
/// INVALID_FILE_PATH_FORMAT error condition.
|
|
new invalidFilePathFormat(Request request, Object? path)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.INVALID_FILE_PATH_FORMAT,
|
|
'Invalid file path format: $path',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent an error condition caused
|
|
/// by a [request] that had invalid parameter. [path] is the path to the
|
|
/// invalid parameter, in JavaScript notation (e.g. "foo.bar" means that the
|
|
/// parameter "foo" contained a key "bar" whose value was the wrong type).
|
|
/// [expectation] is a description of the type of data that was expected.
|
|
new invalidParameter(Request request, String path, String expectation)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.INVALID_PARAMETER,
|
|
"Invalid parameter '$path'. $expectation.",
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent an error condition caused
|
|
/// by a malformed request.
|
|
new invalidRequestFormat()
|
|
: this(
|
|
'',
|
|
error: RequestError(
|
|
RequestErrorCode.INVALID_REQUEST,
|
|
'Invalid request',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent the
|
|
/// ORGANIZE_DIRECTIVES_ERROR error condition.
|
|
new organizeDirectivesError(Request request, String message)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.ORGANIZE_DIRECTIVES_ERROR,
|
|
message,
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent the
|
|
/// REFACTORING_REQUEST_CANCELLED error condition.
|
|
new refactoringRequestCancelled(Request request)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.REFACTORING_REQUEST_CANCELLED,
|
|
'The `edit.getRefactoring` request was cancelled.',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent the SERVER_ERROR error
|
|
/// condition.
|
|
factory serverError(Request request, Object? exception, Object? stackTrace) {
|
|
var error = RequestError(
|
|
RequestErrorCode.SERVER_ERROR,
|
|
exception.toString(),
|
|
);
|
|
if (stackTrace != null) {
|
|
error.stackTrace = stackTrace.toString();
|
|
}
|
|
return Response(request.id, error: error);
|
|
}
|
|
|
|
/// Initialize a newly created instance to represent the
|
|
/// SORT_MEMBERS_INVALID_FILE error condition.
|
|
new sortMembersInvalidFile(Request request)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.SORT_MEMBERS_INVALID_FILE,
|
|
'Error during `edit.sortMembers`: invalid file.',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent the
|
|
/// SORT_MEMBERS_PARSE_ERRORS error condition.
|
|
new sortMembersParseErrors(Request request, int numErrors)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.SORT_MEMBERS_PARSE_ERRORS,
|
|
'Error during `edit.sortMembers`: file has $numErrors scan/parse errors.',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent an error condition caused
|
|
/// by a [request] that cannot be handled by any known handlers.
|
|
new unknownRequest(Request request)
|
|
: this(
|
|
request.id,
|
|
error: RequestError(
|
|
RequestErrorCode.UNKNOWN_REQUEST,
|
|
'Unknown request',
|
|
),
|
|
);
|
|
|
|
/// Initialize a newly created instance to represent an error condition caused
|
|
/// by a [request] for a service that is not supported.
|
|
new unsupportedFeature(String requestId, String message)
|
|
: this(
|
|
requestId,
|
|
error: RequestError(RequestErrorCode.UNSUPPORTED_FEATURE, message),
|
|
);
|
|
|
|
/// Return a table representing the structure of the Json object that will be
|
|
/// sent to the client to represent this response.
|
|
Map<String, Object> toJson() => {
|
|
idAttributeName: id,
|
|
errorAttributeName: ?error?.toJson(clientUriConverter: null),
|
|
resultAttributeName: ?result,
|
|
};
|
|
|
|
/// Initialize a newly created instance based on the given JSON data.
|
|
static Response? fromJson(Map<String, Object?> json) {
|
|
try {
|
|
var id = json[Response.idAttributeName];
|
|
if (id is! String) {
|
|
return null;
|
|
}
|
|
|
|
RequestError? decodedError;
|
|
var error = json[Response.errorAttributeName];
|
|
if (error is Map) {
|
|
decodedError = RequestError.fromJson(
|
|
ResponseDecoder(null),
|
|
'.error',
|
|
error,
|
|
clientUriConverter: null,
|
|
);
|
|
}
|
|
|
|
Map<String, Object?>? decodedResult;
|
|
var result = json[Response.resultAttributeName];
|
|
if (result is Map<String, Object?>) {
|
|
decodedResult = result;
|
|
}
|
|
|
|
return Response(id, error: decodedError, result: decodedResult);
|
|
} catch (exception) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|