Mirror: TypeScript LSP plugin that finds GraphQL documents in your code and provides diagnostics, auto-complete and hover-information.

fix(persisted): nested persisted operation resolution (#330)

Changed files
+81 -30
.changeset
packages
+5
.changeset/fifty-mangos-doubt.md
···
+
---
+
'@0no-co/graphqlsp': patch
+
---
+
+
Fix nested fragment resolution during persisted traversal
+4 -1
packages/graphqlsp/src/diagnostics.ts
···
const generatedHash = generateHashForDocument(
info,
initializer.arguments[0],
-
foundFilename
+
foundFilename,
+
ts.isArrayLiteralExpression(initializer.arguments[1])
+
? initializer.arguments[1]
+
: undefined
);
if (!generatedHash) return null;
+72 -29
packages/graphqlsp/src/persisted.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: {
···
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
···
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<FragmentDefinitionNode> = [];
+
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<string>();
+
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;
+
const visited = new Set();
+
const traversedSpreads = [...spreads];
-
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 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;
+
}
+
+
visit(fragmentDefinition, {
+
FragmentSpread: node => {
+
if (!visited.has(node.name.value))
+
traversedSpreads.push(node.name.value);
+
},
+
});
-
resolvedText = `${resolvedText}\n\n${print(fragmentDefinition)}`;
-
});
+
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 = (