From b0f2f6d44f8865e2611bcd96d3a642a2f84f1d22 Mon Sep 17 00:00:00 2001 From: Landon Gavin Date: Tue, 2 Jan 2024 19:41:14 -0500 Subject: [PATCH 1/4] patch(return types): union return type with undefined. React query data is undefined by default. Previously we were overriding the default type of react query. Now union with undefined. We are also exporting the data type. --- examples/react-app/petstore.yaml | 6 + examples/react-app/src/App.tsx | 5 + src/createUseQuery.ts | 419 +++++++++++++++++-------------- 3 files changed, 235 insertions(+), 195 deletions(-) diff --git a/examples/react-app/petstore.yaml b/examples/react-app/petstore.yaml index ad15f64..11d590f 100644 --- a/examples/react-app/petstore.yaml +++ b/examples/react-app/petstore.yaml @@ -77,6 +77,12 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + /not-defined: + get: + description: This path is not fully defined. + responses: + default: + description: unexpected error /pets/{id}: get: description: Returns a user based on a single ID, if the user does not have access to the pet diff --git a/examples/react-app/src/App.tsx b/examples/react-app/src/App.tsx index 220db20..9a6ec4e 100644 --- a/examples/react-app/src/App.tsx +++ b/examples/react-app/src/App.tsx @@ -3,6 +3,7 @@ import { useDefaultClientAddPet, useDefaultClientFindPets, useDefaultClientFindPetsKey, + useDefaultClientGetNotDefined, } from "../openapi/queries"; import { useState } from "react"; import { queryClient } from "./queryClient"; @@ -13,6 +14,10 @@ function App() { const { data, error, refetch } = useDefaultClientFindPets({ tags, limit }); + // This is an example of a query that is not defined in the OpenAPI spec + // this defaults to any - here we are showing how to override the type + const { data: notDefined } = useDefaultClientGetNotDefined(); + const { mutate: addPet } = useDefaultClientAddPet(); if (error) diff --git a/src/createUseQuery.ts b/src/createUseQuery.ts index 90dde10..c7c01ba 100644 --- a/src/createUseQuery.ts +++ b/src/createUseQuery.ts @@ -43,10 +43,7 @@ export const createUseQuery = ( const customHookName = `use${className}${capitalizeFirstLetter(methodName)}`; const queryKey = `${customHookName}Key`; - const queryKeyGenericType = ts.factory.createTypeReferenceNode( - "TQueryKey", - undefined - ); + const queryKeyGenericType = ts.factory.createTypeReferenceNode("TQueryKey"); const queryKeyConstraint = ts.factory.createTypeReferenceNode("Array", [ ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword), ]); @@ -69,219 +66,251 @@ export const createUseQuery = ( ), ] ); + // DefaultResponseDataType + const defaultApiResponse = ts.factory.createTypeAliasDeclaration( + [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], + ts.factory.createIdentifier( + `${capitalizeFirstLetter(className)}${capitalizeFirstLetter( + methodName + )}DefaultResponse` + ), + undefined, + awaitedResponseDataType + ); + + const TData = ts.factory.createIdentifier("TData"); + const TError = ts.factory.createIdentifier("TError"); const responseDataType = ts.factory.createTypeParameterDeclaration( undefined, - "TData", + TData.text, undefined, - awaitedResponseDataType + ts.factory.createTypeReferenceNode(defaultApiResponse.name) ); - return [ - // QueryKey - ts.factory.createVariableStatement( - [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], - ts.factory.createVariableDeclarationList( + // Omit>, TError>, 'data'> & { data: TData|undefined }; + const responseReturnType = ts.factory.createIntersectionTypeNode([ + ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Omit"), [ + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier("UseQueryResult"), [ - ts.factory.createVariableDeclaration( - ts.factory.createIdentifier(queryKey), - undefined, - undefined, - ts.factory.createStringLiteral( - `${className}${capitalizeFirstLetter(methodName)}` - ) - ), - ], - ts.NodeFlags.Const - ) + defaultApiResponse.type, + ts.factory.createTypeReferenceNode(TError, undefined), + ] + ), + ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral("data")), + ]), + ts.factory.createTypeLiteralNode([ + ts.factory.createPropertySignature( + undefined, + ts.factory.createIdentifier("data"), + undefined, + ts.factory.createUnionTypeNode([ + ts.factory.createTypeReferenceNode(TData, undefined), + ts.factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword), + ]) + ), + ]), + ]); + + // Return Type + const returnTypeExport = ts.factory.createTypeAliasDeclaration( + [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], + ts.factory.createIdentifier( + `${capitalizeFirstLetter(className)}${capitalizeFirstLetter( + methodName + )}QueryResult` ), - // Custom hook - ts.factory.createVariableStatement( - [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], - ts.factory.createVariableDeclarationList( - [ - ts.factory.createVariableDeclaration( - ts.factory.createIdentifier(customHookName), - undefined, + [ + ts.factory.createTypeParameterDeclaration( + undefined, + TData, + undefined, + ts.factory.createTypeReferenceNode(defaultApiResponse.name) + ), + ts.factory.createTypeParameterDeclaration( + undefined, + TError, + undefined, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword) + ), + ], + responseReturnType + ); + + // QueryKey + const queryKeyExport = ts.factory.createVariableStatement( + [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], + ts.factory.createVariableDeclarationList( + [ + ts.factory.createVariableDeclaration( + ts.factory.createIdentifier(queryKey), + undefined, + undefined, + ts.factory.createStringLiteral( + `${className}${capitalizeFirstLetter(methodName)}` + ) + ), + ], + ts.NodeFlags.Const + ) + ); + + // Custom hook + const hookExport = ts.factory.createVariableStatement( + [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], + ts.factory.createVariableDeclarationList( + [ + ts.factory.createVariableDeclaration( + ts.factory.createIdentifier(customHookName), + undefined, + undefined, + ts.factory.createArrowFunction( undefined, - ts.factory.createArrowFunction( - undefined, - ts.factory.createNodeArray([ - ts.factory.createTypeParameterDeclaration( - undefined, - "TQueryKey", - queryKeyConstraint, - ts.factory.createArrayTypeNode( - ts.factory.createKeywordTypeNode( - ts.SyntaxKind.UnknownKeyword - ) - ) - ), - responseDataType, - ts.factory.createTypeParameterDeclaration( - undefined, - "TError", - undefined, + ts.factory.createNodeArray([ + responseDataType, + ts.factory.createTypeParameterDeclaration( + undefined, + TError, + undefined, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword) + ), + ts.factory.createTypeParameterDeclaration( + undefined, + "TQueryKey", + queryKeyConstraint, + ts.factory.createArrayTypeNode( ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword) - ), - ]), - [ - ...requestParam, - ts.factory.createParameterDeclaration( - undefined, - undefined, - ts.factory.createIdentifier("queryKey"), - ts.factory.createToken(ts.SyntaxKind.QuestionToken), - queryKeyGenericType - ), - ts.factory.createParameterDeclaration( - undefined, - undefined, - ts.factory.createIdentifier("options"), - ts.factory.createToken(ts.SyntaxKind.QuestionToken), - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier("Omit"), - [ - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier("UseQueryOptions"), - [ - awaitedResponseDataType, - ts.factory.createKeywordTypeNode( - ts.SyntaxKind.UnknownKeyword - ), - awaitedResponseDataType, - ts.factory.createArrayTypeNode( - ts.factory.createKeywordTypeNode( - ts.SyntaxKind.UnknownKeyword - ) - ), - ] - ), - ts.factory.createUnionTypeNode([ - ts.factory.createLiteralTypeNode( - ts.factory.createStringLiteral("queryKey") - ), - ts.factory.createLiteralTypeNode( - ts.factory.createStringLiteral("queryFn") - ), - ts.factory.createLiteralTypeNode( - ts.factory.createStringLiteral("initialData") - ), - ]), - ] - ) - ), - ], - undefined, - ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), - ts.factory.createAsExpression( - ts.factory.createCallExpression( - ts.factory.createIdentifier("useQuery"), - undefined, + ) + ), + ]), + [ + ...requestParam, + ts.factory.createParameterDeclaration( + undefined, + undefined, + ts.factory.createIdentifier("queryKey"), + ts.factory.createToken(ts.SyntaxKind.QuestionToken), + queryKeyGenericType + ), + ts.factory.createParameterDeclaration( + undefined, + undefined, + ts.factory.createIdentifier("options"), + ts.factory.createToken(ts.SyntaxKind.QuestionToken), + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier("Omit"), [ - ts.factory.createObjectLiteralExpression([ - ts.factory.createPropertyAssignment( - ts.factory.createIdentifier("queryKey"), - ts.factory.createArrayLiteralExpression( - [ - ts.factory.createIdentifier(queryKey), - ts.factory.createSpreadElement( - ts.factory.createParenthesizedExpression( - ts.factory.createBinaryExpression( - ts.factory.createIdentifier("queryKey"), - ts.factory.createToken( - ts.SyntaxKind.QuestionQuestionToken - ), - method.parameters.length - ? ts.factory.createArrayLiteralExpression([ - ts.factory.createObjectLiteralExpression( - method.parameters.map((param) => - ts.factory.createShorthandPropertyAssignment( - ts.factory.createIdentifier( - param.name.getText(node) - ) - ) - ) - ), - ]) - : ts.factory.createArrayLiteralExpression( - [] - ) - ) - ) - ), - ], - false - ) + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier("UseQueryOptions"), + [ + ts.factory.createTypeReferenceNode(TData), + ts.factory.createTypeReferenceNode(TError), + ts.factory.createTypeReferenceNode(TData), + queryKeyGenericType, + ] + ), + ts.factory.createUnionTypeNode([ + ts.factory.createLiteralTypeNode( + ts.factory.createStringLiteral("queryKey") ), - ts.factory.createPropertyAssignment( - ts.factory.createIdentifier("queryFn"), - ts.factory.createArrowFunction( - undefined, - undefined, - [], - undefined, - ts.factory.createToken( - ts.SyntaxKind.EqualsGreaterThanToken - ), - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(className), - ts.factory.createIdentifier(methodName) - ), - undefined, - method.parameters.map((param) => - ts.factory.createIdentifier( - param.name.getText(node) - ) - ) - ) - ) + ts.factory.createLiteralTypeNode( + ts.factory.createStringLiteral("queryFn") ), - ts.factory.createSpreadAssignment( - ts.factory.createIdentifier("options") + ts.factory.createLiteralTypeNode( + ts.factory.createStringLiteral("initialData") ), ]), ] - ), - // Omit>, TError>, 'data'> & { data: TData }; - ts.factory.createIntersectionTypeNode([ - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier("Omit"), - [ - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier("UseQueryResult"), + ) + ), + ], + undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createCallExpression( + ts.factory.createIdentifier("useQuery"), + [ + ts.factory.createTypeReferenceNode(TData), + ts.factory.createTypeReferenceNode(TError), + ts.factory.createTypeReferenceNode(TData), + queryKeyGenericType, + ], + [ + ts.factory.createObjectLiteralExpression([ + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier("queryKey"), + ts.factory.createAsExpression( + ts.factory.createArrayLiteralExpression( [ - awaitedResponseDataType, - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier("TError"), - undefined + ts.factory.createIdentifier(queryKey), + ts.factory.createSpreadElement( + ts.factory.createParenthesizedExpression( + ts.factory.createBinaryExpression( + ts.factory.createIdentifier("queryKey"), + ts.factory.createToken( + ts.SyntaxKind.QuestionQuestionToken + ), + method.parameters.length + ? ts.factory.createArrayLiteralExpression([ + ts.factory.createObjectLiteralExpression( + method.parameters.map((param) => + ts.factory.createShorthandPropertyAssignment( + ts.factory.createIdentifier( + param.name.getText(node) + ) + ) + ) + ), + ]) + : ts.factory.createArrayLiteralExpression([]) + ) + ) ), - ] - ), - ts.factory.createLiteralTypeNode( - ts.factory.createStringLiteral("data") + ], + false ), - ] + queryKeyGenericType + ) ), - ts.factory.createTypeLiteralNode([ - ts.factory.createPropertySignature( + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier("queryFn"), + ts.factory.createArrowFunction( + undefined, undefined, - ts.factory.createIdentifier("data"), + [], undefined, - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier("TData"), - undefined + ts.factory.createToken( + ts.SyntaxKind.EqualsGreaterThanToken + ), + ts.factory.createAsExpression( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(className), + ts.factory.createIdentifier(methodName) + ), + undefined, + method.parameters.map((param) => + ts.factory.createIdentifier( + param.name.getText(node) + ) + ) + ), + ts.factory.createTypeReferenceNode(TData) ) - ), - ]), - ]) - ) + ) + ), + ts.factory.createSpreadAssignment( + ts.factory.createIdentifier("options") + ), + ]), + ] ) - ), - ], - ts.NodeFlags.Const - ) - ), - ]; + ) + ), + ], + ts.NodeFlags.Const + ) + ); + + return [defaultApiResponse, returnTypeExport, queryKeyExport, hookExport]; }; From ac1f8a4967251618c15c5e7dfe9b51230ff9ff13 Mon Sep 17 00:00:00 2001 From: Landon Gavin Date: Tue, 2 Jan 2024 22:16:36 -0500 Subject: [PATCH 2/4] patch(jsdoc): add jsdoc codegen --- examples/react-app/petstore.yaml | 1 + src/createExports.ts | 22 +++++++++++++++-- src/createUseQuery.ts | 42 ++++++++++++++++++++++++++++++-- 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/examples/react-app/petstore.yaml b/examples/react-app/petstore.yaml index 11d590f..6b21d5d 100644 --- a/examples/react-app/petstore.yaml +++ b/examples/react-app/petstore.yaml @@ -79,6 +79,7 @@ paths: $ref: '#/components/schemas/Error' /not-defined: get: + deprecated: true description: This path is not fully defined. responses: default: diff --git a/src/createExports.ts b/src/createExports.ts index e57e904..eb28674 100644 --- a/src/createExports.ts +++ b/src/createExports.ts @@ -1,4 +1,4 @@ -import ts from "typescript"; +import ts, { JSDoc } from "typescript"; import { sync } from "glob"; import { join } from "path"; import fs from "fs"; @@ -45,8 +45,26 @@ export const createExports = (generatedClientsPath: string) => { const httpMethodName = properties .find((p) => p.name?.getText(node) === "method") ?.initializer?.getText(node)!; + + + const getAllChildren = (tsNode: ts.Node): Array => { + const childItems = tsNode.getChildren(node); + if (childItems.length) { + const allChildren = childItems.map(getAllChildren); + return [tsNode].concat(allChildren.flat()); + } + return [tsNode]; + } + + const children = getAllChildren(method); + const jsDoc = children.filter((c) => c.kind === ts.SyntaxKind.JSDoc).map((c) => { + return (c as JSDoc).comment + }); + const hasDeprecated = children + .some((c) => c.kind === ts.SyntaxKind.JSDocDeprecatedTag); + return httpMethodName === "'GET'" - ? createUseQuery(node, className, method) + ? createUseQuery(node, className, method, jsDoc, hasDeprecated) : createUseMutation(node, className, method); }) .flat(); diff --git a/src/createUseQuery.ts b/src/createUseQuery.ts index c7c01ba..e762f33 100644 --- a/src/createUseQuery.ts +++ b/src/createUseQuery.ts @@ -4,7 +4,9 @@ import { capitalizeFirstLetter } from "./common"; export const createUseQuery = ( node: ts.SourceFile, className: string, - method: ts.MethodDeclaration + method: ts.MethodDeclaration, + jsDoc: (string | ts.NodeArray | undefined)[] = [], + deprecated: boolean = false ) => { const methodName = method.name?.getText(node)!; let requestParam = []; @@ -312,5 +314,41 @@ export const createUseQuery = ( ) ); - return [defaultApiResponse, returnTypeExport, queryKeyExport, hookExport]; + const deprecatedString = deprecated ? "@deprecated" : ""; + + const jsDocString = [deprecatedString] + .concat( + jsDoc.map((comment) => { + if (typeof comment === "string") { + return comment; + } + if (Array.isArray(comment)) { + return comment + .map((c) => c.getText(node)) + .join("\n"); + } + return ""; + }) + ) + // remove empty lines + .filter(Boolean) + // trim + .map((comment) => comment.trim()) + // add * to each line + .map((comment) => `* ${comment}`) + // join lines + .join("\n") + // replace new lines with \n * + .replace(/\n/g, "\n * "); + + const hookWithJSDoc = jsDocString + ? ts.addSyntheticLeadingComment( + hookExport, + ts.SyntaxKind.MultiLineCommentTrivia, + `*\n ${jsDocString}\n `, + true, + ) + : hookExport; + + return [defaultApiResponse, returnTypeExport, queryKeyExport, hookWithJSDoc]; }; From 3a080398d650e993722f3fcecbbfbb671bed7046 Mon Sep 17 00:00:00 2001 From: Landon Gavin Date: Wed, 3 Jan 2024 00:01:16 -0500 Subject: [PATCH 3/4] Add JSDoc to mutations --- examples/react-app/petstore.yaml | 6 + examples/react-app/src/App.tsx | 3 + src/createExports.ts | 2 +- src/createUseMutation.ts | 192 +++++++++++++++---------------- src/createUseQuery.ts | 79 ++----------- src/util.ts | 44 +++++++ 6 files changed, 158 insertions(+), 168 deletions(-) create mode 100644 src/util.ts diff --git a/examples/react-app/petstore.yaml b/examples/react-app/petstore.yaml index 6b21d5d..cfe7026 100644 --- a/examples/react-app/petstore.yaml +++ b/examples/react-app/petstore.yaml @@ -84,6 +84,12 @@ paths: responses: default: description: unexpected error + post: + deprecated: true + description: This path is not defined at all. + responses: + default: + description: unexpected error /pets/{id}: get: description: Returns a user based on a single ID, if the user does not have access to the pet diff --git a/examples/react-app/src/App.tsx b/examples/react-app/src/App.tsx index 9a6ec4e..41ee5ff 100644 --- a/examples/react-app/src/App.tsx +++ b/examples/react-app/src/App.tsx @@ -4,6 +4,7 @@ import { useDefaultClientFindPets, useDefaultClientFindPetsKey, useDefaultClientGetNotDefined, + useDefaultClientPostNotDefined, } from "../openapi/queries"; import { useState } from "react"; import { queryClient } from "./queryClient"; @@ -16,7 +17,9 @@ function App() { // This is an example of a query that is not defined in the OpenAPI spec // this defaults to any - here we are showing how to override the type + // Note - this is marked as deprecated in the OpenAPI spec and being passed to the client const { data: notDefined } = useDefaultClientGetNotDefined(); + const { mutate: mutateNotDefined } = useDefaultClientPostNotDefined(); const { mutate: addPet } = useDefaultClientAddPet(); diff --git a/src/createExports.ts b/src/createExports.ts index eb28674..d4d25e1 100644 --- a/src/createExports.ts +++ b/src/createExports.ts @@ -65,7 +65,7 @@ export const createExports = (generatedClientsPath: string) => { return httpMethodName === "'GET'" ? createUseQuery(node, className, method, jsDoc, hasDeprecated) - : createUseMutation(node, className, method); + : createUseMutation(node, className, method, jsDoc, hasDeprecated); }) .flat(); }) diff --git a/src/createUseMutation.ts b/src/createUseMutation.ts index 71832c2..c673696 100644 --- a/src/createUseMutation.ts +++ b/src/createUseMutation.ts @@ -1,10 +1,13 @@ import ts from "typescript"; import { capitalizeFirstLetter } from "./common"; +import { addJSDocToNode } from "./util"; export const createUseMutation = ( node: ts.SourceFile, className: string, - method: ts.MethodDeclaration + method: ts.MethodDeclaration, + jsDoc: (string | ts.NodeArray | undefined)[] = [], + deprecated: boolean = false ) => { const methodName = method.name?.getText(node)!; // Awaited> @@ -26,11 +29,22 @@ export const createUseMutation = ( ] ); + const TData = ts.factory.createIdentifier("TData"); + const TError = ts.factory.createIdentifier("TError"); + const TContext = ts.factory.createIdentifier("TContext"); + + const mutationResult = ts.factory.createTypeAliasDeclaration( + [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], + ts.factory.createIdentifier(`${className}${methodName}MutationResult`), + undefined, + awaitedResponseDataType + ); + const responseDataType = ts.factory.createTypeParameterDeclaration( undefined, - "TData", + TData, undefined, - awaitedResponseDataType + ts.factory.createTypeReferenceNode(mutationResult.name) ); const methodParameters = @@ -49,7 +63,7 @@ export const createUseMutation = ( ) : ts.factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword); - return ts.factory.createVariableStatement( + const exportHook = ts.factory.createVariableStatement( [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList( [ @@ -65,13 +79,13 @@ export const createUseMutation = ( responseDataType, ts.factory.createTypeParameterDeclaration( undefined, - "TError", + TError, undefined, ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword) ), ts.factory.createTypeParameterDeclaration( undefined, - "TContext", + TContext, undefined, ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword) ), @@ -88,14 +102,10 @@ export const createUseMutation = ( ts.factory.createTypeReferenceNode( ts.factory.createIdentifier("UseMutationOptions"), [ - awaitedResponseDataType, - ts.factory.createKeywordTypeNode( - ts.SyntaxKind.UnknownKeyword - ), + ts.factory.createTypeReferenceNode(TData), + ts.factory.createTypeReferenceNode(TError), methodParameters, - ts.factory.createKeywordTypeNode( - ts.SyntaxKind.UnknownKeyword - ), + ts.factory.createTypeReferenceNode(TContext), ] ), ts.factory.createLiteralTypeNode( @@ -108,101 +118,79 @@ export const createUseMutation = ( ], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), - ts.factory.createAsExpression( - ts.factory.createCallExpression( - ts.factory.createIdentifier("useMutation"), - undefined, - [ - ts.factory.createObjectLiteralExpression([ - ts.factory.createPropertyAssignment( - ts.factory.createIdentifier("mutationFn"), - ts.factory.createArrowFunction( - undefined, - undefined, - method.parameters.length !== 0 - ? [ - ts.factory.createParameterDeclaration( - undefined, - undefined, - ts.factory.createObjectBindingPattern( - method.parameters.map((param) => { - return ts.factory.createBindingElement( - undefined, - undefined, - ts.factory.createIdentifier( - param.name.getText(node) - ), - undefined - ); - }) - ), - undefined, - undefined, - undefined + ts.factory.createCallExpression( + ts.factory.createIdentifier("useMutation"), + [ + ts.factory.createTypeReferenceNode(TData), + ts.factory.createTypeReferenceNode(TError), + methodParameters, + ts.factory.createTypeReferenceNode(TContext), + ], + [ + ts.factory.createObjectLiteralExpression([ + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier("mutationFn"), + ts.factory.createArrowFunction( + undefined, + undefined, + method.parameters.length !== 0 + ? [ + ts.factory.createParameterDeclaration( + undefined, + undefined, + ts.factory.createObjectBindingPattern( + method.parameters.map((param) => { + return ts.factory.createBindingElement( + undefined, + undefined, + ts.factory.createIdentifier( + param.name.getText(node) + ), + undefined + ); + }) ), - ] - : [], - undefined, - ts.factory.createToken( - ts.SyntaxKind.EqualsGreaterThanToken - ), - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(className), - ts.factory.createIdentifier(methodName) - ), - undefined, - method.parameters.map((params) => - ts.factory.createIdentifier( - params.name.getText(node) + undefined, + undefined, + undefined + ), + ] + : [], + undefined, + ts.factory.createToken( + ts.SyntaxKind.EqualsGreaterThanToken + ), + ts.factory.createAsExpression( + ts.factory.createAsExpression( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(className), + ts.factory.createIdentifier(methodName) + ), + undefined, + method.parameters.map((params) => + ts.factory.createIdentifier( + params.name.getText(node) + ) ) + ), + ts.factory.createKeywordTypeNode( + ts.SyntaxKind.UnknownKeyword ) - ) - ) - ), - ts.factory.createSpreadAssignment( - ts.factory.createIdentifier("options") - ), - ]), - ] - ), - // Omit>, TError, params, TContext>, 'data'> & { data: TData }; - ts.factory.createIntersectionTypeNode([ - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier("Omit"), - [ - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier("UseMutationResult"), - [ - awaitedResponseDataType, - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier("TError"), - undefined ), - methodParameters, + ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier("TContext"), - undefined - ), - ] - ), - ts.factory.createLiteralTypeNode( - ts.factory.createStringLiteral("data") - ), - ] - ), - ts.factory.createTypeLiteralNode([ - ts.factory.createPropertySignature( - undefined, - ts.factory.createIdentifier("data"), - undefined, - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier("TData"), - undefined + ts.factory.createIdentifier("Promise"), + [ts.factory.createTypeReferenceNode(TData)] + ) + ) ) ), + ts.factory.createSpreadAssignment( + ts.factory.createIdentifier("options") + ), ]), - ]) + ] ) ) ), @@ -210,4 +198,8 @@ export const createUseMutation = ( ts.NodeFlags.Const ) ); + + const hookWithJsDoc = addJSDocToNode(exportHook, node, deprecated, jsDoc); + + return [mutationResult, hookWithJsDoc]; }; diff --git a/src/createUseQuery.ts b/src/createUseQuery.ts index e762f33..d7d137e 100644 --- a/src/createUseQuery.ts +++ b/src/createUseQuery.ts @@ -1,5 +1,6 @@ import ts from "typescript"; import { capitalizeFirstLetter } from "./common"; +import { addJSDocToNode } from './util'; export const createUseQuery = ( node: ts.SourceFile, @@ -69,6 +70,7 @@ export const createUseQuery = ( ] ); // DefaultResponseDataType + // export type MyClassMethodDefaultResponse = Awaited> const defaultApiResponse = ts.factory.createTypeAliasDeclaration( [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createIdentifier( @@ -90,32 +92,8 @@ export const createUseQuery = ( ts.factory.createTypeReferenceNode(defaultApiResponse.name) ); - // Omit>, TError>, 'data'> & { data: TData|undefined }; - const responseReturnType = ts.factory.createIntersectionTypeNode([ - ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Omit"), [ - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier("UseQueryResult"), - [ - defaultApiResponse.type, - ts.factory.createTypeReferenceNode(TError, undefined), - ] - ), - ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral("data")), - ]), - ts.factory.createTypeLiteralNode([ - ts.factory.createPropertySignature( - undefined, - ts.factory.createIdentifier("data"), - undefined, - ts.factory.createUnionTypeNode([ - ts.factory.createTypeReferenceNode(TData, undefined), - ts.factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword), - ]) - ), - ]), - ]); - // Return Type + // export const classNameMethodNameQueryResult = UseQueryResult; const returnTypeExport = ts.factory.createTypeAliasDeclaration( [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createIdentifier( @@ -137,7 +115,13 @@ export const createUseQuery = ( ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword) ), ], - responseReturnType + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier("UseQueryResult"), + [ + ts.factory.createTypeReferenceNode(TData), + ts.factory.createTypeReferenceNode(TError), + ], + ), ); // QueryKey @@ -208,8 +192,6 @@ export const createUseQuery = ( [ ts.factory.createTypeReferenceNode(TData), ts.factory.createTypeReferenceNode(TError), - ts.factory.createTypeReferenceNode(TData), - queryKeyGenericType, ] ), ts.factory.createUnionTypeNode([ @@ -234,8 +216,6 @@ export const createUseQuery = ( [ ts.factory.createTypeReferenceNode(TData), ts.factory.createTypeReferenceNode(TError), - ts.factory.createTypeReferenceNode(TData), - queryKeyGenericType, ], [ ts.factory.createObjectLiteralExpression([ @@ -313,42 +293,7 @@ export const createUseQuery = ( ts.NodeFlags.Const ) ); + const hookWithJsDoc = addJSDocToNode(hookExport, node, deprecated, jsDoc); - const deprecatedString = deprecated ? "@deprecated" : ""; - - const jsDocString = [deprecatedString] - .concat( - jsDoc.map((comment) => { - if (typeof comment === "string") { - return comment; - } - if (Array.isArray(comment)) { - return comment - .map((c) => c.getText(node)) - .join("\n"); - } - return ""; - }) - ) - // remove empty lines - .filter(Boolean) - // trim - .map((comment) => comment.trim()) - // add * to each line - .map((comment) => `* ${comment}`) - // join lines - .join("\n") - // replace new lines with \n * - .replace(/\n/g, "\n * "); - - const hookWithJSDoc = jsDocString - ? ts.addSyntheticLeadingComment( - hookExport, - ts.SyntaxKind.MultiLineCommentTrivia, - `*\n ${jsDocString}\n `, - true, - ) - : hookExport; - - return [defaultApiResponse, returnTypeExport, queryKeyExport, hookWithJSDoc]; + return [defaultApiResponse, returnTypeExport, queryKeyExport, hookWithJsDoc]; }; diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 0000000..8dd7f73 --- /dev/null +++ b/src/util.ts @@ -0,0 +1,44 @@ +import ts from 'typescript'; + +export function addJSDocToNode( + node: T, + sourceFile: ts.SourceFile, + deprecated: boolean, + jsDoc: (string | ts.NodeArray | undefined)[] = [], +): T { + const deprecatedString = deprecated ? "@deprecated" : ""; + + const jsDocString = [deprecatedString] + .concat( + jsDoc.map((comment) => { + if (typeof comment === "string") { + return comment; + } + if (Array.isArray(comment)) { + return comment.map((c) => c.getText(sourceFile)).join("\n"); + } + return ""; + }) + ) + // remove empty lines + .filter(Boolean) + // trim + .map((comment) => comment.trim()) + // add * to each line + .map((comment) => `* ${comment}`) + // join lines + .join("\n") + // replace new lines with \n * + .replace(/\n/g, "\n * "); + + const nodeWithJSDoc = jsDocString + ? ts.addSyntheticLeadingComment( + node, + ts.SyntaxKind.MultiLineCommentTrivia, + `*\n ${jsDocString}\n `, + true + ) + : node; + + return nodeWithJSDoc; +} \ No newline at end of file From 7666938b0a8c69dbf52018134a2cdb6e58d1c9cc Mon Sep 17 00:00:00 2001 From: Landon Gavin Date: Wed, 3 Jan 2024 00:11:39 -0500 Subject: [PATCH 4/4] Remove unneeded as expression for TQuery --- src/createUseQuery.ts | 55 ++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/src/createUseQuery.ts b/src/createUseQuery.ts index d7d137e..3c06227 100644 --- a/src/createUseQuery.ts +++ b/src/createUseQuery.ts @@ -1,6 +1,6 @@ import ts from "typescript"; import { capitalizeFirstLetter } from "./common"; -import { addJSDocToNode } from './util'; +import { addJSDocToNode } from "./util"; export const createUseQuery = ( node: ts.SourceFile, @@ -221,37 +221,34 @@ export const createUseQuery = ( ts.factory.createObjectLiteralExpression([ ts.factory.createPropertyAssignment( ts.factory.createIdentifier("queryKey"), - ts.factory.createAsExpression( - ts.factory.createArrayLiteralExpression( - [ - ts.factory.createIdentifier(queryKey), - ts.factory.createSpreadElement( - ts.factory.createParenthesizedExpression( - ts.factory.createBinaryExpression( - ts.factory.createIdentifier("queryKey"), - ts.factory.createToken( - ts.SyntaxKind.QuestionQuestionToken - ), - method.parameters.length - ? ts.factory.createArrayLiteralExpression([ - ts.factory.createObjectLiteralExpression( - method.parameters.map((param) => - ts.factory.createShorthandPropertyAssignment( - ts.factory.createIdentifier( - param.name.getText(node) - ) + ts.factory.createArrayLiteralExpression( + [ + ts.factory.createIdentifier(queryKey), + ts.factory.createSpreadElement( + ts.factory.createParenthesizedExpression( + ts.factory.createBinaryExpression( + ts.factory.createIdentifier("queryKey"), + ts.factory.createToken( + ts.SyntaxKind.QuestionQuestionToken + ), + method.parameters.length + ? ts.factory.createArrayLiteralExpression([ + ts.factory.createObjectLiteralExpression( + method.parameters.map((param) => + ts.factory.createShorthandPropertyAssignment( + ts.factory.createIdentifier( + param.name.getText(node) ) ) - ), - ]) - : ts.factory.createArrayLiteralExpression([]) - ) + ) + ), + ]) + : ts.factory.createArrayLiteralExpression([]) ) - ), - ], - false - ), - queryKeyGenericType + ) + ), + ], + false ) ), ts.factory.createPropertyAssignment(