chore: adopt Dart 3.9 toolchain — honest floor + tall-style reformat (T-353)
Raise the declared minimums in pubspec.yaml to what our deps already require: Flutter >=3.35.0 / Dart >=3.9.0 (was 3.19.0 / 3.5.0). alchemist 0.12 needs Flutter 3.32; Dart 3.9 first ships in Flutter 3.35, so 3.35 is the binding floor. Pin the exact build toolchain in .fvmrc (Flutter 3.44.1). Moving to the Dart 3.9 language level switches `dart format` to the new "tall" style and enables two new lints. This commit is the resulting mechanical churn, isolated from any behaviour change: - whole-tree `dart format` reformat (tall style) - `dart fix` for unnecessary_underscores + use_null_aware_elements No runtime behaviour change; `make test` green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -109,7 +109,10 @@ void main() {
|
||||
group('normalize (argv shape → named args)', () {
|
||||
const schema = CommandSchema(
|
||||
positional: ['slot'],
|
||||
args: {'slot': ArgSpec(), 'to': ArgSpec(type: ArgType.number)},
|
||||
args: {
|
||||
'slot': ArgSpec(),
|
||||
'to': ArgSpec(type: ArgType.number),
|
||||
},
|
||||
);
|
||||
|
||||
test('positional[i] maps to the i-th declared name', () {
|
||||
|
||||
@@ -4,11 +4,7 @@ import 'package:test/test.dart';
|
||||
void main() {
|
||||
group('IpcRequest', () {
|
||||
test('encodes and decodes round-trip', () {
|
||||
final req = IpcRequest(
|
||||
id: '42',
|
||||
cmd: 'git.status',
|
||||
args: const {'path': '.'},
|
||||
);
|
||||
final req = IpcRequest(id: '42', cmd: 'git.status', args: const {'path': '.'});
|
||||
final line = req.encode();
|
||||
final decoded = IpcMessage.decode(line);
|
||||
expect(decoded, isA<IpcRequest>());
|
||||
@@ -49,32 +45,18 @@ void main() {
|
||||
test('encodes with error payload, no data', () {
|
||||
final r = IpcResponse.err(
|
||||
id: '9',
|
||||
error: IpcError(
|
||||
code: IpcExitCode.notFound,
|
||||
kind: IpcErrorKind.notFound,
|
||||
message: 'missing',
|
||||
hint: 'try --help',
|
||||
),
|
||||
error: IpcError(code: IpcExitCode.notFound, kind: IpcErrorKind.notFound, message: 'missing', hint: 'try --help'),
|
||||
);
|
||||
final json = r.toJson();
|
||||
expect(json['ok'], false);
|
||||
expect(json['error'], {
|
||||
'code': 3,
|
||||
'kind': 'not_found',
|
||||
'message': 'missing',
|
||||
'hint': 'try --help',
|
||||
});
|
||||
expect(json['error'], {'code': 3, 'kind': 'not_found', 'message': 'missing', 'hint': 'try --help'});
|
||||
expect(json.containsKey('data'), isFalse);
|
||||
});
|
||||
|
||||
test('decode preserves error fields', () {
|
||||
final original = IpcResponse.err(
|
||||
id: '9',
|
||||
error: IpcError(
|
||||
code: IpcExitCode.conflict,
|
||||
kind: IpcErrorKind.conflict,
|
||||
message: 'race',
|
||||
),
|
||||
error: IpcError(code: IpcExitCode.conflict, kind: IpcErrorKind.conflict, message: 'race'),
|
||||
);
|
||||
final r = IpcMessage.decode(original.encode()) as IpcResponse;
|
||||
expect(r.ok, false);
|
||||
@@ -89,12 +71,7 @@ void main() {
|
||||
group('IpcEvent', () {
|
||||
test('serializes subsystem + kind + ts + data', () {
|
||||
final ts = DateTime.utc(2026, 4, 21, 12, 0, 0);
|
||||
final e = IpcEvent(
|
||||
subsystem: 'git',
|
||||
kind: 'status-changed',
|
||||
timestamp: ts,
|
||||
data: const {'staged': 3},
|
||||
);
|
||||
final e = IpcEvent(subsystem: 'git', kind: 'status-changed', timestamp: ts, data: const {'staged': 3});
|
||||
final json = e.toJson();
|
||||
expect(json['type'], 'event');
|
||||
expect(json['subsystem'], 'git');
|
||||
@@ -110,10 +87,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('throws on unknown type', () {
|
||||
expect(
|
||||
() => IpcMessage.decode('{"type":"bogus","id":"1"}'),
|
||||
throwsA(isA<FormatException>()),
|
||||
);
|
||||
expect(() => IpcMessage.decode('{"type":"bogus","id":"1"}'), throwsA(isA<FormatException>()));
|
||||
});
|
||||
|
||||
test('throws on malformed JSON', () {
|
||||
|
||||
@@ -20,11 +20,7 @@ void main() {
|
||||
|
||||
setUp(() async {
|
||||
discoveryDir = await Directory.systemTemp.createTemp('clide-mcp-disc-');
|
||||
server = McpServer(
|
||||
workspaceRoot: '/var/mnt/test/clide-fixture',
|
||||
log: _silent(),
|
||||
discoveryDirOverride: discoveryDir.path,
|
||||
);
|
||||
server = McpServer(workspaceRoot: '/var/mnt/test/clide-fixture', log: _silent(), discoveryDirOverride: discoveryDir.path);
|
||||
await server.start();
|
||||
});
|
||||
|
||||
@@ -155,7 +151,7 @@ void main() {
|
||||
'method': 'tools/call',
|
||||
'params': {
|
||||
'name': 'mcp__ide__executeCode',
|
||||
'arguments': {'code': 'print(1)'}
|
||||
'arguments': {'code': 'print(1)'},
|
||||
},
|
||||
});
|
||||
final reply = jsonDecode(await replyFuture.timeout(const Duration(seconds: 2))) as Map<String, Object?>;
|
||||
@@ -214,12 +210,7 @@ void main() {
|
||||
dispatcher.register('echo', (req) async => IpcResponse.ok(id: req.id, data: {'echo': req.args['text']}));
|
||||
// A poor MCP fit — withheld from the tool surface (D-86).
|
||||
dispatcher.register('pane.tail', (req) async => IpcResponse.ok(id: req.id, data: const {}), mcpExpose: false);
|
||||
srv = McpServer(
|
||||
workspaceRoot: '/x',
|
||||
log: _silent(),
|
||||
discoveryDirOverride: disc.path,
|
||||
dispatcher: dispatcher,
|
||||
);
|
||||
srv = McpServer(workspaceRoot: '/x', log: _silent(), discoveryDirOverride: disc.path, dispatcher: dispatcher);
|
||||
await srv.start();
|
||||
});
|
||||
|
||||
@@ -234,8 +225,12 @@ void main() {
|
||||
final client = HttpClient();
|
||||
addTearDown(client.close);
|
||||
final resp = await (await client.getUrl(Uri.parse('http://127.0.0.1:${srv.port}/sse'))).close();
|
||||
final dataLines =
|
||||
resp.transform(utf8.decoder).transform(const LineSplitter()).where((l) => l.startsWith('data: ')).map((l) => l.substring(6)).asBroadcastStream();
|
||||
final dataLines = resp
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.where((l) => l.startsWith('data: '))
|
||||
.map((l) => l.substring(6))
|
||||
.asBroadcastStream();
|
||||
final endpoint = Completer<String>();
|
||||
final sub = dataLines.listen((d) {
|
||||
final m = RegExp(r'sessionId=([\w-]+)').firstMatch(d);
|
||||
@@ -312,8 +307,12 @@ void main() {
|
||||
final client = HttpClient();
|
||||
addTearDown(client.close);
|
||||
final resp = await (await client.getUrl(Uri.parse('http://127.0.0.1:${srv.port}/sse'))).close();
|
||||
final data =
|
||||
resp.transform(utf8.decoder).transform(const LineSplitter()).where((l) => l.startsWith('data: ')).map((l) => l.substring(6)).asBroadcastStream();
|
||||
final data = resp
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.where((l) => l.startsWith('data: '))
|
||||
.map((l) => l.substring(6))
|
||||
.asBroadcastStream();
|
||||
final ep = Completer<String>();
|
||||
final sub = data.listen((d) {
|
||||
final m = RegExp(r'sessionId=([\w-]+)').firstMatch(d);
|
||||
@@ -324,12 +323,14 @@ void main() {
|
||||
final replyFuture = data.firstWhere((s) => s.contains('"id":13'));
|
||||
final post = await client.postUrl(Uri.parse('http://127.0.0.1:${srv.port}/messages?sessionId=$sid'));
|
||||
post.headers.contentType = ContentType.json;
|
||||
post.write(jsonEncode({
|
||||
'jsonrpc': '2.0',
|
||||
'id': 13,
|
||||
'method': 'tools/call',
|
||||
'params': {'name': 'mcp__clide__echo', 'arguments': const {}},
|
||||
}));
|
||||
post.write(
|
||||
jsonEncode({
|
||||
'jsonrpc': '2.0',
|
||||
'id': 13,
|
||||
'method': 'tools/call',
|
||||
'params': {'name': 'mcp__clide__echo', 'arguments': const {}},
|
||||
}),
|
||||
);
|
||||
await post.close();
|
||||
final reply = jsonDecode(await replyFuture.timeout(const Duration(seconds: 2))) as Map<String, Object?>;
|
||||
expect((reply['result'] as Map)['isError'], isTrue);
|
||||
|
||||
@@ -15,17 +15,11 @@ void main() {
|
||||
});
|
||||
|
||||
test('is deterministic for the same input', () {
|
||||
expect(
|
||||
workspaceSocketPath('/home/me/repo'),
|
||||
workspaceSocketPath('/home/me/repo'),
|
||||
);
|
||||
expect(workspaceSocketPath('/home/me/repo'), workspaceSocketPath('/home/me/repo'));
|
||||
});
|
||||
|
||||
test('different workspace roots hash to different paths', () {
|
||||
expect(
|
||||
workspaceSocketPath('/home/me/repo-a'),
|
||||
isNot(workspaceSocketPath('/home/me/repo-b')),
|
||||
);
|
||||
expect(workspaceSocketPath('/home/me/repo-a'), isNot(workspaceSocketPath('/home/me/repo-b')));
|
||||
});
|
||||
|
||||
test('socketDirectory uses XDG_RUNTIME_DIR on Linux when set', () {
|
||||
|
||||
@@ -16,12 +16,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('error kinds are unique strings', () {
|
||||
final kinds = <String>{
|
||||
IpcErrorKind.userError,
|
||||
IpcErrorKind.toolError,
|
||||
IpcErrorKind.notFound,
|
||||
IpcErrorKind.conflict,
|
||||
};
|
||||
final kinds = <String>{IpcErrorKind.userError, IpcErrorKind.toolError, IpcErrorKind.notFound, IpcErrorKind.conflict};
|
||||
expect(kinds.length, 4);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,10 +20,7 @@ import 'package:test/test.dart';
|
||||
|
||||
Logger _silent() => Logger(minLevel: LogLevel.error, sinks: const []);
|
||||
|
||||
Future<Socket> _connect(IpcServer s) async => Socket.connect(
|
||||
InternetAddress(s.socketPath, type: InternetAddressType.unix),
|
||||
0,
|
||||
);
|
||||
Future<Socket> _connect(IpcServer s) async => Socket.connect(InternetAddress(s.socketPath, type: InternetAddressType.unix), 0);
|
||||
|
||||
void _emit(DaemonBus bus, String sub, String kind, [Map<String, Object?> data = const {}]) =>
|
||||
bus.emit(DaemonEvent(subsystem: sub, kind: kind, data: data, ts: DateTime.now().toUtc()));
|
||||
@@ -62,12 +59,11 @@ void main() {
|
||||
try {
|
||||
final lines = s.cast<List<int>>().transform(utf8.decoder).transform(const LineSplitter());
|
||||
final it = StreamIterator(lines);
|
||||
s.write('${IpcRequest(id: 'e', cmd: 'events', args: {
|
||||
'flags': {
|
||||
if (since != null) 'since': since,
|
||||
if (filter != null) 'filter': filter,
|
||||
},
|
||||
}).encode()}\n');
|
||||
s.write(
|
||||
'${IpcRequest(id: 'e', cmd: 'events', args: {
|
||||
'flags': {'since': ?since, 'filter': ?filter},
|
||||
}).encode()}\n',
|
||||
);
|
||||
await s.flush();
|
||||
if (!await it.moveNext().timeout(const Duration(seconds: 2))) {
|
||||
throw StateError('no response');
|
||||
|
||||
@@ -19,10 +19,7 @@ import 'package:test/test.dart';
|
||||
|
||||
Logger _silent() => Logger(minLevel: LogLevel.error, sinks: const []);
|
||||
|
||||
Future<Socket> _connect(IpcServer s) async => Socket.connect(
|
||||
InternetAddress(s.socketPath, type: InternetAddressType.unix),
|
||||
0,
|
||||
);
|
||||
Future<Socket> _connect(IpcServer s) async => Socket.connect(InternetAddress(s.socketPath, type: InternetAddressType.unix), 0);
|
||||
|
||||
/// Wrap a Socket in a line iterator backed by a single broadcast
|
||||
/// stream so the same connection can read multiple framed lines.
|
||||
@@ -50,15 +47,12 @@ Future<void> _send(Socket s, IpcRequest req) async {
|
||||
}
|
||||
|
||||
IpcRequest _tailReq({String? filter, String id = 't'}) => IpcRequest(
|
||||
id: id,
|
||||
cmd: 'tail',
|
||||
args: {
|
||||
'flags': {
|
||||
'events': true,
|
||||
if (filter != null) 'filter': filter,
|
||||
},
|
||||
},
|
||||
);
|
||||
id: id,
|
||||
cmd: 'tail',
|
||||
args: {
|
||||
'flags': {'events': true, 'filter': ?filter},
|
||||
},
|
||||
);
|
||||
|
||||
void main() {
|
||||
late Directory ws;
|
||||
@@ -70,8 +64,13 @@ void main() {
|
||||
ws = await Directory.systemTemp.createTemp('clide-stream-test-');
|
||||
dispatcher = DaemonDispatcher();
|
||||
bus = DaemonBus();
|
||||
server =
|
||||
IpcServer(dispatcher: dispatcher, workspaceRoot: '${ws.path}/${DateTime.now().microsecondsSinceEpoch}', log: _silent(), events: bus, replayDepth: 4);
|
||||
server = IpcServer(
|
||||
dispatcher: dispatcher,
|
||||
workspaceRoot: '${ws.path}/${DateTime.now().microsecondsSinceEpoch}',
|
||||
log: _silent(),
|
||||
events: bus,
|
||||
replayDepth: 4,
|
||||
);
|
||||
await server.start();
|
||||
});
|
||||
|
||||
@@ -213,10 +212,15 @@ void main() {
|
||||
final r = _lineReader(s);
|
||||
final q = _Lines(r.lines);
|
||||
await _send(
|
||||
s,
|
||||
IpcRequest(id: 'a', cmd: '_argv', args: {
|
||||
s,
|
||||
IpcRequest(
|
||||
id: 'a',
|
||||
cmd: '_argv',
|
||||
args: {
|
||||
'argv': ['tail', '--events', '--filter', 'pane'],
|
||||
}));
|
||||
},
|
||||
),
|
||||
);
|
||||
final ack = IpcMessage.decode(await q.next()) as IpcResponse;
|
||||
expect(ack.ok, isTrue);
|
||||
expect(ack.data['streaming'], isTrue);
|
||||
|
||||
@@ -94,10 +94,7 @@ void main() {
|
||||
test('malformed JSON line surfaces a userError', () async {
|
||||
server = IpcServer(dispatcher: dispatcher, workspaceRoot: workRoot, log: _silentLog());
|
||||
await server.start();
|
||||
final c = await Socket.connect(
|
||||
InternetAddress(server.socketPath, type: InternetAddressType.unix),
|
||||
0,
|
||||
);
|
||||
final c = await Socket.connect(InternetAddress(server.socketPath, type: InternetAddressType.unix), 0);
|
||||
c.write('{not json\n');
|
||||
await c.flush();
|
||||
final line = await c.cast<List<int>>().transform(utf8.decoder).transform(const LineSplitter()).first.timeout(const Duration(seconds: 2));
|
||||
@@ -175,10 +172,7 @@ void main() {
|
||||
test('stop closes an in-flight client connection', () async {
|
||||
server = IpcServer(dispatcher: dispatcher, workspaceRoot: workRoot, log: _silentLog());
|
||||
await server.start();
|
||||
final c = await Socket.connect(
|
||||
InternetAddress(server.socketPath, type: InternetAddressType.unix),
|
||||
0,
|
||||
);
|
||||
final c = await Socket.connect(InternetAddress(server.socketPath, type: InternetAddressType.unix), 0);
|
||||
c.write('${IpcRequest(id: 'q', cmd: 'ping').encode()}\n');
|
||||
await c.flush();
|
||||
await c.cast<List<int>>().transform(utf8.decoder).transform(const LineSplitter()).first;
|
||||
@@ -192,10 +186,7 @@ void main() {
|
||||
test('multiple sequential requests on the same connection each get a reply', () async {
|
||||
server = IpcServer(dispatcher: dispatcher, workspaceRoot: workRoot, log: _silentLog());
|
||||
await server.start();
|
||||
final c = await Socket.connect(
|
||||
InternetAddress(server.socketPath, type: InternetAddressType.unix),
|
||||
0,
|
||||
);
|
||||
final c = await Socket.connect(InternetAddress(server.socketPath, type: InternetAddressType.unix), 0);
|
||||
final replies = c.cast<List<int>>().transform(utf8.decoder).transform(const LineSplitter());
|
||||
final iter = StreamIterator(replies);
|
||||
for (var i = 0; i < 3; i++) {
|
||||
@@ -239,10 +230,7 @@ void main() {
|
||||
test('a non-request message (e.g. event) surfaces a userError', () async {
|
||||
server = IpcServer(dispatcher: dispatcher, workspaceRoot: workRoot, log: _silentLog());
|
||||
await server.start();
|
||||
final c = await Socket.connect(
|
||||
InternetAddress(server.socketPath, type: InternetAddressType.unix),
|
||||
0,
|
||||
);
|
||||
final c = await Socket.connect(InternetAddress(server.socketPath, type: InternetAddressType.unix), 0);
|
||||
final evt = IpcEvent(subsystem: 'test', kind: 'wrong-shape', timestamp: DateTime.now().toUtc());
|
||||
c.write('${evt.encode()}\n');
|
||||
await c.flush();
|
||||
@@ -258,10 +246,7 @@ void main() {
|
||||
Logger _silentLog() => Logger(minLevel: LogLevel.error, sinks: const []);
|
||||
|
||||
Future<IpcResponse> _roundTrip(String socketPath, IpcRequest req) async {
|
||||
final c = await Socket.connect(
|
||||
InternetAddress(socketPath, type: InternetAddressType.unix),
|
||||
0,
|
||||
);
|
||||
final c = await Socket.connect(InternetAddress(socketPath, type: InternetAddressType.unix), 0);
|
||||
c.write('${req.encode()}\n');
|
||||
await c.flush();
|
||||
final line = await c.cast<List<int>>().transform(utf8.decoder).transform(const LineSplitter()).first.timeout(const Duration(seconds: 2));
|
||||
|
||||
Reference in New Issue
Block a user