From d517dcf2355c9ad0e271096710de9f5dcf1596e0 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Tue, 4 Jul 2017 12:46:01 -0400 Subject: [PATCH] Selection set, fields --- lib/src/language/ast/fragment_spread.dart | 7 ++- lib/src/language/ast/selection_set.dart | 7 ++- lib/src/language/parser.dart | 50 +++++++++++++++- test/argument_test.dart | 28 +++++++++ test/directive_test.dart | 32 +++++++++- test/field_test.dart | 72 +++++------------------ test/fragment_spread_test.dart | 59 +++++++++++++++++++ test/selection_set_test.dart | 69 ++++++++++++++++++++++ 8 files changed, 258 insertions(+), 66 deletions(-) create mode 100644 test/fragment_spread_test.dart create mode 100644 test/selection_set_test.dart diff --git a/lib/src/language/ast/fragment_spread.dart b/lib/src/language/ast/fragment_spread.dart index a454a538..c6582ba8 100644 --- a/lib/src/language/ast/fragment_spread.dart +++ b/lib/src/language/ast/fragment_spread.dart @@ -9,10 +9,13 @@ class FragmentSpreadContext extends Node { FragmentSpreadContext(this.ELLIPSIS, this.NAME); + String get name => NAME.text; + @override SourceSpan get span { - SourceLocation end; - return new SourceSpan(ELLIPSIS.span?.start, end, toSource()); + var out = ELLIPSIS.span.union(NAME.span); + if (directives.isEmpty) return out; + return directives.fold(out, (o, d) => o.union(d.span)); } @override diff --git a/lib/src/language/ast/selection_set.dart b/lib/src/language/ast/selection_set.dart index 13c86a91..936d9855 100644 --- a/lib/src/language/ast/selection_set.dart +++ b/lib/src/language/ast/selection_set.dart @@ -10,8 +10,11 @@ class SelectionSetContext extends Node { SelectionSetContext(this.LBRACE, this.RBRACE); @override - SourceSpan get span => - new SourceSpan(LBRACE.span?.start, RBRACE.span?.end, toSource()); + SourceSpan get span { + var out = + selections.fold(LBRACE.span, (out, s) => out.union(s.span)); + return out.union(RBRACE.span); + } @override String toSource() { diff --git a/lib/src/language/parser.dart b/lib/src/language/parser.dart index c59592ff..c0ccb1f4 100644 --- a/lib/src/language/parser.dart +++ b/lib/src/language/parser.dart @@ -38,13 +38,57 @@ class Parser { FragmentDefinitionContext parseFragmentDefinition() {} - FragmentSpreadContext parseFragmentSpread() {} + FragmentSpreadContext parseFragmentSpread() { + if (next(TokenType.ELLIPSIS)) { + var ELLIPSIS = current; + if (next(TokenType.NAME)) { + var NAME = current; + return new FragmentSpreadContext(ELLIPSIS, NAME) + ..directives.addAll(parseDirectives()); + } else + throw new SyntaxError.fromSourceLocation( + 'Expected name in fragment spread.', ELLIPSIS.span.end); + } else + return null; + } InlineFragmentContext parseInlineFragment() {} - SelectionSetContext parseSelectionSet() {} + SelectionSetContext parseSelectionSet() { + if (next(TokenType.LBRACE)) { + var LBRACE = current; + List selections = []; + SelectionContext selection = parseSelection(); - SelectionContext parseSelection() {} + while (selection != null) { + selections.add(selection); + next(TokenType.COMMA); + selection = parseSelection(); + } + + if (next(TokenType.RBRACE)) + return new SelectionSetContext(LBRACE, current) + ..selections.addAll(selections); + else + throw new SyntaxError.fromSourceLocation( + 'Expected "}" after selection set.', + selections.isEmpty ? LBRACE.span.end : selections.last.span.end); + } else + return null; + } + + SelectionContext parseSelection() { + var field = parseField(); + if (field != null) return new SelectionContext(field); + var fragmentSpread = parseFragmentSpread(); + if (fragmentSpread != null) + return new SelectionContext(null, fragmentSpread); + var inlineFragment = parseInlineFragment(); + if (inlineFragment != null) + return new SelectionContext(null, null, inlineFragment); + else + return null; + } FieldContext parseField() { var fieldName = parseFieldName(); diff --git a/test/argument_test.dart b/test/argument_test.dart index 6f37424a..4b173105 100644 --- a/test/argument_test.dart +++ b/test/argument_test.dart @@ -19,6 +19,9 @@ ArgumentContext parseArgument(String text) => parse(text).parseArgument(); Matcher isArgument(String name, value) => new _IsArgument(name, value); +Matcher isArgumentList(List arguments) => + new _IsArgumentList(arguments); + class _IsArgument extends Matcher { final String name; final value; @@ -41,3 +44,28 @@ class _IsArgument extends Matcher { matchState); } } + +class _IsArgumentList extends Matcher { + final List arguments; + + _IsArgumentList(this.arguments); + + @override + Description describe(Description description) { + return description.add('is list of ${arguments.length} argument(s)'); + } + + @override + bool matches(item, Map matchState) { + var args = + item is List ? item : parse(item).parseArguments(); + + if (args.length != arguments.length) return false; + + for (int i = 0; i < args.length; i++) { + if (!arguments[i].matches(args[i], matchState)) return false; + } + + return true; + } +} diff --git a/test/directive_test.dart b/test/directive_test.dart index 55b06546..b4cd716a 100644 --- a/test/directive_test.dart +++ b/test/directive_test.dart @@ -35,6 +35,9 @@ Matcher isDirective(String name, {Matcher valueOrVariable, Matcher argument}) => new _IsDirective(name, valueOrVariable: valueOrVariable, argument: argument); +Matcher isDirectiveList(List directives) => + new _IsDirectiveList(directives); + class _IsDirective extends Matcher { final String name; final Matcher valueOrVariable, argument; @@ -54,8 +57,8 @@ class _IsDirective extends Matcher { } @override - bool matches(String item, Map matchState) { - var directive = parseDirective(item); + bool matches(item, Map matchState) { + var directive = item is DirectiveContext ? item : parseDirective(item); if (directive == null) return false; if (valueOrVariable != null) { if (directive.valueOrVariable == null) @@ -74,3 +77,28 @@ class _IsDirective extends Matcher { return true; } } + +class _IsDirectiveList extends Matcher { + final List directives; + + _IsDirectiveList(this.directives); + + @override + Description describe(Description description) { + return description.add('is list of ${directives.length} directive(s)'); + } + + @override + bool matches(item, Map matchState) { + var args = + item is List ? item : parse(item).parseDirectives(); + + if (args.length != directives.length) return false; + + for (int i = 0; i < args.length; i++) { + if (!directives[i].matches(args[i], matchState)) return false; + } + + return true; + } +} diff --git a/test/field_test.dart b/test/field_test.dart index d04ed305..d10c6ac9 100644 --- a/test/field_test.dart +++ b/test/field_test.dart @@ -3,6 +3,8 @@ import 'package:test/test.dart'; import 'common.dart'; import 'argument_test.dart'; import 'directive_test.dart'; +import 'fragment_spread_test.dart'; +import 'selection_set_test.dart'; import 'value_test.dart'; main() { @@ -45,15 +47,27 @@ main() { test('with directives', () { expect( - 'foo: bar @bar @baz: 2 @quux (one: 1)', + 'foo: bar (a: 2) @bar @baz: 2 @quux (one: 1)', isField( fieldName: isFieldName('foo', alias: 'bar'), + arguments: isArgumentList([isArgument('a', 2)]), directives: isDirectiveList([ isDirective('bar'), isDirective('baz', valueOrVariable: isValue(2)), isDirective('quux', argument: isArgument('one', 1)) ]))); }); + + test('with selection set', () { + expect( + 'foo: bar {baz, ...quux}', + isField( + fieldName: isFieldName('foo', alias: 'bar'), + selectionSet: isSelectionSet([ + isField(fieldName: isFieldName('baz')), + isFragmentSpread('quux') + ]))); + }); }); } @@ -61,12 +75,6 @@ FieldContext parseField(String text) => parse(text).parseField(); FieldNameContext parseFieldName(String text) => parse(text).parseFieldName(); -Matcher isArgumentList(List arguments) => - new _IsArgumentList(arguments); - -Matcher isDirectiveList(List directives) => - new _IsDirectiveList(directives); - Matcher isField( {Matcher fieldName, Matcher arguments, @@ -121,53 +129,3 @@ class _IsFieldName extends Matcher { return fieldName.name == name; } } - -class _IsArgumentList extends Matcher { - final List arguments; - - _IsArgumentList(this.arguments); - - @override - Description describe(Description description) { - return description.add('is list of ${arguments.length} argument(s)'); - } - - @override - bool matches(item, Map matchState) { - var args = - item is List ? item : parse(item).parseArguments(); - - if (args.length != arguments.length) return false; - - for (int i = 0; i < args.length; i++) { - if (!arguments[i].matches(args[i], matchState)) return false; - } - - return true; - } -} - -class _IsDirectiveList extends Matcher { - final List directives; - - _IsDirectiveList(this.directives); - - @override - Description describe(Description description) { - return description.add('is list of ${directives.length} directive(s)'); - } - - @override - bool matches(item, Map matchState) { - var args = - item is List ? item : parse(item).parseDirectives(); - - if (args.length != directives.length) return false; - - for (int i = 0; i < args.length; i++) { - if (!directives[i].matches(args[i], matchState)) return false; - } - - return true; - } -} diff --git a/test/fragment_spread_test.dart b/test/fragment_spread_test.dart new file mode 100644 index 00000000..ae4fcc5f --- /dev/null +++ b/test/fragment_spread_test.dart @@ -0,0 +1,59 @@ +import 'package:graphql_parser/graphql_parser.dart'; +import 'package:test/test.dart'; +import 'common.dart'; +import 'argument_test.dart'; +import 'directive_test.dart'; + +main() { + test('name only', () { + expect(['...foo', '... foo'], everyElement(isFragmentSpread('foo'))); + }); + + test('with directives', () { + expect( + '... foo @bar @baz: 2 @quux(one: 1)', + isFragmentSpread('foo', + directives: isDirectiveList([ + isDirective('bar'), + isDirective('baz', valueOrVariable: equals(2)), + isDirective('quux', argument: isArgument('one', 1)) + ]))); + }); + + test('exceptions', () { + expect(() => parseFragmentSpread('...'), throwsSyntaxError); + }); +} + +FragmentSpreadContext parseFragmentSpread(String text) => + parse(text).parseFragmentSpread(); + +Matcher isFragmentSpread(String name, {Matcher directives}) => + new _IsFragmentSpread(name, directives); + +class _IsFragmentSpread extends Matcher { + final String name; + final Matcher directives; + + _IsFragmentSpread(this.name, this.directives); + + @override + Description describe(Description description) { + if (directives != null) + return directives.describe( + description.add('is a fragment spread named "$name" that also ')); + return description.add('is a fragment spread named "$name"'); + } + + @override + bool matches(item, Map matchState) { + var spread = + item is FragmentSpreadContext ? item : parseFragmentSpread(item); + if (spread == null) return false; + if (spread.name != name) return false; + if (directives != null) + return directives.matches(spread.directives, matchState); + else + return true; + } +} diff --git a/test/selection_set_test.dart b/test/selection_set_test.dart new file mode 100644 index 00000000..1c3a750d --- /dev/null +++ b/test/selection_set_test.dart @@ -0,0 +1,69 @@ +import 'package:graphql_parser/graphql_parser.dart'; +import 'package:test/test.dart'; +import 'common.dart'; +import 'field_test.dart'; +import 'fragment_spread_test.dart'; + +// TODO: Test inline fragment... +main() { + test('empty', () { + expect('{}', isSelectionSet([])); + }); + + test('with commas', () { + expect( + '{foo, bar: baz}', + isSelectionSet([ + isField(fieldName: isFieldName('foo')), + isField(fieldName: isFieldName('bar', alias: 'baz')) + ])); + }); + + test('no commas', () { + expect( + '{foo bar: baz ...quux}', + isSelectionSet([ + isField(fieldName: isFieldName('foo')), + isField(fieldName: isFieldName('bar', alias: 'baz')), + isFragmentSpread('quux') + ])); + }); + + test('exceptions', () { + expect(() => parseSelectionSet('{foo,bar,baz'), throwsSyntaxError); + }); +} + +SelectionSetContext parseSelectionSet(String text) => + parse(text).parseSelectionSet(); + +Matcher isSelectionSet(List selections) => + new _IsSelectionSet(selections); + +class _IsSelectionSet extends Matcher { + final List selections; + + _IsSelectionSet(this.selections); + + @override + Description describe(Description description) { + return description + .add('is selection set with ${selections.length} selection(s)'); + } + + @override + bool matches(item, Map matchState) { + var set = item is SelectionSetContext ? item : parseSelectionSet(item); + if (set == null) return false; + if (set.selections.length != selections.length) return false; + + for (int i = 0; i < set.selections.length; i++) { + var sel = set.selections[i]; + if (!selections[i].matches( + sel.field ?? sel.fragmentSpread ?? sel.inlineFragment, matchState)) + return false; + } + + return true; + } +}