From 7768286000ddefac9d45fd51c89443646d648c2d Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Fri, 7 Jun 2024 13:41:43 +0200 Subject: [PATCH] fix(persisted): nested persisted operation resolution (#330) --- .changeset/fifty-mangos-doubt.md | 5 ++ packages/graphqlsp/src/diagnostics.ts | 5 +- packages/graphqlsp/src/persisted.ts | 101 ++++++++++++++++++-------- 3 files changed, 81 insertions(+), 30 deletions(-) create mode 100644 .changeset/fifty-mangos-doubt.md diff --git a/.changeset/fifty-mangos-doubt.md b/.changeset/fifty-mangos-doubt.md new file mode 100644 index 00000000..fdec1641 --- /dev/null +++ b/.changeset/fifty-mangos-doubt.md @@ -0,0 +1,5 @@ +--- +'@0no-co/graphqlsp': patch +--- + +Fix nested fragment resolution during persisted traversal diff --git a/packages/graphqlsp/src/diagnostics.ts b/packages/graphqlsp/src/diagnostics.ts index bf7433f4..74cd1257 100644 --- a/packages/graphqlsp/src/diagnostics.ts +++ b/packages/graphqlsp/src/diagnostics.ts @@ -260,7 +260,10 @@ export function getGraphQLDiagnostics( const generatedHash = generateHashForDocument( info, initializer.arguments[0], - foundFilename + foundFilename, + ts.isArrayLiteralExpression(initializer.arguments[1]) + ? initializer.arguments[1] + : undefined ); if (!generatedHash) return null; diff --git a/packages/graphqlsp/src/persisted.ts b/packages/graphqlsp/src/persisted.ts index cb2f3396..a036723c 100644 --- a/packages/graphqlsp/src/persisted.ts +++ b/packages/graphqlsp/src/persisted.ts @@ -3,9 +3,19 @@ import { ts } from './ts'; import { createHash } from 'crypto'; import * as checks from './ast/checks'; -import { findAllCallExpressions, findNode, getSource } from './ast'; +import { + findAllCallExpressions, + findNode, + getSource, + unrollTadaFragments, +} from './ast'; import { resolveTemplate } from './ast/resolve'; -import { parse, print, visit } from '@0no-co/graphql.web'; +import { + FragmentDefinitionNode, + parse, + print, + visit, +} from '@0no-co/graphql.web'; type PersistedAction = { span: { @@ -113,7 +123,10 @@ export function getPersistedCodeFixAtPosition( const hash = generateHashForDocument( info, initializer.arguments[0], - foundFilename + foundFilename, + ts.isArrayLiteralExpression(initializer.arguments[1]) + ? initializer.arguments[1] + : undefined ); const existingHash = callExpression.arguments[0]; // We assume for now that this is either undefined or an existing string literal @@ -156,38 +169,68 @@ export function getPersistedCodeFixAtPosition( export const generateHashForDocument = ( info: ts.server.PluginCreateInfo, templateLiteral: ts.StringLiteralLike | ts.TaggedTemplateExpression, - foundFilename: string + foundFilename: string, + referencedFragments: ts.ArrayLiteralExpression | undefined ): string | undefined => { - const externalSource = getSource(info, foundFilename)!; - const { fragments } = findAllCallExpressions(externalSource, info); + if (referencedFragments) { + const fragments: Array = []; + unrollTadaFragments(referencedFragments, fragments, info); + let text = resolveTemplate( + templateLiteral, + foundFilename, + info + ).combinedText; + fragments.forEach(fragmentDefinition => { + text = `${text}\n\n${print(fragmentDefinition)}`; + }); + return createHash('sha256').update(print(parse(text))).digest('hex'); + } else { + const externalSource = getSource(info, foundFilename)!; + const { fragments } = findAllCallExpressions(externalSource, info); - const text = resolveTemplate( - templateLiteral, - foundFilename, - info - ).combinedText; - const parsed = parse(text); - const spreads = new Set(); - visit(parsed, { - FragmentSpread: node => { - spreads.add(node.name.value); - }, - }); + const text = resolveTemplate( + templateLiteral, + foundFilename, + info + ).combinedText; + + const parsed = parse(text); + const spreads = new Set(); + visit(parsed, { + FragmentSpread: node => { + spreads.add(node.name.value); + }, + }); - let resolvedText = text; - [...spreads].forEach(spreadName => { - const fragmentDefinition = fragments.find(x => x.name.value === spreadName); - if (!fragmentDefinition) { - info.project.projectService.logger.info( - `[GraphQLSP] could not find fragment for spread ${spreadName}!` + let resolvedText = text; + const visited = new Set(); + const traversedSpreads = [...spreads]; + + let spreadName: string | undefined; + while ((spreadName = traversedSpreads.shift())) { + visited.add(spreadName); + const fragmentDefinition = fragments.find( + x => x.name.value === spreadName ); - return; - } + if (!fragmentDefinition) { + info.project.projectService.logger.info( + `[GraphQLSP] could not find fragment for spread ${spreadName}!` + ); + return; + } - resolvedText = `${resolvedText}\n\n${print(fragmentDefinition)}`; - }); + visit(fragmentDefinition, { + FragmentSpread: node => { + if (!visited.has(node.name.value)) + traversedSpreads.push(node.name.value); + }, + }); + + resolvedText = `${resolvedText}\n\n${print(fragmentDefinition)}`; + } - return createHash('sha256').update(resolvedText).digest('hex'); + return createHash('sha256').update(print(parse(resolvedText))).digest('hex'); + } }; export const getDocumentReferenceFromTypeQuery = (