Skip to content

Commit 80e922c

Browse files
authored
fixes for rawNode and validation rule (#67)
1 parent 6d1cfe9 commit 80e922c

File tree

6 files changed

+212
-21
lines changed

6 files changed

+212
-21
lines changed

.changeset/eighty-maps-juggle.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+
Improved `validate-against-schema` rule configuration (allow to customize rules)

.changeset/serious-games-yawn.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': patch
3+
---
4+
5+
Fix issues with `.rawNode()` values

docs/rules/validate-against-schema.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ This rule validates GraphQL operations against your GraphQL schema, and reflects
77

88
> Super useful with VSCode integration!
99
10+
The default set of validation rules is defined by GraphQL `validate` method ([list of rules](https://github.com/graphql/graphql-js/blob/master/src/validation/specifiedRules.js#L100-L128)).
11+
12+
You can configure the rules by overriding it, or ignoring rules from the default set.
13+
1014
### Usage Example
1115

1216
Examples of **incorrect** code for this rule:
@@ -40,3 +44,39 @@ query something {
4044
something # ok, field exists
4145
}
4246
```
47+
48+
## Configuration
49+
50+
By default, the [default set of validation rules](https://github.com/graphql/graphql-js/blob/master/src/validation/specifiedRules.js#L100-L128) is being executed. You can change that if you wish.
51+
52+
#### Overriding the entire list of rules
53+
54+
If you wish to override the entire list of rules, you can specify `overrideRules` key in your configuration:
55+
56+
```js
57+
// This will run only UniqueDirectivesPerLocationRule rule
58+
{
59+
rules: {
60+
'@graphql-eslint/validate-against-schema': ["error", {
61+
overrideRules: ["UniqueDirectivesPerLocationRule"]
62+
}]
63+
}
64+
}
65+
```
66+
67+
> Just use the name of the rule, as it specified by the list of available rules in `graphql-js` library.
68+
69+
#### Disable specific rules
70+
71+
If you wish to use the default list of rules, and just disable some of them, you can use the following:
72+
73+
```js
74+
// This will use the default list of rules, but will disable only KnownDirectivesRule
75+
{
76+
rules: {
77+
'@graphql-eslint/validate-against-schema': ["error", {
78+
disableRules: ["KnownDirectivesRule"]
79+
}]
80+
}
81+
}
82+
```

packages/plugin/src/estree-parser/converter.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ function stripTokens(location: Location): Pick<Location, 'start' | 'end'> {
3232
};
3333
}
3434

35-
const convertNode = (typeInfo?: TypeInfo) => <T extends ASTNode>(node: T): GraphQLESTreeNode<T> => {
35+
const convertNode = (typeInfo?: TypeInfo) => <T extends ASTNode>(
36+
node: T,
37+
key: string | number,
38+
parent: any
39+
): GraphQLESTreeNode<T> => {
3640
const calculatedTypeInfo = typeInfo
3741
? {
3842
argument: typeInfo.getArgument(),
@@ -64,7 +68,7 @@ const convertNode = (typeInfo?: TypeInfo) => <T extends ASTNode>(node: T): Graph
6468
...typeFieldSafe,
6569
...commonFields,
6670
type: node.kind,
67-
rawNode: () => node,
71+
rawNode: () => parent[key],
6872
gqlLocation: stripTokens(gqlLocation),
6973
} as any) as GraphQLESTreeNode<T>;
7074

@@ -76,7 +80,7 @@ const convertNode = (typeInfo?: TypeInfo) => <T extends ASTNode>(node: T): Graph
7680
...typeFieldSafe,
7781
...commonFields,
7882
type: node.kind,
79-
rawNode: () => node,
83+
rawNode: () => parent[key],
8084
gqlLocation: stripTokens(gqlLocation),
8185
} as any) as GraphQLESTreeNode<T>;
8286

packages/plugin/src/rules/validate-against-schema.ts

Lines changed: 88 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,119 @@
1-
import { Kind, validate, GraphQLSchema, DocumentNode } from 'graphql';
1+
import { Kind, validate, GraphQLSchema, DocumentNode, ASTNode, ValidationRule, specifiedRules } from 'graphql';
22
import { GraphQLESTreeNode } from '../estree-parser';
33
import { GraphQLESLintRule, GraphQLESlintRuleContext } from '../types';
44
import { requireGraphQLSchemaFromContext } from '../utils';
55

6-
function validateDoc(context: GraphQLESlintRuleContext, schema: GraphQLSchema, documentNode: DocumentNode) {
6+
function validateDoc(
7+
sourceNode: GraphQLESTreeNode<ASTNode>,
8+
context: GraphQLESlintRuleContext,
9+
schema: GraphQLSchema,
10+
documentNode: DocumentNode,
11+
rules: ReadonlyArray<ValidationRule>
12+
) {
713
if (documentNode && documentNode.definitions && documentNode.definitions.length > 0) {
8-
const validationErrors = validate(schema, documentNode);
14+
try {
15+
const validationErrors = validate(schema, documentNode, rules);
916

10-
for (const error of validationErrors) {
11-
const node = (error.nodes[0] as any) as GraphQLESTreeNode<typeof error.nodes[0]>;
17+
for (const error of validationErrors) {
18+
const node = (error.nodes[0] as any) as GraphQLESTreeNode<ASTNode>;
1219

20+
context.report({
21+
loc: node.loc,
22+
message: error.message,
23+
});
24+
}
25+
} catch (e) {
1326
context.report({
14-
loc: node.loc,
15-
message: error.message,
27+
node: sourceNode,
28+
message: e.message,
1629
});
1730
}
1831
}
1932
}
2033

21-
const rule: GraphQLESLintRule = {
34+
export type ValidateAgainstSchemaRuleConfig = [
35+
{
36+
overrideRules?: string[];
37+
disableRules?: string[];
38+
}
39+
];
40+
41+
const rule: GraphQLESLintRule<ValidateAgainstSchemaRuleConfig> = {
2242
meta: {
2343
docs: {
2444
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/validate-against-schema.md`,
2545
recommended: true,
2646
description: `This rule validates GraphQL operations against your GraphQL schema, and reflects the error as lint errors.`,
2747
},
48+
schema: {
49+
type: 'array',
50+
minItems: 0,
51+
maxItems: 1,
52+
items: {
53+
allOf: [
54+
{
55+
type: 'object',
56+
properties: {
57+
overrideRules: {
58+
type: 'array',
59+
items: {
60+
type: 'string',
61+
},
62+
},
63+
},
64+
},
65+
{
66+
type: 'object',
67+
properties: {
68+
disableRules: {
69+
type: 'array',
70+
items: {
71+
type: 'string',
72+
},
73+
},
74+
},
75+
},
76+
],
77+
},
78+
},
2879
type: 'problem',
2980
},
3081
create(context) {
82+
const config = context.options[0] || {};
83+
let rulesArr = specifiedRules;
84+
85+
if (config.disableRules && config.disableRules.length > 0) {
86+
rulesArr = specifiedRules.filter(r => !config.disableRules.includes(r.name));
87+
} else if (config.overrideRules && config.overrideRules.length > 0) {
88+
rulesArr = specifiedRules.filter(r => config.overrideRules.includes(r.name));
89+
}
90+
3191
return {
3292
OperationDefinition(node) {
3393
const schema = requireGraphQLSchemaFromContext(context);
34-
35-
validateDoc(context, schema, {
36-
kind: Kind.DOCUMENT,
37-
definitions: [node.rawNode()],
38-
});
94+
validateDoc(
95+
node,
96+
context,
97+
schema,
98+
{
99+
kind: Kind.DOCUMENT,
100+
definitions: [node.rawNode()],
101+
},
102+
rulesArr
103+
);
39104
},
40105
FragmentDefinition(node) {
41106
const schema = requireGraphQLSchemaFromContext(context);
42-
43-
validateDoc(context, schema, {
44-
kind: Kind.DOCUMENT,
45-
definitions: [node.rawNode()],
46-
});
107+
validateDoc(
108+
node,
109+
context,
110+
schema,
111+
{
112+
kind: Kind.DOCUMENT,
113+
definitions: [node.rawNode()],
114+
},
115+
rulesArr
116+
);
47117
},
48118
};
49119
},
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { GraphQLRuleTester } from '../src/testkit';
2+
import rule from '../src/rules/validate-against-schema';
3+
4+
const TEST_SCHEMA = /* GraphQL */ `
5+
type Query {
6+
user(id: ID!): User!
7+
}
8+
9+
type User {
10+
id: ID!
11+
name: String!
12+
}
13+
`;
14+
15+
const WITH_SCHEMA = { parserOptions: { schema: TEST_SCHEMA } };
16+
const ruleTester = new GraphQLRuleTester();
17+
18+
ruleTester.runGraphQLTests('validate-against-schema', rule, {
19+
valid: [
20+
{ ...WITH_SCHEMA, code: `query { user(id: 1) { id } }` },
21+
{ ...WITH_SCHEMA, code: `query test($id: ID!) { user(id: $id) { id } }` },
22+
{ ...WITH_SCHEMA, code: `query named ($id: ID!) { user(id: $id) { id } }` },
23+
{
24+
...WITH_SCHEMA,
25+
options: [{ disableRules: ['KnownDirectivesRule'] }],
26+
code: `query named ($id: ID!) { user(id: $id) { id @client } }`,
27+
},
28+
{
29+
...WITH_SCHEMA,
30+
options: [{ overrideRules: ['NoUnusedVariablesRule'] }],
31+
code: `query named ($id: ID!) { user(id: $id) { id @client } }`,
32+
},
33+
],
34+
invalid: [
35+
{
36+
...WITH_SCHEMA,
37+
code: `query { user(id: 1) { notExists } }`,
38+
errors: ['Cannot query field "notExists" on type "User".'],
39+
},
40+
{
41+
...WITH_SCHEMA,
42+
options: [{ overrideRules: ['NoUnusedVariablesRule'] }],
43+
code: `query named ($id: ID!) { user(id: 2) { id @client } }`,
44+
errors: ['Variable "$id" is never used in operation "named".'],
45+
},
46+
{
47+
...WITH_SCHEMA,
48+
errors: ['Unknown directive "@client".'],
49+
code: `query named ($id: ID!) { user(id: $id) { id @client } }`,
50+
},
51+
{
52+
...WITH_SCHEMA,
53+
errors: ['Unknown directive "@client".'],
54+
options: [{ overrideRules: ['KnownDirectivesRule'] }],
55+
code: `query named ($id: ID!) { user(id: $id) { id @client } }`,
56+
},
57+
{
58+
...WITH_SCHEMA,
59+
code: `query test($id: ID!) { user(invalid: $id) { test } }`,
60+
errors: [
61+
'Unknown argument "invalid" on field "Query.user".',
62+
'Cannot query field "test" on type "User".',
63+
'Field "user" argument "id" of type "ID!" is required, but it was not provided.',
64+
],
65+
},
66+
],
67+
});

0 commit comments

Comments
 (0)