Skip to content

Commit 39764a5

Browse files
authored
Merge pull request #55 from dotansimha/prepare-to-release
Initial release preparation
2 parents 997bf6f + d9bddac commit 39764a5

24 files changed

+381
-18
lines changed

.changeset/brave-years-float.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-eslint/eslint-plugin': minor
3+
---
4+
5+
Initial minor release from this repo.

.changeset/config.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
"access": "restricted",
77
"baseBranch": "master",
88
"updateInternalDependencies": "patch",
9-
"ignore": []
10-
}
9+
"ignore": ["@graphql-eslint/example"]
10+
}
File renamed without changes.
File renamed without changes.

README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This project integrates GraphQL AST parser and ESLint.
1212
- 🚀 Extended type info for more advanced usages
1313
- 🚀 Supports ESLint directives (for example: `disable-next-line`)
1414
- 🚀 Easily extendable - supports custom rules based on GraphQL's AST and ESLint API.
15+
- 🚀 Validates, lints, prettifies and checks for best practices across GraphQL schema and GraphQL operations
1516

1617
Special thanks to [ilyavolodin](https://github.com/ilyavolodin) for his work on a similar project!
1718

@@ -61,7 +62,7 @@ If you are using code files to store your GraphQL schema or your GraphQL operati
6162
}
6263
```
6364

64-
#### Extended linting with GraphQL Schema
65+
#### Extended linting rules with GraphQL Schema
6566

6667
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!
6768

@@ -80,6 +81,8 @@ The parser allow you to specify a json file / graphql files(s) / url / raw strin
8081
}
8182
```
8283

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

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+
100120
## Available Rules
101121

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

104124
> 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: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
- [`prettier`](./rules/prettier.md)
1414
- [`require-description`](./rules/require-description.md)
1515

16-
## Writing your own rules
16+
## Further Reading
1717

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

docs/custom-rules.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# Writing Custom Rules
2+
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).
4+
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`.

example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
},
1111
"dependencies": {
1212
"eslint": "7.10.0",
13-
"@graphql-eslint/eslint-plugin": "0.0.1",
13+
"@graphql-eslint/eslint-plugin": "0.2.1",
1414
"graphql": "15.3.0",
1515
"typescript": "4.0.3"
1616
}

0 commit comments

Comments
 (0)