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

support fragment suggestions

Changed files
+17 -12
example
src
+1 -1
example/src/index.generated.ts
···
export const PokemonFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"pokemonFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Pokemon"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]} as unknown as DocumentNode<PokemonFieldsFragment, unknown>;
export const PokemonsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Pokemons"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pokemons"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode<PokemonsQuery, PokemonsQueryVariables>;
-
export const PokemonDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Pokemon"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pokemon"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"StringValue","value":"1","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode<PokemonQuery, PokemonQueryVariables>;
+
export const PokemonDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Pokemon"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pokemon"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"StringValue","value":"1","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"pokemonFields"}}]}}]}},...PokemonFieldsFragmentDoc.definitions]} as unknown as DocumentNode<PokemonQuery, PokemonQueryVariables>;
+5 -1
example/src/index.ts
···
import { createClient, gql } from '@urql/core'
import { PokemonFields } from './fragment'
+
// testing stuffzzzzzzzz
+
const Pokemons = gql`
query Pokemons {
pokemons {
···
pokemon(id: "1") {
id
name
+
...pokemonFields
}
}
···
` as typeof import('./index.generated').PokemonDocument
const urqlClient = createClient({
-
url: 'http://localhost:3000/api'
+
url: '',
+
exchanges: []
});
urqlClient.query(Pokemons).toPromise().then(result => {
+11 -10
src/index.ts
···
info.project.projectService.logger.info(
"Setting up the GraphQL Plugin"
);
-
// TODO: our config most likely needs to support
-
// a scalars config as well
+
const tagTemplate = info.config.template || 'gql';
const proxy = createBasicDecorator(info);
···
const lines = text.split('\n')
let startingPosition = node.pos + (tagTemplate.length + 1)
-
// TODO: roll our own diagnostic here for operations without an operationName
-
// we can't generate typedDocumentNodes for those hence we will warn our user
return getDiagnostics(text, schema.current).map(x => {
const { start, end } = x.range;
···
nameParts[nameParts.length - 1] = 'generated.ts'
parts[parts.length - 1] = nameParts.join('.')
-
// TODO: we might only want to run this onSave/when file isn't dirty
-
// alternatively we could set up a watcher to react to saves
generateTypedDocumentNodes(schema.current, parts.join('/'), texts.join('\n')).then(() => {
nodes.forEach((node, i) => {
const queryText = texts[i] || '';
···
// This checks whether one of the children is an import-type
// which is a short-circuit if there is no as
const typeImport = parentChildren.find(x => isImportTypeNode(x)) as ImportTypeNode
-
if (typeImport && typeImport.getText() === exportName) return;
+
if (typeImport && typeImport.getText().includes(exportName)) return;
const span = { length: 1, start: node.end };
const text = source.text.substring(0, span.start) + imp + source.text.substring(span.start + span.length, source.text.length);
-
const scriptInfo = info.project.projectService.getScriptInfo(filename);
const snapshot = scriptInfo!.getSnapshot();
···
if (!foundToken || !schema.current) return originalCompletions
-
// TODO: this does not include fragmentSpread suggestions
const suggestions = getAutocompleteSuggestions(schema.current, text, new Cursor(foundToken.line, foundToken.start))
+
const parsed = parse(text);
+
const fragments = parsed.definitions.filter(x => x.kind === Kind.FRAGMENT_DEFINITION) as Array<FragmentDefinitionNode>
+
const result: ts.WithMetadata<ts.CompletionInfo> = {
isGlobalCompletion: false,
isMemberCompletion: false,
···
entries: [...suggestions.map(suggestion => ({
kind: ScriptElementKind.variableElement,
name: suggestion.label,
+
kindModifiers: 'declare',
+
sortText: suggestion.sortText || '0',
+
})), ...fragments.map(fragment => ({
+
kind: ScriptElementKind.variableElement,
+
name: fragment.name.value,
+
insertText: '...' + fragment.name.value,
kindModifiers: 'declare',
sortText: '0',
})), ...originalCompletions.entries],
···
node = node.parent
}
-
// TODO: visualize fragment-data
if (isTaggedTemplateExpression(node)) {
const { template, tag } = node;
if (!isIdentifier(tag) || tag.text !== tagTemplate) return originalInfo;