From 112ec5d25277ad22134d7a91202aa6306de48721 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Fri, 3 May 2024 16:05:20 +0200 Subject: [PATCH] Add more sphinxJsConfig hooks I added hooks preConvert and postConvert. preCommit receives just the typedoc app and runs right after application bootstrap. (To run earlier I think you'd need to make an actual typedoc plugin). `postConvert` runs right at the end. I also added a map from typedoc reflections to the corresponding ir item that postConvert can look at. I also added an extra_data dictionary which we pass to Python so that implementations of these hooks can pass extra information to be handled on the Pythons side. (None of the Python-side hooks can use this info yet, it can only be used via a monkeypatch.) No test coverage for any of this... --- sphinx_js/js/call_typedoc.ts | 10 ++++++++-- sphinx_js/js/convertTopLevel.ts | 6 ++++-- sphinx_js/js/redirectPrivateAliases.ts | 8 ++++++-- sphinx_js/js/sphinxJsConfig.ts | 16 ++++++++++++++-- sphinx_js/js/typedocPatches.ts | 6 +++++- sphinx_js/js/typedocPlugin.ts | 2 +- sphinx_js/typedoc.py | 17 +++++++++++------ tests/testing.py | 4 ++-- 8 files changed, 51 insertions(+), 18 deletions(-) diff --git a/sphinx_js/js/call_typedoc.ts b/sphinx_js/js/call_typedoc.ts index f64bb753..d87bacdb 100644 --- a/sphinx_js/js/call_typedoc.ts +++ b/sphinx_js/js/call_typedoc.ts @@ -55,9 +55,13 @@ async function main() { console.log(app.toString()); return ExitCodes.Ok; } + app.extraData = {}; app.options.getValue("modifierTags").push("@hidetype"); - const config = await loadConfig(app.options.getValue("sphinxJsConfig")); + const userConfigPath = app.options.getValue("sphinxJsConfig"); + const config = await loadConfig(userConfigPath); + app.logger.info(`Loaded user config from ${userConfigPath}`); const symbolToType = redirectPrivateTypes(app); + await config.preConvert?.(app); const project = await app.convert(); if (!project) { @@ -82,7 +86,9 @@ async function main() { const converter = new Converter(project, basePath, config, symbolToType); converter.computePaths(); const space = app.options.getValue("pretty") ? "\t" : ""; - const res = JSON.stringify(converter.convertAll(), null, space); + const result = converter.convertAll(); + await config.postConvert?.(app, project, converter.typedocToIRMap); + const res = JSON.stringify([result, app.extraData], null, space); const json = app.options.getValue("json"); await writeFile(json, res); app.logger.info(`JSON written to ${json}`); diff --git a/sphinx_js/js/convertTopLevel.ts b/sphinx_js/js/convertTopLevel.ts index 45269e2e..3d48de4e 100644 --- a/sphinx_js/js/convertTopLevel.ts +++ b/sphinx_js/js/convertTopLevel.ts @@ -29,8 +29,6 @@ import { TopLevel, Type, TypeParam, - TypeXRefInternal, - TypeXRefExternal, } from "./ir.ts"; import { sep, relative } from "path"; import { SphinxJsConfig } from "./sphinxJsConfig.ts"; @@ -341,6 +339,7 @@ export class Converter { Pathname >; readonly documentationRoots: Set; + readonly typedocToIRMap: Map; constructor( project: ProjectReflection, @@ -352,9 +351,11 @@ export class Converter { this.basePath = basePath; this.config = config; this.symbolToType = symbolToType; + this.pathMap = new Map(); this.filePathMap = new Map(); this.documentationRoots = new Set(); + this.typedocToIRMap = new Map(); } renderType(type: SomeType, context: TypeContext = TypeContext.none): Type { @@ -397,6 +398,7 @@ export class Converter { const node = todo.pop()!; const [converted, rest] = this.toIr(node); if (converted) { + this.typedocToIRMap.set(node, converted); result.push(converted); } todo.push(...(rest || [])); diff --git a/sphinx_js/js/redirectPrivateAliases.ts b/sphinx_js/js/redirectPrivateAliases.ts index 5b61478c..3f1209e2 100644 --- a/sphinx_js/js/redirectPrivateAliases.ts +++ b/sphinx_js/js/redirectPrivateAliases.ts @@ -101,13 +101,17 @@ export function redirectPrivateTypes(app: Application): ReadonlySymbolToType { } const origCreateSymbolReference = ReferenceType.createSymbolReference; - ReferenceType.createSymbolReference = function (symbol, context, name) { + ReferenceType.createSymbolReference = function ( + symbol: ts.Symbol, + context: Context, + name: string, + ) { const owningModule = getOwningModule(context); getReferencedSymbols(owningModule).add(symbol); return origCreateSymbolReference.call(this, symbol, context, name); }; - function onResolveBegin(context: Context) { + function onResolveBegin(context: Context): void { const modules: (DeclarationReflection | ProjectReflection)[] = context.project.getChildrenByKind(ReflectionKind.Module); if (modules.length === 0) { diff --git a/sphinx_js/js/sphinxJsConfig.ts b/sphinx_js/js/sphinxJsConfig.ts index e182bc3f..c7ca15c2 100644 --- a/sphinx_js/js/sphinxJsConfig.ts +++ b/sphinx_js/js/sphinxJsConfig.ts @@ -1,5 +1,17 @@ -import { ParameterReflection } from "typedoc"; +import { + Application, + DeclarationReflection, + ParameterReflection, + ProjectReflection, +} from "typedoc"; +import { TopLevel } from "./ir.ts"; export type SphinxJsConfig = { - shouldDestructureArg?: (p: ParameterReflection) => boolean; + shouldDestructureArg?: (param: ParameterReflection) => boolean; + preConvert?: (app: Application) => Promise; + postConvert?: ( + app: Application, + project: ProjectReflection, + typedocToIRMap: ReadonlyMap, + ) => Promise; }; diff --git a/sphinx_js/js/typedocPatches.ts b/sphinx_js/js/typedocPatches.ts index bd02cd4c..7556a607 100644 --- a/sphinx_js/js/typedocPatches.ts +++ b/sphinx_js/js/typedocPatches.ts @@ -1,7 +1,11 @@ /** Declare some extra stuff we monkeypatch on to typedoc */ - declare module "typedoc" { export interface TypeDocOptionMap { sphinxJsConfig: string; } + export interface Application { + extraData: { + [key: string]: any; + }; + } } diff --git a/sphinx_js/js/typedocPlugin.ts b/sphinx_js/js/typedocPlugin.ts index 18b99f3b..55c71e1d 100644 --- a/sphinx_js/js/typedocPlugin.ts +++ b/sphinx_js/js/typedocPlugin.ts @@ -5,7 +5,7 @@ // TODO: we don't seem to resolve imports correctly in this file, but it works // to do a dynamic import. Figure out why. -export async function load(app: any) { +export async function load(app: any): Promise { // @ts-ignore const typedoc = await import("typedoc"); app.options.addDeclaration({ diff --git a/sphinx_js/typedoc.py b/sphinx_js/typedoc.py index 5df3f37f..0af969b0 100644 --- a/sphinx_js/typedoc.py +++ b/sphinx_js/typedoc.py @@ -11,7 +11,7 @@ from operator import attrgetter from pathlib import Path from tempfile import NamedTemporaryFile -from typing import Literal +from typing import Any, Literal from sphinx.application import Sphinx from sphinx.errors import SphinxError @@ -54,7 +54,7 @@ def typedoc_output( typedoc_config_path: str | None, tsconfig_path: str | None, ts_sphinx_js_config: str | None, -) -> list[ir.TopLevelUnion]: +) -> tuple[list[ir.TopLevelUnion], dict[str, Any]]: """Return the loaded JSON output of the TypeDoc command run over the given paths.""" typedoc = search_node_modules("typedoc", "typedoc/bin/typedoc", sphinx_conf_dir) @@ -100,14 +100,19 @@ def typedoc_output( else: raise # typedoc emits a valid JSON file even if it finds no TS files in the dir: - return ir.json_to_ir(load(temp)) + json_ir, extra_data = load(temp) + return ir.json_to_ir(json_ir), extra_data class Analyzer: _objects_by_path: SuffixTree[ir.TopLevel] _modules_by_path: SuffixTree[ir.Module] + _extra_data: dict[str, Any] - def __init__(self, objects: Sequence[ir.TopLevel], base_dir: str) -> None: + def __init__( + self, objects: Sequence[ir.TopLevel], extra_data: dict[str, Any], base_dir: str + ) -> None: + self._extra_data = extra_data self._base_dir = base_dir self._objects_by_path = SuffixTree() self._objects_by_path.add_many((obj.path.segments, obj) for obj in objects) @@ -130,7 +135,7 @@ def get_object( def from_disk( cls, abs_source_paths: Sequence[str], app: Sphinx, base_dir: str ) -> "Analyzer": - json = typedoc_output( + json, extra_data = typedoc_output( abs_source_paths, base_dir=base_dir, sphinx_conf_dir=app.confdir, @@ -138,7 +143,7 @@ def from_disk( tsconfig_path=app.config.jsdoc_tsconfig_path, ts_sphinx_js_config=app.config.ts_sphinx_js_config, ) - return cls(json, base_dir) + return cls(json, extra_data, base_dir) def _get_toplevel_objects( self, ir_objects: Sequence[ir.TopLevel] diff --git a/tests/testing.py b/tests/testing.py index 8949e12a..5ad64990 100644 --- a/tests/testing.py +++ b/tests/testing.py @@ -82,7 +82,7 @@ def setup_class(cls): config_file = Path(__file__).parent / "sphinxJsConfig.ts" - cls.json = typedoc_output( + [cls.json, cls.extra_data] = typedoc_output( abs_source_paths=[join(cls._source_dir, file) for file in cls.files], base_dir=cls._source_dir, ts_sphinx_js_config=str(config_file), @@ -103,7 +103,7 @@ def setup_class(cls): def should_destructure(sig, p): return p.name == "destructureThisPlease" - cls.analyzer = TsAnalyzer(cls.json, cls._source_dir) + cls.analyzer = TsAnalyzer(cls.json, cls.extra_data, cls._source_dir) NO_MATCH = object()