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>
136 lines
4.5 KiB
Dart
136 lines
4.5 KiB
Dart
// Copyright (c) 2026, 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/session_logger/log_entry.dart';
|
|
import 'package:collection/collection.dart';
|
|
|
|
/// An [Equality] for [Message]s.
|
|
///
|
|
/// Similar to [DeepCollectionEquality] but with some customizations:
|
|
///
|
|
/// - Only supports JSON objects
|
|
/// - Supports ignoring specific map keys (applies to all nested maps)
|
|
/// - Uses unordered list comparisons for lists that are not made of ints.
|
|
/// - Supports conditionally skipping the top level `id` entry.
|
|
class MessageEquality implements Equality<Message> {
|
|
final _CustomDeepCollectionEquality _recursiveEquality;
|
|
|
|
new({Set<String> ignoredKeys = const {}})
|
|
: _recursiveEquality = _CustomDeepCollectionEquality(ignoredKeys);
|
|
|
|
/// If [skipMatchId] is `true`, then the top level `id` field is ignored
|
|
/// during comparison.
|
|
@override
|
|
bool equals(Message a, Message b, {bool skipMatchId = false}) {
|
|
if (a.method != b.method) return false;
|
|
if (!skipMatchId && a.id != b.id) return false;
|
|
return switch (a.method) {
|
|
// No method means this is a response, compare the result.
|
|
null => _recursiveEquality.equals(a.result, b.result, path: 'result'),
|
|
// A method means this is a request, compare the params.
|
|
String() => _recursiveEquality.equals(a.params, b.params, path: 'params'),
|
|
};
|
|
}
|
|
|
|
@override
|
|
int hash(Message e) {
|
|
return _recursiveEquality.hash(e.map);
|
|
}
|
|
|
|
@override
|
|
bool isValidKey(Object? o) => o is Message;
|
|
}
|
|
|
|
/// Implementation for checking equality of the `params` and `result` fields
|
|
/// of [Message]s.
|
|
class _CustomDeepCollectionEquality implements Equality<Object?> {
|
|
final Set<String> _ignoredKeys;
|
|
late final _orderedListEquality = ListEquality<Object?>(this);
|
|
late final _unorderedListEquality = UnorderedIterableEquality<Object?>(this);
|
|
|
|
new(this._ignoredKeys);
|
|
|
|
@override
|
|
bool equals(Object? e1, Object? e2, {String? path}) {
|
|
if (identical(e1, e2)) return true;
|
|
|
|
if (e1 is Map<String, Object?> && e2 is Map<String, Object?>) {
|
|
return _mapEquals(e1, e2, path);
|
|
} else if (e1 is List<Object?> && e2 is List<Object?>) {
|
|
if (e1.every((entry) => entry is int)) {
|
|
return _orderedListEquality.equals(e1, e2);
|
|
}
|
|
return _unorderedListEquality.equals(e1, e2);
|
|
}
|
|
|
|
return const DefaultEquality().equals(e1, e2);
|
|
}
|
|
|
|
@override
|
|
int hash(Object? e) {
|
|
if (e is Map<String, Object?>) {
|
|
return _mapHash(e);
|
|
} else if (e is Iterable<Object?>) {
|
|
return _unorderedListEquality.hash(e);
|
|
}
|
|
return const DefaultEquality().hash(e);
|
|
}
|
|
|
|
@override
|
|
bool isValidKey(Object? o) => true;
|
|
|
|
/// Joins an optional [parentPath] and child [key].
|
|
String _childPath(String? parentPath, String key) =>
|
|
parentPath != null ? '$parentPath.$key' : key;
|
|
|
|
/// Checks if [key] is an ignored field when it exists as a child in a path
|
|
/// of [parentPath].
|
|
///
|
|
/// Paths can be partial, so an ignored path of 'b.c' will ignore a field
|
|
/// at 'a.b.c'.
|
|
bool _isIgnoredPath(String? parentPath, String key) {
|
|
var actualPath = _childPath(parentPath, key);
|
|
return _ignoredKeys.any(
|
|
(ignoredKey) =>
|
|
actualPath == ignoredKey || actualPath.endsWith('.$ignoredKey'),
|
|
);
|
|
}
|
|
|
|
/// Checks if [e1] and [e2] are equal, taking into account any ignored paths.
|
|
///
|
|
/// If these maps are not from the top level, [path] is the qualified path to
|
|
/// them.
|
|
bool _mapEquals(
|
|
Map<String, Object?> e1,
|
|
Map<String, Object?> e2,
|
|
String? path,
|
|
) {
|
|
var keys1 = e1.keys.where((key) => !_isIgnoredPath(path, key)).toSet();
|
|
var keys2 = e2.keys.where((key) => !_isIgnoredPath(path, key)).toSet();
|
|
|
|
if (keys1.length != keys2.length) return false;
|
|
|
|
for (var key in keys1) {
|
|
if (!keys2.contains(key)) return false;
|
|
var childPath = _childPath(path, key);
|
|
if (!equals(e1[key], e2[key], path: childPath)) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// A relatively weak but cheap underdered hash, which is important.
|
|
///
|
|
/// We cannot use [MapEquality] because it doesn't support ignoring keys.
|
|
int _mapHash(Map<String, Object?> e) {
|
|
var resultMapHash = 0;
|
|
for (var key in e.keys) {
|
|
if (_ignoredKeys.contains(key)) continue;
|
|
resultMapHash = resultMapHash ^ key.hashCode;
|
|
resultMapHash = resultMapHash ^ hash(e[key]);
|
|
}
|
|
return resultMapHash;
|
|
}
|
|
}
|