···104104105105export type PokemonFieldsFragment = { __typename?: 'Pokemon', id: string, name: string };
106106107107-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>;107107+export type MorePokemonFieldsFragment = { __typename?: 'Pokemon', id: string, name: string };
108108+109109+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>;
110110+export const MorePokemonFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"morePokemonFields"},"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<MorePokemonFieldsFragment, unknown>;
+13
example/src/fragment.ts
···66 name
77 }
88`
99+1010+// TODO: how to type
1111+// export const PokemonFields = gql`
1212+// fragment pokemonFields on Pokemon {
1313+// id
1414+// name
1515+// }
1616+1717+// fragment morePokemonFields on Pokemon {
1818+// id
1919+// name
2020+// }
2121+// ` as typeof import('./fragment.generated').PokemonsDocument
+7-1
example/src/index.generated.ts
···109109110110export type PokemonFieldsFragment = { __typename?: 'Pokemon', id: string, name: string };
111111112112+export type PokemonQueryVariables = Exact<{ [key: string]: never; }>;
113113+114114+115115+export type PokemonQuery = { __typename?: 'Query', pokemon?: { __typename?: 'Pokemon', id: string, name: string } | null };
116116+112117export 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>;
113113-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>;118118+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>;
119119+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>;
+11-1
example/src/index.ts
···11import { gql } from '@urql/core'
22import { PokemonFields } from './fragment'
3344-const query = gql`
44+const Pokemons = gql`
55 query Pokemons {
66 pokemons {
77 id
···11111212 ${PokemonFields}
1313` as typeof import('./index.generated').PokemonsDocument
1414+const Pokemon = gql`
1515+ query Pokemon {
1616+ pokemon(id: "1") {
1717+ id
1818+ name
1919+ }
2020+ }
2121+2222+ ${PokemonFields}
2323+`
+40-76
src/index.ts
···11import ts from "typescript/lib/tsserverlibrary";
22import { isNoSubstitutionTemplateLiteral, ScriptElementKind, isIdentifier, isTaggedTemplateExpression, isToken, isTemplateExpression} from "typescript";
33import { getHoverInformation, getAutocompleteSuggestions, getDiagnostics, Diagnostic } from 'graphql-language-service'
44-import { GraphQLSchema } from 'graphql'
44+import { GraphQLSchema, parse, Kind, FragmentDefinitionNode, OperationDefinitionNode } from 'graphql'
5566import { Cursor } from "./cursor";
77import { loadSchema } from "./getSchema";
···6666 try {
6767 // TODO: we might only want to run this when there are no
6868 // diagnostic issues.
6969+ // TODO: we might need to issue warnings for docuemnts without an operationName
7070+ // TODO: we will need to check for renamed operations that _do contain_ a type definition
6971 const parts = source.fileName.split('/');
7072 const name = parts[parts.length - 1];
7173 const nameParts = name.split('.');
7274 nameParts[nameParts.length - 1] = 'generated.ts'
7375 parts[parts.length - 1] = nameParts.join('.')
7476 generateTypedDocumentNodes(schema, parts.join('/'), texts.join('\n')).then(() => {
7575- // TODO: should handle all nodes
7676- const node = nodes[0]
7777- const parentChildren = node.parent.getChildren();
7878- if (parentChildren.find(x => x.kind === 200)) {
7777+ nodes.forEach((node, i) => {
7878+ const queryText = texts[i] || '';
7979+ const parsed = parse(queryText);
8080+ const isFragment = parsed.definitions.every(x => x.kind === Kind.FRAGMENT_DEFINITION);
8181+ let name = '';
8282+ if (isFragment) {
8383+ const fragmentNode = parsed.definitions[0] as FragmentDefinitionNode;
8484+ name = fragmentNode.name.value;
8585+ } else {
8686+ const operationNode = parsed.definitions[0] as OperationDefinitionNode;
8787+ name = operationNode.name!.value;
8888+ }
8989+9090+ name = name.charAt(0).toUpperCase() + name.slice(1);
9191+ const parentChildren = node.parent.getChildren();
9292+ if (parentChildren.find(x => x.kind === 200)) {
7993 return;
8080- }
8181- // TODO: we need to get the operationName to find the thing we want to import
8282- // so we need to iterate over all nodes --> get operation-name --> suffix
8383- // "Document" behind it and add that to the template-literal.
8484- const imp = ` as typeof import('./${nameParts.join('.').replace('.ts', '')}').PokemonsDocument`;
8585-8686- const span = { length: 1, start: node.end };
8787- const prefix = source.text.substring(0, span.start);
8888- const suffix = source.text.substring(span.start + span.length, source.text.length);
8989- const text = prefix + imp + suffix;
9090-9191- const scriptInfo = info.project.projectService.getScriptInfo(filename);
9292- const snapshot = scriptInfo!.getSnapshot();
9393- const length = snapshot.getLength();
9494-9595- // scriptInfo!.editContent(0, length, text);
9696- // info.languageServiceHost.writeFile!(source.fileName, text);
9797- // scriptInfo!.registerFileUpdate();
9898-9999- /*
100100- info.session!.send({
101101- seq: 0,
102102- type: 'request',
103103- command: 'window/showMessage',
104104- arguments: {
105105- type: 1,
106106- message: 'GraphQL Type Annotation',
107107- },
108108- } as any);
109109- */
110110-111111- info.session!.event({
112112- file: filename,
113113- diagnostics: [
114114- {
115115- start: { line: 0, offset: 0 },
116116- end: { line: 5, offset: 0 },
117117- text: 'Shit is fucked',
118118- category: 'warning',
119119- },
120120- ],
121121- }, 'suggestionDiag');
122122-123123- /*
124124- info.session!.send({
125125- seq: 0,
126126- type: 'response',
127127- command: 'workspace/applyEdit',
128128- arguments: {
129129- label: 'GraphQL Type Annotation',
130130- edit: {
131131- documentChanges: [{
132132- range: {
133133- start: {
134134- line: 1,
135135- character: 1,
136136- },
137137- end: {
138138- line: 1,
139139- character: 2,
140140- },
141141- },
142142- newText: 'x',
143143- }],
144144- },
145145- },
146146- } as any);
147147- */
148148-149149- // source.update(text, { span, newLength: imp.length })
150150- // info.languageServiceHost.writeFile!(source.fileName, text);
9494+ }
9595+9696+ // TODO: we'll have to combine writing multiple exports when we are dealing with more than
9797+ // one tagged template in a file
9898+ const exportName = isFragment ? `${name}FragmentDoc` : `${name}Document`;
9999+ const imp = ` as typeof import('./${nameParts.join('.').replace('.ts', '')}').${exportName}`;
100100+101101+ const span = { length: 1, start: node.end };
102102+ const prefix = source.text.substring(0, span.start);
103103+ const suffix = source.text.substring(span.start + span.length, source.text.length);
104104+ const text = prefix + imp + suffix;
105105+106106+ const scriptInfo = info.project.projectService.getScriptInfo(filename);
107107+ const snapshot = scriptInfo!.getSnapshot();
108108+ const length = snapshot.getLength();
109109+110110+ source.update(text, { span, newLength: imp.length })
111111+ scriptInfo!.editContent(0, length, text);
112112+ info.languageServiceHost.writeFile!(source.fileName, text);
113113+ scriptInfo!.registerFileUpdate();
114114+ })
151115 });
152116 } catch (e) {
153117 console.error(e)