fix(docblock-generator): fix how type names created from Zod objects are inferred (#7292)
This commit is contained in:
1 parent
e5904f8c75
commit
e960f10aba
4 files changed
+98
-22
No files matched your search
@@ -8,7 +8,8 @@ import getOasOutputBasePath from "../../utils/get-oas-output-base-path.js"
|
|||||||
import { parse } from "yaml"
|
import { parse } from "yaml"
|
||||||
import formatOas from "../../utils/format-oas.js"
|
import formatOas from "../../utils/format-oas.js"
|
||||||
import pluralize from "pluralize"
|
import pluralize from "pluralize"
|
||||||
import { wordsToPascal } from "utils"
|
import { capitalize, wordsToPascal } from "utils"
|
||||||
|
import { OasArea } from "../kinds/oas.js"
|
||||||
|
|
||||||
export type ParsedSchema = {
|
export type ParsedSchema = {
|
||||||
schema: OpenApiSchema
|
schema: OpenApiSchema
|
||||||
@@ -200,7 +201,10 @@ class OasSchemaHelper {
|
|||||||
* @returns The normalized name.
|
* @returns The normalized name.
|
||||||
*/
|
*/
|
||||||
normalizeSchemaName(name: string): string {
|
normalizeSchemaName(name: string): string {
|
||||||
return name.replace("DTO", "").replace(this.schemaRefPrefix, "")
|
return name
|
||||||
|
.replace("DTO", "")
|
||||||
|
.replace(this.schemaRefPrefix, "")
|
||||||
|
.replace(/Type$/, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -256,10 +260,11 @@ class OasSchemaHelper {
|
|||||||
* associated with a tag.
|
* associated with a tag.
|
||||||
*
|
*
|
||||||
* @param tagName - The name of the tag.
|
* @param tagName - The name of the tag.
|
||||||
* @returns The possible name of the associated schema.
|
* @returns The possible names of the associated schema.
|
||||||
*/
|
*/
|
||||||
tagNameToSchemaName(tagName: string): string {
|
tagNameToSchemaName(tagName: string, area: OasArea): string[] {
|
||||||
return wordsToPascal(pluralize.singular(tagName))
|
const mainSchemaName = wordsToPascal(pluralize.singular(tagName))
|
||||||
|
return [mainSchemaName, `${capitalize(area)}Create${mainSchemaName}`]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,13 @@ import OasExamplesGenerator from "../examples/oas.js"
|
|||||||
import pluralize from "pluralize"
|
import pluralize from "pluralize"
|
||||||
import getOasOutputBasePath from "../../utils/get-oas-output-base-path.js"
|
import getOasOutputBasePath from "../../utils/get-oas-output-base-path.js"
|
||||||
import parseOas, { ExistingOas } from "../../utils/parse-oas.js"
|
import parseOas, { ExistingOas } from "../../utils/parse-oas.js"
|
||||||
import OasSchemaHelper from "../helpers/oas-schema.js"
|
import OasSchemaHelper, { ParsedSchema } from "../helpers/oas-schema.js"
|
||||||
import formatOas from "../../utils/format-oas.js"
|
import formatOas from "../../utils/format-oas.js"
|
||||||
import { DEFAULT_OAS_RESPONSES } from "../../constants.js"
|
import { DEFAULT_OAS_RESPONSES } from "../../constants.js"
|
||||||
import { capitalize, kebabToTitle, wordsToKebab } from "utils"
|
import { capitalize, kebabToTitle, wordsToKebab } from "utils"
|
||||||
import SchemaFactory from "../helpers/schema-factory.js"
|
import SchemaFactory from "../helpers/schema-factory.js"
|
||||||
|
import isZodObject from "../../utils/is-zod-object.js"
|
||||||
|
import getCorrectZodTypeName from "../../utils/get-correct-zod-type-name.js"
|
||||||
|
|
||||||
export const API_ROUTE_PARAM_REGEX = /\[(.+?)\]/g
|
export const API_ROUTE_PARAM_REGEX = /\[(.+?)\]/g
|
||||||
const RES_STATUS_REGEX = /^res[\s\S]*\.status\((\d+)\)/
|
const RES_STATUS_REGEX = /^res[\s\S]*\.status\((\d+)\)/
|
||||||
@@ -1094,11 +1096,16 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
const requestTypeArguments = this.checker.getTypeArguments(requestType)
|
const requestTypeArguments = this.checker.getTypeArguments(requestType)
|
||||||
|
|
||||||
if (requestTypeArguments.length === 1) {
|
if (requestTypeArguments.length === 1) {
|
||||||
|
const zodObjectTypeName = getCorrectZodTypeName({
|
||||||
|
typeReferenceNode: node.parameters[0].type,
|
||||||
|
itemType: requestTypeArguments[0],
|
||||||
|
})
|
||||||
requestSchema = this.typeToSchema({
|
requestSchema = this.typeToSchema({
|
||||||
itemType: requestTypeArguments[0],
|
itemType: requestTypeArguments[0],
|
||||||
descriptionOptions: {
|
descriptionOptions: {
|
||||||
parentName: tagName,
|
parentName: tagName,
|
||||||
},
|
},
|
||||||
|
zodObjectTypeName: zodObjectTypeName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1167,6 +1174,10 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
descriptionOptions: {
|
descriptionOptions: {
|
||||||
parentName: tagName,
|
parentName: tagName,
|
||||||
},
|
},
|
||||||
|
zodObjectTypeName: getCorrectZodTypeName({
|
||||||
|
typeReferenceNode: node.parameters[1].type,
|
||||||
|
itemType: responseTypeArguments[0],
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1187,6 +1198,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
descriptionOptions,
|
descriptionOptions,
|
||||||
allowedChildren,
|
allowedChildren,
|
||||||
disallowedChildren,
|
disallowedChildren,
|
||||||
|
zodObjectTypeName,
|
||||||
}: {
|
}: {
|
||||||
/**
|
/**
|
||||||
* The TypeScript type.
|
* The TypeScript type.
|
||||||
@@ -1214,6 +1226,12 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
* only children not included in this array are added to the schema.
|
* only children not included in this array are added to the schema.
|
||||||
*/
|
*/
|
||||||
disallowedChildren?: string[]
|
disallowedChildren?: string[]
|
||||||
|
/**
|
||||||
|
* By default, the type name is generated from itemType, which
|
||||||
|
* doesn't work for types created by Zod. This allows to correct the
|
||||||
|
* generated type name.
|
||||||
|
*/
|
||||||
|
zodObjectTypeName?: string
|
||||||
}): OpenApiSchema {
|
}): OpenApiSchema {
|
||||||
if (level > this.MAX_LEVEL) {
|
if (level > this.MAX_LEVEL) {
|
||||||
return {}
|
return {}
|
||||||
@@ -1227,7 +1245,8 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
: title
|
: title
|
||||||
? this.getSchemaDescription({ typeStr: title, nodeType: itemType })
|
? this.getSchemaDescription({ typeStr: title, nodeType: itemType })
|
||||||
: this.defaultSummary
|
: this.defaultSummary
|
||||||
const typeAsString = this.checker.typeToString(itemType)
|
const typeAsString =
|
||||||
|
zodObjectTypeName || this.checker.typeToString(itemType)
|
||||||
|
|
||||||
const schemaFromFactory = this.schemaFactory.tryGetSchema(
|
const schemaFromFactory = this.schemaFactory.tryGetSchema(
|
||||||
itemType.symbol?.getName() ||
|
itemType.symbol?.getName() ||
|
||||||
@@ -1433,6 +1452,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
(itemType as ts.Type).flags === ts.TypeFlags.Object:
|
(itemType as ts.Type).flags === ts.TypeFlags.Object:
|
||||||
const properties: Record<string, OpenApiSchema> = {}
|
const properties: Record<string, OpenApiSchema> = {}
|
||||||
const requiredProperties: string[] = []
|
const requiredProperties: string[] = []
|
||||||
|
|
||||||
if (level + 1 <= this.MAX_LEVEL) {
|
if (level + 1 <= this.MAX_LEVEL) {
|
||||||
itemType.getProperties().forEach((property) => {
|
itemType.getProperties().forEach((property) => {
|
||||||
if (
|
if (
|
||||||
@@ -1461,7 +1481,9 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
type: "object",
|
type: "object",
|
||||||
description,
|
description,
|
||||||
"x-schemaName":
|
"x-schemaName":
|
||||||
itemType.isClassOrInterface() || itemType.isTypeParameter()
|
itemType.isClassOrInterface() ||
|
||||||
|
itemType.isTypeParameter() ||
|
||||||
|
(isZodObject(itemType) && zodObjectTypeName)
|
||||||
? this.oasSchemaHelper.normalizeSchemaName(typeAsString)
|
? this.oasSchemaHelper.normalizeSchemaName(typeAsString)
|
||||||
: undefined,
|
: undefined,
|
||||||
required:
|
required:
|
||||||
@@ -1871,32 +1893,43 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
const areaYaml = parse(
|
const areaYaml = parse(
|
||||||
readFileSync(areaYamlPath, "utf-8")
|
readFileSync(areaYamlPath, "utf-8")
|
||||||
) as OpenApiDocument
|
) as OpenApiDocument
|
||||||
let addedTags = false
|
let modifiedTags = false
|
||||||
|
|
||||||
areaYaml.tags = [...(areaYaml.tags || [])]
|
areaYaml.tags = [...(areaYaml.tags || [])]
|
||||||
|
|
||||||
this.tags.get(area)?.forEach((tag) => {
|
this.tags.get(area)?.forEach((tag) => {
|
||||||
const existingTag = areaYaml.tags!.find((baseTag) => baseTag.name === tag)
|
const existingTag = areaYaml.tags!.find((baseTag) => baseTag.name === tag)
|
||||||
|
// try to retrieve associated schema
|
||||||
|
let schema: ParsedSchema | undefined
|
||||||
|
this.oasSchemaHelper.tagNameToSchemaName(tag, area).some((schemaName) => {
|
||||||
|
schema = this.oasSchemaHelper.getSchemaByName(schemaName)
|
||||||
|
|
||||||
|
if (schema) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const associatedSchema = schema?.schema?.["x-schemaName"]
|
||||||
|
? {
|
||||||
|
$ref: this.oasSchemaHelper.constructSchemaReference(
|
||||||
|
schema.schema["x-schemaName"]
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
if (!existingTag) {
|
if (!existingTag) {
|
||||||
// try to retrieve associated schema
|
|
||||||
const schema = this.oasSchemaHelper.getSchemaByName(
|
|
||||||
this.oasSchemaHelper.tagNameToSchemaName(tag)
|
|
||||||
)
|
|
||||||
areaYaml.tags!.push({
|
areaYaml.tags!.push({
|
||||||
name: tag,
|
name: tag,
|
||||||
"x-associatedSchema": schema?.schema?.["x-schemaName"]
|
"x-associatedSchema": associatedSchema,
|
||||||
? {
|
|
||||||
$ref: this.oasSchemaHelper.constructSchemaReference(
|
|
||||||
schema.schema["x-schemaName"]
|
|
||||||
),
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
})
|
})
|
||||||
addedTags = true
|
modifiedTags = true
|
||||||
|
} else if (
|
||||||
|
existingTag["x-associatedSchema"]?.$ref !== associatedSchema?.$ref
|
||||||
|
) {
|
||||||
|
existingTag["x-associatedSchema"] = associatedSchema
|
||||||
|
modifiedTags = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (addedTags) {
|
if (modifiedTags) {
|
||||||
// sort alphabetically
|
// sort alphabetically
|
||||||
areaYaml.tags.sort((tagA, tagB) => {
|
areaYaml.tags.sort((tagA, tagB) => {
|
||||||
return tagA.name.localeCompare(tagB.name)
|
return tagA.name.localeCompare(tagB.name)
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Due to some types using Zod in their declaration
|
||||||
|
// The type name isn't picked properly by typescript
|
||||||
|
|
||||||
|
import ts from "typescript"
|
||||||
|
import isZodObject from "./is-zod-object.js"
|
||||||
|
|
||||||
|
// this ensures that the correct type name is used.
|
||||||
|
export default function getCorrectZodTypeName({
|
||||||
|
typeReferenceNode,
|
||||||
|
itemType,
|
||||||
|
}: {
|
||||||
|
typeReferenceNode: ts.TypeReferenceNode
|
||||||
|
itemType: ts.Type
|
||||||
|
}): string | undefined {
|
||||||
|
if (!isZodObject(itemType)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeReferenceNode.typeArguments?.[0] &&
|
||||||
|
"typeName" in typeReferenceNode.typeArguments[0]
|
||||||
|
? (typeReferenceNode.typeArguments?.[0].typeName as ts.Identifier).getText()
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import ts from "typescript"
|
||||||
|
|
||||||
|
export default function isZodObject(itemType: ts.Type): boolean {
|
||||||
|
if (!itemType.symbol?.declarations?.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = itemType.symbol.declarations[0].parent
|
||||||
|
|
||||||
|
if (!("typeName" in parent)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return (parent.typeName as ts.Identifier).getText().includes("ZodObject")
|
||||||
|
}
|
||||||
Reference in new issue
Block a user