Skip to content

Commit

Permalink
Add experimental support for parsing variable definitions in fragments (
Browse files Browse the repository at this point in the history
  • Loading branch information
sam-swarr authored and leebyron committed Dec 14, 2017
1 parent f39b0fd commit ce0a4b9
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 4 deletions.
10 changes: 10 additions & 0 deletions src/language/__tests__/parser-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,16 @@ describe('Parser', () => {
expect(result.loc).to.equal(undefined);
});

it('Experimental: allows parsing fragment defined variables', () => {
const source = new Source(
'fragment a($v: Boolean = false) on t { f(v: $v) }',
);
expect(() =>
parse(source, { experimentalFragmentVariables: true }),
).to.not.throw();
expect(() => parse(source)).to.throw('Syntax Error');
});

it('contains location information that only stringifys start/end', () => {
const source = new Source('{ id }');
const result = parse(source);
Expand Down
16 changes: 16 additions & 0 deletions src/language/__tests__/printer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,22 @@ describe('Printer', () => {
`);
});

it('Experimental: correctly prints fragment defined variables', () => {
const fragmentWithVariable = parse(
`
fragment Foo($a: ComplexType, $b: Boolean = false) on TestType {
id
}
`,
{ experimentalFragmentVariables: true },
);
expect(print(fragmentWithVariable)).to.equal(dedent`
fragment Foo($a: ComplexType, $b: Boolean = false) on TestType {
id
}
`);
});

const kitchenSink = readFileSync(join(__dirname, '/kitchen-sink.graphql'), {
encoding: 'utf8',
});
Expand Down
47 changes: 47 additions & 0 deletions src/language/__tests__/visitor-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,53 @@ describe('Visitor', () => {
]);
});

it('Experimental: visits variables defined in fragments', () => {
const ast = parse('fragment a($v: Boolean = false) on t { f }', {
experimentalFragmentVariables: true,
});
const visited = [];

visit(ast, {
enter(node) {
visited.push(['enter', node.kind, node.value]);
},
leave(node) {
visited.push(['leave', node.kind, node.value]);
},
});

expect(visited).to.deep.equal([
['enter', 'Document', undefined],
['enter', 'FragmentDefinition', undefined],
['enter', 'Name', 'a'],
['leave', 'Name', 'a'],
['enter', 'VariableDefinition', undefined],
['enter', 'Variable', undefined],
['enter', 'Name', 'v'],
['leave', 'Name', 'v'],
['leave', 'Variable', undefined],
['enter', 'NamedType', undefined],
['enter', 'Name', 'Boolean'],
['leave', 'Name', 'Boolean'],
['leave', 'NamedType', undefined],
['enter', 'BooleanValue', false],
['leave', 'BooleanValue', false],
['leave', 'VariableDefinition', undefined],
['enter', 'NamedType', undefined],
['enter', 'Name', 't'],
['leave', 'Name', 't'],
['leave', 'NamedType', undefined],
['enter', 'SelectionSet', undefined],
['enter', 'Field', undefined],
['enter', 'Name', 'f'],
['leave', 'Name', 'f'],
['leave', 'Field', undefined],
['leave', 'SelectionSet', undefined],
['leave', 'FragmentDefinition', undefined],
['leave', 'Document', undefined],
]);
});

const kitchenSink = readFileSync(join(__dirname, '/kitchen-sink.graphql'), {
encoding: 'utf8',
});
Expand Down
3 changes: 3 additions & 0 deletions src/language/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,9 @@ export type FragmentDefinitionNode = {
+kind: 'FragmentDefinition',
+loc?: Location,
+name: NameNode,
// Note: fragment variable definitions are experimental and may be changed
// or removed in the future.
+variableDefinitions?: $ReadOnlyArray<VariableDefinitionNode>,
+typeCondition: NamedTypeNode,
+directives?: $ReadOnlyArray<DirectiveNode>,
+selectionSet: SelectionSetNode,
Expand Down
32 changes: 32 additions & 0 deletions src/language/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,24 @@ export type ParseOptions = {
* disables that behavior for performance or testing.
*/
noLocation?: boolean,

/**
* EXPERIMENTAL:
*
* If enabled, the parser will understand and parse variable definitions
* contained in a fragment definition. They'll be represented in the
* `variableDefinitions` field of the FragmentDefinitionNode.
*
* The syntax is identical to normal, query-defined variables. For example:
*
* fragment A($var: Boolean = false) on T {
* ...
* }
*
* Note: this feature is experimental and may change or be removed in the
* future.
*/
experimentalFragmentVariables?: boolean,
};

/**
Expand Down Expand Up @@ -504,6 +522,20 @@ function parseFragment(
function parseFragmentDefinition(lexer: Lexer<*>): FragmentDefinitionNode {
const start = lexer.token;
expectKeyword(lexer, 'fragment');
// Experimental support for defining variables within fragments changes
// the grammar of FragmentDefinition:
// - fragment FragmentName VariableDefinitions? on TypeCondition Directives? SelectionSet
if (lexer.options.experimentalFragmentVariables) {
return {
kind: FRAGMENT_DEFINITION,
name: parseFragmentName(lexer),
variableDefinitions: parseVariableDefinitions(lexer),
typeCondition: (expectKeyword(lexer, 'on'), parseNamedType(lexer)),
directives: parseDirectives(lexer, false),
selectionSet: parseSelectionSet(lexer),
loc: loc(lexer, start),
};
}
return {
kind: FRAGMENT_DEFINITION,
name: parseFragmentName(lexer),
Expand Down
14 changes: 11 additions & 3 deletions src/language/printer.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,17 @@ const printDocASTReducer = {
' ',
),

FragmentDefinition: ({ name, typeCondition, directives, selectionSet }) =>
`fragment ${name} on ${typeCondition} ` +
wrap('', join(directives, ' '), ' ') +
FragmentDefinition: ({
name,
typeCondition,
variableDefinitions,
directives,
selectionSet,
}) =>
// Note: fragment variable definitions are experimental and may be changed
// or removed in the future.
`fragment ${name}${wrap('(', join(variableDefinitions, ', '), ')')} ` +
`on ${typeCondition} ${wrap('', join(directives, ' '), ' ')}` +
selectionSet,

// Value
Expand Down
10 changes: 9 additions & 1 deletion src/language/visitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,15 @@ export const QueryDocumentKeys = {

FragmentSpread: ['name', 'directives'],
InlineFragment: ['typeCondition', 'directives', 'selectionSet'],
FragmentDefinition: ['name', 'typeCondition', 'directives', 'selectionSet'],
FragmentDefinition: [
'name',
// Note: fragment variable definitions are experimental and may be changed
// or removed in the future.
'variableDefinitions',
'typeCondition',
'directives',
'selectionSet',
],

IntValue: [],
FloatValue: [],
Expand Down

0 comments on commit ce0a4b9

Please sign in to comment.