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

Configure Feed

Select the types of activity you want to include in your feed.

generate a working thing omg

+75 -79
+4 -1
example/src/fragment.generated.ts
··· 104 104 105 105 export type PokemonFieldsFragment = { __typename?: 'Pokemon', id: string, name: string }; 106 106 107 - 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>; 107 + export type MorePokemonFieldsFragment = { __typename?: 'Pokemon', id: string, name: string }; 108 + 109 + 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>; 110 + 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
··· 6 6 name 7 7 } 8 8 ` 9 + 10 + // TODO: how to type 11 + // export const PokemonFields = gql` 12 + // fragment pokemonFields on Pokemon { 13 + // id 14 + // name 15 + // } 16 + 17 + // fragment morePokemonFields on Pokemon { 18 + // id 19 + // name 20 + // } 21 + // ` as typeof import('./fragment.generated').PokemonsDocument
+7 -1
example/src/index.generated.ts
··· 109 109 110 110 export type PokemonFieldsFragment = { __typename?: 'Pokemon', id: string, name: string }; 111 111 112 + export type PokemonQueryVariables = Exact<{ [key: string]: never; }>; 113 + 114 + 115 + export type PokemonQuery = { __typename?: 'Query', pokemon?: { __typename?: 'Pokemon', id: string, name: string } | null }; 116 + 112 117 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>; 113 - 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>; 118 + 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>; 119 + 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
··· 1 1 import { gql } from '@urql/core' 2 2 import { PokemonFields } from './fragment' 3 3 4 - const query = gql` 4 + const Pokemons = gql` 5 5 query Pokemons { 6 6 pokemons { 7 7 id ··· 11 11 12 12 ${PokemonFields} 13 13 ` as typeof import('./index.generated').PokemonsDocument 14 + const Pokemon = gql` 15 + query Pokemon { 16 + pokemon(id: "1") { 17 + id 18 + name 19 + } 20 + } 21 + 22 + ${PokemonFields} 23 + `
+40 -76
src/index.ts
··· 1 1 import ts from "typescript/lib/tsserverlibrary"; 2 2 import { isNoSubstitutionTemplateLiteral, ScriptElementKind, isIdentifier, isTaggedTemplateExpression, isToken, isTemplateExpression} from "typescript"; 3 3 import { getHoverInformation, getAutocompleteSuggestions, getDiagnostics, Diagnostic } from 'graphql-language-service' 4 - import { GraphQLSchema } from 'graphql' 4 + import { GraphQLSchema, parse, Kind, FragmentDefinitionNode, OperationDefinitionNode } from 'graphql' 5 5 6 6 import { Cursor } from "./cursor"; 7 7 import { loadSchema } from "./getSchema"; ··· 66 66 try { 67 67 // TODO: we might only want to run this when there are no 68 68 // diagnostic issues. 69 + // TODO: we might need to issue warnings for docuemnts without an operationName 70 + // TODO: we will need to check for renamed operations that _do contain_ a type definition 69 71 const parts = source.fileName.split('/'); 70 72 const name = parts[parts.length - 1]; 71 73 const nameParts = name.split('.'); 72 74 nameParts[nameParts.length - 1] = 'generated.ts' 73 75 parts[parts.length - 1] = nameParts.join('.') 74 76 generateTypedDocumentNodes(schema, parts.join('/'), texts.join('\n')).then(() => { 75 - // TODO: should handle all nodes 76 - const node = nodes[0] 77 - const parentChildren = node.parent.getChildren(); 78 - if (parentChildren.find(x => x.kind === 200)) { 77 + nodes.forEach((node, i) => { 78 + const queryText = texts[i] || ''; 79 + const parsed = parse(queryText); 80 + const isFragment = parsed.definitions.every(x => x.kind === Kind.FRAGMENT_DEFINITION); 81 + let name = ''; 82 + if (isFragment) { 83 + const fragmentNode = parsed.definitions[0] as FragmentDefinitionNode; 84 + name = fragmentNode.name.value; 85 + } else { 86 + const operationNode = parsed.definitions[0] as OperationDefinitionNode; 87 + name = operationNode.name!.value; 88 + } 89 + 90 + name = name.charAt(0).toUpperCase() + name.slice(1); 91 + const parentChildren = node.parent.getChildren(); 92 + if (parentChildren.find(x => x.kind === 200)) { 79 93 return; 80 - } 81 - // TODO: we need to get the operationName to find the thing we want to import 82 - // so we need to iterate over all nodes --> get operation-name --> suffix 83 - // "Document" behind it and add that to the template-literal. 84 - const imp = ` as typeof import('./${nameParts.join('.').replace('.ts', '')}').PokemonsDocument`; 85 - 86 - const span = { length: 1, start: node.end }; 87 - const prefix = source.text.substring(0, span.start); 88 - const suffix = source.text.substring(span.start + span.length, source.text.length); 89 - const text = prefix + imp + suffix; 90 - 91 - const scriptInfo = info.project.projectService.getScriptInfo(filename); 92 - const snapshot = scriptInfo!.getSnapshot(); 93 - const length = snapshot.getLength(); 94 - 95 - // scriptInfo!.editContent(0, length, text); 96 - // info.languageServiceHost.writeFile!(source.fileName, text); 97 - // scriptInfo!.registerFileUpdate(); 98 - 99 - /* 100 - info.session!.send({ 101 - seq: 0, 102 - type: 'request', 103 - command: 'window/showMessage', 104 - arguments: { 105 - type: 1, 106 - message: 'GraphQL Type Annotation', 107 - }, 108 - } as any); 109 - */ 110 - 111 - info.session!.event({ 112 - file: filename, 113 - diagnostics: [ 114 - { 115 - start: { line: 0, offset: 0 }, 116 - end: { line: 5, offset: 0 }, 117 - text: 'Shit is fucked', 118 - category: 'warning', 119 - }, 120 - ], 121 - }, 'suggestionDiag'); 122 - 123 - /* 124 - info.session!.send({ 125 - seq: 0, 126 - type: 'response', 127 - command: 'workspace/applyEdit', 128 - arguments: { 129 - label: 'GraphQL Type Annotation', 130 - edit: { 131 - documentChanges: [{ 132 - range: { 133 - start: { 134 - line: 1, 135 - character: 1, 136 - }, 137 - end: { 138 - line: 1, 139 - character: 2, 140 - }, 141 - }, 142 - newText: 'x', 143 - }], 144 - }, 145 - }, 146 - } as any); 147 - */ 148 - 149 - // source.update(text, { span, newLength: imp.length }) 150 - // info.languageServiceHost.writeFile!(source.fileName, text); 94 + } 95 + 96 + // TODO: we'll have to combine writing multiple exports when we are dealing with more than 97 + // one tagged template in a file 98 + const exportName = isFragment ? `${name}FragmentDoc` : `${name}Document`; 99 + const imp = ` as typeof import('./${nameParts.join('.').replace('.ts', '')}').${exportName}`; 100 + 101 + const span = { length: 1, start: node.end }; 102 + const prefix = source.text.substring(0, span.start); 103 + const suffix = source.text.substring(span.start + span.length, source.text.length); 104 + const text = prefix + imp + suffix; 105 + 106 + const scriptInfo = info.project.projectService.getScriptInfo(filename); 107 + const snapshot = scriptInfo!.getSnapshot(); 108 + const length = snapshot.getLength(); 109 + 110 + source.update(text, { span, newLength: imp.length }) 111 + scriptInfo!.editContent(0, length, text); 112 + info.languageServiceHost.writeFile!(source.fileName, text); 113 + scriptInfo!.registerFileUpdate(); 114 + }) 151 115 }); 152 116 } catch (e) { 153 117 console.error(e)