// 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 'dart:async'; import 'package:analysis_server/lsp_protocol/protocol_generated.dart'; import 'package:analysis_server/src/lsp/json_parsing.dart'; import 'package:analysis_server/src/protocol/protocol_internal.dart'; import 'package:analyzer/src/generated/utilities_general.dart'; const jsonRpcVersion = '2.0'; const NullJsonHandler = LspJsonHandler(_alwaysTrue, _alwaysNull); ErrorOr cancelled() => error(ErrorCodes.RequestCancelled, 'Request was cancelled', null); ErrorOr error(ErrorCodes code, String message, [String? data]) => ErrorOr.error(ResponseError(code: code, message: message, data: data)); ErrorOr failure(ErrorOr error) => ErrorOr.error(error.error); /// Returns if two objects are equal, recursively checking items in /// Maps/Lists. bool lspEquals(dynamic obj1, dynamic obj2) { if (obj1 is List && obj2 is List) { return listEqual(obj1, obj2, lspEquals); } else if (obj1 is Map && obj2 is Map) { return mapEqual(obj1, obj2, lspEquals); } else { return obj1.runtimeType == obj2.runtimeType && obj1 == obj2; } } /// Returns an objects hash code, recursively combining hashes for items in /// Maps/Lists. int lspHashCode(dynamic obj) { var hash = 0; if (obj is List) { for (var element in obj) { hash = JenkinsSmiHash.combine(hash, lspHashCode(element)); } } else if (obj is Map) { for (var key in obj.keys) { hash = JenkinsSmiHash.combine(hash, lspHashCode(key)); hash = JenkinsSmiHash.combine(hash, lspHashCode(obj[key])); } } else { hash = obj.hashCode; } return JenkinsSmiHash.finish(hash); } Object? specToJson(Object? obj) { if (obj is ToJsonable) { return obj.toJson(); } else { return obj; } } ErrorOr success(R t) => ErrorOr.success(t); Null _alwaysNull(_, [__]) => null; bool _alwaysTrue(_, [__]) => true; class Either2 { final int _which; final T1? _t1; final T2? _t2; Either2.t1(T1 this._t1) : _t2 = null, _which = 1; Either2.t2(T2 this._t2) : _t1 = null, _which = 2; @override int get hashCode => map(lspHashCode, lspHashCode); @override bool operator ==(o) => o is Either2 && lspEquals(o._t1, _t1) && lspEquals(o._t2, _t2); T map(T Function(T1) f1, T Function(T2) f2) { return _which == 1 ? f1(_t1 as T1) : f2(_t2 as T2); } 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(o) => map((t) => t == o, (t) => t == o); } class Either3 { 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 ==(o) => o is Either3 && lspEquals(o._t1, _t1) && lspEquals(o._t2, _t2) && lspEquals(o._t3, _t3); T map(T Function(T1) f1, T Function(T2) f2, T Function(T3) f3) { switch (_which) { case 1: return f1(_t1 as T1); case 2: return f2(_t2 as T2); case 3: return f3(_t3 as T3); default: throw 'Invalid state.'; } } 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(o) => map((t) => t == o, (t) => t == o, (t) => t == o); } class Either4 { 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 ==(o) => o is Either4 && lspEquals(o._t1, _t1) && lspEquals(o._t2, _t2) && lspEquals(o._t3, _t3) && lspEquals(o._t4, _t4); T map(T Function(T1) f1, T Function(T2) f2, T Function(T3) f3, T Function(T4) f4) { switch (_which) { case 1: return f1(_t1 as T1); case 2: return f2(_t2 as T2); case 3: return f3(_t3 as T3); case 4: return f4(_t4 as T4); default: throw 'Invalid state.'; } } 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(o) => map((t) => t == o, (t) => t == o, (t) => t == o, (t) => t == o); } class ErrorOr extends Either2 { ErrorOr.error(ResponseError error) : super.t1(error); ErrorOr.success(T result) : super.t2(result); /// Returns the error or throws if object is not an error. Check [isError] /// before accessing [error]. ResponseError get error { return _which == 1 ? _t1 as ResponseError : (throw 'Value is not an error'); } /// Returns true if this object is an error, false if it is a result. Prefer /// [mapResult] instead of checking this flag if [errors] will simply be /// propagated as-is. bool get isError => _which == 1; /// Returns the result or throws if this object is an error. Check [isError] /// before accessing [result]. It is valid for this to return null is the /// object does not represent an error but the resulting value was null. T get result { return _which == 2 ? _t2 as T : (throw 'Value is not a result'); } /// If this object is a result, maps [result] through [f], otherwise returns /// a new error object representing [error]. FutureOr> mapResult(FutureOr> Function(T) f) { return isError // Re-wrap the error using our new type arg ? ErrorOr.error(error) // Otherwise call the map function : f(result); } } /// A base class containing the fields common to RequestMessage and /// NotificationMessage to simplify handling. abstract class IncomingMessage { Method get method; dynamic get params; } /// A helper to allow handlers to declare both a JSON validation function and /// parse function. class LspJsonHandler { final bool Function(Map?, LspJsonReporter reporter) validateParams; final T Function(Map) convertParams; const LspJsonHandler(this.validateParams, this.convertParams); } abstract class ToJsonable { Object toJson(); }