From 921dca059936c64484e23ff404bb6bf37a64455c Mon Sep 17 00:00:00 2001 From: Mayank Agarwal <mayank1791989@gmail.com> Date: Sat, 3 Jun 2017 11:43:00 +0530 Subject: [PATCH] feat(QueryParser): add support for interpolation at document level. apollo client uses interpolation to include fragments definitions see http://dev.apollodata.com/react/fragments.html --- src/query/_shared/Parsers/QueryParser.js | 40 ++++++++++--------- .../getTokenAtPosition.test.js.snap | 18 ++++----- .../parseQuery_toQueryDocument.test.js.snap | 14 +++++++ .../parseQuery_toQueryDocument.test.js | 23 +++++++++++ src/query/_shared/parseQuery.js | 2 +- 5 files changed, 69 insertions(+), 28 deletions(-) diff --git a/src/query/_shared/Parsers/QueryParser.js b/src/query/_shared/Parsers/QueryParser.js index 76ce508..e4111ed 100644 --- a/src/query/_shared/Parsers/QueryParser.js +++ b/src/query/_shared/Parsers/QueryParser.js @@ -8,34 +8,36 @@ import { import { type Stream, type TokenState } from '../../../shared/types'; import invariant from 'invariant'; -function JSInlineFragment() { +type InterpolationState = { count: number, style: string }; + +function Interpolation(style: string) { return { - style: '', + style: '', // NOTE: should be empty match(token) { return token.value === '${'; }, update(state) { - state.jsInlineFragment = { count: 1 }; // count is number of open curly braces + state.interpolation = { count: 1, style }; // count is number of open curly braces }, }; } -function eatJSInlineFragment(stream, state) { - const { jsInlineFragment: frag } = state; - invariant(frag, 'missing JSInlineFragment'); +function eatInterpolation(stream, state) { + const { interpolation } = state; + invariant(interpolation, 'missing interpolation field in state'); stream.eatWhile((ch) => { - if (frag.count === 0) { - state.jsInlineFragment = null; + if (interpolation.count === 0) { + state.interpolation = null; return false; } if (!ch) { return false; } // eol if (ch === '}') { - frag.count -= 1; + interpolation.count -= 1; } if (ch === '{') { - frag.count += 1; + interpolation.count += 1; } return true; }); @@ -51,9 +53,6 @@ const parserOptions = { parseRules: { ...ParseRules, - // relay only one definition per Relay.QL - // Document: ['Definition'], - // only query, mutation and fragment possible in Relay.QL Definition(token) { switch (token.value) { @@ -65,6 +64,8 @@ const parserOptions = { return 'Subscription'; case 'fragment': return 'FragmentDefinition'; + case '${': + return 'DocumentInterpolation'; default: return null; } @@ -77,7 +78,8 @@ const parserOptions = { return ParseRules.Selection(token, stream); }, - JSInlineFragment: [JSInlineFragment()], + JSInlineFragment: [Interpolation('js-frag')], + DocumentInterpolation: [Interpolation('ws-2')], }, }; @@ -92,11 +94,13 @@ export default class QueryParser { return this._parser.startState(); } - token(stream: Stream, state: TokenState) { - if (state.jsInlineFragment) { - eatJSInlineFragment(stream, state); + token(stream: Stream, state: TokenState & { interpolation: ?InterpolationState }) { + if (state.interpolation) { + const { style } = state.interpolation; + // NOTE: eatInterpolation mutate both stream and state + eatInterpolation(stream, state); stream._start -= 2; // to include '${' in token - return 'js-frag'; + return style; } return this._parser.token(stream, state); diff --git a/src/query/_shared/__tests__/__snapshots__/getTokenAtPosition.test.js.snap b/src/query/_shared/__tests__/__snapshots__/getTokenAtPosition.test.js.snap index c9d7140..f0969fd 100644 --- a/src/query/_shared/__tests__/__snapshots__/getTokenAtPosition.test.js.snap +++ b/src/query/_shared/__tests__/__snapshots__/getTokenAtPosition.test.js.snap @@ -118,7 +118,7 @@ Object { "prevChar": "a", "start": 78, "state": Object { - "jsInlineFragment": null, + "interpolation": null, "kind": "Field", "level": 0, "levels": Array [ @@ -128,7 +128,7 @@ Object { "needsAdvance": true, "needsSeperator": false, "prevState": Object { - "jsInlineFragment": null, + "interpolation": null, "kind": "Selection", "level": 0, "levels": Array [ @@ -138,7 +138,7 @@ Object { "needsAdvance": false, "needsSeperator": false, "prevState": Object { - "jsInlineFragment": null, + "interpolation": null, "kind": "SelectionSet", "level": 0, "levels": Array [ @@ -267,7 +267,7 @@ Object { "prevChar": "a", "start": 62, "state": Object { - "jsInlineFragment": null, + "interpolation": null, "kind": "Field", "level": 0, "levels": Array [ @@ -277,7 +277,7 @@ Object { "needsAdvance": true, "needsSeperator": false, "prevState": Object { - "jsInlineFragment": null, + "interpolation": null, "kind": "Selection", "level": 0, "levels": Array [ @@ -287,7 +287,7 @@ Object { "needsAdvance": false, "needsSeperator": false, "prevState": Object { - "jsInlineFragment": null, + "interpolation": null, "kind": "SelectionSet", "level": 0, "levels": Array [ @@ -416,7 +416,7 @@ Object { "prevChar": "a", "start": 111, "state": Object { - "jsInlineFragment": null, + "interpolation": null, "kind": "Field", "level": 0, "levels": Array [ @@ -426,7 +426,7 @@ Object { "needsAdvance": true, "needsSeperator": false, "prevState": Object { - "jsInlineFragment": null, + "interpolation": null, "kind": "Selection", "level": 0, "levels": Array [ @@ -436,7 +436,7 @@ Object { "needsAdvance": false, "needsSeperator": false, "prevState": Object { - "jsInlineFragment": null, + "interpolation": null, "kind": "SelectionSet", "level": 0, "levels": Array [ diff --git a/src/query/_shared/__tests__/__snapshots__/parseQuery_toQueryDocument.test.js.snap b/src/query/_shared/__tests__/__snapshots__/parseQuery_toQueryDocument.test.js.snap index eaa046e..be450c9 100644 --- a/src/query/_shared/__tests__/__snapshots__/parseQuery_toQueryDocument.test.js.snap +++ b/src/query/_shared/__tests__/__snapshots__/parseQuery_toQueryDocument.test.js.snap @@ -25,6 +25,20 @@ exports[`add dummy fragment name if missing (relay) and \`on\` in next line 1`] " `; +exports[`allow template string interpolation at document level 1`] = ` +" + + fragment FeedEntry on Entry { + commentCount + ...VoteButtons + ...RepoInfo + } + + + + " +`; + exports[`extract embedded queries 1`] = ` " diff --git a/src/query/_shared/__tests__/parseQuery_toQueryDocument.test.js b/src/query/_shared/__tests__/parseQuery_toQueryDocument.test.js index d09fb75..885b770 100644 --- a/src/query/_shared/__tests__/parseQuery_toQueryDocument.test.js +++ b/src/query/_shared/__tests__/parseQuery_toQueryDocument.test.js @@ -212,3 +212,26 @@ test('replace all irregular whitespace with space', () => { expect(qd).toMatchSnapshot(); }); + +test('allow template string interpolation at document level', () => { + // for apollo client which uses interpolation to include child components fragments + // see http://dev.apollodata.com/react/fragments.html + const text = ` + gql\` + fragment FeedEntry on Entry { + commentCount + ...VoteButtons + ...RepoInfo + } + \${VoteButtons.fragments.entry} + \${RepoInfo.fragments.entry} + \` + `; + const qd = toQueryDocument( + new Source(text, 'query.js'), + { parser: ['EmbeddedQueryParser', { startTag: 'gql`', endTag: '`' }] }, + ); + + expect(qd).toMatchSnapshot(); +}); + diff --git a/src/query/_shared/parseQuery.js b/src/query/_shared/parseQuery.js index 6d58941..bf4b9a9 100644 --- a/src/query/_shared/parseQuery.js +++ b/src/query/_shared/parseQuery.js @@ -42,7 +42,7 @@ export function toQueryDocument(source: Source, config: Config): string { condition: () => stream.getCurrentPosition() < source.body.length, call: () => { const style = parser.token(stream, state); - // console.log('current', stream.current(), style); + // console.log('current', `[${stream.current()}]`, style); if ( // add fragment name is missing config.isRelay && state.kind === 'TypeCondition' &&