To get started with your own rules, start by understanding how ESLint custom rules works.
graphql-eslint
converts the GraphQL AST into ESTree structure, so it allows you to easily travel the GraphQL AST tree easily.
You can visit any GraphQL AST node in your custom rules, and report this as error. You don't need to have special handlers for code-files, since graphql-eslint
extracts usages of gql
and magic /* GraphQL */
comments automatically, and runs it through the parser, and eventually it knows to adjust errors location to fit in your code files original location.
Start by creating a simple ESLint rule file, and choose the AST nodes you wish to visit. It can either be a simple AST node Kind
or a complex ESLint selector that allows you to travel and filter AST nodes.
We recommend you to read the graphql-eslint parser documentation before getting started, to understand the differences between the AST structures.
The graphql-eslint
comes with a TypeScript wrapper for ESLint rules, and provides a testkit to simplify testing process with GraphQL schemas, so you can use that by importing GraphQLESLintRule
type. But if you wish to use JavaScript - that's fine :)
Here's an example for a simple rule that reports on anonymous GraphQL operations:
import { GraphQLESLintRule } from '@graphql-eslint/eslint-plugin'
const rule: GraphQLESLintRule = {
create(context) {
return {
OperationDefinition(node) {
if (!node.name || !node.name.value) {
context.report({
node,
message: 'Oops, name is required!'
})
}
}
}
}
}
So what happens here?
@graphql-eslint/eslint-plugin
handles the parsing process for your GraphQL content. It will load the GraphQL files (either from code files or from.graphql
files with SDL), parse it using GraphQL parser, converts it to ESTree structure and let ESLint do the rest.- Your rule is being loaded by ESLint, and executes just like any other ESLint rule.
- Our custom rule asks ESLint to run our function for every
OperationDefinition
found. - If the
OperationDefinition
node doesn't have a validname
- we report an error to ESLint.
You can scan the packages/plugin/src/rules
directory in this repo for references for implementing rules. It coverts most of the use-cases and concepts of rules.
Since our parser converts GraphQL AST to ESTree structure, there are some minor differences in the structure of the objects.
If you are using TypeScript, and you typed your rule with GraphQLESLintRule
- you'll see that each node
is a bit different from the AST nodes of GraphQL (you can read more about that in graphql-eslint parser documentation).
If you need access to the original GraphQL AST node
, you can use .rawNode()
method on each node you get from the AST structure of ESLint.
This is useful if you wish to use other GraphQL tools that works with the original GraphQL AST objects.
Here's an example for using original graphql-js
validate method to validate OperationDefinition
:
import { validate } from 'graphql'
import { requireGraphQLSchemaFromContext } from '@graphql-eslint/eslint-plugin'
export const rule = {
create(context) {
return {
OperationDefinition(node) {
const schema = requireGraphQLSchemaFromContext(context)
validate(context, schema, {
kind: Kind.DOCUMENT,
definitions: [node.rawNode()]
})
}
}
}
}
If you provide GraphQL schema in your ESLint configuration, it will get loaded automatically, and become available in your rules in two ways:
- You'll be able to access the loaded
GraphQLSchema
object. - In every visited node, you'll be able to use
.typeInfo()
method to get an object with complete type information on your visited node (see TypeInfo documentation).
To mark your ESLint rules as a rule that needs access to GraphQL schema, start by running requireGraphQLSchemaFromContext
from the plugin package, it will make sure to return a schema, or throw an error for the user about the missing schema.
const schema = requireGraphQLSchemaFromContext(context)
If schema is provided and loaded successfully, the typeInfo
will be available to use. Otherwise - it will be undefined
.
If your plugin requires typeInfo
in order to operate and run, make sure to call requireGraphQLSchemaFromContext
- it will validate that the schema is loaded.
typeInfo
is provided on every node, based on the type of that node, for example, to access the GraphQLOutputType
while you are visiting a SelectionSet
node, you can do:
import { requireGraphQLSchemaFromContext } from '@graphql-eslint/eslint-plugin'
export const rule = {
create(context) {
requireGraphQLSchemaFromContext('your-rule-name', context)
return {
SelectionSet(node) {
const typeInfo = node.typeInfo()
if (typeInfo.gqlType) {
console.log(`The GraphQLOutputType is: ${typeInfo.gqlType}`)
}
}
}
}
}
The structure of the return value of .typeInfo()
is defined here. So based on the node
you are using, you'll get a different values on .typeInfo()
result.
To test your rules, you can either use the wrapped GraphQLRuleTester
from this library, or use the built-it RuleTester
of ESLint.
The wrapped GraphQLRuleTester
provides built-in configured parser, and a schema loader, if you need to test your rule with a loaded schema.
import { GraphQLRuleTester } from '@graphql-eslint/eslint-plugin'
import { rule } from './my-rule'
const ruleTester = new GraphQLRuleTester()
ruleTester.runGraphQLTests('my-rule', rule, {
valid: [
{
code: 'query something { foo }'
}
],
invalid: [
{
code: 'query invalid { foo }',
errors: [{ message: 'Your error message.' }],
}
]
})