Skip to content

Commit

Permalink
fixes for rawNode and validation rule (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
dotansimha committed Oct 5, 2020
1 parent 6d1cfe9 commit 80e922c
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 21 deletions.
5 changes: 5 additions & 0 deletions .changeset/eighty-maps-juggle.md
@@ -0,0 +1,5 @@
---
'@graphql-eslint/eslint-plugin': minor
---

Improved `validate-against-schema` rule configuration (allow to customize rules)
5 changes: 5 additions & 0 deletions .changeset/serious-games-yawn.md
@@ -0,0 +1,5 @@
---
'@graphql-eslint/eslint-plugin': patch
---

Fix issues with `.rawNode()` values
40 changes: 40 additions & 0 deletions docs/rules/validate-against-schema.md
Expand Up @@ -7,6 +7,10 @@ This rule validates GraphQL operations against your GraphQL schema, and reflects

> Super useful with VSCode integration!
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)).

You can configure the rules by overriding it, or ignoring rules from the default set.

### Usage Example

Examples of **incorrect** code for this rule:
Expand Down Expand Up @@ -40,3 +44,39 @@ query something {
something # ok, field exists
}
```

## Configuration

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.

#### Overriding the entire list of rules

If you wish to override the entire list of rules, you can specify `overrideRules` key in your configuration:

```js
// This will run only UniqueDirectivesPerLocationRule rule
{
rules: {
'@graphql-eslint/validate-against-schema': ["error", {
overrideRules: ["UniqueDirectivesPerLocationRule"]
}]
}
}
```

> Just use the name of the rule, as it specified by the list of available rules in `graphql-js` library.
#### Disable specific rules

If you wish to use the default list of rules, and just disable some of them, you can use the following:

```js
// This will use the default list of rules, but will disable only KnownDirectivesRule
{
rules: {
'@graphql-eslint/validate-against-schema': ["error", {
disableRules: ["KnownDirectivesRule"]
}]
}
}
```
10 changes: 7 additions & 3 deletions packages/plugin/src/estree-parser/converter.ts
Expand Up @@ -32,7 +32,11 @@ function stripTokens(location: Location): Pick<Location, 'start' | 'end'> {
};
}

const convertNode = (typeInfo?: TypeInfo) => <T extends ASTNode>(node: T): GraphQLESTreeNode<T> => {
const convertNode = (typeInfo?: TypeInfo) => <T extends ASTNode>(
node: T,
key: string | number,
parent: any
): GraphQLESTreeNode<T> => {
const calculatedTypeInfo = typeInfo
? {
argument: typeInfo.getArgument(),
Expand Down Expand Up @@ -64,7 +68,7 @@ const convertNode = (typeInfo?: TypeInfo) => <T extends ASTNode>(node: T): Graph
...typeFieldSafe,
...commonFields,
type: node.kind,
rawNode: () => node,
rawNode: () => parent[key],
gqlLocation: stripTokens(gqlLocation),
} as any) as GraphQLESTreeNode<T>;

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

Expand Down
106 changes: 88 additions & 18 deletions packages/plugin/src/rules/validate-against-schema.ts
@@ -1,49 +1,119 @@
import { Kind, validate, GraphQLSchema, DocumentNode } from 'graphql';
import { Kind, validate, GraphQLSchema, DocumentNode, ASTNode, ValidationRule, specifiedRules } from 'graphql';
import { GraphQLESTreeNode } from '../estree-parser';
import { GraphQLESLintRule, GraphQLESlintRuleContext } from '../types';
import { requireGraphQLSchemaFromContext } from '../utils';

function validateDoc(context: GraphQLESlintRuleContext, schema: GraphQLSchema, documentNode: DocumentNode) {
function validateDoc(
sourceNode: GraphQLESTreeNode<ASTNode>,
context: GraphQLESlintRuleContext,
schema: GraphQLSchema,
documentNode: DocumentNode,
rules: ReadonlyArray<ValidationRule>
) {
if (documentNode && documentNode.definitions && documentNode.definitions.length > 0) {
const validationErrors = validate(schema, documentNode);
try {
const validationErrors = validate(schema, documentNode, rules);

for (const error of validationErrors) {
const node = (error.nodes[0] as any) as GraphQLESTreeNode<typeof error.nodes[0]>;
for (const error of validationErrors) {
const node = (error.nodes[0] as any) as GraphQLESTreeNode<ASTNode>;

context.report({
loc: node.loc,
message: error.message,
});
}
} catch (e) {
context.report({
loc: node.loc,
message: error.message,
node: sourceNode,
message: e.message,
});
}
}
}

const rule: GraphQLESLintRule = {
export type ValidateAgainstSchemaRuleConfig = [
{
overrideRules?: string[];
disableRules?: string[];
}
];

const rule: GraphQLESLintRule<ValidateAgainstSchemaRuleConfig> = {
meta: {
docs: {
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/validate-against-schema.md`,
recommended: true,
description: `This rule validates GraphQL operations against your GraphQL schema, and reflects the error as lint errors.`,
},
schema: {
type: 'array',
minItems: 0,
maxItems: 1,
items: {
allOf: [
{
type: 'object',
properties: {
overrideRules: {
type: 'array',
items: {
type: 'string',
},
},
},
},
{
type: 'object',
properties: {
disableRules: {
type: 'array',
items: {
type: 'string',
},
},
},
},
],
},
},
type: 'problem',
},
create(context) {
const config = context.options[0] || {};
let rulesArr = specifiedRules;

if (config.disableRules && config.disableRules.length > 0) {
rulesArr = specifiedRules.filter(r => !config.disableRules.includes(r.name));
} else if (config.overrideRules && config.overrideRules.length > 0) {
rulesArr = specifiedRules.filter(r => config.overrideRules.includes(r.name));
}

return {
OperationDefinition(node) {
const schema = requireGraphQLSchemaFromContext(context);

validateDoc(context, schema, {
kind: Kind.DOCUMENT,
definitions: [node.rawNode()],
});
validateDoc(
node,
context,
schema,
{
kind: Kind.DOCUMENT,
definitions: [node.rawNode()],
},
rulesArr
);
},
FragmentDefinition(node) {
const schema = requireGraphQLSchemaFromContext(context);

validateDoc(context, schema, {
kind: Kind.DOCUMENT,
definitions: [node.rawNode()],
});
validateDoc(
node,
context,
schema,
{
kind: Kind.DOCUMENT,
definitions: [node.rawNode()],
},
rulesArr
);
},
};
},
Expand Down
67 changes: 67 additions & 0 deletions packages/plugin/tests/validate-against-schema.spec.ts
@@ -0,0 +1,67 @@
import { GraphQLRuleTester } from '../src/testkit';
import rule from '../src/rules/validate-against-schema';

const TEST_SCHEMA = /* GraphQL */ `
type Query {
user(id: ID!): User!
}
type User {
id: ID!
name: String!
}
`;

const WITH_SCHEMA = { parserOptions: { schema: TEST_SCHEMA } };
const ruleTester = new GraphQLRuleTester();

ruleTester.runGraphQLTests('validate-against-schema', rule, {
valid: [
{ ...WITH_SCHEMA, code: `query { user(id: 1) { id } }` },
{ ...WITH_SCHEMA, code: `query test($id: ID!) { user(id: $id) { id } }` },
{ ...WITH_SCHEMA, code: `query named ($id: ID!) { user(id: $id) { id } }` },
{
...WITH_SCHEMA,
options: [{ disableRules: ['KnownDirectivesRule'] }],
code: `query named ($id: ID!) { user(id: $id) { id @client } }`,
},
{
...WITH_SCHEMA,
options: [{ overrideRules: ['NoUnusedVariablesRule'] }],
code: `query named ($id: ID!) { user(id: $id) { id @client } }`,
},
],
invalid: [
{
...WITH_SCHEMA,
code: `query { user(id: 1) { notExists } }`,
errors: ['Cannot query field "notExists" on type "User".'],
},
{
...WITH_SCHEMA,
options: [{ overrideRules: ['NoUnusedVariablesRule'] }],
code: `query named ($id: ID!) { user(id: 2) { id @client } }`,
errors: ['Variable "$id" is never used in operation "named".'],
},
{
...WITH_SCHEMA,
errors: ['Unknown directive "@client".'],
code: `query named ($id: ID!) { user(id: $id) { id @client } }`,
},
{
...WITH_SCHEMA,
errors: ['Unknown directive "@client".'],
options: [{ overrideRules: ['KnownDirectivesRule'] }],
code: `query named ($id: ID!) { user(id: $id) { id @client } }`,
},
{
...WITH_SCHEMA,
code: `query test($id: ID!) { user(invalid: $id) { test } }`,
errors: [
'Unknown argument "invalid" on field "Query.user".',
'Cannot query field "test" on type "User".',
'Field "user" argument "id" of type "ID!" is required, but it was not provided.',
],
},
],
});

0 comments on commit 80e922c

Please sign in to comment.