From 8c20dcd15f7a73a57db8a61628c7c83c2994776a Mon Sep 17 00:00:00 2001 From: Joseph Savona Date: Wed, 2 Mar 2016 13:19:47 -0800 Subject: [PATCH] babel plugin: support input object literals --- scripts/babel-relay-plugin/lib/HASH | 2 +- scripts/babel-relay-plugin/lib/RelayQLAST.js | 45 +++++++++---- .../babel-relay-plugin/lib/RelayQLPrinter.js | 23 ++++++- scripts/babel-relay-plugin/src/RelayQLAST.js | 64 +++++++++++++------ .../babel-relay-plugin/src/RelayQLPrinter.js | 17 ++++- .../queryWithArrayObjectArg.fixture | 42 ++++++++++++ ...queryWithArrayObjectNestedVariable.fixture | 15 +++++ .../queryWithArrayObjectValue.fixture | 44 +++++++++++++ .../__fixtures__/queryWithObjectArg.fixture | 4 +- .../queryWithObjectArgNestedVariable.fixture | 15 +++++ .../queryWithObjectArgValue.fixture | 44 +++++++++++++ .../src/__tests__/testschema.rfc.graphql | 5 +- .../src/__tests__/testschema.rfc.json | 48 ++++++++++++-- 13 files changed, 322 insertions(+), 46 deletions(-) create mode 100644 scripts/babel-relay-plugin/src/__fixtures__/queryWithArrayObjectArg.fixture create mode 100644 scripts/babel-relay-plugin/src/__fixtures__/queryWithArrayObjectNestedVariable.fixture create mode 100644 scripts/babel-relay-plugin/src/__fixtures__/queryWithArrayObjectValue.fixture create mode 100644 scripts/babel-relay-plugin/src/__fixtures__/queryWithObjectArgNestedVariable.fixture create mode 100644 scripts/babel-relay-plugin/src/__fixtures__/queryWithObjectArgValue.fixture diff --git a/scripts/babel-relay-plugin/lib/HASH b/scripts/babel-relay-plugin/lib/HASH index 87298f49b64e9..2e77a7dee2105 100644 --- a/scripts/babel-relay-plugin/lib/HASH +++ b/scripts/babel-relay-plugin/lib/HASH @@ -1 +1 @@ -isftDgu2uVYHE61WqmtgLAW/fxE= \ No newline at end of file +Bymfx+W0wVxiTT8S4zaiyJVbRhM= \ No newline at end of file diff --git a/scripts/babel-relay-plugin/lib/RelayQLAST.js b/scripts/babel-relay-plugin/lib/RelayQLAST.js index 5b19f44de0a1c..a1e47ebe04b5c 100644 --- a/scripts/babel-relay-plugin/lib/RelayQLAST.js +++ b/scripts/babel-relay-plugin/lib/RelayQLAST.js @@ -431,21 +431,13 @@ var RelayQLArgument = (function () { invariant(!this.isVariable(), 'Cannot get value of an argument variable.'); var value = this.ast.value; - switch (value.kind) { - case 'IntValue': - return parseInt(value.value, 10); - case 'FloatValue': - return parseFloat(value.value); - case 'StringValue': - case 'BooleanValue': - case 'EnumValue': - return value.value; - case 'ListValue': - return value.values.map(function (value) { - return new RelayQLArgument(_this6.context, _extends({}, _this6.ast, { value: value }), _this6.type.ofType()); - }); + if (value.kind === 'ListValue') { + return value.values.map(function (value) { + return new RelayQLArgument(_this6.context, _extends({}, _this6.ast, { value: value }), _this6.type.ofType()); + }); + } else { + return getLiteralValue(value); } - invariant(false, 'Unexpected argument kind: %s', value.kind); } }]); @@ -815,6 +807,31 @@ function stripMarkerTypes(schemaModifiedType) { return { isListType: isListType, isNonNullType: isNonNullType, schemaUnmodifiedType: schemaUnmodifiedType }; } +function getLiteralValue(value) { + switch (value.kind) { + case 'IntValue': + return parseInt(value.value, 10); + case 'FloatValue': + return parseFloat(value.value); + case 'StringValue': + case 'BooleanValue': + case 'EnumValue': + return value.value; + case 'ListValue': + return value.values.map(getLiteralValue); + case 'ObjectValue': + var object = {}; + value.fields.forEach(function (field) { + object[field.name.value] = getLiteralValue(field.value); + }); + return object; + case 'Variable': + invariant(false, 'Unexpected nested variable `%s`; variables are supported as top-' + 'level arguments - `node(id: $id)` - or directly within lists - ' + '`nodes(ids: [$id])`.', value.name.value); + default: + invariant(false, 'Unexpected value kind: %s', value.kind); + } +} + module.exports = { RelayQLArgument: RelayQLArgument, RelayQLArgumentType: RelayQLArgumentType, diff --git a/scripts/babel-relay-plugin/lib/RelayQLPrinter.js b/scripts/babel-relay-plugin/lib/RelayQLPrinter.js index b6fc13b73efe0..58a68f9c143b7 100644 --- a/scripts/babel-relay-plugin/lib/RelayQLPrinter.js +++ b/scripts/babel-relay-plugin/lib/RelayQLPrinter.js @@ -456,7 +456,7 @@ module.exports = function (t, options) { } return codify({ kind: t.valueToNode('CallValue'), - callValue: t.valueToNode(value) + callValue: printLiteralValue(value) }); } }, { @@ -617,6 +617,27 @@ module.exports = function (t, options) { return t.objectProperty(t.identifier(name), value); } + function printLiteralValue(value) { + if (value == null) { + return NULL; + } else if (Array.isArray(value)) { + return t.arrayExpression(value.map(printLiteralValue)); + } else if (typeof value === 'object' && value != null) { + var _ret2 = (function () { + var objectValue = value; + return { + v: t.objectExpression(Object.keys(objectValue).map(function (key) { + return property(key, printLiteralValue(objectValue[key])); + })) + }; + })(); + + if (typeof _ret2 === 'object') return _ret2.v; + } else { + return t.valueToNode(value); + } + } + function shallowFlatten(arr) { return t.callExpression(t.memberExpression(t.memberExpression(EMPTY_ARRAY, t.identifier('concat')), t.identifier('apply')), [EMPTY_ARRAY, arr]); } diff --git a/scripts/babel-relay-plugin/src/RelayQLAST.js b/scripts/babel-relay-plugin/src/RelayQLAST.js index e27b494a05956..64cb356e6cb15 100644 --- a/scripts/babel-relay-plugin/src/RelayQLAST.js +++ b/scripts/babel-relay-plugin/src/RelayQLAST.js @@ -33,6 +33,7 @@ import type { FragmentSpread as GraphQLFragmentSpread, InlineFragment as GraphQLInlineFragment, OperationDefinition as GraphQLOperationDefinition, + Value as GraphQLValue, } from 'GraphQLAST'; // TODO: Import types from `graphql`. @@ -379,25 +380,17 @@ class RelayQLArgument { 'Cannot get value of an argument variable.' ); const value = this.ast.value; - switch (value.kind) { - case 'IntValue': - return parseInt(value.value, 10); - case 'FloatValue': - return parseFloat(value.value); - case 'StringValue': - case 'BooleanValue': - case 'EnumValue': - return value.value; - case 'ListValue': - return value.values.map( - value => new RelayQLArgument( - this.context, - {...this.ast, value}, - this.type.ofType() - ) - ); + if (value.kind === 'ListValue') { + return value.values.map( + value => new RelayQLArgument( + this.context, + {...this.ast, value}, + this.type.ofType() + ) + ); + } else { + return getLiteralValue(value); } - invariant(false, 'Unexpected argument kind: %s', value.kind); } } @@ -767,6 +760,41 @@ function stripMarkerTypes(schemaModifiedType: GraphQLSchemaType): { return {isListType, isNonNullType, schemaUnmodifiedType}; } +function getLiteralValue(value: GraphQLValue): mixed { + switch (value.kind) { + case 'IntValue': + return parseInt(value.value, 10); + case 'FloatValue': + return parseFloat(value.value); + case 'StringValue': + case 'BooleanValue': + case 'EnumValue': + return value.value; + case 'ListValue': + return value.values.map(getLiteralValue); + case 'ObjectValue': + const object = {}; + value.fields.forEach(field => { + object[field.name.value] = getLiteralValue(field.value); + }); + return object; + case 'Variable': + invariant( + false, + 'Unexpected nested variable `%s`; variables are supported as top-' + + 'level arguments - `node(id: $id)` - or directly within lists - ' + + '`nodes(ids: [$id])`.', + value.name.value + ); + default: + invariant( + false, + 'Unexpected value kind: %s', + value.kind + ); + } +} + module.exports = { RelayQLArgument, RelayQLArgumentType, diff --git a/scripts/babel-relay-plugin/src/RelayQLPrinter.js b/scripts/babel-relay-plugin/src/RelayQLPrinter.js index 55e333fd99b3a..0ff83c5cf82bc 100644 --- a/scripts/babel-relay-plugin/src/RelayQLPrinter.js +++ b/scripts/babel-relay-plugin/src/RelayQLPrinter.js @@ -537,7 +537,7 @@ module.exports = function(t: any, options: PrinterOptions): Function { } return codify({ kind: t.valueToNode('CallValue'), - callValue: t.valueToNode(value), + callValue: printLiteralValue(value), }); } @@ -795,6 +795,21 @@ module.exports = function(t: any, options: PrinterOptions): Function { return t.objectProperty(t.identifier(name), value); } + function printLiteralValue(value: mixed): Printable { + if (value == null) { + return NULL; + } else if (Array.isArray(value)) { + return t.arrayExpression(value.map(printLiteralValue)); + } else if (typeof value === 'object' && value != null) { + const objectValue = value; + return t.objectExpression(Object.keys(objectValue).map(key => + property(key, printLiteralValue(objectValue[key])) + )); + } else { + return t.valueToNode(value); + } + } + function shallowFlatten(arr: mixed) { return t.callExpression( t.memberExpression( diff --git a/scripts/babel-relay-plugin/src/__fixtures__/queryWithArrayObjectArg.fixture b/scripts/babel-relay-plugin/src/__fixtures__/queryWithArrayObjectArg.fixture new file mode 100644 index 0000000000000..631cb3d615223 --- /dev/null +++ b/scripts/babel-relay-plugin/src/__fixtures__/queryWithArrayObjectArg.fixture @@ -0,0 +1,42 @@ +Input: +var Relay = require('Relay'); +var q = Relay.QL` + query { + searchAll(queries: [$query]) { + title, + }, + } +`; + +Output: +var Relay = require('Relay'); +var q = (function () { + return { + calls: [{ + kind: 'Call', + metadata: { + type: '[SearchInput!]!' + }, + name: 'queries', + value: [{ + kind: 'CallVariable', + callVariableName: 'query' + }] + }], + children: [{ + fieldName: 'title', + kind: 'Field', + metadata: {}, + type: 'String' + }], + fieldName: 'searchAll', + kind: 'Query', + metadata: { + isPlural: true, + identifyingArgName: 'queries', + identifyingArgType: '[SearchInput!]!' + }, + name: 'QueryWithArrayObjectArg', + type: 'SearchResult' + }; +})(); diff --git a/scripts/babel-relay-plugin/src/__fixtures__/queryWithArrayObjectNestedVariable.fixture b/scripts/babel-relay-plugin/src/__fixtures__/queryWithArrayObjectNestedVariable.fixture new file mode 100644 index 0000000000000..11d3d9639d9b7 --- /dev/null +++ b/scripts/babel-relay-plugin/src/__fixtures__/queryWithArrayObjectNestedVariable.fixture @@ -0,0 +1,15 @@ +Input: +var Relay = require('Relay'); +var q = Relay.QL` + query { + searchAll(queries: [{queryText: $query}]) { + title, + }, + } +`; + +Output: +var Relay = require('Relay'); +var q = (function () { + throw new Error('GraphQL validation/transform error ``Unexpected nested variable `query`; variables are supported as top-level arguments - `node(id: $id)` - or directly within lists - `nodes(ids: [$id])`.`` in file `queryWithArrayObjectNestedVariable.fixture`.'); +})(); diff --git a/scripts/babel-relay-plugin/src/__fixtures__/queryWithArrayObjectValue.fixture b/scripts/babel-relay-plugin/src/__fixtures__/queryWithArrayObjectValue.fixture new file mode 100644 index 0000000000000..fcb456f8ba5d9 --- /dev/null +++ b/scripts/babel-relay-plugin/src/__fixtures__/queryWithArrayObjectValue.fixture @@ -0,0 +1,44 @@ +Input: +var Relay = require('Relay'); +var q = Relay.QL` + query { + searchAll(queries: [{queryText: "Relay"}]) { + title, + }, + } +`; + +Output: +var Relay = require('Relay'); +var q = (function () { + return { + calls: [{ + kind: 'Call', + metadata: { + type: '[SearchInput!]!' + }, + name: 'queries', + value: [{ + kind: 'CallValue', + callValue: { + queryText: 'Relay' + } + }] + }], + children: [{ + fieldName: 'title', + kind: 'Field', + metadata: {}, + type: 'String' + }], + fieldName: 'searchAll', + kind: 'Query', + metadata: { + isPlural: true, + identifyingArgName: 'queries', + identifyingArgType: '[SearchInput!]!' + }, + name: 'QueryWithArrayObjectValue', + type: 'SearchResult' + }; +})(); diff --git a/scripts/babel-relay-plugin/src/__fixtures__/queryWithObjectArg.fixture b/scripts/babel-relay-plugin/src/__fixtures__/queryWithObjectArg.fixture index 559830155025a..49c6712305cff 100644 --- a/scripts/babel-relay-plugin/src/__fixtures__/queryWithObjectArg.fixture +++ b/scripts/babel-relay-plugin/src/__fixtures__/queryWithObjectArg.fixture @@ -15,7 +15,7 @@ var q = (function () { calls: [{ kind: 'Call', metadata: { - type: '[SearchInput!]' + type: 'SearchInput!' }, name: 'query', value: { @@ -34,7 +34,7 @@ var q = (function () { metadata: { isPlural: true, identifyingArgName: 'query', - identifyingArgType: '[SearchInput!]' + identifyingArgType: 'SearchInput!' }, name: 'QueryWithObjectArg', type: 'SearchResult' diff --git a/scripts/babel-relay-plugin/src/__fixtures__/queryWithObjectArgNestedVariable.fixture b/scripts/babel-relay-plugin/src/__fixtures__/queryWithObjectArgNestedVariable.fixture new file mode 100644 index 0000000000000..f8a5a9362161b --- /dev/null +++ b/scripts/babel-relay-plugin/src/__fixtures__/queryWithObjectArgNestedVariable.fixture @@ -0,0 +1,15 @@ +Input: +var Relay = require('Relay'); +var q = Relay.QL` + query { + search(query: {queryText: $query}) { + title, + }, + } +`; + +Output: +var Relay = require('Relay'); +var q = (function () { + throw new Error('GraphQL validation/transform error ``Unexpected nested variable `query`; variables are supported as top-level arguments - `node(id: $id)` - or directly within lists - `nodes(ids: [$id])`.`` in file `queryWithObjectArgNestedVariable.fixture`.'); +})(); diff --git a/scripts/babel-relay-plugin/src/__fixtures__/queryWithObjectArgValue.fixture b/scripts/babel-relay-plugin/src/__fixtures__/queryWithObjectArgValue.fixture new file mode 100644 index 0000000000000..ba8736f92d179 --- /dev/null +++ b/scripts/babel-relay-plugin/src/__fixtures__/queryWithObjectArgValue.fixture @@ -0,0 +1,44 @@ +Input: +var Relay = require('Relay'); +var q = Relay.QL` + query { + search(query: {queryText: "Relay"}) { + title, + }, + } +`; + +Output: +var Relay = require('Relay'); +var q = (function () { + return { + calls: [{ + kind: 'Call', + metadata: { + type: 'SearchInput!' + }, + name: 'query', + value: { + kind: 'CallValue', + callValue: { + queryText: 'Relay' + } + } + }], + children: [{ + fieldName: 'title', + kind: 'Field', + metadata: {}, + type: 'String' + }], + fieldName: 'search', + kind: 'Query', + metadata: { + isPlural: true, + identifyingArgName: 'query', + identifyingArgType: 'SearchInput!' + }, + name: 'QueryWithObjectArgValue', + type: 'SearchResult' + }; +})(); diff --git a/scripts/babel-relay-plugin/src/__tests__/testschema.rfc.graphql b/scripts/babel-relay-plugin/src/__tests__/testschema.rfc.graphql index 6a27472bec23e..bd257c43a6aaa 100644 --- a/scripts/babel-relay-plugin/src/__tests__/testschema.rfc.graphql +++ b/scripts/babel-relay-plugin/src/__tests__/testschema.rfc.graphql @@ -3,7 +3,8 @@ type Root { nodes(ids: [Int]): [Node] media(id: Int): Media viewer: Viewer - search(query: [SearchInput!]): [SearchResult] + search(query: SearchInput!): [SearchResult] + searchAll(queries: [SearchInput!]!): [SearchResult] _invalid: InvalidType actor: Actor } @@ -21,7 +22,7 @@ type SearchResult { } input SearchInput { - query: String + queryText: String } type Mutation { diff --git a/scripts/babel-relay-plugin/src/__tests__/testschema.rfc.json b/scripts/babel-relay-plugin/src/__tests__/testschema.rfc.json index a43e06caadf87..270bb87672d1a 100644 --- a/scripts/babel-relay-plugin/src/__tests__/testschema.rfc.json +++ b/scripts/babel-relay-plugin/src/__tests__/testschema.rfc.json @@ -113,15 +113,49 @@ "name": "query", "description": null, "type": { - "kind": "LIST", + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "SearchInput", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "SearchResult", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "searchAll", + "description": null, + "args": [ + { + "name": "queries", + "description": null, + "type": { + "kind": "NON_NULL", "name": null, "ofType": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, "ofType": { - "kind": "INPUT_OBJECT", - "name": "SearchInput", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "SearchInput" + } } } }, @@ -173,7 +207,7 @@ { "kind": "SCALAR", "name": "Int", - "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^53 - 1) and 2^53 - 1 since represented in JSON as double-precision floating point numbers specifiedby [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).", + "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. ", "fields": null, "inputFields": null, "interfaces": null, @@ -1343,7 +1377,7 @@ "fields": null, "inputFields": [ { - "name": "query", + "name": "queryText", "description": null, "type": { "kind": "SCALAR",