diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 607c82f..60787aa 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,3 +1,3 @@ -issuehunt: RebeccaStevens/typedoc-plugin-require-tags +issuehunt: RebeccaStevens/typedoc-plugin-custom-validation ko_fi: rebeccastevens -custom: https://github.com/RebeccaStevens/typedoc-plugin-require-tags/blob/main/DONATIONS.md +custom: https://github.com/RebeccaStevens/typedoc-plugin-custom-validation/blob/main/DONATIONS.md diff --git a/.github/workflows/build-node.yml b/.github/workflows/build-node.yml index f7d44ee..d699ad3 100644 --- a/.github/workflows/build-node.yml +++ b/.github/workflows/build-node.yml @@ -11,4 +11,4 @@ jobs: steps: - uses: actions/checkout@v3 - uses: ./.github/actions/prepare - - run: pnpm run build-node + - run: pnpm run build:node diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index af4a8c4..edded09 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,8 +49,7 @@ jobs: run: pnpm run build - name: Release - run: echo "Do Release Here" - # run: pnpm run semantic-release - # env: - # GITHUB_TOKEN: ${{ github.token }} - # NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: pnpm run semantic-release + env: + GITHUB_TOKEN: ${{ github.token }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index 501cab1..63cb2a5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ node_modules/ /coverage/ /dist/ +/docs/ *.log diff --git a/README.md b/README.md index 58ca137..2d83d24 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@
-# typedoc-plugin-require-tags +# typedoc-plugin-custom-validation -[![npm version](https://img.shields.io/npm/v/typedoc-plugin-require-tags.svg)](https://www.npmjs.com/package/typedoc-plugin-require-tags) -[![CI](https://github.com/RebeccaStevens/typedoc-plugin-require-tags/actions/workflows/release.yml/badge.svg)](https://github.com/RebeccaStevens/typedoc-plugin-require-tags/actions/workflows/release.yml) -[![Coverage Status](https://codecov.io/gh/RebeccaStevens/typedoc-plugin-require-tags/branch/main/graph/badge.svg?token=MVpR1oAbIT)](https://codecov.io/gh/RebeccaStevens/typedoc-plugin-require-tags)\ +[![npm version](https://img.shields.io/npm/v/typedoc-plugin-custom-validation.svg)](https://www.npmjs.com/package/typedoc-plugin-custom-validation) +[![CI](https://github.com/RebeccaStevens/typedoc-plugin-custom-validation/actions/workflows/release.yml/badge.svg)](https://github.com/RebeccaStevens/typedoc-plugin-custom-validation/actions/workflows/release.yml) +[![Coverage Status](https://codecov.io/gh/RebeccaStevens/typedoc-plugin-custom-validation/branch/main/graph/badge.svg?token=MVpR1oAbIT)](https://codecov.io/gh/RebeccaStevens/typedoc-plugin-custom-validation)\ [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) -[![GitHub Discussions](https://img.shields.io/github/discussions/RebeccaStevens/typedoc-plugin-require-tags?style=flat-square)](https://github.com/RebeccaStevens/typedoc-plugin-require-tags/discussions) -[![BSD 3 Clause license](https://img.shields.io/github/license/RebeccaStevens/typedoc-plugin-require-tags.svg?style=flat-square)](https://opensource.org/licenses/BSD-3-Clause) +[![GitHub Discussions](https://img.shields.io/github/discussions/RebeccaStevens/typedoc-plugin-custom-validation?style=flat-square)](https://github.com/RebeccaStevens/typedoc-plugin-custom-validation/discussions) +[![BSD 3 Clause license](https://img.shields.io/github/license/RebeccaStevens/typedoc-plugin-custom-validation.svg?style=flat-square)](https://opensource.org/licenses/BSD-3-Clause) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg?style=flat-square)](https://commitizen.github.io/cz-cli/) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=flat-square)](https://github.com/semantic-release/semantic-release) @@ -21,11 +21,57 @@ ```sh # Install with npm -npm install -D typedoc-plugin-require-tags +npm install -D typedoc-plugin-custom-validation # Install with pnpm -pnpm add -D typedoc-plugin-require-tags +pnpm add -D typedoc-plugin-custom-validation # Install with yarn -yarn add -D typedoc-plugin-require-tags +yarn add -D typedoc-plugin-custom-validation +``` + +## Usage + +All options are configured in the `customValidation` option. + +### `byKind` + +This option is for specifying requirements for each kind of node. + +Example: Require all functions to have a summary and have an `@example` tag. + +```json +{ + "plugin": ["typedoc-plugin-custom-validation"], + "customValidation": { + "byKind": [ + { + "kinds": "Function", + "summary": true, + "tags": ["example"] + } + ] + } +} +``` + +### My Tags Don't Exists? + +Due to the way typedoc works, some tags may be move to other nodes than the one they were defined on. + +For example, `@param` tags are removed from the `Function` node they are defined on and its content is put onto the corresponding `Parameter` node. +You can require parameters to be documented with: + +```json +{ + "plugin": ["typedoc-plugin-custom-validation"], + "customValidation": { + "byKind": [ + { + "kinds": "Parameter", + "summary": true + } + ] + } +} ``` diff --git a/package.json b/package.json index ee7b899..930512b 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,18 @@ { - "name": "typedoc-plugin-require-tags", + "name": "typedoc-plugin-custom-validation", "version": "0.0.0-development", "private": true, "description": "", - "keywords": [], - "homepage": "https://github.com/RebeccaStevens/typedoc-plugin-require-tags#readme", + "keywords": [ + "typedoc-plugin" + ], + "homepage": "https://github.com/RebeccaStevens/typedoc-plugin-custom-validation#readme", "bugs": { - "url": "https://github.com/RebeccaStevens/typedoc-plugin-require-tags/issues" + "url": "https://github.com/RebeccaStevens/typedoc-plugin-custom-validation/issues" }, "repository": { "type": "git", - "url": "git+https://github.com/RebeccaStevens/typedoc-plugin-require-tags" + "url": "git+https://github.com/RebeccaStevens/typedoc-plugin-custom-validation" }, "license": "BSD-3-Clause", "author": { @@ -98,7 +100,11 @@ "rollup-plugin-dts": "5.2.0", "semantic-release": "20.1.0", "ts-node": "10.9.1", + "typedoc": "0.23.26", "typescript": "4.9.5" }, + "peerDependencies": { + "typedoc": "^0.23.26" + }, "packageManager": "pnpm@7.27.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3b42d7a..a5542d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,6 +48,7 @@ specifiers: rollup-plugin-dts: 5.2.0 semantic-release: 20.1.0 ts-node: 10.9.1 + typedoc: 0.23.26 typescript: 4.9.5 devDependencies: @@ -98,6 +99,7 @@ devDependencies: rollup-plugin-dts: 5.2.0_vi3xdhr63abcxdtwtptol35g5u semantic-release: 20.1.0 ts-node: 10.9.1_5lyanyhwyggrikgtn7dsw3wuei + typedoc: 0.23.26_typescript@4.9.5 typescript: 4.9.5 packages: @@ -1585,6 +1587,10 @@ packages: engines: {node: '>=12'} dev: true + /ansi-sequence-parser/1.1.0: + resolution: {integrity: sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ==} + dev: true + /ansi-styles/3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -4787,6 +4793,10 @@ packages: engines: {node: '>=12'} dev: true + /lunr/2.3.9: + resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + dev: true + /magic-string/0.16.0: resolution: {integrity: sha512-c4BEos3y6G2qO0B9X7K0FVLOPT9uGrjYwYRLFmDqyl5YMboUviyecnXWp94fJTSMwPw2/sf+CEYt5AGpmklkkQ==} dependencies: @@ -5030,6 +5040,13 @@ packages: brace-expansion: 2.0.1 dev: true + /minimatch/7.4.2: + resolution: {integrity: sha512-xy4q7wou3vUoC9k1xGTXc+awNdGaGVHtFUaey8tiX4H1QRc04DZ/rmDFwNm2EBsuYEhAZ6SgMmYf3InGY6OauA==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + /minimist-options/4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} engines: {node: '>= 6'} @@ -6174,6 +6191,15 @@ packages: resolution: {integrity: sha512-lT297f1WLAdq0A4O+AknIFRP6kkiI3s8C913eJ0XqBxJbZPGWUNkRQk2u8zk4bEAjUJ5i+fSLwB6z1HzeT+DEg==} dev: true + /shiki/0.14.1: + resolution: {integrity: sha512-+Jz4nBkCBe0mEDqo1eKRcCdjRtrCjozmcbTUjbPTX7OOJfEbTZzlUWlZtGe3Gb5oV1/jnojhG//YZc3rs9zSEw==} + dependencies: + ansi-sequence-parser: 1.1.0 + jsonc-parser: 3.2.0 + vscode-oniguruma: 1.7.0 + vscode-textmate: 8.0.0 + dev: true + /side-channel/1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} dependencies: @@ -6745,6 +6771,20 @@ packages: is-typedarray: 1.0.0 dev: true + /typedoc/0.23.26_typescript@4.9.5: + resolution: {integrity: sha512-5m4KwR5tOLnk0OtMaRn9IdbeRM32uPemN9kur7YK9wFqx8U0CYrvO9aVq6ysdZSV1c824BTm+BuQl2Ze/k1HtA==} + engines: {node: '>= 14.14'} + hasBin: true + peerDependencies: + typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x + dependencies: + lunr: 2.3.9 + marked: 4.2.12 + minimatch: 7.4.2 + shiki: 0.14.1 + typescript: 4.9.5 + dev: true + /typescript/4.9.5: resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} engines: {node: '>=4.2.0'} @@ -6842,6 +6882,14 @@ packages: resolution: {integrity: sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==} dev: true + /vscode-oniguruma/1.7.0: + resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} + dev: true + + /vscode-textmate/8.0.0: + resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==} + dev: true + /vscode-uri/3.0.7: resolution: {integrity: sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==} dev: true diff --git a/src/index.ts b/src/index.ts index d4a9007..fee229c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,119 @@ -/* eslint-disable eslint-comments/disable-enable-pair */ -/* eslint-disable unicorn/no-empty-file */ +import type { ProjectReflection, Reflection } from "typedoc"; +import { Application, ParameterType, ReflectionKind } from "typedoc"; -// Write me +export type CustomValidationOptions = { + byKind: ByKindEntry[]; +}; + +export type ByKindEntry = { + kinds: keyof typeof ReflectionKind | Array; + tags?: string | string[]; + summary?: boolean; +}; + +export function load(app: Readonly) { + app.options.addDeclaration({ + name: "customValidation", + help: "The configuration object of the require-tags plugin.", + type: ParameterType.Object, + }); + + app.on( + Application.EVENT_VALIDATE_PROJECT, + (project: Readonly) => { + const customValidationOptions = app.options.getValue( + "customValidation" + ) as CustomValidationOptions; + + let m_kinds = customValidationOptions.byKind + .flatMap((by) => by.kinds) + .map((kind) => ReflectionKind[kind]) + .reduce((p, c) => p | c); + + const reflectionKindReplacements: Array< + [oldKind: number, newKind: number] + > = [ + [ReflectionKind.FunctionOrMethod, ReflectionKind.CallSignature], + [ReflectionKind.Constructor, ReflectionKind.ConstructorSignature], + [ + ReflectionKind.Accessor, + ReflectionKind.GetSignature | ReflectionKind.SetSignature, + ], + ]; + + for (const [oldKind, newKind] of reflectionKindReplacements) { + m_kinds = (m_kinds | newKind) & ~oldKind; + } + + type Requirements = { tags: string[]; summary: boolean }; + + const requirementsByKind = new Map( + customValidationOptions.byKind.flatMap(({ kinds, tags, summary }) => + (Array.isArray(kinds) ? kinds : [kinds]).map( + (kindString): [number, Requirements] => { + const kind = ReflectionKind[kindString]; + const realKind = + reflectionKindReplacements.find( + ([oldKind]) => (oldKind & kind) !== 0 + )?.[1] ?? kind; + + return [ + realKind, + { + tags: + tags === undefined + ? [] + : Array.isArray(tags) + ? tags + : [tags], + summary: summary ?? false, + }, + ]; + } + ) + ) + ); + + const reflections = project.getReflectionsByKind(m_kinds); + const seen = new Set(); + + for (const reflection of reflections) { + if (seen.has(reflection)) { + continue; + } + seen.add(reflection); + + if (!reflection.hasComment() || reflection.comment!.isEmpty()) { + app.logger.warn( + `${reflection.getFriendlyFullName()} does not have any documentation.` + ); + continue; + } + + const requirements = requirementsByKind.get(reflection.kind); + if (requirements !== undefined) { + if ( + requirements.summary && + reflection.comment!.summary.length === 0 + ) { + app.logger.warn( + `${reflection.getFriendlyFullName()} does not have a summary.` + ); + } + + for (const tagName of requirements.tags) { + const tag: `@${string}` = tagName.startsWith("@") + ? (tagName as `@${string}`) + : `@${tagName}`; + + if (reflection.comment!.getTags(tag).length === 0) { + app.logger.warn( + `${reflection.getFriendlyFullName()} does not have any ${tag} tags.` + ); + } + } + } + } + } + ); +}