Skip to content

Commit fdebfe1

Browse files
committed
feat: add useFunctionTypeArguments settings, document and test tagName setting
1 parent 7084a54 commit fdebfe1

12 files changed

+469
-81
lines changed

README.md

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ JSCodeshift transform that inserts Flow types generated from GraphQL documents i
2020
- [`graphql-codegen` outputs messy types for documents](#graphql-codegen-outputs-messy-types-for-documents)
2121
- [I want to extract parts of the query with their own type aliases](#i-want-to-extract-parts-of-the-query-with-their-own-type-aliases)
2222
- [Interpolation in GraphQL tagged template literals](#interpolation-in-graphql-tagged-template-literals)
23-
- [Automatically adding type annotations to `useQuery`, `useMutation`, and `useSubscription` hooks](#automatically-adding-type-annotations-to-usequery-usemutation-and-usesubscription-hooks)
23+
- [Automatically adding type annotations to `Query`, `Mutation`, `useQuery`, `useMutation`, and `useSubscription`](#automatically-adding-type-annotations-to-query-mutation-usequery-usemutation-and-usesubscription)
2424
- [Configuration](#configuration)
2525
- [`schemaFile` / `server`](#schemafile--server)
26+
- [`tagName` (default: `gql`)](#tagname-default-gql)
2627
- [`addTypename` (default: `true`)](#addtypename-default-true)
2728
- [`objectType` (default: `ambiguous`)](#objecttype-default-ambiguous)
2829
- [`useReadOnlyTypes` (default: `false`)](#usereadonlytypes-default-false)
30+
- [`useFunctionTypeArguments` (default: `true`)](#usefunctiontypearguments-default-true)
2931
- [`external as`](#external-as-)
3032
- [`extract [as ]`](#extract-as-)
3133
- [CLI Usage](#cli-usage)
@@ -376,7 +378,7 @@ type UpdateUserMutationVariables = {
376378
}
377379
```
378380
379-
## Automatically adding type annotations to `useQuery`, `useMutation`, and `useSubscription` hooks
381+
## Automatically adding type annotations to `Query`, `Mutation`, `useQuery`, `useMutation`, and `useSubscription`
380382
381383
`graphql-typegen` will analyze all calls to these hooks and add the correct type annotations:
382384
@@ -454,6 +456,18 @@ Or
454456
}
455457
```
456458
459+
## `tagName` (default: `gql`)
460+
461+
Name of the template literal tag used to identify template literals containing GraphQL queries in Javascript/Typescript code
462+
463+
Configure this in your `package.json`:
464+
465+
```
466+
"graphql-typegen": {
467+
"tagName": "gql"
468+
}
469+
```
470+
457471
## `addTypename` (default: `true`)
458472
459473
Places this may be configured, in order of increasing precendence:
@@ -576,6 +590,43 @@ const query = gql`
576590
`
577591
```
578592
593+
## `useFunctionTypeArguments` (default: `true`)
594+
595+
Whether to annotate `useQuery`, `useMutation` and `useSubscription` calls with type arguments,
596+
or annotate the input variables and output data.
597+
598+
Configure this in your `package.json`:
599+
600+
```
601+
"graphql-typegen": {
602+
"useFunctionTypeArguments": false
603+
}
604+
```
605+
606+
### When `true` (default)
607+
608+
Adds `<QueryData, QueryVariables>` to `useQuery`:
609+
610+
```js
611+
const {loading, error, data} = useQuery<QueryData, QueryVariables>(query, {
612+
variables: {id}
613+
})
614+
```
615+
616+
### When `false`:
617+
618+
Annotates this way:
619+
620+
```js
621+
const {
622+
loading,
623+
error,
624+
data,
625+
}: QueryRenderProps<QueryData, QueryVariables> = useQuery(query, {
626+
variables: ({ id }: QueryVariables),
627+
})
628+
```
629+
579630
## `external as <type annotation or import statement>`
580631
581632
Makes `graphql-typegen` use the given external type for a scalar.

src/internal/config.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ export type Config = {
2626
* Which Flow object type to output
2727
*/
2828
objectType?: ObjectType
29+
/**
30+
* Whether to use function type arguments
31+
*
32+
* if true:
33+
*
34+
* const data = useQuery<QueryData, QueryVariables>(query, {variables: {id}})
35+
*
36+
* if false:
37+
*
38+
* const data: QueryRenderProps<QueryData, QueryVariables> = useQuery(query, {variables: {id}})
39+
*/
40+
useFunctionTypeArguments?: boolean
2941
}
3042

3143
export type DefaultedConfig = {
@@ -54,6 +66,18 @@ export type DefaultedConfig = {
5466
* Which Flow object type to output
5567
*/
5668
objectType: ObjectType
69+
/**
70+
* Whether to use function type arguments
71+
*
72+
* if true:
73+
*
74+
* const data = useQuery<QueryData, QueryVariables>(query, {variables: {id}})
75+
*
76+
* if false:
77+
*
78+
* const data: QueryRenderProps<QueryData, QueryVariables> = useQuery(query, {variables: {id}})
79+
*/
80+
useFunctionTypeArguments?: boolean
5781
}
5882

5983
export function applyConfigDefaults(config: Config): DefaultedConfig {
@@ -62,13 +86,23 @@ export function applyConfigDefaults(config: Config): DefaultedConfig {
6286
const addTypename = config.addTypename ?? true
6387
const useReadOnlyTypes = config.useReadOnlyTypes ?? false
6488
const objectType = config.objectType || 'ambiguous'
89+
const useFunctionTypeArguments = config.useFunctionTypeArguments ?? true
6590

66-
return {
91+
const result = {
6792
schemaFile,
6893
server,
6994
tagName,
7095
addTypename,
7196
useReadOnlyTypes,
7297
objectType,
98+
useFunctionTypeArguments,
7399
}
100+
101+
for (const key in config) {
102+
if (config.hasOwnProperty(key) && !result.hasOwnProperty(key)) {
103+
throw new Error(`invalid config option: ${key}`)
104+
}
105+
}
106+
107+
return result
74108
}

src/internal/graphqlTypegenCore.ts

Lines changed: 105 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import j, {
1212
FileInfo,
1313
API,
1414
Options,
15+
TypeParameterInstantiation,
1516
} from 'jscodeshift'
1617
import findImports from 'jscodeshift-find-imports'
1718
import addImports from 'jscodeshift-add-imports'
@@ -40,6 +41,17 @@ function typeCast(
4041
return j.typeCastExpression(node, typeAnnotation)
4142
}
4243

44+
function makeFunctionTypeArguments(
45+
data: TypeAlias,
46+
variables?: TypeAlias | null | undefined
47+
): TypeParameterInstantiation {
48+
const params = [j.genericTypeAnnotation(j.identifier(data.id.name), null)]
49+
if (variables) {
50+
params.push(j.genericTypeAnnotation(j.identifier(variables.id.name), null))
51+
}
52+
return j.typeParameterInstantiation(params)
53+
}
54+
4355
export default function graphqlTypegenCore(
4456
{ path: file, source: code }: FileInfo,
4557
{ j }: API,
@@ -57,7 +69,7 @@ export default function graphqlTypegenCore(
5769
)
5870
}
5971
const config = applyConfigDefaults(Object.assign(packageConf, options))
60-
const { tagName = 'gql' } = config
72+
const { tagName = 'gql', useFunctionTypeArguments } = config
6173

6274
const root = j(code)
6375
const { statement } = j.template
@@ -437,39 +449,48 @@ export default function graphqlTypegenCore(
437449
})
438450
.forEach((path: ASTPath<VariableDeclarator>): void => {
439451
const { data, variables } = onlyValue(generatedTypes.query) || {}
440-
if (!data) return
441-
if (
442-
path.node.id.type === 'Identifier' ||
443-
path.node.id.type === 'ObjectPattern'
444-
) {
445-
path.node.id.typeAnnotation = queryRenderPropsAnnotation(
452+
if (!data || path.node.init?.type !== 'CallExpression') return
453+
if (useFunctionTypeArguments) {
454+
;(path.node.init as any).typeArguments = makeFunctionTypeArguments(
446455
data,
447456
variables
448457
)
449-
}
450-
if (path.node.init?.type !== 'CallExpression') return
451-
const options = path.node.init.arguments[1]
452-
if (variables && options && options.type === 'ObjectExpression') {
453-
const variablesProp = options.properties.find(
454-
p =>
455-
p.type !== 'SpreadProperty' &&
456-
p.type !== 'SpreadElement' &&
457-
p.key.type === 'Identifier' &&
458-
p.key.name === 'variables'
459-
)
458+
} else {
460459
if (
461-
variablesProp &&
462-
variablesProp.type !== 'ObjectMethod' &&
463-
variablesProp.type !== 'SpreadElement' &&
464-
variablesProp.type !== 'SpreadProperty'
460+
path.node.id.type === 'Identifier' ||
461+
path.node.id.type === 'ObjectPattern'
465462
) {
466-
variablesProp.value = typeCast(
467-
variablesProp.value as ExpressionKind,
468-
j.typeAnnotation(
469-
j.genericTypeAnnotation(j.identifier(variables.id.name), null)
470-
)
463+
path.node.id.typeAnnotation = queryRenderPropsAnnotation(
464+
data,
465+
variables
471466
)
472467
}
468+
const options = path.node.init.arguments[1]
469+
if (variables && options && options.type === 'ObjectExpression') {
470+
const variablesProp = options.properties.find(
471+
p =>
472+
p.type !== 'SpreadProperty' &&
473+
p.type !== 'SpreadElement' &&
474+
p.key.type === 'Identifier' &&
475+
p.key.name === 'variables'
476+
)
477+
if (
478+
variablesProp &&
479+
variablesProp.type !== 'ObjectMethod' &&
480+
variablesProp.type !== 'SpreadElement' &&
481+
variablesProp.type !== 'SpreadProperty'
482+
) {
483+
variablesProp.value = typeCast(
484+
variablesProp.value as ExpressionKind,
485+
j.typeAnnotation(
486+
j.genericTypeAnnotation(
487+
j.identifier(variables.id.name),
488+
null
489+
)
490+
)
491+
)
492+
}
493+
}
473494
}
474495
})
475496
}
@@ -490,25 +511,32 @@ export default function graphqlTypegenCore(
490511
},
491512
})
492513
.forEach((path: ASTPath<VariableDeclarator>): void => {
493-
const { data, mutationFunction } =
514+
const { data, variables, mutationFunction } =
494515
onlyValue(generatedTypes.mutation) || {}
495-
if (!mutationFunction) return
516+
if (!mutationFunction || !data) return
496517
const {
497518
node: { id },
498519
} = path
499-
if (id.type !== 'ArrayPattern' && id.type !== 'Identifier') return
500-
const tupleTypes: FlowTypeKind[] = [
501-
j.genericTypeAnnotation(
502-
j.identifier(mutationFunction.id.name),
503-
null
504-
),
505-
]
506-
if (data && id.type === 'ArrayPattern' && id.elements.length > 1)
507-
tupleTypes.push(mutationResultAnnotation(data).typeAnnotation)
508-
// https://github.com/benjamn/ast-types/issues/372
509-
;(id as any).typeAnnotation = j.typeAnnotation(
510-
j.tupleTypeAnnotation(tupleTypes)
511-
)
520+
if (useFunctionTypeArguments) {
521+
;(path.node.init as any).typeArguments = makeFunctionTypeArguments(
522+
data,
523+
variables
524+
)
525+
} else {
526+
if (id.type !== 'ArrayPattern' && id.type !== 'Identifier') return
527+
const tupleTypes: FlowTypeKind[] = [
528+
j.genericTypeAnnotation(
529+
j.identifier(mutationFunction.id.name),
530+
null
531+
),
532+
]
533+
if (data && id.type === 'ArrayPattern' && id.elements.length > 1)
534+
tupleTypes.push(mutationResultAnnotation(data).typeAnnotation)
535+
// https://github.com/benjamn/ast-types/issues/372
536+
;(id as any).typeAnnotation = j.typeAnnotation(
537+
j.tupleTypeAnnotation(tupleTypes)
538+
)
539+
}
512540
})
513541
}
514542

@@ -531,38 +559,48 @@ export default function graphqlTypegenCore(
531559
const { data, variables } =
532560
onlyValue(generatedTypes.subscription) || {}
533561
if (!data) return
534-
if (
535-
path.node.id.type === 'Identifier' ||
536-
path.node.id.type === 'ObjectPattern'
537-
) {
538-
path.node.id.typeAnnotation = subscriptionResultAnnotation(
562+
if (useFunctionTypeArguments) {
563+
;(path.node.init as any).typeArguments = makeFunctionTypeArguments(
539564
data,
540565
variables
541566
)
542-
}
543-
if (path.node.init?.type !== 'CallExpression') return
544-
const options = path.node.init.arguments[1]
545-
if (variables && options && options.type === 'ObjectExpression') {
546-
const variablesProp = options.properties.find(
547-
p =>
548-
p.type !== 'SpreadElement' &&
549-
p.type !== 'SpreadProperty' &&
550-
p.key.type === 'Identifier' &&
551-
p.key.name === 'variables'
552-
)
567+
} else {
553568
if (
554-
variablesProp &&
555-
variablesProp.type !== 'SpreadElement' &&
556-
variablesProp.type !== 'SpreadProperty' &&
557-
variablesProp.type !== 'ObjectMethod'
569+
path.node.id.type === 'Identifier' ||
570+
path.node.id.type === 'ObjectPattern'
558571
) {
559-
variablesProp.value = typeCast(
560-
variablesProp.value as ExpressionKind,
561-
j.typeAnnotation(
562-
j.genericTypeAnnotation(j.identifier(variables.id.name), null)
563-
)
572+
path.node.id.typeAnnotation = subscriptionResultAnnotation(
573+
data,
574+
variables
564575
)
565576
}
577+
if (path.node.init?.type !== 'CallExpression') return
578+
const options = path.node.init.arguments[1]
579+
if (variables && options && options.type === 'ObjectExpression') {
580+
const variablesProp = options.properties.find(
581+
p =>
582+
p.type !== 'SpreadElement' &&
583+
p.type !== 'SpreadProperty' &&
584+
p.key.type === 'Identifier' &&
585+
p.key.name === 'variables'
586+
)
587+
if (
588+
variablesProp &&
589+
variablesProp.type !== 'SpreadElement' &&
590+
variablesProp.type !== 'SpreadProperty' &&
591+
variablesProp.type !== 'ObjectMethod'
592+
) {
593+
variablesProp.value = typeCast(
594+
variablesProp.value as ExpressionKind,
595+
j.typeAnnotation(
596+
j.genericTypeAnnotation(
597+
j.identifier(variables.id.name),
598+
null
599+
)
600+
)
601+
)
602+
}
603+
}
566604
}
567605
})
568606
}

0 commit comments

Comments
 (0)