diff --git a/.idea/runConfigurations/tests_in_value_test_dart.xml b/.idea/runConfigurations/tests_in_value_test_dart.xml new file mode 100644 index 00000000..58aca4ef --- /dev/null +++ b/.idea/runConfigurations/tests_in_value_test_dart.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/graphql_parser/CHANGELOG.md b/graphql_parser/CHANGELOG.md new file mode 100644 index 00000000..5602c605 --- /dev/null +++ b/graphql_parser/CHANGELOG.md @@ -0,0 +1,9 @@ +# 1.1.0 +* Removed `GraphQLVisitor`. +* Enable parsing operations without an explicit +name. +* Parse `null`. +* Completely ignore commas. +* Ignore Unicode BOM, as per the spec. +* Parse object values. +* Parse enum values. \ No newline at end of file diff --git a/graphql_parser/README.md b/graphql_parser/README.md index 9d7ea6bc..56a23f79 100644 --- a/graphql_parser/README.md +++ b/graphql_parser/README.md @@ -1,8 +1,8 @@ # graphql_parser [![Pub](https://img.shields.io/pub/v/graphql_parser.svg)](https://pub.dartlang.org/packages/graphql_parser) -[![build status](https://travis-ci.org/thosakwe/graphql_parser.svg)](https://travis-ci.org/thosakwe/graphql_parser) +[![build status](https://travis-ci.org/angel-dart/graphql.svg)](https://travis-ci.org/angel-dart/graphql) -Parses GraphQL queries and schemas. Also includes a `GraphQLVisitor` class. +Parses GraphQL queries and schemas. *This library is merely a parser/visitor*. Any sort of actual GraphQL API functionality must be implemented by you, or by a third-party package. @@ -24,6 +24,9 @@ dependencies: The AST featured in this library is directly based off this ANTLR4 grammar created by Joseph T. McBride: https://github.com/antlr/grammars-v4/blob/master/graphql/GraphQL.g4 +It has since been updated to reflect upon the grammar in the official GraphQL +specification. + ```dart import 'package:graphql_parser/graphql_parser.dart'; @@ -31,6 +34,10 @@ doSomething(String text) { var tokens = scan(text); var parser = new Parser(tokens); + if (parser.errors.isNotEmpty) { + // Handle errors... + } + // Parse the GraphQL document using recursive descent var doc = parser.parseDocument(); diff --git a/graphql_parser/example/example.dart b/graphql_parser/example/example.dart index 4a842895..34566298 100644 --- a/graphql_parser/example/example.dart +++ b/graphql_parser/example/example.dart @@ -1,6 +1,6 @@ import 'package:graphql_parser/graphql_parser.dart'; -final String INPUT = ''' +final String text = ''' { project(name: "GraphQL") { tagline @@ -10,7 +10,7 @@ final String INPUT = ''' .trim(); main() { - var tokens = scan(INPUT); + var tokens = scan(text); var parser = new Parser(tokens); var doc = parser.parseDocument(); diff --git a/graphql_parser/example/visitor.dart b/graphql_parser/example/visitor.dart deleted file mode 100644 index c9301037..00000000 --- a/graphql_parser/example/visitor.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:graphql_parser/graphql_parser.dart'; -import 'package:graphql_parser/visitor.dart'; - -const String QUERY = ''' -{ - foo, - baz: bar -} -'''; - -const Map DATA = const { - 'foo': 'hello', - 'bar': 'world', - 'quux': 'extraneous' -}; - -main() { - // Highly-simplified querying example... - var result = new MapQuerier(DATA).execute(QUERY); - print(result); // { foo: hello, baz: world } - print(result['foo']); // hello - print(result['baz']); // world -} - -class MapQuerier extends GraphQLVisitor { - final Map data; - final Map result = {}; - - MapQuerier(this.data); - - Map execute(String query) { - var doc = new Parser(scan(query)).parseDocument(); - visitDocument(doc); - return result; - } - - @override - visitField(FieldContext ctx) { - String realName, alias; - if (ctx.fieldName.alias == null) - realName = alias = ctx.fieldName.name; - else { - realName = ctx.fieldName.alias.name; - alias = ctx.fieldName.alias.alias; - } - - // Set output field... - result[alias] = data[realName]; - } -} diff --git a/graphql_parser/lib/visitor.dart b/graphql_parser/lib/visitor.dart deleted file mode 100644 index 8fa22936..00000000 --- a/graphql_parser/lib/visitor.dart +++ /dev/null @@ -1,125 +0,0 @@ -import 'graphql_parser.dart'; - -class GraphQLVisitor { - visitDocument(DocumentContext ctx) { - ctx.definitions.forEach(visitDefinition); - } - - visitDefinition(DefinitionContext ctx) { - if (ctx is OperationDefinitionContext) - visitOperationDefinition(ctx); - else if (ctx is FragmentDefinitionContext) visitFragmentDefinition(ctx); - } - - visitOperationDefinition(OperationDefinitionContext ctx) { - if (ctx.variableDefinitions != null) - visitVariableDefinitions(ctx.variableDefinitions); - ctx.directives.forEach(visitDirective); - visitSelectionSet(ctx.selectionSet); - } - - visitFragmentDefinition(FragmentDefinitionContext ctx) { - visitTypeCondition(ctx.typeCondition); - ctx.directives.forEach(visitDirective); - visitSelectionSet(ctx.selectionSet); - } - - visitSelectionSet(SelectionSetContext ctx) { - ctx.selections.forEach(visitSelection); - } - - visitSelection(SelectionContext ctx) { - if (ctx.field != null) visitField(ctx.field); - if (ctx.fragmentSpread != null) visitFragmentSpread(ctx.fragmentSpread); - if (ctx.inlineFragment != null) visitInlineFragment(ctx.inlineFragment); - } - - visitInlineFragment(InlineFragmentContext ctx) { - visitTypeCondition(ctx.typeCondition); - ctx.directives.forEach(visitDirective); - visitSelectionSet(ctx.selectionSet); - } - - visitFragmentSpread(FragmentSpreadContext ctx) { - ctx.directives.forEach(visitDirective); - } - - visitField(FieldContext ctx) { - visitFieldName(ctx.fieldName); - ctx.arguments.forEach(visitArgument); - ctx.directives.forEach(visitDirective); - if (ctx.selectionSet != null) ; - visitSelectionSet(ctx.selectionSet); - } - - visitFieldName(FieldNameContext ctx) { - if (ctx.alias != null) visitAlias(ctx.alias); - } - - visitAlias(AliasContext ctx) {} - - visitDirective(DirectiveContext ctx) { - if (ctx.valueOrVariable != null) visitValueOrVariable(ctx.valueOrVariable); - if (ctx.argument != null) visitArgument(ctx.argument); - } - - visitArgument(ArgumentContext ctx) { - visitValueOrVariable(ctx.valueOrVariable); - } - - visitVariableDefinitions(VariableDefinitionsContext ctx) { - ctx.variableDefinitions.forEach(visitVariableDefinition); - } - - visitVariableDefinition(VariableDefinitionContext ctx) { - visitVariable(ctx.variable); - visitType(ctx.type); - if (ctx.defaultValue != null) visitDefaultValue(ctx.defaultValue); - } - - visitVariable(VariableContext ctx) {} - - visitValueOrVariable(ValueOrVariableContext ctx) { - if (ctx.variable != null) visitVariable(ctx.variable); - if (ctx.value != null) visitValue(ctx.value); - } - - visitDefaultValue(DefaultValueContext ctx) { - visitValue(ctx.value); - } - - visitValue(ValueContext ctx) { - if (ctx is StringValueContext) - visitStringValue(ctx); - else if (ctx is NumberValueContext) - visitNumberValue(ctx); - else if (ctx is BooleanValueContext) - visitBooleanValue(ctx); - else if (ctx is ListValueContext) visitArrayValue(ctx); - } - - visitStringValue(StringValueContext ctx) {} - - visitBooleanValue(BooleanValueContext ctx) {} - - visitNumberValue(NumberValueContext ctx) {} - - visitArrayValue(ListValueContext ctx) { - ctx.values.forEach(visitValue); - } - - visitTypeCondition(TypeConditionContext ctx) { - visitTypeName(ctx.typeName); - } - - visitType(TypeContext ctx) { - if (ctx.typeName != null) visitTypeName(ctx.typeName); - if (ctx.listType != null) visitListType(ctx.listType); - } - - visitListType(ListTypeContext ctx) { - visitType(ctx.type); - } - - visitTypeName(TypeNameContext ctx) {} -} diff --git a/graphql_parser/pubspec.yaml b/graphql_parser/pubspec.yaml index 0bf80c70..40cda92f 100644 --- a/graphql_parser/pubspec.yaml +++ b/graphql_parser/pubspec.yaml @@ -1,5 +1,5 @@ name: graphql_parser -version: 1.0.0+1 +version: 1.1.0 description: Parses GraphQL queries and schemas. author: Tobe O homepage: https://github.com/thosakwe/graphql_parser diff --git a/graphql_parser/test/argument_test.dart b/graphql_parser/test/argument_test.dart index f6d53a82..ba473144 100644 --- a/graphql_parser/test/argument_test.dart +++ b/graphql_parser/test/argument_test.dart @@ -1,6 +1,7 @@ import 'package:graphql_parser/graphql_parser.dart'; import 'package:matcher/matcher.dart'; import 'package:test/test.dart'; + import 'common.dart'; main() { @@ -10,13 +11,24 @@ main() { }); test('exception', () { - expect(() => parseArgument('foo'), throwsSyntaxError); - expect(() => parseArgument('foo:'), throwsSyntaxError); - expect(() => parseArgumentList(r'(foo: $bar'), throwsSyntaxError); + var isSyntaxError = predicate((x) { + var parser = parse(x.toString())..parseArgument(); + return parser.errors.isNotEmpty; + }, 'fails to parse argument'); + + var isSyntaxErrorOnArguments = predicate((x) { + var parser = parse(x.toString())..parseArguments(); + return parser.errors.isNotEmpty; + }, 'fails to parse arguments'); + + expect('foo', isSyntaxError); + expect('foo:', isSyntaxError); + expect(r'(foo: $bar', isSyntaxErrorOnArguments); }); } ArgumentContext parseArgument(String text) => parse(text).parseArgument(); + List parseArgumentList(String text) => parse(text).parseArguments(); diff --git a/graphql_parser/test/common.dart b/graphql_parser/test/common.dart index 5551d83f..50194027 100644 --- a/graphql_parser/test/common.dart +++ b/graphql_parser/test/common.dart @@ -2,7 +2,4 @@ import 'package:graphql_parser/graphql_parser.dart'; import 'package:matcher/matcher.dart'; import 'package:test/test.dart'; -Parser parse(String text) => new Parser(scan(text)); - -final Matcher throwsSyntaxError = - throwsA(predicate((x) => x is SyntaxError, 'is a syntax error')); +Parser parse(String text) => new Parser(scan(text)); \ No newline at end of file diff --git a/graphql_parser/test/directive_test.dart b/graphql_parser/test/directive_test.dart index 511ca4f9..c1412a0f 100644 --- a/graphql_parser/test/directive_test.dart +++ b/graphql_parser/test/directive_test.dart @@ -21,11 +21,16 @@ main() { }); test('exceptions', () { - expect(() => parseDirective('@'), throwsSyntaxError); - expect(() => parseDirective('@foo:'), throwsSyntaxError); - expect(() => parseDirective('@foo ('), throwsSyntaxError); - expect(() => parseDirective('@foo (bar: 2'), throwsSyntaxError); - expect(() => parseDirective('@foo ()'), throwsSyntaxError); + var isSyntaxError = predicate((x) { + var parser = parse(x.toString())..parseDirective(); + return parser.errors.isNotEmpty; + }, 'fails to parse directive'); + + expect('@', isSyntaxError); + expect('@foo:', isSyntaxError); + expect('@foo (', isSyntaxError); + expect('@foo (bar: 2', isSyntaxError); + expect('@foo (', isSyntaxError); }); } diff --git a/graphql_parser/test/document_test.dart b/graphql_parser/test/document_test.dart index 2b882232..914f6b43 100644 --- a/graphql_parser/test/document_test.dart +++ b/graphql_parser/test/document_test.dart @@ -1,5 +1,6 @@ import 'package:graphql_parser/graphql_parser.dart'; import 'package:test/test.dart'; + import 'common.dart'; import 'directive_test.dart'; import 'field_test.dart'; @@ -34,14 +35,15 @@ main() { }); test('fragment exceptions', () { - expect( - () => parse('fragment').parseFragmentDefinition(), throwsSyntaxError); - expect(() => parse('fragment foo').parseFragmentDefinition(), - throwsSyntaxError); - expect(() => parse('fragment foo on').parseFragmentDefinition(), - throwsSyntaxError); - expect(() => parse('fragment foo on bar').parseFragmentDefinition(), - throwsSyntaxError); + var throwsSyntaxError = predicate((x) { + var parser = parse(x.toString())..parseFragmentDefinition(); + return parser.errors.isNotEmpty; + }, 'fails to parse fragment definition'); + + expect('fragment', throwsSyntaxError); + expect('fragment foo', throwsSyntaxError); + expect('fragment foo on', throwsSyntaxError); + expect('fragment foo on bar', throwsSyntaxError); }); group('operation', () { @@ -59,6 +61,20 @@ main() { ])); }); + test('mutation', () { + var op = parse('mutation {foo, bar: baz}').parseOperationDefinition(); + expect(op.variableDefinitions, isNull); + expect(op.isQuery, isFalse); + expect(op.isMutation, isTrue); + expect(op.name, isNull); + expect( + op.selectionSet, + isSelectionSet([ + isField(fieldName: isFieldName('foo')), + isField(fieldName: isFieldName('bar', alias: 'baz')) + ])); + }); + test('with operation type', () { var doc = parse(r'query foo ($one: [int] = 2) @foo @bar: 2 {foo, bar: baz}') @@ -91,10 +107,13 @@ main() { }); test('exceptions', () { - expect( - () => parse('query').parseOperationDefinition(), throwsSyntaxError); - expect(() => parse('query foo()').parseOperationDefinition(), - throwsSyntaxError); + var throwsSyntaxError = predicate((x) { + var parser = parse(x.toString())..parseOperationDefinition(); + return parser.errors.isNotEmpty; + }, 'fails to parse operation definition'); + + expect('query', throwsSyntaxError); + expect('query foo()', throwsSyntaxError); }); }); } diff --git a/graphql_parser/test/field_test.dart b/graphql_parser/test/field_test.dart index 3efd91a6..a0b3634e 100644 --- a/graphql_parser/test/field_test.dart +++ b/graphql_parser/test/field_test.dart @@ -1,7 +1,8 @@ import 'package:graphql_parser/graphql_parser.dart'; import 'package:test/test.dart'; -import 'common.dart'; + import 'argument_test.dart'; +import 'common.dart'; import 'directive_test.dart'; import 'fragment_spread_test.dart'; import 'selection_set_test.dart'; @@ -16,7 +17,12 @@ main() { expect('foo: bar', isFieldName('foo', alias: 'bar')); }); test('exceptions', () { - expect(() => parseFieldName('foo:'), throwsSyntaxError); + var throwsSyntaxError = predicate((x) { + var parser = parse(x.toString())..parseFieldName(); + return parser.errors.isNotEmpty; + }, 'fails to parse field name'); + + expect('foo:', throwsSyntaxError); }); }); diff --git a/graphql_parser/test/inline_fragment_test.dart b/graphql_parser/test/inline_fragment_test.dart index 2ec374fb..5ad6fbca 100644 --- a/graphql_parser/test/inline_fragment_test.dart +++ b/graphql_parser/test/inline_fragment_test.dart @@ -31,10 +31,14 @@ main() { }); test('exceptions', () { - expect(() => parseInlineFragment('... on foo'), throwsSyntaxError); - expect(() => parseInlineFragment('... on foo @bar'), throwsSyntaxError); - expect(() => parseInlineFragment('... on'), throwsSyntaxError); - expect(() => parseInlineFragment('...'), throwsSyntaxError); + var throwsSyntaxError = predicate((x) { + var parser = parse(x.toString())..parseInlineFragment(); + return parser.errors.isNotEmpty; + }, 'fails to parse inline fragment'); + expect('... on foo', throwsSyntaxError); + expect('... on foo @bar', throwsSyntaxError); + expect('... on', throwsSyntaxError); + expect('...', throwsSyntaxError); }); } diff --git a/graphql_parser/test/selection_set_test.dart b/graphql_parser/test/selection_set_test.dart index 39497226..fd7a094e 100644 --- a/graphql_parser/test/selection_set_test.dart +++ b/graphql_parser/test/selection_set_test.dart @@ -43,7 +43,12 @@ main() { }); test('exceptions', () { - expect(() => parseSelectionSet('{foo,bar,baz'), throwsSyntaxError); + var throwsSyntaxError = predicate((x) { + var parser = parse(x.toString())..parseSelectionSet(); + return parser.errors.isNotEmpty; + }, 'fails to parse selection set'); + + expect('{foo,bar,baz', throwsSyntaxError); }); } diff --git a/graphql_parser/test/type_test.dart b/graphql_parser/test/type_test.dart index f723b3c7..7fa12109 100644 --- a/graphql_parser/test/type_test.dart +++ b/graphql_parser/test/type_test.dart @@ -35,8 +35,13 @@ main() { }); test('exceptions', () { - expect(() => parseType('[foo'), throwsSyntaxError); - expect(() => parseType('['), throwsSyntaxError); + var throwsSyntaxError = predicate((x) { + var parser = parse(x.toString())..parseType(); + return parser.errors.isNotEmpty; + }, 'fails to parse type'); + + expect('[foo', throwsSyntaxError); + expect('[', throwsSyntaxError); }); }); } diff --git a/graphql_parser/test/value_test.dart b/graphql_parser/test/value_test.dart index 640c5430..61e90f48 100644 --- a/graphql_parser/test/value_test.dart +++ b/graphql_parser/test/value_test.dart @@ -1,6 +1,8 @@ import 'dart:math' as math; + import 'package:graphql_parser/graphql_parser.dart'; import 'package:test/test.dart'; + import 'common.dart'; main() { @@ -40,12 +42,38 @@ main() { expect('"\\u0123\\u4567"', isValue('\u0123\u4567')); }); + test('block string', () { + expect('""""""', isValue('')); + expect('"""abc"""', isValue('abc')); + expect('"""\n\n\nabc\n\n\n"""', isValue('abc')); + }); + + test('object', () { + expect('{}', isValue({})); + expect('{a: 2}', isValue({'a': 2})); + expect('{a: 2, b: "c"}', isValue({'a': 2, 'b': 'c'})); + }); + + test('null', () { + expect('null', isValue(null)); + }); + + test('enum', () { + expect('FOO_BAR', isValue('FOO_BAR')); + }); + test('exceptions', () { - expect(() => parseValue('[1'), throwsSyntaxError); + var throwsSyntaxError = predicate((x) { + var parser = parse(x.toString())..parseValue(); + return parser.errors.isNotEmpty; + }, 'fails to parse value'); + + expect('[1', throwsSyntaxError); }); } ValueContext parseValue(String text) => parse(text).parseValue(); + Matcher isValue(value) => new _IsValue(value); class _IsValue extends Matcher { diff --git a/graphql_parser/test/variable_definition_test.dart b/graphql_parser/test/variable_definition_test.dart index 0799bf35..04f85987 100644 --- a/graphql_parser/test/variable_definition_test.dart +++ b/graphql_parser/test/variable_definition_test.dart @@ -1,5 +1,6 @@ import 'package:graphql_parser/graphql_parser.dart'; import 'package:test/test.dart'; + import 'common.dart'; import 'type_test.dart'; import 'value_test.dart'; @@ -18,11 +19,21 @@ main() { }); test('exceptions', () { - expect(() => parseVariableDefinition(r'$foo'), throwsSyntaxError); - expect(() => parseVariableDefinition(r'$foo:'), throwsSyntaxError); - expect(() => parseVariableDefinition(r'$foo: int ='), throwsSyntaxError); - expect(() => parse(r'($foo: int = 2').parseVariableDefinitions(), - throwsSyntaxError); + var throwsSyntaxError = predicate((x) { + var parser = parse(x.toString())..parseVariableDefinition(); + return parser.errors.isNotEmpty; + }, 'fails to parse variable definition'); + + var throwsSyntaxErrorOnDefinitions = predicate((x) { + var parser = parse(x.toString())..parseVariableDefinitions(); + return parser.errors.isNotEmpty; + }, 'fails to parse variable definitions'); + + expect(r'$foo', throwsSyntaxError); + expect(r'$foo:', throwsSyntaxError); + expect(r'$foo: int =', throwsSyntaxError); + + expect(r'($foo: int = 2', throwsSyntaxErrorOnDefinitions); }); } diff --git a/graphql_parser/test/variable_test.dart b/graphql_parser/test/variable_test.dart index ac7aa566..d4484666 100644 --- a/graphql_parser/test/variable_test.dart +++ b/graphql_parser/test/variable_test.dart @@ -1,4 +1,5 @@ import 'package:test/test.dart'; + import 'common.dart'; main() { @@ -12,7 +13,12 @@ main() { }); test('exceptions', () { - expect(() => parse(r'$').parseVariable(), throwsSyntaxError); + var throwsSyntaxError = predicate((x) { + var parser = parse(x.toString())..parseVariable(); + return parser.errors.isNotEmpty; + }, 'fails to parse variable'); + + expect(r'$', throwsSyntaxError); }); }