@graphql-eslint/eslint-plugin 2.5.0-alpha-f60e6aa.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,18 +6,18 @@ This project integrates GraphQL and ESLint, for a better developer experience.
6
6
 
7
7
  [![npm version](https://badge.fury.io/js/%40graphql-eslint%2Feslint-plugin.svg)](https://badge.fury.io/js/%40graphql-eslint%2Feslint-plugin)
8
8
 
9
- > Created and maintained by [The Guild](http://the-guild.dev/)
9
+ > Created and maintained by [The Guild](http://the-guild.dev)
10
10
 
11
11
  ## Key Features
12
12
 
13
- - 🚀 Integrates with ESLint core (as a ESTree parser).
14
- - 🚀 Works on `.graphql` files, `gql` usages and `/* GraphQL */` magic comments.
15
- - 🚀 Lints both GraphQL schema and GraphQL operations.
13
+ - 🚀 Integrates with ESLint core (as a ESTree parser)
14
+ - 🚀 Works on `.graphql` files, `gql` usages and `/* GraphQL */` magic comments
15
+ - 🚀 Lints both GraphQL schema and GraphQL operations
16
16
  - 🚀 Extended type info for more advanced usages
17
- - 🚀 Supports ESLint directives (for example: `disable-next-line`)
18
- - 🚀 Easily extendable - supports custom rules based on GraphQL's AST and ESLint API.
19
- - 🚀 Validates, lints, prettifies and checks for best practices across GraphQL schema and GraphQL operations.
20
- - 🚀 Integrates with [`graphql-config`](https://graphql-config.com/)
17
+ - 🚀 Supports ESLint directives (for example: `eslint-disable-next-line`)
18
+ - 🚀 Easily extendable - supports custom rules based on GraphQL's AST and ESLint API
19
+ - 🚀 Validates, lints, prettifies and checks for best practices across GraphQL schema and GraphQL operations
20
+ - 🚀 Integrates with [`graphql-config`](https://graphql-config.com)
21
21
  - 🚀 Integrates and visualizes lint issues in popular IDEs (VSCode / WebStorm)
22
22
 
23
23
  > Special thanks to [ilyavolodin](https://github.com/ilyavolodin) for his work on a similar project!
@@ -42,11 +42,13 @@ Or, with NPM:
42
42
  npm install --save-dev @graphql-eslint/eslint-plugin
43
43
  ```
44
44
 
45
- > Also, make sure you have `graphql` dependency in your project.
45
+ > Make sure you have `graphql` dependency in your project.
46
46
 
47
47
  ### Configuration
48
48
 
49
- To get started, create an override configuration for your ESLint, while applying it to `.graphql` files (do that even if you are declaring your operations in code files):
49
+ To get started, define an override in your ESLint config to apply this plugin to `.graphql` files. Add the [rules](docs/README.md) you want applied.
50
+
51
+ > 🚨 Important! This step is necessary even if you are declaring operations and/or schema in code files.
50
52
 
51
53
  ```json
52
54
  {
@@ -56,74 +58,90 @@ To get started, create an override configuration for your ESLint, while applying
56
58
  "parser": "@graphql-eslint/eslint-plugin",
57
59
  "plugins": ["@graphql-eslint"],
58
60
  "rules": {
59
- ...
61
+ "@graphql-eslint/known-type-names": "error"
60
62
  }
61
63
  }
62
64
  ]
63
65
  }
64
66
  ```
65
67
 
66
- If you are using code files to store your GraphQL schema or your GraphQL operations, you can extend the behaviour of ESLint and extract those, by adding **an additional `override`** that does that extraction processes:
68
+ If your GraphQL definitions are defined only in `.graphql` files, and you're only using rules that apply to individual files, you should be good to go 👍. If you would like use a remote schema or use rules that apply across the entire collection of definitions at once, see [here](#using-a-remote-schema-or-rules-with-constraints-that-span-the-entire-schema).
67
69
 
68
- ```json
70
+ #### Tell ESLint to apply this plugin to GraphQL definitions defined in code files
71
+
72
+ If you are defining GraphQL schema or GraphQL operations in code files, you'll want to define an additional override to extend the functionality of this plugin to the schema and operations in those files.
73
+
74
+ ```diff
69
75
  {
70
76
  "overrides": [
71
- {
72
- "files": ["*.tsx", "*.ts", "*.jsx", "*.js"],
73
- "processor": "@graphql-eslint/graphql"
74
- },
77
+ + {
78
+ + "files": ["*.js"],
79
+ + "processor": "@graphql-eslint/graphql"
80
+ + },
75
81
  {
76
82
  "files": ["*.graphql"],
77
83
  "parser": "@graphql-eslint/eslint-plugin",
78
84
  "plugins": ["@graphql-eslint"],
79
85
  "rules": {
80
- ...
86
+ "@graphql-eslint/known-type-names": "error"
81
87
  }
82
88
  }
83
89
  ]
84
90
  }
85
91
  ```
86
92
 
87
- #### Extended linting rules with GraphQL Schema
93
+ Under the hood, specifying the `@graphql-eslint/graphql` processor for code files will cause `graphql-eslint/graphql` to extract the schema and operation definitions from these files into virtual GraphQL documents with `.graphql` extensions. This will allow the overrides you've defined for `.graphql` files, via `"files": ["*.graphql"]`, to get applied to the definitions defined in your code files.
88
94
 
89
- 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!
95
+ #### Using a remote schema or rules with constraints that span the entire schema
90
96
 
91
- Linting process can be enriched and extended with GraphQL type information, if you are able to provide your GraphQL schema.
97
+ Some rules require an understanding of the entire schema at once. For example, [no-unreachable-types](https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-unreachable-types.md#no-unreachable-types) checks that all types are reachable by root-level fields.
92
98
 
93
- The parser allow you to specify a json file / graphql files(s) / url / raw string to locate your schema (We are using `graphql-tools` to do that). Just add `parserOptions.schema` to your configuration file:
99
+ To use these rules, you'll need to tell ESLint how to identify the entire set of schema definitions.
94
100
 
95
- ```json
101
+ If you are using [`graphql-config`](https://graphql-config.com), you are good to go. `graphql-eslint` integrates with it automatically and will use it to load your schema!
102
+
103
+ Alternatively, you can define `parserOptions.schema` in the `*.graphql` override in your ESLint config.
104
+
105
+ The parser allows you to specify a json file / graphql files(s) / url / raw string to locate your schema (We are using `graphql-tools` to do that). Just add `parserOptions.schema` to your configuration file:
106
+
107
+ ```diff
96
108
  {
97
109
  "files": ["*.graphql"],
98
110
  "parser": "@graphql-eslint/eslint-plugin",
99
111
  "plugins": ["@graphql-eslint"],
100
- "parserOptions": {
101
- "schema": "./schema.graphql"
102
- }
112
+ "rules": {
113
+ "@graphql-eslint/no-unreachable-types": "error"
114
+ },
115
+ + "parserOptions": {
116
+ + "schema": "./schema.graphql"
117
+ + }
103
118
  }
104
119
  ```
105
120
 
106
- > You can find a complete [documentation of the `parserOptions` here](./docs/parser-options.md)
121
+ > You can find a complete [documentation of the `parserOptions` here](docs/parser-options.md).
107
122
 
108
- > Some rules requires type information to operate, it's marked in the docs of each plugin!
123
+ > Some rules require type information to operate, it's marked in the docs for each rule!
109
124
 
110
125
  #### Extended linting rules with siblings operations
111
126
 
112
127
  While implementing this tool, we had to find solutions for a better integration of the GraphQL ecosystem and ESLint core.
113
128
 
114
- GraphQL operations can be distributed across many files, while ESLint operates on one file at a time. If you are using GraphQL fragments in separate files, some rules might yield incorrect results, due the the missing information.
129
+ GraphQL operations can be distributed across many files, while ESLint operates on one file at a time. If you are using GraphQL fragments in separate files, some rules might yield incorrect results, due the missing information.
115
130
 
116
131
  To workaround that, we allow you to provide additional information on your GraphQL operations, making it available for rules while doing the actual linting.
117
132
 
118
- To provide that, we are using `@graphql-tools` loaders to load your sibling operations and fragments, just specify a glob expression(s) that points to your code/.graphql files:
133
+ To provide that, we are using `graphql-tools` loaders to load your sibling operations and fragments, just specify a glob expression(s) that points to your code/`.graphql` files:
119
134
 
120
- ```json
135
+ ```diff
121
136
  {
122
137
  "files": ["*.graphql"],
123
138
  "parser": "@graphql-eslint/eslint-plugin",
124
139
  "plugins": ["@graphql-eslint"],
140
+ "rules": {
141
+ "@graphql-eslint/unique-operation-name": "error"
142
+ },
125
143
  "parserOptions": {
126
- "operations": ["./src/**/*.graphql"],
144
+ + "operations": "./src/**/*.graphql",
127
145
  "schema": "./schema.graphql"
128
146
  }
129
147
  }
@@ -131,13 +149,13 @@ To provide that, we are using `@graphql-tools` loaders to load your sibling oper
131
149
 
132
150
  ### VSCode Integration
133
151
 
134
- By default, [ESLint VSCode plugin](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) will not lint files with extensions other then js, jsx, ts, tsx.
152
+ By default, [ESLint VSCode plugin](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) will not lint files with extensions other than `js`, `jsx`, `ts` and `tsx`.
135
153
 
136
154
  In order to enable it processing other extensions, add the following section in `settings.json` or workspace configuration.
137
155
 
138
156
  ```json
139
157
  {
140
- "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact", "graphql"],
158
+ "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact", "graphql"]
141
159
  }
142
160
  ```
143
161
 
@@ -156,13 +174,13 @@ type Query {
156
174
  }
157
175
  ```
158
176
 
159
- You can also specify specific rules to disable, apply it over the entire file, `next-line` or (current) `line`.
177
+ You can also specify specific rules to disable, apply it over the entire file, `eslint-disable-next-line` or current `eslint-disable-line`.
160
178
 
161
179
  You can find a list of [ESLint directives here](https://eslint.org/docs/2.13.1/user-guide/configuring#disabling-rules-with-inline-comments).
162
180
 
163
181
  ## Available Rules
164
182
 
165
- You can find a complete list of [all available rules here](./docs/README.md)
183
+ You can find a complete list of [all available rules here](docs/README.md).
166
184
 
167
185
  ## Available Configs
168
186
 
@@ -177,19 +195,13 @@ Enable it in your `.eslintrc` file with the `extends` option.
177
195
  "overrides": [
178
196
  {
179
197
  "files": ["*.js"],
180
- "processor": "@graphql-eslint/graphql",
181
- "rules": {
182
- // your rules for JavaScript files
183
- }
198
+ "processor": "@graphql-eslint/graphql"
184
199
  },
185
200
  {
186
201
  "files": ["*.graphql"],
187
202
  - "parser": "@graphql-eslint/eslint-plugin",
188
203
  - "plugins": ["@graphql-eslint"],
189
- + "extends": "plugin:@graphql-eslint/recommended", // or plugin:@graphql-eslint/all
190
- "rules": {
191
- // your rules for GraphQL files
192
- }
204
+ + "extends": "plugin:@graphql-eslint/recommended" // or plugin:@graphql-eslint/all
193
205
  }
194
206
  ]
195
207
  }
@@ -197,26 +209,23 @@ Enable it in your `.eslintrc` file with the `extends` option.
197
209
 
198
210
  ### `prettier` rule
199
211
 
200
- The original `prettier` rule has been removed because `eslint-plugin-prettier` supports `.graphql` files well actually.
201
-
202
- All you need to do is like the following for now:
212
+ `eslint-plugin-prettier` supports `.graphql` files. You need to do the following:
203
213
 
204
214
  ```js
205
- // .eslintrc.js
206
215
  module.exports = {
207
216
  overrides: [
208
217
  {
209
218
  files: ['*.js'],
210
219
  processor: '@graphql-eslint/graphql',
211
- extends: ['plugin:prettier/recommended'],
220
+ extends: ['plugin:prettier/recommended']
212
221
  },
213
222
  {
214
223
  files: ['*.graphql'],
215
224
  parser: '@graphql-eslint/eslint-plugin',
216
225
  plugins: ['@graphql-eslint'],
217
226
  rules: {
218
- 'prettier/prettier': 'error',
219
- },
227
+ 'prettier/prettier': 'error'
228
+ }
220
229
  },
221
230
  // the following is required for `eslint-plugin-prettier@<=3.4.0` temporarily
222
231
  // after https://github.com/prettier/eslint-plugin-prettier/pull/415
@@ -224,22 +233,22 @@ module.exports = {
224
233
  {
225
234
  files: ['*.js/*.graphql'],
226
235
  rules: {
227
- 'prettier/prettier': 'off',
228
- },
229
- },
230
- ],
231
- };
236
+ 'prettier/prettier': 'off'
237
+ }
238
+ }
239
+ ]
240
+ }
232
241
  ```
233
242
 
234
243
  You can take [`examples/prettier`](examples/prettier/.eslintrc.js) as example.
235
244
 
236
- It could be better to remove the unnecessary `*.js/*.graphql` overrides setting if <https://github.com/prettier/eslint-plugin-prettier/pull/415> will be merged and released.
245
+ It could be better to remove the unnecessary `*.js/*.graphql` override setting if <https://github.com/prettier/eslint-plugin-prettier/pull/415> will be merged and released.
237
246
 
238
247
  Please help to vote up if you want to speed up the progress.
239
248
 
240
249
  ## Further Reading
241
250
 
242
- 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))
251
+ 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).
243
252
 
244
253
  ## Contributions
245
254
 
@@ -249,8 +258,8 @@ And if this is your first time contributing to this project, please do read our
249
258
 
250
259
  ### Code of Conduct
251
260
 
252
- Help us keep GraphQL ESLint open and inclusive. Please read and follow our [Code of Conduct](https://github.com/the-guild-org/Stack/blob/master/CODE_OF_CONDUCT.md) as adopted from [Contributor Covenant](https://www.contributor-covenant.org/)
261
+ Help us keep GraphQL ESLint open and inclusive. Please read and follow our [Code of Conduct](https://github.com/the-guild-org/Stack/blob/master/CODE_OF_CONDUCT.md) as adopted from [Contributor Covenant](https://contributor-covenant.org).
253
262
 
254
263
  ## License
255
264
 
256
- Released under the [MIT license](./LICENSE).
265
+ Released under the [MIT license](LICENSE).
package/configs/all.d.ts CHANGED
@@ -15,6 +15,9 @@ export declare const allConfig: {
15
15
  '@graphql-eslint/match-document-filename': string;
16
16
  '@graphql-eslint/no-deprecated': string;
17
17
  '@graphql-eslint/no-hashtag-description': string;
18
+ '@graphql-eslint/no-root-type': (string | {
19
+ disallow: string[];
20
+ })[];
18
21
  '@graphql-eslint/no-unreachable-types': string;
19
22
  '@graphql-eslint/no-unused-fields': string;
20
23
  '@graphql-eslint/require-deprecation-date': string;
@@ -16,6 +16,9 @@ export declare const configs: {
16
16
  '@graphql-eslint/match-document-filename': string;
17
17
  '@graphql-eslint/no-deprecated': string;
18
18
  '@graphql-eslint/no-hashtag-description': string;
19
+ '@graphql-eslint/no-root-type': (string | {
20
+ disallow: string[];
21
+ })[];
19
22
  '@graphql-eslint/no-unreachable-types': string;
20
23
  '@graphql-eslint/no-unused-fields': string;
21
24
  '@graphql-eslint/require-deprecation-date': string;
package/docs/README.md CHANGED
@@ -35,6 +35,7 @@ Name&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs
35
35
  [no-fragment-cycles](rules/no-fragment-cycles.md)|A GraphQL fragment is only valid when it does not have cycles in fragments usage.|🔮||✅
36
36
  [no-hashtag-description](rules/no-hashtag-description.md)|Requires to use `"""` or `"` for adding a GraphQL description instead of `#`.|🚀||
37
37
  [no-operation-name-suffix](rules/no-operation-name-suffix.md)|Makes sure you are not adding the operation type to the name of the operation.|🚀|🔧|✅
38
+ [no-root-type](rules/no-root-type.md)|Disallow using root types for `read-only` or `write-only` schemas.|🚀||
38
39
  [no-undefined-variables](rules/no-undefined-variables.md)|A GraphQL operation is only valid if all variables encountered, both directly and via fragment spreads, are defined by that operation.|🔮||✅
39
40
  [no-unreachable-types](rules/no-unreachable-types.md)|Requires all types to be reachable at some level by root level fields.|🚀|🔧|
40
41
  [no-unused-fields](rules/no-unused-fields.md)|Requires all fields to be used at some level by siblings operations.|🚀|🔧|
@@ -17,22 +17,22 @@ The `graphql-eslint` comes with a TypeScript wrapper for ESLint rules, and provi
17
17
  Here's an example for a simple rule that reports on anonymous GraphQL operations:
18
18
 
19
19
  ```ts
20
- import { GraphQLESLintRule } from '@graphql-eslint/eslint-plugin';
20
+ import { GraphQLESLintRule } from '@graphql-eslint/eslint-plugin'
21
21
 
22
22
  const rule: GraphQLESLintRule = {
23
23
  create(context) {
24
24
  return {
25
25
  OperationDefinition(node) {
26
- if (node && (!node.name || node.name.value === '')) {
26
+ if (!node.name || !node.name.value) {
27
27
  context.report({
28
- node: node,
29
- message: `Oops, name is required!`,
30
- });
28
+ node,
29
+ message: 'Oops, name is required!'
30
+ })
31
31
  }
32
- },
33
- };
34
- },
35
- };
32
+ }
33
+ }
34
+ }
35
+ }
36
36
  ```
37
37
 
38
38
  So what happens here?
@@ -58,23 +58,23 @@ This is useful if you wish to use other GraphQL tools that works with the origin
58
58
  Here's an example for using original `graphql-js` validate method to validate `OperationDefinition`:
59
59
 
60
60
  ```ts
61
- import { validate } from 'graphql';
62
- import { requireGraphQLSchemaFromContext } from '@graphql-eslint/eslint-plugin';
61
+ import { validate } from 'graphql'
62
+ import { requireGraphQLSchemaFromContext } from '@graphql-eslint/eslint-plugin'
63
63
 
64
64
  export const rule = {
65
65
  create(context) {
66
66
  return {
67
67
  OperationDefinition(node) {
68
- const schema = requireGraphQLSchemaFromContext(context);
68
+ const schema = requireGraphQLSchemaFromContext(context)
69
69
 
70
70
  validate(context, schema, {
71
71
  kind: Kind.DOCUMENT,
72
- definitions: [node.rawNode()],
73
- });
74
- },
75
- };
76
- },
77
- };
72
+ definitions: [node.rawNode()]
73
+ })
74
+ }
75
+ }
76
+ }
77
+ }
78
78
  ```
79
79
 
80
80
  ## `TypeInfo` / `GraphQLSchema`
@@ -89,7 +89,7 @@ If you provide GraphQL schema in your ESLint configuration, it will get loaded a
89
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
90
 
91
91
  ```ts
92
- const schema = requireGraphQLSchemaFromContext(context);
92
+ const schema = requireGraphQLSchemaFromContext(context)
93
93
  ```
94
94
 
95
95
  #### Accessing TypeInfo
@@ -100,23 +100,23 @@ If your plugin requires `typeInfo` in order to operate and run, make sure to cal
100
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
101
 
102
102
  ```ts
103
- import { requireGraphQLSchemaFromContext } from '@graphql-eslint/eslint-plugin';
103
+ import { requireGraphQLSchemaFromContext } from '@graphql-eslint/eslint-plugin'
104
104
 
105
105
  export const rule = {
106
106
  create(context) {
107
- requireGraphQLSchemaFromContext(context);
107
+ requireGraphQLSchemaFromContext(context)
108
108
 
109
109
  return {
110
110
  SelectionSet(node) {
111
- const typeInfo = node.typeInfo();
111
+ const typeInfo = node.typeInfo()
112
112
 
113
113
  if (typeInfo && typeInfo.gqlType) {
114
- console.log(`The GraphQLOutputType is: ${typeInfo.gqlType}`);
114
+ console.log(`The GraphQLOutputType is: ${typeInfo.gqlType}`)
115
115
  }
116
- },
117
- };
118
- },
119
- };
116
+ }
117
+ }
118
+ }
119
+ }
120
120
  ```
121
121
 
122
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.
@@ -128,21 +128,21 @@ To test your rules, you can either use the wrapped `GraphQLRuleTester` from this
128
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
129
 
130
130
  ```ts
131
- import { GraphQLRuleTester } from '@graphql-eslint/eslint-plugin';
132
- import { rule } from './my-rule';
131
+ import { GraphQLRuleTester } from '@graphql-eslint/eslint-plugin'
132
+ import { rule } from './my-rule'
133
133
 
134
- const ruleTester = new GraphQLRuleTester();
134
+ const ruleTester = new GraphQLRuleTester()
135
135
 
136
136
  ruleTester.runGraphQLTests('my-rule', rule, {
137
137
  valid: [
138
138
  {
139
- code: `query something { foo }`,
140
- },
139
+ code: 'query something { foo }'
140
+ }
141
141
  ],
142
142
  invalid: [
143
143
  {
144
- code: `query invalid { foo }`,
145
- },
146
- ],
147
- });
144
+ code: 'query invalid { foo }'
145
+ }
146
+ ]
147
+ })
148
148
  ```
@@ -48,8 +48,8 @@ enum SomeType {
48
48
  mutation {
49
49
  changeSomething(
50
50
  type: OLD # This is deprecated, so you'll get an error
51
- ) {
52
- ...
51
+ ) {
52
+ ...
53
53
  }
54
54
  }
55
55
  ```
@@ -0,0 +1,56 @@
1
+ # `no-root-type`
2
+
3
+ - Category: `Validation`
4
+ - Rule name: `@graphql-eslint/no-root-type`
5
+ - Requires GraphQL Schema: `true` [ℹ️](../../README.md#extended-linting-rules-with-graphql-schema)
6
+ - Requires GraphQL Operations: `false` [ℹ️](../../README.md#extended-linting-rules-with-siblings-operations)
7
+
8
+ Disallow using root types for `read-only` or `write-only` schemas.
9
+
10
+ ## Usage Examples
11
+
12
+ ### Incorrect (`read-only` schema)
13
+
14
+ ```graphql
15
+ # eslint @graphql-eslint/no-root-type: ['error', { disallow: ['mutation', 'subscription'] }]
16
+
17
+ type Mutation {
18
+ createUser(input: CreateUserInput!): User!
19
+ }
20
+ ```
21
+
22
+ ### Incorrect (`write-only` schema)
23
+
24
+ ```graphql
25
+ # eslint @graphql-eslint/no-root-type: ['error', { disallow: ['query'] }]
26
+
27
+ type Query {
28
+ users: [User!]!
29
+ }
30
+ ```
31
+
32
+ ### Correct (`read-only` schema)
33
+
34
+ ```graphql
35
+ # eslint @graphql-eslint/no-root-type: ['error', { disallow: ['mutation', 'subscription'] }]
36
+
37
+ type Query {
38
+ users: [User!]!
39
+ }
40
+ ```
41
+
42
+ ## Config Schema
43
+
44
+ The schema defines the following properties:
45
+
46
+ ### `disallow` (array, required)
47
+
48
+ Additional restrictions:
49
+
50
+ * Minimum items: `1`
51
+ * Unique items: `true`
52
+
53
+ ## Resources
54
+
55
+ - [Rule source](../../packages/plugin/src/rules/no-root-type.ts)
56
+ - [Test source](../../packages/plugin/tests/no-root-type.spec.ts)
package/index.js CHANGED
@@ -91,6 +91,7 @@ const allConfig = {
91
91
  '@graphql-eslint/match-document-filename': 'error',
92
92
  '@graphql-eslint/no-deprecated': 'error',
93
93
  '@graphql-eslint/no-hashtag-description': 'error',
94
+ '@graphql-eslint/no-root-type': ['error', { disallow: ['subscription'] }],
94
95
  '@graphql-eslint/no-unreachable-types': 'error',
95
96
  '@graphql-eslint/no-unused-fields': 'error',
96
97
  '@graphql-eslint/require-deprecation-date': 'error',
@@ -2065,9 +2066,100 @@ const rule$d = {
2065
2066
  },
2066
2067
  };
2067
2068
 
2069
+ const ROOT_TYPES = ['query', 'mutation', 'subscription'];
2070
+ const rule$e = {
2071
+ meta: {
2072
+ type: 'suggestion',
2073
+ docs: {
2074
+ category: 'Validation',
2075
+ description: 'Disallow using root types for `read-only` or `write-only` schemas.',
2076
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-root-type.md',
2077
+ requiresSchema: true,
2078
+ examples: [
2079
+ {
2080
+ title: 'Incorrect (`read-only` schema)',
2081
+ usage: [{ disallow: ['mutation', 'subscription'] }],
2082
+ code: /* GraphQL */ `
2083
+ type Mutation {
2084
+ createUser(input: CreateUserInput!): User!
2085
+ }
2086
+ `,
2087
+ },
2088
+ {
2089
+ title: 'Incorrect (`write-only` schema)',
2090
+ usage: [{ disallow: ['query'] }],
2091
+ code: /* GraphQL */ `
2092
+ type Query {
2093
+ users: [User!]!
2094
+ }
2095
+ `,
2096
+ },
2097
+ {
2098
+ title: 'Correct (`read-only` schema)',
2099
+ usage: [{ disallow: ['mutation', 'subscription'] }],
2100
+ code: /* GraphQL */ `
2101
+ type Query {
2102
+ users: [User!]!
2103
+ }
2104
+ `,
2105
+ },
2106
+ ],
2107
+ optionsForConfig: [{ disallow: ['subscription'] }],
2108
+ },
2109
+ schema: {
2110
+ type: 'array',
2111
+ minItems: 1,
2112
+ maxItems: 1,
2113
+ items: {
2114
+ type: 'object',
2115
+ additionalProperties: false,
2116
+ required: ['disallow'],
2117
+ properties: {
2118
+ disallow: {
2119
+ type: 'array',
2120
+ uniqueItems: true,
2121
+ minItems: 1,
2122
+ items: {
2123
+ enum: ROOT_TYPES,
2124
+ },
2125
+ },
2126
+ },
2127
+ },
2128
+ },
2129
+ },
2130
+ create(context) {
2131
+ const schema = requireGraphQLSchemaFromContext('no-root-type', context);
2132
+ const disallow = new Set(context.options[0].disallow);
2133
+ const rootTypeNames = [
2134
+ disallow.has('query') && schema.getQueryType(),
2135
+ disallow.has('mutation') && schema.getMutationType(),
2136
+ disallow.has('subscription') && schema.getSubscriptionType(),
2137
+ ]
2138
+ .filter(Boolean)
2139
+ .map(type => type.name);
2140
+ if (rootTypeNames.length === 0) {
2141
+ return {};
2142
+ }
2143
+ const selector = [
2144
+ `:matches(${graphql.Kind.OBJECT_TYPE_DEFINITION}, ${graphql.Kind.OBJECT_TYPE_EXTENSION})`,
2145
+ '>',
2146
+ `${graphql.Kind.NAME}[value=/^(${rootTypeNames.join('|')})$/]`,
2147
+ ].join(' ');
2148
+ return {
2149
+ [selector](node) {
2150
+ const typeName = node.value;
2151
+ context.report({
2152
+ loc: getLocation(node.loc, typeName),
2153
+ message: `Root type "${typeName}" is forbidden`,
2154
+ });
2155
+ },
2156
+ };
2157
+ },
2158
+ };
2159
+
2068
2160
  const UNREACHABLE_TYPE = 'UNREACHABLE_TYPE';
2069
2161
  const RULE_NAME = 'no-unreachable-types';
2070
- const rule$e = {
2162
+ const rule$f = {
2071
2163
  meta: {
2072
2164
  messages: {
2073
2165
  [UNREACHABLE_TYPE]: `Type "{{ typeName }}" is unreachable`,
@@ -2143,7 +2235,7 @@ const rule$e = {
2143
2235
 
2144
2236
  const UNUSED_FIELD = 'UNUSED_FIELD';
2145
2237
  const RULE_NAME$1 = 'no-unused-fields';
2146
- const rule$f = {
2238
+ const rule$g = {
2147
2239
  meta: {
2148
2240
  messages: {
2149
2241
  [UNUSED_FIELD]: `Field "{{fieldName}}" is unused`,
@@ -2331,7 +2423,7 @@ const MESSAGE_REQUIRE_DATE = 'MESSAGE_REQUIRE_DATE';
2331
2423
  const MESSAGE_INVALID_FORMAT = 'MESSAGE_INVALID_FORMAT';
2332
2424
  const MESSAGE_INVALID_DATE = 'MESSAGE_INVALID_DATE';
2333
2425
  const MESSAGE_CAN_BE_REMOVED = 'MESSAGE_CAN_BE_REMOVED';
2334
- const rule$g = {
2426
+ const rule$h = {
2335
2427
  meta: {
2336
2428
  type: 'suggestion',
2337
2429
  docs: {
@@ -2434,7 +2526,7 @@ const rule$g = {
2434
2526
  },
2435
2527
  };
2436
2528
 
2437
- const rule$h = {
2529
+ const rule$i = {
2438
2530
  meta: {
2439
2531
  docs: {
2440
2532
  description: `Require all deprecation directives to specify a reason.`,
@@ -2514,7 +2606,7 @@ function verifyRule(context, node) {
2514
2606
  }
2515
2607
  }
2516
2608
  }
2517
- const rule$i = {
2609
+ const rule$j = {
2518
2610
  meta: {
2519
2611
  docs: {
2520
2612
  category: 'Best Practices',
@@ -2579,7 +2671,7 @@ const rule$i = {
2579
2671
  };
2580
2672
 
2581
2673
  const RULE_NAME$2 = 'require-field-of-type-query-in-mutation-result';
2582
- const rule$j = {
2674
+ const rule$k = {
2583
2675
  meta: {
2584
2676
  type: 'suggestion',
2585
2677
  docs: {
@@ -2747,7 +2839,7 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2747
2839
 
2748
2840
  const REQUIRE_ID_WHEN_AVAILABLE = 'REQUIRE_ID_WHEN_AVAILABLE';
2749
2841
  const DEFAULT_ID_FIELD_NAME = 'id';
2750
- const rule$k = {
2842
+ const rule$l = {
2751
2843
  meta: {
2752
2844
  type: 'suggestion',
2753
2845
  docs: {
@@ -2883,7 +2975,7 @@ const rule$k = {
2883
2975
  },
2884
2976
  };
2885
2977
 
2886
- const rule$l = {
2978
+ const rule$m = {
2887
2979
  meta: {
2888
2980
  docs: {
2889
2981
  category: 'Best Practices',
@@ -3005,7 +3097,7 @@ const shouldIgnoreNode = ({ node, exceptions }) => {
3005
3097
  }
3006
3098
  return false;
3007
3099
  };
3008
- const rule$m = {
3100
+ const rule$n = {
3009
3101
  meta: {
3010
3102
  type: 'suggestion',
3011
3103
  docs: {
@@ -3182,7 +3274,7 @@ const checkNode = (context, node, ruleName, messageId) => {
3182
3274
  });
3183
3275
  }
3184
3276
  };
3185
- const rule$n = {
3277
+ const rule$o = {
3186
3278
  meta: {
3187
3279
  type: 'suggestion',
3188
3280
  docs: {
@@ -3241,7 +3333,7 @@ const rule$n = {
3241
3333
 
3242
3334
  const RULE_NAME$4 = 'unique-operation-name';
3243
3335
  const UNIQUE_OPERATION_NAME = 'UNIQUE_OPERATION_NAME';
3244
- const rule$o = {
3336
+ const rule$p = {
3245
3337
  meta: {
3246
3338
  type: 'suggestion',
3247
3339
  docs: {
@@ -3321,17 +3413,18 @@ const rules = {
3321
3413
  'no-deprecated': rule$b,
3322
3414
  'no-hashtag-description': rule$c,
3323
3415
  'no-operation-name-suffix': rule$d,
3324
- 'no-unreachable-types': rule$e,
3325
- 'no-unused-fields': rule$f,
3326
- 'require-deprecation-date': rule$g,
3327
- 'require-deprecation-reason': rule$h,
3328
- 'require-description': rule$i,
3329
- 'require-field-of-type-query-in-mutation-result': rule$j,
3330
- 'require-id-when-available': rule$k,
3331
- 'selection-set-depth': rule$l,
3332
- 'strict-id-in-types': rule$m,
3333
- 'unique-fragment-name': rule$n,
3334
- 'unique-operation-name': rule$o,
3416
+ 'no-root-type': rule$e,
3417
+ 'no-unreachable-types': rule$f,
3418
+ 'no-unused-fields': rule$g,
3419
+ 'require-deprecation-date': rule$h,
3420
+ 'require-deprecation-reason': rule$i,
3421
+ 'require-description': rule$j,
3422
+ 'require-field-of-type-query-in-mutation-result': rule$k,
3423
+ 'require-id-when-available': rule$l,
3424
+ 'selection-set-depth': rule$m,
3425
+ 'strict-id-in-types': rule$n,
3426
+ 'unique-fragment-name': rule$o,
3427
+ 'unique-operation-name': rule$p,
3335
3428
  };
3336
3429
 
3337
3430
  const RELEVANT_KEYWORDS = ['gql', 'graphql', '/* GraphQL */'];
package/index.mjs CHANGED
@@ -85,6 +85,7 @@ const allConfig = {
85
85
  '@graphql-eslint/match-document-filename': 'error',
86
86
  '@graphql-eslint/no-deprecated': 'error',
87
87
  '@graphql-eslint/no-hashtag-description': 'error',
88
+ '@graphql-eslint/no-root-type': ['error', { disallow: ['subscription'] }],
88
89
  '@graphql-eslint/no-unreachable-types': 'error',
89
90
  '@graphql-eslint/no-unused-fields': 'error',
90
91
  '@graphql-eslint/require-deprecation-date': 'error',
@@ -2059,9 +2060,100 @@ const rule$d = {
2059
2060
  },
2060
2061
  };
2061
2062
 
2063
+ const ROOT_TYPES = ['query', 'mutation', 'subscription'];
2064
+ const rule$e = {
2065
+ meta: {
2066
+ type: 'suggestion',
2067
+ docs: {
2068
+ category: 'Validation',
2069
+ description: 'Disallow using root types for `read-only` or `write-only` schemas.',
2070
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-root-type.md',
2071
+ requiresSchema: true,
2072
+ examples: [
2073
+ {
2074
+ title: 'Incorrect (`read-only` schema)',
2075
+ usage: [{ disallow: ['mutation', 'subscription'] }],
2076
+ code: /* GraphQL */ `
2077
+ type Mutation {
2078
+ createUser(input: CreateUserInput!): User!
2079
+ }
2080
+ `,
2081
+ },
2082
+ {
2083
+ title: 'Incorrect (`write-only` schema)',
2084
+ usage: [{ disallow: ['query'] }],
2085
+ code: /* GraphQL */ `
2086
+ type Query {
2087
+ users: [User!]!
2088
+ }
2089
+ `,
2090
+ },
2091
+ {
2092
+ title: 'Correct (`read-only` schema)',
2093
+ usage: [{ disallow: ['mutation', 'subscription'] }],
2094
+ code: /* GraphQL */ `
2095
+ type Query {
2096
+ users: [User!]!
2097
+ }
2098
+ `,
2099
+ },
2100
+ ],
2101
+ optionsForConfig: [{ disallow: ['subscription'] }],
2102
+ },
2103
+ schema: {
2104
+ type: 'array',
2105
+ minItems: 1,
2106
+ maxItems: 1,
2107
+ items: {
2108
+ type: 'object',
2109
+ additionalProperties: false,
2110
+ required: ['disallow'],
2111
+ properties: {
2112
+ disallow: {
2113
+ type: 'array',
2114
+ uniqueItems: true,
2115
+ minItems: 1,
2116
+ items: {
2117
+ enum: ROOT_TYPES,
2118
+ },
2119
+ },
2120
+ },
2121
+ },
2122
+ },
2123
+ },
2124
+ create(context) {
2125
+ const schema = requireGraphQLSchemaFromContext('no-root-type', context);
2126
+ const disallow = new Set(context.options[0].disallow);
2127
+ const rootTypeNames = [
2128
+ disallow.has('query') && schema.getQueryType(),
2129
+ disallow.has('mutation') && schema.getMutationType(),
2130
+ disallow.has('subscription') && schema.getSubscriptionType(),
2131
+ ]
2132
+ .filter(Boolean)
2133
+ .map(type => type.name);
2134
+ if (rootTypeNames.length === 0) {
2135
+ return {};
2136
+ }
2137
+ const selector = [
2138
+ `:matches(${Kind.OBJECT_TYPE_DEFINITION}, ${Kind.OBJECT_TYPE_EXTENSION})`,
2139
+ '>',
2140
+ `${Kind.NAME}[value=/^(${rootTypeNames.join('|')})$/]`,
2141
+ ].join(' ');
2142
+ return {
2143
+ [selector](node) {
2144
+ const typeName = node.value;
2145
+ context.report({
2146
+ loc: getLocation(node.loc, typeName),
2147
+ message: `Root type "${typeName}" is forbidden`,
2148
+ });
2149
+ },
2150
+ };
2151
+ },
2152
+ };
2153
+
2062
2154
  const UNREACHABLE_TYPE = 'UNREACHABLE_TYPE';
2063
2155
  const RULE_NAME = 'no-unreachable-types';
2064
- const rule$e = {
2156
+ const rule$f = {
2065
2157
  meta: {
2066
2158
  messages: {
2067
2159
  [UNREACHABLE_TYPE]: `Type "{{ typeName }}" is unreachable`,
@@ -2137,7 +2229,7 @@ const rule$e = {
2137
2229
 
2138
2230
  const UNUSED_FIELD = 'UNUSED_FIELD';
2139
2231
  const RULE_NAME$1 = 'no-unused-fields';
2140
- const rule$f = {
2232
+ const rule$g = {
2141
2233
  meta: {
2142
2234
  messages: {
2143
2235
  [UNUSED_FIELD]: `Field "{{fieldName}}" is unused`,
@@ -2325,7 +2417,7 @@ const MESSAGE_REQUIRE_DATE = 'MESSAGE_REQUIRE_DATE';
2325
2417
  const MESSAGE_INVALID_FORMAT = 'MESSAGE_INVALID_FORMAT';
2326
2418
  const MESSAGE_INVALID_DATE = 'MESSAGE_INVALID_DATE';
2327
2419
  const MESSAGE_CAN_BE_REMOVED = 'MESSAGE_CAN_BE_REMOVED';
2328
- const rule$g = {
2420
+ const rule$h = {
2329
2421
  meta: {
2330
2422
  type: 'suggestion',
2331
2423
  docs: {
@@ -2428,7 +2520,7 @@ const rule$g = {
2428
2520
  },
2429
2521
  };
2430
2522
 
2431
- const rule$h = {
2523
+ const rule$i = {
2432
2524
  meta: {
2433
2525
  docs: {
2434
2526
  description: `Require all deprecation directives to specify a reason.`,
@@ -2508,7 +2600,7 @@ function verifyRule(context, node) {
2508
2600
  }
2509
2601
  }
2510
2602
  }
2511
- const rule$i = {
2603
+ const rule$j = {
2512
2604
  meta: {
2513
2605
  docs: {
2514
2606
  category: 'Best Practices',
@@ -2573,7 +2665,7 @@ const rule$i = {
2573
2665
  };
2574
2666
 
2575
2667
  const RULE_NAME$2 = 'require-field-of-type-query-in-mutation-result';
2576
- const rule$j = {
2668
+ const rule$k = {
2577
2669
  meta: {
2578
2670
  type: 'suggestion',
2579
2671
  docs: {
@@ -2741,7 +2833,7 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2741
2833
 
2742
2834
  const REQUIRE_ID_WHEN_AVAILABLE = 'REQUIRE_ID_WHEN_AVAILABLE';
2743
2835
  const DEFAULT_ID_FIELD_NAME = 'id';
2744
- const rule$k = {
2836
+ const rule$l = {
2745
2837
  meta: {
2746
2838
  type: 'suggestion',
2747
2839
  docs: {
@@ -2877,7 +2969,7 @@ const rule$k = {
2877
2969
  },
2878
2970
  };
2879
2971
 
2880
- const rule$l = {
2972
+ const rule$m = {
2881
2973
  meta: {
2882
2974
  docs: {
2883
2975
  category: 'Best Practices',
@@ -2999,7 +3091,7 @@ const shouldIgnoreNode = ({ node, exceptions }) => {
2999
3091
  }
3000
3092
  return false;
3001
3093
  };
3002
- const rule$m = {
3094
+ const rule$n = {
3003
3095
  meta: {
3004
3096
  type: 'suggestion',
3005
3097
  docs: {
@@ -3176,7 +3268,7 @@ const checkNode = (context, node, ruleName, messageId) => {
3176
3268
  });
3177
3269
  }
3178
3270
  };
3179
- const rule$n = {
3271
+ const rule$o = {
3180
3272
  meta: {
3181
3273
  type: 'suggestion',
3182
3274
  docs: {
@@ -3235,7 +3327,7 @@ const rule$n = {
3235
3327
 
3236
3328
  const RULE_NAME$4 = 'unique-operation-name';
3237
3329
  const UNIQUE_OPERATION_NAME = 'UNIQUE_OPERATION_NAME';
3238
- const rule$o = {
3330
+ const rule$p = {
3239
3331
  meta: {
3240
3332
  type: 'suggestion',
3241
3333
  docs: {
@@ -3315,17 +3407,18 @@ const rules = {
3315
3407
  'no-deprecated': rule$b,
3316
3408
  'no-hashtag-description': rule$c,
3317
3409
  'no-operation-name-suffix': rule$d,
3318
- 'no-unreachable-types': rule$e,
3319
- 'no-unused-fields': rule$f,
3320
- 'require-deprecation-date': rule$g,
3321
- 'require-deprecation-reason': rule$h,
3322
- 'require-description': rule$i,
3323
- 'require-field-of-type-query-in-mutation-result': rule$j,
3324
- 'require-id-when-available': rule$k,
3325
- 'selection-set-depth': rule$l,
3326
- 'strict-id-in-types': rule$m,
3327
- 'unique-fragment-name': rule$n,
3328
- 'unique-operation-name': rule$o,
3410
+ 'no-root-type': rule$e,
3411
+ 'no-unreachable-types': rule$f,
3412
+ 'no-unused-fields': rule$g,
3413
+ 'require-deprecation-date': rule$h,
3414
+ 'require-deprecation-reason': rule$i,
3415
+ 'require-description': rule$j,
3416
+ 'require-field-of-type-query-in-mutation-result': rule$k,
3417
+ 'require-id-when-available': rule$l,
3418
+ 'selection-set-depth': rule$m,
3419
+ 'strict-id-in-types': rule$n,
3420
+ 'unique-fragment-name': rule$o,
3421
+ 'unique-operation-name': rule$p,
3329
3422
  };
3330
3423
 
3331
3424
  const RELEVANT_KEYWORDS = ['gql', 'graphql', '/* GraphQL */'];
package/package.json CHANGED
@@ -1,18 +1,19 @@
1
1
  {
2
2
  "name": "@graphql-eslint/eslint-plugin",
3
- "version": "2.5.0-alpha-f60e6aa.0",
3
+ "version": "2.5.0",
4
4
  "sideEffects": false,
5
5
  "peerDependencies": {
6
- "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0"
6
+ "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
7
7
  },
8
8
  "dependencies": {
9
- "@graphql-tools/code-file-loader": "^7.0.2",
10
- "@graphql-tools/graphql-tag-pluck": "^7.0.2",
11
- "@graphql-tools/import": "^6.3.1",
12
- "@graphql-tools/utils": "^8.0.2",
13
- "graphql-config": "^4.0.1",
9
+ "@babel/code-frame": "7.16.0",
10
+ "@graphql-tools/code-file-loader": "7.2.2",
11
+ "@graphql-tools/graphql-tag-pluck": "7.1.3",
12
+ "@graphql-tools/import": "6.6.1",
13
+ "@graphql-tools/utils": "8.5.3",
14
+ "graphql-config": "4.1.0",
14
15
  "graphql-depth-limit": "1.1.0",
15
- "lodash.lowercase": "^4.3.0"
16
+ "lodash.lowercase": "4.3.0"
16
17
  },
17
18
  "repository": "https://github.com/dotansimha/graphql-eslint",
18
19
  "author": "Dotan Simha <dotansimha@gmail.com>",
package/rules/index.d.ts CHANGED
@@ -59,6 +59,9 @@ export declare const rules: {
59
59
  'no-deprecated': import("..").GraphQLESLintRule<[], true>;
60
60
  'no-hashtag-description': import("..").GraphQLESLintRule<any[], false>;
61
61
  'no-operation-name-suffix': import("..").GraphQLESLintRule<any[], false>;
62
+ 'no-root-type': import("..").GraphQLESLintRule<[{
63
+ disallow: ("subscription" | "query" | "mutation")[];
64
+ }], false>;
62
65
  'no-unreachable-types': import("..").GraphQLESLintRule<any[], false>;
63
66
  'no-unused-fields': import("..").GraphQLESLintRule<any[], false>;
64
67
  'require-deprecation-date': import("..").GraphQLESLintRule<[{
@@ -0,0 +1,7 @@
1
+ import { GraphQLESLintRule } from '../types';
2
+ declare const ROOT_TYPES: ('query' | 'mutation' | 'subscription')[];
3
+ declare type NoRootTypeConfig = {
4
+ disallow: typeof ROOT_TYPES;
5
+ };
6
+ declare const rule: GraphQLESLintRule<[NoRootTypeConfig]>;
7
+ export default rule;
package/testkit.d.ts CHANGED
@@ -6,7 +6,7 @@ export declare type GraphQLESLintRuleListener<WithTypeInfo extends boolean = fal
6
6
  [K in keyof ASTKindToNode]?: (node: GraphQLESTreeNode<ASTKindToNode[K], WithTypeInfo>) => void;
7
7
  } & Record<string, any>;
8
8
  export declare type GraphQLValidTestCase<Options> = Omit<RuleTester.ValidTestCase, 'options' | 'parserOptions'> & {
9
- name: string;
9
+ name?: string;
10
10
  options?: Options;
11
11
  parserOptions?: ParserOptions;
12
12
  };