Repository: kamilkisiela/graphql-inspector
Pull request #58 (2019-01-17 10:38:26)

Omit unused fragments from a document


Patch:
diff --git a/packages/core/src/validate/index.ts b/packages/core/src/validate/index.ts
index 728591b8..a35ef6f4 100644
--- a/packages/core/src/validate/index.ts
+++ b/packages/core/src/validate/index.ts
@@ -6,6 +6,7 @@ import {
   parse,
   validate as validateDocument,
   FragmentDefinitionNode,
+  DocumentNode,
 } from 'graphql';
 import {DepGraph} from 'dependency-graph';
 
@@ -52,38 +53,34 @@ export function validate(
     // since we include fragments, validate only operations
     .filter(doc => doc.hasOperations)
     .forEach(doc => {
-      const merged = `
-        ${doc.source.body}
+      const docWithOperations: DocumentNode = {
+        kind: 'Document',
+        definitions: doc.operations.map(d => d.node),
+      };
+      const extractedFragments = (
+        extractFragments(print(docWithOperations)) || []
+      )
+        // resolve all nested fragments
+        .map(fragmentName =>
+          resolveFragment(graph.getNodeData(fragmentName), graph),
+        )
+        // flatten arrays
+        .reduce((list, current) => list.concat(current), [])
+        // remove duplicates
+        .filter(
+          (def, i, all) =>
+            all.findIndex(item => item.name.value === def.name.value) === i,
+        );
+      const merged: DocumentNode = {
+        kind: 'Document',
+        definitions: [...docWithOperations.definitions, ...extractedFragments],
+      };
 
-        ${(extractFragments(doc.source.body) || [])
-          // resolve all nested fragments
-          .map(fragmentName =>
-            resolveFragment(graph.getNodeData(fragmentName), graph),
-          )
-          // flatten arrays
-          .reduce((list, current) => list.concat(current), [])
-          // remove duplicates
-          .filter(
-            (def, i, all) =>
-              all.findIndex(item => item.name.value === def.name.value) === i,
-          )
-          // does not include fragment definition
-          .filter(
-            fragment =>
-              doc.source.body.indexOf(`fragment ${fragment.name.value} on`) ===
-              -1,
-          )
-          // print
-          .map(print)
-          // merge
-          .join('\n\n')}
-      `;
-
-      const errors = validateDocument(schema, parse(merged)) as GraphQLError[];
+      const errors = validateDocument(schema, merged) as GraphQLError[];
       const deprecated = findDeprecatedUsages(schema, parse(doc.source.body));
       const duplicatedFragments = findDuplicatedFragments(fragmentNames);
 
-      if (errors || deprecated || duplicatedFragments) {
+      if (sumLengths(errors, duplicatedFragments, deprecated) > 0) {
         invalidDocuments.push({
           source: doc.source,
           errors: [...errors, ...duplicatedFragments],
@@ -125,3 +122,7 @@ function extractFragments(document: string): string[] | undefined {
     name.replace('...', ''),
   );
 }
+
+function sumLengths(...arrays: any[][]): number {
+  return arrays.reduce((sum, {length}) => sum + length, 0);
+}


Test patch:
diff --git a/packages/core/__tests__/validate.ts b/packages/core/__tests__/validate.ts
index a6a86ce3..53d37bed 100644
--- a/packages/core/__tests__/validate.ts
+++ b/packages/core/__tests__/validate.ts
@@ -91,9 +91,7 @@ test('multiple fragments across multiple files with nested fragments (#36)', asy
 
   const results = validate(schema, docs.map(doc => new Source(print(doc))));
 
-  expect(results.length).toEqual(1);
-  expect(results[0].errors.length).toEqual(0);
-  expect(results[0].deprecated.length).toEqual(0);
+  expect(results.length).toEqual(0);
 });
 
 test('fail on non unique fragment names', async () => {
@@ -145,3 +143,46 @@ test('fail on non unique fragment names', async () => {
 
   expect(error.message).toMatch(`Name of 'PostInfo' fragment is not unique`);
 });
+
+test('omit unused fragment from a document', async () => {
+  const schema = buildASTSchema(gql`
+    type Post {
+      id: ID
+      title: String
+      createdAt: Int
+    }
+
+    type Query {
+      post: Post
+    }
+
+    schema {
+      query: Query
+    }
+  `);
+
+  const docs = [
+    gql`
+      query getPost {
+        post {
+          ...PostInfo
+        }
+      }
+
+      fragment UnusedPost on Post {
+        id
+        createdAt
+      }
+    `,
+    gql`
+      fragment PostInfo on Post {
+        id
+        title
+      }
+    `,
+  ];
+
+  const results = validate(schema, docs.map(doc => new Source(print(doc))));
+
+  expect(results.length).toEqual(0);
+});