Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(codegen): generate SanityQueries interface in @sanity/codegen #7304

Merged
merged 1 commit into from
Aug 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 26 additions & 18 deletions packages/@sanity/cli/src/actions/typegen/generateAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export default async function typegenGenerateAction(
workDir,
schemaPath: codegenConfig.schema,
searchPath: codegenConfig.path,
overloadClientMethods: codegenConfig.overloadClientMethods,
prettierConfig,
} satisfies TypegenGenerateTypesWorkerData,
// eslint-disable-next-line no-process-env
Expand Down Expand Up @@ -131,25 +132,32 @@ export default async function typegenGenerateAction(
return
}

stats.queryFilesCount++
for (const {
queryName,
query,
type,
typeNodesGenerated,
unknownTypeNodesGenerated,
emptyUnionTypeNodesGenerated,
} of msg.types) {
fileTypeString += `// Variable: ${queryName}\n`
fileTypeString += `// Query: ${query.replace(/(\r\n|\n|\r)/gm, '')}\n`
fileTypeString += type
stats.queriesCount++
stats.typeNodesGenerated += typeNodesGenerated
stats.unknownTypeNodesGenerated += unknownTypeNodesGenerated
stats.emptyUnionTypeNodesGenerated += emptyUnionTypeNodesGenerated
if (msg.type === 'types') {
stats.queryFilesCount++
for (const {
queryName,
query,
type,
typeNodesGenerated,
unknownTypeNodesGenerated,
emptyUnionTypeNodesGenerated,
} of msg.types) {
fileTypeString += `// Variable: ${queryName}\n`
fileTypeString += `// Query: ${query.replace(/(\r\n|\n|\r)/gm, '')}\n`
fileTypeString += type
stats.queriesCount++
stats.typeNodesGenerated += typeNodesGenerated
stats.unknownTypeNodesGenerated += unknownTypeNodesGenerated
stats.emptyUnionTypeNodesGenerated += emptyUnionTypeNodesGenerated
}
typeFile.write(fileTypeString)
stats.size += Buffer.byteLength(fileTypeString)
}

if (msg.type === 'typemap') {
typeFile.write(msg.typeMap)
stats.size += Buffer.byteLength(msg.typeMap)
}
typeFile.write(fileTypeString)
stats.size += Buffer.byteLength(fileTypeString)
})
worker.addListener('error', reject)
})
Expand Down
28 changes: 23 additions & 5 deletions packages/@sanity/cli/src/workers/typegenGenerate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface TypegenGenerateTypesWorkerData {
schemaPath: string
searchPath: string | string[]
prettierConfig: PrettierOptions | null
overloadClientMethods?: boolean
}

export type TypegenGenerateTypesWorkerMessage =
Expand Down Expand Up @@ -49,6 +50,11 @@ export type TypegenGenerateTypesWorkerMessage =
schema: string
length: number
}
| {
type: 'typemap'
filename: string
typeMap: string
}
| {
type: 'complete'
}
Expand Down Expand Up @@ -115,6 +121,8 @@ async function main() {
queryName: string
query: string
type: string
typeName: string
typeNode: TypeNode
unknownTypeNodesGenerated: number
typeNodesGenerated: number
emptyUnionTypeNodesGenerated: number
Expand All @@ -124,16 +132,17 @@ async function main() {
const ast = safeParseQuery(query)
const queryTypes = typeEvaluate(ast, schema)

const type = await maybeFormatCode(
typeGenerator.generateTypeNodeTypes(`${queryName}Result`, queryTypes).trim(),
opts.prettierConfig,
)
const typeName = `${queryName}Result`
const type = typeGenerator.generateTypeNodeTypes(typeName, queryTypes)
const code = await maybeFormatCode(type.trim(), opts.prettierConfig)

const queryTypeStats = walkAndCountQueryTypeNodeStats(queryTypes)
fileQueryTypes.push({
queryName,
query,
type,
typeName,
typeNode: queryTypes,
type: code,
unknownTypeNodesGenerated: queryTypeStats.unknownTypes,
typeNodesGenerated: queryTypeStats.allTypes,
emptyUnionTypeNodesGenerated: queryTypeStats.emptyUnions,
Expand All @@ -159,6 +168,15 @@ async function main() {
filename: result.filename,
} satisfies TypegenGenerateTypesWorkerMessage)
}

if (fileQueryTypes.length > 0 && opts.overloadClientMethods) {
const typeMap = typeGenerator.generateQueryMap(fileQueryTypes)
parentPort?.postMessage({
type: 'typemap',
filename: result.filename,
typeMap,
} satisfies TypegenGenerateTypesWorkerMessage)
}
}

parentPort?.postMessage({
Expand Down
1 change: 1 addition & 0 deletions packages/@sanity/codegen/src/readConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const configDefintion = z.object({
schema: z.string().default('./schema.json'),
generates: z.string().default('./sanity.types.ts'),
formatGeneratedCode: z.boolean().default(true),
overloadClientMethods: z.boolean().default(false),
})

export type CodegenConfig = z.infer<typeof configDefintion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {describe, expect, test} from '@jest/globals'

import {findQueriesInSource} from '../findQueriesInSource'

describe('findQueries', () => {
describe('findQueries with the groq template', () => {
describe('should find queries in source', () => {
test('plain string', () => {
const source = `
Expand Down Expand Up @@ -157,3 +157,201 @@ describe('findQueries', () => {
expect(queries.length).toBe(0)
})
})

describe('findQueries with defineQuery', () => {
describe('should find queries in source', () => {
test('plain string', () => {
const source = `
import { defineQuery } from "groq";
const postQuery = defineQuery("*[_type == 'author']");
const res = sanity.fetch(postQuery);
`

const queries = findQueriesInSource(source, 'test.ts')
const queryResult = queries[0]

expect(queryResult?.result).toEqual("*[_type == 'author']")
})

test('template string', () => {
const source = `
import { defineQuery } from "groq";
const postQuery = defineQuery(\`*[_type == "author"]\`);
const res = sanity.fetch(postQuery);
`

const queries = findQueriesInSource(source, 'test.ts')
const queryResult = queries[0]

expect(queryResult?.result).toEqual('*[_type == "author"]')
})

test('with variables', () => {
const source = `
import { defineQuery } from "groq";
const type = "author";
const authorQuery = defineQuery(\`*[_type == "\${type}"]\`);
const res = sanity.fetch(authorQuery);
`

const queries = findQueriesInSource(source, 'test.ts')
const queryResult = queries[0]

expect(queryResult?.result).toEqual('*[_type == "author"]')
})

test('with function', () => {
const source = `
import { defineQuery } from "groq";
const getType = () => () => () => "author";
const query = defineQuery(\`*[_type == "\${getType()()()}"]\`);
const res = sanity.fetch(query);
`

const queries = findQueriesInSource(source, 'test.ts')

const queryResult = queries[0]

expect(queryResult?.result).toEqual('*[_type == "author"]')
})

test('with block comment', () => {
const source = `
import { defineQuery } from "groq";
const type = "author";
const query = /* groq */ defineQuery(\`*[_type == "\${type}"]\`);
const res = sanity.fetch(query);
`

const queries = findQueriesInSource(source, 'test.ts')
const queryResult = queries[0]

expect(queryResult?.result).toEqual('*[_type == "author"]')
})
})

test('should not find inline queries in source', () => {
const source = `
import { defineQuery } from "groq";
const res = sanity.fetch(defineQuery(\`*[_type == "author"]\`));
`

const queries = findQueriesInSource(source, 'test.ts')

expect(queries.length).toBe(0)
})

test('should import', () => {
const source = `
import {defineQuery} from "groq";
import {foo} from "./fixtures/exportVar";
const postQuery = defineQuery(\`*[_type == "\${foo}"]\`);
const res = sanity.fetch(postQueryResult);
`
const queries = findQueriesInSource(source, __filename, undefined)
expect(queries.length).toBe(1)
expect(queries[0].result).toBe('*[_type == "foo"]')
})

test('should import, subdirectory', () => {
const source = `
import {defineQuery} from "groq";
import {foo} from "../__tests__/fixtures/exportVar";
const postQuery = defineQuery(\`*[_type == "\${foo}"]\`);
const res = sanity.fetch(postQueryResult);
`
const queries = findQueriesInSource(source, __filename, undefined)
expect(queries.length).toBe(1)
expect(queries[0].result).toBe('*[_type == "foo"]')
})

test('can import sequence of files', () => {
const source = `
import {defineQuery} from "groq";
import {query} from "../__tests__/fixtures/importSeq1";
const someQuery = defineQuery(\`$\{query}\`);
`
const queries = findQueriesInSource(source, __filename, undefined)
expect(queries.length).toBe(1)
expect(queries[0].result).toBe('*[_type == "foo bar"]')
})

test('should detect defineQuery calls that have been required', () => {
const source = `
const {defineQuery} = require("groq");
import {query} from "../__tests__/fixtures/importSeq1";
const someQuery = defineQuery(\`$\{query}\`);
`
const queries = findQueriesInSource(source, __filename, undefined)
expect(queries.length).toBe(1)
expect(queries[0].result).toBe('*[_type == "foo bar"]')
})

test('will ignore declarations with ignore tag', () => {
const source = `
import {defineQuery} from "groq";

// @sanity-typegen-ignore
const postQuery = defineQuery(\`*[_type == "foo"]\`);
`

const queries = findQueriesInSource(source, __filename, undefined)
expect(queries.length).toBe(0)
})

test('will ignore export named declarations with ignore tag', () => {
const source = `
import {defineQuery} from "groq";

// @sanity-typegen-ignore
export const postQuery = defineQuery(\`*[_type == "foo"]\`);
`

const queries = findQueriesInSource(source, __filename, undefined)
expect(queries.length).toBe(0)
})

test('will ignore declarations with ignore tag, even with multiple comments above declaration', () => {
const source = `
import {defineQuery} from "groq";

// This is a query that queries posts
// @sanity-typegen-ignore
export const postQuery = groq\`*[_type == "foo"]\`
`

const queries = findQueriesInSource(source, __filename, undefined)
expect(queries.length).toBe(0)
})

test('will ignore declerations if any of the leading comments are ignore tags', () => {
const source = `
import {defineQuery} from "groq";

// @sanity-typegen-ignore
// This should be ignored because of the comment above
export const postQuery = defineQuery(\`*[_type == "foo"]\`);
`

const queries = findQueriesInSource(source, __filename, undefined)
expect(queries.length).toBe(0)
})

test('will ignore defineQuery calls that are not coming from the groq module', () => {
const source = `
import {defineQuery} from "another-module";
export const postQuery = defineQuery(\`*[_type == "foo"]\`);
`
const queries = findQueriesInSource(source, __filename, undefined)
expect(queries.length).toBe(0)
})

test('will ignore defineQuery calls that are not coming from the groq module when using require', () => {
const source = `
const {defineQuery} = require("another-module");
export const postQuery = defineQuery(\`*[_type == "foo"]\`);
`
const queries = findQueriesInSource(source, __filename, undefined)
expect(queries.length).toBe(0)
})
})
Loading
Loading