Skip to content

Commit ffa06b8

Browse files
comatoryBalvajs
andcommitted
nullable-records rule
This rule enforces that every field that returns type that contains `id` field, its return type must be nullable. Co-Authored-By: Tomas Balvin <[email protected]>
1 parent 219506a commit ffa06b8

File tree

7 files changed

+1174
-0
lines changed

7 files changed

+1174
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`nullable-records > invalid > should disallow non-nullability on Node interface 1`] = `
4+
#### ⌨️ Code
5+
6+
1 | interface Node {
7+
2 | id: ID!
8+
3 | }
9+
4 | type Query {
10+
5 | roadmap: Node!
11+
6 | }
12+
13+
#### ❌ Error
14+
15+
4 | type Query {
16+
> 5 | roadmap: Node!
17+
| ^^^^^^^^^^^^^ Type \`Node\` has to be nullable, because it has \`id\` field and can be deleted in the client runtime. https://the-guild.dev/graphql/eslint/rules/nullable-records
18+
6 | }
19+
20+
#### 🔧 Autofix output
21+
22+
1 | interface Node {
23+
2 | id: ID!
24+
3 | }
25+
4 | type Query {
26+
5 | roadmap: Node
27+
6 | }
28+
`;
29+
30+
exports[`nullable-records > invalid > should disallow non-nullability on a union of types where at least one type has ID 1`] = `
31+
#### ⌨️ Code
32+
33+
1 | type Config {
34+
2 | name: String!
35+
3 | }
36+
4 | type Roadmap {
37+
5 | id: ID!
38+
6 | }
39+
7 | union ConfigOrRoadmap = Config | Roadmap
40+
8 | type Query {
41+
9 | config: ConfigOrRoadmap!
42+
10 | }
43+
44+
#### ❌ Error
45+
46+
8 | type Query {
47+
> 9 | config: ConfigOrRoadmap!
48+
| ^^^^^^^^^^^^^^^^^^^^^^^ Union type \`ConfigOrRoadmap\` has to be nullable, because types \`Roadmap\` have \`id\` field and can be deleted in the client runtime. https://the-guild.dev/graphql/eslint/rules/nullable-records
49+
10 | }
50+
51+
#### 🔧 Autofix output
52+
53+
1 | type Config {
54+
2 | name: String!
55+
3 | }
56+
4 | type Roadmap {
57+
5 | id: ID!
58+
6 | }
59+
7 | union ConfigOrRoadmap = Config | Roadmap
60+
8 | type Query {
61+
9 | config: ConfigOrRoadmap
62+
10 | }
63+
`;
64+
65+
exports[`nullable-records > invalid > should disallow non-nullability on list of unions of types where at least one type has ID 1`] = `
66+
#### ⌨️ Code
67+
68+
1 | type Config {
69+
2 | name: String!
70+
3 | }
71+
4 | type Roadmap {
72+
5 | id: ID!
73+
6 | }
74+
7 | union ConfigOrRoadmap = Config | Roadmap
75+
8 | type Query {
76+
9 | configs: [ConfigOrRoadmap!]!
77+
10 | }
78+
79+
#### ❌ Error
80+
81+
8 | type Query {
82+
> 9 | configs: [ConfigOrRoadmap!]!
83+
| ^^^^^^^^^^^^^^^^^ Union type \`ConfigOrRoadmap\` has to be nullable, because types \`Roadmap\` have \`id\` field and can be deleted in the client runtime. https://the-guild.dev/graphql/eslint/rules/nullable-records
84+
10 | }
85+
86+
#### 🔧 Autofix output
87+
88+
1 | type Config {
89+
2 | name: String!
90+
3 | }
91+
4 | type Roadmap {
92+
5 | id: ID!
93+
6 | }
94+
7 | union ConfigOrRoadmap = Config | Roadmap
95+
8 | type Query {
96+
9 | configs: [ConfigOrRoadmap]!
97+
10 | }
98+
`;
99+
100+
exports[`nullable-records > invalid > should disallow non-nullability on non-nullable list of Node interfaces 1`] = `
101+
#### ⌨️ Code
102+
103+
1 | interface Node {
104+
2 | id: ID!
105+
3 | }
106+
4 | type Query {
107+
5 | roadmaps: [Node!]!
108+
6 | }
109+
110+
#### ❌ Error
111+
112+
4 | type Query {
113+
> 5 | roadmaps: [Node!]!
114+
| ^^^^^^ Type \`Node\` has to be nullable, because it has \`id\` field and can be deleted in the client runtime. https://the-guild.dev/graphql/eslint/rules/nullable-records
115+
6 | }
116+
117+
#### 🔧 Autofix output
118+
119+
1 | interface Node {
120+
2 | id: ID!
121+
3 | }
122+
4 | type Query {
123+
5 | roadmaps: [Node]!
124+
6 | }
125+
`;
126+
127+
exports[`nullable-records > invalid > should disallow non-nullability on type that has ID 1`] = `
128+
#### ⌨️ Code
129+
130+
1 | type Roadmap {
131+
2 | id: ID!
132+
3 | }
133+
4 | type Query {
134+
5 | roadmap: Roadmap!
135+
6 | }
136+
137+
#### ❌ Error
138+
139+
4 | type Query {
140+
> 5 | roadmap: Roadmap!
141+
| ^^^^^^^^^^^^^^^^ Type \`Roadmap\` has to be nullable, because it has \`id\` field and can be deleted in the client runtime. https://the-guild.dev/graphql/eslint/rules/nullable-records
142+
6 | }
143+
144+
#### 🔧 Autofix output
145+
146+
1 | type Roadmap {
147+
2 | id: ID!
148+
3 | }
149+
4 | type Query {
150+
5 | roadmap: Roadmap
151+
6 | }
152+
`;
153+
154+
exports[`nullable-records > invalid > should disallow non-nullability on type that references type with ID 1`] = `
155+
#### ⌨️ Code
156+
157+
1 | type Feature {
158+
2 | id: ID!
159+
3 | }
160+
4 | type Roadmap {
161+
5 | id: ID!
162+
6 | feature: Feature!
163+
7 | }
164+
8 | type Query {
165+
9 | roadmap: Roadmap!
166+
10 | }
167+
168+
#### ❌ Error 1/2
169+
170+
5 | id: ID!
171+
> 6 | feature: Feature!
172+
| ^^^^^^^^^^^^^^^^ Type \`Feature\` has to be nullable, because it has \`id\` field and can be deleted in the client runtime. https://the-guild.dev/graphql/eslint/rules/nullable-records
173+
7 | }
174+
175+
#### ❌ Error 2/2
176+
177+
8 | type Query {
178+
> 9 | roadmap: Roadmap!
179+
| ^^^^^^^^^^^^^^^^ Type \`Roadmap\` has to be nullable, because it has \`id\` field and can be deleted in the client runtime. https://the-guild.dev/graphql/eslint/rules/nullable-records
180+
10 | }
181+
182+
#### 🔧 Autofix output
183+
184+
1 | type Feature {
185+
2 | id: ID!
186+
3 | }
187+
4 | type Roadmap {
188+
5 | id: ID!
189+
6 | feature: Feature
190+
7 | }
191+
8 | type Query {
192+
9 | roadmap: Roadmap
193+
10 | }
194+
`;
195+
196+
exports[`nullable-records > invalid > should disallow non-nullability on type with ID 1`] = `
197+
#### ⌨️ Code
198+
199+
1 | type Roadmap {
200+
2 | id: ID!
201+
3 | }
202+
4 | type Query {
203+
5 | roadmap: Roadmap!
204+
6 | }
205+
206+
#### ❌ Error
207+
208+
4 | type Query {
209+
> 5 | roadmap: Roadmap!
210+
| ^^^^^^^^^^^^^^^^ Type \`Roadmap\` has to be nullable, because it has \`id\` field and can be deleted in the client runtime. https://the-guild.dev/graphql/eslint/rules/nullable-records
211+
6 | }
212+
213+
#### 🔧 Autofix output
214+
215+
1 | type Roadmap {
216+
2 | id: ID!
217+
3 | }
218+
4 | type Query {
219+
5 | roadmap: Roadmap
220+
6 | }
221+
`;
222+
223+
exports[`nullable-records > invalid > should disallow non-nullability on type with ID in a list and the list itself 1`] = `
224+
#### ⌨️ Code
225+
226+
1 | type Roadmap {
227+
2 | id: ID!
228+
3 | }
229+
4 | type Query {
230+
5 | roadmaps: [Roadmap!]!
231+
6 | }
232+
233+
#### ❌ Error
234+
235+
4 | type Query {
236+
> 5 | roadmaps: [Roadmap!]!
237+
| ^^^^^^^^^ Type \`Roadmap\` has to be nullable, because it has \`id\` field and can be deleted in the client runtime. https://the-guild.dev/graphql/eslint/rules/nullable-records
238+
6 | }
239+
240+
#### 🔧 Autofix output
241+
242+
1 | type Roadmap {
243+
2 | id: ID!
244+
3 | }
245+
4 | type Query {
246+
5 | roadmaps: [Roadmap]!
247+
6 | }
248+
`;
249+
250+
exports[`nullable-records > invalid > should disallow non-nullability on type with ID in a nullable list 1`] = `
251+
#### ⌨️ Code
252+
253+
1 | type Roadmap {
254+
2 | id: ID!
255+
3 | }
256+
4 | type Query {
257+
5 | roadmaps: [Roadmap!]
258+
6 | }
259+
260+
#### ❌ Error
261+
262+
4 | type Query {
263+
> 5 | roadmaps: [Roadmap!]
264+
| ^^^^^^^^^ Type \`Roadmap\` has to be nullable, because it has \`id\` field and can be deleted in the client runtime. https://the-guild.dev/graphql/eslint/rules/nullable-records
265+
6 | }
266+
267+
#### 🔧 Autofix output
268+
269+
1 | type Roadmap {
270+
2 | id: ID!
271+
3 | }
272+
4 | type Query {
273+
5 | roadmaps: [Roadmap]
274+
6 | }
275+
`;
276+
277+
exports[`nullable-records > invalid > should disallow non-nullability on type with ID in nested non-nullable lists 1`] = `
278+
#### ⌨️ Code
279+
280+
1 | type Roadmap {
281+
2 | id: ID!
282+
3 | }
283+
4 | type Query {
284+
5 | roadmaps: [[Roadmap!]!]!
285+
6 | }
286+
287+
#### ❌ Error
288+
289+
4 | type Query {
290+
> 5 | roadmaps: [[Roadmap!]!]!
291+
| ^^^^^^^^^ Type \`Roadmap\` has to be nullable, because it has \`id\` field and can be deleted in the client runtime. https://the-guild.dev/graphql/eslint/rules/nullable-records
292+
6 | }
293+
294+
#### 🔧 Autofix output
295+
296+
1 | type Roadmap {
297+
2 | id: ID!
298+
3 | }
299+
4 | type Query {
300+
5 | roadmaps: [[Roadmap]!]!
301+
6 | }
302+
`;
303+
304+
exports[`nullable-records > invalid > should disallow nullability on Node interface field 1`] = `
305+
#### ⌨️ Code
306+
307+
1 | interface Node {
308+
2 | id: ID!
309+
3 | }
310+
4 | type Column implements Node {
311+
5 | id: ID!
312+
6 | }
313+
7 | type Roadmap implements Node {
314+
8 | id: ID!
315+
9 | columns: [Column!]!
316+
10 | }
317+
11 | type Query {
318+
12 | node(id: ID!): Node
319+
13 | }
320+
321+
#### ❌ Error
322+
323+
8 | id: ID!
324+
> 9 | columns: [Column!]!
325+
| ^^^^^^^^ Type \`Column\` has to be nullable, because it has \`id\` field and can be deleted in the client runtime. https://the-guild.dev/graphql/eslint/rules/nullable-records
326+
10 | }
327+
328+
#### 🔧 Autofix output
329+
330+
1 | interface Node {
331+
2 | id: ID!
332+
3 | }
333+
4 | type Column implements Node {
334+
5 | id: ID!
335+
6 | }
336+
7 | type Roadmap implements Node {
337+
8 | id: ID!
338+
9 | columns: [Column]!
339+
10 | }
340+
11 | type Query {
341+
12 | node(id: ID!): Node
342+
13 | }
343+
`;

packages/plugin/src/rules/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { rule as strictIdInTypes } from './strict-id-in-types/index.js';
3737
import { rule as uniqueEnumValueNames } from './unique-enum-value-names/index.js';
3838
import { rule as uniqueFragmentName } from './unique-fragment-name/index.js';
3939
import { rule as uniqueOperationName } from './unique-operation-name/index.js';
40+
import { rule as nullableRecords } from './nullable-records/index.js'
4041

4142
export const rules = {
4243
...GRAPHQL_JS_VALIDATIONS,
@@ -56,6 +57,7 @@ export const rules = {
5657
'no-typename-prefix': noTypenamePrefix,
5758
'no-unreachable-types': noUnreachableTypes,
5859
'no-unused-fields': noUnusedFields,
60+
'nullable-records': nullableRecords,
5961
'relay-arguments': relayArguments,
6062
'relay-connection-types': relayConnectionTypes,
6163
'relay-edge-types': relayEdgeTypes,

0 commit comments

Comments
 (0)