docs(assert,cli,data-structures,expect,fmt,front-matter,html,http,jsonc,semver,streams,text,toml,webgpu): add snippet checks in module, function and class docs to doc checker (#4855)

* chore: add snippet checks to module docs

* fix

* work

* tweak
This commit is contained in:
Asher Gomez 2024-05-31 12:01:46 +10:00 committed by GitHub
parent 3fbdd93691
commit 79d6a70729
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 196 additions and 109 deletions

View File

@ -15,6 +15,7 @@ import {
type DocNodeBase,
type DocNodeClass,
type DocNodeFunction,
type DocNodeModuleDoc,
type JsDoc,
type JsDocTagDocRequired,
type Location,
@ -139,17 +140,78 @@ function assertHasParamTag(
}
}
async function assertSnippetEvals(
snippet: string,
document: { jsDoc: JsDoc; location: Location },
) {
const command = new Deno.Command(Deno.execPath(), {
args: [
"eval",
"--ext=ts",
"--unstable-webgpu",
snippet,
],
stderr: "piped",
});
const timeoutId = setTimeout(() => {
console.warn(
`Snippet at ${document.location.filename}:${document.location.line} has been running for more than 10 seconds...`,
);
console.warn(snippet);
}, 10_000);
try {
const { success, stderr } = await command.output();
const error = new TextDecoder().decode(stderr);
assert(
success,
`Failed to execute snippet: \n${snippet}\n${error}`,
document,
);
} finally {
clearTimeout(timeoutId);
}
}
function assertSnippetsWork(
doc: string,
document: { jsDoc: JsDoc; location: Location },
required = true,
) {
const snippets = doc.match(TS_SNIPPET);
if (snippets === null) {
if (required) {
diagnostics.push(
new DocumentError(
"@example tag must have a TypeScript code snippet",
document,
),
);
return;
} else {
return;
}
}
for (let snippet of snippets) {
if (snippet.split(NEWLINE)[0]?.includes("no-eval")) continue;
// Trim the code block delimiters
snippet = snippet.split(NEWLINE).slice(1, -1).join(NEWLINE);
snippetPromises.push(assertSnippetEvals(snippet, document));
}
}
function assertHasExampleTag(
document: { jsDoc: JsDoc; location: Location },
) {
const tags = document.jsDoc.tags?.filter((tag) => tag.kind === "example");
const tags = document.jsDoc.tags?.filter((tag) =>
tag.kind === "example"
) as JsDocTagDocRequired[];
if (tags === undefined || tags.length === 0) {
diagnostics.push(
new DocumentError("Symbol must have an @example tag", document),
);
return;
}
for (const tag of (tags as JsDocTagDocRequired[])) {
for (const tag of tags) {
assert(
tag.doc !== undefined,
"@example tag must have a title and TypeScript code snippet",
@ -164,48 +226,7 @@ function assertHasExampleTag(
"@example tag must have a title",
document,
);
const snippets = tag.doc.match(TS_SNIPPET);
if (snippets === null) {
diagnostics.push(
new DocumentError(
"@example tag must have a TypeScript code snippet",
document,
),
);
continue;
}
for (let snippet of snippets) {
if (snippet.split(NEWLINE)[0]?.includes("no-eval")) continue;
// Trim the code block delimiters
snippet = snippet.split(NEWLINE).slice(1, -1).join(NEWLINE);
const command = new Deno.Command(Deno.execPath(), {
args: [
"eval",
"--ext=ts",
"--unstable-webgpu",
snippet,
],
stderr: "piped",
});
snippetPromises.push((async () => {
const timeoutId = setTimeout(() => {
console.warn("Snippet has been running for more than 10 seconds...");
console.warn(snippet);
}, 10_000);
try {
const { success, stderr } = await command.output();
assert(
success,
`Example code snippet failed to execute: \n${snippet}\n${
new TextDecoder().decode(stderr)
}`,
document,
);
} finally {
clearTimeout(timeoutId);
}
})());
}
assertSnippetsWork(tag.doc, document);
}
}
@ -244,6 +265,7 @@ function assertHasTypeParamTags(
function assertFunctionDocs(
document: DocNodeWithJsDoc<DocNodeFunction | ClassMethodDef>,
) {
assertSnippetsWork(document.jsDoc.doc!, document, false);
for (const param of document.functionDef.params) {
if (param.kind === "identifier") {
assertHasParamTag(document, param.name);
@ -273,6 +295,7 @@ function assertFunctionDocs(
* - Documentation on all properties, methods, and constructors.
*/
function assertClassDocs(document: DocNodeWithJsDoc<DocNodeClass>) {
assertSnippetsWork(document.jsDoc.doc!, document, false);
for (const typeParam of document.classDef.typeParams) {
assertHasTypeParamTags(document, typeParam.name);
}
@ -358,6 +381,14 @@ function assertConstructorDocs(
assertHasExampleTag(constructor);
}
/**
* Checks a module document for:
* - Code snippets that execute successfully.
*/
function assertModuleDoc(document: DocNodeWithJsDoc<DocNodeModuleDoc>) {
assertSnippetsWork(document.jsDoc.doc!, document);
}
function resolve(specifier: string, referrer: string): string {
if (specifier.startsWith("@std/") && specifier.split("/").length > 2) {
specifier = specifier.replace("@std/", "../").replaceAll("-", "_") + ".ts";
@ -371,6 +402,10 @@ async function checkDocs(specifier: string) {
if (d.jsDoc === undefined) continue; // this is caught by other checks
const document = d as DocNodeWithJsDoc<DocNode>;
switch (document.kind) {
case "moduleDoc": {
assertModuleDoc(document);
break;
}
case "function": {
assertFunctionDocs(document);
break;

View File

@ -8,7 +8,7 @@
* This module is browser compatible, but do not rely on good formatting of
* values for AssertionError messages in browsers.
*
* ```ts
* ```ts no-eval
* import { assert } from "@std/assert/assert";
*
* assert("I am truthy"); // Doesn't throw

View File

@ -4,11 +4,12 @@
* Tools for creating interactive command line tools.
*
* ```ts
* // $ deno run example.ts --foo --bar=baz ./quux.txt
* import { parseArgs } from "@std/cli/parse-args";
* import { assertEquals } from "@std/assert/assert-equals";
*
* const parsedArgs = parseArgs(Deno.args);
* parsedArgs; // { foo: true, bar: "baz", _: ["./quux.txt"] }
* // Same as running `deno run example.ts --foo --bar=baz ./quux.txt`
* const args = parseArgs(["--foo", "--bar=baz", "./quux.txt"]);
* assertEquals(args, { foo: true, bar: "baz", _: ["./quux.txt"] });
* ```
*
* @module

View File

@ -6,19 +6,20 @@
*
* ```ts
* import { BinarySearchTree } from "@std/data-structures";
* import { assertEquals } from "@std/assert/assert-equals";
*
* const values = [3, 10, 13, 4, 6, 7, 1, 14];
* const tree = new BinarySearchTree<number>();
* values.forEach((value) => tree.insert(value));
*
* [...tree]; // [ 1, 3, 4, 6, 7, 10, 13, 14 ]
* tree.min(); // 1
* tree.max(); // 14
* tree.find(42); // null
* tree.find(7); // 7
* tree.remove(42); // false
* tree.remove(7); // true
* [...tree]; // [ 1, 3, 4, 6, 10, 13, 14 ]
* assertEquals([...tree], [1, 3, 4, 6, 7, 10, 13, 14]);
* assertEquals(tree.min(), 1);
* assertEquals(tree.max(), 14);
* assertEquals(tree.find(42), null);
* assertEquals(tree.find(7), 7);
* assertEquals(tree.remove(42), false);
* assertEquals(tree.remove(7), true);
* assertEquals([...tree], [1, 3, 4, 6, 10, 13, 14]);
* ```
*
* @module

View File

@ -69,9 +69,9 @@
* - `expect.hasAssertions`
* - `expect.addSnapshotSerializer`
*
* This module is largely inspired by {@link https://github.com/allain/expect | x/expect} module by Allain Lalonde.
* This module is largely inspired by
* {@link https://github.com/allain/expect | x/expect} module by Allain Lalonde.
*
* @example
* ```ts
* import { expect } from "@std/expect";
*

View File

@ -11,7 +11,6 @@
* This module supports `NO_COLOR` environmental variable disabling any coloring
* if `NO_COLOR` is set.
*
* @example
* ```ts
* import {
* bgBlue,

View File

@ -4,6 +4,16 @@
* {@linkcode sprintf} and {@linkcode printf} for printing formatted strings to
* stdout.
*
* ```ts
* import { sprintf } from "@std/fmt/printf";
* import { assertEquals } from "@std/assert/assert-equals";
*
* assertEquals(sprintf("%d", 9), "9");
* assertEquals(sprintf("%o", 9), "11");
* assertEquals(sprintf("%f", 4), "4.000000");
* assertEquals(sprintf("%.3f", 0.9999), "1.000");
* ```
*
* This implementation is inspired by POSIX and Golang but does not port
* implementation code.
*

View File

@ -12,16 +12,17 @@
* ### JSON
*
* ```ts
* import { test } from "@std/front-matter/test";
* import { extract } from "@std/front-matter/json";
* import { test, extractJson } from "@std/front-matter";
* import { assertEquals } from "@std/assert/assert-equals";
*
* const str = "---json\n{\"and\": \"this\"}\n---\ndeno is awesome";
* const result = extract(str);
*
* test(str); // true
* result.frontMatter; // "{\"and\": \"this\"}"
* result.body; // "deno is awesome"
* result.attrs; // { and: "this" }
* assertEquals(test(str), true);
* assertEquals(extractJson(str), {
* frontMatter: "{\"and\": \"this\"}",
* body: "deno is awesome",
* attrs: { and: "this" }
* });
* ```
*
* {@linkcode extractJson | extract} and {@linkcode test} support the following
@ -44,16 +45,17 @@
* ### TOML
*
* ```ts
* import { test } from "@std/front-matter/test";
* import { extract } from "@std/front-matter/toml";
* import { test, extractToml } from "@std/front-matter";
* import { assertEquals } from "@std/assert/assert-equals";
*
* const str = "---toml\nmodule = 'front_matter'\n---\ndeno is awesome";
* const result = extract(str);
*
* test(str); // true
* result.frontMatter; // "module = 'front_matter'"
* result.body; // "deno is awesome"
* result.attrs; // { module: "front_matter" }
* assertEquals(test(str), true);
* assertEquals(extractToml(str), {
* frontMatter: "module = 'front_matter'",
* body: "deno is awesome",
* attrs: { module: "front_matter" }
* });
* ```
*
* {@linkcode extractToml | extract} and {@linkcode test} support the following
@ -82,16 +84,17 @@
* ### YAML
*
* ```ts
* import { test } from "@std/front-matter/test";
* import { extract } from "@std/front-matter/yaml";
* import { test, extractYaml } from "@std/front-matter";
* import { assertEquals } from "@std/assert/assert-equals";
*
* const str = "---yaml\nmodule: front_matter\n---\ndeno is awesome";
* const result = extract(str);
*
* test(str); // true
* result.frontMatter; // "module: front_matter"
* result.body; // "deno is awesome"
* result.attrs; // { module: "front_matter" }
* assertEquals(test(str), true);
* assertEquals(extractYaml(str), {
* frontMatter: "module: front_matter",
* body: "deno is awesome",
* attrs: { module: "front_matter" }
* });
* ```
*
* {@linkcode extractYaml | extract} and {@linkcode test} support the following

View File

@ -4,6 +4,16 @@
/**
* Functions for HTML tasks such as escaping or unescaping HTML entities.
*
* ```ts
* import { escape } from "@std/html/entities";
*
* escape("<>'&AA"); // "&lt;&gt;&#39;&amp;AA"
*
* // Characters that don't need to be escaped will be left alone,
* // even if named HTML entities exist for them.
* escape("þð"); // "þð"
* ```
*
* @module
*/

View File

@ -49,7 +49,7 @@
* For example to integrate the user agent provided in the header `User-Agent`
* in an http request would look like this:
*
* ```ts
* ```ts no-eval
* import { UserAgent } from "@std/http/user-agent";
*
* Deno.serve((req) => {

View File

@ -8,8 +8,7 @@
*
* This module is browser compatible.
*
* @example
* ```ts Parsing JSONC
* ```ts
* import { parse } from "@std/jsonc";
*
* parse('{"foo": "bar", } // comment'); // { foo: "bar" }

View File

@ -3,10 +3,30 @@
// This module is browser compatible.
/**
* The semantic version parser.
* The Semantic Version parser.
*
* Adapted directly from {@link https://github.com/npm/node-semver | semver}.
*
* ```ts
* import {
* parse,
* parseRange,
* greaterThan,
* lessThan,
* format
* } from "@std/semver";
*
* const semver = parse("1.2.3");
* const range = parseRange("1.x || >=2.5.0 || 5.0.0 - 7.2.3");
*
* const s0 = parse("1.2.3");
* const s1 = parse("9.8.7");
* greaterThan(s0, s1); // false
* lessThan(s0, s1); // true
*
* format(semver) // "1.2.3"
* ```
*
* ## Versions
*
* A "version" is described by the `v2.0.0` specification found at
@ -242,26 +262,7 @@
*
* This module is browser compatible.
*
* @example
* ```ts
* import {
* parse,
* parseRange,
* greaterThan,
* lessThan,
* format
* } from "@std/semver";
*
* const semver = parse("1.2.3");
* const range = parseRange("1.x || >=2.5.0 || 5.0.0 - 7.2.3");
*
* const s0 = parse("1.2.3");
* const s1 = parse("9.8.7");
* greaterThan(s0, s1); // false
* lessThan(s0, s1); // true
*
* format(semver) // "1.2.3"
* ```
*
* @module
*/

View File

@ -5,6 +5,16 @@
*
* Includes buffering and conversion.
*
* ```ts
* import { toText } from "@std/streams";
* import { assertEquals } from "@std/assert/assert-equals";
*
* const stream = ReadableStream.from("Hello, world!");
* const text = await toText(stream);
*
* assertEquals(text, "Hello, world!");
* ```
*
* @module
*/

View File

@ -2,7 +2,6 @@
// This module is browser compatible.
/**
* @module
* Utility functions for working with text.
*
* There are various functions for manipulating text, such as `toCamelCase`:
@ -17,13 +16,17 @@
*
* ```ts
* import { compareSimilarity } from "@std/text/compare-similarity";
* const words = ["hi", "hello", "help"];
* import { assertEquals } from "@std/assert/assert-equals";
*
* // words most-similar to "hep" will be at the front
* words.sort(compareSimilarity("hep"));
* const words = ["hi", "help", "hello"];
*
* // Words most similar to "hep" will be at the front
* assertEquals(words.sort(compareSimilarity("hep")), ["help", "hi", "hello"]);
* ```
*
* This module is browser compatible.
*
* @module
*/
export * from "./levenshtein_distance.ts";

View File

@ -90,7 +90,6 @@
*
* This module is browser compatible.
*
* @example
* ```ts
* import {
* parse,

View File

@ -4,6 +4,22 @@
* Utilities for interacting with the
* {@link https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API | WebGPU API}.
*
* ```ts no-eval
* import { createTextureWithData } from "@std/webgpu";
*
* const adapter = await navigator.gpu.requestAdapter();
* const device = await adapter?.requestDevice()!;
*
* createTextureWithData(device, {
* format: "bgra8unorm-srgb",
* size: {
* width: 3,
* height: 2,
* },
* usage: GPUTextureUsage.COPY_SRC,
* }, new Uint8Array([1, 1, 1, 1, 1, 1, 1]));
* ```
*
* @module
*/