/** * `deno task doc:lint`: runs `deno doc --lint` over the modules given as * arguments and fails on what it reports, with one exception. `deno doc` * doesn't document npm packages, so it calls every type imported from one * private — react-hook-form's `FieldValues` in a form field's props, say — * and no change to our own documentation could satisfy it. A `private-type-ref` * whose type the module imports from a package is let through, and counted; * every other diagnostic fails the run. * * @module */ /** One diagnostic of `deno doc --lint`. */ interface Diagnostic { /** The rule, e.g. `missing-jsdoc`. */ rule: string; /** The rule's message. */ message: string; /** The module it was reported in. */ file: string; /** The diagnostic as printed, location and excerpt included. */ text: string; } /** Returns the diagnostics of `deno doc --lint` over `modules`. */ async function lint(modules: string[]): Promise { const { stderr } = await new Deno.Command(Deno.execPath(), { args: ["doc", "--lint", ...modules], env: { NO_COLOR: "1" }, stdout: "null", stderr: "piped", }).output(); const output = new TextDecoder().decode(stderr); return output.split(/\n(?=error\[)/).flatMap((text) => { const head = /^error\[([\w-]+)\]: (.*)$/m.exec(text); const at = /--> (.+?):\d+:\d+/.exec(text); if (!head || !at) return []; return [{ rule: head[1], message: head[2], file: at[1], text }]; }); } /** Checks whether module `file` imports `name` from a package. */ function importedFromPackage(file: string, name: string): boolean { const source = Deno.readTextFileSync(file); const imports = source.matchAll( /import\s+(?:type\s+)?([^;]*?)\s+from\s+"([^"]+)"/g, ); for (const [, clause, specifier] of imports) { if (specifier.startsWith(".")) continue; const names = clause.replace(/[{}]/g, " ").split(",").map((part) => part.trim().replace(/^type\s+/, "").split(/\s+as\s+/).pop()!.trim() ); if (names.includes(name)) return true; } return false; } /** Checks whether `d` is a private type from a package, which is let through. */ function isPackageTypeRef(d: Diagnostic): boolean { const type = /references private type '([\w$]+)/.exec(d.message)?.[1]; return d.rule === "private-type-ref" && !!type && importedFromPackage(d.file, type); } if (import.meta.main) { const diagnostics = await lint(Deno.args); const failures = diagnostics.filter((d) => !isPackageTypeRef(d)); for (const d of failures) console.error(d.text.trimEnd() + "\n"); const allowed = diagnostics.length - failures.length; console.error( `${failures.length} documentation lint errors` + (allowed ? `, ${allowed} references to npm packages' types let through` : ""), ); if (failures.length) Deno.exit(1); }