Files
sdk/third_party/pkg/language_server_protocol/lib/protocol_special.dart
T
Danny Tuppeny 2b88f4fea8 [analysis_server] Support String IDs in log replay + run some LSP integration tests with string IDs
My previous CL made some LSP tests run with String IDs instead of ints, but those tests don't appear to go through the session logger so did not fail with the casts here.

This change adds a base integration test that also uses string IDs, which did fail on the cast, so I've updated the session logger to use `Either2<int, String>` for IDs instead.

Fixes https://github.com/dart-lang/sdk/issues/62442

Change-Id: Iee582e9ce2b8b5a1127120c987670a679d2ca76c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/473260
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Jake Macdonald <jakemac@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
2026-01-21 12:24:27 -08:00

238 lines
6.0 KiB
Dart

// Copyright (c) 2018, 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:collection/collection.dart';
import 'package:language_server_protocol/json_parsing.dart';
import 'package:language_server_protocol/protocol_custom_generated.dart';
import 'package:language_server_protocol/protocol_generated.dart';
const jsonRpcVersion = '2.0';
const nullJsonHandler = LspJsonHandler<void>(_alwaysTrue, _alwaysNull);
/// Returns if two objects are equal, recursively checking items in
/// Maps/Lists.
bool lspEquals(dynamic obj1, dynamic obj2) {
return const DeepCollectionEquality().equals(obj1, obj2);
}
/// Returns an objects hash code, recursively combining hashes for items in
/// Maps/Lists.
int lspHashCode(dynamic obj) {
return const DeepCollectionEquality().hash(obj);
}
Object? specToJson(Object? obj) {
if (obj is ToJsonable) {
return obj.toJson();
} else if (obj is List) {
return List.generate(
obj.length,
(index) => specToJson(obj[index]),
);
} else {
return obj;
}
}
void _alwaysNull(dynamic _, [dynamic __]) {}
bool _alwaysTrue(dynamic _, [dynamic __]) => true;
/// An LSP Code Action, which can be either a [CodeActionLiteral] (if supported
/// by the client) that contains a [Command] and/or an inline edit, or just a
/// [Command] itself.
typedef CodeAction = Either2<CodeActionLiteral, Command>;
typedef DocumentChanges
= List<Either4<CreateFile, DeleteFile, RenameFile, TextDocumentEdit>>;
class Either2<T1, T2> implements ToJsonable {
final int _which;
final T1? _t1;
final T2? _t2;
const Either2.t1(T1 this._t1)
: _t2 = null,
_which = 1;
const Either2.t2(T2 this._t2)
: _t1 = null,
_which = 2;
@override
int get hashCode => map(lspHashCode, lspHashCode);
@override
bool operator ==(other) =>
other is Either2<T1, T2> &&
other._which == _which &&
lspEquals(other._t1, _t1) &&
lspEquals(other._t2, _t2);
T map<T>(T Function(T1) f1, T Function(T2) f2) {
return _which == 1 ? f1(_t1 as T1) : f2(_t2 as T2);
}
@override
Object? toJson() => map(specToJson, specToJson);
@override
String toString() => map((t) => t.toString(), (t) => t.toString());
/// Checks whether the value of the union equals the supplied value.
bool valueEquals(Object o) => map((t) => t == o, (t) => t == o);
}
class Either3<T1, T2, T3> implements ToJsonable {
final int _which;
final T1? _t1;
final T2? _t2;
final T3? _t3;
Either3.t1(this._t1)
: _t2 = null,
_t3 = null,
_which = 1;
Either3.t2(this._t2)
: _t1 = null,
_t3 = null,
_which = 2;
Either3.t3(this._t3)
: _t1 = null,
_t2 = null,
_which = 3;
@override
int get hashCode => map(lspHashCode, lspHashCode, lspHashCode);
@override
bool operator ==(other) =>
other is Either3<T1, T2, T3> &&
other._which == _which &&
lspEquals(other._t1, _t1) &&
lspEquals(other._t2, _t2) &&
lspEquals(other._t3, _t3);
T map<T>(T Function(T1) f1, T Function(T2) f2, T Function(T3) f3) {
return switch (_which) {
1 => f1(_t1 as T1),
2 => f2(_t2 as T2),
3 => f3(_t3 as T3),
_ => throw 'Invalid state.'
};
}
@override
Object? toJson() => map(specToJson, specToJson, specToJson);
@override
String toString() => map(
(t) => t.toString(),
(t) => t.toString(),
(t) => t.toString(),
);
/// Checks whether the value of the union equals the supplied value.
bool valueEquals(Object o) =>
map((t) => t == o, (t) => t == o, (t) => t == o);
}
class Either4<T1, T2, T3, T4> implements ToJsonable {
final int _which;
final T1? _t1;
final T2? _t2;
final T3? _t3;
final T4? _t4;
Either4.t1(this._t1)
: _t2 = null,
_t3 = null,
_t4 = null,
_which = 1;
Either4.t2(this._t2)
: _t1 = null,
_t3 = null,
_t4 = null,
_which = 2;
Either4.t3(this._t3)
: _t1 = null,
_t2 = null,
_t4 = null,
_which = 3;
Either4.t4(this._t4)
: _t1 = null,
_t2 = null,
_t3 = null,
_which = 4;
@override
int get hashCode => map(lspHashCode, lspHashCode, lspHashCode, lspHashCode);
@override
bool operator ==(other) =>
other is Either4<T1, T2, T3, T4> &&
other._which == _which &&
lspEquals(other._t1, _t1) &&
lspEquals(other._t2, _t2) &&
lspEquals(other._t3, _t3) &&
lspEquals(other._t4, _t4);
T map<T>(T Function(T1) f1, T Function(T2) f2, T Function(T3) f3,
T Function(T4) f4) {
return switch (_which) {
1 => f1(_t1 as T1),
2 => f2(_t2 as T2),
3 => f3(_t3 as T3),
4 => f4(_t4 as T4),
_ => throw 'Invalid state.'
};
}
@override
Object? toJson() => map(specToJson, specToJson, specToJson, specToJson);
@override
String toString() => map(
(t) => t.toString(),
(t) => t.toString(),
(t) => t.toString(),
(t) => t.toString(),
);
/// Checks whether the value of the union equals the supplied value.
bool valueEquals(Object o) =>
map((t) => t == o, (t) => t == o, (t) => t == o, (t) => t == o);
}
/// A helper to allow handlers to declare both a JSON validation function and
/// parse function.
class LspJsonHandler<T> {
final bool Function(Map<String, Object?>?, LspJsonReporter reporter)
validateParams;
final T Function(Map<String, Object?>) convertParams;
const LspJsonHandler(this.validateParams, this.convertParams);
}
abstract class ToJsonable {
Object? toJson();
}
extension IncomingMessageExtension on IncomingMessage {
/// 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;
}
}