Selection set, fields
This commit is contained in:
parent
3be644b96f
commit
d517dcf235
8 changed files with 258 additions and 66 deletions
|
@ -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<SourceSpan>(out, (o, d) => o.union(d.span));
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -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<SourceSpan>(LBRACE.span, (out, s) => out.union(s.span));
|
||||
return out.union(RBRACE.span);
|
||||
}
|
||||
|
||||
@override
|
||||
String toSource() {
|
||||
|
|
|
@ -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<SelectionContext> 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();
|
||||
|
|
|
@ -19,6 +19,9 @@ ArgumentContext parseArgument(String text) => parse(text).parseArgument();
|
|||
|
||||
Matcher isArgument(String name, value) => new _IsArgument(name, value);
|
||||
|
||||
Matcher isArgumentList(List<Matcher> 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<Matcher> 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<ArgumentContext> ? 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,9 @@ Matcher isDirective(String name, {Matcher valueOrVariable, Matcher argument}) =>
|
|||
new _IsDirective(name,
|
||||
valueOrVariable: valueOrVariable, argument: argument);
|
||||
|
||||
Matcher isDirectiveList(List<Matcher> 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<Matcher> 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<DirectiveContext> ? 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Matcher> arguments) =>
|
||||
new _IsArgumentList(arguments);
|
||||
|
||||
Matcher isDirectiveList(List<Matcher> 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<Matcher> 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<ArgumentContext> ? 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<Matcher> 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<DirectiveContext> ? 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;
|
||||
}
|
||||
}
|
||||
|
|
59
test/fragment_spread_test.dart
Normal file
59
test/fragment_spread_test.dart
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
69
test/selection_set_test.dart
Normal file
69
test/selection_set_test.dart
Normal file
|
@ -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<Matcher> selections) =>
|
||||
new _IsSelectionSet(selections);
|
||||
|
||||
class _IsSelectionSet extends Matcher {
|
||||
final List<Matcher> 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;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue