chore: improve JS Client TSDoc comments (#5582)

* add oas schema to tsdoc parser

* add tsdoc (part 1)

* Finished tsdoc in js client

* general fixes

* added tsdoc in core medusa package

* parse schema tags in model files

* added maxlevel option

* added more tsdoc

* added tsdoc in core

* added TSDoc in core package

* generated client types

* support featureFlag and expandable tags

* added support for resource feature flag note

* fix api ignore plugin

* added eslint plugin

* support feature flag and expandable badges

* adjusted overview page + generated reference

* revert generated files

* added changeset

* add details about new typedoc options

* fix broken link
This commit is contained in:
Shahed Nasser
2023-11-09 12:51:17 +02:00
committed by GitHub
parent c68da6d685
commit 91615f9c45
748 changed files with 12354 additions and 1684 deletions
+1 -1
View File
@@ -25,12 +25,12 @@
"typedoc": "0.25.1",
"typedoc-config": "*",
"typedoc-monorepo-link-types": "^0.0.2",
"typedoc-plugin-custom": "*",
"typedoc-plugin-frontmatter": "*",
"typedoc-plugin-markdown": "3.16.0",
"typedoc-plugin-markdown-medusa": "*",
"typedoc-plugin-merge-modules": "5.1.0",
"typedoc-plugin-missing-exports": "^2.1.0",
"typedoc-plugin-modules": "*",
"typedoc-plugin-reference-excluder": "1.1.3",
"typedoc-plugin-rename-defaults": "^0.6.6",
"typescript": "^5.2.2"
@@ -1,4 +1,18 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path")
const pathPrefix = path.join(__dirname, "..", "..", "..")
/** @type {import('typedoc').TypeDocOptions} */
module.exports = {
// extends: [typedocConfig],
plugin: ["typedoc-plugin-markdown-medusa"],
readme: "none",
eslintPathName: path.join(
pathPrefix,
"www/packages/eslint-config-docs/content.js"
),
pluginsResolvePath: path.join(pathPrefix, "www"),
// Uncomment this when debugging
// showConfig: true,
}
@@ -70,10 +70,9 @@ module.exports = ({
...globalTypedocOptions,
entryPoints: [path.join(pathPrefix, entryPointPath)],
out: [path.join(pathPrefix, outPath)],
tsconfig: path.join(
pathPrefix,
tsconfigPath || "packages/types/tsconfig.json"
),
tsconfig: tsconfigPath.length
? path.join(pathPrefix, tsconfigPath)
: path.join(__dirname, "extended-tsconfig", "types.json"),
name: `${moduleName} Reference`,
indexTitle: `${moduleName} Reference`,
entryDocument: "index.md",
@@ -82,7 +81,7 @@ module.exports = ({
hideBreadcrumbs: true,
plugin: [
...globalTypedocOptions.plugin,
"typedoc-plugin-modules",
"typedoc-plugin-custom",
"typedoc-plugin-rename-defaults",
"typedoc-plugin-frontmatter",
],
@@ -8,7 +8,7 @@ module.exports = {
...globalTypedocOptions,
entryPoints: [path.join(pathPrefix, "packages/medusa/src/models/index.ts")],
out: [path.join(pathPrefix, "www/apps/docs/content/references/entities")],
tsconfig: path.join(pathPrefix, "packages/medusa/tsconfig.json"),
tsconfig: path.join(__dirname, "extended-tsconfig", "medusa.json"),
name: "Entities Reference",
indexTitle: "Entities Reference",
entryDocument: "_index.mdx",
@@ -0,0 +1,6 @@
{
"$schema": "http://json.schemastore.org/tsconfig",
"extends": [
"../../../../packages/medusa-js/tsconfig.json"
]
}
@@ -0,0 +1,6 @@
{
"$schema": "http://json.schemastore.org/tsconfig",
"extends": [
"../../../../packages/medusa/tsconfig.json"
]
}
@@ -0,0 +1,22 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json",
"extends": ["typedoc/tsdoc.json"],
"tagDefinitions": [
{
"tagName": "@featureFlag",
"syntaxKind": "block"
},
{
"tagName": "@expandable",
"syntaxKind": "modifier"
},
{
"tagName": "@schema",
"syntaxKind": "block"
},
{
"tagName": "@apiIgnore",
"syntaxKind": "modifier"
}
]
}
@@ -0,0 +1,6 @@
{
"$schema": "http://json.schemastore.org/tsconfig",
"extends": [
"../../../../packages/types/tsconfig.json"
]
}
+34 -20
View File
@@ -4,12 +4,35 @@ const globalTypedocOptions = require("./_base")
const pathPrefix = path.join(__dirname, "..", "..", "..")
const defaultFormattingOptions = {
showCommentsAsHeader: true,
sections: {
member_sources_definedIn: false,
reflection_hierarchy: false,
member_signature_typeParameters: false,
member_signature_sources: false,
member_signature_title: false,
member_signature_returns: false,
},
parameterStyle: "component",
parameterComponent: "ParameterTypes",
mdxImports: [
`import ParameterTypes from "@site/src/components/ParameterTypes"`,
],
reflectionGroups: {
Constructors: false,
},
frontmatterData: {
displayed_sidebar: "jsClientSidebar",
},
}
/** @type {import('typedoc').TypeDocOptions} */
module.exports = {
...globalTypedocOptions,
entryPoints: [path.join(pathPrefix, "packages/medusa-js/src/resources")],
entryPointStrategy: "expand",
out: [path.join(pathPrefix, "www/apps/docs/content/references/js-client")],
tsconfig: path.join(pathPrefix, "packages/medusa-js/tsconfig.json"),
tsconfig: path.join(__dirname, "extended-tsconfig", "js-client.json"),
name: "JS Client Reference",
indexTitle: "JS Client Reference",
entryDocument: "_index.mdx",
@@ -17,35 +40,26 @@ module.exports = {
hideBreadcrumbs: true,
plugin: [
...globalTypedocOptions.plugin,
"typedoc-plugin-reference-excluder",
"typedoc-plugin-frontmatter",
"typedoc-plugin-rename-defaults",
"typedoc-plugin-modules",
"typedoc-plugin-custom",
],
exclude: [
path.join(pathPrefix, "packages/medusa-js/src/resources/base.ts"),
path.join(pathPrefix, "node_modules/**"),
path.join(pathPrefix, "packages/**/node_modules"),
],
excludeConstructors: true,
frontmatterData: {
displayed_sidebar: "jsClientSidebar",
},
internalModule: "internal",
formatting: {
"*": {
showCommentsAsHeader: true,
sections: {
member_sources_definedIn: false,
reflection_hierarchy: false,
"*": defaultFormattingOptions,
"^classes/": {
...defaultFormattingOptions,
frontmatterData: {
...defaultFormattingOptions.frontmatterData,
slug: "/references/js-client/{{alias}}",
},
parameterStyle: "component",
parameterComponent: "ParameterTypes",
mdxImports: [
`import ParameterTypes from "@site/src/components/ParameterTypes"`,
],
},
},
objectLiteralTypeDeclarationStyle: "component",
mdxOutput: true,
maxLevel: 4,
ignoreApi: true,
}
@@ -5,6 +5,9 @@
"publishConfig": {
"access": "public"
},
"peerDependencies": {
"typedoc": "0.25.x"
},
"main": "_base.js",
"version": "0.0.0"
}
@@ -8,7 +8,7 @@ module.exports = {
...globalTypedocOptions,
entryPoints: [path.join(pathPrefix, "packages/medusa/src/services/index.ts")],
out: [path.join(pathPrefix, "www/apps/docs/content/references/services")],
tsconfig: path.join(pathPrefix, "packages/medusa/tsconfig.json"),
tsconfig: path.join(__dirname, "extended-tsconfig", "medusa.json"),
name: "Services Reference",
indexTitle: "Services Reference",
entryDocument: "index.md",
@@ -0,0 +1,44 @@
# typedoc-plugin-custom
A Typedoc plugin that combines utility plugins for documenting references.
## Configurations
### Resolve Reference Configurations
The [`resolve-reference-plugin`](./src/resolve-references-plugin.ts) imitates the [`typedoc-plugin-missing-exports`](https://www.npmjs.com/package/typedoc-plugin-missing-exports) plugin. So, it accepts the same options as the [`typedoc-plugin-missing-exports`](https://www.npmjs.com/package/typedoc-plugin-missing-exports) plugin.
### Frontmatter
`frontmatterData` is an object of key-value pairs. If none provided, no frontmatter variables will be added to the Markdown files.
An example of passing it in a JavaScript configuration file:
```js
frontmatterData: {
displayed_sidebar: "jsClientSidebar",
},
```
### API Ignore
The `ignoreApi` option is a boolean that indicates whether reflections with the `@apiIgnore` tag should be ignored. If enabled, reflections having this tag are removed from the generated documentation. If disabled (which is the default), the `@apiIgnore` tag is removed from the reflection to ensure it's not shown in the generated documentation.
### ESLint
If the `eslintPathName` option is set, code snippets provided in `@example` tag are linted based on the documentation's ESLint rules.
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.
---
## Build the Plugin
Before using any command that makes use of this plugin, make sure to run the `build` command:
```bash
yarn build
```
@@ -1,5 +1,5 @@
{
"name": "typedoc-plugin-modules",
"name": "typedoc-plugin-custom",
"private": true,
"version": "0.0.0",
"license": "MIT",
@@ -32,7 +32,10 @@
"typedoc"
],
"dependencies": {
"eslint": "^8.53.0",
"glob": "^10.3.10",
"typedoc-plugin-markdown": "^3.16.0"
"typedoc-plugin-markdown": "^3.16.0",
"utils": "*",
"yaml": "^2.3.3"
}
}
@@ -0,0 +1,30 @@
import {
Application,
Context,
Converter,
ParameterType,
ReflectionKind,
} from "typedoc"
export function load(app: Application) {
app.options.addDeclaration({
name: "ignoreApi",
help: "Whether to ignore items with the `@apiIgnore` tag.",
type: ParameterType.Boolean, // The default
defaultValue: false,
})
app.converter.on(Converter.EVENT_RESOLVE_BEGIN, (context: Context) => {
for (const reflection of context.project.getReflectionsByKind(
ReflectionKind.All
)) {
if (reflection.comment?.hasModifier("@apiIgnore")) {
if (app.options.getValue("ignoreApi")) {
context.project.removeReflection(reflection)
} else {
reflection.comment.removeModifier(`@apiIgnore`)
}
}
}
})
}
@@ -0,0 +1,65 @@
import { ESLint } from "eslint"
import {
Application,
Context,
Converter,
ParameterType,
ReflectionKind,
} from "typedoc"
export function load(app: Application) {
app.options.addDeclaration({
name: "eslintPathName",
help: "The path to the ESLint configurations to apply.",
type: ParameterType.String,
})
app.options.addDeclaration({
name: "pluginsResolvePath",
help: "The path to resolve plugins used in the ESLint configurations.",
type: ParameterType.String,
})
app.converter.on(Converter.EVENT_RESOLVE_BEGIN, async (context: Context) => {
const eslintConfigPath = app.options.getValue("eslintPathName") as string
const pluginsResolvePath = app.options.getValue(
"pluginsResolvePath"
) as string
if (!eslintConfigPath) {
return
}
const eslint = new ESLint({
overrideConfigFile: eslintConfigPath,
resolvePluginsRelativeTo: pluginsResolvePath,
fix: true,
})
for (const reflection of context.project.getReflectionsByKind(
ReflectionKind.All
)) {
const example = reflection.comment?.getTag(`@example`)
if (example) {
await Promise.all(
example.content
.filter((exampleContent) => exampleContent.kind === "code")
.map(async (exampleContent) => {
const matchedCodeSnippets = exampleContent.text.matchAll(
/```ts(?<code>[\s\S.]*)```/g
)
for (const matchedCodeSnippet of matchedCodeSnippets) {
if (!matchedCodeSnippet.groups?.code) {
return
}
const result = await eslint.lintText(
matchedCodeSnippet.groups.code
)
exampleContent.text =
result.length > 0 && result[0].output
? "```ts\n" + result[0].output + "```"
: exampleContent.text
}
})
)
}
}
})
}
@@ -0,0 +1,14 @@
import { Application } from "typedoc"
import { load as resolveReferencesPluginLoad } from "./resolve-references-plugin"
import { load as frontmatterPlugin } from "./frontmatter-plugin"
import { load as parseOasSchemaPlugin } from "./parse-oas-schema-plugin"
import { load as apiIgnorePlugin } from "./api-ignore"
import { load as eslintExamplePlugin } from "./eslint-example"
export function load(app: Application) {
resolveReferencesPluginLoad(app)
frontmatterPlugin(app)
parseOasSchemaPlugin(app)
apiIgnorePlugin(app)
eslintExamplePlugin(app)
}
@@ -0,0 +1,234 @@
import {
Application,
Comment,
CommentDisplayPart,
CommentTag,
Context,
Converter,
DeclarationReflection,
Reflection,
ReflectionKind,
SomeType,
} from "typedoc"
import { parse } from "yaml"
import { getTypeChildren } from "utils"
// a simplified schema type
// mainly focusing on properties used
// within this plugin
type Schema = {
description?: string
properties?: SchemaProperties
allOf?: SchemaProperty[]
oneOf?: SchemaProperty[]
}
type SchemaProperties = {
[k: string]: SchemaProperty
}
type SchemaProperty = {
description?: string
"x-expandable"?: string
"x-featureFlag"?: string
default?: string
items?: Schema
properties?: SchemaProperties
allOf?: SchemaProperty[]
oneOf?: SchemaProperty[]
}
export function load(app: Application) {
const definedSchemas = new Map<string, Schema>()
// Since some files, such as models, include the
// `@schema` declaration at the end of the file, i.e. not
// before the related class/type/interface, this extracts
// those schemas and applies them to reflections having the same
// name, if those reflections don't have a schema comment of their own.
const origConvertSymbol = app.converter.convertSymbol
app.converter.convertSymbol = (context, symbol, exportSymbol) => {
if (symbol.valueDeclaration) {
const sourceFile = symbol.valueDeclaration?.getSourceFile()
// find block comments
const blockCommentMatch = sourceFile.text.matchAll(
/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm
)
for (const blockMatch of blockCommentMatch) {
blockMatch.forEach((matched) => {
if (!matched) {
return
}
const schemaStartIndex = matched.indexOf("@schema")
if (schemaStartIndex === -1) {
return
}
// find end index
let schemaEndIndex = matched.indexOf(" * @", schemaStartIndex)
if (schemaEndIndex === -1) {
schemaEndIndex = matched.length
}
const schemaText = matched
.substring(schemaStartIndex, schemaEndIndex)
.replaceAll(" */", "")
.replaceAll("*/", "")
.replaceAll(" * ", "")
const { name: schemaName = "" } =
/@schema (?<name>\w+)/.exec(schemaText)?.groups || {}
if (!schemaName || definedSchemas.has(schemaName)) {
return
}
// attempt to parse schema and save it
try {
const parsedSchema = parse(
schemaText.replace("@schema", "schema:")
) as Schema
definedSchemas.set(schemaName, parsedSchema)
} catch (e) {
// ignore errors as the schema may be malformed.
console.error(`Error parsing schema ${schemaName}: ${e}`)
}
})
}
}
return origConvertSymbol(context, symbol, exportSymbol)
}
app.converter.on(Converter.EVENT_RESOLVE_BEGIN, (context: Context) => {
for (const reflection of context.project.getReflectionsByKind(
ReflectionKind.All
)) {
let schema: Schema | undefined
let { comment } = reflection
const schemaTags = comment?.getTags(`@schema`)
if (schemaTags?.length) {
schemaTags.forEach((part) => {
if (part.content.length) {
const schemaComment = prepareSchemaComment(part.content)
schema = parse(schemaComment)
}
})
reflection.comment?.removeTags("@schema")
} else if (!comment) {
if (definedSchemas.has(reflection.name)) {
schema = definedSchemas.get(reflection.name)
comment = new Comment()
} else if ("type" in reflection && reflection.type) {
const reflectionType = reflection.type as SomeType
if (
"name" in reflectionType &&
definedSchemas.has(reflectionType.name)
) {
schema = definedSchemas.get(reflectionType.name)
comment = new Comment()
}
}
}
if (schema) {
if (schema.description) {
comment?.summary.push({
kind: "text",
text: schema.description,
})
}
if (reflection.kind === ReflectionKind.TypeAlias) {
comment?.modifierTags.add(`@interface`)
}
addComments(schema, reflection)
if (!reflection.comment && comment) {
reflection.comment = comment
}
}
}
})
}
function prepareSchemaComment(commentParts: CommentDisplayPart[]) {
let result = `schema: `
commentParts.forEach((commentPart) => {
result += commentPart.text
})
return result
}
function addComments(schema: Schema, reflection: Reflection) {
if (!schema.properties) {
if (schema.allOf) {
schema.allOf.forEach((valueChild) => {
addComments(valueChild, reflection)
})
} else if (schema.oneOf) {
schema.oneOf.forEach((valueChild) => {
addComments(valueChild, reflection)
})
}
return
}
const children =
"type" in reflection
? getTypeChildren(reflection.type as SomeType, reflection.project)
: "children" in reflection
? (reflection.children as DeclarationReflection[])
: []
Object.entries(schema.properties).forEach(([key, value]) => {
const childItem =
children.find((child) => child.name === key) ||
reflection.getChildByName(key)
if (childItem) {
if (!childItem.comment) {
const comment = new Comment()
comment.summary.push({
kind: "text",
text: value.description || "",
})
childItem.comment = comment
}
if (
value.default !== undefined &&
"defaultValue" in childItem &&
!childItem.defaultValue
) {
childItem.defaultValue = value.default
}
if (value["x-expandable"]) {
childItem.comment.modifierTags.add(`@expandable`)
}
if (value["x-featureFlag"]) {
childItem.comment.blockTags.push(
new CommentTag(`@featureFlag`, [
{
kind: `inline-tag`,
text: value["x-featureFlag"],
tag: `@featureFlag`,
},
])
)
}
if (value.items) {
addComments(value.items, childItem)
}
if (value.properties || value.allOf || value.oneOf) {
addComments(value, childItem)
}
}
})
}
@@ -1,45 +0,0 @@
# typedoc-plugin-frontmatter
A Typedoc plugin that allows inserting frontmatter key-value pairs at the top of the exported Markdown files.
> For more flexibility in what pages to apply the frontmatter data to, for example, to specify different frontmatter for different pages, ues the [`typedoc-plugin-markdown-medusa` plugin's `frontmatterData` option](../typedoc-plugin-markdown-medusa/README.md#configurations)
## Configurations
The following options are optional and can be used to customize the configurations of the plugin.
### frontmatterData
`frontmatterData` is an object of key-value pairs. If none provided, no frontmatter variables will be added to the Markdown files.
An example of passing it in a JavaScript configuration file:
```js
frontmatterData: {
displayed_sidebar: "jsClientSidebar",
},
```
If passing the option in the command line the value should be a JSON object.
### pagesPattern
`pagesPattern` is a string that contains a regular expression. This allows you to limit the pages the frontmatter variables should be added to.
By default, the frontmatter variables will be added to all files.
An example of passing it in a JavaScript configuration file:
```js
frontmatterData: {
pagesPattern: "internal\\.",
},
```
## Build the Plugin
Before using any command that makes use of this plugin, make sure to run the `build` command:
```bash
yarn build
```
@@ -1,37 +0,0 @@
{
"name": "typedoc-plugin-frontmatter",
"private": true,
"license": "MIT",
"publishConfig": {
"access": "public"
},
"version": "0.0.0",
"description": "Plugin to add frontmatter key-values at the top of pages",
"main": "./dist/index.js",
"exports": "./dist/index.js",
"files": [
"dist"
],
"author": "Shahed Nasser",
"scripts": {
"build": "tsc",
"watch": "tsc --watch",
"lint": "eslint --ext .ts src"
},
"peerDependencies": {
"typedoc": "0.25.x"
},
"devDependencies": {
"@types/node": "^16.11.10",
"typescript": "^4.6"
},
"keywords": [
"typedocplugin",
"packages",
"monorepo",
"typedoc"
],
"dependencies": {
"yaml": "^2.3.2"
}
}
@@ -23,6 +23,7 @@ Aside from the options detailed in [typedoc-plugin-markdown](https://github.com/
- `frontmatterData`: (optional) an object that will be injected as frontmatter to the pages matching specified pattern.
- `parameterComponent`: (optional) a string indicating the name of a React component to pass the parameters as an object to. This is only used if the `parameterStyle` option is set to `component`. This also must be used with the `mdxOutput` option enabled, and an import string for the component passed to the `mdxImports` option. The React component will receive a `parameters` prop, which is an array of type [Parameter](./src/types.ts#L95).
- `mdxImports`: (optional) an array of strings, each holding an import statement that will be added to the beginning of each page. For example, `["import ParameterTypes from "@site/src/components/ParameterTypes""]`. Must be used along with the `mdxOutput` option enabled.
- `maxLevel`: (optional) a number indicating the maximum level parameters and return types are expanded. Default is `3`.
## Build Plugin
@@ -23,7 +23,8 @@
"devDependencies": {
"@types/node": "^16.11.10",
"copyfiles": "^2.4.1",
"typescript": "^4.6"
"typescript": "^4.6",
"utils": "*"
},
"keywords": [
"typedocplugin",
@@ -109,5 +109,12 @@ export function load(app: Application) {
type: ParameterType.Boolean,
defaultValue: false,
})
app.options.addDeclaration({
help: "[Markdown Plugin] The maximum level to expand when retrieving reflection types.",
name: "maxLevel",
type: ParameterType.Number,
defaultValue: 3,
})
}
export { MarkdownTheme }
@@ -43,6 +43,8 @@ import typeParameterComponentHelper from "./resources/helpers/type-parameter-com
import showPropertiesAsComponentHelper from "./resources/helpers/show-properties-as-component"
import commentTagHelper from "./resources/helpers/comment-tag"
import exampleHelper from "./resources/helpers/example"
import ifFeatureFlagHelper from "./resources/helpers/if-feature-flag"
import featureFlagHelper from "./resources/helpers/feature-flag"
import { MarkdownTheme } from "./theme"
// test
@@ -116,4 +118,6 @@ export function registerHelpers(theme: MarkdownTheme) {
showPropertiesAsComponentHelper(theme)
commentTagHelper(theme)
exampleHelper()
ifFeatureFlagHelper()
featureFlagHelper()
}
@@ -1,6 +1,8 @@
import * as Handlebars from "handlebars"
import { Comment } from "typedoc"
const EXCLUDED_TAGS = ["@returns", "@example", "@featureFlag"]
export default function () {
Handlebars.registerHelper(
"comments",
@@ -18,9 +20,9 @@ export default function () {
}
if (showTags && comment.blockTags?.length) {
const filteredTags = comment.blockTags
.filter((tag) => tag.tag !== "@returns")
.filter((tag) => tag.tag !== "@example")
const filteredTags = comment.blockTags.filter(
(tag) => !EXCLUDED_TAGS.includes(tag.tag)
)
const tags = filteredTags.map((tag) => {
return Handlebars.helpers.commentTag(
tag,
@@ -0,0 +1,21 @@
import * as Handlebars from "handlebars"
import { Comment } from "typedoc"
export default function () {
Handlebars.registerHelper(
"featureFlag",
function (comment: Comment | undefined) {
if (!comment) {
return undefined
}
const featureFlagTag = comment.getTag("@featureFlag")
if (!featureFlagTag) {
return undefined
}
return featureFlagTag.content
.map((tagContent) => tagContent.text)
.join("")
}
)
}
@@ -0,0 +1,13 @@
import * as Handlebars from "handlebars"
import { Reflection } from "typedoc"
export default function () {
Handlebars.registerHelper(
"ifFeatureFlag",
function (this: Reflection, options: Handlebars.HelperOptions) {
return this.comment?.getTag("@featureFlag") !== undefined
? options.fn(this)
: options.inverse(this)
}
)
}
@@ -0,0 +1,13 @@
{{#with model}}
{{#ifFeatureFlag}}
:::note
To use this resource, make sure to [enable its feature flag: {{{featureFlag comment}}}](https://docs.medusajs.com/development/feature-flags/toggle)
:::
{{/ifFeatureFlag}}
{{/with}}
@@ -4,6 +4,8 @@
{{/ifShowPageTitle}}
{{> feature-flag}}
{{#with model}}
{{{reflectionDescription}}}
@@ -56,6 +56,8 @@ export class MarkdownTheme extends Theme {
static URL_PREFIX = /^(http|ftp)s?:\/\//
static MAX_LEVEL = 3
constructor(renderer: Renderer) {
super(renderer)
@@ -85,6 +87,7 @@ export class MarkdownTheme extends Theme {
"formatting"
) as FormattingOptionsType
this.mdxOutput = this.getOption("mdxOutput") as boolean
MarkdownTheme.MAX_LEVEL = this.getOption("maxLevel") as number
this.listenTo(this.owner, {
[RendererEvent.BEGIN]: this.onBeginRenderer,
@@ -74,6 +74,7 @@ export type FormattingOptionType = {
frontmatterData?: Record<string, unknown>
parameterComponent?: string
mdxImports?: string[]
maxLevel?: number
}
export type FormattingOptionsType = {
@@ -98,6 +99,8 @@ export type Parameter = {
optional?: boolean
defaultValue?: string
description?: string
featureFlag?: string
expandable: boolean
children?: Parameter[]
}
@@ -64,6 +64,10 @@ export function stripLineBreaks(str: string) {
: ""
}
export function stripCode(str: string) {
return stripLineBreaks(str.replace("```ts", "").replace("```", ""))
}
export function camelToTitleCase(text: string) {
return (
text.substring(0, 1).toUpperCase() +
@@ -1,17 +1,21 @@
import {
Comment,
DeclarationReflection,
ReflectionKind,
ProjectReflection,
ReflectionType,
SomeType,
} from "typedoc"
import { Comment, ReflectionKind, ReflectionType } from "typedoc"
import * as Handlebars from "handlebars"
import { stripLineBreaks } from "../utils"
import { stripCode, stripLineBreaks } from "../utils"
import { Parameter, ParameterStyle, ReflectionParameterType } from "../types"
import getType, { getReflectionType } from "./type-utils"
import { getTypeChildren } from "utils"
import { MarkdownTheme } from "../theme"
const MAX_LEVEL = 3
const ALLOWED_KINDS: ReflectionKind[] = [
ReflectionKind.EnumMember,
ReflectionKind.TypeParameter,
ReflectionKind.Property,
ReflectionKind.Parameter,
ReflectionKind.TypeAlias,
ReflectionKind.TypeLiteral,
ReflectionKind.Variable,
ReflectionKind.Reference,
]
export default function reflectionFormatter(
reflection: ReflectionParameterType,
@@ -57,7 +61,10 @@ export function reflectionListFormatter(
const hasChildren = "children" in reflection && reflection.children?.length
if ((reflection.type || hasChildren) && level + 1 <= MAX_LEVEL) {
if (
(reflection.type || hasChildren) &&
level + 1 <= MarkdownTheme.MAX_LEVEL
) {
const children = hasChildren
? reflection.children
: getTypeChildren(reflection.type!, reflection.project)
@@ -94,24 +101,32 @@ export function reflectionComponentFormatter(
? getType(reflection.type, "object")
: getReflectionType(reflection, "object"),
description: comments
? stripLineBreaks(Handlebars.helpers.comments(comments))
? stripLineBreaks(Handlebars.helpers.comments(comments, true, false))
: "",
optional,
defaultValue,
expandable: reflection.comment?.hasModifier(`@expandable`) || false,
featureFlag: Handlebars.helpers.featureFlag(reflection.comment),
children: [],
}
const hasChildren = "children" in reflection && reflection.children?.length
if ((reflection.type || hasChildren) && level + 1 <= MAX_LEVEL) {
if (
(reflection.type || hasChildren) &&
level + 1 <= MarkdownTheme.MAX_LEVEL
) {
const children = hasChildren
? reflection.children
: getTypeChildren(reflection.type!, reflection.project)
children?.forEach((childItem) => {
componentItem.children?.push(
reflectionComponentFormatter(childItem, level + 1)
)
})
children
?.filter((childItem) => childItem.kindOf(ALLOWED_KINDS))
.forEach((childItem) => {
componentItem.children?.push(
reflectionComponentFormatter(childItem, level + 1)
)
})
}
return componentItem
@@ -195,11 +210,17 @@ export function getTableHeaders(
export function getDefaultValue(
parameter: ReflectionParameterType
): string | null {
if (!("defaultValue" in parameter)) {
const defaultComment = parameter.comment?.getTag(`@defaultValue`)
if (!("defaultValue" in parameter) && !defaultComment) {
return null
}
return parameter.defaultValue && parameter.defaultValue !== "..."
? `\`${parameter.defaultValue}\``
return "defaultValue" in parameter &&
parameter.defaultValue !== undefined &&
parameter.defaultValue !== "..."
? `${parameter.defaultValue}`
: defaultComment
? defaultComment.content.map((content) => stripCode(content.text)).join()
: null
}
@@ -246,28 +267,3 @@ function getItemExpandText(
return "It accepts the following properties"
}
}
export function getTypeChildren(
reflectionType: SomeType,
project: ProjectReflection
) {
let children: DeclarationReflection[] = []
switch (reflectionType.type) {
case "reference":
// eslint-disable-next-line no-case-declarations
const referencedReflection = project?.getChildByName(reflectionType.name)
if (
referencedReflection instanceof DeclarationReflection &&
referencedReflection.children
) {
children = referencedReflection.children
}
break
case "array":
children = getTypeChildren(reflectionType.elementType, project)
}
return children
}
@@ -12,8 +12,7 @@ import {
getDefaultValue,
reflectionComponentFormatter,
} from "./reflection-formatter"
const MAX_LEVEL = 3
import { MarkdownTheme } from "../theme"
export function returnReflectionComponentFormatter(
reflectionType: SomeType,
@@ -41,9 +40,14 @@ export function returnReflectionComponentFormatter(
) || ""
: "",
description: comment ? getReturnComment(comment) : "",
expandable: comment?.hasModifier(`@expandable`) || false,
featureFlag: Handlebars.helpers.featureFlag(comment),
children: [],
})
if (!isOnlyVoid(reflectionType.typeArguments) && level + 1 <= MAX_LEVEL) {
if (
!isOnlyVoid(reflectionType.typeArguments) &&
level + 1 <= MarkdownTheme.MAX_LEVEL
) {
reflectionType.typeArguments.forEach((typeArg) => {
const typeArgComponent = returnReflectionComponentFormatter(
typeArg,
@@ -95,9 +99,11 @@ export function returnReflectionComponentFormatter(
) || ""
: "",
description: comment ? getReturnComment(comment) : "",
expandable: comment?.hasModifier(`@expandable`) || false,
featureFlag: Handlebars.helpers.featureFlag(comment),
children: [],
})
if (level + 1 <= MAX_LEVEL) {
if (level + 1 <= MarkdownTheme.MAX_LEVEL) {
const elementTypeItem = returnReflectionComponentFormatter(
reflectionType.elementType,
project,
@@ -126,13 +132,15 @@ export function returnReflectionComponentFormatter(
) || ""
: "",
description: comment ? getReturnComment(comment) : "",
expandable: comment?.hasModifier(`@expandable`) || false,
featureFlag: Handlebars.helpers.featureFlag(comment),
children: [],
})
pushTo = componentItem[parentKey - 1].children!
} else {
pushTo = componentItem
}
if (level + 1 <= MAX_LEVEL) {
if (level + 1 <= MarkdownTheme.MAX_LEVEL) {
reflectionType.elements.forEach((element) => {
const elementTypeItem = returnReflectionComponentFormatter(
element,
@@ -159,6 +167,8 @@ export function returnReflectionComponentFormatter(
? getDefaultValue(reflectionType.declaration) || ""
: "",
description: comment ? getReturnComment(comment) : "",
expandable: comment?.hasModifier(`@expandable`) || false,
featureFlag: Handlebars.helpers.featureFlag(comment),
children: [],
})
}
@@ -1,2 +0,0 @@
dist
.yarn
@@ -1,15 +0,0 @@
# typedoc-plugin-modules
A Typedoc plugin that includes helper plugins for documenting modules. The `resolve-reference-plugin` imitates the [`typedoc-plugin-missing-exports`](https://www.npmjs.com/package/typedoc-plugin-missing-exports) plugin.
## Configurations
Accepts the same options as the [`typedoc-plugin-missing-exports`](https://www.npmjs.com/package/typedoc-plugin-missing-exports) plugin.
## Build the Plugin
Before using any command that makes use of this plugin, make sure to run the `build` command:
```bash
yarn build
```
@@ -1,6 +0,0 @@
import { Application } from "typedoc"
import { load as resolveReferencesPluginLoad } from "./resolve-references-plugin"
export function load(app: Application) {
resolveReferencesPluginLoad(app)
}
+26
View File
@@ -0,0 +1,26 @@
{
"name": "utils",
"private": true,
"license": "MIT",
"publishConfig": {
"access": "public"
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"version": "0.0.0",
"scripts": {
"build": "yarn clean && tsc",
"lint": "eslint --ext .ts src",
"clean": "rimraf dist"
},
"peerDependencies": {
"typedoc": "0.25.x"
},
"devDependencies": {
"@types/node": "^16.11.10",
"typescript": "^4.6"
},
"dependencies": {
"rimraf": "^5.0.5"
}
}
@@ -0,0 +1,43 @@
import {
DeclarationReflection,
ProjectReflection,
ReflectionKind,
} from "typedoc"
export function getProjectChild(
project: ProjectReflection,
childName: string
): DeclarationReflection | undefined {
let reflection: DeclarationReflection | undefined = project.getChildByName(
childName
) as DeclarationReflection
const splitChildName = childName.split(".")
if (!reflection && splitChildName.length > 1) {
reflection = getProjectChild(
project,
splitChildName[splitChildName.length - 1]
)
}
if (
!reflection &&
project.parent &&
project.parent instanceof ProjectReflection
) {
reflection = getProjectChild(project.parent, childName)
}
if (!reflection) {
const modules = project.getChildrenByKind(ReflectionKind.Module)
for (const module of modules) {
reflection = module.getChildByName(childName) as DeclarationReflection
if (reflection) {
break
}
}
}
return reflection
}
@@ -0,0 +1,76 @@
import { DeclarationReflection, ProjectReflection, SomeType } from "typedoc"
import { getProjectChild } from "./get-project-child"
export function getTypeChildren(
reflectionType: SomeType,
project: ProjectReflection | undefined
): DeclarationReflection[] {
let children: DeclarationReflection[] = []
switch (reflectionType.type) {
case "intersection":
reflectionType.types.forEach((intersectionType) => {
children.push(...getTypeChildren(intersectionType, project))
})
break
case "reference":
// eslint-disable-next-line no-case-declarations
const referencedReflection =
reflectionType.reflection && "children" in reflectionType.reflection
? reflectionType.reflection
: project
? getProjectChild(project, reflectionType.name)
: undefined
if (referencedReflection instanceof DeclarationReflection) {
if (referencedReflection.children) {
children = referencedReflection.children
} else if (reflectionType.typeArguments?.length) {
reflectionType.typeArguments.forEach((typeArgument, index) => {
if (reflectionType.name === "Omit" && index > 0) {
switch (typeArgument.type) {
case "literal":
removeChild(typeArgument.value?.toString(), children)
break
case "union":
typeArgument.types.forEach((childItem) => {
if (childItem.type === "literal") {
removeChild(childItem.value?.toString(), children)
} else {
getTypeChildren(childItem, project).forEach((child) => {
removeChild(child.name, children)
})
}
})
}
} else {
const typeArgumentChildren = getTypeChildren(
typeArgument,
project
)
children.push(...typeArgumentChildren)
}
})
} else if (referencedReflection.type) {
children = getTypeChildren(referencedReflection.type, project)
}
}
break
case "reflection":
children = reflectionType.declaration.children || [
reflectionType.declaration,
]
break
case "array":
children = getTypeChildren(reflectionType.elementType, project)
}
return children
}
function removeChild(name: unknown, children: DeclarationReflection[]) {
const childIndex = children.findIndex((child) => child.name === name)
if (childIndex !== -1) {
children.splice(childIndex, 1)
}
}
+2
View File
@@ -0,0 +1,2 @@
export * from "./get-type-children"
export * from "./get-project-child"
@@ -2,7 +2,10 @@
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"esModuleInterop": true
},
"include": ["src"]
}