Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to pass schema as string, fixes #59 #78

Merged
merged 2 commits into from
Jun 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
}]
},
Expand Down Expand Up @@ -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
}]
},
Expand Down Expand Up @@ -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'
}]
Expand Down Expand Up @@ -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
}]
},
Expand Down
47 changes: 32 additions & 15 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
parse,
validate,
buildClientSchema,
buildSchema,
specifiedRules as allGraphQLValidators,
} from 'graphql';

Expand Down Expand Up @@ -53,6 +54,9 @@ const defaultRuleProperties = {
schemaJsonFilepath: {
type: 'string',
},
schemaString: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you think about using typeDefs instead? its a term i saw a lot in graphql-tools when referring to the schema when defined via the IDL

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makeExecutableSchema() defines typeDefs:

typeDefs is a required argument and should be an array of GraphQL schema language strings or a function that takes no arguments and returns an array of GraphQL schema language strings. The order of the strings in the array is not important, but it must include a schema definition.

That's not exactly what's used here. Alternatively, buildSchema() just refers to it as source. printSchema() refers to it as "schema in the Schema Language format".

I think schemaString is a decent compromise given the ambiguity and that the API is already using schemaJson.

type: 'string',
},
tagName: {
type: 'string',
pattern: '^[$_a-zA-Z$_][a-zA-Z0-9$_]+(\\.[a-zA-Z0-9$_]+)?$',
Expand Down Expand Up @@ -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'], },
}],
}
},
Expand All @@ -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'], },
}],
},
},
Expand Down Expand Up @@ -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'], },
}],
},
},
},
Expand All @@ -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,
Expand All @@ -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.');
Expand Down Expand Up @@ -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) {
Expand Down
70 changes: 70 additions & 0 deletions test/makeRule.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 <EOF>',
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' },
Expand Down