From be88ab5228c276959a422d47cb0618d97ec73a5c Mon Sep 17 00:00:00 2001 From: Jon Wong Date: Wed, 7 Dec 2016 11:50:19 -0800 Subject: [PATCH 1/2] Taking into account Apollo fragment interpolation In Apollo, fragment interpolation is now valid within the `gql` TaggedTemplateLiteral, but only outside of top-level structures like `query` or `mutation`. In other clients like Lokka and Relay, fragment interpolation actuall results in a text substitution, but in Apollo this gets evaluated at runtime, so we only need to validate the location of the fragment interpolation. --- src/index.js | 20 ++++++++++++++++++-- test/makeRule.js | 47 +++++++++++++++++++++++++++++++++++++---------- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/index.js b/src/index.js index c0c64bf..bc61c63 100644 --- a/src/index.js +++ b/src/index.js @@ -257,12 +257,24 @@ function replaceExpressions(node, context, env) { node.quasis.forEach((element, i) => { const chunk = element.value.cooked; + const value = node.expressions[i]; chunks.push(chunk); - if (!element.tail) { - const value = node.expressions[i]; + if (env === 'apollo') { + // In Apollo, interpolation is only valid outside top-level structures like `query` or `mutation`. + // We'll check to make sure there's an equivalent set of opening and closing brackets, otherwise + // we're attempting to do an invalid interpolation. + if ((chunk.split('{').length - 1) !== (chunk.split('}').length - 1)) { + context.report({ + node: value, + message: 'Invalid interpolation - fragment interpolation must occur outside of the brackets.', + }); + throw new Error('Invalid interpolation'); + } + } + if (!element.tail) { // Preserve location of errors by replacing with exactly the same length const nameLength = value.end - value.start; @@ -282,6 +294,10 @@ function replaceExpressions(node, context, env) { // Ellipsis cancels out extra characters const placeholder = strWithLen(nameLength); chunks.push('...' + placeholder); + } else if (env === 'apollo') { + // In Apollo, fragment interpolation is only valid outside of brackets + // Since we don't know what we'd interpolate here (that occurs at runtime), + // we're not going to do anything with this interpolation. } else { // Invalid interpolation context.report({ diff --git a/test/makeRule.js b/test/makeRule.js index 7cf0907..23ea733 100644 --- a/test/makeRule.js +++ b/test/makeRule.js @@ -49,7 +49,6 @@ const parserOptions = { ], invalid: [ - { options, parserOptions, @@ -68,15 +67,6 @@ const parserOptions = { type: 'TaggedTemplateExpression' }] }, - { - options, - parserOptions, - code: 'const x = gql`{ ${x} }`', - errors: [{ - message: 'Invalid interpolation - not a valid fragment or variable.', - type: 'Identifier' - }] - }, ] }); } @@ -127,6 +117,43 @@ const parserOptions = { }); } +{ + const options = [ + { schemaJson, env: 'apollo' }, + ]; + + ruleTester.run('apollo', rule, { + valid: [ + { + options, + parserOptions, + code: 'const x = gql`{ number } ${SomeFragment}`', + }, + ], + + invalid: [ + { + options, + parserOptions, + code: 'const x = gql`query { ${x} }`', + errors: [{ + message: 'Invalid interpolation - fragment interpolation must occur outside of the brackets.', + type: 'Identifier' + }] + }, + { + options, + parserOptions, + code: 'const x = gql`query }{ ${x}`', + errors: [{ + message: 'Syntax Error GraphQL (1:7) Expected {, found }', + type: 'TaggedTemplateExpression' + }] + } + ], + }) +} + { const options = [ { schemaJson, env: 'lokka' }, From 3cbddfbf8f9f1f7e91db8432fc2ebfae93f3491f Mon Sep 17 00:00:00 2001 From: Jon Wong Date: Thu, 8 Dec 2016 08:54:15 -0800 Subject: [PATCH 2/2] Adding more test cases, handling default env. Making sure to check the invalid fragment or variable case in other environments, as well as ensuring that the default env works similar to how Apollo does. --- src/index.js | 4 ++-- test/makeRule.js | 45 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/index.js b/src/index.js index bc61c63..563df1a 100644 --- a/src/index.js +++ b/src/index.js @@ -261,7 +261,7 @@ function replaceExpressions(node, context, env) { chunks.push(chunk); - if (env === 'apollo') { + if (!env || env === 'apollo') { // In Apollo, interpolation is only valid outside top-level structures like `query` or `mutation`. // We'll check to make sure there's an equivalent set of opening and closing brackets, otherwise // we're attempting to do an invalid interpolation. @@ -294,7 +294,7 @@ function replaceExpressions(node, context, env) { // Ellipsis cancels out extra characters const placeholder = strWithLen(nameLength); chunks.push('...' + placeholder); - } else if (env === 'apollo') { + } else if (!env || env === 'apollo') { // In Apollo, fragment interpolation is only valid outside of brackets // Since we don't know what we'd interpolate here (that occurs at runtime), // we're not going to do anything with this interpolation. diff --git a/test/makeRule.js b/test/makeRule.js index 23ea733..14ed085 100644 --- a/test/makeRule.js +++ b/test/makeRule.js @@ -45,7 +45,12 @@ const parserOptions = { options, parserOptions, code: 'const x = gql.segmented`height: 12px;`' - } + }, + { + options, + parserOptions, + code: 'const x = gql`{ number } ${x}`', + }, ], invalid: [ @@ -67,6 +72,17 @@ const parserOptions = { type: 'TaggedTemplateExpression' }] }, + { + options, + parserOptions, + code: 'const x = gql`{ ${x} }`', + errors: [{ + message: 'Invalid interpolation - fragment interpolation must occur outside of the brackets.', + type: 'Identifier', + line: 1, + column: 19 + }] + }, ] }); } @@ -110,7 +126,7 @@ const parserOptions = { code: 'const x = myGraphQLTag`{ ${x} }`', errors: [{ type: 'Identifier', - message: 'Invalid interpolation - not a valid fragment or variable.' + message: 'Invalid interpolation - fragment interpolation must occur outside of the brackets.' }] }, ] @@ -127,7 +143,7 @@ const parserOptions = { { options, parserOptions, - code: 'const x = gql`{ number } ${SomeFragment}`', + code: 'const x = gql`{ number } ${x}`', }, ], @@ -290,6 +306,29 @@ const parserOptions = { column: 19 }] }, + { + options, + parserOptions, + code: ` + client.query(gql\` + { + allFilms { + films { + \${filmInfo} + } + } + } + \`).then(result => { + console.log(result.allFilms.films); + }); + `, + errors: [{ + message: 'Invalid interpolation - not a valid fragment or variable.', + type: 'Identifier', + line: 6, + column: 21 + }] + }, ] }); }