docs-util: fixes for circular references, PAK in examples, namespaces (#9091)
- Fix circular references in generated OAS comments by cloning objects - Add publishable API key in the header of store cURL examples + change the admin authentication header to use bearer tokens. - Add plugin to generate namespaces from the `@customNamespace` tag in TSDocs, this was available before but was removed. - Other fixes related to re-using schemas, overwritten descriptions, and smaller fixes
This commit is contained in:
1 parent
05c88268d3
commit
cb79a5dbff
10 files changed
+448
-42
No files matched your search
@@ -216,11 +216,15 @@ class OasExamplesGenerator {
|
|||||||
]
|
]
|
||||||
|
|
||||||
if (isAdminAuthenticated) {
|
if (isAdminAuthenticated) {
|
||||||
exampleArr.push(`-H 'x-medusa-access-token: {api_token}'`)
|
exampleArr.push(`-H 'Authorization: Bearer {access_token}'`)
|
||||||
} else if (isStoreAuthenticated) {
|
} else if (isStoreAuthenticated) {
|
||||||
exampleArr.push(`-H 'Authorization: Bearer {access_token}'`)
|
exampleArr.push(`-H 'Authorization: Bearer {access_token}'`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (path.startsWith("/store")) {
|
||||||
|
exampleArr.push(`-H 'x-publishable-api-key: {your_publishable_api_key}'`)
|
||||||
|
}
|
||||||
|
|
||||||
if (requestSchema) {
|
if (requestSchema) {
|
||||||
const requestData = this.getSchemaRequiredData(requestSchema)
|
const requestData = this.getSchemaRequiredData(requestSchema)
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,9 @@ class OasSchemaHelper {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.schemas.set(schema["x-schemaName"], schema)
|
if (this.canAddSchema(schema)) {
|
||||||
|
this.schemas.set(schema["x-schemaName"], schema)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
$ref: this.constructSchemaReference(schema["x-schemaName"]),
|
$ref: this.constructSchemaReference(schema["x-schemaName"]),
|
||||||
@@ -181,6 +183,36 @@ class OasSchemaHelper {
|
|||||||
return clonedSchema
|
return clonedSchema
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isSchemaEmpty(schema: OpenApiSchema): boolean {
|
||||||
|
switch (schema.type) {
|
||||||
|
case "object":
|
||||||
|
return (
|
||||||
|
schema.properties === undefined ||
|
||||||
|
Object.keys(schema.properties).length === 0
|
||||||
|
)
|
||||||
|
case "array":
|
||||||
|
return (
|
||||||
|
!this.isRefObject(schema.items) && this.isSchemaEmpty(schema.items)
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canAddSchema(schema: OpenApiSchema): boolean {
|
||||||
|
if (!schema["x-schemaName"]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingSchema = this.schemas.get(schema["x-schemaName"])
|
||||||
|
|
||||||
|
if (!existingSchema) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.isSchemaEmpty(existingSchema) && !this.isSchemaEmpty(schema)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the expected file name of the schema.
|
* Retrieve the expected file name of the schema.
|
||||||
*
|
*
|
||||||
@@ -212,7 +244,7 @@ class OasSchemaHelper {
|
|||||||
// check if it already exists in the schemas map
|
// check if it already exists in the schemas map
|
||||||
if (this.schemas.has(schemaName)) {
|
if (this.schemas.has(schemaName)) {
|
||||||
return {
|
return {
|
||||||
schema: this.schemas.get(schemaName)!,
|
schema: JSON.parse(JSON.stringify(this.schemas.get(schemaName)!)),
|
||||||
schemaPrefix: `@schema ${schemaName}`,
|
schemaPrefix: `@schema ${schemaName}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,7 +304,7 @@ class OasSchemaHelper {
|
|||||||
return name
|
return name
|
||||||
.replace("DTO", "")
|
.replace("DTO", "")
|
||||||
.replace(this.schemaRefPrefix, "")
|
.replace(this.schemaRefPrefix, "")
|
||||||
.replace(/(?<!AdminProduct)Type$/, "")
|
.replace(/(?<!(AdminProduct|CreateProduct))Type$/, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class SchemaFactory {
|
|||||||
schema = Object.assign(schema, {
|
schema = Object.assign(schema, {
|
||||||
...additionalData,
|
...additionalData,
|
||||||
// keep the description
|
// keep the description
|
||||||
description: schema.description || additionalData.description
|
description: schema.description || additionalData.description,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -463,6 +463,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
node,
|
node,
|
||||||
tagName,
|
tagName,
|
||||||
methodName,
|
methodName,
|
||||||
|
forUpdate: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
// update query parameters
|
// update query parameters
|
||||||
@@ -511,6 +512,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
const newResponseSchema = this.getResponseSchema({
|
const newResponseSchema = this.getResponseSchema({
|
||||||
node,
|
node,
|
||||||
tagName,
|
tagName,
|
||||||
|
forUpdate: true,
|
||||||
})
|
})
|
||||||
let updatedResponseSchema: OpenApiSchema | undefined
|
let updatedResponseSchema: OpenApiSchema | undefined
|
||||||
|
|
||||||
@@ -968,6 +970,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
node,
|
node,
|
||||||
tagName,
|
tagName,
|
||||||
methodName,
|
methodName,
|
||||||
|
forUpdate = false,
|
||||||
}: {
|
}: {
|
||||||
/**
|
/**
|
||||||
* The node to retrieve its request parameters.
|
* The node to retrieve its request parameters.
|
||||||
@@ -981,6 +984,10 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
* The tag's name.
|
* The tag's name.
|
||||||
*/
|
*/
|
||||||
tagName?: string
|
tagName?: string
|
||||||
|
/**
|
||||||
|
* Whether the request parameters are retrieved for update purposes only.
|
||||||
|
*/
|
||||||
|
forUpdate?: boolean
|
||||||
}): {
|
}): {
|
||||||
/**
|
/**
|
||||||
* The query parameters.
|
* The query parameters.
|
||||||
@@ -1041,6 +1048,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
title: propertyName,
|
title: propertyName,
|
||||||
descriptionOptions,
|
descriptionOptions,
|
||||||
context: "request",
|
context: "request",
|
||||||
|
saveSchema: !forUpdate,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -1063,6 +1071,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
},
|
},
|
||||||
zodObjectTypeName: zodObjectTypeName,
|
zodObjectTypeName: zodObjectTypeName,
|
||||||
context: "request",
|
context: "request",
|
||||||
|
saveSchema: !forUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
// If function is a GET function, add the type parameter to the
|
// If function is a GET function, add the type parameter to the
|
||||||
@@ -1135,6 +1144,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
getResponseSchema({
|
getResponseSchema({
|
||||||
node,
|
node,
|
||||||
tagName,
|
tagName,
|
||||||
|
forUpdate = false,
|
||||||
}: {
|
}: {
|
||||||
/**
|
/**
|
||||||
* The node to retrieve its response schema.
|
* The node to retrieve its response schema.
|
||||||
@@ -1144,6 +1154,10 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
* The tag's name.
|
* The tag's name.
|
||||||
*/
|
*/
|
||||||
tagName?: string
|
tagName?: string
|
||||||
|
/**
|
||||||
|
* Whether the response schema is retrieved for update only.
|
||||||
|
*/
|
||||||
|
forUpdate?: boolean
|
||||||
}): OpenApiSchema | undefined {
|
}): OpenApiSchema | undefined {
|
||||||
let responseSchema: OpenApiSchema | undefined
|
let responseSchema: OpenApiSchema | undefined
|
||||||
|
|
||||||
@@ -1170,6 +1184,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
itemType: responseTypeArguments[0],
|
itemType: responseTypeArguments[0],
|
||||||
}),
|
}),
|
||||||
context: "response",
|
context: "response",
|
||||||
|
saveSchema: !forUpdate,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1191,6 +1206,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
allowedChildren,
|
allowedChildren,
|
||||||
disallowedChildren,
|
disallowedChildren,
|
||||||
zodObjectTypeName,
|
zodObjectTypeName,
|
||||||
|
saveSchema = true,
|
||||||
...rest
|
...rest
|
||||||
}: {
|
}: {
|
||||||
/**
|
/**
|
||||||
@@ -1229,6 +1245,10 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
* Whether the type is in a request / response
|
* Whether the type is in a request / response
|
||||||
*/
|
*/
|
||||||
context?: "request" | "response"
|
context?: "request" | "response"
|
||||||
|
/**
|
||||||
|
* Whether to save object schemas. Useful when only getting schemas to update.
|
||||||
|
*/
|
||||||
|
saveSchema?: boolean
|
||||||
}): OpenApiSchema {
|
}): OpenApiSchema {
|
||||||
if (level > this.MAX_LEVEL) {
|
if (level > this.MAX_LEVEL) {
|
||||||
return {}
|
return {}
|
||||||
@@ -1354,6 +1374,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
parentName: title || descriptionOptions?.parentName,
|
parentName: title || descriptionOptions?.parentName,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
saveSchema,
|
||||||
...rest,
|
...rest,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@@ -1384,6 +1405,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
level,
|
level,
|
||||||
title,
|
title,
|
||||||
descriptionOptions,
|
descriptionOptions,
|
||||||
|
saveSchema,
|
||||||
...rest,
|
...rest,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -1407,6 +1429,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
level,
|
level,
|
||||||
title,
|
title,
|
||||||
descriptionOptions,
|
descriptionOptions,
|
||||||
|
saveSchema,
|
||||||
...rest,
|
...rest,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -1437,6 +1460,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
level,
|
level,
|
||||||
descriptionOptions,
|
descriptionOptions,
|
||||||
allowedChildren: pickedProperties,
|
allowedChildren: pickedProperties,
|
||||||
|
saveSchema,
|
||||||
...rest,
|
...rest,
|
||||||
})
|
})
|
||||||
case typeAsString.startsWith("Omit"):
|
case typeAsString.startsWith("Omit"):
|
||||||
@@ -1458,6 +1482,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
level,
|
level,
|
||||||
descriptionOptions,
|
descriptionOptions,
|
||||||
disallowedChildren: omitProperties,
|
disallowedChildren: omitProperties,
|
||||||
|
saveSchema,
|
||||||
...rest,
|
...rest,
|
||||||
})
|
})
|
||||||
case typeAsString.startsWith("Partial"):
|
case typeAsString.startsWith("Partial"):
|
||||||
@@ -1475,6 +1500,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
descriptionOptions,
|
descriptionOptions,
|
||||||
disallowedChildren,
|
disallowedChildren,
|
||||||
allowedChildren,
|
allowedChildren,
|
||||||
|
saveSchema,
|
||||||
...rest,
|
...rest,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1485,13 +1511,29 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
case itemType.isClassOrInterface() ||
|
case itemType.isClassOrInterface() ||
|
||||||
itemType.isTypeParameter() ||
|
itemType.isTypeParameter() ||
|
||||||
(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 | OpenAPIV3.ReferenceObject
|
||||||
|
> = {}
|
||||||
const requiredProperties: string[] = []
|
const requiredProperties: string[] = []
|
||||||
|
|
||||||
const baseType = itemType.getBaseTypes()?.[0]
|
const baseType = itemType.getBaseTypes()?.[0]
|
||||||
const isDeleteResponse =
|
const isDeleteResponse =
|
||||||
baseType?.aliasSymbol?.getEscapedName() === "DeleteResponse"
|
baseType?.aliasSymbol?.getEscapedName() === "DeleteResponse"
|
||||||
|
|
||||||
|
const objSchema: OpenApiSchema = {
|
||||||
|
type: "object",
|
||||||
|
description,
|
||||||
|
"x-schemaName":
|
||||||
|
itemType.isClassOrInterface() ||
|
||||||
|
itemType.isTypeParameter() ||
|
||||||
|
(isZodObject(itemType) && zodObjectTypeName)
|
||||||
|
? this.oasSchemaHelper.normalizeSchemaName(typeAsString)
|
||||||
|
: undefined,
|
||||||
|
// this is changed later
|
||||||
|
required: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
if (level + 1 <= this.MAX_LEVEL) {
|
if (level + 1 <= this.MAX_LEVEL) {
|
||||||
itemType.getProperties().forEach((property) => {
|
itemType.getProperties().forEach((property) => {
|
||||||
if (
|
if (
|
||||||
@@ -1504,6 +1546,41 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
requiredProperties.push(property.name)
|
requiredProperties.push(property.name)
|
||||||
}
|
}
|
||||||
const propertyType = this.checker.getTypeOfSymbol(property)
|
const propertyType = this.checker.getTypeOfSymbol(property)
|
||||||
|
|
||||||
|
// if property's type is same as parent's property,
|
||||||
|
// create a reference to the parent
|
||||||
|
const arrHasParentType =
|
||||||
|
this.checker.isArrayType(propertyType) &&
|
||||||
|
this.areTypesEqual(
|
||||||
|
itemType,
|
||||||
|
this.checker.getTypeArguments(
|
||||||
|
propertyType as ts.TypeReference
|
||||||
|
)[0]
|
||||||
|
)
|
||||||
|
const isParentType = this.areTypesEqual(itemType, propertyType)
|
||||||
|
|
||||||
|
if (isParentType && objSchema["x-schemaName"]) {
|
||||||
|
properties[property.name] = {
|
||||||
|
$ref: this.oasSchemaHelper.constructSchemaReference(
|
||||||
|
objSchema["x-schemaName"]
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
} else if (arrHasParentType && objSchema["x-schemaName"]) {
|
||||||
|
properties[property.name] = {
|
||||||
|
type: "array",
|
||||||
|
description,
|
||||||
|
items: {
|
||||||
|
$ref: this.oasSchemaHelper.constructSchemaReference(
|
||||||
|
objSchema["x-schemaName"]
|
||||||
|
),
|
||||||
|
},
|
||||||
|
} as OpenAPIV3.ArraySchemaObject
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
properties[property.name] = this.typeToSchema({
|
properties[property.name] = this.typeToSchema({
|
||||||
itemType: propertyType,
|
itemType: propertyType,
|
||||||
level: level + 1,
|
level: level + 1,
|
||||||
@@ -1513,38 +1590,34 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
typeStr: property.name,
|
typeStr: property.name,
|
||||||
parentName: title || descriptionOptions?.parentName,
|
parentName: title || descriptionOptions?.parentName,
|
||||||
},
|
},
|
||||||
|
saveSchema,
|
||||||
...rest,
|
...rest,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (isDeleteResponse && property.name === "object") {
|
if (
|
||||||
|
isDeleteResponse &&
|
||||||
|
property.name === "object" &&
|
||||||
|
!this.oasSchemaHelper.isRefObject(properties[property.name])
|
||||||
|
) {
|
||||||
|
const schemaProperty = properties[property.name] as OpenApiSchema
|
||||||
// try to retrieve default from `DeleteResponse`'s type argument
|
// try to retrieve default from `DeleteResponse`'s type argument
|
||||||
const deleteTypeArg = baseType.aliasTypeArguments?.[0]
|
const deleteTypeArg = baseType.aliasTypeArguments?.[0]
|
||||||
properties[property.name].default =
|
schemaProperty.default =
|
||||||
deleteTypeArg && "value" in deleteTypeArg
|
deleteTypeArg && "value" in deleteTypeArg
|
||||||
? (deleteTypeArg.value as string)
|
? (deleteTypeArg.value as string)
|
||||||
: properties[property.name].default
|
: schemaProperty.default
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const objSchema: OpenApiSchema = {
|
|
||||||
type: "object",
|
|
||||||
description,
|
|
||||||
"x-schemaName":
|
|
||||||
itemType.isClassOrInterface() ||
|
|
||||||
itemType.isTypeParameter() ||
|
|
||||||
(isZodObject(itemType) && zodObjectTypeName)
|
|
||||||
? this.oasSchemaHelper.normalizeSchemaName(typeAsString)
|
|
||||||
: undefined,
|
|
||||||
required:
|
|
||||||
requiredProperties.length > 0 ? requiredProperties : undefined,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.values(properties).length) {
|
if (Object.values(properties).length) {
|
||||||
objSchema.properties = properties
|
objSchema.properties = properties
|
||||||
}
|
}
|
||||||
|
|
||||||
if (objSchema["x-schemaName"]) {
|
objSchema.required =
|
||||||
|
requiredProperties.length > 0 ? requiredProperties : undefined
|
||||||
|
|
||||||
|
if (saveSchema && objSchema["x-schemaName"]) {
|
||||||
// add object to schemas to be created
|
// add object to schemas to be created
|
||||||
// if necessary
|
// if necessary
|
||||||
this.oasSchemaHelper.namedSchemaToReference(objSchema)
|
this.oasSchemaHelper.namedSchemaToReference(objSchema)
|
||||||
@@ -1947,8 +2020,6 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
}) || oldSchemaObj.items
|
}) || oldSchemaObj.items
|
||||||
}
|
}
|
||||||
|
|
||||||
// update schema
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
oldSchemaObj!.description !== newSchemaObj?.description &&
|
oldSchemaObj!.description !== newSchemaObj?.description &&
|
||||||
oldSchemaObj!.description === SUMMARY_PLACEHOLDER
|
oldSchemaObj!.description === SUMMARY_PLACEHOLDER
|
||||||
@@ -2184,6 +2255,10 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
return true
|
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
|
export default OasKindGenerator
|
||||||
@@ -17,9 +17,9 @@ const customOptions: Record<string, Partial<TypeDocOptions>> = {
|
|||||||
name: "core-flows",
|
name: "core-flows",
|
||||||
plugin: ["typedoc-plugin-workflows"],
|
plugin: ["typedoc-plugin-workflows"],
|
||||||
enableWorkflowsPlugins: true,
|
enableWorkflowsPlugins: true,
|
||||||
enableNamespaceGenerator: true,
|
enablePathNamespaceGenerator: true,
|
||||||
// @ts-expect-error there's a typing issue in typedoc
|
// @ts-expect-error there's a typing issue in typedoc
|
||||||
generateNamespaces: getCoreFlowNamespaces(),
|
generatePathNamespaces: getCoreFlowNamespaces(),
|
||||||
}),
|
}),
|
||||||
"auth-provider": getOptions({
|
"auth-provider": getOptions({
|
||||||
entryPointPath: "packages/core/utils/src/auth/abstract-auth-provider.ts",
|
entryPointPath: "packages/core/utils/src/auth/abstract-auth-provider.ts",
|
||||||
@@ -35,6 +35,7 @@ const customOptions: Record<string, Partial<TypeDocOptions>> = {
|
|||||||
],
|
],
|
||||||
tsConfigName: "utils.json",
|
tsConfigName: "utils.json",
|
||||||
name: "dml",
|
name: "dml",
|
||||||
|
generateCustomNamespaces: true,
|
||||||
}),
|
}),
|
||||||
file: getOptions({
|
file: getOptions({
|
||||||
entryPointPath: "packages/core/utils/src/file/abstract-file-provider.ts",
|
entryPointPath: "packages/core/utils/src/file/abstract-file-provider.ts",
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ The following options are useful for linting:
|
|||||||
|
|
||||||
### Generate Namespace Plugin
|
### Generate Namespace Plugin
|
||||||
|
|
||||||
If the `generateNamespaces` option is enabled, Namespaces are created from reflections having the `@customNamespace` tag. It also attaches categories (using the `@category` tag) of the same reflection to its generated parent namespace.
|
If the `generatePathNamespaces` option is enabled, Namespaces are created from reflections having the `@customNamespace` tag. It also attaches categories (using the `@category` tag) of the same reflection to its generated parent namespace.
|
||||||
|
|
||||||
It also accepts the following options:
|
It also accepts the following options:
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,279 @@
|
|||||||
|
import {
|
||||||
|
Application,
|
||||||
|
Comment,
|
||||||
|
CommentDisplayPart,
|
||||||
|
CommentTag,
|
||||||
|
Context,
|
||||||
|
Converter,
|
||||||
|
DeclarationReflection,
|
||||||
|
ParameterType,
|
||||||
|
Reflection,
|
||||||
|
ReflectionCategory,
|
||||||
|
ReflectionKind,
|
||||||
|
} from "typedoc"
|
||||||
|
|
||||||
|
type PluginOptions = {
|
||||||
|
generatePathNamespaces: boolean
|
||||||
|
parentNamespace: string
|
||||||
|
namePrefix: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GenerateCustomNamespacePlugin {
|
||||||
|
private options?: PluginOptions
|
||||||
|
private app: Application
|
||||||
|
private parentNamespace?: DeclarationReflection
|
||||||
|
private currentNamespaceHeirarchy: DeclarationReflection[]
|
||||||
|
private currentContext?: Context
|
||||||
|
private scannedComments = false
|
||||||
|
|
||||||
|
constructor(app: Application) {
|
||||||
|
this.app = app
|
||||||
|
this.currentNamespaceHeirarchy = []
|
||||||
|
this.declareOptions()
|
||||||
|
|
||||||
|
this.app.converter.on(
|
||||||
|
Converter.EVENT_RESOLVE,
|
||||||
|
this.handleCreateDeclarationEvent.bind(this)
|
||||||
|
)
|
||||||
|
this.app.converter.on(
|
||||||
|
Converter.EVENT_CREATE_DECLARATION,
|
||||||
|
this.scanComments.bind(this)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
declareOptions() {
|
||||||
|
this.app.options.addDeclaration({
|
||||||
|
name: "generateCustomNamespaces",
|
||||||
|
type: ParameterType.Boolean,
|
||||||
|
defaultValue: false,
|
||||||
|
help: "Whether to enable conversion of categories to namespaces.",
|
||||||
|
})
|
||||||
|
this.app.options.addDeclaration({
|
||||||
|
name: "customParentNamespace",
|
||||||
|
type: ParameterType.String,
|
||||||
|
defaultValue: "",
|
||||||
|
help: "Optionally specify a parent namespace to place all generated namespaces in.",
|
||||||
|
})
|
||||||
|
this.app.options.addDeclaration({
|
||||||
|
name: "customNamespaceNamePrefix",
|
||||||
|
type: ParameterType.String,
|
||||||
|
defaultValue: "",
|
||||||
|
help: "Optionally specify a name prefix for all namespaces.",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
readOptions() {
|
||||||
|
if (this.options) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.options = {
|
||||||
|
generatePathNamespaces: this.app.options.getValue(
|
||||||
|
"generateCustomNamespaces"
|
||||||
|
),
|
||||||
|
parentNamespace: this.app.options.getValue("customParentNamespace"),
|
||||||
|
namePrefix: this.app.options.getValue("customNamespaceNamePrefix"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadNamespace(namespaceName: string): DeclarationReflection {
|
||||||
|
const formattedName = this.formatName(namespaceName)
|
||||||
|
return this.currentContext?.project
|
||||||
|
.getReflectionsByKind(ReflectionKind.Namespace)
|
||||||
|
.find(
|
||||||
|
(m) =>
|
||||||
|
m.name === formattedName &&
|
||||||
|
(!this.currentNamespaceHeirarchy.length ||
|
||||||
|
m.parent?.id ===
|
||||||
|
this.currentNamespaceHeirarchy[
|
||||||
|
this.currentNamespaceHeirarchy.length - 1
|
||||||
|
].id)
|
||||||
|
) as DeclarationReflection
|
||||||
|
}
|
||||||
|
|
||||||
|
createNamespace(namespaceName: string): DeclarationReflection | undefined {
|
||||||
|
if (!this.currentContext) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const formattedName = this.formatName(namespaceName)
|
||||||
|
const namespace = this.currentContext?.createDeclarationReflection(
|
||||||
|
ReflectionKind.Namespace,
|
||||||
|
void 0,
|
||||||
|
void 0,
|
||||||
|
formattedName
|
||||||
|
)
|
||||||
|
|
||||||
|
namespace.children = []
|
||||||
|
|
||||||
|
return namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
formatName(namespaceName: string): string {
|
||||||
|
return `${this.options?.namePrefix}${namespaceName}`
|
||||||
|
}
|
||||||
|
|
||||||
|
generateNamespaceFromTag({
|
||||||
|
tag,
|
||||||
|
summary,
|
||||||
|
}: {
|
||||||
|
tag: CommentTag
|
||||||
|
reflection?: DeclarationReflection
|
||||||
|
summary?: CommentDisplayPart[]
|
||||||
|
}) {
|
||||||
|
const categoryHeirarchy = tag.content[0].text.split(".")
|
||||||
|
categoryHeirarchy.forEach((cat, index) => {
|
||||||
|
// check whether a namespace exists with the category name.
|
||||||
|
let namespace = this.loadNamespace(cat)
|
||||||
|
|
||||||
|
if (!namespace) {
|
||||||
|
// add a namespace for this category
|
||||||
|
namespace = this.createNamespace(cat) || namespace
|
||||||
|
|
||||||
|
namespace.comment = new Comment()
|
||||||
|
if (this.currentNamespaceHeirarchy.length) {
|
||||||
|
namespace.comment.modifierTags.add("@namespaceMember")
|
||||||
|
}
|
||||||
|
if (summary && index === categoryHeirarchy.length - 1) {
|
||||||
|
namespace.comment.summary = summary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.currentContext =
|
||||||
|
this.currentContext?.withScope(namespace) || this.currentContext
|
||||||
|
|
||||||
|
this.currentNamespaceHeirarchy.push(namespace)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create categories in the last namespace if the
|
||||||
|
* reflection has a category
|
||||||
|
*/
|
||||||
|
attachCategories(
|
||||||
|
reflection: DeclarationReflection,
|
||||||
|
comments: Comment | undefined
|
||||||
|
) {
|
||||||
|
if (!this.currentNamespaceHeirarchy.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentNamespace =
|
||||||
|
this.currentNamespaceHeirarchy[this.currentNamespaceHeirarchy.length - 1]
|
||||||
|
comments?.blockTags
|
||||||
|
.filter((tag) => tag.tag === "@category")
|
||||||
|
.forEach((tag) => {
|
||||||
|
const categoryName = tag.content[0].text
|
||||||
|
if (!parentNamespace.categories) {
|
||||||
|
parentNamespace.categories = []
|
||||||
|
}
|
||||||
|
let category = parentNamespace.categories.find(
|
||||||
|
(category) => category.title === categoryName
|
||||||
|
)
|
||||||
|
if (!category) {
|
||||||
|
category = new ReflectionCategory(categoryName)
|
||||||
|
parentNamespace.categories.push(category)
|
||||||
|
}
|
||||||
|
category.children.push(reflection)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCreateDeclarationEvent(context: Context, reflection: Reflection) {
|
||||||
|
if (!(reflection instanceof DeclarationReflection)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.readOptions()
|
||||||
|
if (this.options?.parentNamespace && !this.parentNamespace) {
|
||||||
|
this.parentNamespace =
|
||||||
|
this.loadNamespace(this.options.parentNamespace) ||
|
||||||
|
this.createNamespace(this.options.parentNamespace)
|
||||||
|
}
|
||||||
|
this.currentNamespaceHeirarchy = []
|
||||||
|
if (this.parentNamespace) {
|
||||||
|
this.currentNamespaceHeirarchy.push(this.parentNamespace)
|
||||||
|
}
|
||||||
|
this.currentContext = context
|
||||||
|
const comments = this.getReflectionComments(reflection)
|
||||||
|
comments?.blockTags
|
||||||
|
.filter((tag) => tag.tag === "@customNamespace")
|
||||||
|
.forEach((tag) => {
|
||||||
|
this.generateNamespaceFromTag({
|
||||||
|
tag,
|
||||||
|
})
|
||||||
|
if (
|
||||||
|
reflection.parent instanceof DeclarationReflection ||
|
||||||
|
reflection.parent?.isProject()
|
||||||
|
) {
|
||||||
|
reflection.parent.children = reflection.parent.children?.filter(
|
||||||
|
(child) => child.id !== reflection.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.currentContext?.addChild(reflection)
|
||||||
|
})
|
||||||
|
|
||||||
|
comments?.removeTags("@customNamespace")
|
||||||
|
this.attachCategories(reflection, comments)
|
||||||
|
this.currentContext = undefined
|
||||||
|
this.currentNamespaceHeirarchy = []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan all source files for `@customNamespace` tag to generate namespaces
|
||||||
|
* This is mainly helpful to pull summaries of the namespaces.
|
||||||
|
*/
|
||||||
|
scanComments(context: Context) {
|
||||||
|
if (this.scannedComments) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.currentContext = context
|
||||||
|
const fileNames = context.program.getRootFileNames()
|
||||||
|
|
||||||
|
fileNames.forEach((fileName) => {
|
||||||
|
const sourceFile = context.program.getSourceFile(fileName)
|
||||||
|
if (!sourceFile) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const comments = context.getFileComment(sourceFile)
|
||||||
|
comments?.blockTags
|
||||||
|
.filter((tag) => tag.tag === "@customNamespace")
|
||||||
|
.forEach((tag) => {
|
||||||
|
this.generateNamespaceFromTag({ tag, summary: comments.summary })
|
||||||
|
if (this.currentNamespaceHeirarchy.length) {
|
||||||
|
// add comments of the file to the last created namespace
|
||||||
|
this.currentNamespaceHeirarchy[
|
||||||
|
this.currentNamespaceHeirarchy.length - 1
|
||||||
|
].comment = comments
|
||||||
|
|
||||||
|
this.currentNamespaceHeirarchy[
|
||||||
|
this.currentNamespaceHeirarchy.length - 1
|
||||||
|
].comment!.blockTags = this.currentNamespaceHeirarchy[
|
||||||
|
this.currentNamespaceHeirarchy.length - 1
|
||||||
|
].comment!.blockTags.filter((tag) => tag.tag !== "@customNamespace")
|
||||||
|
}
|
||||||
|
// reset values
|
||||||
|
this.currentNamespaceHeirarchy = []
|
||||||
|
this.currentContext = context
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
this.scannedComments = true
|
||||||
|
}
|
||||||
|
|
||||||
|
getReflectionComments(
|
||||||
|
reflection: DeclarationReflection
|
||||||
|
): Comment | undefined {
|
||||||
|
if (reflection.comment) {
|
||||||
|
return reflection.comment
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to retrieve comment from signature
|
||||||
|
if (!reflection.signatures?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return reflection.signatures.find((signature) => signature.comment)?.comment
|
||||||
|
}
|
||||||
|
|
||||||
|
// for debugging
|
||||||
|
printCurrentHeirarchy() {
|
||||||
|
return this.currentNamespaceHeirarchy.map((heirarchy) => heirarchy.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-9
@@ -12,13 +12,13 @@ import { NamespaceGenerateDetails } from "types"
|
|||||||
|
|
||||||
export function load(app: Application) {
|
export function load(app: Application) {
|
||||||
app.options.addDeclaration({
|
app.options.addDeclaration({
|
||||||
name: "enableNamespaceGenerator",
|
name: "enablePathNamespaceGenerator",
|
||||||
type: ParameterType.Boolean,
|
type: ParameterType.Boolean,
|
||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
help: "Whether to enable the namespace generator plugin.",
|
help: "Whether to enable the namespace generator plugin.",
|
||||||
})
|
})
|
||||||
app.options.addDeclaration({
|
app.options.addDeclaration({
|
||||||
name: "generateNamespaces",
|
name: "generatePathNamespaces",
|
||||||
type: ParameterType.Mixed,
|
type: ParameterType.Mixed,
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
help: "The namespaces to generate.",
|
help: "The namespaces to generate.",
|
||||||
@@ -27,15 +27,15 @@ export function load(app: Application) {
|
|||||||
const generatedNamespaces: Map<string, DeclarationReflection> = new Map()
|
const generatedNamespaces: Map<string, DeclarationReflection> = new Map()
|
||||||
|
|
||||||
app.converter.on(Converter.EVENT_BEGIN, (context) => {
|
app.converter.on(Converter.EVENT_BEGIN, (context) => {
|
||||||
if (!app.options.getValue("enableNamespaceGenerator")) {
|
if (!app.options.getValue("enablePathNamespaceGenerator")) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const namespaces = app.options.getValue(
|
const namespaces = app.options.getValue(
|
||||||
"generateNamespaces"
|
"generatePathNamespaces"
|
||||||
) as unknown as NamespaceGenerateDetails[]
|
) as unknown as NamespaceGenerateDetails[]
|
||||||
|
|
||||||
const generateNamespaces = (ns: NamespaceGenerateDetails[]) => {
|
const generatePathNamespaces = (ns: NamespaceGenerateDetails[]) => {
|
||||||
const createdNamespaces: DeclarationReflection[] = []
|
const createdNamespaces: DeclarationReflection[] = []
|
||||||
ns.forEach((namespace) => {
|
ns.forEach((namespace) => {
|
||||||
const genNamespace = createNamespace(context, namespace)
|
const genNamespace = createNamespace(context, namespace)
|
||||||
@@ -43,7 +43,7 @@ export function load(app: Application) {
|
|||||||
generatedNamespaces.set(namespace.pathPattern, genNamespace)
|
generatedNamespaces.set(namespace.pathPattern, genNamespace)
|
||||||
|
|
||||||
if (namespace.children) {
|
if (namespace.children) {
|
||||||
generateNamespaces(namespace.children).forEach((child) =>
|
generatePathNamespaces(namespace.children).forEach((child) =>
|
||||||
genNamespace.addChild(child)
|
genNamespace.addChild(child)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -54,13 +54,13 @@ export function load(app: Application) {
|
|||||||
return createdNamespaces
|
return createdNamespaces
|
||||||
}
|
}
|
||||||
|
|
||||||
generateNamespaces(namespaces)
|
generatePathNamespaces(namespaces)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.converter.on(
|
app.converter.on(
|
||||||
Converter.EVENT_CREATE_DECLARATION,
|
Converter.EVENT_CREATE_DECLARATION,
|
||||||
(context, reflection) => {
|
(context, reflection) => {
|
||||||
if (!app.options.getValue("enableNamespaceGenerator")) {
|
if (!app.options.getValue("enablePathNamespaceGenerator")) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ export function load(app: Application) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const namespaces = app.options.getValue(
|
const namespaces = app.options.getValue(
|
||||||
"generateNamespaces"
|
"generatePathNamespaces"
|
||||||
) as unknown as NamespaceGenerateDetails[]
|
) as unknown as NamespaceGenerateDetails[]
|
||||||
|
|
||||||
const findNamespace = (
|
const findNamespace = (
|
||||||
@@ -7,11 +7,12 @@ import { load as eslintExamplePlugin } from "./eslint-example"
|
|||||||
import { load as signatureModifierPlugin } from "./signature-modifier"
|
import { load as signatureModifierPlugin } from "./signature-modifier"
|
||||||
import { MermaidDiagramGenerator } from "./mermaid-diagram-generator"
|
import { MermaidDiagramGenerator } from "./mermaid-diagram-generator"
|
||||||
import { load as parentIgnorePlugin } from "./parent-ignore"
|
import { load as parentIgnorePlugin } from "./parent-ignore"
|
||||||
import { load as generateNamespacePlugin } from "./generate-namespace"
|
import { load as generateNamespacePlugin } from "./generate-path-namespaces"
|
||||||
import { DmlRelationsResolver } from "./dml-relations-resolver"
|
import { DmlRelationsResolver } from "./dml-relations-resolver"
|
||||||
import { load as dmlTypesNormalizer } from "./dml-types-normalizer"
|
import { load as dmlTypesNormalizer } from "./dml-types-normalizer"
|
||||||
import { MermaidDiagramDMLGenerator } from "./mermaid-diagram-dml-generator"
|
import { MermaidDiagramDMLGenerator } from "./mermaid-diagram-dml-generator"
|
||||||
import { load as dmlJsonParser } from "./dml-json-parser"
|
import { load as dmlJsonParser } from "./dml-json-parser"
|
||||||
|
import { GenerateCustomNamespacePlugin } from "./generate-custom-namespaces"
|
||||||
|
|
||||||
export function load(app: Application) {
|
export function load(app: Application) {
|
||||||
resolveReferencesPluginLoad(app)
|
resolveReferencesPluginLoad(app)
|
||||||
@@ -28,4 +29,5 @@ export function load(app: Application) {
|
|||||||
new MermaidDiagramGenerator(app)
|
new MermaidDiagramGenerator(app)
|
||||||
new DmlRelationsResolver(app)
|
new DmlRelationsResolver(app)
|
||||||
new MermaidDiagramDMLGenerator(app)
|
new MermaidDiagramDMLGenerator(app)
|
||||||
|
new GenerateCustomNamespacePlugin(app)
|
||||||
}
|
}
|
||||||
+17
-4
@@ -251,14 +251,27 @@ export declare module "typedoc" {
|
|||||||
*/
|
*/
|
||||||
enableWorkflowsPlugins: boolean
|
enableWorkflowsPlugins: boolean
|
||||||
/**
|
/**
|
||||||
* Whether to enable the namespace generator plugin.
|
* Whether to enable the namespace generator plugin for paths.
|
||||||
* @defaultValue false
|
* @defaultValue false
|
||||||
*/
|
*/
|
||||||
enableNamespaceGenerator: boolean
|
enablePathNamespaceGenerator: boolean
|
||||||
/**
|
/**
|
||||||
* The namespaces to generate.
|
* The namespaces to generate for paths.
|
||||||
*/
|
*/
|
||||||
generateNamespaces: NamespaceGenerateDetails[]
|
generatePathNamespaces: NamespaceGenerateDetails[]
|
||||||
|
/**
|
||||||
|
* Whether to enable the namespace generator plugin for `@customNamespaces` usage.
|
||||||
|
* @defaultValue false
|
||||||
|
*/
|
||||||
|
generateCustomNamespaces: boolean
|
||||||
|
/**
|
||||||
|
* Optionally specify a parent namespace to place all generated custom namespaces in.
|
||||||
|
*/
|
||||||
|
customParentNamespace: string
|
||||||
|
/**
|
||||||
|
* Optionally specify a name prefix for all custom namespaces.
|
||||||
|
*/
|
||||||
|
customNamespaceNamePrefix: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user