diff --git a/packages/@sanity/codegen/src/typescript/findQueriesInSource.ts b/packages/@sanity/codegen/src/typescript/findQueriesInSource.ts index 2fbbe50ba8fb..2a48fedc8dc5 100644 --- a/packages/@sanity/codegen/src/typescript/findQueriesInSource.ts +++ b/packages/@sanity/codegen/src/typescript/findQueriesInSource.ts @@ -1,6 +1,6 @@ import {createRequire} from 'node:module' -import {type TransformOptions, traverse} from '@babel/core' +import {type NodePath, type TransformOptions, traverse} from '@babel/core' import * as babelTypes from '@babel/types' import {getBabelConfig} from '../getBabelConfig' @@ -11,6 +11,8 @@ const require = createRequire(__filename) const groqTagName = 'groq' +const ignoreValue = '@sanity-typegen-ignore' + /** * findQueriesInSource takes a source string and returns all GROQ queries in it. * @param source - The source code to search for queries @@ -33,8 +35,11 @@ export function findQueriesInSource( traverse(file, { // Look for variable declarations, e.g. `const myQuery = groq`... and extract the query. // The variable name is used as the name of the query result type - VariableDeclarator({node, scope}) { + VariableDeclarator(path) { + const {node, scope} = path + const init = node.init + // Look for tagged template expressions that are called with the `groq` tag if ( babelTypes.isTaggedTemplateExpression(init) && @@ -42,6 +47,10 @@ export function findQueriesInSource( babelTypes.isIdentifier(node.id) && init.tag.name === groqTagName ) { + if (getDeclarationLeadingComment(path)?.trim() === ignoreValue) { + return + } + const queryName = `${node.id.name}` const queryResult = resolveExpression({ node: init, @@ -51,6 +60,7 @@ export function findQueriesInSource( filename, resolver, }) + queries.push({name: queryName, result: queryResult}) } }, @@ -58,3 +68,72 @@ export function findQueriesInSource( return queries } + +function getDeclarationLeadingComment( + path: NodePath, +): string | null { + /* + * We have to consider these cases: + * + * // @sanity-typegen-ignore + * const query = groq`...` + * + * // AST + * VariableDeclaration { + * declarations: [ + * VariableDeclarator: {init: tag: {name: "groq"}} + * ], + * leadingComments: ... + * } + * + * // @sanity-typegen-ignore + * const query1 = groq`...`, query2 = groq`...` + * + * // AST + * VariableDeclaration { + * declarations: [ + * VariableDeclarator: {init: tag: {name: "groq"}} + * VariableDeclarator: {init: tag: {name: "groq"}} + * ], + * leadingComments: ... + * } + * + * // @sanity-typegen-ignore + * export const query = groq`...` + * + * // AST + * ExportNamedDeclaration { + * declaration: VariableDeclaration { + * declarations: [ + * VariableDeclarator: {init: tag: {name: "groq"}} + * VariableDeclarator: {init: tag: {name: "groq"}} + * ], + * }, + * leadingComments: ... + * } + * + * In the case where multiple variables are under the same VariableDeclaration the leadingComments + * will still be on the VariableDeclaration + * + * In the case where the variable is exported, the leadingComments are on the + * ExportNamedDeclaration which includes the VariableDeclaration in its own declaration property + */ + + const variableDeclaration = path.find((node) => node.isVariableDeclaration()) + if (!variableDeclaration) return null + + if (variableDeclaration.node.leadingComments) { + return getLastInArray(variableDeclaration.node.leadingComments)?.value || null + } + + // If the declaration is exported, the comment lies on the parent of the export declaration + if (variableDeclaration.parent.leadingComments) { + return getLastInArray(variableDeclaration.parent.leadingComments)?.value || null + } + + return null +} + +function getLastInArray(arr: T[]) { + return arr[arr.length - 1] +}