UNPKG

typedoc

Version:

Create api documentation for TypeScript projects.

173 lines (172 loc) 6.8 kB
import { resolve, join, dirname } from "path"; import ts from "typescript"; import { isFile } from "../../fs.js"; import { ok } from "assert"; import { additionalProperties, isTagString, optional, validate, } from "../../validation.js"; import { nicePath, normalizePath } from "../../paths.js"; import { createRequire } from "module"; import { tsdocBlockTags, tsdocInlineTags, tsdocModifierTags, } from "../tsdoc-defaults.js"; import { unique } from "../../array.js"; import { findTsConfigFile, getTypeDocOptionsFromTsConfig, readTsConfig, } from "../../tsconfig.js"; function isSupportForTags(obj) { return (validate({}, obj) && Object.entries(obj).every(([key, val]) => { return (/^@[a-zA-Z][a-zA-Z0-9]*$/.test(key) && typeof val === "boolean"); })); } const tsDocSchema = { $schema: optional(String), extends: optional([Array, String]), noStandardTags: optional(Boolean), tagDefinitions: optional([ Array, { tagName: isTagString, syntaxKind: ["inline", "block", "modifier"], allowMultiple: optional(Boolean), [additionalProperties]: false, }, ]), supportForTags: optional(isSupportForTags), // The official parser has code to support for these two, but // the schema doesn't allow them... just silently ignore them for now. supportedHtmlElements: optional({}), reportUnsupportedHtmlElements: optional(Boolean), [additionalProperties]: false, }; export class TSConfigReader { /** * Note: Runs after the {@link TypeDocReader}. */ order = 200; name = "tsconfig-json"; supportsPackages = true; seenTsdocPaths = new Set(); read(container, logger, cwd) { const file = container.getValue("tsconfig") || cwd; let fileToRead = findTsConfigFile(file); if (!fileToRead) { // If the user didn't give us this option, we shouldn't complain about not being able to find it. if (container.isSet("tsconfig")) { logger.error(logger.i18n.tsconfig_file_0_does_not_exist(nicePath(file))); } return; } fileToRead = normalizePath(resolve(fileToRead)); logger.verbose(`Reading tsconfig at ${nicePath(fileToRead)}`); this.addTagsFromTsdocJson(container, logger, resolve(fileToRead)); const parsed = readTsConfig(fileToRead, logger); if (!parsed) { return; } logger.diagnostics(parsed.errors); if (parsed.errors.length) { return; } const typedocOptions = getTypeDocOptionsFromTsConfig(fileToRead); if (typedocOptions.options) { logger.error(logger.i18n.tsconfig_file_specifies_options_file()); delete typedocOptions.options; } if (typedocOptions.tsconfig) { logger.error(logger.i18n.tsconfig_file_specifies_tsconfig_file()); delete typedocOptions.tsconfig; } container.setCompilerOptions(parsed.fileNames, parsed.options, parsed.projectReferences); for (const [key, val] of Object.entries(typedocOptions || {})) { try { // We catch the error, so can ignore the strict type checks container.setValue(key, val, join(fileToRead, "..")); } catch (error) { ok(error instanceof Error); logger.error(error.message); } } } addTagsFromTsdocJson(container, logger, tsconfig) { this.seenTsdocPaths.clear(); const tsdoc = join(dirname(tsconfig), "tsdoc.json"); if (!isFile(tsdoc)) { return; } const overwritten = ["blockTags", "inlineTags", "modifierTags"].filter((opt) => container.isSet(opt)); if (overwritten.length) { logger.warn(logger.i18n.tags_0_defined_in_typedoc_json_overwritten_by_tsdoc_json(overwritten.join(", "))); } const config = this.readTsDoc(logger, tsdoc); if (!config) return; const supported = (tag) => { return config.supportForTags ? !!config.supportForTags[tag.tagName] : true; }; const blockTags = []; const inlineTags = []; const modifierTags = []; if (!config.noStandardTags) { blockTags.push(...tsdocBlockTags); inlineTags.push(...tsdocInlineTags); modifierTags.push(...tsdocModifierTags); } for (const { tagName, syntaxKind } of config.tagDefinitions?.filter(supported) || []) { const arr = { block: blockTags, inline: inlineTags, modifier: modifierTags, }[syntaxKind]; arr.push(tagName); } container.setValue("blockTags", unique(blockTags)); container.setValue("inlineTags", unique(inlineTags)); container.setValue("modifierTags", unique(modifierTags)); } readTsDoc(logger, path) { if (this.seenTsdocPaths.has(path)) { logger.error(logger.i18n.circular_reference_extends_0(nicePath(path))); return; } this.seenTsdocPaths.add(path); const { config, error } = ts.readConfigFile(normalizePath(path), ts.sys.readFile); if (error) { logger.error(logger.i18n.failed_read_tsdoc_json_0(nicePath(path))); return; } if (!validate(tsDocSchema, config)) { logger.error(logger.i18n.invalid_tsdoc_json_0(nicePath(path))); return; } const workingConfig = {}; if (config.extends) { const resolver = createRequire(path); for (const extendedPath of config.extends) { let resolvedPath; try { resolvedPath = resolver.resolve(extendedPath); } catch { logger.error(logger.i18n.failed_resolve_0_to_file_in_1(extendedPath, nicePath(path))); return; } const parentConfig = this.readTsDoc(logger, resolvedPath); if (!parentConfig) return; mergeConfigs(parentConfig, workingConfig); } } mergeConfigs(config, workingConfig); return workingConfig; } } function mergeConfigs(from, into) { if (from.supportForTags) { into.supportForTags ||= {}; Object.assign(into.supportForTags, from.supportForTags); } if (from.tagDefinitions) { into.tagDefinitions ||= []; into.tagDefinitions.push(...from.tagDefinitions); } into.noStandardTags = from.noStandardTags ?? into.noStandardTags; }