[migrate][analysis_server] LSP Protocol for migrate tool.

This some basic scaffolding for the migrate tool. We'll add a protocol to the analysis server with the following parameters and result:

Parameters
- `uris`: Workspaces/packages to be migrated

Result
- `summary`: Information about fixes that could not be applied automatically. (e.g. if there was a conflict) or information about what fixes were applied and what SDK version the pubspec has been changed to.
- `edit`: A list of edits to be applied.

There are no interesting tests yet, but I hope to have a suite of tests for the next change.

Fixes: https://github.com/dart-lang/sdk/issues/63247
Change-Id: I77508720acb17af5ec86675fd3f3045e2a610bf2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/496801
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Kallen Tu <kallentu@google.com>
This commit is contained in:
Kallen Tu
2026-05-01 12:13:41 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 72600b32af
commit 81a14586a9
8 changed files with 448 additions and 0 deletions
@@ -736,6 +736,31 @@ bool _canParseUri(
return true;
}
bool _canParseWorkspaceEdit(
Map<String, Object?> map, LspJsonReporter reporter, String fieldName,
{required bool allowsUndefined, required bool allowsNull}) {
reporter.push(fieldName);
try {
if (!allowsUndefined && !map.containsKey(fieldName)) {
reporter.reportError('must not be undefined');
return false;
}
final value = map[fieldName];
final nullCheck = allowsNull || allowsUndefined;
if (!nullCheck && value == null) {
reporter.reportError('must not be null');
return false;
}
if ((!nullCheck || value != null) &&
!WorkspaceEdit.canParse(value, reporter)) {
return false;
}
} finally {
reporter.pop();
}
return true;
}
Either2<int, String> _eitherIntString(Object? value) {
return value is int
? Either2.t1(value)
@@ -1253,6 +1278,132 @@ class DartDiagnosticServer implements ToJsonable {
}
}
class DartMigrateParams implements ToJsonable {
static const jsonHandler = LspJsonHandler(
DartMigrateParams.canParse,
DartMigrateParams.fromJson,
);
/// The URIs of the directories (packages or workspaces) to migrate.
/// Individual file URIs are not supported.
final List<DocumentUri> uris;
DartMigrateParams({
required this.uris,
});
@override
int get hashCode => lspHashCode(uris);
@override
bool operator ==(Object other) {
return other is DartMigrateParams &&
other.runtimeType == DartMigrateParams &&
const DeepCollectionEquality().equals(uris, other.uris);
}
@override
Map<String, Object?> toJson() {
var result = <String, Object?>{};
result['uris'] = uris.map((uri) => uri.toString()).toList();
return result;
}
@override
String toString() => jsonEncoder.convert(toJson());
static bool canParse(Object? obj, LspJsonReporter reporter) {
if (obj is Map<String, Object?>) {
return _canParseListUri(obj, reporter, 'uris',
allowsUndefined: false, allowsNull: false);
} else {
reporter.reportError('must be of type DartMigrateParams');
return false;
}
}
static DartMigrateParams fromJson(Map<String, Object?> json) {
final urisJson = json['uris'];
final uris = (urisJson as List<Object?>)
.map((item) => Uri.parse(item as String))
.toList();
return DartMigrateParams(
uris: uris,
);
}
}
class DartMigrateResult implements ToJsonable {
static const jsonHandler = LspJsonHandler(
DartMigrateResult.canParse,
DartMigrateResult.fromJson,
);
/// The edits to be applied to the workspace.
final WorkspaceEdit? edit;
/// A summary of the migration results, detailing which fixes succeeded, which
/// fixes failed to be applied, and the new SDK version constraint applied to
/// the pubspec.yaml.
final String? summary;
DartMigrateResult({
this.edit,
this.summary,
});
@override
int get hashCode => Object.hash(
edit,
summary,
);
@override
bool operator ==(Object other) {
return other is DartMigrateResult &&
other.runtimeType == DartMigrateResult &&
edit == other.edit &&
summary == other.summary;
}
@override
Map<String, Object?> toJson() {
var result = <String, Object?>{};
result['edit'] = edit?.toJson();
result['summary'] = summary;
return result;
}
@override
String toString() => jsonEncoder.convert(toJson());
static bool canParse(Object? obj, LspJsonReporter reporter) {
if (obj is Map<String, Object?>) {
if (!_canParseWorkspaceEdit(obj, reporter, 'edit',
allowsUndefined: false, allowsNull: true)) {
return false;
}
return _canParseString(obj, reporter, 'summary',
allowsUndefined: false, allowsNull: true);
} else {
reporter.reportError('must be of type DartMigrateResult');
return false;
}
}
static DartMigrateResult fromJson(Map<String, Object?> json) {
final editJson = json['edit'];
final edit = editJson != null
? WorkspaceEdit.fromJson(editJson as Map<String, Object?>)
: null;
final summaryJson = json['summary'];
final summary = summaryJson as String?;
return DartMigrateResult(
edit: edit,
summary: summary,
);
}
}
class DartTextDocumentSummaryParams implements ToJsonable {
static const jsonHandler = LspJsonHandler(
DartTextDocumentSummaryParams.canParse,