├── .nvmrc ├── packages ├── client │ ├── changelog │ │ ├── @unreleased │ │ │ └── .gitkeep │ │ └── 5.0.0 │ │ │ ├── pr-156.v2.yml │ │ │ ├── pr-236.v2.yml │ │ │ └── pr-239.v2.yml │ ├── .npmignore │ ├── src │ │ ├── tsconfig.json │ │ ├── client.md │ │ ├── index.ts │ │ ├── utils.ts │ │ ├── npm.ts │ │ ├── kss.ts │ │ ├── plugin.ts │ │ ├── tags.ts │ │ ├── markdown.ts │ │ ├── compiler.ts │ │ └── typescript.ts │ ├── package.json │ └── LICENSE ├── compiler │ ├── changelog │ │ ├── @unreleased │ │ │ ├── .gitkeep │ │ │ └── pr-247.v2.yml │ │ └── 5.0.0 │ │ │ ├── pr-236.v2.yml │ │ │ ├── pr-156.v2.yml │ │ │ └── pr-239.v2.yml │ ├── src │ │ ├── _nav.md │ │ ├── tsconfig.json │ │ ├── __tests__ │ │ │ ├── __fixtures__ │ │ │ │ ├── tsconfig.json │ │ │ │ ├── index.ts │ │ │ │ ├── functions.ts │ │ │ │ ├── enums.ts │ │ │ │ ├── classes.ts │ │ │ │ └── interfaces.ts │ │ │ ├── __snapshots__ │ │ │ │ └── markdown.test.ts.snap │ │ │ ├── markdown.test.ts │ │ │ ├── npm.test.ts │ │ │ ├── typescript.test.ts │ │ │ └── compilerImpl.test.ts │ │ ├── index.ts │ │ ├── plugins │ │ │ ├── index.ts │ │ │ ├── typescript │ │ │ │ ├── index.ts │ │ │ │ ├── typestring.ts │ │ │ │ ├── typescriptPlugin.ts │ │ │ │ └── visitor.ts │ │ │ ├── kss.ts │ │ │ ├── npm.ts │ │ │ └── markdown.ts │ │ ├── launch.ts │ │ ├── compiler.md │ │ ├── page.ts │ │ ├── documentalist.ts │ │ └── compilerImpl.ts │ ├── .npmignore │ ├── jest.config.json │ ├── package.json │ ├── cli.js │ └── LICENSE └── docs │ ├── src │ ├── tsconfig.json │ ├── overview.md │ ├── index.css │ └── index.ts │ ├── package.json │ └── theme │ ├── index.pug │ └── mixins.pug ├── .prettierignore ├── lerna.json ├── .changelog.yml ├── .gitignore ├── .prettierrc.json ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── dependabot.yml ├── .vscode ├── settings.json ├── launch.json └── tasks.json ├── .bulldozer.yml ├── .excavator.yml ├── tsconfig.base.json ├── scripts ├── circle-publish-npm ├── publish-npm-semver-tagged ├── circle-build-preview.js ├── submit-preview-comment.sh ├── get-publishable-paths-from-semver-tags └── submit-comment-with-artifact-links.mjs ├── README.md ├── package.json ├── tslint.json ├── .circleci └── config.yml ├── CHANGELOG.md └── LICENSE /.nvmrc: -------------------------------------------------------------------------------- 1 | v18.18 2 | -------------------------------------------------------------------------------- /packages/client/changelog/@unreleased/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/compiler/changelog/@unreleased/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/compiler/src/_nav.md: -------------------------------------------------------------------------------- 1 | @page overview 2 | @page compiler 3 | @page client 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | generated/ 3 | lib/ 4 | dist/ 5 | yarn.lock 6 | .policy.yml 7 | -------------------------------------------------------------------------------- /packages/client/.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | src/ 3 | 4 | .npmrc 5 | .yarnrc 6 | 7 | jest.config.json 8 | tsconfig.json 9 | tslint.json 10 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "yarn", 3 | "version": "independent", 4 | "$schema": "node_modules/lerna/schemas/lerna-schema.json" 5 | } 6 | -------------------------------------------------------------------------------- /packages/compiler/.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | src/ 3 | 4 | .npmrc 5 | .yarnrc 6 | 7 | jest.config.json 8 | tsconfig.json 9 | tslint.json 10 | -------------------------------------------------------------------------------- /packages/client/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../lib/" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/compiler/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../lib/" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/docs/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/client/changelog/5.0.0/pr-156.v2.yml: -------------------------------------------------------------------------------- 1 | type: feature 2 | feature: 3 | description: Export new type alias `TsDocEntry` 4 | links: 5 | - https://github.com/palantir/documentalist/pull/156 6 | -------------------------------------------------------------------------------- /packages/client/changelog/5.0.0/pr-236.v2.yml: -------------------------------------------------------------------------------- 1 | type: break 2 | break: 3 | description: Upgrade minimum Node.js engine to v18.x 4 | links: 5 | - https://github.com/palantir/documentalist/pull/236 6 | -------------------------------------------------------------------------------- /packages/compiler/changelog/@unreleased/pr-247.v2.yml: -------------------------------------------------------------------------------- 1 | type: feature 2 | feature: 3 | description: Upgrade to Marked v9.0 4 | links: 5 | - https://github.com/palantir/documentalist/pull/247 6 | -------------------------------------------------------------------------------- /packages/compiler/changelog/5.0.0/pr-236.v2.yml: -------------------------------------------------------------------------------- 1 | type: break 2 | break: 3 | description: Upgrade minimum Node.js engine to v18.x 4 | links: 5 | - https://github.com/palantir/documentalist/pull/236 6 | -------------------------------------------------------------------------------- /packages/compiler/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["ts", "tsx", "js"], 3 | "transform": { 4 | ".(ts|tsx)": "ts-jest" 5 | }, 6 | "testRegex": "\\.(test|spec)\\.ts$" 7 | } 8 | -------------------------------------------------------------------------------- /packages/compiler/changelog/5.0.0/pr-156.v2.yml: -------------------------------------------------------------------------------- 1 | type: break 2 | break: 3 | description: Upgrade to typedoc to v0.25.x, which supports TypeScript v4.6 - v5.2 4 | links: 5 | - https://github.com/palantir/documentalist/pull/156 6 | -------------------------------------------------------------------------------- /.changelog.yml: -------------------------------------------------------------------------------- 1 | # Excavator auto-updates this file. Please contribute improvements to the central template. 2 | 3 | # This file is intentionally empty. The file's existence enables changelog-app and is empty to use the default configuration. 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # Node 5 | node_modules 6 | npm-debug.log* 7 | lerna-debug.log 8 | package-lock.json 9 | yarn-error.log 10 | 11 | # Documentalist 12 | build 13 | dist 14 | lib 15 | generated 16 | coverage 17 | reports/*.xml 18 | packages/docs/docs.json 19 | packages/docs/theme/version.txt 20 | -------------------------------------------------------------------------------- /packages/compiler/src/__tests__/__fixtures__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "lib": ["dom", "ES2015"], 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "skipLibCheck": true, 8 | "target": "ES2015" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 4, 4 | "trailingComma": "all", 5 | "arrowParens": "avoid", 6 | "overrides": [ 7 | { 8 | "files": ["*.yml", "*.yaml"], 9 | "options": { 10 | "tabWidth": 2 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## What happened? 2 | 3 | 7 | 8 | ## What did you want to happen? 9 | 10 | 13 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Before this PR 2 | 3 | 4 | ## After this PR 5 | 6 | ==COMMIT_MSG== 7 | ==COMMIT_MSG== 8 | 9 | ## Possible downsides? 10 | 11 | 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "./node_modules/typescript/lib", 3 | "tslint.autoFixOnSave": true, 4 | "files.watcherExclude": { 5 | "**/dist": true 6 | }, 7 | "search.exclude": { 8 | "**/node_modules": true, 9 | "**/dist": true 10 | }, 11 | "editor.insertSpaces": true, 12 | "editor.tabSize": 4, 13 | "[css]": { 14 | "editor.tabSize": 2 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/compiler/changelog/5.0.0/pr-239.v2.yml: -------------------------------------------------------------------------------- 1 | type: break 2 | break: 3 | description: |- 4 | Rename all interfaces to drop the `I` prefix: 5 | - `IPluginEntry` -> `PluginEntry` 6 | - `IMarkdownPluginOptions` -> `MarkdownPluginOptions` 7 | - `INpmPluginOptions` -> `NpmPluginOptions` 8 | - `ITypescriptPluginOptions` -> `TypescriptPluginOptions` 9 | links: 10 | - https://github.com/palantir/documentalist/pull/156 11 | -------------------------------------------------------------------------------- /.bulldozer.yml: -------------------------------------------------------------------------------- 1 | # Excavator auto-updates this file. Please contribute improvements to the central template. 2 | 3 | version: 1 4 | merge: 5 | trigger: 6 | labels: ["merge when ready"] 7 | ignore: 8 | labels: ["do not merge"] 9 | method: squash 10 | options: 11 | squash: 12 | body: pull_request_body 13 | message_delimiter: ==COMMIT_MSG== 14 | delete_after_merge: true 15 | update: 16 | trigger: 17 | labels: ["update me"] 18 | -------------------------------------------------------------------------------- /.excavator.yml: -------------------------------------------------------------------------------- 1 | # Excavator auto-updates this file. Please contribute improvements to the central template. 2 | 3 | auto-label: 4 | names: 5 | versions-props/upgrade-all: [ "merge when ready" ] 6 | circleci/manage-circleci: [ "merge when ready" ] 7 | tags: 8 | donotmerge: [ "do not merge" ] 9 | roomba: [ "merge when ready", "🤖 fix nits" ] 10 | automerge: [ "merge when ready", "🤖 fix nits" ] 11 | standards: [ "merge when ready", "🤖 fix nits" ] 12 | autorelease: [ "autorelease" ] 13 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Attach to debugger", 6 | "type": "node", 7 | "request": "attach", 8 | "port": 9229 9 | }, 10 | { 11 | "name": "Documentalist", 12 | "type": "node", 13 | "request": "launch", 14 | "args": ["src/launch.ts"], 15 | "runtimeArgs": ["--nolazy", "-r", "ts-node/register"], 16 | "sourceMaps": true, 17 | "cwd": "${workspaceRoot}", 18 | "protocol": "inspector" 19 | } 20 | ], 21 | "compounds": [] 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "declaration": true, 5 | "esModuleInterop": true, 6 | "experimentalDecorators": true, 7 | "lib": ["dom", "ES2015"], 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "noFallthroughCasesInSwitch": true, 11 | "noImplicitAny": true, 12 | "noImplicitReturns": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "sourceMap": false, 16 | "strictNullChecks": true, 17 | "target": "ES2015" 18 | }, 19 | "exclude": ["dist", "lib", "node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | reviewers: 13 | - "adidahiya" 14 | versioning-strategy: increase 15 | ignore: 16 | - dependency-name: "@documentalist/*" 17 | - dependency-name: "@types/node" 18 | - dependency-name: "typedoc" 19 | - dependency-name: "typescript" 20 | -------------------------------------------------------------------------------- /packages/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@documentalist/client", 3 | "version": "5.0.0", 4 | "description": "Runtime functions and interfaces used in rendering documentalist data", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "scripts": { 8 | "build": "run-s compile", 9 | "compile": "tsc --project ./src", 10 | "lint": "tslint --project ./src", 11 | "lint-fix": "yarn lint --fix" 12 | }, 13 | "devDependencies": { 14 | "tslint": "^6.1.3", 15 | "typescript": "~5.2.2" 16 | }, 17 | "engines": { 18 | "node": ">=18" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git@github.com:palantir/documentalist.git" 23 | }, 24 | "author": "Palantir Technologies", 25 | "license": "Apache-2.0" 26 | } 27 | -------------------------------------------------------------------------------- /packages/compiler/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export * from "./documentalist"; 18 | export * from "./plugins"; 19 | -------------------------------------------------------------------------------- /packages/compiler/src/__tests__/__fixtures__/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export * from "./classes"; 18 | export * from "./enums"; 19 | export * from "./functions"; 20 | -------------------------------------------------------------------------------- /scripts/circle-publish-npm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -o pipefail 5 | 6 | if [ -z ${CIRCLECI+x} ]; then 7 | echo "Not on Circle; refusing to run." 8 | exit 1 9 | fi 10 | 11 | SCRIPTS_DIR="$( dirname "$(readlink -f "$0")" )" 12 | 13 | branch="$CIRCLE_BRANCH" 14 | if [ ! -z "$CIRCLE_TAG" ]; then 15 | branch='develop' 16 | fi 17 | 18 | if ! [[ "$branch" == "master" || "$branch" == "develop" || "$branch" == "next" || "$branch" == release/* ]]; then 19 | echo "Not on master, develop, next, or release/* branch - not publishing." 20 | exit 1 21 | fi 22 | 23 | if [[ "$branch" == "next" ]]; then 24 | echo "Publishing with next tag because branch name is 'next'." 25 | # must set public access for scoped packages 26 | npm publish --tag next --access public 27 | else 28 | npm publish --access public 29 | fi 30 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "0.1.0", 5 | "command": "npm", 6 | "isShellCommand": true, 7 | "showOutput": "always", 8 | "suppressTaskName": true, 9 | "tasks": [ 10 | { 11 | "taskName": "tsc", 12 | "args": ["run", "tsc"], 13 | "isBuildCommand": true, 14 | "problemMatcher": "$tsc" 15 | }, 16 | { 17 | "taskName": "watch", 18 | "args": ["run", "watch"], 19 | "isWatching": true, 20 | "problemMatcher": "$tsc-watch" 21 | }, 22 | { 23 | "taskName": "test", 24 | "args": ["run", "test"], 25 | "isTestCommand": true 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /packages/compiler/src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export * from "./kss"; 18 | export * from "./markdown"; 19 | export * from "./npm"; 20 | export * from "./typescript/index"; 21 | -------------------------------------------------------------------------------- /packages/compiler/src/plugins/typescript/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export { TypescriptPluginData } from "@documentalist/client"; 18 | export { TypescriptPlugin } from "./typescriptPlugin"; 19 | -------------------------------------------------------------------------------- /packages/client/src/client.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Client 3 | --- 4 | 5 | @# Client 6 | 7 | Now that you've compiled a sweet data file packed with documentation goodness, what next? 8 | 9 | Well, you've got some options. This package does not provide the tools to render the data, but they're fairly easy to construct once you understand the data format. The `@documentalist/client` package provides functions, interfaces, and type guards for working with the output of the compiler. 10 | 11 | - Check out the private [`@documentalist/docs` package](https://github.com/palantir/documentalist/tree/develop/packages/docs) for our simple Pug template that renders the [GitHub Pages site](http://palantir.github.io/documentalist). 12 | - Blueprint publishes a React theme in the [`@blueprintjs/docs-theme`](https://www.npmjs.com/package/@blueprintjs/docs-theme) package (the same one that powers https://blueprintjs.com/docs). 13 | -------------------------------------------------------------------------------- /scripts/publish-npm-semver-tagged: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -o pipefail 5 | 6 | git fetch --tags 7 | 8 | SCRIPTS_DIR="$( dirname "$(readlink -f "$0")" )" 9 | 10 | publishable_paths=$("$SCRIPTS_DIR/get-publishable-paths-from-semver-tags" | sed -E -e '/^ *$/d') 11 | 12 | if [ -z "$publishable_paths" ]; then 13 | echo "Nothing to publish." 14 | exit 0 15 | fi 16 | 17 | if [ -z "${NPM_AUTH_TOKEN}" ]; then 18 | echo "No NPM auth token available. Check the \$NPM_AUTH_TOKEN environment variable." 19 | exit 1 20 | fi 21 | 22 | # set the auth token for this user, not just the repo 23 | # N.B. `npm set` doesn't work here, it complains that it doesn't work for NPM workspaces 24 | echo "//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}" > ~/.npmrc 25 | chmod 0600 ~/.npmrc 26 | 27 | for path in $publishable_paths; do 28 | pushd "$path" 29 | echo "Attempting to publish package at path '$path'" 30 | "$SCRIPTS_DIR/circle-publish-npm" 31 | popd 32 | done 33 | -------------------------------------------------------------------------------- /packages/compiler/src/__tests__/__snapshots__/markdown.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`MarkdownPlugin snapshot 1`] = ` 4 | { 5 | "_nav": { 6 | "contents": [ 7 | { 8 | "tag": "page", 9 | "value": "file", 10 | }, 11 | ], 12 | "contentsRaw": "@page file", 13 | "metadata": {}, 14 | "reference": "_nav", 15 | "route": "_nav", 16 | "sourcePath": "path/to/_nav.md", 17 | "title": "(untitled)", 18 | }, 19 | "file": { 20 | "contents": [ 21 | { 22 | "level": 1, 23 | "route": "file", 24 | "tag": "heading", 25 | "value": "I'm special", 26 | }, 27 | "

I'm regular

28 | ", 29 | { 30 | "tag": "othertag", 31 | "value": "params", 32 | }, 33 | ], 34 | "contentsRaw": " 35 | @# I'm special 36 | 37 | ## I'm regular 38 | 39 | @othertag params", 40 | "metadata": { 41 | "key": "value", 42 | }, 43 | "reference": "file", 44 | "route": "file", 45 | "sourcePath": "path/to/file.md", 46 | "title": "I'm special", 47 | }, 48 | } 49 | `; 50 | -------------------------------------------------------------------------------- /scripts/circle-build-preview.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * @license Copyright 2019 Palantir Technologies, Inc. All rights reserved. 4 | */ 5 | 6 | const bot = require("circle-github-bot").create(); 7 | 8 | /** 9 | * @type {Array<{path: string; url: string;}>} 10 | */ 11 | const artifacts = require("./artifacts.json").items; 12 | 13 | const ARTIFACTS = { 14 | documentation: "packages/docs/dist/index.html", 15 | }; 16 | 17 | if (!process.env.GITHUB_API_TOKEN) { 18 | // simply log artifact URLs if auth token is missed (typical on forks) 19 | Object.keys(ARTIFACTS).forEach(package => console.info(`${ARTIFACTS[package]}: ${getArtifactAnchorLink(package)}`)); 20 | process.exit(); 21 | } 22 | 23 | const links = Object.keys(ARTIFACTS).map(getArtifactAnchorLink).join(" | "); 24 | bot.comment( 25 | process.env.GITHUB_API_TOKEN, 26 | ` 27 |

${bot.commitMessage()}

28 | Previews: ${links} 29 | `, 30 | ); 31 | 32 | function getArtifactAnchorLink(package) { 33 | const artifactInfo = artifacts.find(a => a.path === ARTIFACTS[package]); 34 | return `${package}`; 35 | } 36 | -------------------------------------------------------------------------------- /packages/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@documentalist/docs", 3 | "version": "4.0.0", 4 | "description": "Documentation for documentalist", 5 | "private": true, 6 | "scripts": { 7 | "build": "npm-run-all -p build:json build:ts build:version -s build:pug build:css", 8 | "build:json": "documentalist '../{client,compiler,docs}/src/**/*' --out docs.json", 9 | "build:ts": "tsc --project ./src/tsconfig.json", 10 | "build:version": "echo v$npm_package_version > theme/version.txt", 11 | "build:pug": "pug -O docs.json ./theme/index.pug --pretty -o ./dist", 12 | "build:css": "cp src/*.css dist/", 13 | "lint": "tslint --project ./src", 14 | "lint-fix": "yarn lint --fix" 15 | }, 16 | "dependencies": { 17 | "@documentalist/compiler": "^5.0.0" 18 | }, 19 | "devDependencies": { 20 | "npm-run-all": "^4.1.5", 21 | "pug-cli": "^1.0.0-alpha6", 22 | "tslint": "^6.1.3", 23 | "typescript": "~5.2.2" 24 | }, 25 | "engines": { 26 | "node": ">=18" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git@github.com:palantir/documentalist.git" 31 | }, 32 | "author": "Palantir Technologies" 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Documentalist [![CircleCI](https://circleci.com/gh/palantir/documentalist/tree/develop.svg?style=svg)](https://circleci.com/gh/palantir/workflows/documentalist) 2 | 3 | [![npm](https://img.shields.io/npm/v/@documentalist/compiler.svg?label=@documentalist/compiler)](https://www.npmjs.com/package/@documentalist/compiler) 4 | [![npm](https://img.shields.io/npm/v/@documentalist/client.svg?label=@documentalist/client)](https://www.npmjs.com/package/@documentalist/client) 5 | 6 | > A sort-of-static site generator optimized for living documentation of software projects. 7 | 8 | ### Documentation 9 | 10 | [See the full usage documentation here](https://palantir.github.io/documentalist/). 11 | 12 | ### Development 13 | 14 | #### Prerequisites 15 | 16 | - Yarn v1.x 17 | - Node v18.x 18 | 19 | #### Dev tasks 20 | 21 | - `yarn build` compiles & builds source code 22 | - `yarn lint` lints source code 23 | - `yarn test` runs unit test suites 24 | - `yarn deploy` pushes docs built in the `packages/docs/dist` folder to the `gh-pages` branch 25 | 26 | #### Releases 27 | 28 | _For maintainers only_: 29 | 30 | [![Autorelease](https://img.shields.io/badge/Perform%20an-Autorelease-success.svg)](https://autorelease.general.dmz.palantir.tech/palantir/documentalist) 31 | -------------------------------------------------------------------------------- /packages/compiler/src/__tests__/__fixtures__/functions.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2019 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Exported function. 19 | * @param first The number passed to the function. 20 | * @param second The string passed to the function. 21 | */ 22 | export function numberAndString(first: number, second: string) { 23 | return first + second; 24 | } 25 | 26 | /** 27 | * Non-exported function. 28 | * @param str The string parameter. 29 | * @param bool The boolean parameter. 30 | */ 31 | function stringAndBoolean(str: string, bool: boolean) { 32 | return str + bool; 33 | } 34 | 35 | // so tsc doesn't complain that the unexported function is never used. 36 | export const MyFunc = stringAndBoolean; 37 | -------------------------------------------------------------------------------- /packages/docs/theme/index.pug: -------------------------------------------------------------------------------- 1 | include mixins 2 | 3 | - var toc = Object.keys(typescript).sort((a, b) => a.localeCompare(b)) 4 | 5 | mixin listing(title, kind) 6 | h4= title 7 | each id in toc.filter(a => typescript[a].kind === kind) 8 | - var name = typescript[id].name 9 | a.nav-item(href='#' + name): code= name 10 | 11 | html 12 | head 13 | title Documentalist 14 | link(rel='stylesheet', href='index.css') 15 | body 16 | #nav 17 | each item in nav 18 | h3: a(href="#" + item.reference)= item.title 19 | 20 | h3 API Reference 21 | +listing("Classes", "class") 22 | +listing("Interfaces", "interface") 23 | +listing("Enums", "enum") 24 | +listing("Aliases", "type alias") 25 | 26 | //- include current package.json version from text file (see npm script docs:version) 27 | h4: include version.txt 28 | 29 | .copyright © Copyright 2017-present Palantir 30 | 31 | #content 32 | 33 | each page in pages 34 | article(data-route=page.reference,hidden=true) 35 | +joinContent(page) 36 | 37 | each iface in typescript 38 | section(data-route=iface.name,hidden=true) 39 | +interfaceDocs(iface) 40 | 41 | script(type='text/javascript', src='https://unpkg.com/core-js@2.5.3/client/shim.min.js') 42 | script(type='text/javascript', src='index.js') 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "documentalist-monorepo", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "A sort-of-static site generator optimized for living documentation of software projects", 6 | "workspaces": [ 7 | "packages/*" 8 | ], 9 | "scripts": { 10 | "build": "lerna run build", 11 | "clean": "lerna run clean", 12 | "deploy": "NODE_DEBUG=gh-pages gh-pages -d packages/docs/dist -b master", 13 | "format": "prettier --write \"./**/*.{js,json,md,ts,tsx,yml}\"", 14 | "format-check": "prettier --check \"./**/*.{js,json,md,ts,tsx,yml}\"", 15 | "lint": "lerna run lint", 16 | "lint-fix": "lerna run lint-fix", 17 | "test": "lerna run test", 18 | "verify": "npm-run-all -s build -p test lint" 19 | }, 20 | "dependencies": { 21 | "@blueprintjs/tslint-config": "^4.1.3", 22 | "circle-github-bot": "^2.1.0", 23 | "gh-pages": "^6.1.1", 24 | "lerna": "^8.1.2", 25 | "npm-run-all": "^4.1.5", 26 | "octokit": "^3.1.2", 27 | "prettier": "^3.2.5", 28 | "tslint-plugin-prettier": "^2.3.0", 29 | "yarn-deduplicate": "^6.0.2" 30 | }, 31 | "author": "Palantir Technologies", 32 | "repository": { 33 | "type": "git", 34 | "url": "git@github.com:palantir/documentalist.git" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/compiler/src/__tests__/__fixtures__/enums.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2019 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** classic typescript enum */ 18 | export enum Intent { 19 | PRIMARY = "primary", 20 | SUCCESS = "success", 21 | WARNING = "warning", 22 | DANGER = "danger", 23 | } 24 | 25 | /** const/type pair: enum & string literals */ 26 | export const IconName = { 27 | ADD: "add" as "add", 28 | REMOVE: "remove" as "remove", 29 | // tslint:disable-next-line:object-literal-sort-keys 30 | PLUS: "plus" as "plus", 31 | MINUS: "minus" as "minus", 32 | }; 33 | export type IconName = "add" | "remove" | "plus" | "minus"; 34 | 35 | /** plain old object literal, *not* an enum */ 36 | export const Literal = { 37 | LEFT: "left", 38 | RIGHT: "right", 39 | }; 40 | -------------------------------------------------------------------------------- /packages/client/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * This module exports all __client__ interfaces: Documentalist concepts that are meant to be used at runtime 19 | * alongside a compiled documentation data file. 20 | * 21 | * The `Documentalist` class and its plugins are only available at compile-time, but their interfaces are useful 22 | * when rendering the data, so they are exposed separately in this module. 23 | * 24 | * A few utility functions are also provided, including several type guards for `@tags`. 25 | */ 26 | 27 | export * from "./compiler"; 28 | export * from "./kss"; 29 | export * from "./markdown"; 30 | export * from "./npm"; 31 | export * from "./plugin"; 32 | export * from "./tags"; 33 | export * from "./typescript"; 34 | export * from "./utils"; 35 | -------------------------------------------------------------------------------- /packages/docs/src/overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Documentalist 3 | --- 4 | 5 | # Overview 6 | 7 | > A sort-of-static site generator optimized for living documentation of software projects. 8 | 9 | [![npm](https://img.shields.io/npm/v/@documentalist/compiler.svg?label=@documentalist/compiler)](https://www.npmjs.com/package/@documentalist/compiler) 10 | [![npm](https://img.shields.io/npm/v/@documentalist/client.svg?label=@documentalist/client)](https://www.npmjs.com/package/@documentalist/client) 11 | [![CircleCI](https://circleci.com/gh/palantir/documentalist.svg?style=shield&circle-token=1dbd27fe833e64bafb3e8de8ee111a2aee9bb79d)](https://circleci.com/gh/palantir/documentalist) 12 | 13 | ## Documentalism 101 14 | 15 | Documentalism is a two-step process: 16 | 17 | 1. Get the data. 18 | 2. Render the data. 19 | 20 | The Documentalist compiler is an extensible solution to step 1: it helps you get all your data in one place, in a consistent format. 21 | Configure Documentalist with plugins to extract documentation data from source files, then feed it a glob of files 22 | and `await` your magical blob of documentation data! 23 | 24 | ## Packages 25 | 26 | This project contains multiple NPM packages: 27 | 28 | - [`@documentalist/compiler`](#compiler) 29 | - [`@documentalist/client`](#client) 30 | 31 | ## License 32 | 33 | This project is made available under the [Apache-2.0 License](https://github.com/palantir/documentalist/blob/develop/LICENSE). 34 | -------------------------------------------------------------------------------- /packages/client/changelog/5.0.0/pr-239.v2.yml: -------------------------------------------------------------------------------- 1 | type: break 2 | break: 3 | description: |- 4 | Rename all interfaces to drop the `I` prefix: 5 | - `ICompiler` -> `Compiler` 6 | - `IMetadata` -> `Metadata` 7 | - `IBlock` -> `Block` 8 | - `IKssModifier` -> `KssModifier` 9 | - `IKSSExample` -> `KSSExample` 10 | - `IKSSPluginData` -> `KSSPluginData` 11 | - `IMarkdownPluginData` -> `MarkdownPluginData` 12 | - `IPageNode` -> `PageNode` 13 | - `IPageData` -> `PageData` 14 | - `INavigable` -> `Navigable` 15 | - `IHeadingNode` -> `HeadingNode` 16 | - `INpmPackage` -> `NpmPackageInfo` 17 | - `INpmPluginData` -> `NpmPluginData` 18 | - `IPlugin` -> `Plugin` 19 | - `IFile` -> `File` 20 | - `ITag` -> `Tag` 21 | - `IHeadingTag` -> `HeadingTag` 22 | - `ITsFlags` -> `TsFlags` 23 | - `ITsDocBase` -> `TsDocBase` 24 | - `ITsConstructor` -> `TsConstructor` 25 | - `ITsMethod` -> `TsMethod` 26 | - `ITsCallable` -> `TsCallable` 27 | - `ITsSignature` -> `TsSignature` 28 | - `ITsDefaultValue` -> `TsDefaultValue` 29 | - `ITsObjectDefinition` -> `TsObjectDefinition` 30 | - `ITsProperty` -> `TsProperty` 31 | - `ITsParameter` -> `TsParameter` 32 | - `ITsClass` -> `TsClass` 33 | - `ITsAccessor` -> `TsAccessor` 34 | - `ITsEnumMember` -> `TsEnumMember` 35 | - `ITsEnum` -> `TsEnum` 36 | - `ITypescriptPluginData` -> `TypescriptPluginData` 37 | links: 38 | - https://github.com/palantir/documentalist/pull/156 39 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint-plugin-prettier", "@blueprintjs/tslint-config"], 3 | "rules": { 4 | "align": false, 5 | "file-header": { 6 | "options": [ 7 | "Copyright \\d{4} Palantir Technologies, Inc\\. All rights reserved.", 8 | "Copyright 2022 Palantir Technologies, Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License." 9 | ] 10 | }, 11 | "no-invalid-this": false, 12 | "no-shadowed-variable": false, 13 | "no-submodule-imports": { 14 | "options": ["typedoc/dist/lib/models"] 15 | }, 16 | "prettier": { 17 | "options": { 18 | "printWidth": 120, 19 | "tabWidth": 4, 20 | "trailingComma": "all" 21 | } 22 | }, 23 | "space-within-parens": false, 24 | "unnecessary-bind": false, 25 | "whitespace": false 26 | }, 27 | "jsRules": { 28 | "no-console": false 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/compiler/src/__tests__/markdown.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Documentalist } from "../documentalist"; 18 | import { MarkdownPlugin } from "../plugins/markdown"; 19 | 20 | const TEST_MARKDOWN = `--- 21 | key: value 22 | --- 23 | 24 | @# I'm special 25 | 26 | ## I'm regular 27 | 28 | @othertag params 29 | `; 30 | 31 | const TEST_NAV = `@page file 32 | `; 33 | 34 | const TEST_FILES = [ 35 | { 36 | path: "path/to/file.md", 37 | read: () => TEST_MARKDOWN, 38 | }, 39 | { 40 | path: "path/to/_nav.md", 41 | read: () => TEST_NAV, 42 | }, 43 | ]; 44 | 45 | describe("MarkdownPlugin", () => { 46 | const dm = Documentalist.create().use(".md", new MarkdownPlugin()); 47 | 48 | it("snapshot", async () => { 49 | const { pages } = await dm.documentFiles(TEST_FILES); 50 | expect(pages).toMatchSnapshot(); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /packages/compiler/src/launch.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as path from "path"; 18 | import { Documentalist } from "./documentalist"; 19 | import { NpmPlugin } from "./plugins/npm"; 20 | import { TypescriptPlugin } from "./plugins/typescript"; 21 | 22 | // Launch "Documentalist" from Debug panel in VS Code (see .vscode/launch.json). 23 | 24 | // Run something for the VS Code debugger to attach to. 25 | // Set breakpoints in original .ts source and debug in the editor! 26 | // tslint:disable prettier 27 | Documentalist.create() 28 | .use(".ts", new TypescriptPlugin()) 29 | .use("package.json", new NpmPlugin()) 30 | .documentGlobs( 31 | path.join(__dirname, "..", "package.json"), 32 | // compile test fixtures: 33 | path.join(__dirname, "__tests__", "__fixtures__", "*.ts"), 34 | // compile our own source code: 35 | // path.join(__dirname, "..", "src", "index.ts"), 36 | ); 37 | -------------------------------------------------------------------------------- /packages/compiler/src/compiler.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Compiler 3 | --- 4 | 5 | @# Compiler 6 | 7 | Register plugins with `.use(pattern, plugin)`. Supply a `pattern` to match files against; matched files will be compiled by the `plugin`. Documentation data will be collected into a single blob and can be easily written to a file or fed into another tool. 8 | 9 | ```js 10 | const { Documentalist, MarkdownPlugin, TypescriptPlugin } = require("@documentalist/compiler"); 11 | const { writeFileSync } = require("fs"); 12 | 13 | new Documentalist() 14 | .use(".md", new MarkdownPlugin()) 15 | .use(/\.tsx?$/, new TypescriptPlugin({ excludeNames: [/I.+State$/] })) 16 | .documentGlobs("src/**/*") // ← async operation, returns a Promise 17 | .then(docs => JSON.stringify(docs, null, 2)) 18 | .then(json => writeFileSync("docs.json", json)); 19 | ``` 20 | 21 | @interface Documentalist 22 | 23 | ## CLI 24 | 25 | On the command line, the Markdown and Typescript plugins are enabled by default. 26 | The CSS plugin can be enabled with `--css`. Plugins can be disabled with the `no-` prefix. 27 | 28 | > **Options are not supported** via the command line interface :sob:. 29 | 30 | ```sh 31 | documentalist "src/**/*" --css --no-ts > docs.json 32 | ``` 33 | 34 | ## Plugins 35 | 36 | Documentalist uses a plugin architecture to support arbitrary file types. 37 | Use your own plugins by passing them to `dm.use(pattern, plugin)` with a 38 | pattern to match against source files. The collected matched files will 39 | be passed to your plugin's `compile` function, along with a `compiler` 40 | instance that can be used to render blocks of markdown text. 41 | 42 | @interface Plugin 43 | -------------------------------------------------------------------------------- /packages/compiler/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@documentalist/compiler", 3 | "version": "5.0.0", 4 | "description": "The documentalist compiler", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "bin": { 8 | "documentalist": "./cli.js" 9 | }, 10 | "scripts": { 11 | "build": "run-s compile", 12 | "clean": "rm -rf lib", 13 | "compile": "tsc --project ./src", 14 | "lint": "tslint --project ./src", 15 | "lint-fix": "yarn lint --fix", 16 | "test": "jest --config jest.config.json", 17 | "test:debug": "node --inspect-brk node_modules/.bin/jest --config jest.config.json --runInBand", 18 | "watch": "yarn compile --watch" 19 | }, 20 | "dependencies": { 21 | "@documentalist/client": "^5.0.0", 22 | "@types/kss": "^3.0.4", 23 | "glob": "^10.3.10", 24 | "js-yaml": "^4.1.0", 25 | "kss": "^3.0.1", 26 | "marked": "^10.0.0", 27 | "tsconfig-resolver": "^3.0.1", 28 | "typedoc": "~0.25.2", 29 | "yargs": "^17.7.2" 30 | }, 31 | "devDependencies": { 32 | "@types/glob": "^8.1.0", 33 | "@types/jest": "^29.5.12", 34 | "@types/js-yaml": "^4.0.9", 35 | "@types/node": "^18.18.6", 36 | "@types/yargs": "^17.0.32", 37 | "jest": "^29.7.0", 38 | "jest-junit": "^14.0.1", 39 | "npm-run-all": "^4.1.5", 40 | "ts-jest": "^29.1.2", 41 | "ts-node": "^10.9.2", 42 | "tslint": "^6.1.3", 43 | "typescript": "~5.2.2" 44 | }, 45 | "engines": { 46 | "node": ">=18" 47 | }, 48 | "repository": { 49 | "type": "git", 50 | "url": "git@github.com:palantir/documentalist.git" 51 | }, 52 | "author": "Palantir Technologies", 53 | "license": "Apache-2.0" 54 | } 55 | -------------------------------------------------------------------------------- /scripts/submit-preview-comment.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script submits a Github comment on the current PR with links to built artifacts hosted in CircleCI. 4 | # 5 | # To do this, it first queries the CircleCI API using a personal access token which is stored as an env variable. 6 | # Currently, this is a token generated by @adidahiya. 7 | # See https://support.circleci.com/hc/en-us/articles/360045457592-Access-uploaded-artifact-URL-in-job 8 | # See https://circleci.com/docs/managing-api-tokens/#creating-a-personal-api-token 9 | # 10 | # After querying for artifact information, it delegates to an adjacent Node.js script to parse the links 11 | # and post a Github comment. 12 | 13 | set -e 14 | set -o pipefail 15 | 16 | if [ -z "${CIRCLE_BUILD_NUM}" ]; then 17 | echo "Not on CircleCI, refusing to run script." 18 | exit 1 19 | fi 20 | 21 | if [ -z "${CIRCLE_API_TOKEN}" ]; then 22 | echo "No CircleCI API token available to query for artifact asset URLs from this build." 23 | echo " --> If this is a build on a fork of the main repo, this is expected behavior. You can view artifact URLs through the CircleCI job web UI." 24 | echo " --> If this is a build on the main repo, something is wrong: check the \$CIRCLE_API_TOKEN environment variable." 25 | exit 0 26 | fi 27 | 28 | SCRIPTS_DIR=$(dirname "$(readlink -f "$0")") 29 | # See https://circleci.com/docs/api/v2/index.html#operation/getJobArtifacts 30 | artifacts=$(curl --request GET --url "https://circleci.com/api/v2/project/gh/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/$CIRCLE_BUILD_NUM/artifacts" --header "authorization: $CIRCLE_API_TOKEN") 31 | 32 | echo $artifacts > ./scripts/artifacts.json 33 | node $SCRIPTS_DIR/submit-comment-with-artifact-links.mjs 34 | -------------------------------------------------------------------------------- /packages/client/src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Splits the `text` string into words and invokes the `callback` for each word that is 19 | * found in the `data` record. If not found, the word appears unchanged in the output 20 | * array. 21 | * 22 | * Example: 23 | * ```tsx 24 | * linkify("string | ITag", docs.typescript, (name) => {name}) 25 | * // => 26 | * ["string", " | ", ITag] 27 | * ``` 28 | */ 29 | export function linkify( 30 | text: string, 31 | data: Record, 32 | callback: (name: string, data: D, index: number) => T, 33 | ): Array { 34 | return text 35 | .split(WORD_SEPARATORS) 36 | .map((word, idx) => (data[word] == null ? word : callback(word, data[word], idx))); 37 | } 38 | 39 | const WORD_SEPARATORS = /([\[\]<>()| :.,]+)/g; 40 | 41 | /** 42 | * Slugify a string: "Really Cool Heading!" => "really-cool-heading-" 43 | */ 44 | export function slugify(str: string) { 45 | return str.toLowerCase().replace(/[^\w.\/]/g, "-"); 46 | } 47 | -------------------------------------------------------------------------------- /packages/client/src/npm.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * NPM package metadata 19 | */ 20 | export interface NpmPackageInfo { 21 | /** Package name. */ 22 | name: string; 23 | 24 | /** Package description. */ 25 | description?: string; 26 | 27 | /** Version string from package.json. */ 28 | version: string; 29 | 30 | /** NPM `latest` dist-tag version. */ 31 | latestVersion?: string; 32 | 33 | /** NPM `next` dist-tag version. */ 34 | nextVersion?: string; 35 | 36 | /** Whether this package is marked `private`. */ 37 | private: boolean; 38 | 39 | /** Whether this package is published to NPM. */ 40 | published: boolean; 41 | 42 | /** Relative path from `sourceBaseDir` to this package. */ 43 | sourcePath: string; 44 | 45 | /** All published versions of this package. If published, this contains only `version`. */ 46 | versions: string[]; 47 | } 48 | 49 | /** 50 | * The `NpmPlugin` exports an `npm` key that contains a map of package names to their associated metadata. 51 | * 52 | * @see NpmPlugin 53 | */ 54 | export interface NpmPluginData { 55 | npm: { 56 | [packageName: string]: NpmPackageInfo; 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /packages/client/src/kss.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** A single modifier for an example. */ 18 | export interface KssModifier { 19 | documentation: string; 20 | name: string; 21 | } 22 | 23 | /** 24 | * A markup/modifiers example parsed from a KSS comment block. 25 | */ 26 | export interface KssExample { 27 | /** Raw documentation string. */ 28 | documentation: string; 29 | /** 30 | * Raw HTML markup for example with `{{.modifier}}` templates, 31 | * to be used to render the markup for each modifier. 32 | */ 33 | markup: string; 34 | /** 35 | * Syntax-highlighted version of the markup HTML, to be used 36 | * for rendering the markup itself with pretty colors. 37 | */ 38 | markupHtml: string; 39 | /** Array of modifiers supported by HTML markup. */ 40 | modifiers: KssModifier[]; 41 | /** Unique reference for addressing this example. */ 42 | reference: string; 43 | } 44 | 45 | /** 46 | * The `KssPlugin` exports a `css` key that contains a map of styleguide references to markup/modifier examples. 47 | * 48 | * @see KSSPlugin 49 | */ 50 | export interface KssPluginData { 51 | css: { 52 | [reference: string]: KssExample; 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /scripts/get-publishable-paths-from-semver-tags: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * @license Copyright 2019 Palantir Technologies, Inc. All rights reserved. 4 | * @fileoverview Finds the subset of packages which are ready to be published based on the latest Lerna publish commit. 5 | */ 6 | 7 | "use strict"; 8 | 9 | const cp = require("child_process"); 10 | const path = require("path"); 11 | 12 | const yargs = require("yargs") 13 | .usage("$0 ") 14 | .help(); 15 | 16 | const args = yargs.argv; 17 | const commitish = args._[0] || "HEAD"; 18 | 19 | cp.exec(`git tag --points-at ${commitish}`, (err, stdout) => { 20 | if (err) { 21 | throw err; 22 | } 23 | 24 | const taggedPackageNames = stdout 25 | .toString() 26 | .split("\n") 27 | .map(line => line.trim()) 28 | .filter(line => line.length > 0) 29 | .map(tag => { 30 | const match = /^(.+)\@([^@]+)$/.exec(tag); 31 | if (!match) { 32 | return null; 33 | } else { 34 | return match[1]; 35 | } 36 | }) 37 | .filter(name => name != null); 38 | 39 | const publishablePackagePaths = taggedPackageNames 40 | .map(name => { 41 | const nameParts = name.split("/"); 42 | const unscopedName = nameParts[nameParts.length - 1]; 43 | const packagePath = path.join("packages", unscopedName); 44 | return { 45 | // This will throw if the package name isn't also the path, which is desirable. 46 | packageJson: require(path.resolve(packagePath, "package.json")), 47 | path: packagePath, 48 | }; 49 | }) 50 | .filter(({ packageJson }) => !packageJson.private) 51 | .map(pkg => pkg.path); 52 | 53 | publishablePackagePaths.forEach(pkgPath => console.log(pkgPath)); 54 | }); 55 | -------------------------------------------------------------------------------- /packages/client/src/plugin.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Compiler } from "./compiler"; 18 | 19 | /** 20 | * A Documentalist plugin is an object with a `compile(files, compiler)` function 21 | * that returns a data object (or a promise for that object). 22 | * 23 | * A common pattern is to define a class that implements this interface and use the constructor 24 | * to accept options. 25 | * 26 | * ```ts 27 | * import { Compiler, File, Plugin } from "documentalist/client"; 28 | * 29 | * export interface MyData { 30 | * pluginName: { ... } 31 | * } 32 | * 33 | * export interface MyOptions { 34 | * ... 35 | * } 36 | * 37 | * export class MyPlugin implements Plugin { 38 | * public constructor(options: MyOptions = {}) {} 39 | * 40 | * public compile(files: File[], compiler: Compiler) { 41 | * return files.map(transformToMyData); 42 | * } 43 | * } 44 | * ``` 45 | */ 46 | export interface Plugin { 47 | compile(files: File[], compiler: Compiler): T | Promise; 48 | } 49 | 50 | /** 51 | * Abstract representation of a file, containing absolute path and synchronous `read` operation. 52 | * This allows plugins to use only the path of a file without reading it. 53 | */ 54 | export interface File { 55 | path: string; 56 | read(): string; 57 | } 58 | -------------------------------------------------------------------------------- /packages/compiler/src/__tests__/__fixtures__/classes.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // tslint:disable:max-classes-per-file 18 | 19 | export class Animal { 20 | /** Get the noise of the animal */ 21 | public get accessorNoise(): string { 22 | return this.noise; 23 | } 24 | 25 | /** Set the noise for the animal */ 26 | public set accessorNoise(newNoise: string) { 27 | this.noise = newNoise; 28 | } 29 | 30 | public constructor(private noise: string) {} 31 | 32 | /** Produce a noise. */ 33 | public bark() { 34 | return this.noise; 35 | } 36 | 37 | /** 38 | * Public method. 39 | * @param food Name of the food to eat. 40 | */ 41 | public eat(food: string) { 42 | this.consumePrivate(Food.retrieve(food), true); 43 | } 44 | 45 | /** Private method does not appear in output. */ 46 | private consumePrivate(food: Food, allOfIt = false) { 47 | if (allOfIt) { 48 | food.destroy(); 49 | } 50 | } 51 | } 52 | 53 | export class Dog extends Animal { 54 | public constructor() { 55 | super("arf"); 56 | } 57 | } 58 | 59 | // non-exported class does not appear in output. 60 | class Food { 61 | public static retrieve(name: string) { 62 | return new Food(name); 63 | } 64 | 65 | public constructor(public name: string) {} 66 | 67 | public destroy() { 68 | return true; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/client/src/tags.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** Represents a single `@tag ` line from a file. */ 18 | export interface Tag { 19 | /** Tag name. */ 20 | tag: string; 21 | 22 | /** Tag value, exactly as written in source. */ 23 | value: string; 24 | } 25 | 26 | /** 27 | * The basic components of a navigable resource: a "route" at which it can be accessed and 28 | * its depth in the layout hierarchy. Heading tags and hierarchy nodes both extend this interface. 29 | */ 30 | export interface Navigable { 31 | /** Fully-qualified route of the heading, which can be used as anchor `href`. */ 32 | route: string; 33 | 34 | /** Level of heading, from 1-6. Dictates which `` tag to render. */ 35 | level: number; 36 | } 37 | 38 | /** 39 | * Represents a single `@#+ ` heading tag from a file. Note that all `@#+` tags 40 | * (`@#` through `@######`) are emitted as `tag: "heading"` so only one renderer is necessary to 41 | * capture all six levels. 42 | * 43 | * Heading tags include additional information over regular tags: fully-qualified `route` of the 44 | * heading (which can be used as anchor `href`), and `level` to determine which `` tag to use. 45 | */ 46 | export interface HeadingTag extends Tag, Navigable { 47 | tag: "heading"; 48 | } 49 | 50 | /** An entry in `contents` array: either an HTML string or an `@tag`. */ 51 | export type StringOrTag = string | Tag; 52 | 53 | /** 54 | * Type guard to determine if a `contents` node is an `@tag` statement. 55 | * Optionally tests tag name too, if `tagName` arg is provided. 56 | */ 57 | export function isTag(node: any, tagName?: string): node is Tag { 58 | return node != null && (node as Tag).tag != null && (tagName == null || (node as Tag).tag === tagName); 59 | } 60 | 61 | /** Type guard to deterimine if a `contents` node is an `@#+` heading tag. */ 62 | export function isHeadingTag(node: any): node is HeadingTag { 63 | return isTag(node, "heading"); 64 | } 65 | -------------------------------------------------------------------------------- /packages/client/src/markdown.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Block } from "./compiler"; 18 | import { Navigable } from "./tags"; 19 | 20 | /** 21 | * The `MarkdownPlugin` exports a map of markdown `pages` keyed by reference, 22 | * and a multi-rooted `nav` tree of page nodes. 23 | */ 24 | export interface MarkdownPluginData { 25 | /** 26 | * An ordered, nested, multi-rooted tree describing the navigation layout 27 | * of all the pages and their headings. The representation is constructued by 28 | * tracing `@page` and `@#+` tags. 29 | */ 30 | nav: PageNode[]; 31 | 32 | /** A map of page reference to data. */ 33 | pages: { 34 | [reference: string]: PageData; 35 | }; 36 | } 37 | 38 | /** 39 | * A single Documentalist page, parsed from a single source file. 40 | */ 41 | export interface PageData extends Block { 42 | /** Unique identifier for addressing this page. */ 43 | reference: string; 44 | 45 | /** Fully qualified route to this page: slash-separated references of all parent pages. */ 46 | route: string; 47 | 48 | /** Relative path from cwd to the original source file. */ 49 | sourcePath: string; 50 | 51 | /** Human-friendly title of this page. */ 52 | title: string; 53 | } 54 | 55 | /** An `@#+` tag belongs to a specific page. */ 56 | export interface HeadingNode extends Navigable { 57 | /** Display title of page heading. */ 58 | title: string; 59 | } 60 | 61 | /** A page has ordered children composed of `@#+` and `@page` tags. */ 62 | export interface PageNode extends HeadingNode { 63 | /** Ordered list of pages and headings that appear on this page. */ 64 | children: Array; 65 | 66 | /** Unique reference of this page, used for retrieval from store. */ 67 | reference: string; 68 | } 69 | 70 | /** Type guard for `PageNode`, useful for its `children` array. */ 71 | export function isPageNode(node: any): node is PageNode { 72 | return node != null && (node as PageNode).children != null; 73 | } 74 | -------------------------------------------------------------------------------- /packages/compiler/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // @ts-check 3 | 4 | /** 5 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | // Example Usage 21 | // documentalist "./src/**/*" 22 | 23 | const fs = require("fs"); 24 | const yargs = require("yargs"); 25 | const { Documentalist, KssPlugin, MarkdownPlugin, NpmPlugin, TypescriptPlugin } = require("./lib/"); 26 | 27 | const argv = yargs 28 | .alias("v", "version") 29 | .version(require("./package.json").version) 30 | .usage("$0 [options] ") 31 | .option("md", { 32 | default: true, 33 | desc: "use MarkdownPlugin for .md files", 34 | type: "boolean", 35 | }) 36 | .option("npm", { 37 | default: true, 38 | desc: "use NPM plugin for package.json files", 39 | type: "boolean", 40 | }) 41 | .option("ts", { 42 | default: true, 43 | desc: "use TypescriptPlugin for .tsx? files", 44 | type: "boolean", 45 | }) 46 | .option("css", { 47 | desc: "use KssPlugin for .(css|less|scss) files", 48 | type: "boolean", 49 | }) 50 | .option("out", { 51 | desc: "output file path (defaults to std out)", 52 | type: "string", 53 | }) 54 | .demandCommand(1, "Requires at least one file").argv; 55 | 56 | let docs = Documentalist.create(); 57 | 58 | if (argv.md) { 59 | docs = docs.use(".md", new MarkdownPlugin()); 60 | } 61 | if (argv.npm) { 62 | docs = docs.use("package.json", new NpmPlugin()); 63 | } 64 | if (argv.ts) { 65 | docs = docs.use(/\.tsx?$/, new TypescriptPlugin({ excludePaths: ["__tests__/"] })); 66 | } 67 | if (argv.css) { 68 | docs = docs.use(/\.(css|less|s[ac]ss)$/, new KssPlugin()); 69 | } 70 | 71 | docs.documentGlobs(...argv._) 72 | .then(data => JSON.stringify(data, null, 2)) 73 | .then( 74 | output => { 75 | if (argv.out) { 76 | fs.writeFileSync(argv.out, output, "utf-8"); 77 | } else { 78 | console.log(output); 79 | } 80 | }, 81 | failedReason => console.error(failedReason), 82 | ); 83 | -------------------------------------------------------------------------------- /packages/compiler/src/plugins/kss.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Compiler, File, KssExample, KssModifier, KssPluginData, Plugin } from "@documentalist/client"; 18 | import * as kss from "kss"; 19 | import * as path from "path"; 20 | 21 | /** 22 | * The `KssPlugin` extracts [KSS doc comments](http://warpspire.com/kss/syntax/) from CSS code (or similar languages). 23 | * It emits an object keyed by the "styleguide [ref]" section of the comment. The documentation, markup, and modifiers 24 | * sections will all be emitted in the data. 25 | * 26 | * @see KssExample 27 | */ 28 | export class KssPlugin implements Plugin { 29 | public constructor(private options: kss.Options = {}) {} 30 | 31 | public compile(cssFiles: File[], dm: Compiler): KssPluginData { 32 | const styleguide = this.parseFiles(cssFiles); 33 | const sections = styleguide.sections().map(s => convertSection(s, dm)); 34 | const css = dm.objectify(sections, s => s.reference); 35 | return { css }; 36 | } 37 | 38 | private parseFiles(files: File[]) { 39 | const input = files.map(file => ({ 40 | base: path.dirname(file.path), 41 | contents: file.read(), 42 | path: file.path, 43 | })); 44 | const options = { multiline: false, markdown: false, ...this.options }; 45 | return kss.parse(input, options); 46 | } 47 | } 48 | 49 | function convertSection(section: kss.KssSection, dm: Compiler): KssExample { 50 | return { 51 | documentation: dm.renderMarkdown(section.description()), 52 | markup: section.markup() || "", 53 | markupHtml: dm.renderMarkdown(`\`\`\`html\n${section.markup() || ""}\n\`\`\``), 54 | modifiers: section.modifiers().map(mod => convertModifier(mod, dm)), 55 | reference: section.reference(), 56 | }; 57 | } 58 | 59 | function convertModifier(mod: kss.KssModifier, dm: Compiler): KssModifier { 60 | return { 61 | documentation: dm.renderMarkdown(mod.description()), 62 | name: mod.name(), 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /packages/docs/src/index.css: -------------------------------------------------------------------------------- 1 | /* NATIVE ELEMENT STYLES */ 2 | 3 | html, 4 | body { 5 | margin: 0; 6 | } 7 | 8 | body { 9 | background-color: #F5F5F5; 10 | color: #555; 11 | font-family: Helvetica, Arial, sans-serif; 12 | } 13 | 14 | a { 15 | color: #888; 16 | text-decoration: underline; 17 | } 18 | 19 | a:hover { 20 | color: dodgerblue !important; 21 | } 22 | 23 | h1, 24 | h2, 25 | h3, 26 | h4 { 27 | margin-top: 2em; 28 | margin-bottom: 1em; 29 | } 30 | 31 | h1:first-child, 32 | h2:first-child, 33 | h3:first-child, 34 | h4:first-child { 35 | margin-top: 0em; 36 | } 37 | 38 | pre, 39 | code { 40 | font-size: 1.1em; 41 | } 42 | 43 | pre > code { 44 | display: block; 45 | padding: 20px 20px; 46 | background-color: white; 47 | border-radius: 5px; 48 | } 49 | 50 | blockquote { 51 | border-left: 5px solid #ddd; 52 | margin-left: 0; 53 | padding-left: 1em; 54 | } 55 | 56 | [hidden] { 57 | display: none; 58 | } 59 | 60 | /* LAYOUT */ 61 | 62 | #content { 63 | margin-left: 250px; 64 | max-width: 50em; 65 | padding: 20px; 66 | } 67 | 68 | #nav { 69 | position: fixed; 70 | top: 0; 71 | bottom: 0; 72 | width: 200px; 73 | overflow: auto; 74 | padding: 20px; 75 | } 76 | 77 | .nav-item { 78 | display: block; 79 | margin-bottom: 0.5em; 80 | text-decoration: none; 81 | } 82 | 83 | .nav-item.selected { 84 | color: #555; 85 | font-weight: 900; 86 | } 87 | 88 | .copyright { 89 | margin-top: 20px; 90 | color: #AAA; 91 | font-size: 10px; 92 | } 93 | 94 | /* DOCUMENTATION BLOCKS */ 95 | 96 | .interface-block { 97 | margin-bottom: 40px; 98 | } 99 | 100 | .interface-title small { 101 | font-weight: normal; 102 | } 103 | 104 | .interface-properties { 105 | background-color: white; 106 | border-radius: 5px; 107 | } 108 | 109 | .interface-properties table { 110 | width: 100%; 111 | border-collapse: collapse; 112 | } 113 | 114 | .interface-properties td { 115 | padding: 20px; 116 | border-top: 1px solid #EEE; 117 | } 118 | 119 | .interface-properties .prop-name { 120 | width: 100px; 121 | padding-right: 0; 122 | white-space: nowrap; 123 | } 124 | 125 | .interface-properties .prop-name code:last-child { 126 | font-weight: bold; 127 | } 128 | 129 | .interface-properties .prop-docs { 130 | max-width: 300px; 131 | } 132 | 133 | .type-signature { 134 | display: flex; 135 | justify-content: space-between; 136 | } 137 | 138 | .method-signature { 139 | font-family: monospace; 140 | white-space: pre; 141 | } 142 | 143 | .method-signature .param-type { 144 | background-color: white; 145 | border-radius: 5px; 146 | } 147 | 148 | .interface-properties tr:first-child td { 149 | border-top: none; 150 | } 151 | -------------------------------------------------------------------------------- /packages/docs/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Route 19 | */ 20 | interface Route { 21 | path: string; 22 | render: () => string; 23 | } 24 | 25 | class Router { 26 | private routes: Record = {}; 27 | private currentRoute: Route | null = null; 28 | 29 | constructor( 30 | public el: HTMLElement, 31 | private defaultRoute = "", 32 | ) {} 33 | 34 | public start() { 35 | const routeHandler = () => this.route(); 36 | window.addEventListener("hashchange", routeHandler); 37 | window.addEventListener("load", routeHandler); 38 | this.route(); 39 | } 40 | 41 | public register(route: Route) { 42 | this.routes[route.path] = route; 43 | } 44 | 45 | public route() { 46 | const hashRoute = location.hash.slice(1) || this.defaultRoute; 47 | const route = this.routes[hashRoute]; 48 | 49 | if (this.el && route && route !== this.currentRoute) { 50 | this.currentRoute = route; 51 | this.el.innerHTML = route.render(); 52 | selectCurrent(route.path); 53 | } else { 54 | this.currentRoute = null; 55 | } 56 | } 57 | } 58 | 59 | function queryAll(element: Element, selector: string) { 60 | return Array.from(element.querySelectorAll(selector)); 61 | } 62 | 63 | const nav = document.querySelector("#nav")!; 64 | function selectCurrent(route: string) { 65 | try { 66 | queryAll(nav, "a").forEach(a => a.classList.toggle("selected", false)); 67 | queryAll(nav, 'a[href="#' + route + '"]').forEach(a => a.classList.toggle("selected", true)); 68 | } catch (err) { 69 | // just bail if this doesn't work (IE) 70 | } 71 | } 72 | 73 | const router = new Router(document.querySelector("#content")!, "overview"); 74 | const routables = queryAll(document.body, "[data-route]"); 75 | routables.forEach(routable => { 76 | router.register({ 77 | path: routable.getAttribute("data-route")!, 78 | render: () => routable.innerHTML, 79 | }); 80 | }); 81 | router.start(); 82 | -------------------------------------------------------------------------------- /packages/compiler/src/__tests__/npm.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Documentalist } from "../documentalist"; 18 | import { NpmPlugin } from "../plugins/npm"; 19 | 20 | describe("NpmPlugin", () => { 21 | const dm = Documentalist.create().use("package.json", new NpmPlugin()); 22 | 23 | it("npm info matches package.json", async () => { 24 | const { npm } = await dm.documentGlobs("package.json"); 25 | const compilerPackageInfo = npm["@documentalist/compiler"]; 26 | const pkg = require("../../package.json"); 27 | // NOTE: not using snapshot as it would change with every release due to `npm info` call. 28 | expect(compilerPackageInfo.name).toBe(pkg.name); 29 | expect(compilerPackageInfo.description).toBe(pkg.description); 30 | expect(compilerPackageInfo.version).toBe(pkg.version); 31 | // HACKHACK: skipping because the renamed package has not been published yet 32 | // expect(compilerPackageInfo.latestVersion).toBeDefined(); // npm-info succeeded 33 | }); 34 | 35 | it("handles npm info fails", async () => { 36 | const { 37 | npm: { doesNotExist }, 38 | } = await dm.documentFiles([ 39 | { 40 | path: "package.json", 41 | read: () => `{ "name": "doesNotExist", "version": "1.0.0" }`, 42 | }, 43 | ]); 44 | expect(doesNotExist.name).toBe("doesNotExist"); 45 | expect(doesNotExist.version).toBe("1.0.0"); 46 | expect(doesNotExist.published).toBe(false); 47 | expect(doesNotExist.latestVersion).toBeUndefined(); 48 | }); 49 | 50 | it("options", async () => { 51 | const dm2 = Documentalist.create().use( 52 | "package.json", 53 | new NpmPlugin({ excludeNames: [/two/i], excludePrivate: true }), 54 | ); 55 | const { npm } = await dm2.documentFiles([ 56 | { 57 | // excludePrivate 58 | path: "one/package.json", 59 | read: () => `{ "name": "packageOne", "private": true, "version": "1.0.0" }`, 60 | }, 61 | { 62 | // excludeNames 63 | path: "two/package.json", 64 | read: () => `{ "name": "packageTwo", "version": "1.0.0" }`, 65 | }, 66 | ]); 67 | expect(Object.keys(npm)).toHaveLength(0); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /packages/compiler/src/__tests__/typescript.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { isTsClass, isTsInterface, TypescriptPluginData } from "@documentalist/client"; 18 | import { Documentalist } from "../documentalist"; 19 | import { TypescriptPlugin, TypescriptPluginOptions } from "../plugins/typescript/typescriptPlugin"; 20 | 21 | describe("TypescriptPlugin", () => { 22 | it("classes snapshot", () => expectSnapshot("classes")); 23 | it("enums snapshot", () => expectSnapshot("enums")); 24 | it("interfaces snapshot", () => expectSnapshot("interfaces")); 25 | it("functions snapshot", () => expectSnapshot("functions")); 26 | 27 | describe("options", () => { 28 | it("excludePaths", () => { 29 | // this snapshot is empty: everything is excluded. 30 | expectSnapshot("classes", { excludePaths: ["__fixtures__/"] }); 31 | }); 32 | 33 | it("excludeNames", () => { 34 | // get ButtonProps properties; should be missing a few. 35 | expectSnapshot( 36 | "interfaces", 37 | { excludeNames: [/icon/i, "intent"] }, 38 | ({ ButtonProps }) => isTsInterface(ButtonProps) && ButtonProps.properties.map(p => p.name), 39 | ); 40 | }); 41 | 42 | it("includePrivateMembers", () => { 43 | // class Animal has a private method 44 | expectSnapshot( 45 | "classes", 46 | { includePrivateMembers: true }, 47 | ({ Animal }) => isTsClass(Animal) && Animal.methods.map(m => m.name), 48 | ); 49 | }); 50 | }); 51 | }); 52 | 53 | async function expectSnapshot( 54 | /** name of fixture file to feed into DM */ 55 | name: string, 56 | options?: TypescriptPluginOptions, 57 | /** a function to transform the DM data, to avoid snapshotting _everything_. defaults to identity function. */ 58 | transform: (data: TypescriptPluginData["typescript"]) => any = arg => arg, 59 | ) { 60 | const fixtureFilepath = `src/__tests__/__fixtures__/${name}.ts`; 61 | const dm = Documentalist.create().use( 62 | ".ts", 63 | new TypescriptPlugin({ 64 | ...options, 65 | entryPoints: [fixtureFilepath], 66 | gitBranch: "develop", 67 | tsconfigPath: "src/__tests__/__fixtures__/tsconfig.json", 68 | }), 69 | ); 70 | const { typescript } = await dm.documentGlobs(fixtureFilepath); 71 | expect(transform(typescript)).toMatchSnapshot(); 72 | } 73 | -------------------------------------------------------------------------------- /packages/compiler/src/plugins/typescript/typestring.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { 18 | IntersectionType, 19 | ReferenceType, 20 | ReflectionKind, 21 | ReflectionType, 22 | SignatureReflection, 23 | Type, 24 | UnionType, 25 | } from "typedoc"; 26 | 27 | export function resolveTypeString(type: Type | undefined): string { 28 | if (type instanceof ReflectionType) { 29 | // reflection types include generics and object index signatures 30 | return type.declaration.getAllSignatures().map(resolveSignature).join(" | "); 31 | } else if (type instanceof UnionType) { 32 | return type.types.map(resolveTypeString).join(" | "); 33 | } else if (type instanceof IntersectionType) { 34 | return type.types.map(resolveTypeString).join(" & "); 35 | } else if (type instanceof ReferenceType) { 36 | return resolveReferenceName(type); 37 | } else if (type === undefined) { 38 | return ""; 39 | } else { 40 | return type.toString(); 41 | } 42 | } 43 | 44 | function resolveReferenceName(type: ReferenceType): string { 45 | if (type.name === "__type") { 46 | return "{}"; 47 | } else if ( 48 | type.reflection && 49 | type.reflection.parent !== undefined && 50 | type.reflection.kind === ReflectionKind.EnumMember 51 | ) { 52 | // include parent name in type string for easy identification 53 | return `${type.reflection.parent.name}.${type.name}`; 54 | } else if (type.typeArguments) { 55 | return `${type.name}<${type.typeArguments.map(resolveTypeString).join(", ")}>`; 56 | } 57 | return type.name; 58 | } 59 | 60 | export function resolveSignature(sig: SignatureReflection): string { 61 | const { parameters = [] } = sig; 62 | const paramList = parameters.map(param => 63 | // "[...]name[?]: type" 64 | [ 65 | param.flags.isRest ? "..." : "", 66 | param.name, 67 | param.flags.isOptional || param.defaultValue ? "?" : "", 68 | ": ", 69 | resolveTypeString(param.type), 70 | ].join(""), 71 | ); 72 | const returnType = resolveTypeString(sig.type); 73 | switch (sig.kind) { 74 | case ReflectionKind.CallSignature: 75 | case ReflectionKind.ConstructorSignature: 76 | return `(${paramList.join(", ")}) => ${returnType}`; 77 | case ReflectionKind.IndexSignature: 78 | return `{ [${paramList}]: ${returnType} }`; 79 | default: 80 | return sig.toString(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/client/src/compiler.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { StringOrTag } from "./tags"; 18 | 19 | /** 20 | * Each plugin receives a `Compiler` instance to aid in the processing of source files. 21 | */ 22 | export interface Compiler { 23 | /** 24 | * Converts an array of entries into a map of key to entry, using given 25 | * callback to extract key from each item. 26 | */ 27 | objectify(array: T[], getKey: (item: T) => string): { [key: string]: T }; 28 | 29 | /** 30 | * Get the relative path from `sourceBaseDir` to the given path. 31 | */ 32 | relativePath(path: string): string; 33 | 34 | /** 35 | * Render a block of content by extracting metadata (YAML front matter) and 36 | * splitting text content into markdown-rendered HTML strings and `{ tag, 37 | * value }` objects. 38 | * 39 | * To prevent special strings like "@include" from being parsed, a reserved 40 | * tag words array may be provided, in which case the line will be left as 41 | * is. 42 | */ 43 | renderBlock(blockContent: string, reservedTagWords?: string[]): Block; 44 | 45 | /** 46 | * Render a string of markdown to HTML, using the options from `Documentalist`. 47 | */ 48 | renderMarkdown(markdown: string): string; 49 | } 50 | 51 | /** 52 | * Metadata is parsed from YAML front matter in files and can contain arbitrary data. 53 | * A few keys are understood by Documentalist and, if defined in front matter, 54 | * will override default behavior. 55 | * 56 | * ```md 57 | * --- 58 | * reference: overview 59 | * title: "Welcome to the Jungle" 60 | * --- 61 | * actual contents of file... 62 | * ``` 63 | */ 64 | export interface Metadata { 65 | /** 66 | * Unique ID for addressing this page. 67 | * @default filename without extension 68 | */ 69 | reference?: string; 70 | 71 | /** 72 | * Human-friendly title of this page, for display in the UI. 73 | * @default value of first `@#` tag 74 | */ 75 | title?: string; 76 | 77 | // Supports arbitrary string keys. 78 | [key: string]: any; 79 | } 80 | 81 | /** 82 | * The output of `renderBlock` which parses a long form documentation block into 83 | * metadata, rendered markdown, and tags. 84 | */ 85 | export interface Block { 86 | /** Parsed nodes of source file. An array of markdown-rendered HTML strings or `@tag` objects. */ 87 | contents: StringOrTag[]; 88 | 89 | /** Raw unmodified contents of source file (excluding the metadata). */ 90 | contentsRaw: string; 91 | 92 | /** Arbitrary YAML metadata parsed from front matter of source file, if any, or `{}`. */ 93 | metadata: Metadata; 94 | } 95 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | references: 4 | setup_env: &setup_env 5 | docker: 6 | - image: cimg/node:18.18 7 | save_cache: &save_cache 8 | key: v2-dependency-cache-{{ checksum "yarn.lock" }} 9 | paths: 10 | - node_modules 11 | restore_cache: &restore_cache 12 | keys: 13 | - v2-dependency-cache-{{ checksum "yarn.lock" }} 14 | - v2-dependency-cache- 15 | reports_path: &reports_path 16 | path: ./reports 17 | 18 | jobs: 19 | install-dependencies: 20 | <<: *setup_env 21 | steps: 22 | - checkout 23 | - restore_cache: *restore_cache 24 | - attach_workspace: { at: "." } 25 | - run: yarn --frozen-lockfile 26 | - save_cache: *save_cache 27 | - persist_to_workspace: 28 | root: "." 29 | paths: 30 | - yarn.lock 31 | 32 | build: 33 | <<: *setup_env 34 | steps: 35 | - checkout 36 | - restore_cache: *restore_cache 37 | - attach_workspace: { at: "." } 38 | - run: yarn --frozen-lockfile 39 | - save_cache: *save_cache 40 | - run: yarn build 41 | - store_artifacts: 42 | path: dist 43 | - persist_to_workspace: 44 | root: "." 45 | paths: 46 | - packages/*/dist 47 | - packages/*/lib 48 | 49 | lint: 50 | <<: *setup_env 51 | steps: 52 | - checkout 53 | - restore_cache: *restore_cache 54 | - attach_workspace: { at: "." } 55 | - run: mkdir -p ./reports 56 | - run: yarn lint -- -- --format junit --out ../../reports/tslint.xml 57 | - store_test_results: *reports_path 58 | 59 | test: 60 | <<: *setup_env 61 | steps: 62 | - checkout 63 | - restore_cache: *restore_cache 64 | - attach_workspace: { at: "." } 65 | - run: mkdir -p ./reports 66 | - run: 67 | command: yarn test -- -- --ci --testResultsProcessor="jest-junit" 68 | environment: 69 | JEST_JUNIT_OUTPUT: ./reports/jest.xml 70 | - store_test_results: *reports_path 71 | 72 | deploy-preview: 73 | <<: *setup_env 74 | steps: 75 | - checkout 76 | - restore_cache: *restore_cache 77 | - attach_workspace: { at: "." } 78 | - store_artifacts: { path: packages/docs/dist } 79 | - run: ./scripts/submit-preview-comment.sh 80 | 81 | deploy-npm: 82 | <<: *setup_env 83 | steps: 84 | - checkout 85 | - restore_cache: *restore_cache 86 | - attach_workspace: { at: "." } 87 | - run: ./scripts/publish-npm-semver-tagged 88 | 89 | workflows: 90 | version: 2 91 | build_lint_test_deploy: 92 | jobs: 93 | - install-dependencies 94 | - build: 95 | requires: [install-dependencies] 96 | - lint: 97 | requires: [install-dependencies] 98 | - test: 99 | requires: [build] 100 | - deploy-preview: 101 | requires: [build] 102 | # this job never runs on tags and ignores master 103 | filters: 104 | branches: 105 | ignore: /master/ 106 | - deploy-npm: 107 | requires: [build, test] 108 | filters: 109 | branches: 110 | only: 111 | - develop 112 | - next 113 | - /^release\/.*/ 114 | -------------------------------------------------------------------------------- /scripts/submit-comment-with-artifact-links.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 4 | */ 5 | 6 | // @ts-check 7 | /* eslint-disable camelcase */ 8 | 9 | // Submits a comment to the change PR or commit with links to artifacts that 10 | // show the results of the code change being applied. 11 | 12 | import dedent from "dedent"; 13 | import { execSync } from "node:child_process"; 14 | import { basename } from "node:path"; 15 | import { Octokit } from "octokit"; 16 | 17 | /** 18 | * @type {Array<{path: string; url: string;}>} 19 | */ 20 | const { default: artifacts } = await import("./artifacts.json", { assert: { type: "json" }}); 21 | 22 | if (artifacts.items === undefined) { 23 | throw new Error( 24 | "Unable to read artifacts.json, please make sure the CircleCI API call succeeded with the necessary personal access token.", 25 | ); 26 | } 27 | 28 | const ARTIFACTS = { 29 | documentation: "packages/docs/dist/index.html", 30 | }; 31 | 32 | function getArtifactAnchorLink(pkg) { 33 | const artifactInfo = artifacts.items.find(a => a.path === ARTIFACTS[pkg]); 34 | return `${pkg}`; 35 | } 36 | 37 | if (process.env.GITHUB_API_TOKEN) { 38 | // We can post a comment on the PR if we have the necessary Github.com personal access token with access to this 39 | // repository and PR read/write permissions. 40 | // See https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token#creating-a-fine-grained-personal-access-token 41 | const octokit = new Octokit({ auth: process.env.GITHUB_API_TOKEN }); 42 | 43 | const mainDocumentationLink = getArtifactAnchorLink("documentation"); 44 | const currentGitCommitMessage = execSync('git --no-pager log --pretty=format:"%s" -1') 45 | .toString() 46 | .trim() 47 | .replace(/\\"/g, '\\\\"'); 48 | const commentBody = dedent` 49 | Build preview link for commit "${currentGitCommitMessage}": ${mainDocumentationLink} 50 | 51 | This is an automated comment from the deploy-preview CircleCI job. 52 | `; 53 | 54 | const repoParams = { 55 | owner: "palantir", 56 | repo: "documentalist", 57 | }; 58 | 59 | if (process.env.CIRCLE_PULL_REQUEST) { 60 | // attempt to comment on the PR as an "issue comment" (not a review comment) 61 | await octokit.rest.issues.createComment({ 62 | ...repoParams, 63 | issue_number: parseInt(basename(process.env.CIRCLE_PULL_REQUEST ?? ""), 10), 64 | body: commentBody, 65 | }); 66 | } else if (process.env.CIRCLE_SHA1) { 67 | // attempt to comment on the commit if there is no associated PR (this is most useful on the develop branch) 68 | await octokit.rest.repos.createCommitComment({ 69 | ...repoParams, 70 | commit_sha: process.env.CIRCLE_SHA1, 71 | body: commentBody, 72 | }); 73 | } 74 | } else { 75 | // If the access token is missing, simply log artifact URLs (typical in builds on repository forks). 76 | console.warn( 77 | "No Github API token available, so we cannot post a preview comment on this build's PR. This is expected on forks which have enabled CircleCI building.", 78 | ); 79 | Object.keys(ARTIFACTS).forEach(pkg => console.info(`${ARTIFACTS[pkg]}: ${getArtifactAnchorLink(pkg)}`)); 80 | } 81 | -------------------------------------------------------------------------------- /packages/compiler/src/__tests__/__fixtures__/interfaces.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2019 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** All icon identifiers */ 18 | export type IconName = "add" | "remove" | "plus" | "minus"; 19 | 20 | export interface ActionProps { 21 | /** Whether this action is non-interactive. */ 22 | disabled?: boolean; 23 | 24 | /** Name of the icon (the part after `pt-icon-`) to add to the button. */ 25 | iconName?: IconName; 26 | 27 | /** Click event handler. */ 28 | onClick: (event: MouseEvent) => void; 29 | 30 | /** Action text. */ 31 | text: string; 32 | } 33 | 34 | export interface ButtonProps extends ActionProps { 35 | /** 36 | * If set to `true`, the button will display in an active state. 37 | * This is equivalent to setting `className="pt-active"`. 38 | * @default false 39 | */ 40 | active?: boolean; 41 | 42 | /** A ref handler that receives the native HTML element backing this component. */ 43 | elementRef?: (ref: HTMLElement) => any; 44 | 45 | /** 46 | * Name of the icon (the part after `pt-icon-`) to add to the button. 47 | * @deprecated since v1.2.3 48 | */ 49 | rightIconName?: IconName; 50 | 51 | /** 52 | * If set to `true`, the button will display a centered loading spinner instead of its contents. 53 | * The width of the button is not affected by the value of this prop. 54 | * @default false 55 | * @deprecated 56 | */ 57 | loading?: boolean; 58 | 59 | /** 60 | * HTML `type` attribute of button. Common values are `"button"` and `"submit"`. 61 | * Note that this prop has no effect on `AnchorButton`; it only affects `Button`. 62 | * @default "button" 63 | */ 64 | type?: string; 65 | 66 | /** Index signature for the masses. */ 67 | [x: string]: any; 68 | } 69 | 70 | /** 71 | * Each plugin receives a `Compiler` instance to aid in the processing of source files. 72 | */ 73 | export interface Compiler { 74 | /** 75 | * Converts an array of entries into a map of key to entry, using given 76 | * callback to extract key from each item. 77 | */ 78 | objectify(array: T[], getKey: (item: T) => string): { [key: string]: T }; 79 | 80 | /** 81 | * Render a block of content by extracting metadata (YAML front matter) and 82 | * splitting text content into markdown-rendered HTML strings and `{ tag, 83 | * value }` objects. 84 | * 85 | * To prevent special strings like "@include" from being parsed, a reserved 86 | * tag words array may be provided, in which case the line will be left as 87 | * is. 88 | */ 89 | renderBlock(blockContent: string, reservedTagWords?: string[]): object; 90 | 91 | /** 92 | * Render a string of markdown to HTML, using the options from `Documentalist`. 93 | */ 94 | renderMarkdown(markdown: string): string; 95 | } 96 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | _Release versions v5.0.0+ are documented in the [Releases page](https://github.com/palantir/documentalist/releases) and in package-specific [`changelog/` folders](https://github.com/palantir/documentalist/tree/develop/packages/compiler/changelog)_. 4 | 5 | ### 2023-10-24 6 | 7 | `@documentalist/compiler@5.0.0` 8 | 9 | - [#156](https://github.com/palantir/documentalist/pull/156) ⚠️ break: Upgrade to typedoc to v0.25.x, which supports TypeScript v4.6 - v5.2 10 | - [#236](https://github.com/palantir/documentalist/pull/236) ⚠️ break: Upgrade minimum Node.js engine to v18.x 11 | - [#239](https://github.com/palantir/documentalist/pull/239) ⚠️ break: drop `I` prefix from all interface names 12 | 13 | `@documentalist/client@5.0.0` 14 | 15 | - [#156](https://github.com/palantir/documentalist/pull/156) feat: Export new type alias `TsDocEntry` 16 | - [#236](https://github.com/palantir/documentalist/pull/236) ⚠️ break: Upgrade minimum Node.js engine to v18.x 17 | - [#239](https://github.com/palantir/documentalist/pull/239) ⚠️ break: drop `I` prefix from all interface names 18 | 19 | ### 2022-04-05 20 | 21 | `@documentalist/compiler@4.0.0` 22 | 23 | - [#144](https://github.com/palantir/documentalist/pull/144) :warning: break: Minimum version of Node.js is now v12.0 24 | - N.B. Node v16.x is recommended 25 | - [#144](https://github.com/palantir/documentalist/pull/144) :warning: break(`TypescriptPlugin`): `includeNonExportedMembers` option has been removed. TypeDoc is removing support for documenting non-exported members in a future version, so we will have to drop support for this in Documentalist as well. 26 | - [#144](https://github.com/palantir/documentalist/pull/144) fix(`NpmPlugin`): simplified the plugin to have synchronous execution (replaced `spawn` with `spawnSync`), improving its compatibility with newer versions of Node.js 27 | 28 | `@documentalist/client@4.0.0` 29 | 30 | - [#144](https://github.com/palantir/documentalist/pull/144) :warning: break: Minimum version of Node.js is now v12.0 31 | - N.B. Node v16.x is recommended 32 | 33 | ### 2020-10-05 34 | 35 | `@documentalist/compiler@3.0.0` 36 | 37 | - :warning: Minimum version of Node.js is now v10.0 38 | - :warning: Minimum version of TypeScript is now v3.9 39 | - [#119](https://github.com/palantir/documentalist/pull/119) upgrade dependencies 40 | - typedoc v0.19 41 | - typescript v4.0 42 | - marked v1.2 43 | - kss v3.0 44 | 45 | `@documentalist/client@3.0.0` 46 | 47 | - Minimum version of Node.js is now v10.0 48 | 49 | ### 2020-08-17 50 | 51 | `@documentalist/compiler@2.9.0` 52 | 53 | - [#113](https://github.com/palantir/documentalist/pull/109) chore(deps): upgrade typedoc to 0.18.0 54 | - [#109](https://github.com/palantir/documentalist/pull/109) chore(deps): upgrade marked to v0.8 55 | 56 | ### 2020-04-15 57 | 58 | `@documentalist/compiler@2.8.1` 59 | 60 | - [#106](https://github.com/palantir/documentalist/pull/106) fix: NOENT error on win platform 61 | 62 | `@documentalist/client@2.5.0`, `@documentalist/compiler@2.8.0`, `@documentalist/docs@2.8.0` 63 | 64 | - [#107](https://github.com/palantir/documentalist/pull/107) feat: upgrade to TypeScript 3.8 65 | 66 | ### 2020-02-20 67 | 68 | `@documentalist/client@2.4.0`, `@documentalist/compiler@2.7.0`, `@documentalist/docs@2.7.0` 69 | 70 | - [#104](https://github.com/palantir/documentalist/pull/104) feat: add support for extracting function docs 71 | 72 | ### 2020-01-14 73 | 74 | `@documentalist/compiler@2.6.0`, `@documentalist/docs@2.6.0` 75 | 76 | - [#103](https://github.com/palantir/documentalist/pull/103) feat: upgrade to TypeDoc 0.16 77 | -------------------------------------------------------------------------------- /packages/compiler/src/page.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { HeadingNode, isHeadingTag, isTag, PageData, PageNode } from "@documentalist/client"; 18 | 19 | export class PageMap { 20 | private store: Map = new Map(); 21 | 22 | /** Returns an iterator for all the pages (values) in the map. */ 23 | public pages() { 24 | return Array.from(this.store.values()); 25 | } 26 | 27 | /** Returns the page with the given ID or `undefined` if not found. */ 28 | public get(id: string) { 29 | return this.store.get(id); 30 | } 31 | 32 | /** Removes the page with the given ID and returns it or `undefined` if not found. */ 33 | public remove(id: string) { 34 | const page = this.get(id); 35 | if (page !== undefined) { 36 | this.store.delete(id); 37 | } 38 | return page; 39 | } 40 | 41 | /** 42 | * Sets the page data at the given ID, when you already have a full page object. 43 | * Warns if a page with this ID already exists. 44 | */ 45 | public set(id: string, page: PageData) { 46 | if (this.store.has(id)) { 47 | console.warn(`Found duplicate page "${id}"; overwriting previous data.`); 48 | console.warn("Rename headings or use metadata `reference` key to disambiguate."); 49 | } 50 | this.store.set(id, page); 51 | } 52 | 53 | /** Returns a JS object mapping page IDs to data. */ 54 | public toObject() { 55 | const object: { [key: string]: PageData } = {}; 56 | for (const [key, val] of Array.from(this.store.entries())) { 57 | object[key] = val; 58 | } 59 | return object; 60 | } 61 | 62 | public toTree(id: string, depth = 0): PageNode { 63 | const page = this.get(id); 64 | if (page === undefined) { 65 | throw new Error(`Unknown @page '${id}' in toTree()`); 66 | } 67 | const pageNode = initPageNode(page, depth); 68 | page.contents.forEach(node => { 69 | // we only care about @page and @##+ tag nodes 70 | if (isTag(node, "page")) { 71 | pageNode.children.push(this.toTree(node.value, depth + 1)); 72 | } else if (isHeadingTag(node) && node.level > 1) { 73 | // skipping h1 headings cuz they become the page title itself. 74 | pageNode.children.push(initHeadingNode(node.value, pageNode.level + node.level - 1)); 75 | } 76 | }); 77 | return pageNode; 78 | } 79 | } 80 | 81 | function initPageNode({ reference, title }: PageData, level: number = 0): PageNode { 82 | // NOTE: `route` may be overwritten in MarkdownPlugin based on nesting. 83 | return { children: [], level, reference, route: reference, title }; 84 | } 85 | 86 | function initHeadingNode(title: string, level: number): HeadingNode { 87 | // NOTE: `route` will be populated in MarkdownPlugin. 88 | return { title, level, route: "" }; 89 | } 90 | -------------------------------------------------------------------------------- /packages/docs/theme/mixins.pug: -------------------------------------------------------------------------------- 1 | //- Mixins for Documentalist's own docs 2 | 3 | - var isArray = (arr) => arr != null && arr.length > 0 4 | 5 | //- renders a `contents` array of strings or tags 6 | mixin joinContent(doc) 7 | if doc && doc.contents 8 | each block in doc.contents 9 | if typeof block === "string" 10 | | !{block} 11 | else 12 | +renderTag(block.tag, block.value) 13 | 14 | mixin joinNames(names, joiner =", ") 15 | each name, i in names 16 | +linkify(name) 17 | if i < names.length - 1 18 | = joiner 19 | 20 | //- split text into words and try to link each word to member of docs data 21 | mixin linkify(text = "") 22 | each word in text.split(WORD_SEPARATORS) 23 | if typescript[word] 24 | a(href='#' + word)= word 25 | else 26 | = word 27 | - WORD_SEPARATORS = /([\[\]<>() :.,]+)/g 28 | 29 | //- renders an `@tag` from a (short) known list 30 | mixin renderTag(tag, value) 31 | case tag 32 | when "heading" 33 | h1= value 34 | when "interface" 35 | +interfaceDocs(typescript[value]) 36 | default 37 | p #{tag}: #[code: +linkify(value)] 38 | 39 | //- render a type signature, including default value & inherited from 40 | mixin renderType(sig) 41 | .type-signature 42 | code 43 | +linkify(sig.type) 44 | if sig.defaultValue 45 | em= " = " + sig.defaultValue 46 | if sig.inheritedFrom 47 | small: em 48 | = "Inherited from " 49 | code: +linkify(sig.inheritedFrom) 50 | +joinContent(sig.documentation) 51 | 52 | //- render docs for an interface (or class) member 53 | mixin interfaceDocs(iface) 54 | blockquote.interface-block(id=iface.name) 55 | h2.interface-title 56 | code #[small= iface.kind] #{iface.name} 57 | if iface.extends 58 | small= " extends " 59 | +joinNames(iface.extends) 60 | if iface.implements 61 | small= " implements " 62 | +joinNames(iface.implements) 63 | small= " " 64 | a(href=iface.sourceUrl,target="_blank",title="View source") # 65 | 66 | em 67 | small= "Exported from: " 68 | code @documentalist/#{iface.fileName.split("/", 2)[1]} 69 | 70 | if iface.kind === "type alias" 71 | .interface-properties 72 | table 73 | +propDocsRow(iface) 74 | +renderType(iface) 75 | else 76 | +joinContent(iface.documentation) 77 | 78 | if iface.constructorType 79 | h3 Constructor 80 | .interface-properties 81 | table 82 | each sig in iface.constructorType.signatures 83 | +propDocsRow(sig) 84 | +renderType(sig) 85 | 86 | if isArray(iface.members) 87 | h3 Members 88 | .interface-properties 89 | table 90 | each prop in iface.members 91 | +propDocsRow(prop) 92 | +renderType(prop) 93 | 94 | if isArray(iface.properties) 95 | h3 Properties 96 | .interface-properties 97 | table 98 | each prop in iface.properties 99 | +propDocsRow(prop) 100 | +renderType(prop) 101 | 102 | if isArray(iface.methods) 103 | h3 Methods 104 | .interface-properties 105 | table 106 | each prop in iface.methods 107 | +propDocsRow(prop) 108 | each sig in prop.signatures 109 | +renderType(sig) 110 | 111 | mixin propDocsRow(prop) 112 | tr 113 | td.prop-name(valign="top") 114 | if prop.flags && prop.flags.isStatic 115 | code= "static " 116 | code= prop.name 117 | td.prop-type(valign="top") 118 | block 119 | -------------------------------------------------------------------------------- /packages/compiler/src/plugins/npm.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Compiler, File, NpmPackageInfo, NpmPluginData, Plugin } from "@documentalist/client"; 18 | import { spawnSync } from "child_process"; 19 | 20 | export interface NpmPluginOptions { 21 | /** Whether to exclude packages marked `private`. */ 22 | excludePrivate?: boolean; 23 | 24 | /** Array of patterns to exclude packages by `name`. */ 25 | excludeNames?: Array; 26 | } 27 | 28 | /** 29 | * The `NpmPlugin` parses `package.json` files and emits data about each NPM 30 | * package. It uses `npm info` to look up data about published packages, and 31 | * falls back to `package.json` info if the package is private or unpublished. 32 | */ 33 | export class NpmPlugin implements Plugin { 34 | public constructor(private options: NpmPluginOptions = {}) {} 35 | 36 | public compile(packageJsons: File[], dm: Compiler): NpmPluginData { 37 | const info = packageJsons.map(pkg => this.parseNpmInfo(pkg, dm)); 38 | const { excludeNames, excludePrivate } = this.options; 39 | const npm = arrayToObject( 40 | info.filter(pkg => isNotExcluded(excludeNames, pkg.name) && excludePrivate !== pkg.private), 41 | pkg => pkg.name, 42 | ); 43 | return { npm }; 44 | } 45 | 46 | private parseNpmInfo = (packageJson: File, dm: Compiler): NpmPackageInfo => { 47 | const sourcePath = dm.relativePath(packageJson.path); 48 | const json = JSON.parse(packageJson.read()); 49 | const data = this.getNpmInfo(json.name); 50 | // `npm info` returns an error if it doesn't know the package 51 | // so we can use package.json data instead 52 | if (data.error) { 53 | return { 54 | name: json.name, 55 | published: false, 56 | version: json.version, 57 | // tslint:disable-next-line:object-literal-sort-keys 58 | description: json.description, 59 | private: json.private === true, 60 | sourcePath, 61 | versions: [json.version], 62 | }; 63 | } 64 | 65 | const distTags = data["dist-tags"] || {}; 66 | return { 67 | name: data.name, 68 | published: true, 69 | version: json.version, 70 | // tslint:disable-next-line:object-literal-sort-keys 71 | description: data.description, 72 | latestVersion: distTags.latest, 73 | nextVersion: distTags.next, 74 | private: false, 75 | sourcePath, 76 | versions: data.versions, 77 | }; 78 | }; 79 | 80 | private getNpmInfo(packageName: string): Record { 81 | const info = spawnSync("npm", ["info", "--json", packageName], { 82 | shell: true, 83 | }); 84 | 85 | if (info.status === 0) { 86 | return JSON.parse(info.stdout.toString()); 87 | } else { 88 | return { 89 | error: info.stderr.toString(), 90 | }; 91 | } 92 | } 93 | } 94 | 95 | function arrayToObject(array: T[], keyFn: (item: T) => string) { 96 | const obj: { [key: string]: T } = {}; 97 | array.forEach(item => (obj[keyFn(item)] = item)); 98 | return obj; 99 | } 100 | 101 | /** Returns true if value does not match all patterns. */ 102 | function isNotExcluded(patterns: Array = [], value?: string) { 103 | return value === undefined || patterns.every(p => value.match(p) == null); 104 | } 105 | -------------------------------------------------------------------------------- /packages/compiler/src/__tests__/compilerImpl.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { isHeadingTag } from "@documentalist/client"; 18 | import { CompilerImpl } from "../compilerImpl"; 19 | 20 | describe("CompilerImpl", () => { 21 | const API = new CompilerImpl({}); 22 | 23 | describe("objectify", () => { 24 | it("empty array returns empty object", () => { 25 | expect(API.objectify([], x => x)).toEqual({}); 26 | }); 27 | 28 | it("turns an array into an object", () => { 29 | const array = [ 30 | { name: "Bill", age: 1037 }, 31 | { name: "Gilad", age: 456 }, 32 | { name: "Robert", age: 21 }, 33 | ]; 34 | const byName = API.objectify(array, x => x.name); 35 | expect(Object.keys(byName)).toEqual(["Bill", "Gilad", "Robert"]); 36 | }); 37 | }); 38 | 39 | describe("metadata", () => { 40 | const METADATA = "---\nhello: world\nsize: 1000\n---\n"; 41 | const MARKDOWN = "# Title\nbody body body"; 42 | const OBJECT = { hello: "world", size: 1000 }; 43 | 44 | it("extracts contentsRaw and parses metadata", () => { 45 | const data = API.renderBlock(METADATA + MARKDOWN); 46 | expect(data.contentsRaw).toBe(MARKDOWN); 47 | expect(data.metadata).toEqual(OBJECT); 48 | }); 49 | 50 | it("supports empty metadata block", () => { 51 | const data = API.renderBlock("---\n---\n" + MARKDOWN); 52 | expect(data.contentsRaw).toBe(MARKDOWN); 53 | expect(data.metadata).toEqual({}); 54 | }); 55 | 56 | it("metadata block is optional", () => { 57 | const data = API.renderBlock(MARKDOWN); 58 | expect(data.contentsRaw).toBe(MARKDOWN); 59 | expect(data.metadata).toEqual({}); 60 | }); 61 | }); 62 | 63 | describe("rendered contents", () => { 64 | it("returns a single-element array for string without @tags", () => { 65 | const { contents } = API.renderBlock("simple string"); 66 | expect(contents).toEqual(["

simple string

\n"]); 67 | }); 68 | 69 | it("converts @tag to object in array", () => { 70 | const { contents } = API.renderBlock(FILE); 71 | expect(contents).toHaveLength(3); 72 | expect(contents[1]).toEqual({ 73 | tag: "interface", 74 | value: "ButtonProps", 75 | }); 76 | }); 77 | 78 | it("converts @#+ to heading tags in array", () => { 79 | const { contents } = API.renderBlock(HEADING_FILE); 80 | expect(contents).toHaveLength(9); 81 | const headings = contents.filter(isHeadingTag); 82 | expect(headings).toHaveLength(4); 83 | // choosing one to test deep equality 84 | expect(headings[1]).toEqual({ 85 | level: 2, 86 | route: "", 87 | tag: "heading", 88 | value: "Section 1", 89 | }); 90 | }); 91 | 92 | it("reservedWords will ignore matching @tag", () => { 93 | const { contents } = API.renderBlock(FILE, ["interface"]); 94 | // only one string (reserved @word does not get split to its own entry) 95 | expect(contents).toHaveLength(1); 96 | // @tag value comes out exactly as written in source, on its own line 97 | expect((contents[0] as string).split("\n")).toContain("@interface ButtonProps"); 98 | }); 99 | }); 100 | }); 101 | 102 | const FILE = ` 103 | # Title 104 | description 105 | @interface ButtonProps 106 | more description 107 | `; 108 | 109 | const HEADING_FILE = ` 110 | @# Title 111 | description 112 | @## Section 1 113 | @interface ButtonProps 114 | more words 115 | @### Section 1a 116 | more words 117 | @## Section 2 118 | more words 119 | `; 120 | -------------------------------------------------------------------------------- /packages/compiler/src/documentalist.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { File, Plugin } from "@documentalist/client"; 18 | import * as fs from "fs"; 19 | import * as glob from "glob"; 20 | import * as path from "path"; 21 | import { CompilerImpl, CompilerOptions } from "./compilerImpl"; 22 | 23 | /** 24 | * Plugins are stored with the regex used to match against file paths. 25 | */ 26 | export interface PluginEntry { 27 | pattern: RegExp; 28 | plugin: Plugin; 29 | } 30 | 31 | export class Documentalist { 32 | /** 33 | * Construct a new `Documentalist` instance with the provided options. 34 | * This method lends itself particularly well to the chaining syntax: 35 | * `Documentalist.create(options).use(...)`. 36 | */ 37 | public static create(options?: CompilerOptions): Documentalist<{}> { 38 | return new Documentalist(options, []); 39 | } 40 | 41 | constructor( 42 | private options: CompilerOptions = {}, 43 | private plugins: Array> = [], 44 | ) {} 45 | 46 | /** 47 | * Adds the plugin to Documentalist. Returns a new instance of Documentalist 48 | * with a template type that includes the data from the plugin. This way the 49 | * `documentFiles` and `documentGlobs` methods will return an object that is 50 | * already typed to include each plugin's output. 51 | * 52 | * The plugin is applied to all files whose absolute path matches the 53 | * supplied pattern. 54 | * 55 | * @param pattern - A regexp pattern or a file extension string like "js" 56 | * @param plugin - The plugin implementation 57 | * @returns A new instance of `Documentalist` with an extended type 58 | */ 59 | public use

(pattern: RegExp | string, plugin: Plugin

): Documentalist { 60 | if (typeof pattern === "string") { 61 | pattern = new RegExp(`${pattern}$`); 62 | } 63 | 64 | const newPlugins = [...this.plugins, { pattern, plugin }] as Array>; 65 | return new Documentalist(this.options, newPlugins); 66 | } 67 | 68 | /** 69 | * Returns a new instance of Documentalist with no plugins. 70 | */ 71 | public clearPlugins(): Documentalist<{}> { 72 | return new Documentalist<{}>(this.options, []); 73 | } 74 | 75 | /** 76 | * Finds all files matching the provided variadic glob expressions and then 77 | * runs `documentFiles` with them, emitting all the documentation data. 78 | */ 79 | public async documentGlobs(...filesGlobs: string[]) { 80 | const files = this.expandGlobs(filesGlobs); 81 | return this.documentFiles(files); 82 | } 83 | 84 | /** 85 | * Iterates over all plugins, passing all matching files to each in turn. 86 | * The output of each plugin is merged to produce the resulting 87 | * documentation object. 88 | * 89 | * The return type `T` is a union of each plugin's data type. 90 | */ 91 | public async documentFiles(files: File[]) { 92 | const compiler = new CompilerImpl(this.options); 93 | // need an empty object of correct type that we can merge into 94 | // tslint:disable-next-line:no-object-literal-type-assertion 95 | const documentation = {} as T; 96 | for (const { pattern, plugin } of this.plugins) { 97 | const pluginFiles = files.filter(f => pattern.test(f.path)); 98 | const pluginDocumentation = await plugin.compile(pluginFiles, compiler); 99 | this.mergeInto(documentation, pluginDocumentation); 100 | } 101 | return documentation; 102 | } 103 | 104 | /** 105 | * Expands an array of globs and flatten to a single array of files. 106 | */ 107 | private expandGlobs(filesGlobs: string[]): File[] { 108 | return filesGlobs 109 | .map(filesGlob => glob.sync(filesGlob)) 110 | .reduce((a, b) => a.concat(b)) 111 | .map(fileName => { 112 | const absolutePath = path.resolve(fileName); 113 | return { 114 | path: absolutePath, 115 | read: () => fs.readFileSync(absolutePath, "utf8"), 116 | }; 117 | }); 118 | } 119 | 120 | /** 121 | * Shallow-merges keys form source into destination object (modifying it in the process). 122 | */ 123 | private mergeInto(destination: T, source: T) { 124 | for (const key in source) { 125 | if (source[key] != null) { 126 | if (destination[key] != null) { 127 | console.warn(`WARNING: Duplicate plugin key "${key}". Your plugins are overwriting each other.`); 128 | } 129 | destination[key] = source[key]; 130 | } 131 | } 132 | return destination; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /packages/compiler/src/compilerImpl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Block, Compiler, HeadingTag, StringOrTag } from "@documentalist/client"; 18 | import * as yaml from "js-yaml"; 19 | import { marked, MarkedOptions } from "marked"; 20 | import { relative } from "path"; 21 | 22 | /** 23 | * Matches the triple-dash metadata block on the first line of markdown file. 24 | * The first capture group contains YAML content. 25 | */ 26 | const METADATA_REGEX = /^---\n?((?:.|\n)*)\n---\n/; 27 | 28 | /** 29 | * Splits text content for lines that begin with `@tagName`. 30 | */ 31 | const TAG_REGEX = /^@(\S+)(?:\s+([^\n]+))?$/; 32 | const TAG_SPLIT_REGEX = /^(@\S+(?:\s+[^\n]+)?)$/gm; 33 | 34 | export interface CompilerOptions { 35 | /** Options for markdown rendering. See https://github.com/chjj/marked#options-1. */ 36 | markdown?: MarkedOptions; 37 | 38 | /** 39 | * Reserved @tags that should be preserved in the contents string. 40 | * A common use case is allowing specific code constructs, like `@Decorator` names. 41 | * Do not include the `@` prefix in the strings. 42 | */ 43 | reservedTags?: string[]; 44 | 45 | /** 46 | * Base directory for generating relative `sourcePath`s. 47 | * 48 | * This option _does not affect_ glob expansion, only the generation of 49 | * `sourcePath` in plugin data. 50 | * @default process.cwd() 51 | */ 52 | sourceBaseDir?: string; 53 | } 54 | 55 | export class CompilerImpl implements Compiler { 56 | public constructor(private options: CompilerOptions) {} 57 | 58 | public objectify(array: T[], getKey: (item: T) => string) { 59 | return array.reduce<{ [key: string]: T }>((obj, item) => { 60 | obj[getKey(item)] = item; 61 | return obj; 62 | }, {}); 63 | } 64 | 65 | public relativePath = (path: string) => { 66 | const { sourceBaseDir = process.cwd() } = this.options; 67 | return relative(sourceBaseDir, path); 68 | }; 69 | 70 | public renderBlock = (blockContent: string, reservedTagWords = this.options.reservedTags): Block => { 71 | const { contentsRaw, metadata } = this.extractMetadata(blockContent.trim()); 72 | const contents = this.renderContents(contentsRaw, reservedTagWords); 73 | return { contents, contentsRaw, metadata }; 74 | }; 75 | 76 | public renderMarkdown = (markdown: string) => marked(markdown, this.options.markdown); 77 | 78 | /** 79 | * Converts the content string into an array of `ContentNode`s. If the 80 | * `contents` option is `html`, the string nodes will also be rendered with 81 | * markdown. 82 | */ 83 | private renderContents(content: string, reservedTagWords?: string[]) { 84 | const splitContents = this.parseTags(content, reservedTagWords); 85 | return splitContents 86 | .map(node => (typeof node === "string" ? this.renderMarkdown(node) : node)) 87 | .filter(node => node !== ""); 88 | } 89 | 90 | /** 91 | * Extracts optional YAML frontmatter metadata block from the beginning of a 92 | * markdown file and parses it to a JS object. 93 | */ 94 | private extractMetadata(text: string) { 95 | const match = METADATA_REGEX.exec(text); 96 | if (match === null) { 97 | return { contentsRaw: text, metadata: {} }; 98 | } 99 | 100 | const contentsRaw = text.substr(match[0].length); 101 | const yamlObject: any | undefined = yaml.load(match[1]); 102 | 103 | return { contentsRaw, metadata: yamlObject ?? {} }; 104 | } 105 | 106 | /** 107 | * Splits the content string when it encounters a line that begins with a 108 | * `@tag`. You may prevent this splitting by specifying an array of reserved 109 | * tag names. 110 | */ 111 | private parseTags(content: string, reservedWords: string[] = []) { 112 | // using reduce so we can squash consecutive strings (<= 1 entry per iteration) 113 | return content.split(TAG_SPLIT_REGEX).reduce((arr, str) => { 114 | const match = TAG_REGEX.exec(str); 115 | if (match === null || reservedWords.indexOf(match[1]) >= 0) { 116 | if (typeof arr[arr.length - 1] === "string") { 117 | // merge consecutive strings to avoid breaking up code blocks 118 | arr[arr.length - 1] += str; 119 | } else { 120 | arr.push(str); 121 | } 122 | } else { 123 | const tag = match[1]; 124 | const value = match[2]; 125 | if (/#+/.test(tag)) { 126 | // NOTE: not enough information to populate `route` field yet 127 | const heading: HeadingTag = { 128 | level: tag.length, 129 | route: "", 130 | tag: "heading", 131 | value, 132 | }; 133 | arr.push(heading); 134 | } else { 135 | arr.push({ tag, value }); 136 | } 137 | } 138 | return arr; 139 | }, []); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /packages/compiler/src/plugins/markdown.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { 18 | Block, 19 | Compiler, 20 | File, 21 | HeadingNode, 22 | isHeadingTag, 23 | isPageNode, 24 | MarkdownPluginData, 25 | PageData, 26 | PageNode, 27 | Plugin, 28 | slugify, 29 | } from "@documentalist/client"; 30 | import * as path from "path"; 31 | import { PageMap } from "../page"; 32 | 33 | export interface MarkdownPluginOptions { 34 | /** 35 | * Page reference that lists the nav roots. 36 | * @default "_nav" 37 | */ 38 | navPage: string; 39 | } 40 | 41 | /** 42 | * The `MarkdownPlugin` parses and renders markdown pages and produces a navigation tree of all documents. 43 | * This plugin traces `@page` and `@#+` "heading" tags to discover pages (given a single starting `navPage`) 44 | * and build up a tree representation of those pages. 45 | * 46 | * @see PageData (rendered markdown page) 47 | * @see PageNode (node in navigation tree) 48 | */ 49 | export class MarkdownPlugin implements Plugin { 50 | private options: MarkdownPluginOptions; 51 | 52 | public constructor(options: Partial = {}) { 53 | this.options = { 54 | navPage: "_nav", 55 | ...options, 56 | }; 57 | } 58 | 59 | /** 60 | * Reads the given set of markdown files and adds their data to the internal storage. 61 | * Returns a plain object mapping page references to their data. 62 | */ 63 | public compile(markdownFiles: File[], compiler: Compiler): MarkdownPluginData { 64 | const pageMap = this.buildPageStore(markdownFiles, compiler); 65 | // now that we have all known pages, we can resolve @include tags. 66 | this.resolveIncludeTags(pageMap); 67 | // generate navigation tree after all pages loaded and processed. 68 | const { navPage } = this.options; 69 | if (pageMap.get(navPage) == null) { 70 | throw new Error(`Error generating page map: options.navPage "${navPage}" does not exist.`); 71 | } 72 | const nav = pageMap.toTree(navPage).children.filter(isPageNode); 73 | // use nav tree to fill in `route` for all pages and headings. 74 | this.resolveRoutes(pageMap, nav); 75 | // generate object at the end, after `route` has been computed throughout. 76 | const pages = pageMap.toObject(); 77 | return { nav, pages }; 78 | } 79 | 80 | private blockToPage(sourcePath: string, block: Block): PageData { 81 | const reference = getReference(sourcePath, block); 82 | return { 83 | reference, 84 | route: reference, 85 | sourcePath, 86 | title: getTitle(block), 87 | ...block, 88 | }; 89 | } 90 | 91 | /** Convert each file to PageData and populate store. */ 92 | private buildPageStore(markdownFiles: File[], { relativePath, renderBlock }: Compiler) { 93 | const pageMap = new PageMap(); 94 | for (const file of markdownFiles) { 95 | const block = renderBlock(file.read()); 96 | const page = this.blockToPage(relativePath(file.path), block); 97 | pageMap.set(page.reference, page); 98 | } 99 | return pageMap; 100 | } 101 | 102 | /** 103 | * Computes `route` for the given `node` based on its parent. 104 | * If node is a page, then it also computes `route` for each heading and recurses through child 105 | * pages. 106 | */ 107 | private recurseRoute(pageMap: PageMap, node: PageNode | HeadingNode, parent?: PageNode) { 108 | // compute route for page and heading NODES (from nav tree) 109 | const baseRoute = parent === undefined ? [] : [parent.route]; 110 | const route = isPageNode(node) 111 | ? baseRoute.concat(node.reference).join("/") 112 | : baseRoute.concat(slugify(node.title)).join("."); 113 | node.route = route; 114 | 115 | if (isPageNode(node)) { 116 | // node is a page, so it must exist in PageMap. 117 | const page = pageMap.get(node.reference)!; 118 | page.route = route; 119 | 120 | page.contents.forEach(content => { 121 | // inject `route` field into heading TAGS (from page contents) 122 | if (isHeadingTag(content)) { 123 | // h1 tags do not get nested as they are used as page title 124 | content.route = content.level > 1 ? [route, slugify(content.value)].join(".") : route; 125 | } 126 | }); 127 | node.children.forEach(child => this.recurseRoute(pageMap, child, node)); 128 | } 129 | } 130 | 131 | private resolveRoutes(pageMap: PageMap, nav: PageNode[]) { 132 | for (const page of nav) { 133 | // walk the nav tree and compute `route` property for each resource. 134 | this.recurseRoute(pageMap, page); 135 | } 136 | } 137 | 138 | /** Iterates `contents` array and inlines any `@include page` tags. */ 139 | private resolveIncludeTags(pageStore: PageMap) { 140 | for (const page of pageStore.pages()) { 141 | // using `reduce` so we can add one or many entries for each node 142 | page.contents = page.contents.reduce((array, content) => { 143 | if (typeof content === "string" || content.tag !== "include") { 144 | return array.concat(content); 145 | } 146 | // inline @include page 147 | const pageToInclude = pageStore.get(content.value); 148 | if (pageToInclude === undefined) { 149 | throw new Error(`Unknown @include reference '${content.value}' in '${page.reference}'`); 150 | } 151 | return array.concat(pageToInclude.contents); 152 | }, []); 153 | } 154 | } 155 | } 156 | 157 | function getReference(absolutePath: string, { metadata }: Block) { 158 | if (metadata.reference != null) { 159 | return metadata.reference; 160 | } 161 | return path.basename(absolutePath, path.extname(absolutePath)); 162 | } 163 | 164 | function getTitle(block: Block) { 165 | if (block.metadata.title != null) { 166 | return block.metadata.title; 167 | } 168 | 169 | const first = block.contents[0]; 170 | if (isHeadingTag(first)) { 171 | return first.value; 172 | } 173 | 174 | return "(untitled)"; 175 | } 176 | -------------------------------------------------------------------------------- /packages/client/src/typescript.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Block } from "./compiler"; 18 | 19 | /** Enumeration describing the various kinds of member supported by this plugin. */ 20 | export enum Kind { 21 | Class = "class", 22 | Constructor = "constructor", 23 | Enum = "enum", 24 | EnumMember = "enum member", 25 | Interface = "interface", 26 | Method = "method", 27 | Parameter = "parameter", 28 | Signature = "signature", 29 | Property = "property", 30 | TypeAlias = "type alias", 31 | Accessor = "accessor", 32 | } 33 | 34 | /** Compiler flags about this member. */ 35 | export interface TsFlags { 36 | /** This flag supports an optional message, typically used to include a version number. */ 37 | isDeprecated?: boolean | string; 38 | isExported?: boolean; 39 | isExternal?: boolean; 40 | isOptional?: boolean; 41 | isPrivate?: boolean; 42 | isProtected?: boolean; 43 | isPublic?: boolean; 44 | isRest?: boolean; 45 | isStatic?: boolean; 46 | } 47 | 48 | /** Base type for all typescript documentation members. */ 49 | export interface TsDocBase { 50 | /** Type brand indicating kind of member; type guards will reveal further information about it. */ 51 | kind: K; 52 | 53 | /** Compiled documentation: `contents` field contains an array of markdown strings or `@tag value` objects. */ 54 | documentation?: Block; 55 | 56 | /** Original file name in which this member originated, relative to current working directory. */ 57 | fileName?: string; 58 | 59 | flags?: TsFlags; 60 | 61 | /** Name of this member in code, also used as its identifiers in the data store. */ 62 | name: string; 63 | 64 | /** 65 | * Absolute URL pointing to source file in repository, including line number. 66 | * If `gitBranch` option is provided to the `TypescriptPlugin`, the URL will reference that branch. 67 | * Otherwise, it will reference the current commit hash. 68 | * @see ITypescriptPluginOptions.gitBranch 69 | */ 70 | sourceUrl?: string; 71 | } 72 | 73 | /** 74 | * Common type for a callable member, something that can be invoked. 75 | * @see TsConstructor 76 | * @see TsMethod 77 | */ 78 | export interface TsCallable { 79 | /** Type name from which this method was inherited. Typically takes the form `Interface.member`. */ 80 | inheritedFrom?: string; 81 | /** A method has at least one signature, which describes the parameters and return type and contains documentation. */ 82 | signatures: TsSignature[]; 83 | } 84 | 85 | /** Re-usable interface for Typescript members that support a notion of "default value." */ 86 | export interface TsDefaultValue { 87 | /** The default value of this property, from an initializer or an `@default` tag. */ 88 | defaultValue?: string; 89 | } 90 | 91 | /** Re-usable interface for Typescript members that look like objects. */ 92 | export interface TsObjectDefinition { 93 | /** List of type strings that this definition `extends`. */ 94 | extends?: string[]; 95 | /** List of type names that this definition `implements`. */ 96 | implements?: string[]; 97 | /** Index signature for this object, if declared. */ 98 | indexSignature?: TsSignature; 99 | /** Property members of this definition. */ 100 | properties: TsProperty[]; 101 | /** Method members of this definiton. */ 102 | methods: TsMethod[]; 103 | } 104 | 105 | /** 106 | * Documentation for a class constructor. See `signatures` array for actual callable signatures and rendered docs. 107 | * @see TsClass 108 | */ 109 | export interface TsConstructor extends TsDocBase, TsCallable { 110 | kind: Kind.Constructor; 111 | } 112 | 113 | export interface TsAccessor extends TsDocBase { 114 | kind: Kind.Accessor; 115 | /** If a set signature is defined and documented for this accessor, this will contain its documentation. */ 116 | getDocumentation: Block | undefined; 117 | /** If a get signature is defined and documented for this accessor, this will contain its documentation. */ 118 | setDocumentation: Block | undefined; 119 | /** Type of the accessor. */ 120 | type: string; 121 | } 122 | 123 | /** Documentation for a method. See `signatures` array for actual callable signatures and rendered docs. */ 124 | export interface TsMethod extends TsDocBase, TsCallable { 125 | kind: Kind.Method; 126 | } 127 | 128 | /** 129 | * Documentation for a single signature, including parameters, return type, and full type string. 130 | * Signatures are used for methods and constructors on classes or interfaces, and for index signatures on objects. 131 | */ 132 | export interface TsSignature extends TsDocBase { 133 | kind: Kind.Signature; 134 | /** Signatures do not have flags of their own. Flags can be found on the parent and on each parameter. */ 135 | flags: undefined; 136 | /** Signature parameters, each with their own docs and data. */ 137 | parameters: TsParameter[]; 138 | /** Return type of the signature. */ 139 | returnType: string; 140 | /** Fully qualified type string describing this method, including parameters and return type. */ 141 | type: string; 142 | } 143 | 144 | /** Documentation for a single parameter to a signature. */ 145 | export interface TsParameter extends TsDocBase, TsDefaultValue { 146 | kind: Kind.Parameter; 147 | /** Fully qualified type string describing this parameter. */ 148 | type: string; 149 | /** Parameters do not have their own URL; see the containing signature. */ 150 | sourceUrl: undefined; 151 | } 152 | 153 | /** Documentation for a property of an object, which may have a default value. */ 154 | export interface TsProperty extends TsDocBase, TsDefaultValue { 155 | kind: Kind.Property; 156 | /** Type name from which this property was inherited. Typically takes the form `Interface.member`. */ 157 | inheritedFrom?: string; 158 | /** Type string describing this property. */ 159 | type: string; 160 | } 161 | 162 | /** Documentation for an `interface` definition. */ 163 | export interface TsInterface extends TsDocBase, TsObjectDefinition { 164 | kind: Kind.Interface; 165 | } 166 | 167 | /** Documentation for a `class` definition. */ 168 | export interface TsClass extends TsDocBase, TsObjectDefinition { 169 | kind: Kind.Class; 170 | /** Constructor signature of this class. Note the special name here, as `constructor` is a JavaScript keyword. */ 171 | constructorType: TsConstructor; 172 | accessors: TsAccessor[]; 173 | } 174 | /** A member of an `enum` definition. An enum member will have a `defaultValue` if it was declared with an initializer. */ 175 | export interface TsEnumMember extends TsDocBase, TsDefaultValue { 176 | kind: Kind.EnumMember; 177 | } 178 | 179 | /** Documentation for an `enum` definition. */ 180 | export interface TsEnum extends TsDocBase { 181 | kind: Kind.Enum; 182 | /** Enumeration members. */ 183 | members: TsEnumMember[]; 184 | } 185 | 186 | /** A type alias, defined using `export type {name} = {type}.` The `type` property will contain the full type alias as a string. */ 187 | export interface TsTypeAlias extends TsDocBase { 188 | kind: Kind.TypeAlias; 189 | /** Type string for which this member is an alias. */ 190 | type: string; 191 | } 192 | 193 | export type TsDocEntry = TsClass | TsInterface | TsEnum | TsMethod | TsTypeAlias; 194 | 195 | /** 196 | * The `TypescriptPlugin` exports a `typescript` key that contains a map of member name to 197 | * `class` or `interface` definition. 198 | * 199 | * Only classes and interfaces are provided at this root level, but each member contains full 200 | * information about its children, such as methods (and signatures and parameters) and properties. 201 | */ 202 | export interface TypescriptPluginData { 203 | typescript: { 204 | [name: string]: TsDocEntry; 205 | }; 206 | } 207 | 208 | function typeguard(kind: Kind) { 209 | return (data: any): data is T => data != null && (data as T).kind === kind; 210 | } 211 | 212 | export const isTsClass = typeguard(Kind.Class); 213 | export const isTsConstructor = typeguard(Kind.Constructor); 214 | export const isTsEnum = typeguard(Kind.Enum); 215 | export const isTsEnumMember = typeguard(Kind.EnumMember); 216 | export const isTsInterface = typeguard(Kind.Interface); 217 | export const isTsMethod = typeguard(Kind.Method); 218 | export const isTsParameter = typeguard(Kind.Parameter); 219 | export const isTsProperty = typeguard(Kind.Property); 220 | export const isTsSignature = typeguard(Kind.Signature); 221 | export const isTsTypeAlias = typeguard(Kind.TypeAlias); 222 | -------------------------------------------------------------------------------- /packages/compiler/src/plugins/typescript/typescriptPlugin.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import type { Compiler, File, Plugin, TsDocEntry, TypescriptPluginData } from "@documentalist/client"; 18 | import { readFileSync } from "fs"; 19 | import { dirname } from "path"; 20 | import { tsconfigResolverSync } from "tsconfig-resolver"; 21 | import { Application, LogLevel, TSConfigReader, TypeDocOptions, TypeDocReader } from "typedoc"; 22 | import * as ts from "typescript"; 23 | import { Visitor } from "./visitor"; 24 | 25 | export interface TypescriptPluginOptions { 26 | /** 27 | * List of entry point modules. 28 | * @default ["src/index.ts"] 29 | */ 30 | entryPoints?: TypeDocOptions["entryPoints"]; 31 | 32 | /** 33 | * Array of patterns (string or RegExp) to exclude members by name. 34 | * Strings will be converted to regular expressions through `string.match(pattern)`. 35 | * 36 | * Note that excluded members will still be parsed by the compiler, so they can be referenced 37 | * by other symbols, but they will not appear in the output data. 38 | */ 39 | excludeNames?: Array; 40 | 41 | /** 42 | * Array of patterns (string or RegExp) to exclude members based on file path. 43 | * See `excludeNames` above for usage notes. 44 | */ 45 | excludePaths?: Array; 46 | 47 | /** 48 | * Specify the branch name to use when generating source file URLs. 49 | * If omitted, the current commit hash will be used. 50 | * @see ITsDocBase.url 51 | */ 52 | gitBranch?: string; 53 | 54 | /** 55 | * Enable parsing of `.d.ts` files. 56 | * @default false 57 | */ 58 | includeDeclarations?: boolean; 59 | 60 | /** 61 | * Whether files in `node_modules` should be included in the TypeScript 62 | * compilation context. This is disabled by default because it typically 63 | * results in an explosion of data size due to including all types from _all 64 | * installed packages_, the vast majority of which are not useful for 65 | * documenting your own APIs. 66 | * 67 | * Enable at your own risk, and consider using the `excludeNames` and 68 | * `excludePaths` options above to filter the output data. 69 | * @default false 70 | */ 71 | includeNodeModules?: boolean; 72 | 73 | /** 74 | * Whether `private` fields should be included in the data. 75 | * This is disabled by default as `private` fields typically do not need to be publicly documented. 76 | * @default false 77 | */ 78 | includePrivateMembers?: boolean; 79 | 80 | /** Path to tsconfig file. */ 81 | tsconfigPath?: string; 82 | 83 | /** 84 | * If enabled, logs messages and compiler errors to the console. 85 | * Note that compiler errors are ignored by Typedoc so they do not affect docs generation. 86 | * @default false 87 | */ 88 | verbose?: boolean; 89 | } 90 | 91 | export class TypescriptPlugin implements Plugin { 92 | private typedocOptions: Partial; 93 | 94 | /* 95 | * Maps of tsconfig.json paths to their TS programs and TypeDoc apps, respectively. 96 | * 97 | * These are necesary to support compilation of a list of files which may belong to separate TypeScript projects, 98 | * a situation which occurs frequently in a monorepo. 99 | */ 100 | private tsPrograms: Map = new Map(); 101 | private typedocApps: Map = new Map(); 102 | 103 | public constructor(private options: TypescriptPluginOptions = {}) { 104 | const { 105 | entryPoints = ["src/index.ts"], 106 | includeDeclarations = false, 107 | includeNodeModules = false, 108 | includePrivateMembers = false, 109 | verbose = false, 110 | } = options; 111 | 112 | this.typedocOptions = { 113 | commentStyle: "jsdoc", 114 | entryPointStrategy: "expand", 115 | entryPoints, 116 | exclude: [ 117 | includeNodeModules ? undefined : "**/node_modules/**", 118 | includeDeclarations ? undefined : "**/*.d.ts", 119 | ].filter(Boolean) as string[], 120 | excludePrivate: !includePrivateMembers, 121 | gitRevision: options.gitBranch, 122 | logLevel: verbose ? LogLevel.Verbose : LogLevel.Error, 123 | skipErrorChecking: false, 124 | }; 125 | } 126 | 127 | private async initializeTypedocAppAndTsProgram(tsconfig: string, entryPoints: string[]) { 128 | const options = { 129 | ...this.typedocOptions, 130 | entryPoints, 131 | tsconfig, 132 | }; 133 | const app = await Application.bootstrapWithPlugins(options, [new TypeDocReader(), new TSConfigReader()]); 134 | 135 | this.typedocApps.set(tsconfig, app); 136 | 137 | const { config } = ts.readConfigFile(tsconfig, path => readFileSync(path, { encoding: "utf-8" })); 138 | const program = ts.createProgram(entryPoints, config); 139 | this.tsPrograms.set(tsconfig, program); 140 | 141 | return app; 142 | } 143 | 144 | public async compile(files: File[], compiler: Compiler): Promise { 145 | // List of existing projects which contain some of the files to compile 146 | const existingProjectsToCompile: string[] = []; 147 | 148 | // Map of (tsconfig path -> list of files to compile) 149 | const newProjectsToCreate: Record = {}; 150 | 151 | for (const file of files) { 152 | let hasExistingProject = false; 153 | 154 | // attempt to load an existing project which contains this file 155 | for (const [tsconfigPath, program] of this.tsPrograms.entries()) { 156 | if (program.getRootFileNames().includes(file.path)) { 157 | existingProjectsToCompile.push(tsconfigPath); 158 | hasExistingProject = true; 159 | } 160 | } 161 | 162 | // if we don't have one, keep track of it in the new projects we must create 163 | if (!hasExistingProject) { 164 | const tsconfigPath = this.resolveClosestTsconfig(file); 165 | if (tsconfigPath !== undefined) { 166 | if (newProjectsToCreate[tsconfigPath] !== undefined) { 167 | newProjectsToCreate[tsconfigPath].push(file.path); 168 | } else { 169 | newProjectsToCreate[tsconfigPath] = [file.path]; 170 | } 171 | } 172 | } 173 | } 174 | 175 | const output: Record = {}; 176 | 177 | for (const projectPath of existingProjectsToCompile) { 178 | const app = this.typedocApps.get(projectPath); 179 | if (app === undefined) { 180 | throw new Error(`[Documentalist] could not find TypeDoc application for project at ${projectPath}`); 181 | } 182 | 183 | const docs = await this.getDocumentationOutput(compiler, app); 184 | for (const [key, value] of Object.entries(docs)) { 185 | output[key] = value; 186 | } 187 | } 188 | 189 | for (const [projectPath, files] of Object.entries(newProjectsToCreate)) { 190 | const app = await this.initializeTypedocAppAndTsProgram(projectPath, files); 191 | const docs = await this.getDocumentationOutput(compiler, app); 192 | for (const [key, value] of Object.entries(docs)) { 193 | output[key] = value; 194 | } 195 | } 196 | 197 | return { typescript: output }; 198 | } 199 | 200 | private async getDocumentationOutput(compiler: Compiler, app: Application) { 201 | const visitor = new Visitor(compiler, this.options); 202 | const project = await app.convert(); 203 | if (project === undefined) { 204 | throw new Error( 205 | `[Documentalist] unable to generate typescript documentation for project at ${app.options.getValue( 206 | "tsconfig", 207 | )}`, 208 | ); 209 | } 210 | 211 | return compiler.objectify(visitor.visitProject(project), i => i.name); 212 | } 213 | 214 | private resolveClosestTsconfig(file: File) { 215 | const { path, reason } = tsconfigResolverSync({ cwd: dirname(file.path) }); 216 | 217 | switch (reason) { 218 | case "invalid-config": 219 | console.error( 220 | `[Documentalist] invalid tsconfig resolved for ${file.path}, skipping documentation of this file`, 221 | ); 222 | return undefined; 223 | case "not-found": 224 | console.error( 225 | `[Documentalist] unable to find any relevant tsconfig for ${file.path}, skipping documentation of this file`, 226 | ); 227 | return undefined; 228 | default: 229 | return path; 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /packages/client/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /packages/compiler/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /packages/compiler/src/plugins/typescript/visitor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { 18 | Compiler, 19 | Kind, 20 | TsAccessor, 21 | TsClass, 22 | TsConstructor, 23 | TsDocBase, 24 | TsEnum, 25 | TsEnumMember, 26 | TsFlags, 27 | TsInterface, 28 | TsMethod, 29 | TsParameter, 30 | TsProperty, 31 | TsSignature, 32 | TsTypeAlias, 33 | } from "@documentalist/client"; 34 | import { relative } from "path"; 35 | import { 36 | Comment, 37 | DeclarationReflection, 38 | ParameterReflection, 39 | ProjectReflection, 40 | Reflection, 41 | ReflectionKind, 42 | SignatureReflection, 43 | } from "typedoc"; 44 | import { TypescriptPluginOptions } from "./typescriptPlugin"; 45 | import { resolveSignature, resolveTypeString } from "./typestring"; 46 | 47 | export class Visitor { 48 | public constructor( 49 | private compiler: Compiler, 50 | private options: TypescriptPluginOptions, 51 | ) {} 52 | 53 | public visitProject(project: ProjectReflection) { 54 | const { excludePaths = [] } = this.options; 55 | // get top-level members of typedoc project 56 | return [ 57 | ...this.visitChildren(project.getReflectionsByKind(ReflectionKind.Class), this.visitClass), 58 | ...this.visitChildren(project.getReflectionsByKind(ReflectionKind.Enum), this.visitEnum), 59 | ...this.visitChildren(project.getReflectionsByKind(ReflectionKind.Function), this.visitMethod), 60 | ...this.visitChildren(project.getReflectionsByKind(ReflectionKind.Interface), this.visitInterface), 61 | ...this.visitChildren(project.getReflectionsByKind(ReflectionKind.TypeAlias), def => ({ 62 | ...this.makeDocEntry(def, Kind.TypeAlias), 63 | type: resolveTypeString(def.type), 64 | })), 65 | ].filter( 66 | // remove members excluded by path option 67 | ref => isNotExcluded(excludePaths, ref.fileName), 68 | ); 69 | } 70 | 71 | private makeDocEntry(ref: Reflection, kind: K): TsDocBase { 72 | let comment = ref.comment; 73 | 74 | if (comment === undefined && ref.isDeclaration()) { 75 | // special case for interface properties which have function signatures - we need to go one level deeper 76 | // to access the comment 77 | ref.type?.visit({ 78 | reflection: reflectionType => { 79 | if (reflectionType.declaration.signatures !== undefined) { 80 | comment = reflectionType.declaration.signatures[0].comment; 81 | } 82 | }, 83 | }); 84 | } 85 | 86 | return { 87 | documentation: this.renderComment(comment), 88 | fileName: getSourceFileName(ref), 89 | flags: getFlags(ref), 90 | kind, 91 | name: ref.name, 92 | sourceUrl: getSourceUrl(ref), 93 | }; 94 | } 95 | 96 | private visitClass = (def: DeclarationReflection): TsClass => ({ 97 | ...this.visitInterface(def), 98 | accessors: this.visitChildren(def.getChildrenByKind(ReflectionKind.Accessor), this.visitAccessor), 99 | constructorType: this.visitChildren( 100 | def.getChildrenByKind(ReflectionKind.Constructor), 101 | this.visitConstructor, 102 | )[0], 103 | kind: Kind.Class, 104 | }); 105 | 106 | private visitInterface = (def: DeclarationReflection): TsInterface => ({ 107 | ...this.makeDocEntry(def, Kind.Interface), 108 | extends: def.extendedTypes?.map(resolveTypeString), 109 | implements: def.implementedTypes?.map(resolveTypeString), 110 | indexSignature: def.indexSignature && this.visTsignature(def.indexSignature), 111 | methods: this.visitChildren(def.getChildrenByKind(ReflectionKind.Method), this.visitMethod, sortStaticFirst), 112 | properties: this.visitChildren( 113 | def.getChildrenByKind(ReflectionKind.Property), 114 | this.visitProperty, 115 | sortStaticFirst, 116 | ), 117 | }); 118 | 119 | private visitConstructor = (def: DeclarationReflection): TsConstructor => ({ 120 | ...this.visitMethod(def), 121 | kind: Kind.Constructor, 122 | }); 123 | 124 | private visitEnum = (def: DeclarationReflection): TsEnum => ({ 125 | ...this.makeDocEntry(def, Kind.Enum), 126 | members: this.visitChildren(def.getChildrenByKind(ReflectionKind.EnumMember), m => ({ 127 | ...this.makeDocEntry(m, Kind.EnumMember), 128 | defaultValue: getDefaultValue(m), 129 | })), 130 | }); 131 | 132 | private visitProperty = (def: DeclarationReflection): TsProperty => ({ 133 | ...this.makeDocEntry(def, Kind.Property), 134 | defaultValue: getDefaultValue(def), 135 | inheritedFrom: def.inheritedFrom && resolveTypeString(def.inheritedFrom), 136 | type: resolveTypeString(def.type), 137 | }); 138 | 139 | private visitMethod = (def: DeclarationReflection): TsMethod => ({ 140 | ...this.makeDocEntry(def, Kind.Method), 141 | inheritedFrom: def.inheritedFrom && resolveTypeString(def.inheritedFrom), 142 | signatures: def.signatures !== undefined ? def.signatures.map(sig => this.visTsignature(sig)) : [], 143 | }); 144 | 145 | private visTsignature = (sig: SignatureReflection): TsSignature => ({ 146 | ...this.makeDocEntry(sig, Kind.Signature), 147 | flags: undefined, 148 | parameters: (sig.parameters || []).map(param => this.visitParameter(param)), 149 | returnType: resolveTypeString(sig.type), 150 | type: resolveSignature(sig), 151 | }); 152 | 153 | private visitParameter = (param: ParameterReflection): TsParameter => ({ 154 | ...this.makeDocEntry(param, Kind.Parameter), 155 | defaultValue: getDefaultValue(param), 156 | sourceUrl: undefined, 157 | type: resolveTypeString(param.type), 158 | }); 159 | 160 | private visitAccessor = (param: DeclarationReflection): TsAccessor => { 161 | let type: string; 162 | let getDocumentation; 163 | let setDocumentation; 164 | 165 | if (param.getSignature) { 166 | type = resolveTypeString(param.getSignature.type); 167 | } else if (param.setSignature?.parameters && param.setSignature?.parameters[0] !== undefined) { 168 | type = resolveTypeString(param.setSignature.parameters[0].type); 169 | } else { 170 | throw Error("Accessor did neither define get nor set signature."); 171 | } 172 | 173 | if (param.getSignature) { 174 | getDocumentation = this.renderComment(param.getSignature.comment); 175 | } 176 | if (param.setSignature) { 177 | setDocumentation = this.renderComment(param.setSignature.comment); 178 | } 179 | 180 | return { 181 | ...this.makeDocEntry(param, Kind.Accessor), 182 | getDocumentation, 183 | setDocumentation, 184 | type, 185 | }; 186 | }; 187 | 188 | /** VisTs each child that passes the filter condition (based on options). */ 189 | private visitChildren( 190 | children: Reflection[], 191 | visitor: (def: DeclarationReflection) => T, 192 | comparator?: (a: T, b: T) => number, 193 | ): T[] { 194 | const { excludeNames = [], excludePaths = [] } = this.options; 195 | return children 196 | .map(visitor) 197 | .filter(doc => isNotExcluded(excludeNames, doc.name) && isNotExcluded(excludePaths, doc.fileName)) 198 | .sort(comparator); 199 | } 200 | 201 | /** 202 | * Converts a typedoc comment object to a rendered `Block`. 203 | */ 204 | private renderComment(comment: Comment | undefined) { 205 | if (comment === undefined) { 206 | return; 207 | } 208 | 209 | let documentation = ""; 210 | documentation += comment.summary.map(part => part.text).join("\n"); 211 | 212 | const blockTags = comment.blockTags.filter(tag => tag.tag !== "@default" && tag.tag !== "@deprecated"); 213 | if (blockTags.length > 0) { 214 | documentation += "\n\n"; 215 | documentation += blockTags.map(tag => `${tag.tag} ${tag.content}`).join("\n"); 216 | } 217 | 218 | return this.compiler.renderBlock(documentation); 219 | } 220 | } 221 | 222 | function getCommentTagValue(comment: Comment | undefined, tagName: string) { 223 | const maybeTag = comment?.getTag(`@${tagName}`); 224 | return maybeTag?.content.map(part => part.text.trim()).join("\n"); 225 | } 226 | 227 | function getDefaultValue(ref: ParameterReflection | DeclarationReflection): string | undefined { 228 | // N.B. TypeDoc no longer sets defaultValue for enum members as of v0.23, see https://typedoc.org/guides/changelog/#v0.23.0-(2022-06-26) 229 | // Also, we typically expect enum member values to only have literal types, so we can just use the type value. 230 | if (ref.kind === ReflectionKind.EnumMember && ref.type?.type === "literal") { 231 | return ref.type?.value?.toString(); 232 | } 233 | 234 | return ref.defaultValue ?? getCommentTagValue(ref.comment, "default"); 235 | } 236 | 237 | function getSourceFileName(reflection: Reflection): string | undefined { 238 | if (reflection.isDeclaration() || isSignatureReflection(reflection)) { 239 | if (reflection.sources !== undefined) { 240 | const { fullFileName } = reflection.sources[0]; 241 | // fullFileName relative to cwd, so it can be saved in a snapshot (machine-independent) 242 | return fullFileName && relative(process.cwd(), fullFileName); 243 | } 244 | } 245 | return undefined; 246 | } 247 | 248 | function isSignatureReflection(reflection: Reflection): reflection is SignatureReflection { 249 | return reflection.variant === "signature"; 250 | } 251 | 252 | function getSourceUrl(reflection: Reflection): string | undefined { 253 | if (reflection.isDeclaration() || isSignatureReflection(reflection)) { 254 | if (reflection.sources !== undefined) { 255 | return reflection.sources[0]?.url; 256 | } 257 | } 258 | return undefined; 259 | } 260 | 261 | function getFlags(ref: Reflection): TsFlags | undefined { 262 | if (ref === undefined || ref.flags === undefined) { 263 | return undefined; 264 | } 265 | const isDeprecated = getIsDeprecated(ref); 266 | const { isExternal, isOptional, isPrivate, isProtected, isPublic, isRest, isStatic } = ref.flags; 267 | return { 268 | isDeprecated, 269 | isExternal, 270 | isOptional, 271 | isPrivate, 272 | isProtected, 273 | isPublic, 274 | isRest, 275 | isStatic, 276 | }; 277 | } 278 | 279 | function getIsDeprecated(ref: Reflection) { 280 | const deprecatedTagValue = getCommentTagValue(ref.comment, "deprecated"); 281 | const deprecatedModifier = ref.comment?.hasModifier("@deprecated"); 282 | return deprecatedModifier || deprecatedTagValue !== undefined; 283 | } 284 | 285 | /** Returns true if value does not match all patterns. */ 286 | function isNotExcluded(patterns: Array, value?: string) { 287 | return value === undefined || patterns.every(p => value.match(p) == null); 288 | } 289 | 290 | /** Sorts static members (`flags.isStatic`) before non-static members. */ 291 | function sortStaticFirst({ flags: aFlags = {} }: T, { flags: bFlags = {} }: T) { 292 | if (aFlags.isStatic && bFlags.isStatic) { 293 | return 0; 294 | } else if (aFlags.isStatic) { 295 | return -1; 296 | } else if (bFlags.isStatic) { 297 | return 1; 298 | } 299 | return 0; 300 | } 301 | --------------------------------------------------------------------------------