docs: create typedoc theme and plugins for references (#5297)

* update typedoc and its plugins

* refactor existing typedoc configurations

* added new typedoc plugin and themes

* added more customization options

* added more customization options

* refactored doc-utils to a workspace

* fix tsconfig

* update README files

* remove comments

* revert type changes

* remove dependencies no longer needed

* removed modules action
This commit is contained in:
Shahed Nasser
2023-10-05 12:09:42 +03:00
committed by GitHub
parent b3f75d8f21
commit 0350eeb0a1
110 changed files with 7182 additions and 930 deletions
@@ -32,5 +32,10 @@ jobs:
with:
extension: docs-freshness-check
- name: Install Workspace dependencies
run: yarn install
working-directory: docs-utils
- name: Perform Freshness Check
run: yarn check:freshness
working-directory: docs-utils/packages/scripts
+4 -2
View File
@@ -30,11 +30,13 @@ jobs:
with:
extension: docs-release
- name: Build Packages
run: yarn build
- name: Install Workspace dependencies
run: yarn install
working-directory: docs-utils
- name: Generate Announcement Bar
run: yarn generate:announcement
working-directory: docs-utils/packages/scripts
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
@@ -31,8 +31,13 @@ jobs:
with:
extension: docs-remove-announcement
- name: Install Workspace dependencies
run: yarn install
working-directory: docs-utils
- name: Remove Announcement Bar
run: yarn generate:announcement --expire
working-directory: docs-utils/packages/scripts
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
+1
View File
@@ -32,6 +32,7 @@ jobs:
- name: Install Workspace dependencies
working-directory: www
run: yarn install
- name: Build Docusaurus website
working-directory: www
env:
@@ -1,48 +0,0 @@
name: Generate API Reference
on:
workflow_dispatch:
release:
types: [published]
jobs:
api:
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Checkout
uses: actions/checkout@v3
with:
token: ${{ secrets.REFERENCE_PAT }}
fetch-depth: 0
- name: Setup Node.js environment
uses: actions/setup-node@v3
with:
node-version: "16.20.2"
cache: "yarn"
- name: Install dependencies
uses: ./.github/actions/cache-deps
with:
extension: reference
- name: Build Packages
run: yarn build
- name: Generate API Reference
run: yarn openapi:generate
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
commit-message: "chore(docs): Generated API Reference"
base: "develop"
title: "chore(docs): Updated API Reference"
labels: "type: chore"
add-paths: www/apps/api-reference/specs
branch: "chore/generate-api-ref"
branch-suffix: "timestamp"
@@ -1,47 +0,0 @@
name: Generate Entity Reference
on:
workflow_dispatch:
release:
types: [published]
jobs:
services:
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node.js environment
uses: actions/setup-node@v3
with:
node-version: "16.20.2"
cache: "yarn"
- name: Install dependencies
uses: ./.github/actions/cache-deps
with:
extension: reference
- name: Build Packages
run: yarn build
- name: Generate Entities Reference
run: yarn generate:entities
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
commit-message: "chore(docs): Generated Entities Reference (automated)"
base: "develop"
title: "chore(docs): Generated Entities Reference (automated)"
labels: "type: chore"
add-paths: www/apps/docs/content/references/entities/**
branch: "chore/generate-entities"
branch-suffix: "timestamp"
@@ -1,47 +0,0 @@
name: Generate JS Client Reference
on:
workflow_dispatch:
release:
types: [published]
jobs:
services:
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node.js environment
uses: actions/setup-node@v3
with:
node-version: "16.20.2"
cache: "yarn"
- name: Install dependencies
uses: ./.github/actions/cache-deps
with:
extension: reference
- name: Build Packages
run: yarn build
- name: Generate Services Reference
run: yarn generate:js-client
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
commit-message: "chore(docs): Generated JS Client Reference (automated)"
base: "develop"
title: "chore(docs): Generated JS Client Reference"
labels: "type: chore"
add-paths: www/apps/docs/content/references/js-client/**
branch: "chore/generate-js-client"
branch-suffix: "timestamp"
-47
View File
@@ -1,47 +0,0 @@
name: Generate Services Reference
on:
workflow_dispatch:
release:
types: [published]
jobs:
services:
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node.js environment
uses: actions/setup-node@v3
with:
node-version: "16.20.2"
cache: "yarn"
- name: Install dependencies
uses: ./.github/actions/cache-deps
with:
extension: reference
- name: Build Packages
run: yarn build
- name: Generate Services Reference
run: yarn generate:services
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
commit-message: "chore(docs): Generated Services Reference (automated)"
base: "develop"
title: "chore(docs): Generated Services Reference (automated)"
labels: "type: chore"
add-paths: www/apps/docs/content/references/services/**
branch: "chore/generate-services"
branch-suffix: "timestamp"
+183
View File
@@ -0,0 +1,183 @@
name: Generate Documentation Reference
on:
workflow_dispatch:
release:
types: [published]
jobs:
services:
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node.js environment
uses: actions/setup-node@v3
with:
node-version: "16.20.2"
cache: "yarn"
- name: Install dependencies
uses: ./.github/actions/cache-deps
with:
extension: reference
- name: Build Packages
run: yarn build
- name: Install Workspace dependencies
run: yarn install
working-directory: docs-utils
- name: Generate Services Reference
run: yarn generate:reference services
working-directory: docs-utils/packages/scripts
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
commit-message: "chore(docs): Generated Services Reference (automated)"
base: "develop"
title: "chore(docs): Generated Services Reference (automated)"
labels: "type: chore"
add-paths: www/apps/docs/content/references/services/**
branch: "chore/generate-services"
branch-suffix: "timestamp"
api:
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Checkout
uses: actions/checkout@v3
with:
token: ${{ secrets.REFERENCE_PAT }}
fetch-depth: 0
- name: Setup Node.js environment
uses: actions/setup-node@v3
with:
node-version: "16.20.2"
cache: "yarn"
- name: Install dependencies
uses: ./.github/actions/cache-deps
with:
extension: reference
- name: Build Packages
run: yarn build
- name: Generate API Reference
run: yarn openapi:generate
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
commit-message: "chore(docs): Generated API Reference"
base: "develop"
title: "chore(docs): Updated API Reference"
labels: "type: chore"
add-paths: www/apps/api-reference/specs
branch: "chore/generate-api-ref"
branch-suffix: "timestamp"
entities:
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node.js environment
uses: actions/setup-node@v3
with:
node-version: "16.20.2"
cache: "yarn"
- name: Install dependencies
uses: ./.github/actions/cache-deps
with:
extension: reference
- name: Build Packages
run: yarn build
- name: Install Workspace dependencies
run: yarn install
working-directory: docs-utils
- name: Generate Entities Reference
run: yarn generate:references entities
working-directory: docs-utils/packages/scripts
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
commit-message: "chore(docs): Generated Entities Reference (automated)"
base: "develop"
title: "chore(docs): Generated Entities Reference (automated)"
labels: "type: chore"
add-paths: www/apps/docs/content/references/entities/**
branch: "chore/generate-entities"
branch-suffix: "timestamp"
js-client:
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node.js environment
uses: actions/setup-node@v3
with:
node-version: "16.20.2"
cache: "yarn"
- name: Install dependencies
uses: ./.github/actions/cache-deps
with:
extension: reference
- name: Build Packages
run: yarn build
- name: Install Workspace dependencies
run: yarn install
working-directory: docs-utils
- name: Generate Services Reference
run: yarn generate:references js-client
working-directory: docs-utils/packages/scripts
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
commit-message: "chore(docs): Generated JS Client Reference (automated)"
base: "develop"
title: "chore(docs): Generated JS Client Reference"
labels: "type: chore"
add-paths: www/apps/docs/content/references/js-client/**
branch: "chore/generate-js-client"
branch-suffix: "timestamp"
+121
View File
@@ -0,0 +1,121 @@
const path = require("path")
module.exports = {
root: true,
parser: "@babel/eslint-parser",
parserOptions: {
requireConfigFile: false,
ecmaFeatures: {
experimentalDecorators: true,
},
project: true,
},
plugins: ["prettier"],
extends: [
"eslint:recommended",
"plugin:prettier/recommended",
],
rules: {
curly: ["error", "all"],
"new-cap": "off",
"require-jsdoc": "off",
"no-unused-expressions": "off",
"no-unused-vars": "off",
camelcase: "off",
"no-invalid-this": "off",
"max-len": [
"error",
{
code: 80,
ignoreStrings: true,
ignoreRegExpLiterals: true,
ignoreComments: true,
ignoreTrailingComments: true,
ignoreUrls: true,
ignoreTemplateLiterals: true,
},
],
semi: ["error", "never"],
quotes: [
"error",
"double",
{
allowTemplateLiterals: true,
},
],
"comma-dangle": [
"error",
{
arrays: "always-multiline",
objects: "always-multiline",
imports: "always-multiline",
exports: "always-multiline",
functions: "never",
},
],
"object-curly-spacing": ["error", "always"],
"arrow-parens": ["error", "always"],
"linebreak-style": 0,
"no-confusing-arrow": [
"error",
{
allowParens: false,
},
],
"space-before-function-paren": [
"error",
{
anonymous: "always",
named: "never",
asyncArrow: "always",
},
],
"space-infix-ops": "error",
"eol-last": ["error", "always"],
},
env: {
es6: true,
node: true,
},
ignorePatterns: [
".eslintrc.js",
"dist"
],
overrides: [
{
files: ["*.ts", "*.js"],
plugins: ["@typescript-eslint/eslint-plugin"],
extends: [
"plugin:@typescript-eslint/recommended",
],
parser: "@typescript-eslint/parser",
parserOptions: {
project: [
"./tsconfig.json",
"./packages/scripts/tsconfig.json",
"./packages/typedoc-config/tsconfig.json",
"./packages/typedoc-frontmatter-plugin/tsconfig.json",
"./packages/typedoc-modules-plugin/tsconfig.json",
"./packages/typedoc-markdown-medusa-plugin/tsconfig.json"
]
},
rules: {
"valid-jsdoc": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/promise-function-async": "error",
"@typescript-eslint/keyword-spacing": "error",
"@typescript-eslint/space-before-function-paren": [
"error",
{
anonymous: "always",
named: "never",
asyncArrow: "always",
},
],
"@typescript-eslint/space-infix-ops": "error",
},
}
],
}
+8
View File
@@ -0,0 +1,8 @@
{
"endOfLine": "auto",
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5",
"arrowParens": "always"
}
+1
View File
@@ -0,0 +1 @@
nodeLinker: node-modules
+3
View File
@@ -0,0 +1,3 @@
# Docs Util Workspace
All utility scripts and plugins used for documentation, such as generating documentation, are placed in this workspace.
+22
View File
@@ -0,0 +1,22 @@
{
"name": "docs-util",
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"build": "turbo run build",
"watch": "turbo run watch",
"lint": "turbo run lint"
},
"dependencies": {
"@babel/eslint-parser": "^7.22.15",
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.7.4",
"eslint": "^8.36.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"prettier": "^3.0.3",
"turbo": "latest"
}
}
@@ -0,0 +1,90 @@
#!/usr/bin/env node
import { Octokit } from "@octokit/core"
import fs from "fs"
import path from "path"
const shouldExpire = process.argv.indexOf("--expire") !== -1
const octokit = new Octokit({
auth: process.env.GH_TOKEN,
})
async function main() {
let announcement = {}
if (shouldExpire) {
//check if the file was last updated 6 days ago
try {
const commitResponse = await octokit.request(
"GET /repos/{owner}/{repo}/commits",
{
owner: "medusajs",
repo: "medusa",
path: path.join("www", "apps", "docs", "announcement.json"),
per_page: 1,
}
)
if (
commitResponse.data.length &&
commitResponse.data[0].commit.committer?.date &&
dateDiffInDays(
new Date(commitResponse.data[0].commit.committer.date),
new Date()
) < 6
) {
console.log("File was edited less than 6 days ago. Expiry canceled.")
return
}
} catch (e) {
//continue as if file doesn't exist
}
} else {
//retrieve the latest release
const response = await octokit.request(
"GET /repos/{owner}/{repo}/releases/latest",
{
owner: "medusajs",
repo: "medusa",
}
)
const version = response.data.tag_name
//add new announcement
announcement = {
id: response.data.html_url,
content: `${version} is out`,
isCloseable: true,
}
}
//write new config file
fs.writeFileSync(
path.join(
__dirname,
"..",
"..",
"..",
"www",
"apps",
"docs",
"announcement.json"
),
JSON.stringify(announcement)
)
console.log(`Announcement Bar has been ${shouldExpire ? "removed" : "added"}`)
}
const _MS_PER_DAY = 1000 * 60 * 60 * 24
// a and b are javascript Date objects
function dateDiffInDays(a: Date, b: Date) {
// Discard the time and time-zone information.
const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate())
const utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate())
return Math.floor((utc2 - utc1) / _MS_PER_DAY)
}
void main()
@@ -0,0 +1,171 @@
#!/usr/bin/env node
import { LinearClient } from "@linear/sdk"
import { Octokit } from "@octokit/core"
import fs from "fs"
import path from "path"
const octokit = new Octokit({
auth: process.env.GH_TOKEN,
})
const linearClient = new LinearClient({
apiKey: process.env.LINEAR_API_KEY,
})
const repoPath = path.join(
__dirname,
"..",
"..",
"..",
"www",
"apps",
"docs",
"content"
)
let freshnessCheckLabelId = ""
let documentationTeamId = ""
async function scanDirectory(startPath: string) {
const files = fs.readdirSync(path.join(startPath), {
withFileTypes: true,
})
for (const file of files) {
const filePath = path.join(startPath, file.name)
if (file.isDirectory()) {
//if it's references directory, skip
if (file.name !== "references" && file.name !== "upgrade-guides") {
await scanDirectory(filePath)
}
continue
}
//check that the file is a markdown file
if (file.name.indexOf(".md") === -1 && file.name.indexOf(".mdx") === -1) {
continue
}
//if it is a file, check its commits in GitHub
const commitResponse = await octokit.request(
"GET /repos/{owner}/{repo}/commits",
{
owner: "medusajs",
repo: "medusa",
path: filePath,
per_page: 1,
}
)
if (
!commitResponse.data.length ||
!commitResponse.data[0].commit.committer?.date
) {
continue
}
const today = new Date()
const lastEditedDate = new Date(
commitResponse.data[0].commit.committer.date
)
const monthsSinceEdited = getMonthDifference(lastEditedDate, today)
if (monthsSinceEdited > 6) {
//file was edited more than 6 months ago.
//check if there's an issue created for this file since the commit date
const existingIssue = await linearClient.issues({
filter: {
createdAt: {
gte: subtractMonths(monthsSinceEdited - 6, today),
},
title: {
containsIgnoreCase: `Freshness check for ${filePath}`,
},
labels: {
some: {
id: {
eq: freshnessCheckLabelId,
},
},
},
},
first: 1,
})
if (existingIssue.nodes.length) {
//an issue has been created for the past 6 months. Don't create an issue for it.
continue
}
console.log(`Creating an issue for ${filePath}...`)
//there are no issues in the past 6 months. Create an issue
await linearClient.issueCreate({
teamId: documentationTeamId,
title: `Freshness check for ${filePath}`,
labelIds: [freshnessCheckLabelId],
description: `File \`${filePath}\` was last edited on ${lastEditedDate.toDateString()}.`,
})
}
}
}
async function main() {
//fetch documentation team ID from linear
const documentationTeam = await linearClient.teams({
filter: {
name: {
eqIgnoreCase: "Documentation",
},
},
first: 1,
})
if (!documentationTeam.nodes.length) {
console.log("Please add Documentation team in Linear first then try again")
process.exit(1)
}
documentationTeamId = documentationTeam.nodes[0].id
//fetch freshness check label ID from linear
const freshnessCheckLabel = await linearClient.issueLabels({
filter: {
name: {
eqIgnoreCase: "type: freshness-check",
},
team: {
id: {
eq: documentationTeamId,
},
},
},
})
if (!freshnessCheckLabel.nodes.length) {
console.log(
"Please add freshness check label in Linear under the documentation team first then try again"
)
process.exit(1)
}
freshnessCheckLabelId = freshnessCheckLabel.nodes[0].id
await scanDirectory(repoPath)
}
function getMonthDifference(startDate: Date, endDate: Date) {
return (
endDate.getMonth() -
startDate.getMonth() +
12 * (endDate.getFullYear() - startDate.getFullYear())
)
}
function subtractMonths(numOfMonths: number, date = new Date()) {
date.setMonth(date.getMonth() - numOfMonths)
return date
}
void main()
@@ -0,0 +1,25 @@
#!/usr/bin/env node
import path from "path"
import fs from "fs"
import { exec } from "child_process"
const referenceNames = process.argv.slice(2)
referenceNames.forEach((names) => {
const configPathName = path.join(
__dirname,
"..",
"typedoc-config",
`${names}.js`
)
// check if the config file exists
if (!fs.existsSync(configPathName)) {
throw new Error(
`Config file for ${names} doesn't exist. Make sure to create it in ${configPathName}`
)
}
const typedocProcess = exec(`typedoc --options ${configPathName}`)
typedocProcess.stdout?.pipe(process.stdout)
typedocProcess.stderr?.pipe(process.stdout)
})
+28
View File
@@ -0,0 +1,28 @@
{
"name": "scripts",
"private": true,
"license": "MIT",
"publishConfig": {
"access": "public"
},
"scripts": {
"generate:announcement": "ts-node ./doc-change-release.ts",
"generate:reference": "ts-node ./generate-reference.ts",
"check:freshness": "ts-node ./freshness-check.ts"
},
"version": "0.0.0",
"dependencies": {
"@linear/sdk": "^1.22.0",
"@octokit/core": "^4.0.5",
"ts-node": "^10.9.1",
"typedoc": "0.25.1",
"typedoc-monorepo-link-types": "^0.0.2",
"typedoc-plugin-frontmatter": "*",
"typedoc-plugin-markdown": "3.16.0",
"typedoc-plugin-markdown-medusa": "*",
"typedoc-plugin-merge-modules": "5.1.0",
"typedoc-plugin-modules": "*",
"typedoc-plugin-reference-excluder": "1.1.3",
"typedoc-plugin-rename-defaults": "^0.6.6"
}
}
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "./dist"
},
"include": ["*.ts"]
}
@@ -0,0 +1,21 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path")
const globalTypedocOptions = require("./typedoc")
const pathPrefix = path.join(__dirname, "..", "..")
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"),
name: "Entities Reference",
indexTitle: "Entities Reference",
entryDocument: "index.md",
hideInPageTOC: true,
hideBreadcrumbs: true,
plugin: [...globalTypedocOptions.plugin, "typedoc-plugin-frontmatter"],
frontmatterData: {
displayed_sidebar: "entitiesSidebar",
},
}
@@ -1,11 +1,15 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path")
const globalTypedocOptions = require("./typedoc")
const pathPrefix = path.join(__dirname, "..", "..")
module.exports = {
...globalTypedocOptions,
entryPoints: ["packages/medusa-js/src/resources"],
entryPoints: [path.join(pathPrefix, "packages/medusa-js/src/resources")],
entryPointStrategy: "expand",
out: ["www/apps/docs/content/references/js-client"],
tsconfig: "packages/medusa-js/tsconfig.json",
out: [path.join(pathPrefix, "www/apps/docs/content/references/js-client")],
tsconfig: path.join(pathPrefix, "packages/medusa-js/tsconfig.json"),
name: "JS Client Reference",
indexTitle: "JS Client Reference",
entryDocument: "index.md",
@@ -15,11 +19,10 @@ module.exports = {
...globalTypedocOptions.plugin,
"typedoc-plugin-merge-modules",
"typedoc-plugin-reference-excluder",
"typedoc-monorepo-link-types",
"typedoc-frontmatter-plugin",
"typedoc-plugin-frontmatter",
],
exclude: [
"packages/medusa-js/src/resources/base.ts",
path.join(pathPrefix, "packages/medusa-js/src/resources/base.ts"),
"node_modules/**",
"packages/**/node_modules",
],
@@ -0,0 +1,71 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path")
const globalTypedocOptions = require("./base")
const pathPrefix = path.join(__dirname, "..", "..", "..")
module.exports = ({
entryPointPath,
outPath,
tsconfigPath = "",
moduleName = "",
documentsToFormat = [],
additionalFormatting = {},
extraOptions = {},
}) => {
const formatting = {}
documentsToFormat.forEach((document) => {
formatting[document] = {
reflectionTitle: {
kind: false,
typeParameters: false,
suffix: "Reference",
},
expandMembers: true,
showCommentsAsHeader: true,
parameterStyle: "list",
useTsLinkResolution: false,
showReturnSignature: false,
sections: {
reflection_typeParameters: false,
member_declaration_typeParameters: false,
reflection_implements: false,
reflection_implementedBy: false,
reflection_callable: false,
reflection_indexable: false,
member_signature_typeParameters: false,
member_signature_sources: false,
member_signature_title: false,
},
reflectionGroups: {
Constructors: false,
Properties: false,
},
...additionalFormatting,
}
})
return {
...globalTypedocOptions,
entryPoints: [path.join(pathPrefix, entryPointPath)],
out: [path.join(pathPrefix, outPath)],
tsconfig: path.join(
pathPrefix,
tsconfigPath || "packages/types/tsconfig.json"
),
name: `${moduleName} Reference`,
indexTitle: `${moduleName} Reference`,
entryDocument: "index.md",
entryPointStrategy: "expand",
hideInPageTOC: true,
hideBreadcrumbs: true,
plugin: [
"typedoc-plugin-markdown-medusa",
"typedoc-plugin-modules",
"typedoc-plugin-rename-defaults",
"typedoc-plugin-frontmatter",
],
hideMembersSymbol: true,
formatting,
...extraOptions,
}
}
@@ -0,0 +1,9 @@
{
"name": "typedoc-config",
"private": true,
"license": "MIT",
"publishConfig": {
"access": "public"
},
"version": "0.0.0"
}
@@ -0,0 +1,17 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path")
const globalTypedocOptions = require("./typedoc")
const pathPrefix = path.join(__dirname, "..", "..")
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"),
name: "Services Reference",
indexTitle: "Services Reference",
entryDocument: "index.md",
hideInPageTOC: true,
hideBreadcrumbs: true,
}
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "./dist"
},
"include": ["*.js"]
}
@@ -0,0 +1,2 @@
dist
.yarn
@@ -1,4 +1,4 @@
# typedoc-frontmatter-plugin
# typedoc-plugin-frontmatter
A Typedoc plugin that allows inserting frontmatter key-value pairs at the top of the exported Markdown files.
@@ -34,7 +34,7 @@ frontmatterData: {
},
```
## Building the Plugin
## Build the Plugin
Before using any command that makes use of this plugin, make sure to run the `build` command:
@@ -1,6 +1,11 @@
{
"name": "typedoc-frontmatter-plugin",
"version": "1.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",
@@ -8,23 +13,23 @@
"dist"
],
"author": "Shahed Nasser",
"license": "MIT",
"scripts": {
"build": "tsc"
"build": "tsc",
"watch": "tsc --watch",
"lint": "eslint --ext .ts src"
},
"peerDependencies": {
"typedoc": "0.22.x"
"typedoc": "0.25.x"
},
"devDependencies": {
"@types/node": "^16.11.10",
"typedoc": "^0.22.10",
"typescript": "4.5.2"
"typedoc": "^0.25.1",
"typescript": "^4.6"
},
"keywords": [
"typedocplugin",
"packages",
"monorepo",
"lerna",
"typedoc"
]
}
@@ -0,0 +1,54 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Application, PageEvent, ParameterType } from "typedoc"
export function load(app: Application) {
app.options.addDeclaration({
name: "frontmatterData",
help: "An object of key-value pairs to be added to frontmatter",
type: ParameterType.Mixed, // The default
defaultValue: {},
validate: (value: any) => {
if (typeof value === "string") {
//decode it with JSON to check if it's an object
value = JSON.parse(value)
}
if (
!(typeof value === "object" && !Array.isArray(value) && value !== null)
) {
throw new Error("Value should be an object")
}
},
})
app.options.addDeclaration({
name: "pagesPattern",
help: "A string of pages pattern. The pattern will be tested using RegExp to determine whether the frontmatterData will be added or not.",
type: ParameterType.String,
defaultValue: "",
})
app.renderer.on(PageEvent.END, (page: PageEvent) => {
const patternStr: string | any = app.options.getValue("pagesPattern")
const pattern = new RegExp(patternStr)
let frontmatterData: object | any = app.options.getValue("frontmatterData")
if (typeof frontmatterData === "string") {
frontmatterData = JSON.parse(frontmatterData)
}
const frontmatterDataEntries = Object.entries(frontmatterData)
if (!frontmatterDataEntries.length || !pattern.test(page.filename)) {
return
}
let frontmatterStr = `---\n`
for (const [key, value] of frontmatterDataEntries) {
frontmatterStr += `${key}: ${value}\n`
}
frontmatterStr += `---\n\n`
page.contents = frontmatterStr + page.contents
})
}
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "./dist"
},
"include": ["src"]
}
@@ -0,0 +1,2 @@
dist
.yarn
@@ -0,0 +1,28 @@
# typedoc-plugin-markdown-medusa
A plugin that forks and customizes the [typedoc-plugin-markdown](https://github.com/tgreyuk/typedoc-plugin-markdown/tree/master/packages/typedoc-plugin-markdown) to create a theme with more formatting options.
## Configurations
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:
- `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.
- `reflectionGroups`: (optional) an object whose keys are titles of reflection groups (for example, `Constructors`), and values are boolean. This property is used to enable/disable reflection group from being documented.
- `reflectionTitle`: (optional) an object used to customize how the title of a generated documentation page is rendered. It accepts the following properties:
- `kind`: a boolean value indicating whether the documented resource's kind should be shown in the title.
- `typeParameters`: a boolean value indicating whether the documented resource's type parameters should b shown in the title.
- `suffix`: a string used to add additional text to the end of the page's title.
- `reflectionDescription`: (optional) a string used to add description in a documentation page after the page's title.
- `expandMembers`: (optional) a boolean indicating whether members in a page should be expanded. When enabled, the member titles (for example, `Methods`) are removed and the heading level of nested titles whithin the member is elevated by `1`.
- `showCommentAsHeader`: (optional) a boolean indicating whether comments, for example, a method's name, are represented as headers.
- `parameterStyle`: (optional) a string indicating how parameters are displayed. Its value can be `table` (default) or `list`.
- `showReturnSignature`: (optional) a boolean indicating whether to show the signature for returned values.
## Build Plugin
Before using any command that makes use of this plugin, make sure to run the `build` command:
```bash
yarn build
```
@@ -0,0 +1,37 @@
{
"name": "typedoc-plugin-markdown-medusa",
"private": true,
"license": "MIT",
"publishConfig": {
"access": "public"
},
"version": "1.0.0",
"description": "A plugin that creates a theme with customization capabilities.",
"main": "./dist/index.js",
"exports": "./dist/index.js",
"files": [
"dist"
],
"author": "Shahed Nasser",
"scripts": {
"build": "tsc && copyfiles --up 1 ./src/**/*.hbs ./dist/",
"lint": "eslint --ext .ts src"
},
"peerDependencies": {
"typedoc": "0.25.x"
},
"devDependencies": {
"@types/node": "^16.11.10",
"copyfiles": "^2.4.1",
"typescript": "^4.6"
},
"keywords": [
"typedocplugin",
"packages",
"monorepo",
"typedoc"
],
"dependencies": {
"handlebars": "^4.7.8"
}
}
@@ -0,0 +1,23 @@
import { ReflectionKind } from "typedoc"
const PLURALS = {
[ReflectionKind.Class]: "Classes",
[ReflectionKind.Property]: "Properties",
[ReflectionKind.Enum]: "Enumerations",
[ReflectionKind.EnumMember]: "Enumeration members",
[ReflectionKind.TypeAlias]: "Type aliases",
}
export function getKindPlural(kind: ReflectionKind): string {
if (kind in PLURALS) {
return PLURALS[kind as keyof typeof PLURALS]
} else {
return getKindString(kind) + "s"
}
}
function getKindString(kind: ReflectionKind): string {
let str = ReflectionKind[kind]
str = str.replace(/(.)([A-Z])/g, (_m, a, b) => a + " " + b.toLowerCase())
return str
}
@@ -0,0 +1,106 @@
import { Application, ParameterType } from "typedoc"
import { MarkdownThemeOptionsReader } from "./options-reader"
import { MarkdownTheme } from "./theme"
export function load(app: Application) {
app.renderer.defineTheme("markdown", MarkdownTheme)
app.options.addReader(new MarkdownThemeOptionsReader())
app.options.addDeclaration({
help: "[Markdown Plugin] Do not render page title.",
name: "hidePageTitle",
type: ParameterType.Boolean,
defaultValue: false,
})
app.options.addDeclaration({
help: "[Markdown Plugin] Do not render breadcrumbs in template.",
name: "hideBreadcrumbs",
type: ParameterType.Boolean,
defaultValue: false,
})
app.options.addDeclaration({
help: "[Markdown Plugin] Specifies the base path that all links to be served from. If omitted all urls will be relative.",
name: "publicPath",
type: ParameterType.String,
})
app.options.addDeclaration({
help: "[Markdown Plugin] Use HTML named anchors as fragment identifiers for engines that do not automatically assign header ids. Should be set for Bitbucket Server docs.",
name: "namedAnchors",
type: ParameterType.Boolean,
defaultValue: false,
})
app.options.addDeclaration({
help: "[Markdown Plugin] Output all reflections into seperate output files.",
name: "allReflectionsHaveOwnDocument",
type: ParameterType.Boolean,
defaultValue: false,
})
app.options.addDeclaration({
help: "[Markdown Plugin] Separator used to format filenames.",
name: "filenameSeparator",
type: ParameterType.String,
defaultValue: ".",
})
app.options.addDeclaration({
help: "[Markdown Plugin] The file name of the entry document.",
name: "entryDocument",
type: ParameterType.String,
defaultValue: "README.md",
})
app.options.addDeclaration({
help: "[Markdown Plugin] Do not render in-page table of contents items.",
name: "hideInPageTOC",
type: ParameterType.Boolean,
defaultValue: false,
})
app.options.addDeclaration({
help: "[Markdown Plugin] Customise the index page title.",
name: "indexTitle",
type: ParameterType.String,
})
app.options.addDeclaration({
help: "[Markdown Plugin] Do not add special symbols for class members.",
name: "hideMembersSymbol",
type: ParameterType.Boolean,
defaultValue: false,
})
app.options.addDeclaration({
help: "[Markdown Plugin] Preserve anchor casing when generating links.",
name: "preserveAnchorCasing",
type: ParameterType.Boolean,
defaultValue: false,
})
app.options.addDeclaration({
help: "[Markdown Plugin] Specify the Type Declaration Render Style",
name: "objectLiteralTypeDeclarationStyle",
type: ParameterType.String,
defaultValue: "table",
validate: (x) => {
const availableValues = ["table", "list"]
if (!availableValues.includes(x)) {
throw new Error(
`Wrong value for objectLiteralTypeDeclarationStyle, the expected value is one of ${availableValues}`
)
}
},
})
app.options.addDeclaration({
help: "[Markdown Plugin] Formatting options that can be specified either on a specific document or to all documents",
name: "formatting",
type: ParameterType.Object,
defaultValue: {},
})
}
export { MarkdownTheme }
@@ -0,0 +1,12 @@
import { Options, OptionsReader } from "typedoc"
export class MarkdownThemeOptionsReader implements OptionsReader {
name = "markdown-theme-reader"
readonly order = 1000
readonly supportsPackages = false
read(container: Options) {
if (container.getValue("theme") === "default") {
container.setValue("theme", "markdown")
}
}
}
@@ -0,0 +1,99 @@
import * as fs from "fs"
import * as Handlebars from "handlebars"
import * as path from "path"
import breadcrumbsHelper from "./resources/helpers/breadcrumbs"
import commentHelper from "./resources/helpers/comment"
import commentsHelper from "./resources/helpers/comments"
import declarationTitleHelper from "./resources/helpers/declaration-title"
import escapeHelper from "./resources/helpers/escape"
import hierarchyHelper from "./resources/helpers/hierarchy"
import ifIsReference from "./resources/helpers/if-is-reference"
import ifNamedAnchors from "./resources/helpers/if-named-anchors"
import ifShowBreadcrumbsHelper from "./resources/helpers/if-show-breadcrumbs"
import ifShowNamedAnchorsHelper from "./resources/helpers/if-show-named-anchors"
import ifShowPageTitleHelper from "./resources/helpers/if-show-page-title"
import ifShowReturnsHelper from "./resources/helpers/if-show-returns"
import ifShowTypeHierarchyHelper from "./resources/helpers/if-show-type-hierarchy"
import indexSignatureTitleHelper from "./resources/helpers/index-signature-title"
import parameterTableHelper from "./resources/helpers/parameter-table"
import objectLiteralMemberHelper from "./resources/helpers/type-declaration-object-literal"
import referenceMember from "./resources/helpers/reference-member"
import reflectionPathHelper from "./resources/helpers/reflection-path"
import reflectionTitleHelper from "./resources/helpers/reflection-title"
import relativeUrlHelper from "./resources/helpers/relative-url"
import returns from "./resources/helpers/returns"
import signatureTitleHelper from "./resources/helpers/signature-title"
import tocHelper from "./resources/helpers/toc"
import typeHelper from "./resources/helpers/type"
import typeAndParentHelper from "./resources/helpers/type-and-parent"
import typeParameterTableHelper from "./resources/helpers/type-parameter-table"
import sectionsHelper from "./resources/helpers/section-enabled"
import getFormattingOptionHelper from "./resources/helpers/get-formatting-option"
import titleLevelHelper from "./resources/helpers/title-level"
import typeParameterListHelper from "./resources/helpers/type-parameter-list"
import typeParameterHelper from "./resources/helpers/type-parameter"
import parameterListHelper from "./resources/helpers/parameter-list"
import parameterHelper from "./resources/helpers/parameter"
import { MarkdownTheme } from "./theme"
const TEMPLATE_PATH = path.join(__dirname, "resources", "templates")
export const indexTemplate = Handlebars.compile(
fs.readFileSync(path.join(TEMPLATE_PATH, "index.hbs")).toString()
)
export const reflectionTemplate = Handlebars.compile(
fs.readFileSync(path.join(TEMPLATE_PATH, "reflection.hbs")).toString()
)
export const reflectionMemberTemplate = Handlebars.compile(
fs.readFileSync(path.join(TEMPLATE_PATH, "reflection.member.hbs")).toString()
)
export function registerPartials() {
const partialsFolder = path.join(__dirname, "resources", "partials")
const partialFiles = fs.readdirSync(partialsFolder)
partialFiles.forEach((partialFile) => {
const partialName = path.basename(partialFile, ".hbs")
const partialContent = fs
.readFileSync(partialsFolder + "/" + partialFile)
.toString()
Handlebars.registerPartial(partialName, partialContent)
})
}
export function registerHelpers(theme: MarkdownTheme) {
breadcrumbsHelper(theme)
commentHelper(theme)
commentsHelper(theme)
declarationTitleHelper(theme)
escapeHelper()
hierarchyHelper()
ifIsReference()
ifNamedAnchors(theme)
ifShowBreadcrumbsHelper(theme)
ifShowNamedAnchorsHelper(theme)
ifShowPageTitleHelper(theme)
ifShowReturnsHelper()
ifShowTypeHierarchyHelper()
indexSignatureTitleHelper()
parameterTableHelper()
objectLiteralMemberHelper(theme)
referenceMember()
reflectionPathHelper()
reflectionTitleHelper(theme)
relativeUrlHelper(theme)
returns()
signatureTitleHelper(theme)
tocHelper(theme)
typeHelper()
typeAndParentHelper()
typeParameterTableHelper()
sectionsHelper(theme)
getFormattingOptionHelper(theme)
titleLevelHelper(theme)
typeParameterListHelper()
typeParameterHelper(theme)
parameterListHelper()
parameterHelper(theme)
}
@@ -0,0 +1,51 @@
import * as Handlebars from "handlebars"
import { PageEvent } from "typedoc"
import { MarkdownTheme } from "../../theme"
import { escapeChars, getDisplayName } from "../../utils"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper("breadcrumbs", function (this: PageEvent) {
const { entryPoints, entryDocument, project, readme } = theme
if (!project) {
return ""
}
const hasReadmeFile = !readme.endsWith("none")
const breadcrumbs: string[] = []
const globalsName = entryPoints.length > 1 ? "Modules" : "Exports"
breadcrumbs.push(
this.url === entryDocument
? project.name
: `[${getDisplayName(project)}](${Handlebars.helpers.relativeURL(
entryDocument
)})`
)
if (hasReadmeFile) {
breadcrumbs.push(
this.url === project.url
? globalsName
: `[${globalsName}](${Handlebars.helpers.relativeURL("modules.md")})`
)
}
const breadcrumbsOut = breadcrumb(this, this.model, breadcrumbs)
return breadcrumbsOut
})
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function breadcrumb(page: PageEvent, model: any, md: string[]) {
if (model && model.parent) {
breadcrumb(page, model.parent, md)
if (model.url) {
md.push(
page.url === model.url
? `${escapeChars(model.name)}`
: `[${escapeChars(model.name)}](${Handlebars.helpers.relativeURL(
model.url
)})`
)
}
}
return md.join(" / ")
}
@@ -0,0 +1,120 @@
import * as fs from "fs"
import * as Handlebars from "handlebars"
import * as Path from "path"
import { CommentDisplayPart } from "typedoc/dist/lib/models/comments/comment"
import { MarkdownTheme } from "../../theme"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper("comment", function (parts: CommentDisplayPart[]) {
const result: string[] = []
for (const part of parts) {
switch (part.kind) {
case "text":
case "code":
result.push(part.text)
break
case "inline-tag":
switch (part.tag) {
case "@label":
case "@inheritdoc":
break
case "@link":
case "@linkcode":
case "@linkplain": {
if (part.target) {
const url =
typeof part.target === "string"
? part.target
: "url" in part.target
? Handlebars.helpers.relativeURL(part.target.url)
: ""
const wrap = part.tag === "@linkcode" ? "`" : ""
result.push(
url ? `[${wrap}${part.text}${wrap}](${url})` : part.text
)
} else {
result.push(part.text)
}
break
}
default:
result.push(`{${part.tag} ${part.text}}`)
break
}
break
default:
result.push("")
}
}
return parseMarkdown(result.join(""), theme)
})
}
function parseMarkdown(text: string, theme: MarkdownTheme) {
const includePattern = /\[\[include:([^\]]+?)\]\]/g
const mediaPattern = /media:\/\/([^ ")\]}]+)/g
if (theme.includes) {
text = text.replace(includePattern, (_match, path) => {
path = Path.join(theme.includes!, path.trim())
if (fs.existsSync(path) && fs.statSync(path).isFile()) {
const contents = readFile(path)
return contents
} else {
theme.application.logger.warn("Could not find file to include: " + path)
return ""
}
})
}
if (theme.mediaDirectory) {
text = text.replace(mediaPattern, (match: string, path: string) => {
const fileName = Path.join(theme.mediaDirectory!, path)
if (fs.existsSync(fileName) && fs.statSync(fileName).isFile()) {
return Handlebars.helpers.relativeURL("media") + "/" + path
} else {
theme.application.logger.warn("Could not find media file: " + fileName)
return match
}
})
}
return text
}
/**
* Load the given file and return its contents.
*
* @param file The path of the file to read.
* @returns The files contents.
*/
export function readFile(file: string): string {
const buffer = fs.readFileSync(file)
switch (buffer[0]) {
case 0xfe:
if (buffer[1] === 0xff) {
let i = 0
while (i + 1 < buffer.length) {
const temp = buffer[i]
buffer[i] = buffer[i + 1]
buffer[i + 1] = temp
i += 2
}
return buffer.toString("ucs2", 2)
}
break
case 0xff:
if (buffer[1] === 0xfe) {
return buffer.toString("ucs2", 2)
}
break
case 0xef:
if (buffer[1] === 0xbb) {
return buffer.toString("utf8", 3)
}
}
return buffer.toString("utf8", 0)
}
@@ -0,0 +1,42 @@
import * as Handlebars from "handlebars"
import { Comment } from "typedoc"
import { camelToTitleCase } from "../../utils"
import { MarkdownTheme } from "../../theme"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"comments",
function (
comment: Comment,
showSummary = true,
showTags = true,
commentLevel = 4
) {
const { showCommentsAsHeader } = theme.getFormattingOptionsForLocation()
const md: string[] = []
if (showSummary && comment.summary) {
md.push(Handlebars.helpers.comment(comment.summary))
}
const filteredTags = comment.blockTags.filter(
(tag) => tag.tag !== "@returns"
)
if (showTags && comment.blockTags?.length) {
const tags = filteredTags.map((tag) => {
return `${
showCommentsAsHeader
? `${Handlebars.helpers.titleLevel(commentLevel)} `
: "**`"
}${camelToTitleCase(tag.tag.substring(1))}${
showCommentsAsHeader ? "" : "`**"
}\n\n${Handlebars.helpers.comment(tag.content)}`
})
md.push(tags.join("\n\n"))
}
return md.join("\n\n")
}
)
}
@@ -0,0 +1,65 @@
import * as Handlebars from "handlebars"
import {
DeclarationReflection,
LiteralType,
ParameterReflection,
ReflectionKind,
ReflectionType,
} from "typedoc"
import { MarkdownTheme } from "../../theme"
import {
escapeChars,
memberSymbol,
stripComments,
stripLineBreaks,
} from "../../utils"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"declarationTitle",
function (this: ParameterReflection | DeclarationReflection) {
const md = theme.hideMembersSymbol ? [] : [memberSymbol(this)]
function getType(
reflection: ParameterReflection | DeclarationReflection
) {
const reflectionType = reflection.type as ReflectionType
if (reflectionType && reflectionType.declaration?.children) {
return ": `Object`"
}
return (
(reflection.parent?.kindOf(ReflectionKind.Enum) ? " = " : ": ") +
Handlebars.helpers.type.call(
reflectionType ? reflectionType : reflection,
"object"
)
)
}
if (this.flags && this.flags.length > 0 && !this.flags.isRest) {
md.push(" " + this.flags.map((flag) => `\`${flag}\``).join(" "))
}
md.push(
`${this.flags.isRest ? "... " : ""} **${escapeChars(this.name)}**`
)
if (this instanceof DeclarationReflection && this.typeParameters) {
md.push(
`<${this.typeParameters
.map((typeParameter) => `\`${typeParameter.name}\``)
.join(", ")}\\>`
)
}
md.push(getType(this))
if (
!(this.type instanceof LiteralType) &&
this.defaultValue &&
this.defaultValue !== "..."
) {
md.push(` = \`${stripLineBreaks(stripComments(this.defaultValue))}\``)
}
return md.join("")
}
)
}
@@ -0,0 +1,8 @@
import * as Handlebars from "handlebars"
import { escapeChars } from "../../utils"
export default function () {
Handlebars.registerHelper("escape", function (str: string) {
return escapeChars(str)
})
}
@@ -0,0 +1,15 @@
import { MarkdownTheme } from "../../theme"
import * as Handlebars from "handlebars"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"getFormattingOption",
function (optionName: string): unknown {
const options = theme.getFormattingOptionsForLocation()
return optionName in options
? options[optionName as keyof typeof options]
: null
}
)
}
@@ -0,0 +1,28 @@
import * as Handlebars from "handlebars"
import { DeclarationHierarchy } from "typedoc/dist/lib/models"
import { spaces } from "../../utils"
export default function () {
Handlebars.registerHelper(
"hierarchy",
function (this: DeclarationHierarchy, level: number) {
const md: string[] = []
const symbol = level > 0 ? getSymbol(level) : "-"
this.types.forEach((hierarchyType) => {
if (this.isTarget) {
md.push(`${symbol} **\`${hierarchyType}\`**`)
} else {
md.push(`${symbol} ${Handlebars.helpers.type.call(hierarchyType)}`)
}
})
if (this.next) {
md.push(Handlebars.helpers.hierarchy.call(this.next, level + 1))
}
return md.join("\n\n")
}
)
function getSymbol(level: number) {
return spaces(2) + [...Array(level)].map(() => "↳").join("")
}
}
@@ -0,0 +1,16 @@
import * as Handlebars from "handlebars"
import { DeclarationReflection, ReferenceReflection } from "typedoc"
export default function () {
Handlebars.registerHelper(
"ifIsReference",
function (
this: DeclarationReflection | ReferenceReflection,
options: Handlebars.HelperOptions
) {
return this instanceof ReferenceReflection
? options.fn(this)
: options.inverse(this)
}
)
}
@@ -0,0 +1,12 @@
import * as Handlebars from "handlebars"
import { PageEvent } from "typedoc"
import { MarkdownTheme } from "../../theme"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"ifNamedAnchors",
function (this: PageEvent, options: Handlebars.HelperOptions) {
return theme.namedAnchors ? options.fn(this) : options.inverse(this)
}
)
}
@@ -0,0 +1,12 @@
import * as Handlebars from "handlebars"
import { PageEvent } from "typedoc"
import { MarkdownTheme } from "../../theme"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"ifShowBreadcrumbs",
function (this: PageEvent, options: Handlebars.HelperOptions) {
return theme.hideBreadcrumbs ? options.inverse(this) : options.fn(this)
}
)
}
@@ -0,0 +1,12 @@
import * as Handlebars from "handlebars"
import { PageEvent } from "typedoc"
import { MarkdownTheme } from "../../theme"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"ifShowNamedAnchors",
function (this: PageEvent, options: Handlebars.HelperOptions) {
return theme.namedAnchors ? options.fn(this) : options.inverse(this)
}
)
}
@@ -0,0 +1,12 @@
import * as Handlebars from "handlebars"
import { PageEvent } from "typedoc"
import { MarkdownTheme } from "../../theme"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"ifShowPageTitle",
function (this: PageEvent, options: Handlebars.HelperOptions) {
return theme.hidePageTitle ? options.inverse(this) : options.fn(this)
}
)
}
@@ -0,0 +1,13 @@
import * as Handlebars from "handlebars"
import { ReflectionKind, SignatureReflection } from "typedoc"
export default function () {
Handlebars.registerHelper(
"ifShowReturns",
function (this: SignatureReflection, options: Handlebars.HelperOptions) {
return this.type && !this.parent?.kindOf(ReflectionKind.Constructor)
? options.fn(this)
: options.inverse(this)
}
)
}
@@ -0,0 +1,18 @@
import * as Handlebars from "handlebars"
import { DeclarationHierarchy, DeclarationReflection } from "typedoc"
import { PageEvent } from "typedoc"
export default function () {
Handlebars.registerHelper(
"ifShowTypeHierarchy",
function (
this: PageEvent<DeclarationReflection>,
options: Handlebars.HelperOptions
) {
const typeHierarchy = this.model?.typeHierarchy as DeclarationHierarchy
return typeHierarchy && typeHierarchy.next
? options.fn(this)
: options.inverse(this)
}
)
}
@@ -0,0 +1,22 @@
import * as Handlebars from "handlebars"
import { SignatureReflection } from "typedoc"
export default function () {
Handlebars.registerHelper(
"indexSignatureTitle",
function (this: SignatureReflection) {
const md = ["▪"]
const parameters = this.parameters
? this.parameters.map((parameter) => {
return `${parameter.name}: ${Handlebars.helpers.type.call(
parameter.type
)}`
})
: []
md.push(
`[${parameters.join("")}]: ${Handlebars.helpers.type.call(this.type)}`
)
return md.join(" ")
}
)
}
@@ -0,0 +1,28 @@
import * as Handlebars from "handlebars"
import reflectionFomatter from "../../utils/reflection-formatter"
import { ReflectionParameterType } from "../../types"
import { parseParams } from "../../utils/params-utils"
export default function () {
Handlebars.registerHelper(
"parameterList",
function (this: ReflectionParameterType[]) {
return list(
this.reduce(
(acc: ReflectionParameterType[], current) =>
parseParams(current, acc),
[]
)
)
}
)
}
function list(parameters: ReflectionParameterType[]) {
const items = parameters.map((parameter) => {
return reflectionFomatter(parameter)
})
return items.join("\n")
}
@@ -0,0 +1,119 @@
import * as Handlebars from "handlebars"
import { ParameterReflection, ReflectionType } from "typedoc"
import { stripLineBreaks } from "../../utils"
import { getReflectionType } from "./type"
import { parseParams } from "../../utils/params-utils"
import { ReflectionParameterType } from "../../types"
export default function () {
Handlebars.registerHelper(
"parameterTable",
function (this: ReflectionParameterType[]) {
return table(
this.reduce(
(acc: ReflectionParameterType[], current: ReflectionParameterType) =>
parseParams(current, acc),
[]
) as ParameterReflection[]
)
}
)
}
function table(parameters: ParameterReflection[]) {
const showDefaults = hasDefaultValues(parameters)
const comments = parameters.map(
(param: ParameterReflection) => !!param.comment?.hasVisibleComponent()
)
const hasComments = !comments.every((value) => !value)
const headers = ["Name", "Type"]
if (showDefaults) {
headers.push("Default value")
}
if (hasComments) {
headers.push("Description")
}
const rows = parameters.map((parameter) => {
const row: string[] = []
const nbsp = " " // ? <== Unicode no-break space character
const rest = parameter.flags.isRest ? "..." : ""
const optional = parameter.flags.isOptional ? "?" : ""
const isDestructuredParam = parameter.name == "__namedParameters"
const isDestructuredParamProp =
parameter.name.startsWith("__namedParameters.")
if (isDestructuredParam) {
row.push(`\`${rest}«destructured»\``)
} else if (isDestructuredParamProp) {
row.push(`${nbsp}\`${rest}${parameter.name.slice(18)}${optional}\``)
} else {
row.push(`\`${rest}${parameter.name}${optional}\``)
}
row.push(
parameter.type
? Handlebars.helpers.type.call(parameter.type, "object")
: getReflectionType(parameter, "object")
)
if (showDefaults) {
row.push(getDefaultValue(parameter))
}
if (hasComments) {
const comments = getComments(parameter)
if (comments) {
row.push(
stripLineBreaks(Handlebars.helpers.comments(comments)).replace(
/\|/g,
"\\|"
)
)
} else {
row.push("-")
}
}
return `| ${row.join(" | ")} |\n`
})
const output = `\n| ${headers.join(" | ")} |\n| ${headers
.map(() => ":------")
.join(" | ")} |\n${rows.join("")}`
return output
}
function getDefaultValue(parameter: ParameterReflection) {
return parameter.defaultValue && parameter.defaultValue !== "..."
? `\`${parameter.defaultValue}\``
: "`undefined`"
}
function hasDefaultValues(parameters: ParameterReflection[]) {
const defaultValues = (parameters as ParameterReflection[]).map(
(param) =>
param.defaultValue !== "{}" &&
param.defaultValue !== "..." &&
!!param.defaultValue
)
return !defaultValues.every((value) => !value)
}
function getComments(parameter: ParameterReflection) {
if (parameter.type instanceof ReflectionType) {
if (
parameter.type?.declaration?.signatures &&
parameter.type?.declaration?.signatures[0]?.comment
) {
return parameter.type?.declaration?.signatures[0]?.comment
}
}
return parameter.comment
}
@@ -0,0 +1,18 @@
import * as Handlebars from "handlebars"
import { MarkdownTheme } from "../../theme"
import { ParameterReflection } from "typedoc"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"parameter",
function (this: ParameterReflection[]) {
const { parameterStyle } = theme.getFormattingOptionsForLocation()
if (parameterStyle === "list") {
return Handlebars.helpers.parameterList.call(this)
} else {
return Handlebars.helpers.parameterTable.call(this)
}
}
)
}
@@ -0,0 +1,25 @@
import * as Handlebars from "handlebars"
import { ReferenceReflection } from "typedoc"
export default function () {
Handlebars.registerHelper(
"referenceMember",
function (this: ReferenceReflection) {
const referenced = this.tryGetTargetReflectionDeep()
if (!referenced) {
return `Re-exports ${this.name}`
}
if (this.name === referenced.name) {
return `Re-exports [${
referenced.name
}](${Handlebars.helpers.relativeURL(referenced.url)})`
}
return `Renames and re-exports [${
referenced.name
}](${Handlebars.helpers.relativeURL(referenced.url)})`
}
)
}
@@ -0,0 +1,36 @@
import * as Handlebars from "handlebars"
import { ContainerReflection, ReflectionKind } from "typedoc"
import { PageEvent } from "typedoc/dist/lib/output/events"
export default function () {
Handlebars.registerHelper(
"reflectionPath",
function (this: PageEvent<ContainerReflection>) {
if (this.model) {
if (this.model.kind && this.model.kind !== ReflectionKind.Module) {
const title: string[] = []
if (this.model.parent && this.model.parent.parent) {
if (this.model.parent.parent.parent) {
title.push(
`[${
this.model.parent.parent.name
}](${Handlebars.helpers.relativeURL(
this.model?.parent?.parent.url
)})`
)
}
title.push(
`[${this.model.parent.name}](${Handlebars.helpers.relativeURL(
this.model.parent.url
)})`
)
}
title.push(this.model.name)
return title.length > 1 ? `${title.join(".")}` : null
}
}
return null
}
)
}
@@ -0,0 +1,40 @@
import * as Handlebars from "handlebars"
import { PageEvent, ParameterReflection, ReflectionKind } from "typedoc"
import { MarkdownTheme } from "../../theme"
import { escapeChars, getDisplayName } from "../../utils"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"reflectionTitle",
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function (this: PageEvent<any>, shouldEscape = true) {
const { reflectionTitle } = theme.getFormattingOptionsForLocation()
const title: string[] = [""]
if (
reflectionTitle?.kind &&
this.model?.kind &&
this.url !== this.project.url
) {
title.push(`${ReflectionKind.singularString(this.model.kind)}: `)
}
if (this.url === this.project.url) {
title.push(theme.indexTitle || getDisplayName(this.model))
} else {
title.push(
shouldEscape ? escapeChars(this.model.name) : this.model.name
)
if (reflectionTitle?.typeParameters && this.model.typeParameters) {
const typeParameters = this.model.typeParameters
.map((typeParameter: ParameterReflection) => typeParameter.name)
.join(", ")
title.push(`<${typeParameters}${shouldEscape ? "\\>" : ">"}`)
}
}
if (reflectionTitle?.suffix) {
title.push(` ${reflectionTitle.suffix}`)
}
return title.join("")
}
)
}
@@ -0,0 +1,13 @@
import * as Handlebars from "handlebars"
import { MarkdownTheme } from "../../theme"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper("relativeURL", function (url: string) {
return url
? theme.publicPath
? theme.publicPath + url
: theme.getRelativeUrl(url)
: url
})
}
@@ -0,0 +1,34 @@
import * as Handlebars from "handlebars"
import { Comment, DeclarationReflection } from "typedoc"
import reflectionFomatter from "../../utils/reflection-formatter"
export default function () {
Handlebars.registerHelper("returns", function (comment: Comment) {
const md: string[] = []
if (comment.blockTags?.length) {
const tags = comment.blockTags
.filter((tag) => tag.tag === "@returns")
.map((tag) => {
let result = Handlebars.helpers.comment(tag.content)
tag.content.forEach((commentPart) => {
if (
"target" in commentPart &&
commentPart.target instanceof DeclarationReflection
) {
const content = commentPart.target.children?.map((childItem) =>
reflectionFomatter(childItem)
)
result += `\n\n<details>\n<summary>\n${
commentPart.target.name
}\n</summary>\n\n${content?.join("\n")}\n\n</details>`
}
})
return result
})
md.push(tags.join("\n\n"))
}
return md.join("")
})
}
@@ -0,0 +1,13 @@
import { MarkdownTheme } from "../../theme"
import * as Handlebars from "handlebars"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"sectionEnabled",
function (sectionName: string): boolean {
const { sections } = theme.getFormattingOptionsForLocation()
return !sections || !(sectionName in sections) || sections[sectionName]
}
)
}
@@ -0,0 +1,60 @@
import * as Handlebars from "handlebars"
import {
ParameterReflection,
ReflectionKind,
SignatureReflection,
} from "typedoc"
import { memberSymbol } from "../../utils"
import { MarkdownTheme } from "../../theme"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"signatureTitle",
function (this: SignatureReflection, accessor?: string, standalone = true) {
const md: string[] = []
if (standalone && !theme.hideMembersSymbol) {
md.push(`${memberSymbol(this)} `)
}
if (this.parent && this.parent.flags?.length > 0) {
md.push(this.parent.flags.map((flag) => `\`${flag}\``).join(" ") + " ")
}
if (accessor) {
md.push(`\`${accessor}\` **${this.name}**`)
} else if (this.name !== "__call" && this.name !== "__type") {
md.push(`**${this.name}**`)
}
if (this.typeParameters) {
md.push(
`<${this.typeParameters
.map((typeParameter) => `\`${typeParameter.name}\``)
.join(", ")}\\>`
)
}
md.push(`(${getParameters(this.parameters)})`)
if (this.type && !this.parent?.kindOf(ReflectionKind.Constructor)) {
md.push(`: ${Handlebars.helpers.type.call(this.type, "object")}`)
}
return md.join("") + (standalone ? "\n" : "")
}
)
}
const getParameters = (
parameters: ParameterReflection[] = [],
backticks = true
) => {
return parameters
.map((param) => {
const isDestructuredParam = param.name == "__namedParameters"
const paramItem = `${param.flags.isRest ? "..." : ""}${
isDestructuredParam ? "«destructured»" : param.name
}${param.flags.isOptional || param.defaultValue ? "?" : ""}`
return backticks ? `\`${paramItem}\`` : paramItem
})
.join(", ")
}
@@ -0,0 +1,12 @@
import { MarkdownTheme } from "../../theme"
import * as Handlebars from "handlebars"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper("titleLevel", function (originalLevel = 3): string {
const { expandMembers } = theme.getFormattingOptionsForLocation()
const level = expandMembers ? originalLevel - 1 : originalLevel
return Array(level).fill("#").join("")
})
}
@@ -0,0 +1,57 @@
import * as Handlebars from "handlebars"
import {
DeclarationReflection,
ProjectReflection,
ReflectionGroup,
} from "typedoc"
import { MarkdownTheme } from "../../theme"
import { escapeChars } from "../../utils"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"toc",
function (this: ProjectReflection | DeclarationReflection) {
const md: string[] = []
const { hideInPageTOC } = theme
const isVisible = this.groups?.some((group) =>
group.allChildrenHaveOwnDocument()
)
function pushGroup(group: ReflectionGroup, md: string[]) {
const children = group.children.map(
(child) =>
`- [${escapeChars(child.name)}](${Handlebars.helpers.relativeURL(
child.url
)})`
)
md.push(children.join("\n"))
}
if ((!hideInPageTOC && this.groups) || (isVisible && this.groups)) {
if (!hideInPageTOC) {
md.push(`## Table of contents\n\n`)
}
const headingLevel = hideInPageTOC ? `##` : `###`
this.groups?.forEach((group) => {
const groupTitle = group.title
if (group.categories) {
group.categories.forEach((category) => {
md.push(`${headingLevel} ${category.title} ${groupTitle}\n\n`)
pushGroup(category as ReflectionGroup, md)
md.push("\n")
})
} else {
if (!hideInPageTOC || group.allChildrenHaveOwnDocument()) {
md.push(`${headingLevel} ${groupTitle}\n\n`)
pushGroup(group, md)
md.push("\n")
}
}
})
}
return md.length > 0 ? md.join("\n") : null
}
)
}
@@ -0,0 +1,57 @@
import * as Handlebars from "handlebars"
import { SignatureReflection } from "typedoc"
import { ArrayType, ReferenceType } from "typedoc/dist/lib/models/types"
import { escapeChars } from "../../utils"
export default function () {
Handlebars.registerHelper(
"typeAndParent",
function (this: ArrayType | ReferenceType) {
const getUrl = (name: string, url: string) =>
`[${name}](${Handlebars.helpers.relativeURL(url)})`
if (this) {
if ("elementType" in this) {
return Handlebars.helpers.typeAndParent.call(this.elementType) + "[]"
} else {
if (this.reflection) {
const md: string[] = []
if (this.reflection instanceof SignatureReflection) {
if (this.reflection.parent?.parent?.url) {
md.push(
getUrl(
this.reflection.parent.parent.name,
this.reflection.parent.parent.url
)
)
if (this.reflection.parent.url) {
md.push(
getUrl(
this.reflection.parent.name,
this.reflection.parent.url
)
)
}
}
} else {
if (this.reflection.parent?.url) {
md.push(
getUrl(
this.reflection.parent.name,
this.reflection.parent.url
)
)
if (this.reflection.url) {
md.push(getUrl(this.reflection.name, this.reflection.url))
}
}
}
return md.join(".")
} else {
return escapeChars(this.toString())
}
}
}
return "void"
}
)
}
@@ -0,0 +1,139 @@
import * as Handlebars from "handlebars"
import { DeclarationReflection, ReflectionType } from "typedoc"
import { MarkdownTheme } from "../../theme"
import { escapeChars, stripLineBreaks } from "../../utils"
import reflectionFomatter from "../../utils/reflection-formatter"
import { parseParams } from "../../utils/params-utils"
import { ReflectionParameterType } from "../../types"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"typeDeclarationMembers",
function (this: DeclarationReflection[]) {
const comments = this.map(
(param) => !!param.comment?.hasVisibleComponent()
)
const hasComments = !comments.every((value) => !value)
const properties = this.reduce(
(acc: ReflectionParameterType[], current: ReflectionParameterType) =>
parseParams(current, acc),
[]
) as DeclarationReflection[]
let result = ""
switch (theme.objectLiteralTypeDeclarationStyle) {
case "list": {
result = getListMarkdownContent(properties)
break
}
case "table": {
result = getTableMarkdownContent(properties, hasComments)
break
}
}
return result
}
)
}
function getListMarkdownContent(properties: DeclarationReflection[]) {
const items = properties.map((property) => reflectionFomatter(property))
return items.join("\n")
}
function getTableMarkdownContent(
properties: DeclarationReflection[],
hasComments: boolean
) {
const headers = ["Name", "Type"]
if (hasComments) {
headers.push("Description")
}
const rows = properties.map((property) => {
const propertyType = getPropertyType(property)
const row: string[] = []
const nameCol: string[] = []
const name =
property.name.match(/[\\`\\|]/g) !== null
? escapeChars(getName(property))
: `\`${getName(property)}\``
nameCol.push(name)
row.push(nameCol.join(" "))
row.push(
Handlebars.helpers.type.call(propertyType).replace(/(?<!\\)\|/g, "\\|")
)
if (hasComments) {
const comments = getComments(property)
if (comments) {
row.push(
stripLineBreaks(Handlebars.helpers.comments(comments)).replace(
/\|/g,
"\\|"
)
)
} else {
row.push("-")
}
}
return `| ${row.join(" | ")} |\n`
})
const output = `\n| ${headers.join(" | ")} |\n| ${headers
.map(() => ":------")
.join(" | ")} |\n${rows.join("")}`
return output
}
function getPropertyType(property: DeclarationReflection) {
if (property.getSignature) {
return property.getSignature.type
}
if (property.setSignature) {
return property.setSignature.type
}
return property.type ? property.type : property
}
function getName(property: DeclarationReflection) {
const md: string[] = []
if (property.flags.isRest) {
md.push("...")
}
if (property.getSignature) {
md.push(`get ${property.getSignature.name}()`)
} else if (property.setSignature) {
md.push(
`set ${
property.setSignature.name
}(${property.setSignature.parameters?.map((parameter) => {
return `${parameter.name}:${Handlebars.helpers.type.call(
parameter.type,
"all",
false
)}`
})})`
)
} else {
md.push(property.name)
}
if (property.flags.isOptional) {
md.push("?")
}
return md.join("")
}
function getComments(property: DeclarationReflection) {
if (property.type instanceof ReflectionType) {
if (property.type?.declaration?.signatures) {
return property.type?.declaration.signatures[0].comment
}
}
if (property.signatures?.length) {
return property.signatures[0].comment
}
return property.comment
}
@@ -0,0 +1,18 @@
import * as Handlebars from "handlebars"
import { TypeParameterReflection } from "typedoc"
import reflectionFomatter from "../../utils/reflection-formatter"
export default function () {
Handlebars.registerHelper(
"typeParameterList",
function (this: TypeParameterReflection[]) {
return list(this)
}
)
}
function list(parameters: TypeParameterReflection[]) {
const items = parameters.map((parameter) => reflectionFomatter(parameter))
return items.join("\n")
}
@@ -0,0 +1,82 @@
import * as Handlebars from "handlebars"
import { TypeParameterReflection } from "typedoc"
import { stripLineBreaks } from "../../utils"
export default function () {
Handlebars.registerHelper(
"typeParameterTable",
function (this: TypeParameterReflection[]) {
return table(this)
}
)
}
function table(parameters: TypeParameterReflection[]) {
const showTypeCol = hasTypes(parameters)
const comments = parameters.map(
(param: TypeParameterReflection) => !!param.comment?.hasVisibleComponent()
)
const hasComments = !comments.every((value) => !value)
const headers = ["Name"]
if (showTypeCol) {
headers.push("Type")
}
if (hasComments) {
headers.push("Description")
}
const rows = parameters.map((parameter: TypeParameterReflection) => {
const row: string[] = []
row.push(`\`${parameter.name}\``)
if (showTypeCol) {
const typeCol: string[] = []
if (!parameter.type && !parameter.default) {
typeCol.push(`\`${parameter.name}\``)
}
if (parameter.type) {
typeCol.push(
`extends ${Handlebars.helpers.type.call(parameter.type, "object")}`
)
}
if (parameter.default) {
if (parameter.type) {
typeCol.push(" = ")
}
typeCol.push(Handlebars.helpers.type.call(parameter.default))
}
row.push(typeCol.join(""))
}
if (hasComments) {
if (parameter.comment?.summary) {
row.push(
stripLineBreaks(
Handlebars.helpers.comment(parameter.comment?.summary)
).replace(/\|/g, "\\|")
)
} else {
row.push("-")
}
}
return `| ${row.join(" | ")} |\n`
})
const output = `\n| ${headers.join(" | ")} |\n| ${headers
.map(() => ":------")
.join(" | ")} |\n${rows.join("")}`
return output
}
function hasTypes(parameters: TypeParameterReflection[]) {
const types = (parameters as TypeParameterReflection[]).map(
(param) => !!param.type || !!param.default
)
return !types.every((value) => !value)
}
@@ -0,0 +1,18 @@
import * as Handlebars from "handlebars"
import { MarkdownTheme } from "../../theme"
import { TypeParameterReflection } from "typedoc"
export default function (theme: MarkdownTheme) {
Handlebars.registerHelper(
"typeParameter",
function (this: TypeParameterReflection[]) {
const { parameterStyle } = theme.getFormattingOptionsForLocation()
if (parameterStyle === "list") {
return Handlebars.helpers.typeParameterList.call(this)
} else {
return Handlebars.helpers.typeParameterTable.call(this)
}
}
)
}
@@ -0,0 +1,306 @@
import * as Handlebars from "handlebars"
import {
ArrayType,
ConditionalType,
DeclarationReflection,
IndexedAccessType,
InferredType,
IntersectionType,
IntrinsicType,
LiteralType,
PredicateType,
QueryType,
ReferenceType,
ReflectionType,
SignatureReflection,
TupleType,
TypeOperatorType,
UnionType,
UnknownType,
} from "typedoc"
import { escapeChars } from "../../utils"
import { ReflectionParameterType } from "../../types"
type Collapse = "object" | "function" | "all" | "none"
export default function () {
Handlebars.registerHelper(
"type",
function (
this:
| ArrayType
| IntersectionType
| IntrinsicType
| ReferenceType
| TupleType
| UnionType
| TypeOperatorType
| QueryType
| PredicateType
| ReferenceType
| ConditionalType
| IndexedAccessType
| UnknownType
| InferredType,
collapse: Collapse = "none",
emphasis = true
) {
if (this instanceof ReferenceType) {
return getReferenceType(this, emphasis)
}
if (this instanceof ArrayType && this.elementType) {
return getArrayType(this, emphasis)
}
if (this instanceof UnionType && this.types) {
return getUnionType(this, emphasis)
}
if (this instanceof IntersectionType && this.types) {
return getIntersectionType(this)
}
if (this instanceof TupleType && this.elements) {
return getTupleType(this)
}
if (this instanceof IntrinsicType && this.name) {
return getIntrinsicType(this, emphasis)
}
if (this instanceof ReflectionType) {
return getReflectionType(this, collapse)
}
if (this instanceof DeclarationReflection) {
return getReflectionType(this, collapse)
}
if (this instanceof TypeOperatorType) {
return getTypeOperatorType(this)
}
if (this instanceof QueryType) {
return getQueryType(this)
}
if (this instanceof ConditionalType) {
return getConditionalType(this)
}
if (this instanceof IndexedAccessType) {
return getIndexAccessType(this)
}
if (this instanceof UnknownType) {
return getUnknownType(this)
}
if (this instanceof InferredType) {
return getInferredType(this)
}
if (this instanceof LiteralType) {
return getLiteralType(this)
}
return this ? escapeChars(this.toString()) : ""
}
)
}
function getLiteralType(model: LiteralType) {
if (typeof model.value === "bigint") {
return `\`${model.value}n\``
}
return `\`\`${JSON.stringify(model.value)}\`\``
}
export function getReflectionType(
model: ReflectionParameterType,
collapse: Collapse
) {
const root = model instanceof ReflectionType ? model.declaration : model
if ("signatures" in root && root.signatures) {
return collapse === "function" || collapse === "all"
? `\`fn\``
: getFunctionType(root.signatures)
}
return collapse === "object" || collapse === "all"
? `\`Object\``
: getDeclarationType(root as DeclarationReflection)
}
export function getDeclarationType(model: DeclarationReflection) {
if (model.indexSignature || model.children) {
let indexSignature = ""
const declarationIndexSignature = model.indexSignature
if (declarationIndexSignature) {
const key = declarationIndexSignature.parameters
? declarationIndexSignature.parameters.map(
(param) => `\`[${param.name}: ${param.type}]\``
)
: ""
const obj = Handlebars.helpers.type.call(declarationIndexSignature.type)
indexSignature = `${key}: ${obj}; `
}
const types =
model.children &&
model.children.map((obj) => {
return `\`${obj.name}${
obj.flags.isOptional ? "?" : ""
}\`: ${Handlebars.helpers.type.call(
obj.signatures || obj.children ? obj : obj.type
)} ${
obj.defaultValue && obj.defaultValue !== "..."
? `= ${escapeChars(obj.defaultValue)}`
: ""
}`
})
return `{ ${indexSignature ? indexSignature : ""}${
types ? types.join("; ") : ""
} }${
model.defaultValue && model.defaultValue !== "..."
? `= ${escapeChars(model.defaultValue)}`
: ""
}`
}
return "{}"
}
export function getFunctionType(modelSignatures: SignatureReflection[]) {
const functions = modelSignatures.map((fn) => {
const typeParams = fn.typeParameters
? `<${fn.typeParameters
.map((typeParameter) => typeParameter.name)
.join(", ")}\\>`
: []
const params = fn.parameters
? fn.parameters.map((param) => {
return `${param.flags.isRest ? "..." : ""}\`${param.name}${
param.flags.isOptional ? "?" : ""
}\`: ${Handlebars.helpers.type.call(param.type ? param.type : param)}`
})
: []
const returns = Handlebars.helpers.type.call(fn.type)
return typeParams + `(${params.join(", ")}) => ${returns}`
})
return functions.join("")
}
export function getReferenceType(model: ReferenceType, emphasis: boolean) {
if (model.reflection || (model.name && model.typeArguments)) {
const reflection: string[] = []
if (model.reflection?.url) {
reflection.push(
`[${`\`${model.reflection.name}\``}](${Handlebars.helpers.relativeURL(
model.reflection.url
)})`
)
} else {
reflection.push(
model.externalUrl
? `[${`\`${model.name}\``}]( ${model.externalUrl} )`
: `\`${model.name}\``
)
}
if (model.typeArguments && model.typeArguments.length > 0) {
reflection.push(
`<${model.typeArguments
.map((typeArgument) => Handlebars.helpers.type.call(typeArgument))
.join(", ")}\\>`
)
}
return reflection.join("")
}
return emphasis
? model.externalUrl
? `[${`\`${model.name}\``}]( ${model.externalUrl} )`
: `\`${model.name}\``
: escapeChars(model.name)
}
export function getArrayType(model: ArrayType, emphasis: boolean) {
const arrayType = Handlebars.helpers.type.call(
model.elementType,
"none",
emphasis
)
return model.elementType.type === "union"
? `(${arrayType})[]`
: `${arrayType}[]`
}
export function getUnionType(model: UnionType, emphasis: boolean) {
return model.types
.map((unionType) =>
Handlebars.helpers.type.call(unionType, "none", emphasis)
)
.join(` \\| `)
}
export function getIntersectionType(model: IntersectionType) {
return model.types
.map((intersectionType) => Handlebars.helpers.type.call(intersectionType))
.join(" & ")
}
export function getTupleType(model: TupleType) {
return `[${model.elements
.map((element) => Handlebars.helpers.type.call(element))
.join(", ")}]`
}
export function getIntrinsicType(model: IntrinsicType, emphasis: boolean) {
return emphasis ? `\`${model.name}\`` : escapeChars(model.name)
}
export function getTypeOperatorType(model: TypeOperatorType) {
return `${model.operator} ${Handlebars.helpers.type.call(model.target)}`
}
export function getQueryType(model: QueryType) {
return `typeof ${Handlebars.helpers.type.call(model.queryType)}`
}
export function getInferredType(model: InferredType) {
return `infer ${escapeChars(model.name)}`
}
export function getUnknownType(model: UnknownType) {
return escapeChars(model.name)
}
export function getConditionalType(model: ConditionalType) {
const md: string[] = []
if (model.checkType) {
md.push(Handlebars.helpers.type.call(model.checkType))
}
md.push("extends")
if (model.extendsType) {
md.push(Handlebars.helpers.type.call(model.extendsType))
}
md.push("?")
if (model.trueType) {
md.push(Handlebars.helpers.type.call(model.trueType))
}
md.push(":")
if (model.falseType) {
md.push(Handlebars.helpers.type.call(model.falseType))
}
return md.join(" ")
}
export function getIndexAccessType(model: IndexedAccessType) {
const md: string[] = []
if (model.objectType) {
md.push(Handlebars.helpers.type.call(model.objectType))
}
if (model.indexType) {
md.push(`[${Handlebars.helpers.type.call(model.indexType)}]`)
}
return md.join("")
}
@@ -0,0 +1,13 @@
{{#if (sectionEnabled "comment")}}
{{#with comment}}
{{#if hasVisibleComponent}}
{{{comments this}}}
{{/if}}
{{/with}}
{{/if}}
@@ -0,0 +1,5 @@
{{#ifShowBreadcrumbs}}
{{{breadcrumbs}}}
{{/ifShowBreadcrumbs}}
@@ -0,0 +1,3 @@
{{ toc }}
{{> members}}
@@ -0,0 +1,87 @@
{{{declarationTitle}}}
{{> comment}}
{{#if (sectionEnabled "member_declaration_typeParameters")}}
{{#if typeParameters}}
{{titleLevel 4}} Type parameters
{{#with typeParameters}}
{{{typeParameter}}}
{{/with}}
{{/if}}
{{/if}}
{{#if type.declaration}}
{{#if (sectionEnabled "member_declaration_indexSignature")}}
{{#if type.declaration.indexSignature}}
{{#with type.declaration.indexSignature}}
{{titleLevel 4}} Index signature
{{{indexSignatureTitle}}}
{{> comment}}
{{/with}}
{{/if}}
{{/if}}
{{#if (sectionEnabled "member_declaration_signatures")}}
{{#if type.declaration.signatures}}
{{#if type.declaration.children}}
{{titleLevel 4}} Call signature
{{else}}
{{titleLevel 4}} Type declaration
{{/if}}
{{#each type.declaration.signatures}}
{{> member.signature showSources=false }}
{{/each}}
{{/if}}
{{/if}}
{{#if (sectionEnabled "member_declaration_typeDeclaration")}}
{{#if type.declaration.children}}
{{#with type.declaration}}
{{titleLevel 4}} Type declaration
{{/with}}
{{#with type.declaration.children}}
{{{typeDeclarationMembers}}}
{{/with}}
{{/if}}
{{/if}}
{{/if}}
{{> member.sources}}
@@ -0,0 +1,27 @@
{{#if getSignature}}
{{#if (sectionEnabled "member_getteSetter_getSignature")}}
{{#with getSignature}}
{{> member.signature accessor="get" showSources=true }}
{{/with}}
{{/if}}
{{/if}}
{{#if setSignature}}
{{#if (sectionEnabled "member_getteSetter_setSignature")}}
{{#with setSignature}}
{{> member.signature accessor="set" showSources=true }}
{{/with}}
{{/if}}
{{/if}}
@@ -0,0 +1,61 @@
{{#unless hasOwnDocument}}
{{#if name}}
{{titleLevel 3}} {{#ifNamedAnchors}}<a id="{{anchor}}" name="{{this.anchor}}"></a> {{/ifNamedAnchors}}{{ escape name }}
{{/if}}
{{/unless}}
{{#if signatures}}
{{#if (sectionEnabled "member_signatures")}}
{{#each signatures}}
{{> member.signature showSources=true }}
{{/each}}
{{/if}}
{{else}}
{{#if hasGetterOrSetter}}
{{#if (sectionEnabled "member_getterSetter")}}
{{> member.getterSetter}}
{{/if}}
{{else}}
{{#ifIsReference}}
{{#if (sectionEnabled "member_reference")}}
{{referenceMember}}
{{/if}}
{{else}}
{{#if (sectionEnabled "member_declaration")}}
{{> member.declaration}}
{{/if}}
{{/ifIsReference}}
{{/if}}
{{/if}}
{{#unless @last}}
{{#unless hasOwnDocument}}
___
{{/unless}}
{{/unless}}
@@ -0,0 +1,147 @@
{{#if (sectionEnabled "member_signature_title")}}
{{{signatureTitle accessor}}}
{{/if}}
{{#if (sectionEnabled "member_signature_comment")}}
{{#with comment}}
{{#if hasVisibleComponent}}
{{{comments this true false}}}
{{/if}}
{{/with}}
{{/if}}
{{#if (sectionEnabled "member_signature_typeParameters")}}
{{#if typeParameters}}
{{#with typeParameters}}
{{{typeParameter}}}
{{/with}}
{{/if}}
{{/if}}
{{#if (sectionEnabled "member_signature_parameters")}}
{{#if parameters}}
{{#if showSources}}
{{titleLevel 4}} Parameters
{{else}}
{{titleLevel 5}} Parameters
{{/if}}
{{#with parameters}}
{{{parameter}}}
{{/with}}
{{/if}}
{{/if}}
{{#ifShowReturns}}
{{#if type}}
{{#if showSources}}
{{titleLevel 4}} Returns
{{else}}
{{titleLevel 5}} Returns
{{/if}}
{{#if (getFormattingOption "showReturnSignature")}}
{{#with type}}
{{{type 'all'}}}
{{/with}}
{{/if}}
{{#with comment}}
{{{returns this}}}
{{/with}}
{{#with type}}
{{#if (sectionEnabled "member_signature_declarationSignatures")}}
{{#if declaration.signatures}}
{{#each declaration.signatures}}
{{> member.signature showSources=false }}
{{/each}}
{{/if}}
{{/if}}
{{#if (sectionEnabled "member_signature_declarationChildren")}}
{{#if declaration.children}}
{{#with declaration.children}}
{{{typeDeclarationMembers}}}
{{/with}}
{{/if}}
{{/if}}
{{/with}}
{{/if}}
{{/ifShowReturns}}
{{#if (sectionEnabled "member_signature_comment")}}
{{#with comment}}
{{#if hasVisibleComponent}}
{{{comments this false true 4}}}
{{/if}}
{{/with}}
{{/if}}
{{#if (sectionEnabled "member_signature_sources")}}
{{#if showSources}}
{{> member.sources}}
{{/if}}
{{/if}}
@@ -0,0 +1,71 @@
{{#if (sectionEnabled "member_sources_implementationOf")}}
{{#if implementationOf}}
{{titleLevel 4}} Implementation of
{{#with implementationOf}}
{{typeAndParent}}
{{/with}}
{{/if}}
{{/if}}
{{#if (sectionEnabled "member_sources_inheritedFrom")}}
{{#if inheritedFrom}}
{{titleLevel 4}} Inherited from
{{#with inheritedFrom}}
{{{typeAndParent}}}
{{/with}}
{{/if}}
{{/if}}
{{#if (sectionEnabled "member_sources_overrides")}}
{{#if overwrites}}
{{titleLevel 4}} Overrides
{{#with overwrites}}
{{typeAndParent}}
{{/with}}
{{/if}}
{{/if}}
{{#if (sectionEnabled "member_sources_definedIn")}}
{{#if sources}}
{{titleLevel 4}} Defined in
{{#each sources}}
{{#if url}}
[{{fileName}}:{{line}}]({{url}})
{{else}}
{{fileName}}:{{line}}
{{/if}}
{{/each}}
{{/if}}
{{/if}}
@@ -0,0 +1,41 @@
{{#if (sectionEnabled "members_group_categories")}}
{{#if categories}}
{{#each categories}}
{{#unless @first}}
___
{{/unless}}
{{#unless (getFormattingOption "expandMembers")}}
## {{title}} {{../title}}
{{/unless}}
{{#each children}}
{{> member}}
{{/each}}
{{/each}}
{{else}}
{{#unless (getFormattingOption "expandMembers")}}
## {{title}}
{{/unless}}
{{#each children}}
{{> member}}
{{/each}}
{{/if}}
{{/if}}
@@ -0,0 +1,43 @@
{{#if (sectionEnabled "members_categories")}}
{{#if categories}}
{{#each categories}}
{{#unless allChildrenHaveOwnDocument}}
{{#unless (getFormattingOption "expandMembers")}}
## {{title}}
{{/unless}}
{{#each children}}
{{#unless hasOwnDocument}}
{{> member}}
{{/unless}}
{{/each}}
{{/unless}}
{{/each}}
{{else}}
{{#each groups}}
{{#unless allChildrenHaveOwnDocument}}
{{> members.group}}
{{/unless}}
{{/each}}
{{/if}}
{{/if}}
@@ -0,0 +1,13 @@
{{#ifShowPageTitle}}
# {{{reflectionTitle true}}}
{{/ifShowPageTitle}}
{{{getFormattingOption "reflectionDescription"}}}
{{#if (sectionEnabled "title_reflectionPath")}}
{{{reflectionPath}}}
{{/if}}
@@ -0,0 +1,7 @@
{{> header}}
{{#with model.readme}}
{{{comment this}}}
{{/with}}
@@ -0,0 +1,127 @@
{{> header}}
{{> title}}
{{#with model}}
{{#if (sectionEnabled "reflection_comment")}}
{{#if hasComment}}
{{> comment}}
{{/if}}
{{/if}}
{{/with}}
{{#if (sectionEnabled "reflection_typeParameters")}}
{{#if model.typeParameters}}
## Type parameters
{{#with model.typeParameters}}
{{{typeParameter}}}
{{/with}}
{{/if}}
{{/if}}
{{#if (sectionEnabled "reflection_hierarchy")}}
{{#ifShowTypeHierarchy}}
## Hierarchy
{{#with model.typeHierarchy}}
{{{hierarchy 0}}}
{{/with}}
{{/ifShowTypeHierarchy}}
{{/if}}
{{#if (sectionEnabled "reflection_implements")}}
{{#if model.implementedTypes}}
## Implements
{{#each model.implementedTypes}}
- {{{type}}}
{{/each}}
{{/if}}
{{/if}}
{{#if (sectionEnabled "reflection_implementedBy")}}
{{#if model.implementedBy}}
## Implemented by
{{#each model.implementedBy}}
- {{{type}}}
{{/each}}
{{/if}}
{{/if}}
{{#if (sectionEnabled "reflection_callable")}}
{{#if model.signatures}}
## Callable
{{#with model}}
{{#each signatures}}
### {{name}}
{{> member.signature showSources=true }}
{{/each}}
{{/with}}
{{/if}}
{{/if}}
{{#if (sectionEnabled "reflection_indexable")}}
{{#if model.indexSignature}}
## Indexable
{{#with model}}
{{#with indexSignature}}
{{{indexSignatureTitle}}}
{{> comment}}
{{/with}}
{{/with}}
{{/if}}
{{/if}}
{{#with model}}
{{> main}}
{{/with}}
@@ -0,0 +1,9 @@
{{> header}}
{{> title}}
{{#with model}}
{{> member}}
{{/with}}
@@ -0,0 +1,454 @@
import * as path from "path"
import {
ContainerReflection,
DeclarationReflection,
PageEvent,
ProjectReflection,
Reflection,
ReflectionGroup,
ReflectionKind,
RenderTemplate,
Renderer,
RendererEvent,
Theme,
UrlMapping,
} from "typedoc"
import { getKindPlural } from "./groups"
import {
indexTemplate,
reflectionMemberTemplate,
reflectionTemplate,
registerHelpers,
registerPartials,
} from "./render-utils"
import { formatContents } from "./utils"
import {
FormattingOptionType,
FormattingOptionsType,
Mapping,
NavigationItem,
ObjectLiteralDeclarationStyle,
} from "./types"
export class MarkdownTheme extends Theme {
allReflectionsHaveOwnDocument!: boolean
entryDocument: string
entryPoints!: string[]
filenameSeparator!: string
hideBreadcrumbs!: boolean
hideInPageTOC!: boolean
hidePageTitle!: boolean
hideMembersSymbol!: boolean
includes!: string
indexTitle!: string
mediaDirectory!: string
namedAnchors!: boolean
readme!: string
out!: string
publicPath!: string
preserveAnchorCasing!: boolean
objectLiteralTypeDeclarationStyle: ObjectLiteralDeclarationStyle
formattingOptions: FormattingOptionsType
project?: ProjectReflection
reflection?: DeclarationReflection
location!: string
anchorMap: Record<string, string[]> = {}
static URL_PREFIX = /^(http|ftp)s?:\/\//
constructor(renderer: Renderer) {
super(renderer)
// prettier-ignore
this.allReflectionsHaveOwnDocument = this.getOption("allReflectionsHaveOwnDocument") as boolean
this.entryDocument = this.getOption("entryDocument") as string
this.entryPoints = this.getOption("entryPoints") as string[]
this.filenameSeparator = this.getOption("filenameSeparator") as string
this.hideBreadcrumbs = this.getOption("hideBreadcrumbs") as boolean
this.hideInPageTOC = this.getOption("hideInPageTOC") as boolean
this.hidePageTitle = this.getOption("hidePageTitle") as boolean
this.hideMembersSymbol = this.getOption("hideMembersSymbol") as boolean
this.includes = this.getOption("includes") as string
this.indexTitle = this.getOption("indexTitle") as string
this.mediaDirectory = this.getOption("media") as string
this.namedAnchors = this.getOption("namedAnchors") as boolean
this.readme = this.getOption("readme") as string
this.out = this.getOption("out") as string
this.publicPath = this.getOption("publicPath") as string
this.preserveAnchorCasing = this.getOption(
"preserveAnchorCasing"
) as boolean
this.objectLiteralTypeDeclarationStyle = this.getOption(
"objectLiteralTypeDeclarationStyle"
) as ObjectLiteralDeclarationStyle
this.formattingOptions = this.getOption(
"formatting"
) as FormattingOptionsType
this.listenTo(this.owner, {
[RendererEvent.BEGIN]: this.onBeginRenderer,
[PageEvent.BEGIN]: this.onBeginPage,
})
registerPartials()
registerHelpers(this)
}
render(
page: PageEvent<Reflection>,
template: RenderTemplate<PageEvent<Reflection>>
): string {
return formatContents(template(page) as string)
}
getOption(key: string) {
return this.application.options.getValue(key)
}
getUrls(project: ProjectReflection) {
const urls: UrlMapping[] = []
const noReadmeFile = this.readme.endsWith("none")
if (noReadmeFile) {
project.url = this.entryDocument
urls.push(
new UrlMapping(
this.entryDocument,
project,
this.getReflectionTemplate()
)
)
} else {
project.url = this.globalsFile
urls.push(
new UrlMapping(this.globalsFile, project, this.getReflectionTemplate())
)
urls.push(
new UrlMapping(this.entryDocument, project, this.getIndexTemplate())
)
}
project.children?.forEach((child: Reflection) => {
if (child instanceof DeclarationReflection) {
this.buildUrls(child as DeclarationReflection, urls)
}
})
return urls
}
buildUrls(
reflection: DeclarationReflection,
urls: UrlMapping[]
): UrlMapping[] {
const mapping = this.mappings.find((mapping) =>
reflection.kindOf(mapping.kind)
)
if (mapping) {
if (!reflection.url || !MarkdownTheme.URL_PREFIX.test(reflection.url)) {
const url = this.toUrl(mapping, reflection)
urls.push(new UrlMapping(url, reflection, mapping.template))
reflection.url = url
reflection.hasOwnDocument = true
}
for (const child of reflection.children || []) {
if (mapping.isLeaf) {
this.applyAnchorUrl(child, reflection)
} else {
this.buildUrls(child, urls)
}
}
} else if (reflection.parent) {
this.applyAnchorUrl(reflection, reflection.parent, true)
}
return urls
}
toUrl(mapping: Mapping, reflection: DeclarationReflection) {
return mapping.directory + "/" + this.getUrl(reflection) + ".md"
}
getUrl(reflection: Reflection, relative?: Reflection): string {
let url = reflection.getAlias()
if (
reflection.parent &&
reflection.parent !== relative &&
!(reflection.parent instanceof ProjectReflection)
) {
url =
this.getUrl(reflection.parent, relative) + this.filenameSeparator + url
}
return url.replace(/^_/, "")
}
applyAnchorUrl(
reflection: Reflection,
container: Reflection,
isSymbol = false
) {
if (
container.url &&
(!reflection.url || !MarkdownTheme.URL_PREFIX.test(reflection.url))
) {
const reflectionId = this.preserveAnchorCasing
? reflection.name
: reflection.name.toLowerCase()
if (isSymbol) {
this.anchorMap[container.url]
? this.anchorMap[container.url].push(reflectionId)
: (this.anchorMap[container.url] = [reflectionId])
}
const count = this.anchorMap[container.url]?.filter(
(id) => id === reflectionId
)?.length
const anchor = this.toAnchorRef(
reflectionId + (count > 1 ? "-" + (count - 1).toString() : "")
)
reflection.url = container.url + "#" + anchor
reflection.anchor = anchor
reflection.hasOwnDocument = false
}
reflection.traverse((child) => {
if (child instanceof DeclarationReflection) {
this.applyAnchorUrl(child, container)
}
})
}
toAnchorRef(reflectionId: string) {
return reflectionId
}
getRelativeUrl(absolute: string) {
if (MarkdownTheme.URL_PREFIX.test(absolute)) {
return absolute
} else {
const relative = path.relative(
path.dirname(this.location),
path.dirname(absolute)
)
return path.join(relative, path.basename(absolute)).replace(/\\/g, "/")
}
}
getReflectionTemplate() {
return (pageEvent: PageEvent<ContainerReflection>) => {
return reflectionTemplate(pageEvent, {
allowProtoMethodsByDefault: true,
allowProtoPropertiesByDefault: true,
data: { theme: this },
})
}
}
getReflectionMemberTemplate() {
return (pageEvent: PageEvent<ContainerReflection>) => {
return reflectionMemberTemplate(pageEvent, {
allowProtoMethodsByDefault: true,
allowProtoPropertiesByDefault: true,
data: { theme: this },
})
}
}
getIndexTemplate() {
return (pageEvent: PageEvent<ContainerReflection>) => {
return indexTemplate(pageEvent, {
allowProtoMethodsByDefault: true,
allowProtoPropertiesByDefault: true,
data: { theme: this },
})
}
}
getNavigation(project: ProjectReflection) {
const urls = this.getUrls(project)
const getUrlMapping = (name: string) => {
if (!name) {
return ""
}
return urls.find((url) => url.model.name === name)
}
const createNavigationItem = (
title: string,
url: string | undefined,
isLabel: boolean,
children: NavigationItem[] = []
) => {
const navigationItem = new NavigationItem(title, url)
navigationItem.isLabel = isLabel
navigationItem.children = children
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { reflection, parent, ...filteredNavigationItem } = navigationItem
return filteredNavigationItem as NavigationItem
}
const navigation = createNavigationItem(project.name, undefined, false)
const hasReadme = !this.readme.endsWith("none")
if (hasReadme) {
navigation.children?.push(
createNavigationItem("Readme", this.entryDocument, false)
)
}
if (this.entryPoints.length === 1) {
navigation.children?.push(
createNavigationItem(
"Exports",
hasReadme ? this.globalsFile : this.entryDocument,
false
)
)
}
this.mappings.forEach((mapping) => {
const kind = mapping.kind[0]
const items = project.getReflectionsByKind(kind)
if (items.length > 0) {
const children = items
.map((item) => {
const urlMapping = getUrlMapping(item.name) || ""
return createNavigationItem(
item.getFullName(),
typeof urlMapping === "string" ? urlMapping : urlMapping.url,
true
)
})
.sort((a, b) => {
return a.title > b.title ? 1 : -1
})
const group = createNavigationItem(
getKindPlural(kind),
undefined,
true,
children
)
navigation.children?.push(group)
}
})
return navigation
}
get mappings(): Mapping[] {
return [
{
kind: [ReflectionKind.Module],
isLeaf: false,
directory: "modules",
template: this.getReflectionTemplate(),
},
{
kind: [ReflectionKind.Namespace],
isLeaf: false,
directory: "modules",
template: this.getReflectionTemplate(),
},
{
kind: [ReflectionKind.Enum],
isLeaf: false,
directory: "enums",
template: this.getReflectionTemplate(),
},
{
kind: [ReflectionKind.Class],
isLeaf: false,
directory: "classes",
template: this.getReflectionTemplate(),
},
{
kind: [ReflectionKind.Interface],
isLeaf: false,
directory: "interfaces",
template: this.getReflectionTemplate(),
},
...(this.allReflectionsHaveOwnDocument
? [
{
kind: [ReflectionKind.TypeAlias],
isLeaf: true,
directory: "types",
template: this.getReflectionMemberTemplate(),
},
{
kind: [ReflectionKind.Variable],
isLeaf: true,
directory: "variables",
template: this.getReflectionMemberTemplate(),
},
{
kind: [ReflectionKind.Function],
isLeaf: true,
directory: "functions",
template: this.getReflectionMemberTemplate(),
},
]
: []),
]
}
/**
* Triggered before the renderer starts rendering a project.
*
* @param event An event object describing the current render operation.
*/
protected onBeginRenderer(event: RendererEvent) {
this.project = event.project
}
/**
* Triggered before a document will be rendered.
*
* @param page An event object describing the current render operation.
*/
protected onBeginPage(page: PageEvent) {
this.location = page.url
this.reflection =
page.model instanceof DeclarationReflection ? page.model : undefined
const options = this.getFormattingOptionsForLocation()
if (this.reflection && this.reflection.groups) {
// filter out unwanted groups
const tempGroups: ReflectionGroup[] = []
this.reflection.groups.forEach((reflectionGroup) => {
if (
!options.reflectionGroups ||
!(reflectionGroup.title in options.reflectionGroups) ||
options.reflectionGroups[reflectionGroup.title]
) {
tempGroups.push(reflectionGroup)
}
})
this.reflection.groups = tempGroups
}
}
get globalsFile() {
return "modules.md"
}
getFormattingOptionsForLocation(): FormattingOptionType {
if (!this.location) {
return {}
}
const optionKey =
Object.keys(this.formattingOptions).find((key) => {
if (key === "*") {
return false
}
const keyPattern = new RegExp(key)
if (keyPattern.test(this.location)) {
return true
}
}) || "*"
return optionKey in this.formattingOptions
? this.formattingOptions[optionKey]
: {}
}
}
@@ -0,0 +1,143 @@
import {
ContainerReflection,
DeclarationReflection,
PageEvent,
ParameterReflection,
Reflection,
ReflectionKind,
TypeParameterReflection,
} from "typedoc"
export type ParameterStyle = "table" | "list"
export type ReflectionTitleOptions = {
typeParameters?: boolean
kind?: boolean
}
export type ObjectLiteralDeclarationStyle = "table" | "list"
export type SectionKey =
| "comment"
| "member_declaration_typeParameters"
| "member_declaration_indexSignature"
| "member_declaration_signatures"
| "member_declaration_typeDeclaration"
| "member_getteSetter_getSignature"
| "member_getteSetter_setSignature"
| "member_signatures"
| "member_getterSetter"
| "member_reference"
| "member_declaration"
| "member_signature_title"
| "member_signature_comment"
| "member_signature_typeParameters"
| "member_signature_parameters"
| "showReturnSignature"
| "member_signature_declarationSignatures"
| "member_signature_declarationChildren"
| "member_signature_comment"
| "member_signature_sources"
| "member_sources_implementationOf"
| "member_sources_inheritedFrom"
| "member_sources_overrides"
| "member_sources_definedIn"
| "members_group_categories"
| "members_categories"
| "title_reflectionPath"
| "reflection_comment"
| "reflection_typeParameters"
| "reflection_hierarchy"
| "reflection_implements"
| "reflection_implementedBy"
| "reflection_callable"
| "reflection_indexable"
export type FormattingOptionType = {
sections?: {
[k in SectionKey]: boolean
}
reflectionGroups?: {
[k: string]: boolean
}
reflectionTitle?: {
kind: boolean
typeParameters: boolean
suffix?: string
}
reflectionDescription?: string
expandMembers?: boolean
showCommentsAsHeader?: boolean
parameterStyle?: ParameterStyle
showReturnSignature?: boolean
}
export type FormattingOptionsType = {
[k: string]: FormattingOptionType
}
export type ReflectionParameterType =
| ParameterReflection
| DeclarationReflection
| TypeParameterReflection
export type Mapping = {
kind: ReflectionKind[]
isLeaf: boolean
directory: string
template: (pageEvent: PageEvent<ContainerReflection>) => string
}
export class NavigationItem {
title: string
url: string
dedicatedUrls?: string[]
parent?: NavigationItem
children?: NavigationItem[]
isLabel?: boolean
isVisible?: boolean
isCurrent?: boolean
isModules?: boolean
isInPath?: boolean
reflection?: Reflection
constructor(
title?: string,
url?: string,
parent?: NavigationItem,
reflection?: Reflection
) {
this.title = title || ""
this.url = url || ""
this.parent = parent
this.reflection = reflection
if (!url) {
this.isLabel = true
}
if (this.parent) {
if (!this.parent.children) {
this.parent.children = []
}
this.parent.children.push(this)
}
}
static create(
reflection: Reflection,
parent?: NavigationItem,
useShortNames?: boolean
) {
let name: string
if (useShortNames || (parent && parent.parent)) {
name = reflection.name
} else {
name = reflection.getFullName()
}
name = name.trim()
return new NavigationItem(name, reflection.url, parent, reflection)
}
}
@@ -0,0 +1,83 @@
import {
DeclarationReflection,
ParameterReflection,
ProjectReflection,
Reflection,
ReflectionKind,
SignatureReflection,
} from "typedoc"
export function formatContents(contents: string) {
return (
contents
.replace(/[\r\n]{3,}/g, "\n\n")
.replace(/!spaces/g, "")
.replace(/^\s+|\s+$/g, "") + "\n"
)
}
export function escapeChars(str: string) {
return str
.replace(/>/g, "\\>")
.replace(/_/g, "\\_")
.replace(/`/g, "\\`")
.replace(/\|/g, "\\|")
}
export function memberSymbol(
reflection: DeclarationReflection | ParameterReflection | SignatureReflection
) {
const isStatic = reflection.flags && reflection.flags.isStatic
if (reflection.kind === ReflectionKind.CallSignature) {
return "▸"
}
if (reflection.kind === ReflectionKind.TypeAlias) {
return "Ƭ"
}
if (reflection.kind === ReflectionKind.Property && isStatic) {
return "▪"
}
return "•"
}
export function spaces(length: number) {
return `!spaces${[...Array(length)].map(() => " ").join("")}`
}
export function stripComments(str: string) {
return str
.replace(/(?:\/\*(?:[\s\S]*?)\*\/)|(?:^\s*\/\/(?:.*)$)/g, " ")
.replace(/\n/g, "")
.replace(/^\s+|\s+$|(\s)+/g, "$1")
}
export function stripLineBreaks(str: string) {
return str
? str
.replace(/\n/g, " ")
.replace(/\r/g, " ")
.replace(/\t/g, " ")
.replace(/[\s]{2,}/g, " ")
.trim()
: ""
}
export function camelToTitleCase(text: string) {
return (
text.substring(0, 1).toUpperCase() +
text.substring(1).replace(/[a-z][A-Z]/g, (x) => `${x[0]} ${x[1]}`)
)
}
export function getDisplayName(refl: Reflection): string {
let version = ""
if (
(refl instanceof DeclarationReflection ||
refl instanceof ProjectReflection) &&
refl.packageVersion
) {
version = ` - v${refl.packageVersion}`
}
return `${refl.name}${version}`
}
@@ -0,0 +1,46 @@
import * as Handlebars from "handlebars"
import { PageEvent } from "typedoc/dist/lib/output/events"
export interface FrontMatterVars {
[key: string]: string | number | boolean
}
/**
* Prepends YAML block to a string
* @param contents - the string to prepend
* @param vars - object of required front matter variables
*/
export const prependYAML = (contents: string, vars: FrontMatterVars) => {
return contents
.replace(/^/, toYAML(vars) + "\n\n")
.replace(/[\r\n]{3,}/g, "\n\n")
}
/**
* Returns the page title as rendered in the document h1(# title)
* @param page
*/
export const getPageTitle = (page: PageEvent) => {
return Handlebars.helpers.reflectionTitle.call(page, false)
}
/**
* Converts YAML object to a YAML string
* @param vars
*/
const toYAML = (vars: FrontMatterVars) => {
const yaml = `---
${Object.entries(vars)
.map(
([key, value]) =>
`${key}: ${
typeof value === "string" ? `"${escapeString(value)}"` : value
}`
)
.join("\n")}
---`
return yaml
}
// prettier-ignore
const escapeString = (str: string) => str.replace(/"/g, "\\\"")
@@ -0,0 +1,36 @@
import { ReflectionKind } from "typedoc"
import { ReflectionParameterType } from "../types"
export function flattenParams(
current: ReflectionParameterType
): ReflectionParameterType[] {
if (!current.type || !("declaration" in current.type)) {
return []
}
return (
current.type?.declaration?.children?.reduce(
(acc: ReflectionParameterType[], child: ReflectionParameterType) => {
const childObj = {
...child,
name: `${current.name}.${child.name}`,
} as ReflectionParameterType
return parseParams(childObj, acc)
},
[]
) || []
)
}
export function parseParams(
current: ReflectionParameterType,
acc: ReflectionParameterType[]
): ReflectionParameterType[] {
const shouldFlatten =
current.type &&
"declaration" in current.type &&
current.type?.declaration?.kind === ReflectionKind.TypeLiteral &&
current.type?.declaration?.children
return shouldFlatten
? [...acc, current, ...flattenParams(current)]
: [...acc, current]
}
@@ -0,0 +1,70 @@
import { Comment, DeclarationReflection, ReflectionType } from "typedoc"
import * as Handlebars from "handlebars"
import { stripLineBreaks } from "../utils"
import { ReflectionParameterType } from "../types"
const MAX_LEVEL = 3
export default function reflectionFomatter(
reflection: ReflectionParameterType,
level = 1
) {
const prefix = `${Array(level - 1)
.fill("\t")
.join("")}-`
let item = `${prefix} \`${reflection.name}\`: `
const defaultValue = getDefaultValue(reflection)
if (defaultValue || reflection.flags.isOptional) {
item += `(${reflection.flags.isOptional ? "optional" : ""}${
reflection.flags.isOptional && defaultValue ? "," : ""
}${defaultValue ? `default: ${defaultValue}` : ""}) `
}
const comments = getComments(reflection)
if (comments) {
item += stripLineBreaks(Handlebars.helpers.comments(comments))
const itemChildren: string[] = []
comments.summary.forEach((commentSummary) => {
if ("target" in commentSummary) {
const reflection = commentSummary.target as DeclarationReflection
if (reflection.children && level + 1 < MAX_LEVEL) {
reflection.children.forEach((childItem) => {
itemChildren.push(reflectionFomatter(childItem, level + 1))
})
}
}
})
if (itemChildren.length) {
// TODO maybe we should check the type of the reflection and replace
// `properties` with the text that makes sense for the type.
item += ` It accepts the following properties:\n${itemChildren.join(
"\n"
)}`
}
}
return item
}
function getDefaultValue(parameter: ReflectionParameterType): string | null {
if (!("defaultValue" in parameter)) {
return null
}
return parameter.defaultValue && parameter.defaultValue !== "..."
? `\`${parameter.defaultValue}\``
: null
}
function getComments(parameter: ReflectionParameterType): Comment | undefined {
if (parameter.type instanceof ReflectionType) {
if (
parameter.type?.declaration?.signatures &&
parameter.type?.declaration?.signatures[0]?.comment
) {
return parameter.type?.declaration?.signatures[0]?.comment
}
}
return parameter.comment
}
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "./dist"
},
"include": ["src"]
}
@@ -0,0 +1,2 @@
dist
.yarn
@@ -0,0 +1,15 @@
# 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
```
@@ -0,0 +1,38 @@
{
"name": "typedoc-plugin-modules",
"private": true,
"version": "0.0.0",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"description": "A plugin that holds all necessary changes to generate a module reference",
"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": {
"glob": "^10.3.10",
"typedoc-plugin-markdown": "^3.16.0"
}
}
@@ -0,0 +1,6 @@
import { Application } from "typedoc"
import { load as resolveReferencesPluginLoad } from "./resolve-references-plugin"
export function load(app: Application) {
resolveReferencesPluginLoad(app)
}
@@ -0,0 +1,184 @@
import { ok } from "assert"
import {
Application,
Context,
Converter,
ReflectionKind,
TypeScript as ts,
ReferenceType,
Reflection,
DeclarationReflection,
ProjectReflection,
} from "typedoc"
declare module "typedoc" {
export interface TypeDocOptionMap {
internalModule: string
}
}
let hasMonkeyPatched = false
export function load(app: Application) {
if (hasMonkeyPatched) {
throw new Error(
"typedoc-plugin-missing-exports cannot be loaded multiple times"
)
}
hasMonkeyPatched = true
let activeReflection: Reflection | undefined
const referencedSymbols = new Map<ts.Program, Set<ts.Symbol>>()
const symbolToActiveRefl = new Map<ts.Symbol, Reflection>()
const knownPrograms = new Map<Reflection, ts.Program>()
function discoverMissingExports(
context: Context,
program: ts.Program
): Set<ts.Symbol> {
// An export is missing if if was referenced
// Is not contained in the documented
// And is "owned" by the active reflection
const referenced = referencedSymbols.get(program) || new Set()
const ownedByOther = new Set<ts.Symbol>()
referencedSymbols.set(program, ownedByOther)
for (const s of [...referenced]) {
if (context.project.getReflectionFromSymbol(s)) {
referenced.delete(s)
} else if (symbolToActiveRefl.get(s) !== activeReflection) {
referenced.delete(s)
ownedByOther.add(s)
}
}
return referenced
}
// Monkey patch the constructor for references so that we can get every
const origCreateSymbolReference = ReferenceType.createSymbolReference
ReferenceType.createSymbolReference = function (symbol, context, name) {
ok(activeReflection, "active reflection has not been set")
const set = referencedSymbols.get(context.program)
symbolToActiveRefl.set(symbol, activeReflection)
if (set) {
set.add(symbol)
} else {
referencedSymbols.set(context.program, new Set([symbol]))
}
return origCreateSymbolReference.call(this, symbol, context, name)
}
app.options.addDeclaration({
name: "internalModule",
help: "Define the name of the module that internal symbols which are not exported should be placed into.",
})
app.converter.on(
Converter.EVENT_CREATE_DECLARATION,
(context: Context, refl: Reflection) => {
if (refl.kindOf(ReflectionKind.Project | ReflectionKind.Module)) {
knownPrograms.set(refl, context.program)
activeReflection = refl
}
}
)
app.converter.on(
Converter.EVENT_RESOLVE_BEGIN,
function onResolveBegin(context: Context) {
const modules: (DeclarationReflection | ProjectReflection)[] =
context.project.getChildrenByKind(ReflectionKind.Module)
if (modules.length === 0) {
// Single entry point, just target the project.
modules.push(context.project)
}
for (const mod of modules) {
activeReflection = mod
const program = knownPrograms.get(mod)
if (!program) {
continue
}
let missing = discoverMissingExports(context, program)
if (!missing || !missing.size) {
continue
}
// Nasty hack here that will almost certainly break in future TypeDoc versions.
context.setActiveProgram(program)
const internalModuleOption =
context.converter.application.options.getValue("internalModule")
let internalNs: DeclarationReflection | undefined = undefined
if (internalModuleOption) {
internalNs = context
.withScope(mod)
.createDeclarationReflection(
ReflectionKind.Module,
void 0,
void 0,
context.converter.application.options.getValue("internalModule")
)
context.finalizeDeclarationReflection(internalNs)
}
const internalContext = context.withScope(internalNs || mod)
// Keep track of which symbols we've tried to convert. If they don't get converted
// when calling convertSymbol, then the user has excluded them somehow, don't go into
// an infinite loop when converting.
const tried = new Set<ts.Symbol>()
do {
for (const s of missing) {
if (shouldConvertSymbol(s, context.checker)) {
internalContext.converter.convertSymbol(internalContext, s)
}
tried.add(s)
}
missing = discoverMissingExports(context, program)
for (const s of tried) {
missing.delete(s)
}
} while (missing.size > 0)
// All the missing symbols were excluded, so get rid of our namespace.
if (internalNs && !internalNs.children?.length) {
context.project.removeReflection(internalNs)
}
context.setActiveProgram(void 0)
}
knownPrograms.clear()
referencedSymbols.clear()
symbolToActiveRefl.clear()
},
void 0,
1e9
)
}
function shouldConvertSymbol(symbol: ts.Symbol, checker: ts.TypeChecker) {
while (symbol.flags & ts.SymbolFlags.Alias) {
symbol = checker.getAliasedSymbol(symbol)
}
// We're looking at an unknown symbol which is declared in some package without
// type declarations. We know nothing about it, so don't convert it.
if (symbol.flags & ts.SymbolFlags.Transient) {
return false
}
// This is something inside the special Node `Globals` interface. Don't convert it
// because TypeDoc will reasonably assert that "Property" means that a symbol should be
// inside something that can have properties.
if (symbol.flags & ts.SymbolFlags.Property && symbol.name !== "default") {
return false
}
return true
}
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "./dist"
},
"include": ["src"]
}
@@ -1,13 +1,17 @@
{
"compilerOptions": {
"target": "es2018",
"target": "es2021",
"module": "commonjs",
"outDir": "./dist",
"isolatedModules": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"sourceMap": true
"sourceMap": true,
"skipLibCheck": true,
"rootDir": ".",
"allowJs": true
},
"include": ["src"]
"include": ["**/*.ts", "**/*.js"],
"exclude": ["**/node_modules/**", "dist"]
}

Some files were not shown because too many files have changed in this diff Show More