Skip to content

Commit

Permalink
馃帀 New feature: Support #import for known-fragment-names rule (#441)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dimitri POSTOLOV committed Jun 21, 2021
1 parent 152ecc6 commit a44d426
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/dry-vans-smile.md
@@ -0,0 +1,5 @@
---
'@graphql-eslint/eslint-plugin': minor
---

support #import in known-fragment-names rule
50 changes: 49 additions & 1 deletion docs/rules/known-fragment-names.md
Expand Up @@ -7,4 +7,52 @@

A GraphQL document is only valid if all `...Fragment` fragment spreads refer to fragments defined in the same document.

> This rule is a wrapper around a `graphql-js` validation function. [You can find it's source code here](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/KnownFragmentNamesRule.ts).
> This rule is a wrapper around a `graphql-js` validation function. [You can find it's source code here](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/KnownFragmentNamesRule.ts).
## Usage Examples

### Incorrect (fragment not defined in the document)

```graphql
# eslint @graphql-eslint/known-fragment-names: ["error"]

query {
user {
id
...UserFields
}
}
```

### Correct

```graphql
# eslint @graphql-eslint/known-fragment-names: ["error"]

fragment UserFields on User {
firstName
lastName
}

query {
user {
id
...UserFields
}
}
```

### Correct (existing import to UserFields fragment)

```graphql
# eslint @graphql-eslint/known-fragment-names: ["error"]

#import '../UserFields.gql'

query {
user {
id
...UserFields
}
}
```
14 changes: 10 additions & 4 deletions packages/plugin/src/parser.ts
@@ -1,7 +1,8 @@
import { convertToESTree } from './estree-parser/converter';
import { parseGraphQLSDL } from '@graphql-tools/utils';
import { convertToESTree } from './estree-parser';
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
import { GraphQLError, TypeInfo } from 'graphql';
import { Linter } from 'eslint';
import fs from 'fs';
import { GraphQLESLintParseResult, ParserOptions } from './types';
import { extractTokens } from './utils';
import { getSchema } from './schema';
Expand All @@ -26,9 +27,14 @@ export function parseForESLint(code: string, options?: ParserOptions): GraphQLES
};

try {
const graphqlAst = parseGraphQLSDL(options.filePath || '', code, {
...(options.graphQLParserOptions || {}),
const filePath = options.filePath || '';
const isVirtualFile = !fs.existsSync(options.filePath);
const fileLoader = new GraphQLFileLoader();

const graphqlAst = fileLoader.handleFileContent(code, filePath, {
...options.graphQLParserOptions,
noLocation: false,
skipGraphQLImport: isVirtualFile,
});

const { rootTree, comments } = convertToESTree(graphqlAst.document, schema ? new TypeInfo(schema) : null);
Expand Down
42 changes: 42 additions & 0 deletions packages/plugin/src/rules/graphql-js-validation.ts
Expand Up @@ -128,6 +128,48 @@ export const GRAPHQL_JS_VALIDATIONS = Object.assign(
validationToRule('known-fragment-names', 'KnownFragmentNames', {
docs: {
description: `A GraphQL document is only valid if all \`...Fragment\` fragment spreads refer to fragments defined in the same document.`,
examples: [
{
title: 'Incorrect (fragment not defined in the document)',
code: /* GraphQL */ `
query {
user {
id
...UserFields
}
}
`,
},
{
title: 'Correct',
code: /* GraphQL */ `
fragment UserFields on User {
firstName
lastName
}
query {
user {
id
...UserFields
}
}
`,
},
{
title: 'Correct (existing import to UserFields fragment)',
code: /* GraphQL */ `
#import '../UserFields.gql'
query {
user {
id
...UserFields
}
}
`,
},
],
},
}),
validationToRule('known-type-names', 'KnownTypeNames', {
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin/src/sibling-operations.ts
Expand Up @@ -13,7 +13,7 @@ import {
} from 'graphql';
import { ParserOptions } from './types';
import { GraphQLConfig } from 'graphql-config';
import { dirname, join } from 'path';
import { dirname } from 'path';

export type FragmentSource = { filePath: string; document: FragmentDefinitionNode };
export type OperationSource = { filePath: string; document: OperationDefinitionNode };
Expand Down Expand Up @@ -77,7 +77,7 @@ export function getSiblingOperations(options: ParserOptions, gqlConfig: GraphQLC
}

if (!siblings && options?.operations) {
const loadPaths = Array.isArray(options.operations) ? options.operations : [options.operations] || [];
const loadPaths = Array.isArray(options.operations) ? options.operations : [options.operations];
const loadKey = loadPaths.join(',');

if (operationsCache.has(loadKey)) {
Expand Down
29 changes: 29 additions & 0 deletions packages/plugin/tests/known-fragment-names.spec.ts
@@ -0,0 +1,29 @@
import { join } from 'path';
import { GraphQLRuleTester } from '../src';
import { GRAPHQL_JS_VALIDATIONS } from '../src/rules/graphql-js-validation';

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

const ruleTester = new GraphQLRuleTester();

ruleTester.runGraphQLTests('known-fragment-names', GRAPHQL_JS_VALIDATIONS['known-fragment-names'], {
valid: [
{
filename: join(__dirname, 'mocks/user.graphql'),
code: ruleTester.fromMockFile('user.graphql'),
parserOptions: {
schema: TEST_SCHEMA,
},
},
],
invalid: [],
});
4 changes: 4 additions & 0 deletions packages/plugin/tests/mocks/user-fields.graphql
@@ -0,0 +1,4 @@
fragment UserFields on User {
id
firstName
}
7 changes: 7 additions & 0 deletions packages/plugin/tests/mocks/user.graphql
@@ -0,0 +1,7 @@
#import "./user-fields.graphql"

query User {
user {
...UserFields
}
}

0 comments on commit a44d426

Please sign in to comment.