From ac5723de737e009a21dd7bc945031323c7dd1711 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Mon, 22 Apr 2024 17:07:34 +1000 Subject: [PATCH] chore: JSDoc checker (#4618) * chore: JSDoc checker * work * fix --- _tools/check_deprecation.ts | 2 +- _tools/check_docs.ts | 117 ++++++++++++++++++++++++++++++++++ bytes/concat.ts | 1 + bytes/copy.ts | 1 + bytes/index_of_needle.ts | 2 + bytes/last_index_of_needle.ts | 2 + bytes/repeat.ts | 1 - datetime/difference.ts | 1 + deno.json | 4 +- 9 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 _tools/check_docs.ts diff --git a/_tools/check_deprecation.ts b/_tools/check_deprecation.ts index 90ab142b4..c3c776953 100644 --- a/_tools/check_deprecation.ts +++ b/_tools/check_deprecation.ts @@ -8,7 +8,7 @@ * ``` */ -import { doc } from "deno_doc"; +import { doc } from "deno_doc/mod.ts"; import { walk } from "../fs/walk.ts"; import { toFileUrl } from "../path/to_file_url.ts"; diff --git a/_tools/check_docs.ts b/_tools/check_docs.ts new file mode 100644 index 000000000..ff11959c0 --- /dev/null +++ b/_tools/check_docs.ts @@ -0,0 +1,117 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +/** + * This script checks that all exported functions have JSDoc comments with + * `@param`, `@return`, and `@example` tags, according to the contributing + * guidelines. + * + * @see {@link https://github.com/denoland/deno_std/blob/main/.github/CONTRIBUTING.md#documentation} + * + * TODO(iuioiua): Add support for classes and methods. + */ +import { doc } from "deno_doc/mod.ts"; +import type { + DocNodeBase, + DocNodeFunction, + JsDocTag, +} from "deno_doc/types.d.ts"; + +const ENTRY_POINTS = [ + "../bytes/mod.ts", + "../datetime/mod.ts", +] as const; + +class ValidationError extends Error { + constructor(message: string, document: DocNodeBase) { + super(message, { + cause: `${document.location.filename}:${document.location.line}`, + }); + this.name = this.constructor.name; + } +} + +function assert( + condition: boolean, + message: string, + document: DocNodeBase, +): asserts condition { + if (!condition) { + throw new ValidationError(message, document); + } +} + +function isFunctionDoc(document: DocNodeBase): document is DocNodeFunction { + return document.kind === "function"; +} + +function isExported(document: DocNodeBase) { + return document.declarationKind === "export"; +} + +function assertHasTag(tags: JsDocTag[], kind: string, document: DocNodeBase) { + const tag = tags.find((tag) => tag.kind === kind); + assert(tag !== undefined, `Symbol must have a @${kind} tag`, document); + assert( + // @ts-ignore doc is defined + tag.doc !== undefined, + `@${kind} tag must have a description`, + document, + ); +} + +function assertHasParamTag( + tags: JsDocTag[], + param: string, + document: DocNodeBase, +) { + const tag = tags.find((tag) => tag.kind === "param" && tag.name === param); + assert( + tag !== undefined, + `Symbol must have a @param tag for ${param}`, + document, + ); + assert( + // @ts-ignore doc is defined + tag.doc !== undefined, + `@param tag for ${param} must have a description`, + document, + ); +} + +function assertFunctionDocs(document: DocNodeFunction) { + assert( + document.jsDoc !== undefined, + "Symbol must have a JSDoc block", + document, + ); + const { tags } = document.jsDoc; + assert(tags !== undefined, "JSDoc block must have tags", document); + for (const param of document.functionDef.params) { + if (param.kind === "identifier") { + assertHasParamTag(tags, param.name, document); + } + if (param.kind === "assign") { + // @ts-ignore Trust me + assertHasParamTag(tags, param.left.name, document); + } + } + assertHasTag(tags, "return", document); + assertHasTag(tags, "example", document); +} + +async function checkDocs(specifier: string) { + const docs = await doc(specifier); + docs.filter(isExported) + .forEach((document) => { + if (isFunctionDoc(document)) { + assertFunctionDocs(document); + } + }); +} + +const promises = []; +for (const entry of ENTRY_POINTS) { + const { href } = new URL(entry, import.meta.url); + promises.push(checkDocs(href)); +} +await Promise.all(promises); diff --git a/bytes/concat.ts b/bytes/concat.ts index c3b1112f8..a7ee786ef 100644 --- a/bytes/concat.ts +++ b/bytes/concat.ts @@ -5,6 +5,7 @@ * Concatenate an array of byte slices into a single slice. * * @param buffers Array of byte slices to concatenate. + * @returns Hello * * @example Basic usage * ```ts diff --git a/bytes/copy.ts b/bytes/copy.ts index 7dfaf8c40..9c15edb7b 100644 --- a/bytes/copy.ts +++ b/bytes/copy.ts @@ -12,6 +12,7 @@ * @param dst Destination array to copy to. * @param offset Offset in the destination array to start copying to. Defaults * to 0. + * @returns Number of bytes copied. * * @example Basic usage * ```ts diff --git a/bytes/index_of_needle.ts b/bytes/index_of_needle.ts index 2cbccbc1c..9a752a57b 100644 --- a/bytes/index_of_needle.ts +++ b/bytes/index_of_needle.ts @@ -14,6 +14,8 @@ * @param needle Needle array to check for. * @param start Start index in the source array to begin the search. Defaults to * 0. + * @returns Index of the first occurrence of the needle array in the source + * array, or -1 if it is not present. * * @example Basic usage * ```ts diff --git a/bytes/last_index_of_needle.ts b/bytes/last_index_of_needle.ts index 0b07bda48..fdd008d34 100644 --- a/bytes/last_index_of_needle.ts +++ b/bytes/last_index_of_needle.ts @@ -11,6 +11,8 @@ * @param needle Needle array to check for. * @param start Start index in the source array to begin the search. Defaults to * the end of the array. + * @returns Index of the last occurrence of the needle array in the source + * array, or -1 if it is not present. * * @example Basic usage * ```ts diff --git a/bytes/repeat.ts b/bytes/repeat.ts index 9fc166df5..bb7013442 100644 --- a/bytes/repeat.ts +++ b/bytes/repeat.ts @@ -10,7 +10,6 @@ import { copy } from "./copy.ts"; * @param count Number of times to repeat the source array. * @returns A new byte slice composed of `count` repetitions of the `source` * array. - * @throws {RangeError} If `count` is a negative or not an integer. * * @example Basic usage * ```ts diff --git a/datetime/difference.ts b/datetime/difference.ts index 2d2e5db22..2600cede7 100644 --- a/datetime/difference.ts +++ b/datetime/difference.ts @@ -45,6 +45,7 @@ function calculateMonthsDifference(from: Date, to: Date): number { * @param from Year to calculate difference from. * @param to Year to calculate difference to. * @param options Options such as units to calculate difference in. + * @returns The difference of the 2 given dates in various units. * * @example Basic usage * ```ts diff --git a/deno.json b/deno.json index c75f9133d..c5c125628 100644 --- a/deno.json +++ b/deno.json @@ -8,7 +8,7 @@ "imports": { "https://deno.land/std@$STD_VERSION/": "./", "@deno/graph": "jsr:@deno/graph@^0.70", - "deno_doc": "https://deno.land/x/deno_doc@0.123.1/mod.ts", + "deno_doc/": "https://deno.land/x/deno_doc@0.123.1/", "npm:/typescript": "npm:typescript@5.4.4", "automation/": "https://raw.githubusercontent.com/denoland/automation/0.10.0/" }, @@ -22,7 +22,7 @@ "lint:circular": "deno run --allow-env --allow-read --allow-net=deno.land,jsr.io ./_tools/check_circular_package_dependencies.ts", "lint:mod-exports": "deno run --allow-env --allow-read ./_tools/check_mod_exports.ts", "lint:tools-types": "deno check _tools/*.ts", - "lint:docs": "deno doc --lint bytes/mod.ts datetime/mod.ts url/mod.ts", + "lint:docs": "deno run -A _tools/check_docs.ts && deno doc --lint bytes/mod.ts datetime/mod.ts url/mod.ts", "lint": "deno lint && deno task fmt:licence-headers --check && deno task lint:deprecations && deno task lint:doc-imports && deno task lint:circular && deno task lint:tools-types && deno task lint:mod-exports && deno task lint:docs", "typos": "typos -c ./.github/workflows/typos.toml", "build:crypto": "deno task --cwd crypto/_wasm wasmbuild",