Skip to content

Commit

Permalink
Tweaks to be able to parse v3.15 spec
Browse files Browse the repository at this point in the history
Change-Id: Ifdb8696246b6893b743754dab5344d5939550a10
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/153776
Reviewed-by: Brian Wilkerson <[email protected]>
  • Loading branch information
DanTup authored and [email protected] committed Jul 9, 2020
1 parent 0162e7c commit d86fa96
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 20 deletions.
46 changes: 46 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 @@ -242,6 +242,39 @@ export type DocumentSelector = DocumentFilter[];
expect(typeAlias.baseType, isArrayOf(isSimpleType('DocumentFilter')));
});

test('parses a type alias that is a union of unnamed types', () {
final input = '''
export type NameOrLength = { name: string } | { length: number };
''';
final output = parseString(input);
expect(output, hasLength(3));

// Results should be the two inline interfaces followed by the type alias.

expect(output[0], const TypeMatcher<InlineInterface>());
final InlineInterface interface1 = output[0];
expect(interface1.name, equals('NameOrLength1'));
expect(interface1.members, hasLength(1));
expect(interface1.members[0].name, equals('name'));

expect(output[1], const TypeMatcher<InlineInterface>());
final InlineInterface interface2 = output[1];
expect(interface2.name, equals('NameOrLength2'));
expect(interface2.members, hasLength(1));
expect(interface2.members[0].name, equals('length'));

expect(output[2], const TypeMatcher<TypeAlias>());
final TypeAlias typeAlias = output[2];
expect(typeAlias.name, equals('NameOrLength'));
expect(typeAlias.baseType, const TypeMatcher<UnionType>());

// The type alias should be a union of the two types above.
UnionType union = typeAlias.baseType;
expect(union.types, hasLength(2));
expect(union.types[0], isSimpleType(interface1.name));
expect(union.types[1], isSimpleType(interface2.name));
});

test('parses a namespace of constants', () {
final input = '''
export namespace ResourceOperationKind {
Expand Down Expand Up @@ -321,5 +354,18 @@ interface SomeInformation {
expect(field.name, equals('label'));
expect(field.type, isSimpleType('object'));
});

test('parses multiple single-line comments into a single token', () {
final input = '''
// This is line 1
// This is line 2
interface SomeInformation {
}
''';
final output = parseString(input);
expect(output, hasLength(1));
expect(output[0].commentNode.token.lexeme, equals('''// This is line 1
// This is line 2'''));
});
});
}
59 changes: 45 additions & 14 deletions pkg/analysis_server/tool/lsp_spec/codegen_dart.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ bool enumClassAllowsAnyValue(String name) {

String generateDartForTypes(List<AstNode> types) {
final buffer = IndentableStringBuffer();
_getSorted(types).forEach((t) => _writeType(buffer, t));
_getSortedUnique(types).forEach((t) => _writeType(buffer, t));
final formattedCode = _formatCode(buffer.toString());
return formattedCode.trim() + '\n'; // Ensure a single trailing newline.
}
Expand Down Expand Up @@ -95,9 +95,27 @@ List<Field> _getAllFields(Interface interface) {
.toList();
}

/// Returns a copy of the list sorted by name.
List<AstNode> _getSorted(List<AstNode> items) {
final sortedList = items.toList();
/// Returns a copy of the list sorted by name with duplicates (by name+type) removed.
List<AstNode> _getSortedUnique(List<AstNode> items) {
final uniqueByName = <String, AstNode>{};
items.forEach((item) {
// It's fine to have the same name used for different types (eg. namespace +
// type alias) but some types are just duplicated entirely in the spec in
// different positions which should not be emitted twice.
final nameTypeKey = '${item.name}|${item.runtimeType}';
if (uniqueByName.containsKey(nameTypeKey)) {
// At the time of writing, there were two duplicated types:
// - TextDocumentSyncKind (same defintion in both places)
// - TextDocumentSyncOptions (first definition is just a subset)
// If this list grows, consider handling this better - or try to have the
// spec updated to be unambigious.
print('WARN: More than one definition for $nameTypeKey.');
}

// Keep the last one as in some cases the first definition is less specific.
uniqueByName[nameTypeKey] = item;
});
final sortedList = uniqueByName.values.toList();
sortedList.sort((item1, item2) => item1.name.compareTo(item2.name));
return sortedList;
}
Expand Down Expand Up @@ -199,7 +217,7 @@ void _writeCanParseMethod(IndentableStringBuffer buffer, Interface interface) {
}
buffer.write('!(');
_writeTypeCheckCondition(
buffer, "obj['${field.name}']", field.type, 'reporter');
buffer, interface, "obj['${field.name}']", field.type, 'reporter');
buffer
..write(')) {')
..indent()
Expand Down Expand Up @@ -302,7 +320,7 @@ void _writeEnumClass(IndentableStringBuffer buffer, Namespace namespace) {
..indent();
if (allowsAnyValue) {
buffer.writeIndentedln('return ');
_writeTypeCheckCondition(buffer, 'obj', typeOfValues, 'reporter');
_writeTypeCheckCondition(buffer, null, 'obj', typeOfValues, 'reporter');
buffer.writeln(';');
} else {
buffer
Expand Down Expand Up @@ -445,7 +463,8 @@ void _writeFromJsonCodeForUnion(

// Dynamic matches all type checks, so only emit it if required.
if (!isDynamic) {
_writeTypeCheckCondition(buffer, valueCode, type, 'nullLspJsonReporter');
_writeTypeCheckCondition(
buffer, null, valueCode, type, 'nullLspJsonReporter');
buffer.write(' ? ');
}

Expand Down Expand Up @@ -609,7 +628,7 @@ void _writeMember(IndentableStringBuffer buffer, Member member) {
}

void _writeMembers(IndentableStringBuffer buffer, List<Member> members) {
_getSorted(members).forEach((m) => _writeMember(buffer, m));
_getSortedUnique(members).forEach((m) => _writeMember(buffer, m));
}

void _writeToJsonFieldsForResponseMessage(
Expand Down Expand Up @@ -685,8 +704,8 @@ void _writeType(IndentableStringBuffer buffer, AstNode type) {
}
}

void _writeTypeCheckCondition(IndentableStringBuffer buffer, String valueCode,
TypeBase type, String reporter) {
void _writeTypeCheckCondition(IndentableStringBuffer buffer,
Interface interface, String valueCode, TypeBase type, String reporter) {
type = resolveTypeAlias(type);

final dartType = type.dartType;
Expand All @@ -703,17 +722,20 @@ void _writeTypeCheckCondition(IndentableStringBuffer buffer, String valueCode,
// 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) => ');
_writeTypeCheckCondition(buffer, 'item', type.elementType, reporter);
_writeTypeCheckCondition(
buffer, interface, 'item', type.elementType, reporter);
buffer.write('))');
}
buffer.write(')');
} else if (type is MapType) {
buffer.write('($valueCode is Map');
if (fullDartType != 'dynamic') {
buffer..write(' && (')..write('$valueCode.keys.every((item) => ');
_writeTypeCheckCondition(buffer, 'item', type.indexType, reporter);
_writeTypeCheckCondition(
buffer, interface, 'item', type.indexType, reporter);
buffer..write('&& $valueCode.values.every((item) => ');
_writeTypeCheckCondition(buffer, 'item', type.valueType, reporter);
_writeTypeCheckCondition(
buffer, interface, 'item', type.valueType, reporter);
buffer.write(')))');
}
buffer.write(')');
Expand All @@ -724,9 +746,18 @@ void _writeTypeCheckCondition(IndentableStringBuffer buffer, String valueCode,
if (i != 0) {
buffer.write(' || ');
}
_writeTypeCheckCondition(buffer, valueCode, type.types[i], reporter);
_writeTypeCheckCondition(
buffer, interface, valueCode, type.types[i], reporter);
}
buffer.write(')');
} else if (interface != null &&
interface.typeArgs != null &&
interface.typeArgs.any((typeArg) => typeArg.lexeme == fullDartType)) {
final comment = '/* $fullDartType.canParse($valueCode) */';
print(
'WARN: Unable to write a type check for $valueCode with generic type $fullDartType. '
'Please review the generated code annotated with $comment');
buffer.write('true $comment');
} else {
throw 'Unable to type check $valueCode against $fullDartType';
}
Expand Down
30 changes: 24 additions & 6 deletions pkg/analysis_server/tool/lsp_spec/typescript_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ class Interface extends AstNode {
this.baseTypes,
this.members,
) : super(comment);

@override
String get name => nameToken.lexeme;
String get nameWithTypeArgs => '$name$typeArgsString';
Expand Down Expand Up @@ -502,7 +503,9 @@ class Parser {
if (includeUndefined) {
types.add(Type.Undefined);
}
var typeIndex = 0;
while (true) {
typeIndex++;
TypeBase type;
if (_match([TokenType.LEFT_BRACE])) {
// Inline interfaces.
Expand All @@ -521,7 +524,12 @@ class Parser {
type = MapType(indexer.indexType, indexer.valueType);
} else {
// Add a synthetic interface to the parsers list of nodes to represent this type.
final generatedName = _joinNames(containerName, fieldName);
// If we have no fieldName to base the synthetic name from, we should use
// the index of this type, for example in:
// type Foo = { [..] } | { [...] }
// we will generate Foo1 and Foo2 for the types.
final nameSuffix = fieldName ?? '$typeIndex';
final generatedName = _joinNames(containerName, nameSuffix);
_nodes.add(InlineInterface(generatedName, members));
// Record the type as a simple type that references this interface.
type = Type.identifier(generatedName);
Expand Down Expand Up @@ -616,7 +624,9 @@ class Parser {
final name = _consume(TokenType.IDENTIFIER, 'Expected identifier');
_consume(TokenType.EQUAL, 'Expected =');
final type = _type(name.lexeme, null);
_consume(TokenType.SEMI_COLON, 'Expected ;');
if (!_isAtEnd) {
_consume(TokenType.SEMI_COLON, 'Expected ;');
}

return TypeAlias(leadingComment, name, type);
}
Expand All @@ -640,8 +650,16 @@ class Scanner {
return _tokens;
}

void _addToken(TokenType type) {
final text = _source.substring(_startOfToken, _currentPos);
void _addToken(TokenType type, {bool mergeSameTypes = false}) {
var text = _source.substring(_startOfToken, _currentPos);

// Consecutive tokens of some types (for example Comments) are merged
// together.
if (mergeSameTypes && _tokens.isNotEmpty && type == _tokens.last.type) {
text = '${_tokens.last.lexeme}\n$text';
_tokens.removeLast();
}

_tokens.add(Token(type, text));
}

Expand Down Expand Up @@ -744,13 +762,13 @@ class Scanner {
_advance();
_advance();
}
_addToken(TokenType.COMMENT);
_addToken(TokenType.COMMENT, mergeSameTypes: true);
} else if (_match('/')) {
// Single line comment.
while (_peek() != '\n' && !_isAtEnd) {
_advance();
}
_addToken(TokenType.COMMENT);
_addToken(TokenType.COMMENT, mergeSameTypes: true);
} else {
_addToken(TokenType.SLASH);
}
Expand Down

0 comments on commit d86fa96

Please sign in to comment.