typedoc
Version:
Create api documentation for TypeScript projects.
328 lines (327 loc) • 12.2 kB
JavaScript
import { isAbsolute, join, resolve } from "path";
/** @enum */
export const EmitStrategy = {
both: "both", // Emit both documentation and JS
docs: "docs", // Emit documentation, but not JS (default)
none: "none", // Emit nothing, just convert and run validation
};
/**
* Determines how TypeDoc searches for comments.
* @enum
*/
export const CommentStyle = {
JSDoc: "jsdoc",
Block: "block",
Line: "line",
All: "all",
};
export var ParameterHint;
(function (ParameterHint) {
ParameterHint[ParameterHint["File"] = 0] = "File";
ParameterHint[ParameterHint["Directory"] = 1] = "Directory";
})(ParameterHint || (ParameterHint = {}));
export var ParameterType;
(function (ParameterType) {
ParameterType[ParameterType["String"] = 0] = "String";
/**
* Resolved according to the config directory.
*/
ParameterType[ParameterType["Path"] = 1] = "Path";
ParameterType[ParameterType["Number"] = 2] = "Number";
ParameterType[ParameterType["Boolean"] = 3] = "Boolean";
ParameterType[ParameterType["Map"] = 4] = "Map";
ParameterType[ParameterType["Mixed"] = 5] = "Mixed";
ParameterType[ParameterType["Array"] = 6] = "Array";
/**
* Resolved according to the config directory.
*/
ParameterType[ParameterType["PathArray"] = 7] = "PathArray";
/**
* Resolved according to the config directory if it starts with `.`
*/
ParameterType[ParameterType["ModuleArray"] = 8] = "ModuleArray";
/**
* Resolved according to the config directory unless it starts with `**`, after skipping any leading `!` and `#` characters.
*/
ParameterType[ParameterType["GlobArray"] = 9] = "GlobArray";
/**
* An object which partially merges user-set values into the defaults.
*/
ParameterType[ParameterType["Object"] = 10] = "Object";
/**
* An object with true/false flags
*/
ParameterType[ParameterType["Flags"] = 11] = "Flags";
})(ParameterType || (ParameterType = {}));
const converters = {
[ParameterType.String](value, option, i18n) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
const stringValue = value == null ? "" : String(value);
option.validate?.(stringValue, i18n);
return stringValue;
},
[ParameterType.Path](value, option, i18n, configPath) {
const stringValue =
// eslint-disable-next-line @typescript-eslint/no-base-to-string
value == null ? "" : resolve(configPath, String(value));
option.validate?.(stringValue, i18n);
return stringValue;
},
[ParameterType.Number](value, option, i18n) {
const numValue = parseInt(String(value), 10) || 0;
if (!valueIsWithinBounds(numValue, option.minValue, option.maxValue)) {
throw new Error(getBoundsError(option.name, i18n, option.minValue, option.maxValue));
}
option.validate?.(numValue, i18n);
return numValue;
},
[ParameterType.Boolean](value) {
return !!value;
},
[ParameterType.Array](value, option, i18n) {
let strArrValue = new Array();
if (Array.isArray(value)) {
strArrValue = value.map(String);
}
else if (typeof value === "string") {
strArrValue = [value];
}
option.validate?.(strArrValue, i18n);
return strArrValue;
},
[ParameterType.PathArray](value, option, i18n, configPath) {
let strArrValue = new Array();
if (Array.isArray(value)) {
strArrValue = value.map(String);
}
else if (typeof value === "string") {
strArrValue = [value];
}
strArrValue = strArrValue.map((path) => resolve(configPath, path));
option.validate?.(strArrValue, i18n);
return strArrValue;
},
[ParameterType.ModuleArray](value, option, i18n, configPath) {
let strArrValue = new Array();
if (Array.isArray(value)) {
strArrValue = value.map(String);
}
else if (typeof value === "string") {
strArrValue = [value];
}
strArrValue = resolveModulePaths(strArrValue, configPath);
option.validate?.(strArrValue, i18n);
return strArrValue;
},
[ParameterType.GlobArray](value, option, i18n, configPath) {
let strArrValue = new Array();
if (Array.isArray(value)) {
strArrValue = value.map(String);
}
else if (typeof value === "string") {
strArrValue = [value];
}
strArrValue = resolveGlobPaths(strArrValue, configPath);
option.validate?.(strArrValue, i18n);
return strArrValue;
},
[ParameterType.Map](value, option, i18n) {
const key = String(value);
if (option.map instanceof Map) {
if (option.map.has(key)) {
return option.map.get(key);
}
else if ([...option.map.values()].includes(value)) {
return value;
}
}
else if (key in option.map) {
if (isTsNumericEnum(option.map) && typeof value === "number") {
return value;
}
return option.map[key];
}
else if (Object.values(option.map).includes(value)) {
return value;
}
throw new Error(getMapError(option.map, i18n, option.name));
},
[ParameterType.Mixed](value, option, i18n) {
option.validate?.(value, i18n);
return value;
},
[ParameterType.Object](value, option, i18n, _configPath, oldValue) {
option.validate?.(value, i18n);
if (typeof oldValue !== "undefined")
value = { ...oldValue, ...value };
return value;
},
[ParameterType.Flags](value, option, i18n) {
if (typeof value === "boolean") {
value = Object.fromEntries(Object.keys(option.defaults).map((key) => [key, value]));
}
if (typeof value !== "object" || value == null) {
throw new Error(i18n.expected_object_with_flag_values_for_0(option.name));
}
const obj = { ...value };
for (const key of Object.keys(obj)) {
if (!Object.prototype.hasOwnProperty.call(option.defaults, key)) {
throw new Error(i18n.flag_0_is_not_valid_for_1_expected_2(key, option.name, Object.keys(option.defaults).join(", ")));
}
if (typeof obj[key] !== "boolean") {
// Explicit null/undefined, switch to default.
if (obj[key] == null) {
obj[key] = option.defaults[key];
}
else {
throw new Error(i18n.flag_values_for_0_must_be_booleans(option.name));
}
}
}
return obj;
},
};
/**
* The default conversion function used by the Options container. Readers may
* re-use this conversion function or implement their own. The arguments reader
* implements its own since 'false' should not be converted to true for a boolean option.
* @param value The value to convert.
* @param option The option for which the value should be converted.
* @returns The result of the conversion. Might be the value or an error.
*/
export function convert(value, option, i18n, configPath, oldValue) {
const _converters = converters;
return _converters[option.type ?? ParameterType.String](value, option, i18n, configPath, oldValue);
}
const defaultGetters = {
[ParameterType.String](option) {
return option.defaultValue ?? "";
},
[ParameterType.Path](option) {
const defaultStr = option.defaultValue ?? "";
if (defaultStr == "") {
return "";
}
return isAbsolute(defaultStr)
? defaultStr
: join(process.cwd(), defaultStr);
},
[ParameterType.Number](option) {
return option.defaultValue ?? 0;
},
[ParameterType.Boolean](option) {
return option.defaultValue ?? false;
},
[ParameterType.Map](option) {
return option.defaultValue;
},
[ParameterType.Mixed](option) {
return option.defaultValue;
},
[ParameterType.Object](option) {
return option.defaultValue;
},
[ParameterType.Array](option) {
return option.defaultValue?.slice() ?? [];
},
[ParameterType.PathArray](option) {
return (option.defaultValue?.map((value) => resolve(process.cwd(), value)) ?? []);
},
[ParameterType.ModuleArray](option) {
return (option.defaultValue?.map((value) => value.startsWith(".") ? resolve(process.cwd(), value) : value) ?? []);
},
[ParameterType.GlobArray](option) {
return resolveGlobPaths(option.defaultValue ?? [], process.cwd());
},
[ParameterType.Flags](option) {
return { ...option.defaults };
},
};
export function getDefaultValue(option) {
const getters = defaultGetters;
return getters[option.type ?? ParameterType.String](option);
}
function resolveGlobPaths(globs, configPath) {
return globs.map((path) => {
const start = path.match(/^[!#]+/)?.[0] ?? "";
const remaining = path.substring(start.length);
if (!remaining.startsWith("**")) {
return start + resolve(configPath, remaining);
}
return start + remaining;
});
}
function resolveModulePaths(modules, configPath) {
return modules.map((path) => {
if (path.startsWith(".")) {
return resolve(configPath, path);
}
return path;
});
}
function isTsNumericEnum(map) {
return Object.values(map).every((key) => map[map[key]] === key);
}
/**
* Returns an error message for a map option, indicating that a given value was not one of the values within the map.
* @param map The values for the option.
* @param name The name of the option.
* @returns The error message.
*/
function getMapError(map, i18n, name) {
let keys = map instanceof Map ? [...map.keys()] : Object.keys(map);
// If the map is a TS numeric enum we need to filter out the numeric keys.
// TS numeric enums have the property that every key maps to a value, which maps back to that key.
if (!(map instanceof Map) && isTsNumericEnum(map)) {
// This works because TS enum keys may not be numeric.
keys = keys.filter((key) => Number.isNaN(parseInt(key, 10)));
}
return i18n.option_0_must_be_one_of_1(name, keys.join(", "));
}
/**
* Returns an error message for a value that is out of bounds of the given min and/or max values.
* @param name The name of the thing the value represents.
* @param minValue The lower bound of the range of allowed values.
* @param maxValue The upper bound of the range of allowed values.
* @returns The error message.
*/
function getBoundsError(name, i18n, minValue, maxValue) {
if (isFiniteNumber(minValue) && isFiniteNumber(maxValue)) {
return i18n.option_0_must_be_between_1_and_2(name, String(minValue), String(maxValue));
}
else if (isFiniteNumber(minValue)) {
return i18n.option_0_must_be_equal_to_or_greater_than_1(name, String(minValue));
}
else {
return i18n.option_0_must_be_less_than_or_equal_to_1(name, String(maxValue));
}
}
/**
* Checks if the given value is a finite number.
* @param value The value being checked.
* @returns True, if the value is a finite number, otherwise false.
*/
function isFiniteNumber(value) {
return Number.isFinite(value);
}
/**
* Checks if a value is between the bounds of the given min and/or max values.
* @param value The value being checked.
* @param minValue The lower bound of the range of allowed values.
* @param maxValue The upper bound of the range of allowed values.
* @returns True, if the value is within the given bounds, otherwise false.
*/
function valueIsWithinBounds(value, minValue, maxValue) {
if (isFiniteNumber(minValue) && isFiniteNumber(maxValue)) {
return minValue <= value && value <= maxValue;
}
else if (isFiniteNumber(minValue)) {
return minValue <= value;
}
else if (isFiniteNumber(maxValue)) {
return value <= maxValue;
}
else {
return true;
}
}