Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit

Permalink
Handle generic types in LSP spec parsing
Browse files Browse the repository at this point in the history
The regex wasn't picking up generate type args (in interface definitions or usages in field definitions). This adds <> characters to the regex and handles them in mapping of types (as well as adds some additional tests to cover this).

Change-Id: I8187b3d2abb2b35c0d7638839e3116700cfbf9e0
Reviewed-on: https://dart-review.googlesource.com/c/81004
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Danny Tuppeny <dantup@google.com>
  • Loading branch information
DanTup authored and commit-bot@chromium.org committed Oct 22, 2018
1 parent 8b6f896 commit cddc2d4
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 45 deletions.
90 changes: 84 additions & 6 deletions pkg/analysis_server/lib/lsp_protocol/protocol_generated.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3586,7 +3586,7 @@ class MessageType {
}

class NotificationMessage implements Message {
NotificationMessage(this.method, this.jsonrpc) {
NotificationMessage(this.method, this.params, this.jsonrpc) {
if (method == null) {
throw 'method is required but was not provided';
}
Expand All @@ -3596,18 +3596,30 @@ class NotificationMessage implements Message {
}
factory NotificationMessage.fromJson(Map<String, dynamic> json) {
final method = json['method'];
final params = (json['params'] is List &&
(json['params'].length == 0 ||
json['params'].every((item) => true)))
? new Either2<List<dynamic>, dynamic>.t1(
json['params']?.map((item) => item)?.cast<dynamic>()?.toList())
: (json['params']);
final jsonrpc = json['jsonrpc'];
return new NotificationMessage(method, jsonrpc);
return new NotificationMessage(method, params, jsonrpc);
}

final String jsonrpc;

/// The method to be invoked.
final String method;

/// The notification's params.
final Either2<List<dynamic>, dynamic> params;

Map<String, dynamic> toJson() {
Map<String, dynamic> __result = {};
__result['method'] = method ?? (throw 'method is required but was not set');
if (params != null) {
__result['params'] = params;
}
__result['jsonrpc'] =
jsonrpc ?? (throw 'jsonrpc is required but was not set');
return __result;
Expand Down Expand Up @@ -4140,7 +4152,7 @@ class RenameRegistrationOptions implements TextDocumentRegistrationOptions {
}

class RequestMessage implements Message {
RequestMessage(this.id, this.method, this.jsonrpc) {
RequestMessage(this.id, this.method, this.params, this.jsonrpc) {
if (id == null) {
throw 'id is required but was not provided';
}
Expand All @@ -4158,8 +4170,14 @@ class RequestMessage implements Message {
? new Either2<num, String>.t2(json['id'])
: (throw '''${json['id']} was not one of (number, string)'''));
final method = json['method'];
final params = (json['params'] is List &&
(json['params'].length == 0 ||
json['params'].every((item) => true)))
? new Either2<List<dynamic>, dynamic>.t1(
json['params']?.map((item) => item)?.cast<dynamic>()?.toList())
: (json['params']);
final jsonrpc = json['jsonrpc'];
return new RequestMessage(id, method, jsonrpc);
return new RequestMessage(id, method, params, jsonrpc);
}

/// The request id.
Expand All @@ -4169,10 +4187,16 @@ class RequestMessage implements Message {
/// The method to be invoked.
final String method;

/// The method's params.
final Either2<List<dynamic>, dynamic> params;

Map<String, dynamic> toJson() {
Map<String, dynamic> __result = {};
__result['id'] = id ?? (throw 'id is required but was not set');
__result['method'] = method ?? (throw 'method is required but was not set');
if (params != null) {
__result['params'] = params;
}
__result['jsonrpc'] =
jsonrpc ?? (throw 'jsonrpc is required but was not set');
return __result;
Expand Down Expand Up @@ -4225,8 +4249,55 @@ class ResourceOperationKind {
bool operator ==(o) => o is ResourceOperationKind && o._value == _value;
}

class ResponseError<D> {
ResponseError(this.code, this.message, this.data) {
if (code == null) {
throw 'code is required but was not provided';
}
if (message == null) {
throw 'message is required but was not provided';
}
}
factory ResponseError.fromJson(Map<String, dynamic> json) {
final code = json['code'];
final message = json['message'];
final data = json['data'];
return new ResponseError<D>(code, message, data);
}

/// A number indicating the error type that occurred.
final num code;

/// A Primitive or Structured value that contains
/// additional information about the error. Can be
/// omitted.
final D data;

/// A string providing a short description of the error.
final String message;

Map<String, dynamic> toJson() {
Map<String, dynamic> __result = {};
__result['code'] = code ?? (throw 'code is required but was not set');
__result['message'] =
message ?? (throw 'message is required but was not set');
if (data != null) {
__result['data'] = data;
}
return __result;
}

static bool canParse(Object obj) {
return obj is Map<String, dynamic> &&
obj.containsKey('code') &&
obj['code'] is num &&
obj.containsKey('message') &&
obj['message'] is String;
}
}

class ResponseMessage implements Message {
ResponseMessage(this.id, this.result, this.jsonrpc) {
ResponseMessage(this.id, this.result, this.error, this.jsonrpc) {
if (jsonrpc == null) {
throw 'jsonrpc is required but was not provided';
}
Expand All @@ -4238,10 +4309,14 @@ class ResponseMessage implements Message {
? new Either2<num, String>.t2(json['id'])
: (throw '''${json['id']} was not one of (number, string)'''));
final result = json['result'];
final error = json['error'];
final jsonrpc = json['jsonrpc'];
return new ResponseMessage(id, result, jsonrpc);
return new ResponseMessage(id, result, error, jsonrpc);
}

/// The error object in case a request fails.
final ResponseError<dynamic> error;

/// The request id.
final Either2<num, String> id;
final String jsonrpc;
Expand All @@ -4256,6 +4331,9 @@ class ResponseMessage implements Message {
if (result != null) {
__result['result'] = result;
}
if (error != null) {
__result['error'] = error;
}
__result['jsonrpc'] =
jsonrpc ?? (throw 'jsonrpc is required but was not set');
return __result;
Expand Down
48 changes: 48 additions & 0 deletions pkg/analysis_server/test/tool/lsp_spec/dart_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:test/test.dart';

import '../../../tool/lsp_spec/codegen_dart.dart';

main() {
group('mapType', () {
test('handles basic types', () {
expect(mapType(['string']), equals('String'));
expect(mapType(['boolean']), equals('bool'));
expect(mapType(['any']), equals('dynamic'));
expect(mapType(['object']), equals('dynamic'));
expect(mapType(['int']), equals('int'));
expect(mapType(['num']), equals('num'));
});

test('handles union types', () {
expect(mapType(['string', 'int']), equals('Either2<String, int>'));
expect(mapType(['string | int']), equals('Either2<String, int>'));
});

test('handles arrays', () {
expect(mapType(['string[]']), equals('List<String>'));
expect(mapType(['Array<string>']), equals('List<String>'));
});

test('handles types with args', () {
expect(mapType(['Class<string[]>']), equals('Class<List<String>>'));
expect(mapType(['Array<string | num>']),
equals('List<Either2<String, num>>'));
});

test('handles complex nested types', () {
expect(
mapType([
'Array<string>',
'any[]',
'Response<A>',
'Request<Array<string | num>>'
]),
equals(
'Either4<List<String>, List<dynamic>, Response<A>, Request<List<Either2<String, num>>>>'));
});
});
}
3 changes: 2 additions & 1 deletion pkg/analysis_server/test/tool/lsp_spec/json_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ main() {
});

test('returns correct output for union types', () {
final message = new RequestMessage(new Either2.t1(1), "test", "test");
final message =
new RequestMessage(new Either2.t1(1), "test", null, "test");
String output = json.encode(message.toJson());
expect(output, equals('{"id":1,"method":"test","jsonrpc":"test"}'));
});
Expand Down
42 changes: 42 additions & 0 deletions pkg/analysis_server/test/tool/lsp_spec/typescript_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,48 @@ export interface SomeOptions {
});
});

test('parses an interface with type args', () {
final String input = '''
interface ResponseError<D> {
data?: D;
}
''';
final List<ApiItem> output = extractTypes(input);
expect(output, hasLength(1));
expect(output[0], const TypeMatcher<Interface>());
final Interface interface = output[0];
expect(interface.members, hasLength(1));
final Field field = interface.members.first;
expect(field, const TypeMatcher<Field>());
expect(field.name, equals('data'));
expect(field.allowsUndefined, true);
expect(field.allowsNull, false);
expect(field.types, equals(['D']));
});

test('parses an interface with Arrays in Array<T> format', () {
final String input = '''
export interface RequestMessage {
/**
* The method's params.
*/
params?: Array<any> | object;
}
''';
final List<ApiItem> output = extractTypes(input);
expect(output, hasLength(1));
expect(output[0], const TypeMatcher<Interface>());
final Interface interface = output[0];
expect(interface.members, hasLength(1));
final Field field = interface.members.first;
expect(field, const TypeMatcher<Field>());
expect(field.name, equals('params'));
expect(field.comment, equals('''The method's params.'''));
expect(field.allowsUndefined, true);
expect(field.allowsNull, false);
expect(field.types, equals(['Array<any>', 'object']));
});

test('flags nullable undefined values', () {
final String input = '''
export interface A {
Expand Down
Loading

0 comments on commit cddc2d4

Please sign in to comment.