chore(oas): [19/n] improve oas schemas (#9317)
- Improve OAS schemas [19/n] - docs-generator: combine `Date | string` type to just `Date`.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import ts from "typescript"
|
||||
|
||||
type ConstructorParams = {
|
||||
checker: ts.TypeChecker
|
||||
}
|
||||
|
||||
export default class TypesHelper {
|
||||
private checker: ts.TypeChecker
|
||||
|
||||
constructor({ checker }: ConstructorParams) {
|
||||
this.checker = checker
|
||||
}
|
||||
|
||||
areTypesEqual(type1: ts.Type, type2: ts.Type): boolean {
|
||||
return "id" in type1 && "id" in type2 && type1.id === type2.id
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the name of a type. This is useful when retrieving allowed/disallowed
|
||||
* properties in an Omit/Pick type.
|
||||
*
|
||||
* @param itemType - The type to retrieve its name.
|
||||
* @returns The type's name.
|
||||
*/
|
||||
getTypeName(itemType: ts.Type): string {
|
||||
if (itemType.symbol || itemType.aliasSymbol) {
|
||||
return (itemType.aliasSymbol || itemType.symbol).name
|
||||
}
|
||||
|
||||
if (itemType.isLiteral()) {
|
||||
return itemType.value.toString()
|
||||
}
|
||||
|
||||
return this.checker.typeToString(itemType)
|
||||
}
|
||||
|
||||
cleanUpTypes(types: ts.Type[]): ts.Type[] {
|
||||
let cleanedUpTypes = this.removeStringRegExpTypeOverlaps(types)
|
||||
|
||||
cleanedUpTypes = this.joinDateAndString(cleanedUpTypes)
|
||||
|
||||
return cleanedUpTypes
|
||||
}
|
||||
|
||||
private removeStringRegExpTypeOverlaps(types: ts.Type[]): ts.Type[] {
|
||||
return types.filter((itemType) => {
|
||||
// remove overlapping string / regexp types
|
||||
if (this.checker.typeToString(itemType) === "RegExp") {
|
||||
const hasString = types.some((t) => {
|
||||
return (
|
||||
t.flags === ts.TypeFlags.String ||
|
||||
t.flags === ts.TypeFlags.StringLiteral
|
||||
)
|
||||
})
|
||||
return !hasString
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
private joinDateAndString(types: ts.Type[]): ts.Type[] {
|
||||
if (types.length !== 2) {
|
||||
return types
|
||||
}
|
||||
|
||||
let dateType: ts.Type | undefined
|
||||
let hasStringType = false
|
||||
|
||||
types.forEach((tsType) => {
|
||||
if (
|
||||
tsType.flags === ts.TypeFlags.String ||
|
||||
tsType.flags === ts.TypeFlags.StringLiteral
|
||||
) {
|
||||
hasStringType = true
|
||||
} else if (this.getTypeName(tsType) === "Date") {
|
||||
dateType = tsType
|
||||
}
|
||||
})
|
||||
|
||||
return dateType && hasStringType ? [dateType] : types
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import FunctionKindGenerator, {
|
||||
VariableNode,
|
||||
} from "./function.js"
|
||||
import { API_ROUTE_PARAM_REGEX } from "../../constants.js"
|
||||
import TypesHelper from "../helpers/types.js"
|
||||
import {
|
||||
isLevelExceeded,
|
||||
maybeIncrementLevel,
|
||||
@@ -93,6 +94,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
protected oasExamplesGenerator: OasExamplesGenerator
|
||||
protected oasSchemaHelper: OasSchemaHelper
|
||||
protected schemaFactory: SchemaFactory
|
||||
protected typesHelper: TypesHelper
|
||||
|
||||
constructor(options: GeneratorOptions) {
|
||||
super(options)
|
||||
@@ -103,6 +105,9 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
this.tags = new Map()
|
||||
this.oasSchemaHelper = new OasSchemaHelper()
|
||||
this.schemaFactory = new SchemaFactory()
|
||||
this.typesHelper = new TypesHelper({
|
||||
checker: this.checker,
|
||||
})
|
||||
this.init()
|
||||
|
||||
this.generatorEventManager.listen(
|
||||
@@ -1397,18 +1402,18 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
const oneOfItems = this.removeStringRegExpTypeOverlaps(
|
||||
(itemType as ts.UnionType).types
|
||||
).map((unionType) =>
|
||||
this.typeToSchema({
|
||||
itemType: unionType,
|
||||
level: maybeIncrementLevel(level, "oneOf"),
|
||||
title,
|
||||
descriptionOptions,
|
||||
saveSchema,
|
||||
...rest,
|
||||
})
|
||||
)
|
||||
const oneOfItems = this.typesHelper
|
||||
.cleanUpTypes((itemType as ts.UnionType).types)
|
||||
.map((unionType) =>
|
||||
this.typeToSchema({
|
||||
itemType: unionType,
|
||||
level: maybeIncrementLevel(level, "oneOf"),
|
||||
title,
|
||||
descriptionOptions,
|
||||
saveSchema,
|
||||
...rest,
|
||||
})
|
||||
)
|
||||
|
||||
if (oneOfItems.length === 1) {
|
||||
return oneOfItems[0]
|
||||
@@ -1418,18 +1423,18 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
oneOf: oneOfItems,
|
||||
}
|
||||
case itemType.isIntersection():
|
||||
const allOfItems = this.removeStringRegExpTypeOverlaps(
|
||||
(itemType as ts.IntersectionType).types
|
||||
).map((intersectionType) => {
|
||||
return this.typeToSchema({
|
||||
itemType: intersectionType,
|
||||
level: maybeIncrementLevel(level, "allOf"),
|
||||
title,
|
||||
descriptionOptions,
|
||||
saveSchema,
|
||||
...rest,
|
||||
const allOfItems = this.typesHelper
|
||||
.cleanUpTypes((itemType as ts.IntersectionType).types)
|
||||
.map((intersectionType) => {
|
||||
return this.typeToSchema({
|
||||
itemType: intersectionType,
|
||||
level: maybeIncrementLevel(level, "allOf"),
|
||||
title,
|
||||
descriptionOptions,
|
||||
saveSchema,
|
||||
...rest,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
if (allOfItems.length === 1) {
|
||||
return allOfItems[0]
|
||||
@@ -1448,9 +1453,9 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
}
|
||||
const pickedProperties = pickTypeArgs[1].isUnion()
|
||||
? pickTypeArgs[1].types.map((unionType) =>
|
||||
this.getTypeName(unionType)
|
||||
this.typesHelper.getTypeName(unionType)
|
||||
)
|
||||
: [this.getTypeName(pickTypeArgs[1])]
|
||||
: [this.typesHelper.getTypeName(pickTypeArgs[1])]
|
||||
return this.typeToSchema({
|
||||
itemType: pickTypeArgs[0],
|
||||
title,
|
||||
@@ -1470,9 +1475,9 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
}
|
||||
const omitProperties = omitTypeArgs[1].isUnion()
|
||||
? omitTypeArgs[1].types.map((unionType) =>
|
||||
this.getTypeName(unionType)
|
||||
this.typesHelper.getTypeName(unionType)
|
||||
)
|
||||
: [this.getTypeName(omitTypeArgs[1])]
|
||||
: [this.typesHelper.getTypeName(omitTypeArgs[1])]
|
||||
return this.typeToSchema({
|
||||
itemType: omitTypeArgs[0],
|
||||
title,
|
||||
@@ -1581,7 +1586,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
const arrHasParentType =
|
||||
rest.context !== "query" &&
|
||||
this.checker.isArrayType(propertyType) &&
|
||||
this.areTypesEqual(
|
||||
this.typesHelper.areTypesEqual(
|
||||
itemType,
|
||||
this.checker.getTypeArguments(
|
||||
propertyType as ts.TypeReference
|
||||
@@ -1589,7 +1594,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
)
|
||||
const isParentType =
|
||||
rest.context !== "query" &&
|
||||
this.areTypesEqual(itemType, propertyType)
|
||||
this.typesHelper.areTypesEqual(itemType, propertyType)
|
||||
|
||||
if (isParentType && objSchema["x-schemaName"]) {
|
||||
properties[property.name] = {
|
||||
@@ -1792,25 +1797,6 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the name of a type. This is useful when retrieving allowed/disallowed
|
||||
* properties in an Omit/Pick type.
|
||||
*
|
||||
* @param itemType - The type to retrieve its name.
|
||||
* @returns The type's name.
|
||||
*/
|
||||
getTypeName(itemType: ts.Type): string {
|
||||
if (itemType.symbol || itemType.aliasSymbol) {
|
||||
return (itemType.aliasSymbol || itemType.symbol).name
|
||||
}
|
||||
|
||||
if (itemType.isLiteral()) {
|
||||
return itemType.value.toString()
|
||||
}
|
||||
|
||||
return this.checker.typeToString(itemType)
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the {@link tags} property.
|
||||
*/
|
||||
@@ -2299,27 +2285,6 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
fnText.includes(responseType)
|
||||
)
|
||||
}
|
||||
|
||||
private removeStringRegExpTypeOverlaps(types: ts.Type[]): ts.Type[] {
|
||||
return types.filter((itemType) => {
|
||||
// remove overlapping string / regexp types
|
||||
if (this.checker.typeToString(itemType) === "RegExp") {
|
||||
const hasString = types.some((t) => {
|
||||
return (
|
||||
t.flags === ts.TypeFlags.String ||
|
||||
t.flags === ts.TypeFlags.StringLiteral
|
||||
)
|
||||
})
|
||||
return !hasString
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
private areTypesEqual(type1: ts.Type, type2: ts.Type): boolean {
|
||||
return "id" in type1 && "id" in type2 && type1.id === type2.id
|
||||
}
|
||||
}
|
||||
|
||||
export default OasKindGenerator
|
||||
|
||||
Reference in New Issue
Block a user