Skip to content

Commit a625b51

Browse files
committed
fix(query): fix getInfo crashing for meta fields
1 parent c7bb3f3 commit a625b51

File tree

7 files changed

+282
-11
lines changed

7 files changed

+282
-11
lines changed

src/query/_shared/getTypeInfo.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@ import {
66
getNullableType,
77
GraphQLList,
88
isCompositeType,
9-
10-
SchemaMetaFieldDef,
11-
TypeMetaFieldDef,
12-
TypeNameMetaFieldDef,
139
} from 'graphql/type';
1410

1511
import {
@@ -22,6 +18,10 @@ import {
2218
type GQLType,
2319
type GQLNamedType,
2420
type GQLSchema,
21+
22+
SchemaMetaFieldDef,
23+
TypeMetaFieldDef,
24+
TypeNameMetaFieldDef,
2525
} from '../../shared/GQLTypes';
2626

2727
type Info = {

src/query/commands/__tests__/__snapshots__/getHintsAtPosition.test.js.snap

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,28 @@ Array [
104104
},
105105
]
106106
`;
107+
108+
exports[`show meta field __typename in abstract types interface type 1`] = `
109+
Array [
110+
Object {
111+
"description": "",
112+
"text": "id",
113+
"type": "ID!",
114+
},
115+
Object {
116+
"description": "The name of the current Object type at runtime.",
117+
"text": "__typename",
118+
"type": "String!",
119+
},
120+
]
121+
`;
122+
123+
exports[`show meta field __typename in abstract types union type 1`] = `
124+
Array [
125+
Object {
126+
"description": "The name of the current Object type at runtime.",
127+
"text": "__typename",
128+
"type": "String!",
129+
},
130+
]
131+
`;

src/query/commands/__tests__/__snapshots__/getInfoOfTokenAtPosition.test.js.snap

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,101 @@ Object {
106106
}
107107
`;
108108

109+
exports[`meta field __schema 1`] = `
110+
Object {
111+
"contents": Array [
112+
"# Access the current type schema of this server.
113+
(meta-field) __schema: __Schema!",
114+
"# A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all
115+
# available types and directives on the server, as well as the entry points for
116+
# query, mutation, and subscription operations.
117+
type __Schema {
118+
# A list of all types supported by this server.
119+
types: [__Type!]!
120+
121+
# The type that query operations will be rooted at.
122+
queryType: __Type!
123+
124+
# If this server supports mutation, the type that mutation operations will be rooted at.
125+
mutationType: __Type
126+
127+
# If this server support subscription, the type that subscription operations will be rooted at.
128+
subscriptionType: __Type
129+
130+
# A list of all directives supported by this server.
131+
directives: [__Directive!]!
132+
}",
133+
],
134+
}
135+
`;
136+
137+
exports[`meta field __type 1`] = `
138+
Object {
139+
"contents": Array [
140+
"# Request the type information of a single type.
141+
(meta-field) __type(name: String!): __Type",
142+
"# The fundamental unit of any GraphQL Schema is the type. There are many kinds of
143+
# types in GraphQL as represented by the \`__TypeKind\` enum.
144+
#
145+
# Depending on the kind of a type, certain fields describe information about that
146+
# type. Scalar types provide no information beyond a name and description, while
147+
# Enum types provide their values. Object and Interface types provide the fields
148+
# they describe. Abstract types, Union and Interface, provide the Object types
149+
# possible at runtime. List and NonNull types compose other types.
150+
type __Type {
151+
kind: __TypeKind!
152+
name: String
153+
description: String
154+
fields(includeDeprecated: Boolean = false): [__Field!]
155+
interfaces: [__Type!]
156+
possibleTypes: [__Type!]
157+
enumValues(includeDeprecated: Boolean = false): [__EnumValue!]
158+
inputFields: [__InputValue!]
159+
ofType: __Type
160+
}",
161+
],
162+
}
163+
`;
164+
165+
exports[`meta field __typename when inside Interface Type 1`] = `
166+
Object {
167+
"contents": Array [
168+
"# The name of the current Object type at runtime.
169+
(meta-field) __typename: String!",
170+
"# The \`String\` scalar type represents textual data, represented as UTF-8 character
171+
# sequences. The String type is most often used by GraphQL to represent free-form
172+
# human-readable text.
173+
scalar String",
174+
],
175+
}
176+
`;
177+
178+
exports[`meta field __typename when inside Object Type 1`] = `
179+
Object {
180+
"contents": Array [
181+
"# The name of the current Object type at runtime.
182+
(meta-field) __typename: String!",
183+
"# The \`String\` scalar type represents textual data, represented as UTF-8 character
184+
# sequences. The String type is most often used by GraphQL to represent free-form
185+
# human-readable text.
186+
scalar String",
187+
],
188+
}
189+
`;
190+
191+
exports[`meta field __typename when inside Union Type 1`] = `
192+
Object {
193+
"contents": Array [
194+
"# The name of the current Object type at runtime.
195+
(meta-field) __typename: String!",
196+
"# The \`String\` scalar type represents textual data, represented as UTF-8 character
197+
# sequences. The String type is most often used by GraphQL to represent free-form
198+
# human-readable text.
199+
scalar String",
200+
],
201+
}
202+
`;
203+
109204
exports[`mutations field: Include both input and output type 1`] = `
110205
Object {
111206
"contents": Array [

src/query/commands/__tests__/getHintsAtPosition.test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,35 @@ describe('directives', () => {
259259
).toMatchSnapshot();
260260
});
261261
});
262+
263+
264+
describe('show meta field __typename in abstract types', () => {
265+
it('interface type', () => {
266+
const { sourceText, position } = code(`
267+
const a = Relay.QL\`
268+
fragment on Node {
269+
id
270+
__type
271+
#-------^
272+
}
273+
\`
274+
`);
275+
expect(
276+
getHintsAtPosition(schema, sourceText, position, relayConfig),
277+
).toMatchSnapshot();
278+
});
279+
280+
it('union type', () => {
281+
const { sourceText, position } = code(`
282+
const a = Relay.QL\`
283+
fragment on Entity {
284+
__type
285+
#-------^
286+
}
287+
\`
288+
`);
289+
expect(
290+
getHintsAtPosition(schema, sourceText, position, relayConfig),
291+
).toMatchSnapshot();
292+
});
293+
});

src/query/commands/__tests__/getInfoOfTokenAtPosition.test.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,3 +252,74 @@ describe('query', () => {
252252
).toMatchSnapshot();
253253
});
254254
});
255+
256+
describe('meta field __typename', () => {
257+
it('when inside Object Type', () => {
258+
const { sourceText, position } = code(`
259+
const a = Relay.QL\`
260+
fragment on Viewer {
261+
__typename
262+
#----^
263+
}
264+
\`
265+
`);
266+
expect(
267+
getInfoOfTokenAtPosition(schema, sourceText, position, relayQLParser),
268+
).toMatchSnapshot();
269+
});
270+
it('when inside Interface Type', () => {
271+
const { sourceText, position } = code(`
272+
const a = Relay.QL\`
273+
fragment on Node {
274+
__typename
275+
#----^
276+
}
277+
\`
278+
`);
279+
expect(
280+
getInfoOfTokenAtPosition(schema, sourceText, position, relayQLParser),
281+
).toMatchSnapshot();
282+
});
283+
it('when inside Union Type', () => {
284+
const { sourceText, position } = code(`
285+
const a = Relay.QL\`
286+
fragment on Entity {
287+
__typename
288+
#----^
289+
}
290+
\`
291+
`);
292+
expect(
293+
getInfoOfTokenAtPosition(schema, sourceText, position, relayQLParser),
294+
).toMatchSnapshot();
295+
});
296+
});
297+
298+
test('meta field __schema', () => {
299+
const { sourceText, position } = code(`
300+
const a = Relay.QL\`
301+
query Viewer {
302+
__schema
303+
#----^
304+
}
305+
\`
306+
`);
307+
expect(
308+
getInfoOfTokenAtPosition(schema, sourceText, position, relayQLParser),
309+
).toMatchSnapshot();
310+
});
311+
312+
test('meta field __type', () => {
313+
const { sourceText, position } = code(`
314+
const a = Relay.QL\`
315+
query Viewer {
316+
__type
317+
#--^
318+
}
319+
\`
320+
`);
321+
expect(
322+
getInfoOfTokenAtPosition(schema, sourceText, position, relayQLParser),
323+
).toMatchSnapshot();
324+
});
325+

src/query/commands/getHintsAtPosition.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import {
99
GQLEnumType,
1010
getNamedType,
1111
type GQLSchema,
12+
13+
SchemaMetaFieldDef,
14+
TypeMetaFieldDef,
15+
TypeNameMetaFieldDef,
1216
} from '../../shared/GQLTypes';
1317
import { type QueryParser } from '../../config/GQLConfig';
1418
import getTokenAtPosition from '../_shared/getTokenAtPosition';
@@ -19,10 +23,6 @@ import {
1923

2024
isInputType,
2125
isCompositeType,
22-
23-
SchemaMetaFieldDef,
24-
TypeMetaFieldDef,
25-
TypeNameMetaFieldDef,
2626
} from 'graphql/type';
2727

2828
import getTypeInfo from '../_shared/getTypeInfo';

src/shared/GQLTypes.js

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,17 @@ import {
1818
GraphQLObjectType,
1919
GraphQLSkipDirective,
2020
GraphQLDirective,
21+
GraphqlField,
22+
23+
SchemaMetaFieldDef as _SchemaMetaFieldDef,
24+
TypeMetaFieldDef as _TypeMetaFieldDef,
25+
TypeNameMetaFieldDef as _TypeNameMetaFieldDef,
2126
} from 'graphql/type';
2227

28+
import { printType } from 'graphql/utilities/schemaPrinter';
29+
30+
import _keyBy from 'lodash/keyBy';
31+
2332
import {
2433
type TypeDefinitionNode,
2534
type FieldDefinitionNode,
@@ -48,7 +57,7 @@ export type GQLField = {
4857
resolve?: any,
4958
isDeprecated?: boolean,
5059
deprecationReason?: ?string,
51-
node: FieldDefinitionNode,
60+
node: ?FieldDefinitionNode,
5261
print: () => string,
5362
};
5463

@@ -132,15 +141,16 @@ function print(node: ?ASTNode, description: ?string, type: ?string): string {
132141
].filter(Boolean).join('\n');
133142
}
134143

135-
function patchFields(fields) {
136-
Object.keys(fields).forEach((name) => {
144+
function patchFields(fields: Array<GraphqlField>): Array<GQLField> {
145+
return Object.keys(fields).map((name) => {
137146
const field = fields[name];
138147
field.print = memoize(() => print(field.node, field.description, 'field'));
139148
field.args = field.args.map((arg, index) => ({
140149
...arg,
141150
node: field.node.arguments[index],
142151
print: memoize(() => print(field.node.arguments[index], arg.description, 'argument')),
143152
}));
153+
return field;
144154
});
145155
}
146156

@@ -166,6 +176,44 @@ GQLFloat.print = () => printDescription(GQLFloat.description);
166176
export const GQLBoolean: GQLScalarType = GraphQLBoolean;
167177
GQLBoolean.print = () => printDescription(GQLBoolean.description);
168178

179+
const [SchemaMetaFieldDef, TypeMetaFieldDef, TypeNameMetaFieldDef] = [
180+
_SchemaMetaFieldDef,
181+
_TypeMetaFieldDef,
182+
_TypeNameMetaFieldDef,
183+
].map(field => {
184+
const type = getNamedType(field.type);
185+
type.print = memoize(() => printType(type));
186+
187+
return {
188+
...field,
189+
print: memoize(() => {
190+
// HACK: graphql doesnt expose printField method
191+
// so creating fake type and printing it and extracting field string
192+
const printedType = printType(
193+
new GraphQLObjectType({
194+
name: 'Demo',
195+
fields: {
196+
FIELD_NAME: {
197+
...field,
198+
args: Array.isArray(field.args)
199+
? _keyBy(field.args, 'name')
200+
: field.args,
201+
},
202+
},
203+
}),
204+
).replace('FIELD_NAME', `(meta-field) ${field.name}`);
205+
206+
const lines = printedType.split('\n');
207+
return lines
208+
.slice(1, lines.length - 1) // remove first and last line which is type we need field only
209+
.map(line => line.trim())
210+
.join('\n');
211+
}),
212+
};
213+
});
214+
215+
export { SchemaMetaFieldDef, TypeMetaFieldDef, TypeNameMetaFieldDef };
216+
169217
function printArg(arg, indentation = '') {
170218
return [
171219
`${indentation}${printDescription(arg.description)}`,

0 commit comments

Comments
 (0)