diff --git a/README.md b/README.md index bc7b77f..32ffa4f 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Has built-in settings for three GraphQL clients out of the box: ### Importing schema JSON -You'll need to import your [introspection query result](https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js). This can be done if you define your ESLint config in a JS file. Note: we're always looking for better ways to get the schema, so please open an issue with suggestions. +You'll need to import your [introspection query result](https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js) or the schema as a string in the Schema Language format. This can be done if you define your ESLint config in a JS file. Note: we're always looking for better ways to get the schema, so please open an issue with suggestions. ### Identity template literal tag @@ -73,6 +73,9 @@ module.exports = { // OR provide absolute path to your schema JSON // schemaJsonFilepath: path.resolve(__dirname, './schema.json'), + // OR provide the schema in the Schema Language format + // schemaString: printSchema(schema), + // tagName is gql by default }] }, @@ -100,6 +103,9 @@ module.exports = { // OR provide absolute path to your schema JSON // schemaJsonFilepath: path.resolve(__dirname, './schema.json'), + // OR provide the schema in the Schema Language format + // schemaString: printSchema(schema), + // tagName is set for you to Relay.QL }] }, @@ -127,6 +133,9 @@ module.exports = { // OR provide absolute path to your schema JSON // schemaJsonFilepath: path.resolve(__dirname, './schema.json'), + // OR provide the schema in the Schema Language format + // schemaString: printSchema(schema), + // Optional, the name of the template tag, defaults to 'gql' tagName: 'gql' }] @@ -155,6 +164,9 @@ module.exports = { // OR provide absolute path to your schema JSON // schemaJsonFilepath: path.resolve(__dirname, './schema.json'), + // OR provide the schema in the Schema Language format + // schemaString: printSchema(schema), + // tagName is set automatically }] }, diff --git a/src/index.js b/src/index.js index 660e920..1398491 100644 --- a/src/index.js +++ b/src/index.js @@ -3,6 +3,7 @@ import { parse, validate, buildClientSchema, + buildSchema, specifiedRules as allGraphQLValidators, } from 'graphql'; @@ -53,6 +54,9 @@ const defaultRuleProperties = { schemaJsonFilepath: { type: 'string', }, + schemaString: { + type: 'string', + }, tagName: { type: 'string', pattern: '^[$_a-zA-Z$_][a-zA-Z0-9$_]+(\\.[a-zA-Z0-9$_]+)?$', @@ -114,13 +118,16 @@ const rules = { }], }, }, - // schemaJson and schemaJsonFilepath are mutually exclusive: + // schemaJson, schemaJsonFilepath and schemaString are mutually exclusive: oneOf: [{ required: ['schemaJson'], - not: { required: ['schemaJsonFilepath'], }, + not: { required: ['schemaString', 'schemaJsonFilepath'], }, }, { required: ['schemaJsonFilepath'], - not: { required: ['schemaJson'], }, + not: { required: ['schemaString', 'schemaJson'], }, + }, { + required: ['schemaString'], + not: { required: ['schemaJson', 'schemaJsonFilepath'], }, }], } }, @@ -137,10 +144,13 @@ const rules = { properties: { ...defaultRuleProperties }, oneOf: [{ required: ['schemaJson'], - not: { required: ['schemaJsonFilepath'], }, + not: { required: ['schemaString', 'schemaJsonFilepath'], }, }, { required: ['schemaJsonFilepath'], - not: { required: ['schemaJson'], }, + not: { required: ['schemaString', 'schemaJson'], }, + }, { + required: ['schemaString'], + not: { required: ['schemaJson', 'schemaJsonFilepath'], }, }], }, }, @@ -168,16 +178,16 @@ const rules = { }, }, }, - oneOf: [ - { - required: ['schemaJson'], - not: { required: ['schemaJsonFilepath'] }, - }, - { - required: ['schemaJsonFilepath'], - not: { required: ['schemaJson'] }, - }, - ], + oneOf: [{ + required: ['schemaJson'], + not: { required: ['schemaString', 'schemaJsonFilepath'], }, + }, { + required: ['schemaJsonFilepath'], + not: { required: ['schemaString', 'schemaJson'], }, + }, { + required: ['schemaString'], + not: { required: ['schemaJson', 'schemaJsonFilepath'], }, + }], }, }, }, @@ -197,6 +207,7 @@ function parseOptions(optionGroup) { const { schemaJson, // Schema via JSON object schemaJsonFilepath, // Or Schema via absolute filepath + schemaString, // Or Schema as string, env, tagName: tagNameOption, validators: validatorNamesOption, @@ -208,6 +219,8 @@ function parseOptions(optionGroup) { schema = initSchema(schemaJson); } else if (schemaJsonFilepath) { schema = initSchemaFromFile(schemaJsonFilepath); + } else if (schemaString) { + schema = initSchemaFromString(schemaString); } else { throw new Error('Must pass in `schemaJson` option with schema object ' + 'or `schemaJsonFilepath` with absolute path to the json file.'); @@ -265,6 +278,10 @@ function initSchemaFromFile(jsonFile) { return initSchema(JSON.parse(fs.readFileSync(jsonFile, 'utf8'))); } +function initSchemaFromString(source) { + return buildSchema(source) +} + function templateExpressionMatchesTag(tagName, node) { const tagNameSegments = tagName.split('.').length; if (tagNameSegments === 1) { diff --git a/test/makeRule.js b/test/makeRule.js index cc143e6..15ed05e 100644 --- a/test/makeRule.js +++ b/test/makeRule.js @@ -7,9 +7,11 @@ import { values, entries, } from 'lodash'; +import { printSchema, buildClientSchema } from 'graphql'; const schemaJsonFilepath = path.resolve(__dirname, './schema.json'); const secondSchemaJsonFilepath = path.resolve(__dirname, './second-schema.json'); +const schemaString = printSchema(buildClientSchema(schemaJson.data)) // Init rule @@ -92,6 +94,74 @@ const parserOptions = { }); } +{ + const options = [ + { schemaString }, + ]; + + ruleTester.run('schemaString', rule, { + valid: [ + { + options, + parserOptions, + code: 'const x = gql`{ number }`', + }, + { + options, + parserOptions, + code: 'const x = segmented.TagName`height: 12px;`' + }, + { + options, + parserOptions, + code: 'const x = segmented.gql`height: 12px;`' + }, + { + options, + parserOptions, + code: 'const x = gql.segmented`height: 12px;`' + }, + { + options, + parserOptions, + code: 'const x = gql`{ number } ${x}`', + }, + ], + + invalid: [ + { + options, + parserOptions, + code: 'const x = gql``', + errors: [{ + message: 'Syntax Error GraphQL request (1:1) Unexpected ', + type: 'TaggedTemplateExpression' + }] + }, + { + options, + parserOptions, + code: 'const x = gql`{ nonExistentQuery }`', + errors: [{ + message: 'Cannot query field "nonExistentQuery" on type "RootQuery".', + 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 + }] + }, + ] + }); +} + { const options = [ { schemaJson, tagName: 'myGraphQLTag' },