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:
2026-06-11 12:11:53 +02:00
co-authored by Claude Opus 4.8
parent bcea5f15b7
commit 6d0ebab721
444 changed files with 7587 additions and 12849 deletions
+8 -33
View File
@@ -69,9 +69,7 @@ Stream<List<SearchMatch>> grepWorkspace({
final rootPath = root.absolute.path;
// Launch every chunk concurrently; await in chunk order to stream.
final futures = <Future<List<SearchMatch>>>[
for (final c in chunks) _runChunk(rootPath, c, query, maxPerFile, useIsolates),
];
final futures = <Future<List<SearchMatch>>>[for (final c in chunks) _runChunk(rootPath, c, query, maxPerFile, useIsolates)];
var emitted = 0;
for (final f in futures) {
@@ -104,13 +102,7 @@ void _drain(List<Future<List<SearchMatch>>> futures) {
}
}
Future<List<SearchMatch>> _runChunk(
String rootPath,
List<String> paths,
SearchQuery query,
int maxPerFile,
bool useIsolates,
) {
Future<List<SearchMatch>> _runChunk(String rootPath, List<String> paths, SearchQuery query, int maxPerFile, bool useIsolates) {
if (useIsolates) {
return Isolate.run(() => grepChunk(rootPath, paths, query, maxPerFile));
}
@@ -133,12 +125,7 @@ List<List<String>> _chunk(List<String> items, int buckets) {
/// Grep a chunk of files. Runs in a worker isolate (or in-process for
/// tests). Reads each file, skips binaries, and collects up to
/// [maxPerFile] matches per file.
List<SearchMatch> grepChunk(
String rootPath,
List<String> relPaths,
SearchQuery query,
int maxPerFile,
) {
List<SearchMatch> grepChunk(String rootPath, List<String> relPaths, SearchQuery query, int maxPerFile) {
final compiled = CompiledQuery(query);
final out = <SearchMatch>[];
for (final rel in relPaths) {
@@ -160,25 +147,13 @@ List<SearchMatch> grepChunk(
/// Match [content]'s lines, appending up to [maxPerFile] hits to [out].
/// Exposed (with a pre-built [compiled]) for unit testing without I/O.
void grepContent(
String relPath,
String content,
CompiledQuery compiled,
int maxPerFile,
List<SearchMatch> out,
) {
void grepContent(String relPath, String content, CompiledQuery compiled, int maxPerFile, List<SearchMatch> out) {
var lineNo = 0;
final added0 = out.length;
for (final line in const LineSplitter().convert(content)) {
lineNo++;
for (final span in compiled.matches(line)) {
out.add(SearchMatch(
path: relPath,
line: lineNo,
matchStart: span.$1,
matchEnd: span.$2,
preview: line.length > 500 ? line.substring(0, 500) : line,
));
out.add(SearchMatch(path: relPath, line: lineNo, matchStart: span.$1, matchEnd: span.$2, preview: line.length > 500 ? line.substring(0, 500) : line));
if (out.length - added0 >= maxPerFile) return;
}
}
@@ -188,9 +163,9 @@ void grepContent(
/// matcher with an optional case-insensitive fast-path.
class CompiledQuery {
CompiledQuery(SearchQuery q)
: _regex = q.regex ? RegExp(q.pattern, caseSensitive: !q.ignoreCase) : null,
_needle = q.regex ? '' : (q.ignoreCase ? q.pattern.toLowerCase() : q.pattern),
_ignoreCase = q.ignoreCase;
: _regex = q.regex ? RegExp(q.pattern, caseSensitive: !q.ignoreCase) : null,
_needle = q.regex ? '' : (q.ignoreCase ? q.pattern.toLowerCase() : q.pattern),
_ignoreCase = q.ignoreCase;
final RegExp? _regex;
final String _needle;
+16 -40
View File
@@ -9,13 +9,7 @@ library;
/// result, navigate to the line, and (for replace, T-53) address the
/// matched span within the line.
class SearchMatch {
const SearchMatch({
required this.path,
required this.line,
required this.matchStart,
required this.matchEnd,
required this.preview,
});
const SearchMatch({required this.path, required this.line, required this.matchStart, required this.matchEnd, required this.preview});
/// Repo-relative, forward-slashed path of the file.
final String path;
@@ -32,32 +26,20 @@ class SearchMatch {
/// The matched line's text (capped for transport/render).
final String preview;
Map<String, Object?> toJson() => {
'path': path,
'line': line,
'matchStart': matchStart,
'matchEnd': matchEnd,
'preview': preview,
};
Map<String, Object?> toJson() => {'path': path, 'line': line, 'matchStart': matchStart, 'matchEnd': matchEnd, 'preview': preview};
factory SearchMatch.fromJson(Map<String, Object?> j) => SearchMatch(
path: j['path'] as String,
line: (j['line'] as num).toInt(),
matchStart: (j['matchStart'] as num).toInt(),
matchEnd: (j['matchEnd'] as num).toInt(),
preview: j['preview'] as String,
);
path: j['path'] as String,
line: (j['line'] as num).toInt(),
matchStart: (j['matchStart'] as num).toInt(),
matchEnd: (j['matchEnd'] as num).toInt(),
preview: j['preview'] as String,
);
}
/// Parameters for a workspace search.
class SearchQuery {
const SearchQuery({
required this.pattern,
this.regex = false,
this.ignoreCase = false,
this.include = const [],
this.exclude = const [],
});
const SearchQuery({required this.pattern, this.regex = false, this.ignoreCase = false, this.include = const [], this.exclude = const []});
/// The literal text (when [regex] is false) or regular expression
/// source (when true) to search for.
@@ -71,21 +53,15 @@ class SearchQuery {
/// Glob patterns; matching paths are skipped (applied after [include]).
final List<String> exclude;
Map<String, Object?> toJson() => {
'pattern': pattern,
'regex': regex,
'ignoreCase': ignoreCase,
'include': include,
'exclude': exclude,
};
Map<String, Object?> toJson() => {'pattern': pattern, 'regex': regex, 'ignoreCase': ignoreCase, 'include': include, 'exclude': exclude};
factory SearchQuery.fromJson(Map<String, Object?> j) => SearchQuery(
pattern: (j['pattern'] as String?) ?? '',
regex: j['regex'] == true,
ignoreCase: j['ignoreCase'] == true,
include: _strings(j['include']),
exclude: _strings(j['exclude']),
);
pattern: (j['pattern'] as String?) ?? '',
regex: j['regex'] == true,
ignoreCase: j['ignoreCase'] == true,
include: _strings(j['include']),
exclude: _strings(j['exclude']),
);
static List<String> _strings(Object? v) => v is List ? [for (final e in v) '$e'] : const [];
}
+10 -15
View File
@@ -28,11 +28,8 @@ class ReplacementEdit {
Map<String, Object?> toJson() => {'line': line, 'before': before, 'after': after};
factory ReplacementEdit.fromJson(Map<String, Object?> j) => ReplacementEdit(
line: (j['line'] as num).toInt(),
before: j['before'] as String,
after: j['after'] as String,
);
factory ReplacementEdit.fromJson(Map<String, Object?> j) =>
ReplacementEdit(line: (j['line'] as num).toInt(), before: j['before'] as String, after: j['after'] as String);
}
/// The set of edits a replacement would make to one file.
@@ -46,18 +43,16 @@ class FileReplacement {
final List<ReplacementEdit> edits;
Map<String, Object?> toJson() => {
'path': path,
'count': count,
'edits': [for (final e in edits) e.toJson()],
};
'path': path,
'count': count,
'edits': [for (final e in edits) e.toJson()],
};
factory FileReplacement.fromJson(Map<String, Object?> j) => FileReplacement(
path: j['path'] as String,
count: (j['count'] as num).toInt(),
edits: [
for (final e in (j['edits'] as List? ?? const []).whereType<Map>()) ReplacementEdit.fromJson(e.cast<String, Object?>()),
],
);
path: j['path'] as String,
count: (j['count'] as num).toInt(),
edits: [for (final e in (j['edits'] as List? ?? const []).whereType<Map>()) ReplacementEdit.fromJson(e.cast<String, Object?>())],
);
}
/// Apply [query]'s pattern to [text], substituting [replacement]. Returns