typedoc
Version:
Create api documentation for TypeScript projects.
138 lines (137 loc) • 5.27 kB
JavaScript
import { join, dirname, resolve } from "path";
import * as FS from "fs";
import ts from "typescript";
import { ok } from "assert";
import { nicePath, normalizePath } from "../../paths.js";
import { isFile } from "../../fs.js";
import { createRequire } from "module";
import { pathToFileURL } from "url";
/**
* Obtains option values from typedoc.json
*/
// Changes need to happen here at some point. I think we should follow ESLint's new config
// system eventually: https://eslint.org/blog/2022/08/new-config-system-part-1/
export class TypeDocReader {
/**
* Should run before the tsconfig reader so that it can specify a tsconfig file to read.
*/
order = 100;
name = "typedoc-json";
supportsPackages = true;
/**
* Read user configuration from a typedoc.json or typedoc.js configuration file.
*/
async read(container, logger, cwd) {
const path = container.getValue("options") || cwd;
const file = this.findTypedocFile(path);
if (!file) {
if (container.isSet("options")) {
logger.error(logger.i18n.options_file_0_does_not_exist(nicePath(path)));
}
return;
}
const seen = new Set();
await this.readFile(file, container, logger, seen);
}
/**
* Read the given options file + any extended files.
* @param file
* @param container
* @param logger
*/
async readFile(file, container, logger, seen) {
if (seen.has(file)) {
logger.error(logger.i18n.circular_reference_extends_0(nicePath(file)));
return;
}
seen.add(file);
let fileContent;
if (file.endsWith(".json") || file.endsWith(".jsonc")) {
const readResult = ts.readConfigFile(normalizePath(file), (path) => FS.readFileSync(path, "utf-8"));
if (readResult.error) {
logger.error(logger.i18n.failed_read_options_file_0(nicePath(file)));
return;
}
else {
fileContent = readResult.config;
}
}
else {
try {
// On Windows, we need to ensure this path is a file path.
// Or we'll get ERR_UNSUPPORTED_ESM_URL_SCHEME
const esmPath = pathToFileURL(file).toString();
fileContent = await (await import(esmPath)).default;
}
catch (error) {
logger.error(logger.i18n.failed_read_options_file_0(nicePath(file)));
logger.error(String(error instanceof Error ? error.message : error));
return;
}
}
if (typeof fileContent !== "object" || !fileContent) {
logger.error(logger.i18n.failed_read_options_file_0(nicePath(file)));
return;
}
// clone option object to avoid of property changes in re-calling this file
const data = { ...fileContent };
delete data["$schema"]; // Useful for better autocompletion, should not be read as a key.
if ("extends" in data) {
const resolver = createRequire(file);
const extended = getStringArray(data["extends"]);
for (const extendedFile of extended) {
let resolvedParent;
try {
resolvedParent = resolver.resolve(extendedFile);
}
catch {
logger.error(logger.i18n.failed_resolve_0_to_file_in_1(extendedFile, nicePath(file)));
continue;
}
await this.readFile(resolvedParent, container, logger, seen);
}
delete data["extends"];
}
for (const [key, val] of Object.entries(data)) {
try {
container.setValue(key, val, resolve(dirname(file)));
}
catch (error) {
ok(error instanceof Error);
logger.error(error.message);
}
}
}
/**
* Search for the configuration file given path
*
* @param path Path to the typedoc.(js|json) file. If path is a directory
* typedoc file will be attempted to be found at the root of this path
* @returns the typedoc.(js|json) file path or undefined
*/
findTypedocFile(path) {
path = resolve(path);
return [
path,
join(path, "typedoc.json"),
join(path, "typedoc.jsonc"),
join(path, "typedoc.config.js"),
join(path, "typedoc.config.cjs"),
join(path, "typedoc.config.mjs"),
join(path, "typedoc.js"),
join(path, "typedoc.cjs"),
join(path, "typedoc.mjs"),
join(path, ".config/typedoc.json"),
join(path, ".config/typedoc.jsonc"),
join(path, ".config/typedoc.config.js"),
join(path, ".config/typedoc.config.cjs"),
join(path, ".config/typedoc.config.mjs"),
join(path, ".config/typedoc.js"),
join(path, ".config/typedoc.cjs"),
join(path, ".config/typedoc.mjs"),
].find(isFile);
}
}
function getStringArray(arg) {
return Array.isArray(arg) ? arg.map(String) : [String(arg)];
}