Skip to content

Commit

Permalink
Add JSDoc comments generation in $result types and enum declarations (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
ewen-lbh authored Mar 8, 2024
1 parent bc2f115 commit 7f426d9
Show file tree
Hide file tree
Showing 9 changed files with 451 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/quick-experts-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'houdini': patch
---

GraphQL documentation strings and deprecation reasons are reflected as JSDoc comments on generated type definitions. Hover over any field of a query store, an enum, or an enum's value and your IDE should show you the documentation from the GraphQL API.
35 changes: 33 additions & 2 deletions e2e/_api/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ Date custom scalar type
scalar DateTime
scalar File

"""
Can be Value1 or Value2.
"""
enum MyEnum {
"The first value"
Value1
Value2
"The second value"
Value2 @deprecated(reason: "Use Value1 instead")
}

enum TypeOfUser {
Expand All @@ -15,13 +20,17 @@ enum TypeOfUser {
}

enum ForceReturn {
"Normal"
NORMAL
"No value"
NULL
"Some error"
ERROR
}

type Mutation {
addUser(
"""The users birth date"""
birthDate: DateTime!
name: String!
snapshot: String!
Expand Down Expand Up @@ -53,6 +62,9 @@ type Mutation {
createB(b: String!): B!
}

"""
A node.
"""
interface Node {
id: ID!
}
Expand Down Expand Up @@ -93,6 +105,9 @@ type Query {
rentedBooks: [RentedBook!]!
animals: AnimalConnection!
monkeys: MonkeyConnection!
"""
Get a monkey by its id
"""
monkey(id: ID!): Monkey
}

Expand All @@ -105,7 +120,13 @@ type User implements Node {
friendsConnection(after: String, before: String, first: Int, last: Int): UserConnection!
"This is the same list as what's used globally. its here to tests fragments"
usersConnection(after: String, before: String, first: Int, last: Int): UserConnection!
usersConnectionSnapshot(after: String, before: String, first: Int, last: Int, snapshot: String!): UserConnection!
usersConnectionSnapshot(
after: String
before: String
first: Int
last: Int
snapshot: String!
): UserConnection!
"This is the same list as what's used globally. its here to tests fragments"
userSearch(filter: UserNameFilter!, snapshot: String!): [User!]!
friendsList(limit: Int, offset: Int): [User!]!
Expand All @@ -121,10 +142,20 @@ interface Animal implements Node {
name: String!
}

"""
A monkey.
"""
type Monkey implements Node & Animal {
id: ID!
name: String!
"""
Whether the monkey has a banana or not
"""
hasBanana: Boolean!
"""
Whether the monkey has a banana or not
"""
oldHasBanana: Boolean @deprecated(reason: "Use hasBanana")
}

interface AnimalConnection {
Expand Down
10 changes: 10 additions & 0 deletions packages/houdini/src/codegen/generators/comments/jsdoc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as recast from 'recast'

const AST = recast.types.builders
export function jsdocComment(text: string, deprecated?: string) {
let commentContent = `*\n * ${text}\n`
if (deprecated) {
commentContent = `${commentContent} * @deprecated ${deprecated}\n`
}
return AST.commentBlock(commentContent, true)
}
15 changes: 15 additions & 0 deletions packages/houdini/src/codegen/generators/definitions/enums.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,24 @@ test('generates runtime definitions for each enum', async function () {
expect(parsedQuery).toMatchInlineSnapshot(`
type ValuesOf<T> = T[keyof T]
/** Documentation of testenum1 */
export declare const TestEnum1: {
/** Documentation of Value1 */
readonly Value1: "Value1";
/** Documentation of Value2 */
readonly Value2: "Value2";
}
/** Documentation of testenum1 */
export type TestEnum1$options = ValuesOf<typeof TestEnum1>
/** Documentation of testenum2 */
export declare const TestEnum2: {
readonly Value3: "Value3";
readonly Value2: "Value2";
}
/** Documentation of testenum2 */
export type TestEnum2$options = ValuesOf<typeof TestEnum2>
`)

Expand All @@ -48,11 +54,20 @@ test('generates runtime definitions for each enum', async function () {
}).program

expect(parsedQuery).toMatchInlineSnapshot(`
/** Documentation of testenum1 */
export const TestEnum1 = {
/**
* Documentation of Value1
*/
"Value1": "Value1",
/**
* Documentation of Value2
*/
"Value2": "Value2"
};
/** Documentation of testenum2 */
export const TestEnum2 = {
"Value3": "Value3",
"Value2": "Value2"
Expand Down
42 changes: 37 additions & 5 deletions packages/houdini/src/codegen/generators/definitions/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as recast from 'recast'
import type { Config } from '../../../lib'
import { fs, path, printJS } from '../../../lib'
import { moduleExport } from '../../utils'
import { jsdocComment } from '../comments/jsdoc'

const AST = recast.types.builders

Expand All @@ -25,19 +26,39 @@ export default async function definitionsGenerator(config: Config) {
enums.map((defn) => {
const name = defn.name.value

return moduleExport(
const declaration = moduleExport(
config,
name,
AST.objectExpression(
defn.values?.map((value) => {
const str = value.name.value
return AST.objectProperty(
const prop = AST.objectProperty(
AST.stringLiteral(str),
AST.stringLiteral(str)
)
const deprecationReason = (
value.directives
?.find((d) => d.name.value === 'deprecated')
?.arguments?.find((a) => a.name.value === 'reason')
?.value as graphql.StringValueNode
)?.value

if (value.description || deprecationReason)
prop.comments = [
jsdocComment(value.description?.value ?? '', deprecationReason),
]
return prop
}) || []
)
)

if (defn.description) {
declaration.comments = [
AST.commentBlock(`* ${defn.description.value} `, true, false),
]
}

return declaration
})
)
)
Expand All @@ -53,11 +74,22 @@ type ValuesOf<T> = T[keyof T]
const name = definition.name.value
const values = definition.values

return `
let jsdoc = ''
if (definition.description) {
jsdoc = `\n/** ${definition.description.value} */`
}

return `${jsdoc}
export declare const ${name}: {
${values?.map((value) => ` readonly ${value.name.value}: "${value.name.value}";`).join('\n')}
${values
?.map(
(value) =>
(value.description ? ` /** ${value.description.value} */\n` : '') +
` readonly ${value.name.value}: "${value.name.value}";`
)
.join('\n')}
}
${jsdoc}
export type ${name}$options = ValuesOf<typeof ${name}>
`
})
Expand Down
Loading

0 comments on commit 7f426d9

Please sign in to comment.