docs: generate medusa-react reference (#6004)

* add new plugin for better organization

* added handling in theme for mutations and query types

* added tsdoc to hooks

* added tsdocs to utility functions

* added tsdoc to providers

* generated reference

* general fixes for generated reference

* generated api reference specs + general fixes

* add missing import react

* split utilities into different directories

* added overview page

* added link to customer authentication section

* fix lint errors

* added changeset

* fix readme

* fixed build error

* added expand fields + other sections to overview

* updated what's new section

* general refactoring

* remove unnecessary query field

* fix links

* added ignoreApi option
This commit is contained in:
Shahed Nasser authored and GitHub committed 2024-01-05 17:03:38 +02:00
1 parent 6fc6a9de6a
commit 7d650771d1
2811 files changed
+231856 -455063

No files matched your search

@@ -128,10 +128,10 @@ export default class TypedocManager {
const signature = mappedSignature.signatures[0]
// get the props of the component from the
// first parameter in the signature.
const props = getTypeChildren(
signature.parameters![0].type!,
this.project
)
const props = getTypeChildren({
reflectionType: signature.parameters![0].type!,
project: this.project,
})
// this stores props that should be removed from the
// spec
@@ -482,7 +482,10 @@ export default class TypedocManager {
childName
) as DeclarationReflection
} else if (reflection.type) {
getTypeChildren(reflection.type, this.project).some((child) => {
getTypeChildren({
reflectionType: reflection.type,
project: this.project,
}).some((child) => {
if (child.name === childName) {
childReflection = child
return true
@@ -61,6 +61,7 @@ export function generateReference(referenceName: string) {
formatColoredLog(colorLog, referenceName, chunk.trim())
})
typedocProcess.on("exit", (code) => {
formatColoredLog(colorLog, referenceName, "Finished Generating reference.")
generatedCount++
if (generatedCount >= totalCount && !ranMerger && code !== 1) {
runMerger()
@@ -81,7 +82,6 @@ export function generateReference(referenceName: string) {
)
})
})
formatColoredLog(colorLog, referenceName, "Finished Generating reference.")
}
function formatColoredLog(
+111 -21
View File
@@ -64,6 +64,7 @@ module.exports = {
"stock-location",
"workflows",
],
allReflectionsHaveOwnDocumentInNamespace: ["Utilities"],
formatting: {
"*": {
showCommentsAsHeader: true,
@@ -131,7 +132,7 @@ module.exports = {
displayed_sidebar: "inventoryReference",
},
},
"^IInventoryService/methods": {
"^inventory/IInventoryService/methods": {
reflectionDescription:
"This documentation provides a reference to the `{{alias}}` {{kind}}. This belongs to the Inventory Module.",
frontmatterData: {
@@ -189,29 +190,118 @@ module.exports = {
"^js_client/.*LineItemsResource": {
maxLevel: 3,
},
"^(js_client/.*modules/.*internal|internal.*/modules)": {
reflectionGroups: {
Constructors: false,
"Type Aliases": false,
Enumerations: false,
"Enumeration Members": false,
Classes: false,
Functions: false,
Interfaces: false,
References: false,
// MEDUSA REACT CONFIG
"^medusa_react": {
frontmatterData: {
displayed_sidebar: "medusaReactSidebar",
},
parameterComponentExtraProps: {
expandUrl:
"https://docs.medusajs.com/medusa-react/overview#expanding-fields",
},
},
"^internal.*/.*js_client.*": {
reflectionGroups: {
Constructors: false,
},
"^modules/medusa_react\\.mdx": {
frontmatterData: {
displayed_sidebar: "jsClientSidebar",
displayed_sidebar: "medusaReactSidebar",
},
reflectionGroups: {
Variables: false,
Functions: false,
},
reflectionCategories: {
Mutations: false,
Queries: false,
Other: false,
},
},
"^medusa_react/(medusa_react\\.Hooks\\.mdx|.*medusa_react\\.Hooks\\.Admin\\.mdx|.*medusa_react\\.Hooks\\.Store\\.mdx|medusa_react\\.Providers\\.mdx)":
{
reflectionGroups: {
Functions: false,
},
},
"^medusa_react/Providers/.*": {
expandMembers: true,
frontmatterData: {
displayed_sidebar: "medusaReactSidebar",
slug: "/references/medusa-react/providers/{{alias-lower}}",
sidebar_label: "{{alias}}",
},
reflectionTitle: {
suffix: " Provider Overview",
},
},
"^medusa_react/medusa_react\\.Utilities": {
expandMembers: true,
reflectionTitle: {
prefix: "Medusa React ",
},
},
"^medusa_react/Utilities/.*": {
expandMembers: true,
frontmatterData: {
displayed_sidebar: "medusaReactSidebar",
slug: "/references/medusa-react/utilities/{{alias}}",
},
},
"^medusa_react/medusa_react\\.Hooks\\.mdx": {
frontmatterData: {
displayed_sidebar: "medusaReactSidebar",
slug: "/references/medusa-react/hooks",
},
},
"^medusa_react/Hooks/Admin/.*Admin\\.mdx": {
frontmatterData: {
displayed_sidebar: "medusaReactSidebar",
slug: "/references/medusa-react/hooks/admin",
},
},
"^medusa_react/Hooks/Admin/.*": {
frontmatterData: {
displayed_sidebar: "medusaReactSidebar",
slug: "/references/medusa-react/hooks/admin/{{alias-lower}}",
},
},
"^medusa_react/Hooks/Store/.*Store\\.mdx": {
frontmatterData: {
displayed_sidebar: "medusaReactSidebar",
slug: "/references/medusa-react/hooks/store",
},
},
"^medusa_react/Hooks/Store/.*": {
frontmatterData: {
displayed_sidebar: "medusaReactSidebar",
slug: "/references/medusa-react/hooks/store/{{alias-lower}}",
},
},
"^medusa_react/medusa_react\\.Providers\\.mdx": {
frontmatterData: {
displayed_sidebar: "medusaReactSidebar",
slug: "/references/medusa-react/providers",
},
},
"^medusa_react/medusa_react\\.Utilities\\.mdx": {
frontmatterData: {
displayed_sidebar: "medusaReactSidebar",
slug: "/references/medusa-react/utilities",
},
},
"^medusa_react/Hooks/.*Admin\\.Inventory_Items": {
maxLevel: 4,
},
"^medusa_react/Hooks/.*Admin\\.Products": {
maxLevel: 4,
},
"^medusa_react/Hooks/.*Admin\\.Stock_Locations": {
maxLevel: 5,
},
"^medusa_react/Hooks/.*Admin\\.Users": {
maxLevel: 5,
},
// MEDUSA CONFIG
"^medusa": {
"^medusa/": {
frontmatterData: {
displayed_sidebar: "homepage",
},
@@ -249,13 +339,13 @@ module.exports = {
},
// PRICING CONFIG
"^(pricing|IPricingModuleService)": {
"^pricing": {
...modulesOptions,
frontmatterData: {
displayed_sidebar: "pricingReference",
},
},
"^IPricingModuleService/methods": {
"^pricing/IPricingModuleService/methods": {
reflectionDescription:
"This documentation provides a reference to the `{{alias}}` {{kind}}. This belongs to the Pricing Module.",
frontmatterData: {
@@ -298,7 +388,7 @@ module.exports = {
displayed_sidebar: "productReference",
},
},
"^IProductModuleService/methods": {
"^product/IProductModuleService/methods": {
reflectionDescription:
"This documentation provides a reference to the {{alias}} {{kind}}. This belongs to the Product Module.",
frontmatterData: {
@@ -349,7 +439,7 @@ module.exports = {
displayed_sidebar: "stockLocationReference",
},
},
"^IStockLocationService/methods": {
"^stock-location/IStockLocationService/methods": {
reflectionDescription:
"This documentation provides a reference to the `{{alias}}` {{kind}}. This belongs to the Stock Location Module.",
frontmatterData: {
@@ -0,0 +1,9 @@
{
"$schema": "http://json.schemastore.org/tsconfig",
"extends": [
"../../../../packages/medusa-react/tsconfig.json"
],
"compilerOptions": {
"noImplicitReturns": false,
}
}
@@ -33,6 +33,18 @@
{
"tagName": "@excludeExternal",
"syntaxKind": "modifier"
},
{
"tagName": "@customNamespace",
"syntaxKind": "block"
},
{
"tagName": "@namespaceMember",
"syntaxKind": "modifier"
},
{
"tagName": "@typeParamDefinition",
"syntaxKind": "block"
}
]
}
@@ -0,0 +1,10 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const getConfig = require("./utils/get-config")
module.exports = getConfig({
entryPointPath: "packages/medusa-react/src/index.ts",
tsConfigName: "medusa-react.json",
name: "medusa-react",
generateNamespaces: true,
ignoreApi: true,
})
@@ -35,6 +35,15 @@ The following options are useful for linting:
- `eslintPathName`: The path to the ESLint configuration file.
- `pluginsResolvePath`: The path to resolve plugins used in the ESLint configuration files.
### 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.
It also accepts the following options:
- `parentNamespace`: The name of a parent namespace to make the generated namespaces as its children.
- `namePrefix`: A prefix to add to the name of the generated namespaces.
---
## Build the Plugin
@@ -0,0 +1,258 @@
import {
Application,
Comment,
CommentDisplayPart,
CommentTag,
Context,
Converter,
DeclarationReflection,
ParameterType,
ReflectionCategory,
ReflectionKind,
} from "typedoc"
type PluginOptions = {
generateNamespaces: boolean
parentNamespace: string
namePrefix: string
}
export class GenerateNamespacePlugin {
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_CREATE_DECLARATION,
this.handleCreateDeclarationEvent.bind(this)
)
this.app.converter.on(
Converter.EVENT_CREATE_DECLARATION,
this.scanComments.bind(this)
)
}
declareOptions() {
this.app.options.addDeclaration({
name: "generateNamespaces",
type: ParameterType.Boolean,
defaultValue: false,
help: "Whether to enable conversion of categories to namespaces.",
})
this.app.options.addDeclaration({
name: "parentNamespace",
type: ParameterType.String,
defaultValue: "",
help: "Optionally specify a parent namespace to place all generated namespaces in.",
})
this.app.options.addDeclaration({
name: "namePrefix",
type: ParameterType.String,
defaultValue: "",
help: "Optionally specify a name prefix for all namespaces.",
})
}
readOptions() {
if (this.options) {
return
}
this.options = {
generateNamespaces: this.app.options.getValue("generateNamespaces"),
parentNamespace: this.app.options.getValue("parentNamespace"),
namePrefix: this.app.options.getValue("namePrefix"),
}
}
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) {
if (!this.currentNamespaceHeirarchy.length) {
return
}
const parentNamespace =
this.currentNamespaceHeirarchy[this.currentNamespaceHeirarchy.length - 1]
reflection.comment?.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: DeclarationReflection
) {
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
reflection.comment?.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)
})
reflection.comment?.removeTags("@customNamespace")
this.attachCategories(reflection)
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
}
// for debugging
printCurrentHeirarchy() {
return this.currentNamespaceHeirarchy.map((heirarchy) => heirarchy.name)
}
}
@@ -5,6 +5,7 @@ import { load as parseOasSchemaPlugin } from "./parse-oas-schema-plugin"
import { load as apiIgnorePlugin } from "./api-ignore"
import { load as eslintExamplePlugin } from "./eslint-example"
import { load as signatureModifierPlugin } from "./signature-modifier"
import { GenerateNamespacePlugin } from "./generate-namespace"
export function load(app: Application) {
resolveReferencesPluginLoad(app)
@@ -13,4 +14,6 @@ export function load(app: Application) {
apiIgnorePlugin(app)
eslintExamplePlugin(app)
signatureModifierPlugin(app)
new GenerateNamespacePlugin(app)
}
@@ -178,7 +178,10 @@ function addComments(schema: Schema, reflection: Reflection) {
}
const children =
"type" in reflection
? getTypeChildren(reflection.type as SomeType, reflection.project)
? getTypeChildren({
reflectionType: reflection.type as SomeType,
project: reflection.project,
})
: "children" in reflection
? (reflection.children as DeclarationReflection[])
: []
@@ -6,6 +6,7 @@ A plugin that forks and customizes the [typedoc-plugin-markdown](https://github.
Aside from the options detailed in [typedoc-plugin-markdown](https://github.com/tgreyuk/typedoc-plugin-markdown/tree/master/packages/typedoc-plugin-markdown#options), the following options are accepted:
- `allReflectionsHaveOwnDocumentInNamespace`: An array of namespace names whose child members should have their own documents.
- `mdxImports`: A boolean value indicating whether the outputted files should be MDX files with the `.mdx` extension.
- `formatting`: An object whose keys are string patterns used to target specific files. You can also use the string `*` to target all files. The values are objects having the following properties:
- `sections`: (optional) an object whose keys are of type [`SectionKey`](./src/types.ts#L19) and values are boolean. This property is used to enable/disable certain sections in the outputted generated docs.
@@ -40,6 +40,13 @@ export function load(app: Application) {
defaultValue: [],
})
app.options.addDeclaration({
help: "[Markdown Plugin] Specify namespace names where all reflections are outputted into seperate files.",
name: "allReflectionsHaveOwnDocumentInNamespace",
type: ParameterType.Array,
defaultValue: [],
})
app.options.addDeclaration({
help: "[Markdown Plugin] Separator used to format filenames.",
name: "filenameSeparator",
@@ -49,10 +49,17 @@ import decrementCurrentTitleLevelHelper from "./resources/helpers/decrement-curr
import incrementCurrentTitleLevelHelper from "./resources/helpers/increment-current-title-level"
import hasMoreThanOneSignatureHelper from "./resources/helpers/has-more-than-one-signature"
import ifCanShowConstructorsTitleHelper from "./resources/helpers/if-can-show-constructors-title"
import ifReactQueryTypeHelper from "./resources/helpers/if-react-query-type"
import ifHasHookParamsHelper from "./resources/helpers/if-has-hook-params"
import reactQueryHookParamsHelper from "./resources/helpers/react-query-hook-params"
import ifHasMutationParamsHelper from "./resources/helpers/if-has-mutation-params"
import reactQueryMutationParamsHelper from "./resources/helpers/react-query-mutation-params"
import ifHasMutationReturnHelper from "./resources/helpers/if-has-mutation-return"
import reactQueryMutationReturnHelper from "./resources/helpers/react-query-mutation-return"
import ifHasQueryReturnHelper from "./resources/helpers/if-has-query-return"
import reactQueryQueryReturnHelper from "./resources/helpers/react-query-query-return"
import { MarkdownTheme } from "./theme"
// test
const TEMPLATE_PATH = path.join(__dirname, "resources", "templates")
export const indexTemplate = Handlebars.compile(
@@ -128,4 +135,13 @@ export function registerHelpers(theme: MarkdownTheme) {
incrementCurrentTitleLevelHelper(theme)
hasMoreThanOneSignatureHelper(theme)
ifCanShowConstructorsTitleHelper()
ifReactQueryTypeHelper()
ifHasHookParamsHelper()
reactQueryHookParamsHelper()
ifHasMutationParamsHelper(theme)
reactQueryMutationParamsHelper(theme)
ifHasMutationReturnHelper(theme)
reactQueryMutationReturnHelper(theme)
ifHasQueryReturnHelper(theme)
reactQueryQueryReturnHelper(theme)
}
@@ -1,7 +1,13 @@
import * as Handlebars from "handlebars"
import { Comment } from "typedoc"
const EXCLUDED_TAGS = ["@returns", "@example", "@featureFlag"]
const EXCLUDED_TAGS = [
"@returns",
"@example",
"@featureFlag",
"@category",
"@typeParamDefinition",
]
export default function () {
Handlebars.registerHelper(
@@ -0,0 +1,14 @@
import * as Handlebars from "handlebars"
import { SignatureReflection } from "typedoc"
import { getHookParams } from "../../utils/react-query-utils"
export default function () {
Handlebars.registerHelper(
"ifHasHookParams",
function (this: SignatureReflection, options: Handlebars.HelperOptions) {
return getHookParams(this).length
? options.fn(this)
: options.inverse(this)
}
)
}
@@ -0,0 +1,23 @@
import * as Handlebars from "handlebars"
import { SignatureReflection } from "typedoc"
import { getMutationParams } from "../../utils/react-query-utils"
import { MarkdownTheme } from "../../theme"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"ifHasMutationParams",
function (this: SignatureReflection, options: Handlebars.HelperOptions) {
return getMutationParams({
signatureReflection: this,
project: theme.project || this.project,
reflectionTypeGetterOptions: {
// we only need to check if there are mutation parameters,
// so no need to get all the children
maxLevel: 1,
},
}).length
? options.fn(this)
: options.inverse(this)
}
)
}
@@ -0,0 +1,23 @@
import * as Handlebars from "handlebars"
import { SignatureReflection } from "typedoc"
import { getMutationReturn } from "../../utils/react-query-utils"
import { MarkdownTheme } from "../../theme"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"ifHasMutationReturn",
function (this: SignatureReflection, options: Handlebars.HelperOptions) {
return getMutationReturn({
signatureReflection: this,
project: theme.project || this.project,
reflectionTypeGetterOptions: {
// we only need to check if there are mutation returns,
// so no need to get all the children
maxLevel: 1,
},
}).length
? options.fn(this)
: options.inverse(this)
}
)
}
@@ -0,0 +1,23 @@
import * as Handlebars from "handlebars"
import { SignatureReflection } from "typedoc"
import { getQueryReturn } from "../../utils/react-query-utils"
import { MarkdownTheme } from "../../theme"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"ifHasQueryReturn",
function (this: SignatureReflection, options: Handlebars.HelperOptions) {
return getQueryReturn({
signatureReflection: this,
project: theme.project || this.project,
reflectionTypeGetterOptions: {
// we only need to check if there are query returns,
// so no need to get all the children
maxLevel: 1,
},
}).length
? options.fn(this)
: options.inverse(this)
}
)
}
@@ -0,0 +1,17 @@
import * as Handlebars from "handlebars"
import { SignatureReflection } from "typedoc"
import {
isReactQueryMutation,
isReactQueryQuery,
} from "../../utils/react-query-utils"
export default function () {
Handlebars.registerHelper(
"ifReactQueryType",
function (this: SignatureReflection, options: Handlebars.HelperOptions) {
return isReactQueryMutation(this) || isReactQueryQuery(this)
? options.fn(this)
: options.inverse(this)
}
)
}
@@ -3,6 +3,7 @@ import { ReflectionParameterType } from "../../types"
import { parseParams } from "../../utils/params-utils"
import { MarkdownTheme } from "../../theme"
import { reflectionComponentFormatter } from "../../utils/reflection-formatter"
import { formatParameterComponent } from "../../utils/format-parameter-component"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
@@ -21,19 +22,11 @@ export default function (theme: MarkdownTheme) {
})
)
let extraProps: string[] = []
if (parameterComponentExtraProps) {
extraProps = Object.entries(parameterComponentExtraProps).map(
([key, value]) => `${key}=${JSON.stringify(value)}`
)
}
return `<${parameterComponent} parameters={${JSON.stringify(
parameters,
null,
2
)}} ${extraProps.join(" ")}/>`
return formatParameterComponent({
parameterComponent,
componentItems: parameters,
extraProps: parameterComponentExtraProps,
})
}
)
}
@@ -0,0 +1,19 @@
import * as Handlebars from "handlebars"
import { SignatureReflection } from "typedoc"
import { getHookParams } from "../../utils/react-query-utils"
export default function () {
Handlebars.registerHelper(
"reactQueryHookParams",
function (this: SignatureReflection) {
let parametersStr = ""
const hookParameters = getHookParams(this)
if (hookParameters?.length) {
parametersStr = Handlebars.helpers.parameter.call(hookParameters)
}
return parametersStr
}
)
}
@@ -0,0 +1,37 @@
import * as Handlebars from "handlebars"
import { SignatureReflection } from "typedoc"
import { getMutationParams } from "../../utils/react-query-utils"
import { MarkdownTheme } from "../../theme"
import { formatParameterComponent } from "../../utils/format-parameter-component"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"reactQueryMutationParams",
function (this: SignatureReflection) {
const {
parameterStyle,
parameterComponent,
maxLevel,
parameterComponentExtraProps,
} = theme.getFormattingOptionsForLocation()
const mutationParameters = getMutationParams({
signatureReflection: this,
project: theme.project || this.project,
reflectionTypeGetterOptions: {
maxLevel,
},
})
if (parameterStyle !== "component") {
// TODO maybe handle other cases? But for now it's not important
return ""
}
return formatParameterComponent({
parameterComponent,
componentItems: mutationParameters,
extraProps: parameterComponentExtraProps,
})
}
)
}
@@ -0,0 +1,37 @@
import * as Handlebars from "handlebars"
import { SignatureReflection } from "typedoc"
import { getMutationReturn } from "../../utils/react-query-utils"
import { MarkdownTheme } from "../../theme"
import { formatParameterComponent } from "../../utils/format-parameter-component"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"reactQueryMutationReturn",
function (this: SignatureReflection) {
const {
parameterStyle,
parameterComponent,
maxLevel,
parameterComponentExtraProps,
} = theme.getFormattingOptionsForLocation()
const mutationParameters = getMutationReturn({
signatureReflection: this,
project: theme.project || this.project,
reflectionTypeGetterOptions: {
maxLevel,
},
})
if (parameterStyle !== "component") {
// TODO maybe handle other cases? But for now it's not important
return ""
}
return formatParameterComponent({
parameterComponent,
componentItems: mutationParameters,
extraProps: parameterComponentExtraProps,
})
}
)
}
@@ -0,0 +1,37 @@
import * as Handlebars from "handlebars"
import { SignatureReflection } from "typedoc"
import { getQueryReturn } from "../../utils/react-query-utils"
import { MarkdownTheme } from "../../theme"
import { formatParameterComponent } from "../../utils/format-parameter-component"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"reactQueryQueryReturn",
function (this: SignatureReflection) {
const {
parameterStyle,
parameterComponent,
maxLevel,
parameterComponentExtraProps,
} = theme.getFormattingOptionsForLocation()
const mutationParameters = getQueryReturn({
signatureReflection: this,
project: theme.project || this.project,
reflectionTypeGetterOptions: {
maxLevel,
},
})
if (parameterStyle !== "component") {
// TODO maybe handle other cases? But for now it's not important
return ""
}
return formatParameterComponent({
parameterComponent,
componentItems: mutationParameters,
extraProps: parameterComponentExtraProps,
})
}
)
}
@@ -3,6 +3,7 @@ import { PageEvent, ParameterReflection, ReflectionKind } from "typedoc"
import { MarkdownTheme } from "../../theme"
import { getDisplayName } from "../../utils"
import { escapeChars } from "utils"
import { replaceTemplateVariables } from "../../utils/reflection-template-strings"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
@@ -12,10 +13,16 @@ export default function (theme: MarkdownTheme) {
const { reflectionTitle } = theme.getFormattingOptionsForLocation()
if (reflectionTitle?.fullReplacement?.length) {
return reflectionTitle.fullReplacement
return theme.reflection
? replaceTemplateVariables(
theme.reflection,
reflectionTitle.fullReplacement
)
: reflectionTitle.fullReplacement
}
const title: string[] = [""]
const title: string[] = [reflectionTitle?.prefix || ""]
if (
reflectionTitle?.kind &&
this.model?.kind &&
@@ -2,8 +2,9 @@ import * as Handlebars from "handlebars"
import { Comment, DeclarationReflection, SignatureReflection } from "typedoc"
import { MarkdownTheme } from "../../theme"
import reflectionFormatter from "../../utils/reflection-formatter"
import { returnReflectionComponentFormatter } from "../../utils/return-reflection-formatter"
import { getReflectionTypeParameters } from "../../utils/reflection-type-parameters"
import { Parameter } from "../../types"
import { formatParameterComponent } from "../../utils/format-parameter-component"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
@@ -24,14 +25,18 @@ function getReturnFromType(
theme: MarkdownTheme,
reflection: SignatureReflection
) {
const { parameterStyle, parameterComponent, maxLevel } =
theme.getFormattingOptionsForLocation()
const {
parameterStyle,
parameterComponent,
maxLevel,
parameterComponentExtraProps,
} = theme.getFormattingOptionsForLocation()
if (!reflection.type) {
return ""
}
const componentItems = returnReflectionComponentFormatter({
const componentItems = getReflectionTypeParameters({
reflectionType: reflection.type,
project: reflection.project || theme.project,
comment: reflection.comment,
@@ -40,11 +45,11 @@ function getReturnFromType(
})
if (parameterStyle === "component") {
return `<${parameterComponent} parameters={${JSON.stringify(
return formatParameterComponent({
parameterComponent,
componentItems,
null,
2
)}} />`
extraProps: parameterComponentExtraProps,
})
} else {
return formatReturnAsList(componentItems)
}
@@ -74,8 +79,12 @@ function formatReturnAsList(componentItems: Parameter[], level = 1): string {
function getReturnFromComment(theme: MarkdownTheme, comment: Comment) {
const md: string[] = []
const { parameterStyle, parameterComponent, maxLevel } =
theme.getFormattingOptionsForLocation()
const {
parameterStyle,
parameterComponent,
maxLevel,
parameterComponentExtraProps,
} = theme.getFormattingOptionsForLocation()
if (comment.blockTags?.length) {
const tags = comment.blockTags
@@ -97,11 +106,14 @@ function getReturnFromComment(theme: MarkdownTheme, comment: Comment) {
)
result +=
parameterStyle === "component"
? `\n\n<${parameterComponent} parameters={${JSON.stringify(
content,
null,
2
)}} title={"${commentPart.target.name}"} />\n\n`
? `\n\n${formatParameterComponent({
parameterComponent,
componentItems: content as Parameter[],
extraProps: {
...parameterComponentExtraProps,
title: commentPart.target.name,
},
})}\n\n`
: `\n\n<details>\n<summary>\n${
commentPart.target.name
}\n</summary>\n\n${content?.join("\n")}\n\n</details>`
@@ -2,15 +2,16 @@ import * as Handlebars from "handlebars"
import { DeclarationReflection, ReflectionType } from "typedoc"
import { MarkdownTheme } from "../../theme"
import { parseParams } from "../../utils/params-utils"
import { ReflectionParameterType } from "../../types"
import { Parameter, ReflectionParameterType } from "../../types"
import reflectionFormatter from "../../utils/reflection-formatter"
import { escapeChars, stripLineBreaks } from "utils"
import { formatParameterComponent } from "../../utils/format-parameter-component"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"typeDeclarationMembers",
function (this: DeclarationReflection[]) {
const { parameterComponent, maxLevel } =
const { parameterComponent, maxLevel, parameterComponentExtraProps } =
theme.getFormattingOptionsForLocation()
const comments = this.map(
(param) => !!param.comment?.hasVisibleComponent()
@@ -30,11 +31,12 @@ export default function (theme: MarkdownTheme) {
break
}
case "component": {
result = getComponentMarkdownContent(
result = getComponentMarkdownContent({
properties,
parameterComponent,
maxLevel
)
maxLevel,
parameterComponentExtraProps,
})
break
}
case "table": {
@@ -58,11 +60,17 @@ function getListMarkdownContent(properties: DeclarationReflection[]) {
return items.join("\n")
}
function getComponentMarkdownContent(
properties: DeclarationReflection[],
parameterComponent?: string,
function getComponentMarkdownContent({
properties,
parameterComponent,
maxLevel,
parameterComponentExtraProps,
}: {
properties: DeclarationReflection[]
parameterComponent?: string
maxLevel?: number | undefined
) {
parameterComponentExtraProps?: Record<string, unknown>
}) {
const parameters = properties.map((property) =>
reflectionFormatter({
reflection: property,
@@ -72,11 +80,11 @@ function getComponentMarkdownContent(
})
)
return `<${parameterComponent} parameters={${JSON.stringify(
parameters,
null,
2
)}} />`
return formatParameterComponent({
parameterComponent,
componentItems: parameters as Parameter[],
extraProps: parameterComponentExtraProps,
})
}
function getTableMarkdownContent(
@@ -2,12 +2,13 @@ import * as Handlebars from "handlebars"
import { TypeParameterReflection } from "typedoc"
import { reflectionComponentFormatter } from "../../utils/reflection-formatter"
import { MarkdownTheme } from "../../theme"
import { formatParameterComponent } from "../../utils/format-parameter-component"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"typeParameterComponent",
function (this: TypeParameterReflection[]) {
const { parameterComponent, maxLevel } =
const { parameterComponent, maxLevel, parameterComponentExtraProps } =
theme.getFormattingOptionsForLocation()
const parameters = this.map((parameter) =>
reflectionComponentFormatter({
@@ -17,11 +18,11 @@ export default function (theme: MarkdownTheme) {
})
)
return `<${parameterComponent} parameters={${JSON.stringify(
parameters,
null,
2
)}} />`
return formatParameterComponent({
parameterComponent,
componentItems: parameters,
extraProps: parameterComponentExtraProps,
})
}
)
}
@@ -70,7 +70,7 @@
{{incrementCurrentTitleLevel}}
{{> member.signature showSources=false parent=../type.declaration }}
{{> member.signature-wrapper showSources=false parent=../type.declaration }}
{{decrementCurrentTitleLevel}}
@@ -4,7 +4,7 @@
{{#with getSignature}}
{{> member.signature accessor="get" showSources=true }}
{{> member.signature-wrapper accessor="get" showSources=true }}
{{/with}}
@@ -18,7 +18,7 @@
{{#with setSignature}}
{{> member.signature accessor="set" showSources=true }}
{{> member.signature-wrapper accessor="set" showSources=true }}
{{/with}}
@@ -16,7 +16,7 @@
{{#each signatures}}
{{> member.signature showSources=true parent=.. }}
{{> member.signature-wrapper showSources=true parent=.. }}
{{/each}}
@@ -58,9 +58,11 @@
{{#unless @last}}
{{#unless hasOwnDocument}}
{{#unless removeSeparator}}
___
{{/unless}}
{{/unless}}
{{/unless}}
{{#unless hasOwnDocument}}
@@ -0,0 +1,125 @@
{{{signatureTitle accessor parent}}}
{{#if (getFormattingOption "expandMembers")}}
{{#if (hasMoreThanOneSignature parent)}}
{{incrementCurrentTitleLevel}}
{{/if}}
{{/if}}
{{#if (sectionEnabled "member_signature_comment")}}
{{#with comment}}
{{#if hasVisibleComponent}}
{{{comments this true false}}}
{{/if}}
{{/with}}
{{/if}}
{{#if (sectionEnabled "member_signature_example")}}
{{{example this}}}
{{/if}}
{{#if (sectionEnabled "member_signature_typeParameters")}}
{{#if typeParameters}}
{{{titleLevel}}} Type Parameters
{{#with typeParameters}}
{{{typeParameter}}}
{{/with}}
{{/if}}
{{/if}}
{{#if (sectionEnabled "member_signature_parameters")}}
{{#ifHasHookParams}}
{{{titleLevel}}} Hook Parameters
{{{reactQueryHookParams}}}
{{/ifHasHookParams}}
{{#ifHasMutationParams}}
{{{titleLevel}}} Mutation Function Parameters
{{{reactQueryMutationParams}}}
{{/ifHasMutationParams}}
{{/if}}
{{#ifShowReturns}}
{{#ifHasMutationReturn}}
{{{titleLevel}}} Mutation Function Returned Data
{{{reactQueryMutationReturn}}}
{{/ifHasMutationReturn}}
{{#ifHasQueryReturn}}
{{{titleLevel}}} Query Returned Data
{{{reactQueryQueryReturn}}}
{{/ifHasQueryReturn}}
{{/ifShowReturns}}
{{#if (sectionEnabled "member_signature_comment")}}
{{#with comment}}
{{#if hasVisibleComponent}}
{{{comments this false true ..}}}
{{/if}}
{{/with}}
{{/if}}
{{#if (sectionEnabled "member_signature_sources")}}
{{#if showSources}}
{{incrementCurrentTitleLevel}}
{{> member.sources}}
{{decrementCurrentTitleLevel}}
{{/if}}
{{/if}}
{{#if (getFormattingOption "expandMembers")}}
{{#if (hasMoreThanOneSignature parent)}}
{{decrementCurrentTitleLevel}}
{{/if}}
{{/if}}
@@ -0,0 +1,9 @@
{{#ifReactQueryType}}
{{> member.react-query.signature}}
{{else}}
{{> member.signature}}
{{/ifReactQueryType}}
@@ -90,7 +90,7 @@
{{#each declaration.signatures}}
{{> member.signature showSources=false parent=../declaration }}
{{> member.signature-wrapper showSources=false parent=../declaration }}
{{/each}}
@@ -4,6 +4,10 @@
{{#each categories}}
{{#unless @first}}
___
{{/unless}}
{{#unless allChildrenHaveOwnDocument}}
{{#unless (getFormattingOption "expandMembers")}}
@@ -18,7 +22,7 @@
{{#unless hasOwnDocument}}
{{> member}}
{{> member removeSeparator=true}}
{{/unless}}
@@ -100,7 +100,7 @@
{{incrementCurrentTitleLevel}}
{{> member.signature showSources=true }}
{{> member.signature-wrapper showSources=true }}
{{decrementCurrentTitleLevel}}
@@ -30,6 +30,7 @@ import { Mapping } from "./types"
export class MarkdownTheme extends Theme {
allReflectionsHaveOwnDocument!: string[]
allReflectionsHaveOwnDocumentInNamespace: string[]
entryDocument: string
entryPoints!: string[]
filenameSeparator!: string
@@ -66,6 +67,9 @@ export class MarkdownTheme extends Theme {
// prettier-ignore
this.allReflectionsHaveOwnDocument = this.getOption("allReflectionsHaveOwnDocument") as string[]
this.allReflectionsHaveOwnDocumentInNamespace = this.getOption(
"allReflectionsHaveOwnDocumentInNamespace"
) as string[]
this.entryDocument = this.getOption("entryDocument") as string
this.entryPoints = this.getOption("entryPoints") as string[]
this.filenameSeparator = this.getOption("filenameSeparator") as string
@@ -143,14 +147,36 @@ export class MarkdownTheme extends Theme {
return urls
}
getAliasPath(reflection: Reflection): string {
return path.join(
reflection.parent && !reflection.parent.isProject()
? this.getAliasPath(reflection.parent)
: "",
reflection.getAlias()
)
}
buildUrls(
reflection: DeclarationReflection,
urls: UrlMapping[]
): UrlMapping[] {
const mapping = this.getMappings(
reflection,
reflection.parent?.isProject() ? "" : reflection.parent?.getAlias()
).find((mapping) => reflection.kindOf(mapping.kind))
reflection.parent?.isProject() || !reflection.parent
? ""
: this.getAliasPath(reflection.parent)
).find(
(mapping) =>
reflection.kindOf(mapping.kind) &&
mapping.modifiers.has.every(
(modifier) => reflection.comment?.hasModifier(modifier) || false
) &&
mapping.modifiers.not.every((modifier) => {
return reflection.comment
? !reflection.comment.hasModifier(modifier)
: true
})
)
if (mapping) {
if (!reflection.url || !MarkdownTheme.URL_PREFIX.test(reflection.url)) {
const url = this.toUrl(mapping, reflection)
@@ -176,12 +202,23 @@ export class MarkdownTheme extends Theme {
return (
mapping.directory +
"/" +
this.getUrl(reflection) +
this.getUrl({
reflection,
directory: mapping.directory,
}) +
(this.mdxOutput ? ".mdx" : ".md")
)
}
getUrl(reflection: Reflection, relative?: Reflection): string {
getUrl({
reflection,
directory,
relative,
}: {
reflection: Reflection
directory: string
relative?: Reflection
}): string {
let url = reflection.getAlias()
if (
@@ -189,8 +226,15 @@ export class MarkdownTheme extends Theme {
reflection.parent !== relative &&
!(reflection.parent instanceof ProjectReflection)
) {
url =
this.getUrl(reflection.parent, relative) + this.filenameSeparator + url
const urlPrefix = this.getUrl({
reflection: reflection.parent,
directory,
relative,
})
const fileNameSeparator = this.getFileNameSeparator(
`${directory}/${urlPrefix}`
)
url = urlPrefix + fileNameSeparator + url
}
return url.replace(/^_/, "")
@@ -280,11 +324,14 @@ export class MarkdownTheme extends Theme {
}
}
getModuleParents(reflection: DeclarationReflection): DeclarationReflection[] {
getParentsOfKind(
reflection: DeclarationReflection,
kind: ReflectionKind
): DeclarationReflection[] {
const parents: DeclarationReflection[] = []
let currentParent = reflection?.parent as DeclarationReflection | undefined
do {
if (currentParent?.kind === ReflectionKind.Module) {
if (currentParent?.kind === kind) {
parents.push(currentParent)
}
currentParent = currentParent?.parent as DeclarationReflection | undefined
@@ -294,11 +341,28 @@ export class MarkdownTheme extends Theme {
}
getAllReflectionsHaveOwnDocument(reflection: DeclarationReflection): boolean {
const moduleParents = this.getModuleParents(reflection)
return moduleParents.some((parent) =>
this.allReflectionsHaveOwnDocument.includes(parent.name)
const moduleParents = this.getParentsOfKind(
reflection,
ReflectionKind.Module
)
const namespaceParents = this.getParentsOfKind(
reflection,
ReflectionKind.Namespace
)
return (
moduleParents.some((parent) =>
this.allReflectionsHaveOwnDocument.includes(parent.name)
) ||
namespaceParents.some((parent) =>
this.allReflectionsHaveOwnDocumentInNamespace.includes(parent.name)
)
)
}
getFileNameSeparator(pathPrefix: string): string {
const formattingOptions = this.getFormattingOptions(pathPrefix)
return formattingOptions.fileNameSeparator || this.filenameSeparator
}
getMappings(
@@ -308,36 +372,60 @@ export class MarkdownTheme extends Theme {
return [
{
kind: [ReflectionKind.Module],
modifiers: {
has: [],
not: [],
},
isLeaf: false,
directory: path.join(directoryPrefix || "", "modules"),
template: this.getReflectionTemplate(),
},
{
kind: [ReflectionKind.Namespace],
modifiers: {
has: [],
not: [],
},
isLeaf: false,
directory: path.join(directoryPrefix || "", "modules"),
directory: path.join(directoryPrefix || ""),
template: this.getReflectionTemplate(),
},
{
kind: [ReflectionKind.Enum],
modifiers: {
has: [],
not: [],
},
isLeaf: false,
directory: path.join(directoryPrefix || "", "enums"),
template: this.getReflectionTemplate(),
},
{
kind: [ReflectionKind.Class],
modifiers: {
has: [],
not: [],
},
isLeaf: false,
directory: path.join(directoryPrefix || "", "classes"),
template: this.getReflectionTemplate(),
},
{
kind: [ReflectionKind.Interface],
modifiers: {
has: [],
not: [],
},
isLeaf: false,
directory: path.join(directoryPrefix || "", "interfaces"),
template: this.getReflectionTemplate(),
},
{
kind: [ReflectionKind.TypeAlias],
modifiers: {
has: [],
not: [],
},
isLeaf: true,
directory: path.join(directoryPrefix || "", "types"),
template: this.getReflectionMemberTemplate(),
@@ -346,18 +434,30 @@ export class MarkdownTheme extends Theme {
? [
{
kind: [ReflectionKind.Variable],
modifiers: {
has: [],
not: [],
},
isLeaf: true,
directory: path.join(directoryPrefix || "", "variables"),
template: this.getReflectionMemberTemplate(),
},
{
kind: [ReflectionKind.Function],
modifiers: {
has: [],
not: [],
},
isLeaf: true,
directory: path.join(directoryPrefix || "", "functions"),
template: this.getReflectionMemberTemplate(),
},
{
kind: [ReflectionKind.Method],
modifiers: {
has: [],
not: [],
},
isLeaf: true,
directory: path.join(directoryPrefix || "", "methods"),
template: this.getReflectionMemberTemplate(),
@@ -393,6 +493,25 @@ export class MarkdownTheme extends Theme {
page.model instanceof ProjectReflection
) {
this.removeGroups(page.model)
this.removeCategories(page.model)
this.sortCategories(page.model)
}
if (
this.reflection instanceof DeclarationReflection &&
this.reflection.parent instanceof ProjectReflection
) {
const namespacesGroup = this.reflection.groups?.find(
(group) => group.title === "Namespaces"
)
if (namespacesGroup) {
// remove the namespaces that have the `@namespaceMember` modifier
namespacesGroup.children = namespacesGroup.children.filter((child) => {
return child.comment
? !child.comment.hasModifier("@namespaceMember")
: true
})
}
}
if (
@@ -428,6 +547,34 @@ export class MarkdownTheme extends Theme {
})
}
protected removeCategories(
model?: DeclarationReflection | ProjectReflection
) {
if (!model?.categories) {
return
}
const options = this.getFormattingOptionsForLocation()
model.categories = model.categories.filter((category) => {
return (
!options.reflectionCategories ||
!(category.title in options.reflectionCategories) ||
options.reflectionCategories[category.title]
)
})
}
protected sortCategories(model?: DeclarationReflection | ProjectReflection) {
if (!model?.categories) {
return
}
model.categories.sort((categoryA, categoryB) => {
return categoryA.title.localeCompare(categoryB.title)
})
}
get globalsFile() {
return `modules.${this.mdxOutput ? "mdx" : "md"}`
}
@@ -441,10 +588,14 @@ export class MarkdownTheme extends Theme {
return {}
}
return this.getFormattingOptions(this.location)
}
getFormattingOptions(location: string): FormattingOptionType {
const applicableOptions: FormattingOptionType[] = []
Object.keys(this.formattingOptions).forEach((key) => {
if (key === "*" || new RegExp(key).test(this.location)) {
if (key === "*" || new RegExp(key).test(location)) {
applicableOptions.push(this.formattingOptions[key])
}
})
@@ -16,6 +16,10 @@ export type ReflectionParameterType =
export type Mapping = {
kind: ReflectionKind[]
modifiers: {
has: `@${string}`[]
not: `@${string}`[]
}
isLeaf: boolean
directory: string
template: (pageEvent: PageEvent<ContainerReflection>) => string
@@ -0,0 +1,58 @@
import { Parameter } from "../types"
type FormatParameterComponentProps = {
parameterComponent: string | undefined
componentItems: Parameter[]
extraProps?: Record<string, unknown>
}
/**
* Function passed to an a Parameter array's sort method. It sorts
* parameter items by whether they're optional or not. So, required items
* are placed first in the array.
*/
function componentItemsSorter(a: Parameter, b: Parameter): number {
if (a.optional) {
return 1
}
if (b.optional) {
return -1
}
return 0
}
/**
* Function that goes through items and their children to apply the
* {@link componentItemsSorter} function on them and sort the items.
*/
function sortComponentItems(items: Parameter[]): Parameter[] {
items.sort(componentItemsSorter)
items = items.map((item) => {
if (item.children) {
item.children = sortComponentItems(item.children)
}
return item
})
return items
}
export function formatParameterComponent({
parameterComponent,
componentItems,
extraProps,
}: FormatParameterComponentProps): string {
let extraPropsArr: string[] = []
if (extraProps) {
extraPropsArr = Object.entries(extraProps).map(
([key, value]) => `${key}=${JSON.stringify(value)}`
)
}
// reorder component items to show required items first
componentItems = sortComponentItems(componentItems)
return `<${parameterComponent} parameters={${JSON.stringify(
componentItems
)}} ${extraPropsArr.join(" ")}/>`
}
@@ -0,0 +1,263 @@
import {
Comment,
ParameterReflection,
ProjectReflection,
ReferenceType,
SignatureReflection,
SomeType,
} from "typedoc"
import { Parameter } from "../types"
import {
GetReflectionTypeParametersParams,
getReflectionTypeParameters,
} from "./reflection-type-parameters"
const MUTATION_PARAM_TYPE_NAME = "UseMutationOptions"
const MUTATION_RETURN_TYPE_NAME = "UseMutationResult"
const QUERY_PARAM_TYPE_NAME = "UseQueryOptionsWrapper"
const BLACKLISTED_PARAM_NAMES = ["constructor", "void"]
export function isReactQueryMutation(
signatureReflection: SignatureReflection
): boolean {
return (
signatureReflection.type?.type === "reference" &&
signatureReflection.type.name === MUTATION_RETURN_TYPE_NAME
)
}
export function getReactQueryQueryParameterType(
signatureReflection: SignatureReflection
): ReferenceType | undefined {
let parameterType: ReferenceType | undefined
signatureReflection.parameters?.some((parameter) => {
if (
parameter.type?.type === "reference" &&
parameter.type.name === QUERY_PARAM_TYPE_NAME
) {
parameterType = parameter.type
return true
}
return false
})
return parameterType
}
export function isReactQueryQuery(
signatureReflection: SignatureReflection
): boolean {
return getReactQueryQueryParameterType(signatureReflection) !== undefined
}
/**
* Some parameter types are declared as classes which shows their constructor as
* an accepted parameter. This removes the constructor from the list of parameters.
*/
export function cleanUpParameterTypes(parameters: Parameter[]): Parameter[] {
return parameters.filter((parameter, index) => {
const keep = !BLACKLISTED_PARAM_NAMES.includes(parameter.name)
if (keep && parameter.children) {
parameters[index].children = cleanUpParameterTypes(parameter.children)
}
return keep
})
}
export function getHookParams(
signatureReflection: SignatureReflection
): ParameterReflection[] {
let hookParameters: ParameterReflection[] = []
if (isReactQueryMutation(signatureReflection)) {
/**
* check parameters before the options parameter having the {@link MUTATION_PARAM_TYPE_NAME} type
*/
hookParameters =
signatureReflection.parameters?.filter((parameter) => {
return (
parameter.type?.type !== "reference" ||
parameter.type.name !== MUTATION_PARAM_TYPE_NAME
)
}) || []
} else if (isReactQueryQuery(signatureReflection)) {
/**
* check parameters before the options parameter having the {@link QUERY_PARAM_TYPE_NAME} type
*/
hookParameters =
signatureReflection.parameters?.filter((parameter) => {
return (
parameter.type?.type !== "reference" ||
parameter.type.name !== QUERY_PARAM_TYPE_NAME
)
}) || []
}
return hookParameters
}
/**
* Load comments of a type param from the signature's comments if it has the
* `@typeParamDefinition` tag.
*/
function loadTypeComments(
signatureReflection: SignatureReflection,
typeName: string
): Comment | undefined {
const tagRegex = /^(?<typeName>\w+) - (?<typeDef>.*)$/
const typeComment = new Comment()
signatureReflection.comment?.blockTags
.filter((tag) => tag.tag === "@typeParamDefinition")
.some((tag) => {
return tag.content.some((tagContent) => {
const match = tagContent.text.match(tagRegex)
if (match?.groups?.typeName === typeName && match?.groups?.typeDef) {
typeComment.summary.push({
kind: "text",
text: match.groups.typeDef,
})
return true
}
return false
})
})
return typeComment.summary.length > 0 ? typeComment : undefined
}
type GetMutationParamsParams = {
signatureReflection: SignatureReflection
project: ProjectReflection
reflectionTypeGetterOptions?: Partial<GetReflectionTypeParametersParams>
}
type GetParamsOptions = GetMutationParamsParams & {
reflectionType: SomeType
}
function getParams({
signatureReflection,
reflectionType,
reflectionTypeGetterOptions,
...rest
}: GetParamsOptions): Parameter[] {
const comment =
"name" in reflectionType
? loadTypeComments(signatureReflection, reflectionType.name)
: reflectionTypeGetterOptions?.comment
return cleanUpParameterTypes(
getReflectionTypeParameters({
reflectionType,
comment,
isReturn: false,
...reflectionTypeGetterOptions,
...rest,
})
)
}
export function getMutationParams({
signatureReflection,
reflectionTypeGetterOptions,
project,
}: GetMutationParamsParams): Parameter[] {
let mutationParameters: Parameter[] = []
if (isReactQueryMutation(signatureReflection)) {
const mutationOptionParam = signatureReflection.parameters?.find(
(parameter) => {
return (
parameter.type?.type === "reference" &&
parameter.type.name === MUTATION_PARAM_TYPE_NAME
)
}
)
if (
mutationOptionParam?.type?.type === "reference" &&
(mutationOptionParam.type.typeArguments?.length || 0) >= 3
) {
mutationParameters = getParams({
signatureReflection,
project,
reflectionType: mutationOptionParam.type.typeArguments![2],
reflectionTypeGetterOptions: {
...reflectionTypeGetterOptions,
wrapObject:
mutationOptionParam.type.typeArguments![2].type === "reference",
},
})
}
}
return mutationParameters
}
export function hasResponseTypeArgument(type: ReferenceType): boolean {
return (
(type.typeArguments?.length || 0) >= 1 &&
type.typeArguments![0].type === "reference" &&
type.typeArguments![0].name === "Response" &&
(type.typeArguments![0].typeArguments?.length || 0) >= 1
)
}
type GetMutationReturnParams = GetMutationParamsParams
export function getMutationReturn({
signatureReflection,
reflectionTypeGetterOptions,
project,
}: GetMutationReturnParams): Parameter[] {
let returnParams: Parameter[] = []
if (!isReactQueryMutation(signatureReflection)) {
return returnParams
}
const returnType = signatureReflection.type! as ReferenceType
if (hasResponseTypeArgument(returnType)) {
returnParams = getParams({
signatureReflection,
project,
reflectionType: (returnType.typeArguments![0] as ReferenceType)
.typeArguments![0],
reflectionTypeGetterOptions: {
...reflectionTypeGetterOptions,
wrapObject:
(returnType.typeArguments![0] as ReferenceType).typeArguments![0]
.type === "reference",
},
})
}
return returnParams
}
type GetQueryReturnParams = GetMutationParamsParams
export function getQueryReturn({
signatureReflection,
reflectionTypeGetterOptions,
project,
}: GetQueryReturnParams): Parameter[] {
let returnParams: Parameter[] = []
const parameterType = getReactQueryQueryParameterType(signatureReflection)
if (parameterType && hasResponseTypeArgument(parameterType)) {
returnParams = getParams({
signatureReflection,
project,
reflectionType: (parameterType.typeArguments![0] as ReferenceType)
.typeArguments![0],
reflectionTypeGetterOptions,
})
}
return returnParams
}
@@ -85,7 +85,10 @@ export function reflectionListFormatter({
) {
const children = hasChildren
? reflection.children
: getTypeChildren(reflection.type!, reflection.project)
: getTypeChildren({
reflectionType: reflection.type!,
project: reflection.project,
})
const itemChildren: string[] = []
let itemChildrenKind: ReflectionKind | null = null
children?.forEach((childItem: DeclarationReflection) => {
@@ -156,7 +159,11 @@ export function reflectionComponentFormatter({
) {
const children = hasChildren
? reflection.children
: getTypeChildren(reflection.type!, project || reflection.project)
: getTypeChildren({
reflectionType: reflection.type!,
project: project || reflection.project,
maxLevel,
})
children
?.filter((childItem: DeclarationReflection) =>
@@ -10,6 +10,16 @@ export function replaceTemplateVariables(
return text
.replaceAll("{{alias}}", reflection.getAlias())
.replaceAll("{{alias-lower}}", reflection.getAlias().toLowerCase())
.replaceAll("{{parent.alias}}", reflection.parent?.getAlias() || "")
.replaceAll(
"{{parent.alias-lower}}",
reflection.parent?.getAlias().toLowerCase() || ""
)
.replaceAll(
"{{parent.parent.alias}}",
reflection.parent?.parent?.getAlias() || ""
)
.replaceAll("{{kind}}", getKindAsText(reflection.kind))
}
@@ -15,21 +15,25 @@ import {
import { MarkdownTheme } from "../theme"
import { getProjectChild, getType, getTypeChildren } from "utils"
type ReturnReflectionComponentFormatterParams = {
export type GetReflectionTypeParametersParams = {
reflectionType: SomeType
project: ProjectReflection
comment?: Comment
level: number
level?: number
maxLevel?: number | undefined
wrapObject?: boolean
isReturn?: boolean
}
export function returnReflectionComponentFormatter({
export function getReflectionTypeParameters({
reflectionType,
project,
comment,
level = 1,
maxLevel,
}: ReturnReflectionComponentFormatterParams): Parameter[] {
wrapObject = false,
isReturn = true,
}: GetReflectionTypeParametersParams): Parameter[] {
const typeName = getType({
reflectionType,
collapse: "object",
@@ -39,11 +43,6 @@ export function returnReflectionComponentFormatter({
project,
getRelativeUrlMethod: Handlebars.helpers.relativeURL,
})
// if (
// typeName === "Record&#60;string, unknown&#62; \\| PaymentProcessorError"
// ) {
// console.log(reflectionType)
// }
const type = getType({
reflectionType: reflectionType,
collapse: "object",
@@ -51,28 +50,39 @@ export function returnReflectionComponentFormatter({
escape: true,
getRelativeUrlMethod: Handlebars.helpers.relativeURL,
})
const formatParameter = () => {
return {
name: "name" in reflectionType ? reflectionType.name : typeName,
type,
optional:
"flags" in reflectionType
? (reflectionType.flags as ReflectionFlags).isOptional
: false,
defaultValue:
"declaration" in reflectionType
? getDefaultValue(
reflectionType.declaration as DeclarationReflection
) || ""
: "",
description: comment
? isReturn
? getReturnComment(comment)
: Handlebars.helpers.comment(comment.summary)
: !comment && !isReturn
? loadComment(typeName, project)
: "",
expandable: comment?.hasModifier(`@expandable`) || false,
featureFlag: Handlebars.helpers.featureFlag(comment),
children: [],
}
}
const componentItem: Parameter[] = []
const canRetrieveChildren = level + 1 <= (maxLevel || MarkdownTheme.MAX_LEVEL)
if (reflectionType.type === "reference") {
if (reflectionType.typeArguments || reflectionType.refersToTypeParameter) {
const parentKey = componentItem.push({
name: "name" in reflectionType ? reflectionType.name : typeName,
type,
optional:
"flags" in reflectionType
? (reflectionType.flags as ReflectionFlags).isOptional
: false,
defaultValue:
"declaration" in reflectionType
? getDefaultValue(
reflectionType.declaration as DeclarationReflection
) || ""
: "",
description: comment ? getReturnComment(comment) : "",
expandable: comment?.hasModifier(`@expandable`) || false,
featureFlag: Handlebars.helpers.featureFlag(comment),
children: [],
})
const parentKey = componentItem.push(formatParameter())
const typeArgs = reflectionType.typeArguments
? reflectionType.typeArguments
: "typeParameters" in reflectionType
@@ -89,7 +99,7 @@ export function returnReflectionComponentFormatter({
if (!reflectionTypeArg) {
return
}
const typeArgComponent = returnReflectionComponentFormatter({
const typeArgComponent = getReflectionTypeParameters({
reflectionType: reflectionTypeArg,
project,
level: level + 1,
@@ -102,75 +112,53 @@ export function returnReflectionComponentFormatter({
} else {
const reflection = (reflectionType.reflection ||
getProjectChild(project, reflectionType.name)) as DeclarationReflection
const parentKey = wrapObject
? componentItem.push(formatParameter())
: undefined
if (reflection) {
const reflectionChildren = canRetrieveChildren
? reflection.children ||
getTypeChildren(reflectionType, project, level)
getTypeChildren({
reflectionType,
project,
level,
maxLevel,
})
: undefined
if (reflectionChildren?.length) {
reflectionChildren.forEach((childItem) => {
componentItem.push(
reflectionComponentFormatter({
reflection: childItem as DeclarationReflection,
level,
maxLevel,
project,
})
)
})
} else {
componentItem.push(
reflectionComponentFormatter({
reflection,
const childParameter = reflectionComponentFormatter({
reflection: childItem as DeclarationReflection,
level,
maxLevel,
project,
})
)
parentKey
? componentItem[parentKey - 1].children?.push(childParameter)
: componentItem.push(childParameter)
})
} else {
const childParameter = reflectionComponentFormatter({
reflection,
level,
maxLevel,
project,
})
parentKey
? componentItem[parentKey - 1].children?.push(childParameter)
: componentItem.push(childParameter)
}
} else {
componentItem.push({
name: "name" in reflectionType ? reflectionType.name : typeName,
type,
optional:
"flags" in reflectionType
? (reflectionType.flags as ReflectionFlags).isOptional
: false,
defaultValue:
"declaration" in reflectionType
? getDefaultValue(
reflectionType.declaration as DeclarationReflection
) || ""
: "",
description: comment ? getReturnComment(comment) : "",
expandable: comment?.hasModifier(`@expandable`) || false,
featureFlag: Handlebars.helpers.featureFlag(comment),
children: [],
})
parentKey
? componentItem[parentKey - 1].children?.push(formatParameter())
: componentItem.push(formatParameter())
}
}
} else if (reflectionType.type === "array") {
const parentKey = componentItem.push({
name:
"name" in reflectionType ? (reflectionType.name as string) : typeName,
type,
optional:
"flags" in reflectionType
? (reflectionType.flags as ReflectionFlags).isOptional
: false,
defaultValue:
"declaration" in reflectionType
? getDefaultValue(
reflectionType.declaration as DeclarationReflection
) || ""
: "",
description: comment ? getReturnComment(comment) : "",
expandable: comment?.hasModifier(`@expandable`) || false,
featureFlag: Handlebars.helpers.featureFlag(comment),
children: [],
})
const parentKey = componentItem.push(formatParameter())
if (canRetrieveChildren) {
const elementTypeItem = returnReflectionComponentFormatter({
const elementTypeItem = getReflectionTypeParameters({
reflectionType: reflectionType.elementType,
project,
level: level + 1,
@@ -181,32 +169,19 @@ export function returnReflectionComponentFormatter({
} else if (reflectionType.type === "tuple") {
let pushTo: Parameter[] = []
if (level === 1) {
const parentKey = componentItem.push({
name:
"name" in reflectionType ? (reflectionType.name as string) : typeName,
type,
optional:
"flags" in reflectionType
? (reflectionType.flags as ReflectionFlags).isOptional
: false,
defaultValue:
"declaration" in reflectionType
? getDefaultValue(
reflectionType.declaration as DeclarationReflection
) || ""
: "",
description: comment ? getReturnComment(comment) : "",
expandable: comment?.hasModifier(`@expandable`) || false,
featureFlag: Handlebars.helpers.featureFlag(comment),
children: [],
})
const parentKey = componentItem.push(formatParameter())
pushTo = componentItem[parentKey - 1].children!
} else {
pushTo = componentItem
const parentKey = wrapObject
? componentItem.push(formatParameter())
: undefined
pushTo = parentKey
? componentItem[parentKey - 1].children!
: componentItem
}
if (canRetrieveChildren) {
reflectionType.elements.forEach((element) => {
const elementTypeItem = returnReflectionComponentFormatter({
const elementTypeItem = getReflectionTypeParameters({
reflectionType: element,
project,
level: level + 1,
@@ -215,24 +190,47 @@ export function returnReflectionComponentFormatter({
pushTo.push(...elementTypeItem)
})
}
} else {
// put type as the only component.
componentItem.push({
name: "name" in reflectionType ? reflectionType.name : typeName,
type,
optional:
"flags" in reflectionType
? (reflectionType.flags as ReflectionFlags).isOptional
: true,
defaultValue:
"declaration" in reflectionType
? getDefaultValue(reflectionType.declaration) || ""
: "",
description: comment ? getReturnComment(comment) : "",
expandable: comment?.hasModifier(`@expandable`) || false,
featureFlag: Handlebars.helpers.featureFlag(comment),
children: [],
} else if (reflectionType.type === "intersection") {
const parentKey = wrapObject
? componentItem.push(formatParameter())
: undefined
reflectionType.types.forEach((childType) => {
const childParameter = getReflectionTypeParameters({
reflectionType: childType,
project,
level: level + 1,
maxLevel,
})
parentKey
? componentItem[parentKey - 1].children?.push(...childParameter)
: componentItem.push(...childParameter)
})
} else if (reflectionType.type === "reflection") {
const parentKey =
type === "`object`" && typeName === "object"
? undefined
: componentItem.push(formatParameter())
reflectionType.declaration.children?.forEach((childItem) => {
const childParameter = reflectionComponentFormatter({
reflection: childItem as DeclarationReflection,
level,
maxLevel,
project,
})
parentKey
? componentItem[parentKey - 1].children?.push(childParameter)
: componentItem.push(childParameter)
})
} else {
const parentKey = wrapObject
? componentItem.push(formatParameter())
: undefined
// put type as the only component.
parentKey
? componentItem[parentKey - 1].children?.push(formatParameter())
: componentItem.push(formatParameter())
}
return componentItem
@@ -249,6 +247,23 @@ export function getReturnComment(comment: Comment): string {
.join("\n\n")
}
export function loadComment(
reflectionTypeName: string,
project: ProjectReflection
): string {
// try to load reflection from project
const reflection = getProjectChild(project, reflectionTypeName)
if (reflection) {
return (
reflection.comment?.summary
.map((summaryPart) => summaryPart.text)
.join("\n\n") || ""
)
}
return ""
}
export function isOnlyVoid(reflectionTypes: SomeType[]) {
return reflectionTypes.every(
(type) => type.type === "intrinsic" && type.name === "void"
+27
View File
@@ -50,9 +50,13 @@ export type FormattingOptionType = {
reflectionGroups?: {
[k: string]: boolean
}
reflectionCategories?: {
[k: string]: boolean
}
reflectionTitle?: {
kind: boolean
typeParameters: boolean
prefix?: string
suffix?: string
fullReplacement?: string
}
@@ -66,6 +70,7 @@ export type FormattingOptionType = {
parameterComponentExtraProps?: Record<string, unknown>
mdxImports?: string[]
maxLevel?: number
fileNameSeparator?: string
}
export declare module "typedoc" {
@@ -178,5 +183,27 @@ export declare module "typedoc" {
* @defaultValue true
*/
outputModules: boolean
/**
* Whether to enable category to namespace conversion.
* @defaultValue false
*/
generateNamespaces: boolean
/**
* Optionally specify a parent namespace to place all generated namespaces in.
*/
parentNamespace: string
/**
* Optionally specify a name prefix for all generated namespaces.
*/
namePrefix: string
/**
* Whether to enable the React Query manipulator.
* @defaultValue false
*/
enableReactQueryManipulator: boolean
/**
* Namespace names whose child members should have their own documents.
*/
allReflectionsHaveOwnDocumentInNamespace: string[]
}
}
@@ -1,23 +1,37 @@
/* eslint-disable no-case-declarations */
import { DeclarationReflection, ProjectReflection, SomeType } from "typedoc"
import { getProjectChild } from "./get-project-child"
const MAX_LEVEL = 3
type GetTypeChildrenOptions = {
reflectionType: SomeType
project: ProjectReflection | undefined
level?: number
maxLevel?: number
}
export function getTypeChildren(
reflectionType: SomeType,
project: ProjectReflection | undefined,
level = 1
): DeclarationReflection[] {
export function getTypeChildren({
reflectionType,
project,
level = 1,
maxLevel = 3,
}: GetTypeChildrenOptions): DeclarationReflection[] {
let children: DeclarationReflection[] = []
if (level > MAX_LEVEL) {
if (level > maxLevel) {
return children
}
switch (reflectionType.type) {
case "intersection":
reflectionType.types.forEach((intersectionType) => {
children.push(...getTypeChildren(intersectionType, project, level + 1))
children.push(
...getTypeChildren({
reflectionType: intersectionType,
project,
level: level + 1,
maxLevel,
})
)
})
break
case "reference":
@@ -36,13 +50,16 @@ export function getTypeChildren(
if (referencedReflection.children) {
children = referencedReflection.children
} else if (referencedReflection.type) {
children = getTypeChildren(
referencedReflection.type,
children = getTypeChildren({
reflectionType: referencedReflection.type,
project,
level + 1
)
level: level + 1,
maxLevel,
})
}
} else if (reflectionType.typeArguments?.length) {
// Only useful if the reflection type is `Pick<...>`.
const toKeepChildren: string[] = []
reflectionType.typeArguments.forEach((typeArgument, index) => {
if (reflectionType.name === "Omit" && index > 0) {
switch (typeArgument.type) {
@@ -54,23 +71,69 @@ export function getTypeChildren(
if (childItem.type === "literal") {
removeChild(childItem.value?.toString(), children)
} else {
getTypeChildren(childItem, project, level + 1).forEach(
(child) => {
removeChild(child.name, children)
}
)
getTypeChildren({
reflectionType: childItem,
project,
level: level + 1,
maxLevel,
}).forEach((child) => {
removeChild(child.name, children)
})
}
})
}
} else if (reflectionType.name === "Pick") {
if (index === 0 && !children.length) {
children = getTypeChildren({
reflectionType: typeArgument,
project,
level: level + 1,
maxLevel,
})
} else {
switch (typeArgument.type) {
case "literal":
if (typeArgument.value) {
toKeepChildren.push(typeArgument.value?.toString())
}
break
case "union":
typeArgument.types.forEach((childItem) => {
if (childItem.type === "literal") {
if (childItem.value) {
toKeepChildren.push(childItem.value?.toString())
}
} else {
getTypeChildren({
reflectionType: childItem,
project,
level: level + 1,
maxLevel,
}).forEach((child) => {
if (child.name) {
toKeepChildren.push(child.name)
}
})
}
})
}
}
} else {
const typeArgumentChildren = getTypeChildren(
typeArgument,
const typeArgumentChildren = getTypeChildren({
reflectionType: typeArgument,
project,
level + 1
)
level: level + 1,
maxLevel,
})
children.push(...typeArgumentChildren)
}
})
if (toKeepChildren.length) {
children = children.filter((child) =>
toKeepChildren.includes(child.name)
)
}
}
break
case "reflection":
@@ -79,7 +142,12 @@ export function getTypeChildren(
]
break
case "array":
children = getTypeChildren(reflectionType.elementType, project, level + 1)
children = getTypeChildren({
reflectionType: reflectionType.elementType,
project,
level: level + 1,
maxLevel,
})
}
return filterChildren(children)