Skip to content

Commit 921dca0

Browse files
committed
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
1 parent e889937 commit 921dca0

File tree

5 files changed

+69
-28
lines changed

5 files changed

+69
-28
lines changed

src/query/_shared/Parsers/QueryParser.js

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,36 @@ import {
88
import { type Stream, type TokenState } from '../../../shared/types';
99
import invariant from 'invariant';
1010

11-
function JSInlineFragment() {
11+
type InterpolationState = { count: number, style: string };
12+
13+
function Interpolation(style: string) {
1214
return {
13-
style: '',
15+
style: '', // NOTE: should be empty
1416
match(token) {
1517
return token.value === '${';
1618
},
1719
update(state) {
18-
state.jsInlineFragment = { count: 1 }; // count is number of open curly braces
20+
state.interpolation = { count: 1, style }; // count is number of open curly braces
1921
},
2022
};
2123
}
2224

23-
function eatJSInlineFragment(stream, state) {
24-
const { jsInlineFragment: frag } = state;
25-
invariant(frag, 'missing JSInlineFragment');
25+
function eatInterpolation(stream, state) {
26+
const { interpolation } = state;
27+
invariant(interpolation, 'missing interpolation field in state');
2628
stream.eatWhile((ch) => {
27-
if (frag.count === 0) {
28-
state.jsInlineFragment = null;
29+
if (interpolation.count === 0) {
30+
state.interpolation = null;
2931
return false;
3032
}
3133
if (!ch) {
3234
return false;
3335
} // eol
3436
if (ch === '}') {
35-
frag.count -= 1;
37+
interpolation.count -= 1;
3638
}
3739
if (ch === '{') {
38-
frag.count += 1;
40+
interpolation.count += 1;
3941
}
4042
return true;
4143
});
@@ -51,9 +53,6 @@ const parserOptions = {
5153
parseRules: {
5254
...ParseRules,
5355

54-
// relay only one definition per Relay.QL
55-
// Document: ['Definition'],
56-
5756
// only query, mutation and fragment possible in Relay.QL
5857
Definition(token) {
5958
switch (token.value) {
@@ -65,6 +64,8 @@ const parserOptions = {
6564
return 'Subscription';
6665
case 'fragment':
6766
return 'FragmentDefinition';
67+
case '${':
68+
return 'DocumentInterpolation';
6869
default:
6970
return null;
7071
}
@@ -77,7 +78,8 @@ const parserOptions = {
7778
return ParseRules.Selection(token, stream);
7879
},
7980

80-
JSInlineFragment: [JSInlineFragment()],
81+
JSInlineFragment: [Interpolation('js-frag')],
82+
DocumentInterpolation: [Interpolation('ws-2')],
8183
},
8284
};
8385

@@ -92,11 +94,13 @@ export default class QueryParser {
9294
return this._parser.startState();
9395
}
9496

95-
token(stream: Stream, state: TokenState) {
96-
if (state.jsInlineFragment) {
97-
eatJSInlineFragment(stream, state);
97+
token(stream: Stream, state: TokenState & { interpolation: ?InterpolationState }) {
98+
if (state.interpolation) {
99+
const { style } = state.interpolation;
100+
// NOTE: eatInterpolation mutate both stream and state
101+
eatInterpolation(stream, state);
98102
stream._start -= 2; // to include '${' in token
99-
return 'js-frag';
103+
return style;
100104
}
101105

102106
return this._parser.token(stream, state);

src/query/_shared/__tests__/__snapshots__/getTokenAtPosition.test.js.snap

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ Object {
118118
"prevChar": "a",
119119
"start": 78,
120120
"state": Object {
121-
"jsInlineFragment": null,
121+
"interpolation": null,
122122
"kind": "Field",
123123
"level": 0,
124124
"levels": Array [
@@ -128,7 +128,7 @@ Object {
128128
"needsAdvance": true,
129129
"needsSeperator": false,
130130
"prevState": Object {
131-
"jsInlineFragment": null,
131+
"interpolation": null,
132132
"kind": "Selection",
133133
"level": 0,
134134
"levels": Array [
@@ -138,7 +138,7 @@ Object {
138138
"needsAdvance": false,
139139
"needsSeperator": false,
140140
"prevState": Object {
141-
"jsInlineFragment": null,
141+
"interpolation": null,
142142
"kind": "SelectionSet",
143143
"level": 0,
144144
"levels": Array [
@@ -267,7 +267,7 @@ Object {
267267
"prevChar": "a",
268268
"start": 62,
269269
"state": Object {
270-
"jsInlineFragment": null,
270+
"interpolation": null,
271271
"kind": "Field",
272272
"level": 0,
273273
"levels": Array [
@@ -277,7 +277,7 @@ Object {
277277
"needsAdvance": true,
278278
"needsSeperator": false,
279279
"prevState": Object {
280-
"jsInlineFragment": null,
280+
"interpolation": null,
281281
"kind": "Selection",
282282
"level": 0,
283283
"levels": Array [
@@ -287,7 +287,7 @@ Object {
287287
"needsAdvance": false,
288288
"needsSeperator": false,
289289
"prevState": Object {
290-
"jsInlineFragment": null,
290+
"interpolation": null,
291291
"kind": "SelectionSet",
292292
"level": 0,
293293
"levels": Array [
@@ -416,7 +416,7 @@ Object {
416416
"prevChar": "a",
417417
"start": 111,
418418
"state": Object {
419-
"jsInlineFragment": null,
419+
"interpolation": null,
420420
"kind": "Field",
421421
"level": 0,
422422
"levels": Array [
@@ -426,7 +426,7 @@ Object {
426426
"needsAdvance": true,
427427
"needsSeperator": false,
428428
"prevState": Object {
429-
"jsInlineFragment": null,
429+
"interpolation": null,
430430
"kind": "Selection",
431431
"level": 0,
432432
"levels": Array [
@@ -436,7 +436,7 @@ Object {
436436
"needsAdvance": false,
437437
"needsSeperator": false,
438438
"prevState": Object {
439-
"jsInlineFragment": null,
439+
"interpolation": null,
440440
"kind": "SelectionSet",
441441
"level": 0,
442442
"levels": Array [

src/query/_shared/__tests__/__snapshots__/parseQuery_toQueryDocument.test.js.snap

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,20 @@ exports[`add dummy fragment name if missing (relay) and \`on\` in next line 1`]
2525
"
2626
`;
2727

28+
exports[`allow template string interpolation at document level 1`] = `
29+
"
30+
31+
fragment FeedEntry on Entry {
32+
commentCount
33+
...VoteButtons
34+
...RepoInfo
35+
}
36+
37+
38+
39+
"
40+
`;
41+
2842
exports[`extract embedded queries 1`] = `
2943
"
3044

src/query/_shared/__tests__/parseQuery_toQueryDocument.test.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,3 +212,26 @@ test('replace all irregular whitespace with space', () => {
212212

213213
expect(qd).toMatchSnapshot();
214214
});
215+
216+
test('allow template string interpolation at document level', () => {
217+
// for apollo client which uses interpolation to include child components fragments
218+
// see http://dev.apollodata.com/react/fragments.html
219+
const text = `
220+
gql\`
221+
fragment FeedEntry on Entry {
222+
commentCount
223+
...VoteButtons
224+
...RepoInfo
225+
}
226+
\${VoteButtons.fragments.entry}
227+
\${RepoInfo.fragments.entry}
228+
\`
229+
`;
230+
const qd = toQueryDocument(
231+
new Source(text, 'query.js'),
232+
{ parser: ['EmbeddedQueryParser', { startTag: 'gql`', endTag: '`' }] },
233+
);
234+
235+
expect(qd).toMatchSnapshot();
236+
});
237+

src/query/_shared/parseQuery.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export function toQueryDocument(source: Source, config: Config): string {
4242
condition: () => stream.getCurrentPosition() < source.body.length,
4343
call: () => {
4444
const style = parser.token(stream, state);
45-
// console.log('current', stream.current(), style);
45+
// console.log('current', `[${stream.current()}]`, style);
4646
if ( // add fragment name is missing
4747
config.isRelay &&
4848
state.kind === 'TypeCondition' &&

0 commit comments

Comments
 (0)