Skip to content

Commit 7af9370

Browse files
authored
refactor(cli): Clean up migration logic (#297)
- Abstract some logic around source edits - Improve flow for `dart fix` (should be rare)
1 parent c3baa22 commit 7af9370

File tree

14 files changed

+204
-138
lines changed

14 files changed

+204
-138
lines changed

apps/cli/fixtures/fixtures_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ class TestRunner {
180180
projectRoot: projectRoot,
181181
projectName: celestProject.projectName,
182182
parentProject: parentProject,
183+
upgradeFromVersion: null,
183184
);
184185
await (_warmUp(projectRoot), migrator.migrate()).wait;
185186
});

apps/cli/lib/src/analyzer/celest_analysis_helpers.dart

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:analyzer/src/dart/resolver/scope.dart';
1111
import 'package:celest_cli/src/analyzer/analysis_error.dart';
1212
import 'package:celest_cli/src/analyzer/resolver/project_resolver.dart';
1313
import 'package:celest_cli/src/context.dart';
14+
import 'package:celest_cli/src/init/edits/source_edit.dart';
1415
import 'package:celest_cli/src/utils/analyzer.dart';
1516
import 'package:celest_cli/src/utils/json.dart';
1617
import 'package:logging/logging.dart';
@@ -27,31 +28,6 @@ enum CustomType {
2728
};
2829
}
2930

30-
final class SourceEdit {
31-
const SourceEdit(this.offset, this.length, this.replacement);
32-
33-
final int offset;
34-
final int length;
35-
final String replacement;
36-
37-
@override
38-
bool operator ==(Object other) {
39-
if (identical(this, other)) return true;
40-
return other is SourceEdit &&
41-
other.offset == offset &&
42-
other.length == length &&
43-
other.replacement == replacement;
44-
}
45-
46-
@override
47-
int get hashCode => Object.hash(offset, length, replacement);
48-
49-
@override
50-
String toString() {
51-
return 'SourceEdit(offset: $offset, length: $length, replacement: $replacement)';
52-
}
53-
}
54-
5531
mixin CelestAnalysisHelpers implements CelestErrorReporter {
5632
AnalysisContext get context;
5733
Set<InterfaceElement> get customExceptionTypes;

apps/cli/lib/src/analyzer/celest_analyzer.dart

Lines changed: 2 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import 'package:celest_cli/src/ast/ast.dart';
1717
import 'package:celest_cli/src/config/feature_flags.dart';
1818
import 'package:celest_cli/src/context.dart';
1919
import 'package:celest_cli/src/database/cache/cache_database.dart';
20+
import 'package:celest_cli/src/init/edits/source_edit_applier.dart';
2021
import 'package:celest_cli/src/pub/project_dependency.dart';
2122
import 'package:celest_cli/src/pub/pub_action.dart';
2223
import 'package:celest_cli/src/pub/pub_environment.dart';
@@ -25,7 +26,6 @@ import 'package:celest_cli/src/sdk/dart_sdk.dart';
2526
import 'package:celest_cli/src/types/type_helper.dart';
2627
import 'package:celest_cli/src/utils/analyzer.dart';
2728
import 'package:celest_cli/src/utils/reference.dart';
28-
import 'package:collection/collection.dart';
2929
import 'package:logging/logging.dart';
3030
import 'package:pubspec_parse/pubspec_parse.dart';
3131
import 'package:source_span/source_span.dart';
@@ -583,47 +583,7 @@ const project = Project(name: 'cache_warmup');
583583
}
584584

585585
Future<void> _applyMigrations() async {
586-
if (resolver.pendingEdits.isEmpty) {
587-
return;
588-
}
589-
590-
_logger.fine('Applying ${resolver.pendingEdits.length} migrations');
591-
592-
final fileChanges = <Future<void>>[];
593-
for (final entry in resolver.pendingEdits.entries) {
594-
final path = entry.key;
595-
596-
// Sort edits in reserve order to avoid offset changes.
597-
final edits = entry.value.sorted((a, b) {
598-
return -a.offset.compareTo(b.offset);
599-
});
600-
601-
_logger.finest('Applying migrations to $path: $edits');
602-
603-
final file = context.currentSession.getFile(path) as FileResult;
604-
var source = file.content;
605-
606-
for (final edit in edits) {
607-
source = source.replaceRange(
608-
edit.offset,
609-
edit.offset + edit.length,
610-
edit.replacement,
611-
);
612-
}
613-
614-
fileChanges.add(fileSystem.file(path).writeAsString(source));
615-
}
616-
617-
await Future.wait(fileChanges);
618-
619-
_logger.finest('Applied migrations to disk');
620-
621-
for (final path in pendingEdits.keys) {
622-
context.changeFile(path);
623-
}
624-
final changes = await context.applyPendingFileChanges();
625-
626-
_logger.finest('Applied changes in analyzer: $changes');
586+
await SourceEditApplier(resolver.pendingEdits).apply();
627587
}
628588
}
629589

apps/cli/lib/src/analyzer/resolver/project_resolver.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import 'package:celest_cli/src/analyzer/celest_analysis_helpers.dart';
1616
import 'package:celest_cli/src/analyzer/resolver/config_value_resolver.dart';
1717
import 'package:celest_cli/src/config/feature_flags.dart';
1818
import 'package:celest_cli/src/context.dart';
19+
import 'package:celest_cli/src/init/edits/source_edit.dart';
1920
import 'package:celest_cli/src/sdk/dart_sdk.dart';
2021
import 'package:celest_cli/src/serialization/common.dart';
2122
import 'package:celest_cli/src/serialization/serialization_verdict.dart';

apps/cli/lib/src/commands/uninstall/celest_uninstaller.dart

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:convert';
22
import 'dart:io';
33

4+
import 'package:celest_cli/src/cli/cli_runtime.dart';
45
import 'package:celest_cli/src/context.dart';
56
import 'package:celest_cli/src/exceptions.dart';
67
import 'package:celest_cli/src/utils/error.dart';
@@ -17,12 +18,14 @@ class CelestUninstaller {
1718
Future<void> uninstall() async {
1819
await removeConfig();
1920

20-
if (fileSystem.path.fromUri(platform.script).endsWith('.snapshot')) {
21-
await _uninstallPubGlobal();
22-
} else if (platform.executable.contains('dart')) {
23-
// Celest is running from source. Nothing to uninstall.
24-
} else {
25-
await _uninstallAot();
21+
switch (CliRuntime.current) {
22+
case CliRuntime.pubGlobal:
23+
await _uninstallPubGlobal();
24+
case CliRuntime.local:
25+
// Celest is running from source. Nothing to uninstall.
26+
break;
27+
case CliRuntime.aot:
28+
await _uninstallAot();
2629
}
2730
}
2831

apps/cli/lib/src/database/cache/cache_database.dart

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,21 +69,23 @@ final class CacheDatabase extends $CacheDatabase {
6969
await database.clear();
7070
await database._setVersionInfo(update: true);
7171
}
72-
if (semver.Version.parse(celest) < semver.Version.parse(packageVersion)) {
73-
database._needsProjectUpgrade = true;
72+
final upgradeFromVersion = semver.Version.parse(celest);
73+
database._upgradeFromVersion = upgradeFromVersion;
74+
if (upgradeFromVersion < currentVersion) {
7475
await database._setVersionInfo(update: true);
7576
}
7677
} else if (versionInfo == null) {
77-
database._needsProjectUpgrade = true;
7878
await database._setVersionInfo(update: false);
7979
}
8080
final rawDb = await rawCompleter.future;
8181
database.byteStore = CachingByteStore(rawDb);
8282
return database;
8383
}
8484

85-
bool _needsProjectUpgrade = false;
86-
bool get needsProjectUpgrade => _needsProjectUpgrade;
85+
semver.Version? _upgradeFromVersion;
86+
87+
/// The version of the Celest package before the current one.
88+
semver.Version? get upgradeFromVersion => _upgradeFromVersion;
8789

8890
Future<void> _setVersionInfo({required bool update}) async {
8991
if (update) {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
final class SourceEdit {
2+
const SourceEdit(this.offset, this.length, this.replacement);
3+
4+
final int offset;
5+
final int length;
6+
final String replacement;
7+
8+
@override
9+
bool operator ==(Object other) {
10+
if (identical(this, other)) return true;
11+
return other is SourceEdit &&
12+
other.offset == offset &&
13+
other.length == length &&
14+
other.replacement == replacement;
15+
}
16+
17+
@override
18+
int get hashCode => Object.hash(offset, length, replacement);
19+
20+
@override
21+
String toString() {
22+
return 'SourceEdit(offset: $offset, length: $length, replacement: $replacement)';
23+
}
24+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import 'package:celest_cli/src/context.dart';
2+
import 'package:celest_cli/src/init/edits/source_edit.dart';
3+
import 'package:collection/collection.dart';
4+
import 'package:logging/logging.dart';
5+
6+
final class SourceEditApplier {
7+
SourceEditApplier(this.edits);
8+
9+
final Map<String, Iterable<SourceEdit>> edits;
10+
11+
static final Logger _logger = Logger('SourceEditApplier');
12+
13+
Future<void> _applyEdits(String path, List<SourceEdit> edits) async {
14+
_logger.finest('Applying migrations to $path: $edits');
15+
16+
var source = await fileSystem.file(path).readAsString();
17+
18+
for (final edit in edits) {
19+
source = source.replaceRange(
20+
edit.offset,
21+
edit.offset + edit.length,
22+
edit.replacement,
23+
);
24+
}
25+
26+
await fileSystem.file(path).writeAsString(source);
27+
}
28+
29+
/// Applies all pendings edits to disk.
30+
Future<void> apply() async {
31+
if (edits.isEmpty) {
32+
_logger.fine('No edits to apply');
33+
return;
34+
}
35+
36+
_logger.fine('Applying ${edits.length} migrations');
37+
38+
final fileChanges = <Future<void>>[];
39+
for (final entry in edits.entries) {
40+
final path = entry.key;
41+
if (entry.value.isEmpty) {
42+
_logger.fine('No edits to apply to $path');
43+
continue;
44+
}
45+
46+
// Sort edits in reserve order to avoid offset changes.
47+
final edits = entry.value.sorted((a, b) {
48+
return -a.offset.compareTo(b.offset);
49+
});
50+
51+
fileChanges.add(_applyEdits(path, edits));
52+
}
53+
54+
await Future.wait(fileChanges);
55+
56+
_logger.finest('Applied migrations to disk');
57+
58+
await celestProject.invalidate(edits.keys);
59+
}
60+
}

apps/cli/lib/src/init/migrations/pubspec_updater.dart

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,18 @@ import 'package:pubspec_parse/pubspec_parse.dart';
1313
import 'package:yaml_edit/yaml_edit.dart' hide SourceEdit;
1414

1515
final class PubspecUpdater extends ProjectMigration {
16-
const PubspecUpdater(super.projectRoot, this.parentProject, this.projectName);
16+
const PubspecUpdater(
17+
super.projectRoot,
18+
this.parentProject,
19+
this.projectName, {
20+
this.upgradeFromVersion,
21+
});
1722

1823
static final _logger = Logger('PubspecUpdater');
1924

2025
final ParentProject? parentProject;
2126
final String projectName;
27+
final Version? upgradeFromVersion;
2228

2329
@override
2430
String get name => 'core.project.pubspec';
@@ -55,7 +61,7 @@ final class PubspecUpdater extends ProjectMigration {
5561
}
5662

5763
/// Returns the version updated from.
58-
Future<Version?> _updateBackendDependencies({
64+
Future<void> _updateBackendDependencies({
5965
required Pubspec pubspec,
6066
required String pubspecYaml,
6167
required File pubspecFile,
@@ -65,16 +71,8 @@ final class PubspecUpdater extends ProjectMigration {
6571
if (ProjectDependency.backendDependencies.upToDate(pubspec) &&
6672
currentSdkVersion == requiredSdkVersion) {
6773
_logger.fine('Project dependencies are up to date.');
68-
return null;
74+
return;
6975
}
70-
final fromVersion = switch (pubspec.dependencies['celest']) {
71-
final HostedDependency hosted => switch (hosted.version) {
72-
final Version version => version,
73-
final VersionRange range => range.min,
74-
_ => Version.none,
75-
},
76-
_ => Version.none,
77-
};
7876
_logger.fine('Updating project dependencies to latest versions...');
7977
pubspec = pubspec.copyWith(
8078
environment: {'sdk': PubEnvironment.dartSdkConstraint},
@@ -95,7 +93,6 @@ final class PubspecUpdater extends ProjectMigration {
9593
);
9694
pubspecYaml = pubspec.toYaml(source: pubspecYaml);
9795
await pubspecFile.writeAsString(pubspecYaml);
98-
return fromVersion;
9996
}
10097

10198
Future<bool> _updateClientDependencies({
@@ -173,14 +170,14 @@ final class PubspecUpdater extends ProjectMigration {
173170
final pubspecFile = fileSystem.file(p.join(projectRoot, 'pubspec.yaml'));
174171
final pubspecYaml = await pubspecFile.readAsString();
175172
final pubspec = Pubspec.parse(pubspecYaml);
176-
final fromVersion = await _updateBackendDependencies(
173+
await _updateBackendDependencies(
177174
pubspec: pubspec,
178175
pubspecYaml: pubspecYaml,
179176
pubspecFile: pubspecFile,
180177
);
181178
// await _updateProjectName();
182-
needsAnalyzerMigration |=
183-
fromVersion != null && fromVersion < Version(1, 0, 0).firstPreRelease;
179+
needsAnalyzerMigration |= upgradeFromVersion != null &&
180+
upgradeFromVersion! < Version(1, 0, 0).firstPreRelease;
184181
if (needsAnalyzerMigration) {
185182
operations.add(
186183
runPub(action: PubAction.get, workingDirectory: projectRoot),

0 commit comments

Comments
 (0)