Skip to content

Commit d86fa96

Browse files
DanTupcommit-bot@chromium.org
authored andcommitted
Tweaks to be able to parse v3.15 spec
Change-Id: Ifdb8696246b6893b743754dab5344d5939550a10 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/153776 Reviewed-by: Brian Wilkerson <[email protected]>
1 parent 0162e7c commit d86fa96

File tree

3 files changed

+115
-20
lines changed

3 files changed

+115
-20
lines changed

pkg/analysis_server/test/tool/lsp_spec/typescript_test.dart

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,39 @@ export type DocumentSelector = DocumentFilter[];
242242
expect(typeAlias.baseType, isArrayOf(isSimpleType('DocumentFilter')));
243243
});
244244

245+
test('parses a type alias that is a union of unnamed types', () {
246+
final input = '''
247+
export type NameOrLength = { name: string } | { length: number };
248+
''';
249+
final output = parseString(input);
250+
expect(output, hasLength(3));
251+
252+
// Results should be the two inline interfaces followed by the type alias.
253+
254+
expect(output[0], const TypeMatcher<InlineInterface>());
255+
final InlineInterface interface1 = output[0];
256+
expect(interface1.name, equals('NameOrLength1'));
257+
expect(interface1.members, hasLength(1));
258+
expect(interface1.members[0].name, equals('name'));
259+
260+
expect(output[1], const TypeMatcher<InlineInterface>());
261+
final InlineInterface interface2 = output[1];
262+
expect(interface2.name, equals('NameOrLength2'));
263+
expect(interface2.members, hasLength(1));
264+
expect(interface2.members[0].name, equals('length'));
265+
266+
expect(output[2], const TypeMatcher<TypeAlias>());
267+
final TypeAlias typeAlias = output[2];
268+
expect(typeAlias.name, equals('NameOrLength'));
269+
expect(typeAlias.baseType, const TypeMatcher<UnionType>());
270+
271+
// The type alias should be a union of the two types above.
272+
UnionType union = typeAlias.baseType;
273+
expect(union.types, hasLength(2));
274+
expect(union.types[0], isSimpleType(interface1.name));
275+
expect(union.types[1], isSimpleType(interface2.name));
276+
});
277+
245278
test('parses a namespace of constants', () {
246279
final input = '''
247280
export namespace ResourceOperationKind {
@@ -321,5 +354,18 @@ interface SomeInformation {
321354
expect(field.name, equals('label'));
322355
expect(field.type, isSimpleType('object'));
323356
});
357+
358+
test('parses multiple single-line comments into a single token', () {
359+
final input = '''
360+
// This is line 1
361+
// This is line 2
362+
interface SomeInformation {
363+
}
364+
''';
365+
final output = parseString(input);
366+
expect(output, hasLength(1));
367+
expect(output[0].commentNode.token.lexeme, equals('''// This is line 1
368+
// This is line 2'''));
369+
});
324370
});
325371
}

pkg/analysis_server/tool/lsp_spec/codegen_dart.dart

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ bool enumClassAllowsAnyValue(String name) {
3636

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

98-
/// Returns a copy of the list sorted by name.
99-
List<AstNode> _getSorted(List<AstNode> items) {
100-
final sortedList = items.toList();
98+
/// Returns a copy of the list sorted by name with duplicates (by name+type) removed.
99+
List<AstNode> _getSortedUnique(List<AstNode> items) {
100+
final uniqueByName = <String, AstNode>{};
101+
items.forEach((item) {
102+
// It's fine to have the same name used for different types (eg. namespace +
103+
// type alias) but some types are just duplicated entirely in the spec in
104+
// different positions which should not be emitted twice.
105+
final nameTypeKey = '${item.name}|${item.runtimeType}';
106+
if (uniqueByName.containsKey(nameTypeKey)) {
107+
// At the time of writing, there were two duplicated types:
108+
// - TextDocumentSyncKind (same defintion in both places)
109+
// - TextDocumentSyncOptions (first definition is just a subset)
110+
// If this list grows, consider handling this better - or try to have the
111+
// spec updated to be unambigious.
112+
print('WARN: More than one definition for $nameTypeKey.');
113+
}
114+
115+
// Keep the last one as in some cases the first definition is less specific.
116+
uniqueByName[nameTypeKey] = item;
117+
});
118+
final sortedList = uniqueByName.values.toList();
101119
sortedList.sort((item1, item2) => item1.name.compareTo(item2.name));
102120
return sortedList;
103121
}
@@ -199,7 +217,7 @@ void _writeCanParseMethod(IndentableStringBuffer buffer, Interface interface) {
199217
}
200218
buffer.write('!(');
201219
_writeTypeCheckCondition(
202-
buffer, "obj['${field.name}']", field.type, 'reporter');
220+
buffer, interface, "obj['${field.name}']", field.type, 'reporter');
203221
buffer
204222
..write(')) {')
205223
..indent()
@@ -302,7 +320,7 @@ void _writeEnumClass(IndentableStringBuffer buffer, Namespace namespace) {
302320
..indent();
303321
if (allowsAnyValue) {
304322
buffer.writeIndentedln('return ');
305-
_writeTypeCheckCondition(buffer, 'obj', typeOfValues, 'reporter');
323+
_writeTypeCheckCondition(buffer, null, 'obj', typeOfValues, 'reporter');
306324
buffer.writeln(';');
307325
} else {
308326
buffer
@@ -445,7 +463,8 @@ void _writeFromJsonCodeForUnion(
445463

446464
// Dynamic matches all type checks, so only emit it if required.
447465
if (!isDynamic) {
448-
_writeTypeCheckCondition(buffer, valueCode, type, 'nullLspJsonReporter');
466+
_writeTypeCheckCondition(
467+
buffer, null, valueCode, type, 'nullLspJsonReporter');
449468
buffer.write(' ? ');
450469
}
451470

@@ -609,7 +628,7 @@ void _writeMember(IndentableStringBuffer buffer, Member member) {
609628
}
610629

611630
void _writeMembers(IndentableStringBuffer buffer, List<Member> members) {
612-
_getSorted(members).forEach((m) => _writeMember(buffer, m));
631+
_getSortedUnique(members).forEach((m) => _writeMember(buffer, m));
613632
}
614633

615634
void _writeToJsonFieldsForResponseMessage(
@@ -685,8 +704,8 @@ void _writeType(IndentableStringBuffer buffer, AstNode type) {
685704
}
686705
}
687706

688-
void _writeTypeCheckCondition(IndentableStringBuffer buffer, String valueCode,
689-
TypeBase type, String reporter) {
707+
void _writeTypeCheckCondition(IndentableStringBuffer buffer,
708+
Interface interface, String valueCode, TypeBase type, String reporter) {
690709
type = resolveTypeAlias(type);
691710

692711
final dartType = type.dartType;
@@ -703,17 +722,20 @@ void _writeTypeCheckCondition(IndentableStringBuffer buffer, String valueCode,
703722
// TODO(dantup): If we're happy to assume we never have two lists in a union
704723
// we could skip this bit.
705724
buffer.write(' && ($valueCode.every((item) => ');
706-
_writeTypeCheckCondition(buffer, 'item', type.elementType, reporter);
725+
_writeTypeCheckCondition(
726+
buffer, interface, 'item', type.elementType, reporter);
707727
buffer.write('))');
708728
}
709729
buffer.write(')');
710730
} else if (type is MapType) {
711731
buffer.write('($valueCode is Map');
712732
if (fullDartType != 'dynamic') {
713733
buffer..write(' && (')..write('$valueCode.keys.every((item) => ');
714-
_writeTypeCheckCondition(buffer, 'item', type.indexType, reporter);
734+
_writeTypeCheckCondition(
735+
buffer, interface, 'item', type.indexType, reporter);
715736
buffer..write('&& $valueCode.values.every((item) => ');
716-
_writeTypeCheckCondition(buffer, 'item', type.valueType, reporter);
737+
_writeTypeCheckCondition(
738+
buffer, interface, 'item', type.valueType, reporter);
717739
buffer.write(')))');
718740
}
719741
buffer.write(')');
@@ -724,9 +746,18 @@ void _writeTypeCheckCondition(IndentableStringBuffer buffer, String valueCode,
724746
if (i != 0) {
725747
buffer.write(' || ');
726748
}
727-
_writeTypeCheckCondition(buffer, valueCode, type.types[i], reporter);
749+
_writeTypeCheckCondition(
750+
buffer, interface, valueCode, type.types[i], reporter);
728751
}
729752
buffer.write(')');
753+
} else if (interface != null &&
754+
interface.typeArgs != null &&
755+
interface.typeArgs.any((typeArg) => typeArg.lexeme == fullDartType)) {
756+
final comment = '/* $fullDartType.canParse($valueCode) */';
757+
print(
758+
'WARN: Unable to write a type check for $valueCode with generic type $fullDartType. '
759+
'Please review the generated code annotated with $comment');
760+
buffer.write('true $comment');
730761
} else {
731762
throw 'Unable to type check $valueCode against $fullDartType';
732763
}

pkg/analysis_server/tool/lsp_spec/typescript_parser.dart

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ class Interface extends AstNode {
151151
this.baseTypes,
152152
this.members,
153153
) : super(comment);
154+
154155
@override
155156
String get name => nameToken.lexeme;
156157
String get nameWithTypeArgs => '$name$typeArgsString';
@@ -502,7 +503,9 @@ class Parser {
502503
if (includeUndefined) {
503504
types.add(Type.Undefined);
504505
}
506+
var typeIndex = 0;
505507
while (true) {
508+
typeIndex++;
506509
TypeBase type;
507510
if (_match([TokenType.LEFT_BRACE])) {
508511
// Inline interfaces.
@@ -521,7 +524,12 @@ class Parser {
521524
type = MapType(indexer.indexType, indexer.valueType);
522525
} else {
523526
// Add a synthetic interface to the parsers list of nodes to represent this type.
524-
final generatedName = _joinNames(containerName, fieldName);
527+
// If we have no fieldName to base the synthetic name from, we should use
528+
// the index of this type, for example in:
529+
// type Foo = { [..] } | { [...] }
530+
// we will generate Foo1 and Foo2 for the types.
531+
final nameSuffix = fieldName ?? '$typeIndex';
532+
final generatedName = _joinNames(containerName, nameSuffix);
525533
_nodes.add(InlineInterface(generatedName, members));
526534
// Record the type as a simple type that references this interface.
527535
type = Type.identifier(generatedName);
@@ -616,7 +624,9 @@ class Parser {
616624
final name = _consume(TokenType.IDENTIFIER, 'Expected identifier');
617625
_consume(TokenType.EQUAL, 'Expected =');
618626
final type = _type(name.lexeme, null);
619-
_consume(TokenType.SEMI_COLON, 'Expected ;');
627+
if (!_isAtEnd) {
628+
_consume(TokenType.SEMI_COLON, 'Expected ;');
629+
}
620630

621631
return TypeAlias(leadingComment, name, type);
622632
}
@@ -640,8 +650,16 @@ class Scanner {
640650
return _tokens;
641651
}
642652

643-
void _addToken(TokenType type) {
644-
final text = _source.substring(_startOfToken, _currentPos);
653+
void _addToken(TokenType type, {bool mergeSameTypes = false}) {
654+
var text = _source.substring(_startOfToken, _currentPos);
655+
656+
// Consecutive tokens of some types (for example Comments) are merged
657+
// together.
658+
if (mergeSameTypes && _tokens.isNotEmpty && type == _tokens.last.type) {
659+
text = '${_tokens.last.lexeme}\n$text';
660+
_tokens.removeLast();
661+
}
662+
645663
_tokens.add(Token(type, text));
646664
}
647665

@@ -744,13 +762,13 @@ class Scanner {
744762
_advance();
745763
_advance();
746764
}
747-
_addToken(TokenType.COMMENT);
765+
_addToken(TokenType.COMMENT, mergeSameTypes: true);
748766
} else if (_match('/')) {
749767
// Single line comment.
750768
while (_peek() != '\n' && !_isAtEnd) {
751769
_advance();
752770
}
753-
_addToken(TokenType.COMMENT);
771+
_addToken(TokenType.COMMENT, mergeSameTypes: true);
754772
} else {
755773
_addToken(TokenType.SLASH);
756774
}

0 commit comments

Comments
 (0)