forked from mozilla/sphinx-js
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a typedoc plugin to resolve References to private type aliases
If we have a private type alias that appears in a public function, it generates a broken XRef. This replaces these type references with a reflection containing the original contents. It's a bit involved, we had to add a typedoc plugin based on `typedoc-plugin-missing-exports` to create the missing Reflections
- Loading branch information
Showing
9 changed files
with
264 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
/** | ||
* This is very heavily inspired by typedoc-plugin-missing-exports. | ||
* | ||
* The goal isn't to document the missing exports, but rather to remove them | ||
* from the documentation of actually exported stuff. If someone says: | ||
* | ||
* ``` | ||
* type MyPrivateAlias = ... | ||
* | ||
* function f(a: MyPrivateAlias) { | ||
* | ||
* } | ||
* ``` | ||
* | ||
* Then the documentation for f should document the value of MyPrivateAlias. We | ||
* create a ReflectionType for each missing export and stick them in a | ||
* SymbolToType map which we add to the application. In renderType.ts, if we | ||
* have a reference type we check if it's in the SymbolToType map and if so we | ||
* can use the reflection in place of the reference. | ||
* | ||
* More or less unrelatedly, we also add the --sphinxJsConfig option to the | ||
* options parser so we can pass the sphinxJsConfig on the command line. | ||
*/ | ||
import { | ||
Application, | ||
Context, | ||
Converter, | ||
DeclarationReflection, | ||
ProjectReflection, | ||
ReferenceType, | ||
Reflection, | ||
ReflectionKind, | ||
SomeType, | ||
} from "typedoc"; | ||
import ts from "typescript"; | ||
|
||
type SymbolToTypeKey = `${string}:${number}` | `${string}:${string}`; | ||
export type SymbolToType = Map<SymbolToTypeKey, SomeType>; | ||
export type ReadonlySymbolToType = ReadonlyMap<SymbolToTypeKey, SomeType>; | ||
|
||
const ModuleLike: ReflectionKind = | ||
ReflectionKind.Project | ReflectionKind.Module; | ||
|
||
function getOwningModule(context: Context): Reflection { | ||
let refl = context.scope; | ||
// Go up the reflection hierarchy until we get to a module | ||
while (!refl.kindOf(ModuleLike)) { | ||
refl = refl.parent!; | ||
} | ||
|
||
return refl; | ||
} | ||
|
||
export function redirectPrivateTypes(app: Application): ReadonlySymbolToType { | ||
const referencedSymbols = new Map<ts.Program, Set<ts.Symbol>>(); | ||
const symbolToOwningModule = new Map<ts.Symbol, Reflection>(); | ||
const knownPrograms = new Map<Reflection, ts.Program>(); | ||
const symbolToType: SymbolToType = new Map<`${string}:${number}`, SomeType>(); | ||
|
||
app.converter.on( | ||
Converter.EVENT_CREATE_DECLARATION, | ||
(context: Context, refl: Reflection) => { | ||
if (refl.kindOf(ModuleLike)) { | ||
knownPrograms.set(refl, context.program); | ||
} | ||
}, | ||
); | ||
|
||
function discoverMissingExports( | ||
owningModule: Reflection, | ||
context: Context, | ||
program: ts.Program, | ||
): Set<ts.Symbol> { | ||
// An export is missing if if was referenced and is not contained in the | ||
// documented | ||
const referenced = referencedSymbols.get(program) || new Set(); | ||
const ownedByOther = new Set<ts.Symbol>(); | ||
referencedSymbols.set(program, ownedByOther); | ||
|
||
for (const s of [...referenced]) { | ||
const refl = context.project.getReflectionFromSymbol(s); | ||
if (refl && !refl.flags.isPrivate) { | ||
// Already documented | ||
referenced.delete(s); | ||
} else if (symbolToOwningModule.get(s) !== owningModule) { | ||
referenced.delete(s); | ||
ownedByOther.add(s); | ||
} | ||
} | ||
|
||
return referenced; | ||
} | ||
|
||
const origCreateSymbolReference = ReferenceType.createSymbolReference; | ||
ReferenceType.createSymbolReference = function (symbol, context, name) { | ||
const owningModule = getOwningModule(context); | ||
const set = referencedSymbols.get(context.program); | ||
symbolToOwningModule.set(symbol, owningModule); | ||
if (set) { | ||
set.add(symbol); | ||
} else { | ||
referencedSymbols.set(context.program, new Set([symbol])); | ||
} | ||
return origCreateSymbolReference.call(this, symbol, context, name); | ||
}; | ||
|
||
function onResolveBegin(context: Context) { | ||
const modules: (DeclarationReflection | ProjectReflection)[] = | ||
context.project.getChildrenByKind(ReflectionKind.Module); | ||
if (modules.length === 0) { | ||
// Single entry point, just target the project. | ||
modules.push(context.project); | ||
} | ||
|
||
for (const mod of modules) { | ||
const program = knownPrograms.get(mod); | ||
if (!program) continue; | ||
|
||
// Nasty hack here that will almost certainly break in future TypeDoc versions. | ||
context.setActiveProgram(program); | ||
|
||
let missing = discoverMissingExports(mod, context, program); | ||
for (const name of missing) { | ||
const decl = name.declarations![0]; | ||
if (decl.getSourceFile().fileName.includes("node_modules")) { | ||
continue; | ||
} | ||
// TODO: maybe handle things other than TypeAliases? | ||
if (ts.isTypeAliasDeclaration(decl)) { | ||
const sf = decl.getSourceFile(); | ||
const fileName = sf.fileName; | ||
const pos = decl.pos; | ||
const converted = context.converter.convertType(context, decl.type); | ||
// Depending on whether we have a symbolId or a reflection in | ||
// renderType we'll use different keys to look this up. | ||
symbolToType.set(`${fileName}:${pos}`, converted); | ||
// Ideally we should be able to key on position rather than file and | ||
// name when the reflection is present but I couldn't figure out how. | ||
symbolToType.set(`${fileName}:${decl.name.getText()}`, converted); | ||
} | ||
} | ||
context.setActiveProgram(void 0); | ||
} | ||
} | ||
|
||
app.converter.on(Converter.EVENT_RESOLVE_BEGIN, onResolveBegin); | ||
return symbolToType; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/** Declare some extra stuff we monkeypatch on to typedoc */ | ||
|
||
declare module "typedoc" { | ||
export interface TypeDocOptionMap { | ||
sphinxJsConfig: string; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/** | ||
* Typedoc plugin which adds --sphinxJsConfig option | ||
*/ | ||
import { Application, ParameterType } from "typedoc"; | ||
|
||
export function load(app: Application) { | ||
app.options.addDeclaration({ | ||
name: "sphinxJsConfig", | ||
help: "[typedoc-plugin-sphinx-js]: the sphinx-js config", | ||
type: ParameterType.String, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.