// Copyright (c) 2025, 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:convert'; import 'dart:io' as io; import 'dart:math'; import 'package:crypto/crypto.dart'; import 'package:path/path.dart' as p; import 'models.dart'; /// Apply a single contiguous text edit. String applyEdit(String before, MutationEdit e) { return before.replaceRange(e.offset, e.offset + e.length, e.replacement); } /// Discovers .dart files under [roots], skipping generated/build/tooling dirs. List discoverDartFiles(List roots, String repo) { var result = []; for (var root in roots) { if (!io.Directory(root).existsSync()) continue; for (var entry in io.Directory( root, ).listSync(recursive: true, followLinks: false)) { if (entry is io.File && entry.path.endsWith('.dart')) { var relPath = p.relative(entry.path, from: repo); if (relPath.endsWith('.g.dart') || relPath.contains(RegExp(r'(^|/)build/')) || relPath.contains(RegExp(r'(^|/)\.dart_tool/'))) { continue; } result.add(entry.path); } } } result.sort(); return result; } double medianSpeedup(List xs) { var ys = xs.where((e) => e.isFinite && e > 0).toList()..sort(); if (ys.isEmpty) return 1.0; var mid = ys.length ~/ 2; return ys.length.isOdd ? ys[mid] : (ys[mid - 1] + ys[mid]) / 2.0; } double p90Speedup(List xs) { var ys = xs.where((e) => e.isFinite && e > 0).toList()..sort(); if (ys.isEmpty) return 1.0; var idx = (ys.length * 0.9).floor().clamp(0, ys.length - 1); return ys[idx]; } /// Stateless pick of an index in [0, upper). int pickIndex(int upper, List seedParts) { if (upper <= 1) return 0; var random = Random(seedOf(seedParts)); return random.nextInt(upper); } /// Stable 32-bit seed from [parts] using SHA-256 over a canonical encoding. /// Supports: null, String, int. Extend with more tags if needed. int seedOf(Iterable parts) { var sb = StringBuffer(); for (var part in parts) { switch (part) { case null: sb.write('N;'); case String(): sb ..write('S') ..write(part.length) ..write(':') ..write(part) ..write(';'); case int(): sb ..write('I') ..write(part) ..write(';'); default: throw UnimplementedError('Unsupported: ${part.runtimeType}'); } } var bytes = sha256.convert(utf8.encode(sb.toString())).bytes; return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); } /// Returns A/B speedup as A_time/B_time; defaults to 1.0 on missing/zeros. double speedup(Map m) { var a = (m['A_time_ms'] as num?)?.toDouble() ?? 0.0; var b = (m['B_time_ms'] as num?)?.toDouble() ?? 0.0; if (a <= 0 || b <= 0) return 1.0; return a / b; } Iterable splitCsv(String str) { return str.split(',').map((e) => e.trim()).where((e) => e.isNotEmpty); } String timestampId() { String pad(int v) => v < 10 ? '0$v' : '$v'; var n = DateTime.now().toUtc(); return '${n.year}${pad(n.month)}${pad(n.day)}T' '${pad(n.hour)}${pad(n.minute)}${pad(n.second)}Z'; } /// Pretty JSON writer with recursive directories creation. void writeJson(String path, Object? data) { io.File(path).createSync(recursive: true); io.File(path).writeAsStringSync(JsonEncoder.withIndent(' ').convert(data)); }