Skip to content

Commit 498b720

Browse files
committed
Add placeInternalsInOwningModule option
Resolves #16
1 parent 92dc820 commit 498b720

File tree

4 files changed

+96
-26
lines changed

4 files changed

+96
-26
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
### 2.1.1 (2024-01-14)
1+
### 2.2.0 (2024-01-14)
22

33
- Fixed an issue where if a re-exported symbol referenced an internal symbol, and more than one entry point was provided to TypeDoc,
44
this plugin would add the internal symbol to the last module, rather than the one it was associated with, #22.
5+
- Added `--placeInternalsInOwningModule` option.
56

67
### 2.1.0 (2023-08-25)
78

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ Automatically document symbols which aren't exported but are referenced.
44

55
> Supports TypeDoc 0.24.x and 0.25.x
66
7-
TypeDoc 0.20 switched from documenting each file individually to documenting based on entry points. TypeDoc looks at each provided entry point and documents all exports from that entry point.
7+
TypeDoc looks at each entry point provided and documents all exports from that entry point.
88

99
For libraries which export their full exposed API, this works well, but some packages are extremely resistant to exporting everything. This plugin is for them. After TypeDoc has finished converting packages, it will look for types which are referenced, but not exported, and place them into an internal module for that entry point (called `<internal>` by default).
1010

11-
If your project references classes which are built into the language (e.g. `HTMLElement`), this package _will_ result in those types being documented to. If you want to prevent this, set TypeDoc's `excludeExternals` option to `true`. The default pattern for determining if a symbol is external will exclude everything within `node_modules`.
11+
If your project references classes which are built into the language (e.g. `HTMLElement`), this package _will_ result in those types being documented too. If you want to prevent this, set TypeDoc's `excludeExternals` option to `true`. The default pattern for determining if a symbol is external will exclude everything within `node_modules`.
1212

1313
### Usage
1414

@@ -20,6 +20,7 @@ npx typedoc --plugin typedoc-plugin-missing-exports
2020
### Options
2121

2222
- `internalModule` - Define the name of the module that internal symbols which are not exported should be placed into.
23+
- `placeInternalsInOwningModule` - Disable creating a module for internal symbols, and instead place them into the referencing module
2324

2425
### Additional Reading
2526

index.ts

+44-21
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ import {
88
Reflection,
99
DeclarationReflection,
1010
ProjectReflection,
11+
ParameterType,
12+
ContainerReflection,
1113
} from "typedoc";
1214

1315
declare module "typedoc" {
1416
export interface TypeDocOptionMap {
1517
internalModule: string;
18+
placeInternalsInOwningModule: boolean;
1619
}
1720

1821
export interface Reflection {
@@ -64,12 +67,6 @@ export function load(app: Application) {
6467
const origCreateSymbolReference = ReferenceType.createSymbolReference;
6568
ReferenceType.createSymbolReference = function (symbol, context, name) {
6669
const owningModule = getOwningModule(context);
67-
console.log(
68-
"Created ref",
69-
symbol.name,
70-
"owner",
71-
owningModule.getFullName(),
72-
);
7370
const set = referencedSymbols.get(context.program);
7471
symbolToOwningModule.set(symbol, owningModule);
7572
if (set) {
@@ -82,10 +79,28 @@ export function load(app: Application) {
8279

8380
app.options.addDeclaration({
8481
name: "internalModule",
85-
help: "Define the name of the module that internal symbols which are not exported should be placed into.",
82+
help: "[typedoc-plugin-missing-exports] Define the name of the module that internal symbols which are not exported should be placed into.",
8683
defaultValue: "<internal>",
8784
});
8885

86+
app.options.addDeclaration({
87+
name: "placeInternalsInOwningModule",
88+
help: "[typedoc-plugin-missing-exports] If set internal symbols will not be placed into an internals module, but directly into the module which references them.",
89+
defaultValue: false,
90+
type: ParameterType.Boolean,
91+
});
92+
93+
app.converter.on(Converter.EVENT_BEGIN, () => {
94+
if (
95+
app.options.getValue("placeInternalsInOwningModule") &&
96+
app.options.isSet("internalModule")
97+
) {
98+
app.logger.warn(
99+
`[typedoc-plugin-missing-exports] Both placeInternalsInOwningModule and internalModule are set, the internalModule option will be ignored.`,
100+
);
101+
}
102+
});
103+
89104
app.converter.on(
90105
Converter.EVENT_CREATE_DECLARATION,
91106
(context: Context, refl: Reflection) => {
@@ -115,17 +130,22 @@ export function load(app: Application) {
115130
// Nasty hack here that will almost certainly break in future TypeDoc versions.
116131
context.setActiveProgram(program);
117132

118-
const internalNs = context
119-
.withScope(mod)
120-
.createDeclarationReflection(
121-
ReflectionKind.Module,
122-
void 0,
123-
void 0,
124-
context.converter.application.options.getValue("internalModule"),
125-
);
126-
internalNs[InternalModule] = true;
127-
context.finalizeDeclarationReflection(internalNs);
128-
const internalContext = context.withScope(internalNs);
133+
let internalContext: Context;
134+
if (app.options.getValue("placeInternalsInOwningModule")) {
135+
internalContext = context.withScope(mod);
136+
} else {
137+
const internalNs = context
138+
.withScope(mod)
139+
.createDeclarationReflection(
140+
ReflectionKind.Module,
141+
void 0,
142+
void 0,
143+
app.options.getValue("internalModule"),
144+
);
145+
internalNs[InternalModule] = true;
146+
context.finalizeDeclarationReflection(internalNs);
147+
internalContext = context.withScope(internalNs);
148+
}
129149

130150
// Keep track of which symbols we've tried to convert. If they don't get converted
131151
// when calling convertSymbol, then the user has excluded them somehow, don't go into
@@ -146,9 +166,12 @@ export function load(app: Application) {
146166
}
147167
} while (missing.size > 0);
148168

149-
// All the missing symbols were excluded, so get rid of our namespace.
150-
if (!internalNs.children?.length) {
151-
context.project.removeReflection(internalNs);
169+
// If we added a module and all the missing symbols were excluded, get rid of our namespace.
170+
if (
171+
internalContext.scope[InternalModule] &&
172+
!(internalContext.scope as ContainerReflection).children?.length
173+
) {
174+
context.project.removeReflection(internalContext.scope);
152175
}
153176

154177
context.setActiveProgram(void 0);

test/packages.test.ts

+47-2
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@ import {
55
Application,
66
ContainerReflection,
77
LogLevel,
8+
Logger,
89
Reflection,
910
ReflectionKind,
1011
TSConfigReader,
1112
} from "typedoc";
12-
import { test, expect, beforeAll } from "vitest";
13+
import { test, expect, beforeAll, afterEach } from "vitest";
1314
import { load } from "../index.js";
1415

1516
let app: Application;
1617
let program: ts.Program;
18+
let logger: TestLogger;
1719

1820
function toStringHierarchy(refl: Reflection, indent = 0) {
1921
const text: string[] = [];
@@ -38,6 +40,19 @@ function toStringHierarchy(refl: Reflection, indent = 0) {
3840
return text.join("\n");
3941
}
4042

43+
class TestLogger extends Logger {
44+
messages: string[] = [];
45+
log(message: string, level: LogLevel): void {
46+
if (level === LogLevel.Verbose) return;
47+
this.messages.push(`${LogLevel[level]}: ${message}`);
48+
}
49+
50+
expectMessage(message: string) {
51+
expect(this.messages).toContain(message);
52+
this.messages.splice(this.messages.indexOf(message), 1);
53+
}
54+
}
55+
4156
beforeAll(async () => {
4257
app = await Application.bootstrap(
4358
{
@@ -48,6 +63,7 @@ beforeAll(async () => {
4863
},
4964
[new TSConfigReader()],
5065
);
66+
app.logger = logger = new TestLogger();
5167
load(app);
5268

5369
program = ts.createProgram(
@@ -56,6 +72,14 @@ beforeAll(async () => {
5672
);
5773
});
5874

75+
afterEach(() => {
76+
app.options.reset("internalModule");
77+
app.options.reset("placeInternalsInOwningModule");
78+
79+
expect(logger.messages).toEqual([]);
80+
logger.messages = [];
81+
});
82+
5983
function convert(...paths: string[]) {
6084
const entries = paths.map((path) => {
6185
return {
@@ -167,7 +191,28 @@ test("Issue #22", () => {
167191
test("Custom module name", () => {
168192
app.options.setValue("internalModule", "internals");
169193
const project = convert("single-missing-export/index.ts");
170-
app.options.reset("internalModule");
171194

172195
expect(project.children?.map((c) => c.name)).toEqual(["internals", "foo"]);
173196
});
197+
198+
test("Disabling <internals> module, #16", () => {
199+
app.options.setValue("placeInternalsInOwningModule", true);
200+
const project = convert("single-missing-export/index.ts");
201+
202+
const hierarchy = outdent`
203+
Type alias FooType
204+
Function foo
205+
`;
206+
207+
expect(toStringHierarchy(project)).toBe(hierarchy);
208+
});
209+
210+
test("Disabling <internals> module but internalModule is set gives warning", () => {
211+
app.options.setValue("placeInternalsInOwningModule", true);
212+
app.options.setValue("internalModule", "internals");
213+
convert("single-missing-export/index.ts");
214+
215+
logger.expectMessage(
216+
"Warn: [typedoc-plugin-missing-exports] Both placeInternalsInOwningModule and internalModule are set, the internalModule option will be ignored.",
217+
);
218+
});

0 commit comments

Comments
 (0)