68f0d0e3a9
This moves the check for whether we need to apply edits sequentially to above some of the validation checks (because they are only applicable if not applying sequentially) and tweaks the logic slightly. It also removes a third copy of this function in analysis_server that was only used in one test, so we're down to two copies now (analyzer_plugin and analysis_server_client). Fixes https://github.com/dart-lang/sdk/issues/61501 Fixes https://github.com/dart-lang/sdk/issues/61488 Change-Id: I89ac94e8fd65e904c4eddcd8f1ebba3bf1293487 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/450380 Commit-Queue: Brian Wilkerson <brianwilkerson@google.com> Reviewed-by: Samuel Rawlins <srawlins@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
314 lines
8.1 KiB
Dart
314 lines
8.1 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.
|
|
|
|
import 'dart:collection';
|
|
import 'dart:convert' hide JsonDecoder;
|
|
|
|
import 'package:analysis_server/protocol/protocol.dart';
|
|
import 'package:analysis_server/protocol/protocol_generated.dart';
|
|
import 'package:analyzer_plugin/protocol/protocol_common.dart';
|
|
import 'package:analyzer_plugin/src/protocol/protocol_internal.dart'
|
|
show JsonDecoder;
|
|
import 'package:analyzer_plugin/src/utilities/client_uri_converter.dart';
|
|
|
|
export 'package:analyzer_plugin/src/protocol/protocol_internal.dart'
|
|
show JsonDecoder;
|
|
|
|
// Ignored for legacy code reasons; this variable is used across a few packages.
|
|
// ignore: non_constant_identifier_names
|
|
final Map<String, RefactoringKind> REQUEST_ID_REFACTORING_KINDS =
|
|
HashMap<String, RefactoringKind>();
|
|
|
|
/// Compare the lists [listA] and [listB], using [itemEqual] to compare
|
|
/// list elements.
|
|
bool listEqual<T>(
|
|
List<T>? listA,
|
|
List<T>? listB,
|
|
bool Function(T a, T b) itemEqual,
|
|
) {
|
|
if (listA == null) {
|
|
return listB == null;
|
|
}
|
|
if (listB == null) {
|
|
return false;
|
|
}
|
|
if (listA.length != listB.length) {
|
|
return false;
|
|
}
|
|
for (var i = 0; i < listA.length; i++) {
|
|
if (!itemEqual(listA[i], listB[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Compare the maps [mapA] and [mapB], using [valueEqual] to compare map
|
|
/// values.
|
|
bool mapEqual<K, V>(
|
|
Map<K, V>? mapA,
|
|
Map<K, V>? mapB,
|
|
bool Function(V a, V b) valueEqual,
|
|
) {
|
|
if (mapA == null) {
|
|
return mapB == null;
|
|
}
|
|
if (mapB == null) {
|
|
return false;
|
|
}
|
|
if (mapA.length != mapB.length) {
|
|
return false;
|
|
}
|
|
for (var entryA in mapA.entries) {
|
|
var key = entryA.key;
|
|
var valueA = entryA.value;
|
|
var valueB = mapB[key];
|
|
if (valueB == null || !valueEqual(valueA, valueB)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Translate the input [map], applying [keyCallback] to all its keys, and
|
|
/// [valueCallback] to all its values.
|
|
Map<KR, VR> mapMap<KP, VP, KR, VR>(
|
|
Map<KP, VP> map, {
|
|
KR Function(KP key)? keyCallback,
|
|
VR Function(VP value)? valueCallback,
|
|
}) {
|
|
var result = <KR, VR>{};
|
|
map.forEach((key, value) {
|
|
KR resultKey;
|
|
VR resultValue;
|
|
if (keyCallback != null) {
|
|
resultKey = keyCallback(key);
|
|
} else {
|
|
resultKey = key as KR;
|
|
}
|
|
if (valueCallback != null) {
|
|
resultValue = valueCallback(value);
|
|
} else {
|
|
resultValue = value as VR;
|
|
}
|
|
result[resultKey] = resultValue;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
/// Create a [RefactoringFeedback] corresponding the given [kind].
|
|
RefactoringFeedback? refactoringFeedbackFromJson(
|
|
JsonDecoder jsonDecoder,
|
|
String jsonPath,
|
|
Object? json,
|
|
Map<Object?, Object?> feedbackJson, {
|
|
required ClientUriConverter? clientUriConverter,
|
|
}) {
|
|
var kind = jsonDecoder.refactoringKind;
|
|
if (kind == RefactoringKind.EXTRACT_LOCAL_VARIABLE) {
|
|
return ExtractLocalVariableFeedback.fromJson(
|
|
jsonDecoder,
|
|
jsonPath,
|
|
json,
|
|
clientUriConverter: clientUriConverter,
|
|
);
|
|
}
|
|
if (kind == RefactoringKind.EXTRACT_METHOD) {
|
|
return ExtractMethodFeedback.fromJson(
|
|
jsonDecoder,
|
|
jsonPath,
|
|
json,
|
|
clientUriConverter: clientUriConverter,
|
|
);
|
|
}
|
|
if (kind == RefactoringKind.EXTRACT_WIDGET) {
|
|
return ExtractWidgetFeedback.fromJson(
|
|
jsonDecoder,
|
|
jsonPath,
|
|
json,
|
|
clientUriConverter: clientUriConverter,
|
|
);
|
|
}
|
|
if (kind == RefactoringKind.INLINE_LOCAL_VARIABLE) {
|
|
return InlineLocalVariableFeedback.fromJson(
|
|
jsonDecoder,
|
|
jsonPath,
|
|
json,
|
|
clientUriConverter: clientUriConverter,
|
|
);
|
|
}
|
|
if (kind == RefactoringKind.INLINE_METHOD) {
|
|
return InlineMethodFeedback.fromJson(
|
|
jsonDecoder,
|
|
jsonPath,
|
|
json,
|
|
clientUriConverter: clientUriConverter,
|
|
);
|
|
}
|
|
if (kind == RefactoringKind.RENAME) {
|
|
return RenameFeedback.fromJson(
|
|
jsonDecoder,
|
|
jsonPath,
|
|
json,
|
|
clientUriConverter: clientUriConverter,
|
|
);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Create a [RefactoringOptions] corresponding the given [kind].
|
|
RefactoringOptions? refactoringOptionsFromJson(
|
|
JsonDecoder jsonDecoder,
|
|
String jsonPath,
|
|
Object? json,
|
|
RefactoringKind kind, {
|
|
required ClientUriConverter? clientUriConverter,
|
|
}) {
|
|
if (kind == RefactoringKind.EXTRACT_LOCAL_VARIABLE) {
|
|
return ExtractLocalVariableOptions.fromJson(
|
|
jsonDecoder,
|
|
jsonPath,
|
|
json,
|
|
clientUriConverter: clientUriConverter,
|
|
);
|
|
}
|
|
if (kind == RefactoringKind.EXTRACT_METHOD) {
|
|
return ExtractMethodOptions.fromJson(
|
|
jsonDecoder,
|
|
jsonPath,
|
|
json,
|
|
clientUriConverter: clientUriConverter,
|
|
);
|
|
}
|
|
if (kind == RefactoringKind.EXTRACT_WIDGET) {
|
|
return ExtractWidgetOptions.fromJson(
|
|
jsonDecoder,
|
|
jsonPath,
|
|
json,
|
|
clientUriConverter: clientUriConverter,
|
|
);
|
|
}
|
|
if (kind == RefactoringKind.INLINE_METHOD) {
|
|
return InlineMethodOptions.fromJson(
|
|
jsonDecoder,
|
|
jsonPath,
|
|
json,
|
|
clientUriConverter: clientUriConverter,
|
|
);
|
|
}
|
|
if (kind == RefactoringKind.MOVE_FILE) {
|
|
return MoveFileOptions.fromJson(
|
|
jsonDecoder,
|
|
jsonPath,
|
|
json,
|
|
clientUriConverter: clientUriConverter,
|
|
);
|
|
}
|
|
if (kind == RefactoringKind.RENAME) {
|
|
return RenameOptions.fromJson(
|
|
jsonDecoder,
|
|
jsonPath,
|
|
json,
|
|
clientUriConverter: clientUriConverter,
|
|
);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Instances of the class [HasToJson] implement [toJson] method that returns
|
|
/// a JSON presentation.
|
|
abstract class HasToJson {
|
|
/// Returns a JSON presentation of the object.
|
|
Map<String, Object> toJson({required ClientUriConverter? clientUriConverter});
|
|
}
|
|
|
|
/// JsonDecoder for decoding requests. Errors are reporting by throwing a
|
|
/// [RequestFailure].
|
|
class RequestDecoder extends JsonDecoder {
|
|
/// The request being deserialized.
|
|
final Request _request;
|
|
|
|
RequestDecoder(this._request);
|
|
|
|
@override
|
|
RefactoringKind? get refactoringKind {
|
|
// Refactoring feedback objects should never appear in requests.
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
Object mismatch(String jsonPath, String expected, [Object? actual]) {
|
|
var buffer = StringBuffer();
|
|
buffer.write('Expected to be ');
|
|
buffer.write(expected);
|
|
if (actual != null) {
|
|
buffer.write('; found "');
|
|
buffer.write(json.encode(actual));
|
|
buffer.write('"');
|
|
}
|
|
return RequestFailure(
|
|
Response.invalidParameter(_request, jsonPath, buffer.toString()),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Object missingKey(String jsonPath, String key) {
|
|
return RequestFailure(
|
|
Response.invalidParameter(
|
|
_request,
|
|
jsonPath,
|
|
'Expected to contain key ${json.encode(key)}',
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
abstract class RequestParams implements HasToJson {
|
|
/// Return a request whose parameters are taken from this object and that has
|
|
/// the given [id].
|
|
Request toRequest(
|
|
String id, {
|
|
required ClientUriConverter? clientUriConverter,
|
|
});
|
|
}
|
|
|
|
/// JsonDecoder for decoding responses from the server. This is intended to be
|
|
/// used only for testing. Errors are reported using bare [Exception] objects.
|
|
class ResponseDecoder extends JsonDecoder {
|
|
@override
|
|
final RefactoringKind? refactoringKind;
|
|
|
|
ResponseDecoder(this.refactoringKind);
|
|
|
|
@override
|
|
Object mismatch(String jsonPath, String expected, [Object? actual]) {
|
|
var buffer = StringBuffer();
|
|
buffer.write('Expected ');
|
|
buffer.write(expected);
|
|
if (actual != null) {
|
|
buffer.write(' found "');
|
|
buffer.write(json.encode(actual));
|
|
buffer.write('"');
|
|
}
|
|
buffer.write(' at ');
|
|
buffer.write(jsonPath);
|
|
return Exception(buffer.toString());
|
|
}
|
|
|
|
@override
|
|
Object missingKey(String jsonPath, String key) {
|
|
return Exception('Missing key $key at $jsonPath');
|
|
}
|
|
}
|
|
|
|
/// The result data associated with a response.
|
|
abstract class ResponseResult implements HasToJson {
|
|
/// Return a response whose result data is this object for the request with
|
|
/// the given [id].
|
|
Response toResponse(
|
|
String id, {
|
|
required ClientUriConverter? clientUriConverter,
|
|
});
|
|
}
|