From 73ff4b68fc9afcc318fc348ab2ce490a32d59410 Mon Sep 17 00:00:00 2001 From: chad Date: Tue, 13 Dec 2022 09:29:03 -0500 Subject: [PATCH] feat: Add support for linting typescript snippets in markdown (#1117) --- .gitignore | 1 + README.md | 9 +++--- md/github-actions.md | 1 + package.json | 2 ++ src/cmds/document-check.js | 49 +++++++++++++++++++++++++++++++ src/config/user.js | 8 +++++ src/document-check.js | 60 ++++++++++++++++++++++++++++++++++++++ src/index.js | 2 ++ src/types.ts | 23 ++++++++++++++- 9 files changed, 149 insertions(+), 6 deletions(-) create mode 100644 src/cmds/document-check.js create mode 100644 src/document-check.js diff --git a/.gitignore b/.gitignore index 32a2d46a8..53cf8c17e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ yarn.lock *.log .vscode .coverage +.DS_Store diff --git a/README.md b/README.md index c73c9b9d4..0b1260a5b 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ Commands: aegir build Builds a browser bundle and TS type declarations from the `src` folder. aegir check Check project aegir docs Generate documentation from TS type declarations. + aegir doc-check Verify TS code snippets in documentation. aegir lint Lint all project files aegir release Release your code onto the world aegir test-dependant [repo] Run the tests of an module that depends on this module to see if the current changes have caused a regression @@ -103,15 +104,13 @@ Aegir can be fully configured using a config file named `.aegir.js` or the packa ```js // file: .aegir.js - - /** @type {import('aegir').PartialOptions} */ module.exports = { tsRepo: true, release: { - build: false - } -} + build: false, + }, +}; ``` ```json diff --git a/md/github-actions.md b/md/github-actions.md index 36caef3ed..931a63a0f 100644 --- a/md/github-actions.md +++ b/md/github-actions.md @@ -19,6 +19,7 @@ jobs: - run: npx aegir lint - run: npx aegir build - run: npx aegir dep-check + - run: npx aegir doc-check - uses: ipfs/aegir/actions/bundle-size@master with: github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/package.json b/package.json index 30b46a032..24009e5bc 100644 --- a/package.json +++ b/package.json @@ -209,6 +209,7 @@ "test": "node src/index.js test", "docs": "node src/index.js docs", "dep-check": "node src/index.js dep-check", + "doc-check": "node src/index.js doc-check", "test:node": "node src/index.js test -t node --cov", "test:chrome": "node src/index.js test -t browser --cov", "test:chrome-webworker": "node src/index.js test -t webworker", @@ -305,6 +306,7 @@ "typedoc": "^0.23.21", "typedoc-plugin-mdn-links": "^2.0.0", "typescript": "^4.6.3", + "typescript-docs-verifier": "2.4.0", "uint8arrays": "^4.0.2", "undici": "^5.0.0", "update-notifier": "^6.0.2", diff --git a/src/cmds/document-check.js b/src/cmds/document-check.js new file mode 100644 index 000000000..09a5c4c62 --- /dev/null +++ b/src/cmds/document-check.js @@ -0,0 +1,49 @@ +/* eslint-disable no-console */ + +import { loadUserConfig } from '../config/user.js' +import docCheck from '../document-check.js' + +/** + * @typedef {import("yargs").Argv} Argv + * @typedef {import("yargs").Arguments} Arguments + * @typedef {import("yargs").CommandModule} CommandModule + */ + +const EPILOG = ` +Docs are verified using typescript-docs-verifier (https://github.com/bbc/typescript-docs-verifier#typescript-docs-verifier) +For more info read: https://github.com/bbc/typescript-docs-verifier#how-it-works +` + +/** @type {CommandModule} */ +export default { + command: 'document-check [input...]', + aliases: ['doc-check'], + describe: 'Run `document-check` cli with aegir defaults.', + /** + * @param {Argv} yargs + */ + builder: async (yargs) => { + const userConfig = await loadUserConfig() + + return yargs + .epilog(EPILOG) + .options({ + inputFiles: { + array: true, + describe: 'The files to verify, defaults to `README.md`', + default: userConfig.documentCheck.inputFiles + }, + tsConfigPath: { + type: 'string', + describe: 'The path to the `tsconfig.json`, defaults to the root.', + default: userConfig.documentCheck.tsConfigPath + } + }) + }, + /** + * @param {any} argv + */ + async handler (argv) { + await docCheck.run(argv) + } +} diff --git a/src/config/user.js b/src/config/user.js index 21febfb5a..24e1aaebd 100644 --- a/src/config/user.js +++ b/src/config/user.js @@ -68,6 +68,14 @@ const defaults = { email: 'aegir[bot]@users.noreply.github.com', directory: '.docs' }, + // document check cmd options + documentCheck: { + inputFiles: [ + '*.md', + 'src/*.md' + ], + tsConfigPath: '.' + }, // ts cmd options ts: { preset: undefined, diff --git a/src/document-check.js b/src/document-check.js new file mode 100644 index 000000000..3dd44851c --- /dev/null +++ b/src/document-check.js @@ -0,0 +1,60 @@ +/* eslint-disable no-console */ + +import Listr from 'listr' +import { hasTsconfig } from './utils.js' +import { globby } from 'globby' +import { compileSnippets } from 'typescript-docs-verifier' +/** + * @typedef {import("./types").GlobalOptions} GlobalOptions + * @typedef {import("./types").DocsVerifierOptions} DocsVerifierOptions + * @typedef {import("listr").ListrTaskWrapper} Task + */ + +const tasks = new Listr( + [ + { + title: 'typescript-doc-verify', + /** + * @param {GlobalOptions & DocsVerifierOptions} ctx + */ + enabled: ctx => hasTsconfig, + /** + * @param {GlobalOptions & DocsVerifierOptions} ctx + * @param {Task} task + */ + task: async (ctx, task) => { + let tsconfigPath = 'tsconfig.json' + let markdownFiles = ['README.md'] + + if (ctx.tsConfigPath) { + tsconfigPath = `${ctx.tsConfigPath}/tsconfig.json` + } + + if (ctx.inputFiles) { + markdownFiles = await globby(ctx.inputFiles) + } + + compileSnippets({ markdownFiles, project: tsconfigPath }) + .then((results) => { + results.forEach((result) => { + if (result.error) { + console.log(`Error compiling example code block ${result.index} in file ${result.file}`) + console.log(result.error.message) + console.log('Original code:') + console.log(result.snippet) + } + }) + }) + .catch((error) => { + console.error('Error compiling TypeScript snippets', error) + }) + } + + } + ], + { + renderer: 'verbose' + } +) + +export default tasks diff --git a/src/index.js b/src/index.js index cd22da686..7ffe0c4d5 100755 --- a/src/index.js +++ b/src/index.js @@ -17,6 +17,7 @@ import releaseCmd from './cmds/release.js' import testDependantCmd from './cmds/test-dependant.js' import testCmd from './cmds/test.js' import docsCmd from './cmds/docs.js' +import docVerifyCmd from './cmds/document-check.js' /** * @typedef {import('./types').BuildOptions} BuildOptions @@ -84,6 +85,7 @@ async function main () { res.command(cleanCmd) res.command(dependencyCheckCmd) res.command(docsCmd) + res.command(docVerifyCmd) res.command(lintPackageJsonCmd) res.command(lintCmd) res.command(releaseCmd) diff --git a/src/types.ts b/src/types.ts index ff92ec331..16fb8f8f6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -32,6 +32,10 @@ interface Options extends GlobalOptions { * Options for the `dependency-check` command */ dependencyCheck: DependencyCheckOptions + /** + * Options for the `document-check` command + */ + documentCheck: DocsVerifierOptions } /** @@ -70,6 +74,10 @@ interface PartialOptions { * Options for the `dependency-check` command */ dependencyCheck?: DependencyCheckOptions + /** + * Options for the `document-check` command + */ + documentCheck?: DocsVerifierOptions } interface GlobalOptions { @@ -152,6 +160,18 @@ interface DocsOptions { directory: string } +interface DocsVerifierOptions { + /** + * The Markdown files to be verified, defaults to `README.md` + */ + inputFiles?: string[] + + /** + * An alternative `.tsconfig.json` path to be used seperately from the default + */ + tsConfigPath?: string +} + interface LintOptions { /** * Automatically fix errors if possible. @@ -319,5 +339,6 @@ export type { LintOptions, TestOptions, ReleaseOptions, - DependencyCheckOptions + DependencyCheckOptions, + DocsVerifierOptions }