mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 08:20:31 +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
17 changed files with 6326 additions and 4843 deletions
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,8 +294,8 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
|
|||
..interestingFiles = ['*.foo'];
|
||||
pluginManager.pluginsChangedController.add(null);
|
||||
});
|
||||
final unregistrations =
|
||||
UnregistrationParams.fromJson(unregisterRequest.params)
|
||||
final unregistrations = UnregistrationParams.fromJson(
|
||||
unregisterRequest.params as Map<String, Object?>)
|
||||
.unregisterations;
|
||||
|
||||
// folding method should have been unregistered as the server now supports
|
||||
|
@ -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