Skip to content

Commit a97e6b3

Browse files
coleturnerfreiksenet
authored andcommitted
[Feature] Support lexical parser options (#567)
Add feature supporting lexical parser options This adds the ability to specify `parseOptions` through makeExecutableSchema into graphql.parse - unlocking finer control of lexical options when building a schema.
1 parent deb7ef0 commit a97e6b3

File tree

8 files changed

+188
-24
lines changed

8 files changed

+188
-24
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ sudo: false
2222
env:
2323
- GRAPHQL_VERSION='^0.11'
2424
- GRAPHQL_VERSION='^0.12'
25+
- GRAPHQL_VERSION='^0.13'

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
### vNEXT
44

55
* Make iterall a runtime dependency [PR #627](https://github.com/apollographql/graphql-tools/pull/627)
6+
* Added support for lexical parser options [PR #567](https://github.com/apollographql/graphql-tools/pull/567)
7+
* Support `graphql@^0.13.0` [PR #567](https://github.com/apollographql/graphql-tools/pull/567)
68

79
### v2.20.2
810

docs/source/generate-schema.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,8 @@ const jsSchema = makeExecutableSchema({
336336

337337
- `logger` is an optional argument, which can be used to print errors to the server console that are usually swallowed by GraphQL. The `logger` argument should be an object with a `log` function, eg. `const logger = { log: (e) => console.log(e) }`
338338

339+
- `parseOptions` is an optional argument which allows customization of parse when specifying `typeDefs` as a string.
340+
339341
- `allowUndefinedInResolve` is an optional argument, which is `true` by default. When set to `false`, causes your resolve functions to throw errors if they return undefined, which can help make debugging easier.
340342

341343
- `resolverValidationOptions` is an optional argument which accepts an object of the following shape: `{ requireResolversForArgs, requireResolversForNonScalar }`.

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@
4848
},
4949
"homepage": "https://github.com/apollostack/graphql-tools#readme",
5050
"dependencies": {
51-
"apollo-link": "^1.0.0",
51+
"apollo-link": "^1.1.0",
5252
"apollo-utilities": "^1.0.1",
5353
"deprecated-decorator": "^0.1.6",
54-
"iterall": "^1.1.3",
54+
"iterall": "^1.1.3",
5555
"uuid": "^3.1.0"
5656
},
5757
"peerDependencies": {
58-
"graphql": "^0.11.0 || ^0.12.0"
58+
"graphql": "^0.11.0 || ^0.12.0 || ^0.13.0"
5959
},
6060
"devDependencies": {
6161
"@types/chai": "4.0.10",
@@ -67,8 +67,8 @@
6767
"body-parser": "^1.18.2",
6868
"chai": "^4.1.2",
6969
"express": "^4.16.2",
70-
"graphql": "^0.12.3",
71-
"graphql-subscriptions": "^0.5.6",
70+
"graphql": "^0.13.0",
71+
"graphql-subscriptions": "^0.5.7",
7272
"graphql-type-json": "^0.1.4",
7373
"istanbul": "^0.4.5",
7474
"mocha": "^4.0.1",

src/Interfaces.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export interface IExecutableSchemaDefinition {
7878
allowUndefinedInResolve?: boolean;
7979
resolverValidationOptions?: IResolverValidationOptions;
8080
directiveResolvers?: IDirectiveResolvers<any, any>;
81+
parseOptions?: GraphQLParseOptions;
8182
}
8283

8384
export type IFieldIteratorFn = (
@@ -122,3 +123,10 @@ export interface IMockServer {
122123
}
123124

124125
export type ResolveType<T extends GraphQLType> = (type: T) => T;
126+
127+
export type GraphQLParseOptions = {
128+
noLocation?: boolean,
129+
allowLegacySDLEmptyFields?: boolean,
130+
allowLegacySDLImplementsInterfaces?: boolean,
131+
experimentalFragmentVariables?: boolean,
132+
};

src/schemaGenerator.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
IResolverValidationOptions,
4242
IDirectiveResolvers,
4343
UnitOrList,
44+
GraphQLParseOptions,
4445
} from './Interfaces';
4546

4647
import { deprecated } from 'deprecated-decorator';
@@ -67,6 +68,7 @@ function _generateSchema(
6768
allowUndefinedInResolve: boolean,
6869
resolverValidationOptions: IResolverValidationOptions,
6970
directiveResolvers: IDirectiveResolvers<any, any>,
71+
parseOptions: GraphQLParseOptions,
7072
) {
7173
if (typeof resolverValidationOptions !== 'object') {
7274
throw new SchemaError(
@@ -88,7 +90,7 @@ function _generateSchema(
8890

8991
// TODO: check that typeDefinitions is either string or array of strings
9092

91-
const schema = buildSchemaFromTypeDefinitions(typeDefinitions);
93+
const schema = buildSchemaFromTypeDefinitions(typeDefinitions, parseOptions);
9294

9395
addResolveFunctionsToSchema(schema, resolvers, resolverValidationOptions);
9496

@@ -117,6 +119,7 @@ function makeExecutableSchema({
117119
allowUndefinedInResolve = true,
118120
resolverValidationOptions = {},
119121
directiveResolvers = null,
122+
parseOptions = {},
120123
}: IExecutableSchemaDefinition) {
121124
const jsSchema = _generateSchema(
122125
typeDefs,
@@ -125,6 +128,7 @@ function makeExecutableSchema({
125128
allowUndefinedInResolve,
126129
resolverValidationOptions,
127130
directiveResolvers,
131+
parseOptions,
128132
);
129133
if (typeof resolvers['__schema'] === 'function') {
130134
// TODO a bit of a hack now, better rewrite generateSchema to attach it there.
@@ -186,6 +190,7 @@ function concatenateTypeDefs(
186190

187191
function buildSchemaFromTypeDefinitions(
188192
typeDefinitions: ITypeDefinitions,
193+
parseOptions?: GraphQLParseOptions,
189194
): GraphQLSchema {
190195
// TODO: accept only array here, otherwise interfaces get confusing.
191196
let myDefinitions = typeDefinitions;
@@ -204,7 +209,7 @@ function buildSchemaFromTypeDefinitions(
204209
}
205210

206211
if (typeof myDefinitions === 'string') {
207-
astDocument = parse(myDefinitions);
212+
astDocument = parse(myDefinitions, parseOptions);
208213
}
209214

210215
const backcompatOptions = { commentDescriptions: true };

src/test/testSchemaGenerator.ts

Lines changed: 131 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -701,8 +701,8 @@ describe('generating schema from shorthand', () => {
701701
RootQuery: {
702702
numberOfSpecies() {
703703
return 1;
704-
}
705-
}
704+
},
705+
},
706706
};
707707

708708
const testQuery = `{
@@ -983,8 +983,8 @@ describe('generating schema from shorthand', () => {
983983
RED: '#EA3232',
984984
},
985985
NumericEnum: {
986-
TEST: 1
987-
}
986+
TEST: 1,
987+
},
988988
};
989989

990990
const jsSchema = makeExecutableSchema({
@@ -994,7 +994,9 @@ describe('generating schema from shorthand', () => {
994994

995995
expect(jsSchema.getQueryType().name).to.equal('Query');
996996
expect(jsSchema.getType('Color')).to.be.an.instanceof(GraphQLEnumType);
997-
expect(jsSchema.getType('NumericEnum')).to.be.an.instanceof(GraphQLEnumType);
997+
expect(jsSchema.getType('NumericEnum')).to.be.an.instanceof(
998+
GraphQLEnumType,
999+
);
9981000
});
9991001

10001002
it('supports passing the value for a GraphQLEnumType in resolveFunctions', () => {
@@ -1035,7 +1037,7 @@ describe('generating schema from shorthand', () => {
10351037
},
10361038
numericEnum() {
10371039
return 1;
1038-
}
1040+
},
10391041
},
10401042
};
10411043

@@ -2393,3 +2395,126 @@ describe('attachDirectiveResolvers on field', () => {
23932395
);
23942396
});
23952397
});
2398+
2399+
describe('can specify lexical parser options', () => {
2400+
it("can specify 'noLocation' option", () => {
2401+
const schema = makeExecutableSchema({
2402+
typeDefs: `
2403+
type RootQuery {
2404+
test: String
2405+
}
2406+
schema {
2407+
query: RootQuery
2408+
}
2409+
`,
2410+
resolvers: {},
2411+
parseOptions: {
2412+
noLocation: true,
2413+
},
2414+
});
2415+
2416+
expect(schema.astNode.loc).to.equal(undefined);
2417+
});
2418+
2419+
if (['^0.11', '^0.12'].indexOf(process.env.GRAPHQL_VERSION) === -1) {
2420+
it("can specify 'allowLegacySDLEmptyFields' option", () => {
2421+
return expect(() => {
2422+
makeExecutableSchema({
2423+
typeDefs: `
2424+
type RootQuery {
2425+
}
2426+
schema {
2427+
query: RootQuery
2428+
}
2429+
`,
2430+
resolvers: {},
2431+
parseOptions: {
2432+
allowLegacySDLEmptyFields: true,
2433+
},
2434+
});
2435+
}).to.not.throw();
2436+
});
2437+
2438+
it("can specify 'allowLegacySDLImplementsInterfaces' option", () => {
2439+
const typeDefs = `
2440+
interface A {
2441+
hello: String
2442+
}
2443+
interface B {
2444+
world: String
2445+
}
2446+
type RootQuery implements A, B {
2447+
hello: String
2448+
world: String
2449+
}
2450+
schema {
2451+
query: RootQuery
2452+
}
2453+
`;
2454+
2455+
const resolvers = {};
2456+
2457+
expect(() => {
2458+
makeExecutableSchema({
2459+
typeDefs,
2460+
resolvers,
2461+
parseOptions: {
2462+
allowLegacySDLImplementsInterfaces: true,
2463+
},
2464+
});
2465+
}).to.not.throw();
2466+
2467+
expect(() => {
2468+
makeExecutableSchema({
2469+
typeDefs,
2470+
resolvers,
2471+
parseOptions: {
2472+
allowLegacySDLImplementsInterfaces: false,
2473+
},
2474+
});
2475+
}).to.throw('Syntax Error: Unexpected Name');
2476+
});
2477+
}
2478+
2479+
if (process.env.GRAPHQL_VERSION !== '^0.11') {
2480+
it("can specify 'experimentalFragmentVariables' option", () => {
2481+
const typeDefs = `
2482+
type Hello {
2483+
world(phrase: String): String
2484+
}
2485+
2486+
fragment hello($phrase: String = "world") on Hello {
2487+
world(phrase: $phrase)
2488+
}
2489+
2490+
type RootQuery {
2491+
hello: Hello
2492+
}
2493+
2494+
schema {
2495+
query: RootQuery
2496+
}
2497+
`;
2498+
2499+
const resolvers = {
2500+
RootQuery: {
2501+
hello() {
2502+
return {
2503+
world: (phrase: string) => `hello ${phrase}`,
2504+
};
2505+
},
2506+
},
2507+
};
2508+
2509+
expect(() => {
2510+
makeExecutableSchema({
2511+
typeDefs,
2512+
resolvers,
2513+
parseOptions: {
2514+
experimentalFragmentVariables: true,
2515+
},
2516+
});
2517+
}).to.not.throw();
2518+
});
2519+
}
2520+
});

src/test/testingSchemas.ts

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ export type Property = {
2828
export type Product = {
2929
id: string;
3030
price?: number;
31-
url?: string,
32-
type: string,
31+
url?: string;
32+
type: string;
3333
};
3434

3535
export type Booking = {
@@ -369,29 +369,50 @@ const propertyResolvers: IResolvers = {
369369
},
370370
};
371371

372-
const productTypeDefs = `
373-
interface Product {
372+
let DownloadableProduct = `
373+
type DownloadableProduct implements Product & Downloadable {
374374
id: ID!
375+
url: String!
375376
}
377+
`;
376378

377-
interface Sellable {
379+
let SimpleProduct = `type SimpleProduct implements Product & Sellable {
380+
id: ID!
378381
price: Int!
379382
}
383+
`;
380384

381-
interface Downloadable {
382-
url: String!
383-
}
385+
if (['^0.11', '^0.12'].indexOf(process.env.GRAPHQL_VERSION) !== -1) {
386+
DownloadableProduct = `
387+
type DownloadableProduct implements Product, Downloadable {
388+
id: ID!
389+
url: String!
390+
}
391+
`;
384392

385-
type SimpleProduct implements Product, Sellable {
393+
SimpleProduct = `type SimpleProduct implements Product, Sellable {
394+
id: ID!
395+
price: Int!
396+
}
397+
`;
398+
}
399+
400+
const productTypeDefs = `
401+
interface Product {
386402
id: ID!
403+
}
404+
405+
interface Sellable {
387406
price: Int!
388407
}
389408
390-
type DownloadableProduct implements Product, Downloadable {
391-
id: ID!
409+
interface Downloadable {
392410
url: String!
393411
}
394412
413+
${SimpleProduct}
414+
${DownloadableProduct}
415+
395416
type Query {
396417
products: [Product]
397418
}

0 commit comments

Comments
 (0)