mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
[analysis_server] Swap from dynamic to Object? in LSP generated code
Change-Id: Ib8ce2c722806b53b727d256c73859becbc1dbd11 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/206120 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
parent
77487355b7
commit
c2f5625fa7
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -132,8 +132,9 @@ class LspClientCapabilities {
|
|||
raw.workspace?.symbol?.symbolKind?.valueSet,
|
||||
defaults: defaultSupportedSymbolKinds),
|
||||
experimentalSnippetTextEdit =
|
||||
raw.experimental is Map<String, dynamic> &&
|
||||
raw.experimental['snippetTextEdit'] == true;
|
||||
raw.experimental is Map<String, Object?> &&
|
||||
(raw.experimental as Map<String, Object?>)['snippetTextEdit'] ==
|
||||
true;
|
||||
|
||||
static Set<MarkupKind>? _completionDocumentationFormats(
|
||||
ClientCapabilities raw) {
|
||||
|
|
|
@ -78,8 +78,8 @@ abstract class SimpleEditCommandHandler
|
|||
// sent - and may have failed to apply - was related to this command
|
||||
// execution).
|
||||
// We need to fromJson to convert the JSON map to the real types.
|
||||
final editResponseResult =
|
||||
ApplyWorkspaceEditResponse.fromJson(editResponse.result);
|
||||
final editResponseResult = ApplyWorkspaceEditResponse.fromJson(
|
||||
editResponse.result as Map<String, Object?>);
|
||||
if (editResponseResult.applied) {
|
||||
return success(null);
|
||||
} else {
|
||||
|
|
|
@ -51,9 +51,6 @@ class ExecuteCommandHandler
|
|||
: server.clientCapabilities?.workDoneProgress ?? false
|
||||
? ProgressReporter.serverCreated(server)
|
||||
: ProgressReporter.noop;
|
||||
// TODO(dantup): Remove this cast and update codegen to use `Object?` in
|
||||
// place of the `dynamics`.
|
||||
return handler.handle(
|
||||
params.arguments?.cast<Object?>(), progress, cancellationToken);
|
||||
return handler.handle(params.arguments, progress, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -614,7 +614,7 @@ class LspAnalysisServer extends AbstractAnalysisServer {
|
|||
ShowMessageRequestParams(type: type, message: message, actions: actions),
|
||||
);
|
||||
|
||||
return MessageActionItem.fromJson(response.result);
|
||||
return MessageActionItem.fromJson(response.result as Map<String, Object?>);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -38,7 +38,7 @@ abstract class AbstractLspAnalysisServerIntegrationTest
|
|||
? null as T
|
||||
: throw 'Expected Null response but got ${resp.result}';
|
||||
} else {
|
||||
return fromJson(resp.result);
|
||||
return fromJson(resp.result as R);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,8 @@ class ExtractMethodRefactorCodeActionsTest extends AbstractCodeActionsTest {
|
|||
requestsFromServer
|
||||
.where((r) => r.method == Method.window_workDoneProgress_create)
|
||||
.listen((request) async {
|
||||
final params = WorkDoneProgressCreateParams.fromJson(request.params);
|
||||
final params = WorkDoneProgressCreateParams.fromJson(
|
||||
request.params as Map<String, Object?>);
|
||||
if (params.token != analyzingProgressToken) {
|
||||
controller.add('CREATE');
|
||||
}
|
||||
|
@ -45,7 +46,8 @@ class ExtractMethodRefactorCodeActionsTest extends AbstractCodeActionsTest {
|
|||
notificationsFromServer
|
||||
.where((n) => n.method == Method.progress)
|
||||
.listen((notification) {
|
||||
final params = ProgressParams.fromJson(notification.params);
|
||||
final params =
|
||||
ProgressParams.fromJson(notification.params as Map<String, Object?>);
|
||||
if (params.token != analyzingProgressToken) {
|
||||
if (WorkDoneProgressBegin.canParse(params.value, nullLspJsonReporter)) {
|
||||
controller.add('BEGIN');
|
||||
|
@ -118,7 +120,8 @@ void newMethod() {
|
|||
late WorkspaceEdit edit;
|
||||
requestsFromServer.listen((request) async {
|
||||
if (request.method == Method.workspace_applyEdit) {
|
||||
final params = ApplyWorkspaceEditParams.fromJson(request.params);
|
||||
final params = ApplyWorkspaceEditParams.fromJson(
|
||||
request.params as Map<String, Object?>);
|
||||
edit = params.edit;
|
||||
respondTo(request, ApplyWorkspaceEditResponse(applied: true));
|
||||
}
|
||||
|
|
|
@ -154,7 +154,8 @@ void f() {
|
|||
|
||||
// By default, there should be no commit characters.
|
||||
var reg = registration(Method.textDocument_completion);
|
||||
var options = CompletionRegistrationOptions.fromJson(reg.registerOptions);
|
||||
var options = CompletionRegistrationOptions.fromJson(
|
||||
reg.registerOptions as Map<String, Object?>);
|
||||
expect(options.allCommitCharacters, isNull);
|
||||
|
||||
// When we change config, we should get a re-registration (unregister then
|
||||
|
@ -162,7 +163,8 @@ void f() {
|
|||
await monitorDynamicReregistration(
|
||||
registrations, () => updateConfig({'previewCommitCharacters': true}));
|
||||
reg = registration(Method.textDocument_completion);
|
||||
options = CompletionRegistrationOptions.fromJson(reg.registerOptions);
|
||||
options = CompletionRegistrationOptions.fromJson(
|
||||
reg.registerOptions as Map<String, Object?>);
|
||||
expect(options.allCommitCharacters, equals(dartCompletionCommitCharacters));
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,8 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
|
|||
Method method,
|
||||
) {
|
||||
return TextDocumentRegistrationOptions.fromJson(
|
||||
registrationFor(registrations, method)?.registerOptions);
|
||||
registrationFor(registrations, method)?.registerOptions
|
||||
as Map<String, Object?>);
|
||||
}
|
||||
|
||||
Future<void> test_bazelWorkspace() async {
|
||||
|
@ -64,14 +65,15 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
|
|||
),
|
||||
);
|
||||
|
||||
final initResult = InitializeResult.fromJson(initResponse.result);
|
||||
final initResult =
|
||||
InitializeResult.fromJson(initResponse.result as Map<String, Object?>);
|
||||
expect(initResult.capabilities, isNotNull);
|
||||
|
||||
// Check Dart-only registration.
|
||||
final dartRegistration =
|
||||
registrationForDart(registrations, Method.textDocument_completion);
|
||||
final dartOptions = CompletionRegistrationOptions.fromJson(
|
||||
dartRegistration.registerOptions);
|
||||
dartRegistration.registerOptions as Map<String, Object?>);
|
||||
expect(dartOptions.documentSelector, hasLength(1));
|
||||
expect(dartOptions.documentSelector![0].language, dartLanguageId);
|
||||
expect(dartOptions.triggerCharacters, isNotEmpty);
|
||||
|
@ -81,7 +83,7 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
|
|||
r.method == Method.textDocument_completion.toJson() &&
|
||||
r != dartRegistration);
|
||||
final nonDartOptions = CompletionRegistrationOptions.fromJson(
|
||||
nonDartRegistration.registerOptions);
|
||||
nonDartRegistration.registerOptions as Map<String, Object?>);
|
||||
final otherLanguages = nonDartOptions.documentSelector!
|
||||
.map((selector) => selector.language)
|
||||
.toList();
|
||||
|
@ -109,7 +111,8 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
|
|||
// Because we support dynamic registration for synchronization, we won't send
|
||||
// static registrations for them.
|
||||
// https://github.com/dart-lang/sdk/issues/38490
|
||||
final initResult = InitializeResult.fromJson(initResponse.result);
|
||||
final initResult =
|
||||
InitializeResult.fromJson(initResponse.result as Map<String, Object?>);
|
||||
expect(initResult.serverInfo!.name, 'Dart SDK LSP Analysis Server');
|
||||
expect(initResult.serverInfo!.version, isNotNull);
|
||||
expect(initResult.capabilities, isNotNull);
|
||||
|
@ -123,7 +126,7 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
|
|||
registrationOptionsFor(registrations, Method.textDocument_didChange);
|
||||
final rename = FileOperationRegistrationOptions.fromJson(
|
||||
registrationFor(registrations, Method.workspace_willRenameFiles)
|
||||
?.registerOptions);
|
||||
?.registerOptions as Map<String, Object?>);
|
||||
expect(registrationOptionsFor(registrations, Method.textDocument_didOpen),
|
||||
isNotNull);
|
||||
expect(registrationOptionsFor(registrations, Method.textDocument_didClose),
|
||||
|
@ -164,7 +167,8 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
|
|||
final initResponse = await initialize();
|
||||
await pumpEventQueue();
|
||||
|
||||
final initResult = InitializeResult.fromJson(initResponse.result);
|
||||
final initResult =
|
||||
InitializeResult.fromJson(initResponse.result as Map<String, Object?>);
|
||||
expect(initResult.capabilities, isNotNull);
|
||||
// When dynamic registration is not supported, we will always statically
|
||||
// request text document open/close and incremental updates.
|
||||
|
@ -229,7 +233,8 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
|
|||
),
|
||||
);
|
||||
|
||||
final initResult = InitializeResult.fromJson(initResponse.result);
|
||||
final initResult =
|
||||
InitializeResult.fromJson(initResponse.result as Map<String, Object?>);
|
||||
expect(initResult.capabilities, isNotNull);
|
||||
|
||||
// Ensure no static registrations. This list should include all server equivilents
|
||||
|
@ -289,9 +294,9 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
|
|||
..interestingFiles = ['*.foo'];
|
||||
pluginManager.pluginsChangedController.add(null);
|
||||
});
|
||||
final unregistrations =
|
||||
UnregistrationParams.fromJson(unregisterRequest.params)
|
||||
.unregisterations;
|
||||
final unregistrations = UnregistrationParams.fromJson(
|
||||
unregisterRequest.params as Map<String, Object?>)
|
||||
.unregisterations;
|
||||
|
||||
// folding method should have been unregistered as the server now supports
|
||||
// *.foo files for it as well.
|
||||
|
@ -321,7 +326,9 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
|
|||
.firstWhere((r) => r.method == Method.client_unregisterCapability)
|
||||
.then((request) {
|
||||
respondTo(request, null);
|
||||
return UnregistrationParams.fromJson(request.params).unregisterations;
|
||||
return UnregistrationParams.fromJson(
|
||||
request.params as Map<String, Object?>)
|
||||
.unregisterations;
|
||||
});
|
||||
|
||||
final request = await expectRequest(Method.client_registerCapability, () {
|
||||
|
@ -332,7 +339,8 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
|
|||
});
|
||||
|
||||
final registrations =
|
||||
RegistrationParams.fromJson(request.params).registrations;
|
||||
RegistrationParams.fromJson(request.params as Map<String, Object?>)
|
||||
.registrations;
|
||||
|
||||
final documentFilterSql =
|
||||
DocumentFilter(scheme: 'file', pattern: '**/*.sql');
|
||||
|
@ -343,7 +351,8 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
|
|||
contains(isA<Registration>()
|
||||
.having((r) => r.method, 'method', 'textDocument/foldingRange')
|
||||
.having(
|
||||
(r) => TextDocumentRegistrationOptions.fromJson(r.registerOptions)
|
||||
(r) => TextDocumentRegistrationOptions.fromJson(
|
||||
r.registerOptions as Map<String, Object?>)
|
||||
.documentSelector,
|
||||
'registerOptions.documentSelector',
|
||||
containsAll([documentFilterSql, documentFilterDart]),
|
||||
|
@ -483,7 +492,8 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
|
|||
expect(response.result, isNotNull);
|
||||
expect(InitializeResult.canParse(response.result, nullLspJsonReporter),
|
||||
isTrue);
|
||||
final result = InitializeResult.fromJson(response.result);
|
||||
final result =
|
||||
InitializeResult.fromJson(response.result as Map<String, Object?>);
|
||||
expect(result.capabilities, isNotNull);
|
||||
// Check some basic capabilities that are unlikely to change.
|
||||
expect(result.capabilities.textDocumentSync, isNotNull);
|
||||
|
|
|
@ -176,7 +176,8 @@ class RenameTest extends AbstractLspAnalysisServerTest {
|
|||
throw error;
|
||||
}
|
||||
|
||||
final result = WorkspaceEdit.fromJson(response.result);
|
||||
final result =
|
||||
WorkspaceEdit.fromJson(response.result as Map<String, Object?>);
|
||||
|
||||
// Ensure applying the changes will give us the expected content.
|
||||
final contents = {
|
||||
|
@ -205,7 +206,7 @@ class RenameTest extends AbstractLspAnalysisServerTest {
|
|||
// Expect a successful empty response if cancelled.
|
||||
expect(response.error, isNull);
|
||||
expect(
|
||||
WorkspaceEdit.fromJson(response.result),
|
||||
WorkspaceEdit.fromJson(response.result as Map<String, Object?>),
|
||||
equals(emptyWorkspaceEdit),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ abstract class AbstractLspAnalysisServerTest
|
|||
throw error;
|
||||
} else {
|
||||
// resp.result should only be null when error != null if T allows null.
|
||||
return resp.result == null ? null as T : fromJson(resp.result);
|
||||
return resp.result == null ? null as T : fromJson(resp.result as R);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,7 +122,8 @@ abstract class AbstractLspAnalysisServerTest
|
|||
) {
|
||||
return registrations.singleWhere((r) =>
|
||||
r.method == method.toJson() &&
|
||||
(TextDocumentRegistrationOptions.fromJson(r.registerOptions)
|
||||
(TextDocumentRegistrationOptions.fromJson(
|
||||
r.registerOptions as Map<String, Object?>)
|
||||
.documentSelector
|
||||
?.any((selector) => selector.language == dartLanguageId) ??
|
||||
false));
|
||||
|
@ -875,7 +876,8 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
|
|||
final notificationFromServer = await firstError.timeout(timeout);
|
||||
|
||||
expect(notificationFromServer, isNotNull);
|
||||
return ShowMessageParams.fromJson(notificationFromServer.params);
|
||||
return ShowMessageParams.fromJson(
|
||||
notificationFromServer.params as Map<String, Object?>);
|
||||
}
|
||||
|
||||
Future<T> expectNotification<T>(
|
||||
|
@ -1253,7 +1255,8 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
|
|||
});
|
||||
|
||||
// Handle the request from the server and send the response back.
|
||||
final clientsResponse = await handler(fromJson(incomingRequest.params));
|
||||
final clientsResponse =
|
||||
await handler(fromJson(incomingRequest.params as Map<String, Object?>));
|
||||
respondTo(incomingRequest, clientsResponse);
|
||||
|
||||
// Return a future that completes when the response to the original request
|
||||
|
@ -1649,7 +1652,8 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
|
|||
'but client supports workDoneProgress');
|
||||
}
|
||||
|
||||
final params = AnalyzerStatusParams.fromJson(message.params);
|
||||
final params = AnalyzerStatusParams.fromJson(
|
||||
message.params as Map<String, Object?>);
|
||||
return params.isAnalyzing == analyzing;
|
||||
} else if (message.method == Method.progress) {
|
||||
if (_clientCapabilities!.window?.workDoneProgress != true) {
|
||||
|
@ -1658,7 +1662,8 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
|
|||
'but client supports workDoneProgress');
|
||||
}
|
||||
|
||||
final params = ProgressParams.fromJson(message.params);
|
||||
final params =
|
||||
ProgressParams.fromJson(message.params as Map<String, Object?>);
|
||||
|
||||
// Skip unrelated progress notifications.
|
||||
if (params.token != analyzingProgressToken) {
|
||||
|
@ -1688,8 +1693,8 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
|
|||
await serverToClient.firstWhere((message) {
|
||||
if (message is NotificationMessage &&
|
||||
message.method == CustomMethods.publishClosingLabels) {
|
||||
closingLabelsParams =
|
||||
PublishClosingLabelsParams.fromJson(message.params);
|
||||
closingLabelsParams = PublishClosingLabelsParams.fromJson(
|
||||
message.params as Map<String, Object?>);
|
||||
|
||||
return closingLabelsParams.uri == uri.toString();
|
||||
}
|
||||
|
@ -1704,7 +1709,8 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
|
|||
(message) {
|
||||
if (message is NotificationMessage &&
|
||||
message.method == Method.textDocument_publishDiagnostics) {
|
||||
diagnosticParams = PublishDiagnosticsParams.fromJson(message.params);
|
||||
diagnosticParams = PublishDiagnosticsParams.fromJson(
|
||||
message.params as Map<String, Object?>);
|
||||
return diagnosticParams!.uri == uri.toString();
|
||||
}
|
||||
return false;
|
||||
|
@ -1717,7 +1723,8 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
|
|||
await serverToClient.firstWhere((message) {
|
||||
if (message is NotificationMessage &&
|
||||
message.method == CustomMethods.publishFlutterOutline) {
|
||||
outlineParams = PublishFlutterOutlineParams.fromJson(message.params);
|
||||
outlineParams = PublishFlutterOutlineParams.fromJson(
|
||||
message.params as Map<String, Object?>);
|
||||
|
||||
return outlineParams.uri == uri.toString();
|
||||
}
|
||||
|
@ -1731,7 +1738,8 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
|
|||
await serverToClient.firstWhere((message) {
|
||||
if (message is NotificationMessage &&
|
||||
message.method == CustomMethods.publishOutline) {
|
||||
outlineParams = PublishOutlineParams.fromJson(message.params);
|
||||
outlineParams = PublishOutlineParams.fromJson(
|
||||
message.params as Map<String, Object?>);
|
||||
|
||||
return outlineParams.uri == uri.toString();
|
||||
}
|
||||
|
@ -1760,7 +1768,8 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
|
|||
(input) => input.cast<Map<String, dynamic>>().map(fromJson).toList();
|
||||
|
||||
Future<void> _handleProgress(NotificationMessage request) async {
|
||||
final params = ProgressParams.fromJson(request.params);
|
||||
final params =
|
||||
ProgressParams.fromJson(request.params as Map<String, Object?>);
|
||||
if (params.token != clientProvidedTestWorkDoneToken &&
|
||||
!validProgressTokens.contains(params.token)) {
|
||||
throw Exception('Server sent a progress notification for a token '
|
||||
|
@ -1777,7 +1786,8 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
|
|||
throw Exception('Server sent ${Method.window_workDoneProgress_create} '
|
||||
'but client capabilities do not allow');
|
||||
}
|
||||
final params = WorkDoneProgressCreateParams.fromJson(request.params);
|
||||
final params = WorkDoneProgressCreateParams.fromJson(
|
||||
request.params as Map<String, Object?>);
|
||||
if (validProgressTokens.contains(params.token)) {
|
||||
throw Exception('Server tried to create already-active progress token');
|
||||
}
|
||||
|
|
|
@ -40,12 +40,14 @@ class MockLspServerChannel implements LspServerCommunicationChannel {
|
|||
// `window/showMessage`.
|
||||
_serverToClient.stream.listen((message) {
|
||||
if (message is lsp.NotificationMessage &&
|
||||
message.method == Method.window_showMessage &&
|
||||
message.params is lsp.ShowMessageParams) {
|
||||
if (message.params?.type == MessageType.Error) {
|
||||
shownErrors.add(message.params);
|
||||
} else if (message.params?.type == MessageType.Warning) {
|
||||
shownWarnings.add(message.params);
|
||||
message.method == Method.window_showMessage) {
|
||||
final params = message.params;
|
||||
if (params is lsp.ShowMessageParams) {
|
||||
if (params.type == MessageType.Error) {
|
||||
shownErrors.add(params);
|
||||
} else if (params.type == MessageType.Warning) {
|
||||
shownWarnings.add(params);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -170,7 +172,9 @@ class MockLspServerChannel implements LspServerCommunicationChannel {
|
|||
(throwOnError &&
|
||||
message is lsp.NotificationMessage &&
|
||||
message.method == Method.window_showMessage &&
|
||||
lsp.ShowMessageParams.fromJson(message.params).type ==
|
||||
lsp.ShowMessageParams.fromJson(
|
||||
message.params as Map<String, Object?>)
|
||||
.type ==
|
||||
MessageType.Error));
|
||||
|
||||
if (response is lsp.ResponseMessage) {
|
||||
|
|
|
@ -11,8 +11,8 @@ void main() {
|
|||
test('handles basic types', () {
|
||||
expect(_simple('string').dartType, equals('String'));
|
||||
expect(_simple('boolean').dartType, equals('bool'));
|
||||
expect(_simple('any').dartType, equals('dynamic'));
|
||||
expect(_simple('object').dartType, equals('dynamic'));
|
||||
expect(_simple('any').dartType, equals('Object?'));
|
||||
expect(_simple('object').dartType, equals('Object?'));
|
||||
expect(_simple('int').dartType, equals('int'));
|
||||
expect(_simple('num').dartType, equals('num'));
|
||||
});
|
||||
|
|
|
@ -177,6 +177,20 @@ String _rewriteCommentReference(String comment) {
|
|||
});
|
||||
}
|
||||
|
||||
/// Returns a String representing the underlying Dart type for the provided
|
||||
/// spec [type].
|
||||
///
|
||||
/// This is `Map<String, Object?>` for complex types but can be a simple type
|
||||
/// for enums.
|
||||
String _specJsonType(TypeBase type) {
|
||||
if (type is Type && _namespaces.containsKey(type.name)) {
|
||||
final valueType = _namespaces[type.name]!.members.cast<Const>().first.type;
|
||||
return resolveTypeAlias(valueType, resolveEnumClasses: true)
|
||||
.dartTypeWithTypeArgs;
|
||||
}
|
||||
return 'Map<String, Object?>';
|
||||
}
|
||||
|
||||
Iterable<String> _wrapLines(List<String> lines, int maxLength) sync* {
|
||||
lines = lines.map((l) => l.trimRight()).toList();
|
||||
for (var line in lines) {
|
||||
|
@ -204,7 +218,7 @@ void _writeCanParseMethod(IndentableStringBuffer buffer, Interface interface) {
|
|||
..writeIndentedln(
|
||||
'static bool canParse(Object? obj, LspJsonReporter reporter) {')
|
||||
..indent()
|
||||
..writeIndentedln('if (obj is Map<String, dynamic>) {')
|
||||
..writeIndentedln('if (obj is Map<String, Object?>) {')
|
||||
..indent();
|
||||
// In order to consider this valid for parsing, all fields that must not be
|
||||
// undefined must be present and also type check for the correct type.
|
||||
|
@ -227,9 +241,12 @@ void _writeCanParseMethod(IndentableStringBuffer buffer, Interface interface) {
|
|||
..outdent()
|
||||
..writeIndentedln('}');
|
||||
}
|
||||
// Add a local variable to allow type promotion (and avoid multiple lookups).
|
||||
final localName = _makeValidIdentifier(field.name);
|
||||
buffer.writeIndentedln("final $localName = obj['${field.name}'];");
|
||||
if (!field.allowsNull && !field.allowsUndefined) {
|
||||
buffer
|
||||
..writeIndentedln("if (obj['${field.name}'] == null) {")
|
||||
..writeIndentedln('if ($localName == null) {')
|
||||
..indent()
|
||||
..writeIndentedln("reporter.reportError('must not be null');")
|
||||
..writeIndentedln('return false;')
|
||||
|
@ -238,11 +255,11 @@ void _writeCanParseMethod(IndentableStringBuffer buffer, Interface interface) {
|
|||
}
|
||||
buffer.writeIndented('if (');
|
||||
if (field.allowsNull || field.allowsUndefined) {
|
||||
buffer.write("obj['${field.name}'] != null && ");
|
||||
buffer.write('$localName != null && ');
|
||||
}
|
||||
buffer.write('!(');
|
||||
_writeTypeCheckCondition(
|
||||
buffer, interface, "obj['${field.name}']", field.type, 'reporter');
|
||||
buffer, interface, localName, field.type, 'reporter');
|
||||
buffer
|
||||
..write(')) {')
|
||||
..indent()
|
||||
|
@ -469,47 +486,56 @@ void _writeField(IndentableStringBuffer buffer, Field field) {
|
|||
}
|
||||
|
||||
void _writeFromJsonCode(
|
||||
IndentableStringBuffer buffer, TypeBase type, String valueCode,
|
||||
{required bool allowsNull, bool requiresBracesInInterpolation = false}) {
|
||||
IndentableStringBuffer buffer,
|
||||
TypeBase type,
|
||||
String valueCode, {
|
||||
required bool allowsNull,
|
||||
bool requiresCast = true,
|
||||
}) {
|
||||
type = resolveTypeAlias(type);
|
||||
final nullOperator = allowsNull ? '?' : '';
|
||||
final cast = requiresCast && type.dartTypeWithTypeArgs != 'Object?'
|
||||
? ' as ${type.dartTypeWithTypeArgs}$nullOperator'
|
||||
: '';
|
||||
|
||||
if (_isSimpleType(type)) {
|
||||
buffer.write('$valueCode');
|
||||
buffer.write('$valueCode$cast');
|
||||
} else if (_isSpecType(type)) {
|
||||
// Our own types have fromJson() constructors we can call.
|
||||
if (allowsNull) {
|
||||
buffer.write('$valueCode != null ? ');
|
||||
}
|
||||
buffer.write('${type.dartType}.fromJson${type.typeArgsString}($valueCode)');
|
||||
buffer
|
||||
..write('${type.dartType}.fromJson${type.typeArgsString}')
|
||||
..write('($valueCode as ${_specJsonType(type)})');
|
||||
if (allowsNull) {
|
||||
buffer.write(': null');
|
||||
}
|
||||
} else if (type is ArrayType) {
|
||||
// Lists need to be map()'d so we can recursively call writeFromJsonCode
|
||||
// as they may need fromJson on each element.
|
||||
buffer.write('$valueCode?.map((item) => ');
|
||||
_writeFromJsonCode(buffer, type.elementType, 'item',
|
||||
allowsNull: allowsNull);
|
||||
buffer
|
||||
.write(')?.cast<${type.elementType.dartTypeWithTypeArgs}>()?.toList()');
|
||||
final listCast = requiresCast ? ' as List<Object?>$nullOperator' : '';
|
||||
buffer.write('($valueCode$listCast)$nullOperator.map((item) => ');
|
||||
_writeFromJsonCode(buffer, type.elementType, 'item', allowsNull: false);
|
||||
buffer.write(').toList()');
|
||||
} else if (type is MapType) {
|
||||
// Maps need to be map()'d so we can recursively call writeFromJsonCode as
|
||||
// they may need fromJson on each key or value.
|
||||
buffer.write('$valueCode?.map((key, value) => MapEntry(');
|
||||
_writeFromJsonCode(buffer, type.indexType, 'key', allowsNull: allowsNull);
|
||||
final mapCast = requiresCast ? ' as Map<Object, Object?>$nullOperator' : '';
|
||||
buffer
|
||||
..write('($valueCode$mapCast)$nullOperator.map(')
|
||||
..write('(key, value) => MapEntry(');
|
||||
_writeFromJsonCode(buffer, type.indexType, 'key', allowsNull: false);
|
||||
buffer.write(', ');
|
||||
_writeFromJsonCode(buffer, type.valueType, 'value', allowsNull: allowsNull);
|
||||
buffer.write(
|
||||
'))?.cast<${type.indexType.dartTypeWithTypeArgs}, ${type.valueType.dartTypeWithTypeArgs}>()');
|
||||
_writeFromJsonCode(buffer, type.valueType, 'value', allowsNull: false);
|
||||
buffer.write('))');
|
||||
} else if (type is LiteralUnionType) {
|
||||
_writeFromJsonCodeForLiteralUnion(buffer, type, valueCode,
|
||||
allowsNull: allowsNull);
|
||||
} else if (type is UnionType) {
|
||||
_writeFromJsonCodeForUnion(buffer, type, valueCode,
|
||||
allowsNull: allowsNull,
|
||||
requiresBracesInInterpolation: requiresBracesInInterpolation);
|
||||
_writeFromJsonCodeForUnion(buffer, type, valueCode, allowsNull: allowsNull);
|
||||
} else {
|
||||
buffer.write('$valueCode');
|
||||
buffer.write('$valueCode$cast');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -520,14 +546,16 @@ void _writeFromJsonCodeForLiteralUnion(
|
|||
if (allowsNull) null,
|
||||
...union.literalTypes.map((t) => t.literal)
|
||||
];
|
||||
final valueType = union.literalTypes.first.dartTypeWithTypeArgs;
|
||||
final cast = ' as $valueType${allowsNull ? '?' : ''}';
|
||||
buffer.write(
|
||||
"const {${allowedValues.join(', ')}}.contains($valueCode) ? $valueCode : "
|
||||
"throw '''\${$valueCode} was not one of (${allowedValues.join(', ')})'''");
|
||||
"const {${allowedValues.join(', ')}}.contains($valueCode) ? $valueCode$cast : "
|
||||
"throw '''\$$valueCode was not one of (${allowedValues.join(', ')})'''");
|
||||
}
|
||||
|
||||
void _writeFromJsonCodeForUnion(
|
||||
IndentableStringBuffer buffer, UnionType union, String valueCode,
|
||||
{required bool allowsNull, required bool requiresBracesInInterpolation}) {
|
||||
{required bool allowsNull}) {
|
||||
// Write a check against each type, eg.:
|
||||
// x is y ? new Either.tx(x) : (...)
|
||||
var hasIncompleteCondition = false;
|
||||
|
@ -543,7 +571,7 @@ void _writeFromJsonCodeForUnion(
|
|||
final type = union.types[i];
|
||||
final isAny = isAnyType(type);
|
||||
|
||||
// Dynamic matches all type checks, so only emit it if required.
|
||||
// "any" matches all type checks, so only emit it if required.
|
||||
if (!isAny) {
|
||||
_writeTypeCheckCondition(
|
||||
buffer, null, valueCode, type, 'nullLspJsonReporter');
|
||||
|
@ -552,10 +580,13 @@ void _writeFromJsonCodeForUnion(
|
|||
|
||||
// The code to construct a value with this "side" of the union.
|
||||
buffer.write('${union.dartTypeWithTypeArgs}.t${i + 1}(');
|
||||
_writeFromJsonCode(buffer, type, valueCode,
|
||||
allowsNull: false, // null is already handled above this loop
|
||||
requiresBracesInInterpolation:
|
||||
requiresBracesInInterpolation); // Call recursively!
|
||||
// Call recursively as unions may be nested.
|
||||
_writeFromJsonCode(
|
||||
buffer, type, valueCode,
|
||||
// null + type checks are already handled above this loop
|
||||
allowsNull: false,
|
||||
requiresCast: false,
|
||||
);
|
||||
buffer.write(')');
|
||||
|
||||
// If we output the type condition at the top, prepare for the next condition.
|
||||
|
@ -570,8 +601,7 @@ void _writeFromJsonCodeForUnion(
|
|||
// Fill the final parens with a throw because if we fell through all of the
|
||||
// cases then the value we had didn't match any of the types in the union.
|
||||
if (hasIncompleteCondition) {
|
||||
var interpolation =
|
||||
requiresBracesInInterpolation ? '\${$valueCode}' : '\$$valueCode';
|
||||
var interpolation = '\$$valueCode';
|
||||
buffer.write(
|
||||
"throw '''$interpolation was not one of (${union.types.map((t) => t.dartTypeWithTypeArgs).join(', ')})'''");
|
||||
}
|
||||
|
@ -583,7 +613,7 @@ void _writeFromJsonConstructor(
|
|||
final allFields = _getAllFields(interface);
|
||||
buffer
|
||||
..writeIndentedln('static ${interface.nameWithTypeArgs} '
|
||||
'fromJson${interface.typeArgsString}(Map<String, dynamic> json) {')
|
||||
'fromJson${interface.typeArgsString}(Map<String, Object?> json) {')
|
||||
..indent();
|
||||
// First check whether any of our subclasses can deserialise this.
|
||||
for (final subclassName in _subtypes[interface.name] ?? const <String>[]) {
|
||||
|
@ -597,10 +627,13 @@ void _writeFromJsonConstructor(
|
|||
..writeIndentedln('}');
|
||||
}
|
||||
for (final field in allFields) {
|
||||
buffer.writeIndented('final ${field.name} = ');
|
||||
_writeFromJsonCode(buffer, field.type, "json['${field.name}']",
|
||||
allowsNull: field.allowsNull || field.allowsUndefined,
|
||||
requiresBracesInInterpolation: true);
|
||||
// Add a local variable to allow type promotion (and avoid multiple lookups).
|
||||
final localName = _makeValidIdentifier(field.name);
|
||||
final localNameJson = '${localName}Json';
|
||||
buffer.writeIndented("final $localNameJson = json['${field.name}'];");
|
||||
buffer.writeIndented('final $localName = ');
|
||||
_writeFromJsonCode(buffer, field.type, localNameJson,
|
||||
allowsNull: field.allowsNull || field.allowsUndefined);
|
||||
buffer.writeln(';');
|
||||
}
|
||||
buffer
|
||||
|
@ -760,9 +793,9 @@ void _writeToJsonMethod(IndentableStringBuffer buffer, Interface interface) {
|
|||
// It's important the name we use for the map here isn't in use in the object
|
||||
// already. 'result' was, so we prefix it with some underscores.
|
||||
buffer
|
||||
..writeIndentedln('Map<String, dynamic> toJson() {')
|
||||
..writeIndentedln('Map<String, Object?> toJson() {')
|
||||
..indent()
|
||||
..writeIndentedln('var __result = <String, dynamic>{};');
|
||||
..writeIndentedln('var __result = <String, Object?>{};');
|
||||
// ResponseMessage must confirm to JSON-RPC which says only one of
|
||||
// result/error can be included. Since this isn't encoded in the types we
|
||||
// need to special-case it's toJson generation.
|
||||
|
@ -806,7 +839,7 @@ void _writeTypeCheckCondition(IndentableStringBuffer buffer,
|
|||
|
||||
final dartType = type.dartType;
|
||||
final fullDartType = type.dartTypeWithTypeArgs;
|
||||
if (fullDartType == 'dynamic') {
|
||||
if (fullDartType == 'Object?') {
|
||||
buffer.write('true');
|
||||
} else if (_isSimpleType(type)) {
|
||||
buffer.write('$valueCode is $fullDartType');
|
||||
|
@ -816,7 +849,7 @@ void _writeTypeCheckCondition(IndentableStringBuffer buffer,
|
|||
buffer.write('$dartType.canParse($valueCode, $reporter)');
|
||||
} else if (type is ArrayType) {
|
||||
buffer.write('($valueCode is List');
|
||||
if (fullDartType != 'dynamic') {
|
||||
if (fullDartType != 'Object?') {
|
||||
// TODO(dantup): If we're happy to assume we never have two lists in a union
|
||||
// we could skip this bit.
|
||||
buffer.write(' && ($valueCode.every((item) => ');
|
||||
|
@ -827,7 +860,7 @@ void _writeTypeCheckCondition(IndentableStringBuffer buffer,
|
|||
buffer.write(')');
|
||||
} else if (type is MapType) {
|
||||
buffer.write('($valueCode is Map');
|
||||
if (fullDartType != 'dynamic') {
|
||||
if (fullDartType != 'Object?') {
|
||||
buffer..write(' && (')..write('$valueCode.keys.every((item) => ');
|
||||
_writeTypeCheckCondition(
|
||||
buffer, interface, 'item', type.indexType, reporter);
|
||||
|
|
|
@ -98,7 +98,7 @@ String? getImprovedType(String interfaceName, String? fieldName) {
|
|||
},
|
||||
'ResponseError': {
|
||||
'code': 'ErrorCodes',
|
||||
// This is dynamic normally, but since this class can be serialised
|
||||
// This is Object? normally, but since this class can be serialised
|
||||
// we will crash if it data is set to something that can't be converted to
|
||||
// JSON (for ex. Uri) so this forces anyone setting this to convert to a
|
||||
// String.
|
||||
|
|
|
@ -507,17 +507,17 @@ class Parser {
|
|||
/// Returns the next token without advancing.
|
||||
Token _peek() => _tokenAt(_current);
|
||||
|
||||
/// Remove any duplicate types (for ex. if we map multiple types into dynamic)
|
||||
/// we don't want to end up with `dynamic | dynamic`. Key on dartType to
|
||||
/// Remove any duplicate types (for ex. if we map multiple types into Object?)
|
||||
/// we don't want to end up with `Object? | Object?`. Key on dartType to
|
||||
/// ensure we different types that will map down to the same type.
|
||||
TypeBase _simplifyUnionTypes(List<TypeBase> types) {
|
||||
final uniqueTypes = Map.fromEntries(
|
||||
types.map((t) => MapEntry(t.uniqueTypeIdentifier, t)),
|
||||
).values.toList();
|
||||
|
||||
// If our list includes something that maps to dynamic as well as other
|
||||
// types, we should just treat the whole thing as dynamic as we get no value
|
||||
// typing Either4<bool, String, num, dynamic> but it becomes much more
|
||||
// If our list includes something that maps to Object? as well as other
|
||||
// types, we should just treat the whole thing as Object? as we get no value
|
||||
// typing Either4<bool, String, num, Object?> but it becomes much more
|
||||
// difficult to use.
|
||||
if (uniqueTypes.any(isAnyType)) {
|
||||
return uniqueTypes.firstWhere(isAnyType);
|
||||
|
@ -954,8 +954,8 @@ class Type extends TypeBase {
|
|||
'number': 'num',
|
||||
'integer': 'int',
|
||||
'uinteger': 'int',
|
||||
'any': 'dynamic',
|
||||
'object': 'dynamic',
|
||||
'any': 'Object?',
|
||||
'object': 'Object?',
|
||||
// Simplify MarkedString from
|
||||
// string | { language: string; value: string }
|
||||
// to just String
|
||||
|
@ -1001,7 +1001,7 @@ abstract class TypeBase {
|
|||
String get typeArgsString;
|
||||
|
||||
/// A unique identifier for this type. Used for folding types together
|
||||
/// (for example two types that resolve to "dynamic" in Dart).
|
||||
/// (for example two types that resolve to "Object?" in Dart).
|
||||
String get uniqueTypeIdentifier => dartTypeWithTypeArgs;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue