Skip to content

Commit d9bddac

Browse files
committed
readme and docs
1 parent 83ab69a commit d9bddac

File tree

5 files changed

+292
-4
lines changed

5 files changed

+292
-4
lines changed

README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ If you are using code files to store your GraphQL schema or your GraphQL operati
6262
}
6363
```
6464

65-
#### Extended linting with GraphQL Schema
65+
#### Extended linting rules with GraphQL Schema
6666

6767
If you are using [`graphql-config`](https://graphql-config.com/) - you are good to go. This package integrates with it automatically, and will use it to load your schema!
6868

@@ -81,6 +81,8 @@ The parser allow you to specify a json file / graphql files(s) / url / raw strin
8181
}
8282
```
8383

84+
> You can find a complete [documentation of the `parserOptions` here](./docs/parser-options.md)
85+
8486
> Some rules requires type information to operate, it's marked in the docs of each plugin!
8587
8688
### VSCode Integration
@@ -98,8 +100,33 @@ In order to enable it processing other extensions, add the following section in
98100
}
99101
```
100102

103+
### Disabling Rules
104+
105+
The `graphql-eslint` parser looks for GraphQL comments syntax (marked with `#`) and will send it to ESLint as directives. That means, you can use ESLint directives syntax to hint ESLint, just like in any other type of files.
106+
107+
To disable ESLint for a specific line, you can do:
108+
109+
```graphql
110+
# eslint-disable-next-line
111+
type Query {
112+
foo: String!
113+
}
114+
```
115+
116+
You can also specify specific rules to disable, apply it over the entire file, `next-line` or (current) `line`.
117+
118+
You can find a list of [ESLint directives here](https://eslint.org/docs/2.13.1/user-guide/configuring#disabling-rules-with-inline-comments).
119+
101120
## Available Rules
102121

103122
You can find a complete list of [all available plugins here](./docs/README.md)
104123

105124
> This repo doesn't exports a "recommended" set of rules - feel free to recommend us!
125+
126+
## Further Reading
127+
128+
If you wish to learn more about this project, how the parser works, how to add custom rules and more, [please refer to the docs directory](./docs/README.md))
129+
130+
## Contributing
131+
132+
If you think a rule is missing, or can be modified, feel free to report issues, on open PRs. We always welcome contributions.

docs/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@
1616
## Further Reading
1717

1818
- [Writing Custom Rules](./custom-rules.md)
19+
- [How the parser works?](./parser.md)
20+
- [`parserOptions`](./parser-options.md)

docs/custom-rules.md

Lines changed: 146 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,148 @@
1-
## Writing Custom Rules
1+
# Writing Custom Rules
22

3-
To get started with your own rules, start by understanding how
3+
To get started with your own rules, start by understanding how [ESLint custom rules works](https://eslint.org/docs/developer-guide/working-with-rules).
44

5-
TBD
5+
`graphql-eslint` converts the [GraphQL AST](https://graphql.org/graphql-js/language/) into [ESTree structure](https://github.com/estree/estree), so it allows you to easily travel the GraphQL AST tree easily.
6+
7+
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.
8+
9+
## Getting Started
10+
11+
Start by creating a [simple ESLint rule file](https://eslint.org/docs/developer-guide/working-with-rules), and choose the AST nodes you wish to visit. It can either be a [simple AST node `Kind`](https://github.com/graphql/graphql-js/blob/master/src/language/kinds.d.ts) or a complex [ESLint selector](https://eslint.org/docs/developer-guide/selectors) that allows you to travel and filter AST nodes.
12+
13+
We recommend you to read the [graphql-eslint parser documentation](./parser.md) before getting started, to understand the differences between the AST structures.
14+
15+
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 :)
16+
17+
Here's an example for a simple rule that reports on anonymous GraphQL operations:
18+
19+
```ts
20+
import { GraphQLESLintRule } from '@graphql-eslint/eslint-plugin';
21+
22+
const rule: GraphQLESLintRule = {
23+
create(context) {
24+
return {
25+
OperationDefinition(node) {
26+
if (node && (!node.name || node.name.value === '')) {
27+
context.report({
28+
node: node,
29+
message: `Oops, name is required!`,
30+
});
31+
}
32+
},
33+
};
34+
},
35+
};
36+
```
37+
38+
So what happens here?
39+
40+
1. `@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.
41+
1. You rule is being loaded by ESLint, and executes just like any other ESLint rule.
42+
1. Our custom rule asks ESLint to run our function for every `OperationDefinition` found.
43+
1. If the `OperationDefinition` node doesn't have a valid `name` - we report an error to ESLint.
44+
45+
#### More Examples
46+
47+
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.
48+
49+
## Accessing original GraphQL AST nodes
50+
51+
Since our parser converts GraphQL AST to ESTree structure, there are some minor differences in the structure of the objects.
52+
If you are using TypeScript, and you typed your rule with `GraphQLESLintRule` - you'll see that each `node` is a bit different from from the AST nodes of GraphQL (you can read more about that in [graphql-eslint parser documentation](./parser.md)).
53+
54+
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.
55+
56+
This is useful if you wish to use other GraphQL tools that works with the original GraphQL AST objects.
57+
58+
Here's an example for using original `graphql-js` validate method to validate `OperationDefinition`:
59+
60+
```ts
61+
import { validate } from 'graphql-js';
62+
import { requireGraphQLSchemaFromContext } from '@graphql-eslint/eslint-plugin';
63+
64+
export const rule = {
65+
create(context) {
66+
return {
67+
OperationDefinition(node) {
68+
const schema = requireGraphQLSchemaFromContext(context);
69+
70+
validate(context, schema, {
71+
kind: Kind.DOCUMENT,
72+
definitions: [node.rawNode()],
73+
});
74+
},
75+
};
76+
},
77+
};
78+
```
79+
80+
## `TypeInfo` / `GraphQLSchema`
81+
82+
If you provide GraphQL schema in your ESLint configuration, it will get loaded automatically, and become available in your rules in two ways:
83+
84+
1. You'll be able to access the loaded `GraphQLSchema` object.
85+
2. 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](https://graphql.org/graphql-js/utilities/#typeinfo)).
86+
87+
#### Getting `GraphQLSchema`
88+
89+
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.
90+
91+
```ts
92+
const schema = requireGraphQLSchemaFromContext(context);
93+
```
94+
95+
#### Accessing TypeInfo
96+
97+
If schema is provided and loaded successfully, the `typeInfo` will be available to use. Otherwise - it will be `undefined`.
98+
If your plugin requires `typeInfo` in order to operate and run, make sure to call `requireGraphQLSchemaFromContext` - it will validate that the schema is loaded.
99+
100+
`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:
101+
102+
```ts
103+
import { requireGraphQLSchemaFromContext } from '@graphql-eslint/eslint-plugin';
104+
105+
export const rule = {
106+
create(context) {
107+
requireGraphQLSchemaFromContext(context);
108+
109+
return {
110+
SelectionSet(node) {
111+
const typeInfo = node.typeInfo();
112+
113+
if (typeInfo && typeInfo.gqlType) {
114+
console.log(`The GraphQLOutputType is: ${typeInfo.gqlType}`);
115+
}
116+
},
117+
};
118+
},
119+
};
120+
```
121+
122+
The structure of the return value of `.typeInfo()` is [defined here](https://github.com/dotansimha/graphql-eslint/blob/master/packages/plugin/src/estree-parser/converter.ts#L38-L46). So based on the `node` you are using, you'll get a different values on `.typeInfo()` result.
123+
124+
## Testing your rules
125+
126+
To test your rules, you can either use the wrapped `GraphQLRuleTester` from this library, or use the built-it [`RuleTester`](https://eslint.org/docs/developer-guide/working-with-rules#rule-unit-tests) of ESLint.
127+
128+
The wrapped `GraphQLRuleTester` provides built-in configured parser, and a schema loader, if you need to test your rule with a loaded schema.
129+
130+
```ts
131+
import { GraphQLRuleTester } from '@graphql-eslint/eslint-plugin';
132+
import { rule } from './my-rule';
133+
134+
const ruleTester = new GraphQLRuleTester();
135+
136+
ruleTester.runGraphQLTests('my-rule', rule, {
137+
valid: [
138+
{
139+
code: `query something { foo }`,
140+
},
141+
],
142+
invalid: [
143+
{
144+
code: `query invalid { foo }`,
145+
},
146+
],
147+
});
148+
```

docs/parser-options.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
## Parser Options
2+
3+
### `skipGraphQLConfig`
4+
5+
If you are using [`graphql-config`](https://graphql-config.com/) in your project, the parser will automatically use that to load your default GraphQL schema.
6+
7+
You can disable this behaviour using `skipGraphQLConfig: true` in the `parserOptions`:
8+
9+
```json
10+
"parserOptions": {
11+
"skipGraphQLConfig": true
12+
}
13+
```
14+
15+
### `schema`
16+
17+
You can specify `parserOptions.schema` to load your GraphQL schema. The parser uses `graphql-tools` and it's loaders, that means you can either specify a URL, a path to a local `.json` (introspection) file, or a path to a local `.graphql` file(s). You can also use Glob expressions to load multiple files.
18+
19+
Here are a few examples for a valid setup:
20+
21+
```json
22+
"parserOptions": {
23+
"schema": "./schema.graphql"
24+
}
25+
```
26+
27+
```json
28+
"parserOptions": {
29+
"schema": "./schema.json"
30+
}
31+
```
32+
33+
```json
34+
"parserOptions": {
35+
"schema": "http://my-server/graphql"
36+
}
37+
```
38+
39+
```json
40+
"parserOptions": {
41+
"schema": "./src/**/*.graphql"
42+
}
43+
```
44+
45+
```json
46+
"parserOptions": {
47+
"schema": [
48+
"src/schema-a.graphql",
49+
"src/schema-b.graphql",
50+
"src/schema-c.graphql"
51+
]
52+
}
53+
```
54+
55+
### `schemaOptions`
56+
57+
If you wish to send additional configuration for the `graphql-tools` loaders that loads your schema, you can specify `schemaOptions` object:
58+
59+
```json
60+
"parserOptions": {
61+
"schema": "http://my-server/graphql",
62+
"schemaOptions": {
63+
"headers": {
64+
"Authorization": "Bearer MY_TOKEN"
65+
}
66+
}
67+
}
68+
```
69+
70+
```json
71+
"parserOptions": {
72+
"schema": "./src/**/*.graphql",
73+
"schemaOptions": {
74+
"assumeValid": true
75+
}
76+
}
77+
```
78+
79+
> The configuration here is flexible, and will be sent to `graphql-tools` and it's loaders. So depends on the schema source, the options may vary. [You can read more about these loaders and their configuration here](https://www.graphql-tools.com/docs/api/interfaces/_loaders_graphql_file_src_index_.graphqlfileloaderoptions).

docs/parser.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
## GraphQL-ESLint Parser
2+
3+
The `graphql-eslint` parser is works in the following way:
4+
5+
1. Loads all relevant GraphQL code using ESLint core (either from `.graphql` files, or using [ESLint `processor`](https://eslint.org/docs/developer-guide/working-with-plugins#processors-in-plugins) to find in code-files).
6+
1. Is uses `graphql-js` (and `graphql-tools`) to parse the found string into a `DocumentNode`.
7+
1. Extracts all comments (marked as `# ...`) from the parsed AST, and provides to ESLint as directives hints.
8+
1. If `graphql-config` is used, or `schema` field is provided, the schema is being loaded and provided to the rules using `parserServices`.
9+
1. Converts the `DocumentNode` to ESTree structure (and enrich the nodes with `typeInfo`, if schema is loaded).
10+
11+
### ESTree Conversion
12+
13+
The GraphQL AST structure is very similar to ESTree structure, but there are a few differences that the `parser` does.
14+
15+
Here's a list of changes that the parser performs, in order to make the GraphQL AST compatible with ESTree:
16+
17+
**Problem**: GraphQL uses `kind` field to define the kind of the AST node, while ESTree uses `type`.
18+
**Solution**: The parser adds `type` field on each node, and just copies the value from `kind` field.
19+
20+
**Problem**: Some GraphQL AST nodes are using `type` field (which conflicts with the ESTree kind).
21+
**Solution**: AST nodes that has `type` field are being transformed, and the `type` field changes to `gqlType`.
22+
23+
**Problem**: GraphQL AST structure allows circular JSON links (while ESTree might fail on `Maximum call stack exceeded`).
24+
**Solution**: The parser removes circular JSONs (specifically around GraphQL `Location` and the `Lexer`)
25+
26+
**Problem**: GraphQL uses `location` field to store the AST locations, while ESTree also uses it in a different structure.
27+
**Solution**: The parser creates a new `location` field that is compatible with ESTree, and renames the GraphQL `location` to `gqlLocation`.
28+
29+
### Loading GraphQL Schema
30+
31+
If you are using [`graphql-config`](https://graphql-config.com/) in your project, the parser will automatically use that to load your default GraphQL schema (you can disable this behaviour using `skipGraphQLConfig: true` in the `parserOptions`).
32+
33+
If you are not using `graphql-config`, you can specify `parserOptions.schema` to load your GraphQL schema. The parser uses `graphql-tools` and it's loaders, that means you can either specify a URL, a path to a local `.json` (introspection) file, or a path to a local `.graphql` file(s). You can also use Glob expressions to load multiple files.
34+
35+
[You can find more detail on the `parserOptions` config here](./parser-options.md)
36+
37+
Providing the schema will make sure that rules that needs it will be able to access it, and it enriches every converted AST node with `typeInfo`.

0 commit comments

Comments
 (0)