| // 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/typescript_parser.dart'; |
| import 'matchers.dart'; |
| |
| main() { |
| group('typescript parser', () { |
| test('parses an interface', () { |
| final String input = ''' |
| /** |
| * Some options. |
| */ |
| export interface SomeOptions { |
| /** |
| * Options used by something. |
| */ |
| options?: OptionKind[]; |
| } |
| '''; |
| final List<AstNode> output = parseString(input); |
| expect(output, hasLength(1)); |
| expect(output[0], const TypeMatcher<Interface>()); |
| final Interface interface = output[0]; |
| expect(interface.name, equals('SomeOptions')); |
| expect(interface.commentText, equals('Some options.')); |
| expect(interface.baseTypes, hasLength(0)); |
| expect(interface.members, hasLength(1)); |
| expect(interface.members[0], const TypeMatcher<Field>()); |
| final Field field = interface.members[0]; |
| expect(field.name, equals('options')); |
| expect(field.commentText, equals('''Options used by something.''')); |
| expect(field.allowsNull, isFalse); |
| expect(field.allowsUndefined, isTrue); |
| expect(field.type, isArrayOf(isSimpleType('OptionKind'))); |
| }); |
| |
| test('parses an interface with a field with an inline/unnamed type', () { |
| final String input = ''' |
| export interface Capabilities { |
| textDoc?: { |
| deprecated?: bool; |
| }; |
| } |
| '''; |
| final List<AstNode> output = parseString(input); |
| // Length is two because we'll fabricate the type of textDoc. |
| expect(output, hasLength(2)); |
| |
| // Check there was a full fabricarted interface for this type. |
| expect(output[0], const TypeMatcher<Interface>()); |
| Interface interface = output[0]; |
| expect(interface.name, equals('CapabilitiesTextDoc')); |
| expect(interface.members, hasLength(1)); |
| expect(interface.members[0], const TypeMatcher<Field>()); |
| Field field = interface.members[0]; |
| expect(field.name, equals('deprecated')); |
| expect(field.allowsNull, isFalse); |
| expect(field.allowsUndefined, isTrue); |
| expect(field.type, isSimpleType('bool')); |
| expect(field.allowsUndefined, isTrue); |
| |
| expect(output[1], const TypeMatcher<Interface>()); |
| interface = output[1]; |
| expect(interface.name, equals('Capabilities')); |
| expect(interface.members, hasLength(1)); |
| expect(interface.members[0], const TypeMatcher<Field>()); |
| field = interface.members[0]; |
| expect(field.name, equals('textDoc')); |
| expect(field.allowsNull, isFalse); |
| expect(field.type, isSimpleType('CapabilitiesTextDoc')); |
| }); |
| |
| test('parses an interface with multiple fields', () { |
| final String input = ''' |
| export interface SomeOptions { |
| /** |
| * Options0 used by something. |
| */ |
| options0: any; |
| /** |
| * Options1 used by something. |
| */ |
| options1: any; |
| } |
| '''; |
| final List<AstNode> output = parseString(input); |
| expect(output, hasLength(1)); |
| expect(output[0], const TypeMatcher<Interface>()); |
| final Interface interface = output[0]; |
| expect(interface.members, hasLength(2)); |
| [0, 1].forEach((i) { |
| expect(interface.members[i], const TypeMatcher<Field>()); |
| final Field field = interface.members[i]; |
| expect(field.name, equals('options$i')); |
| expect(field.commentText, equals('''Options$i used by something.''')); |
| }); |
| }); |
| |
| test('parses an interface with type args', () { |
| final String input = ''' |
| interface MyInterface<D> { |
| data?: D; |
| } |
| '''; |
| final List<AstNode> output = parseString(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, isTrue); |
| expect(field.allowsNull, isFalse); |
| expect(field.type, isSimpleType('D')); |
| }); |
| |
| test('parses an interface with Arrays in Array<T> format', () { |
| final String input = ''' |
| export interface MyMessage { |
| /** |
| * The method's params. |
| */ |
| params?: Array<any> | object; |
| } |
| '''; |
| final List<AstNode> output = parseString(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.commentText, equals('''The method's params.''')); |
| expect(field.allowsUndefined, isTrue); |
| expect(field.allowsNull, isFalse); |
| expect(field.type, const TypeMatcher<UnionType>()); |
| UnionType union = field.type; |
| expect(union.types, hasLength(2)); |
| expect(union.types[0], isArrayOf(isSimpleType('any'))); |
| expect(union.types[1], isSimpleType('object')); |
| }); |
| |
| test('parses an interface with a map into a MapType', () { |
| final String input = ''' |
| export interface WorkspaceEdit { |
| changes: { [uri: string]: TextEdit[]; }; |
| } |
| '''; |
| final List<AstNode> output = parseString(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('changes')); |
| expect(field.type, |
| isMapOf(isSimpleType('string'), isArrayOf(isSimpleType('TextEdit')))); |
| }); |
| |
| test('flags nullable undefined values', () { |
| final String input = ''' |
| export interface A { |
| canBeBoth?: string | null; |
| canBeNeither: string; |
| canBeNull: string | null; |
| canBeUndefined?: string; |
| } |
| '''; |
| final List<AstNode> output = parseString(input); |
| final Interface interface = output[0]; |
| expect(interface.members, hasLength(4)); |
| interface.members.forEach((m) => expect(m, const TypeMatcher<Field>())); |
| final Field canBeBoth = interface.members[0], |
| canBeNeither = interface.members[1], |
| canBeNull = interface.members[2], |
| canBeUndefined = interface.members[3]; |
| expect(canBeNeither.allowsNull, isFalse); |
| expect(canBeNeither.allowsUndefined, isFalse); |
| expect(canBeNull.allowsNull, isTrue); |
| expect(canBeNull.allowsUndefined, isFalse); |
| expect(canBeUndefined.allowsNull, isFalse); |
| expect(canBeUndefined.allowsUndefined, isTrue); |
| expect(canBeBoth.allowsNull, isTrue); |
| expect(canBeBoth.allowsUndefined, isTrue); |
| }); |
| |
| test('formats comments correctly', () { |
| final String input = ''' |
| /** |
| * Describes the what this class in lots of words that wrap onto |
| * multiple lines that will need re-wrapping to format nicely when |
| * converted into Dart. |
| * |
| * Blank lines should remain in-tact, as should: |
| * - Indented |
| * - Things |
| * |
| * Some docs have: |
| * - List items that are not indented |
| * |
| * Sometimes after a blank line we'll have a note. |
| * |
| * *Note* that something. |
| */ |
| export interface A { |
| a: a; |
| } |
| '''; |
| final List<AstNode> output = parseString(input); |
| final Interface interface = output[0]; |
| expect(interface.commentText, equals(''' |
| Describes the what this class in lots of words that wrap onto multiple lines that will need re-wrapping to format nicely when converted into Dart. |
| |
| Blank lines should remain in-tact, as should: |
| - Indented |
| - Things |
| |
| Some docs have: |
| - List items that are not indented |
| |
| Sometimes after a blank line we'll have a note. |
| |
| *Note* that something.''')); |
| }); |
| |
| test('parses a type alias', () { |
| final String input = ''' |
| export type DocumentSelector = DocumentFilter[]; |
| '''; |
| final List<AstNode> output = parseString(input); |
| expect(output, hasLength(1)); |
| expect(output[0], const TypeMatcher<TypeAlias>()); |
| final TypeAlias typeAlias = output[0]; |
| expect(typeAlias.name, equals('DocumentSelector')); |
| expect(typeAlias.baseType, isArrayOf(isSimpleType('DocumentFilter'))); |
| }); |
| |
| test('parses a namespace of constants', () { |
| final String input = ''' |
| export namespace ResourceOperationKind { |
| /** |
| * Supports creating new files and folders. |
| */ |
| export const Create: ResourceOperationKind = 'create'; |
| |
| /** |
| * Supports deleting existing files and folders. |
| */ |
| export const Delete: ResourceOperationKind = 'delete'; |
| |
| /** |
| * Supports renaming existing files and folders. |
| */ |
| export const Rename: ResourceOperationKind = 'rename'; |
| } |
| '''; |
| final List<AstNode> output = parseString(input); |
| expect(output, hasLength(1)); |
| expect(output[0], const TypeMatcher<Namespace>()); |
| final Namespace namespace = output[0]; |
| expect(namespace.members, hasLength(3)); |
| namespace.members.forEach((m) => expect(m, const TypeMatcher<Const>())); |
| final Const create = namespace.members[0], |
| delete = namespace.members[1], |
| rename = namespace.members[2]; |
| expect(create.name, equals('Create')); |
| expect(create.type, isSimpleType('ResourceOperationKind')); |
| expect(create.commentText, |
| equals('Supports creating new files and folders.')); |
| expect(rename.name, equals('Rename')); |
| expect(rename.type, isSimpleType('ResourceOperationKind')); |
| expect(rename.commentText, |
| equals('Supports renaming existing files and folders.')); |
| expect(delete.name, equals('Delete')); |
| expect(delete.type, isSimpleType('ResourceOperationKind')); |
| expect(delete.commentText, |
| equals('Supports deleting existing files and folders.')); |
| }); |
| |
| test('parses a tuple in an array', () { |
| final String input = ''' |
| interface SomeInformation { |
| label: string | [number, number]; |
| } |
| '''; |
| final List<AstNode> output = parseString(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('label')); |
| expect(field.type, const TypeMatcher<UnionType>()); |
| UnionType union = field.type; |
| expect(union.types, hasLength(2)); |
| expect(union.types[0], isSimpleType('string')); |
| expect(union.types[1], isArrayOf(isSimpleType('number'))); |
| }); |
| }); |
| } |