Skip to content

Commit

Permalink
Add placeInternalsInOwningModule option
Browse files Browse the repository at this point in the history
Resolves #16
  • Loading branch information
Gerrit0 committed Jan 14, 2024
1 parent 92dc820 commit 498b720
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 26 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
### 2.1.1 (2024-01-14)
### 2.2.0 (2024-01-14)

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

### 2.1.0 (2023-08-25)

Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ Automatically document symbols which aren't exported but are referenced.

> Supports TypeDoc 0.24.x and 0.25.x
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.
TypeDoc looks at each entry point provided and documents all exports from that entry point.

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).

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`.
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`.

### Usage

Expand All @@ -20,6 +20,7 @@ npx typedoc --plugin typedoc-plugin-missing-exports
### Options

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

### Additional Reading

Expand Down
65 changes: 44 additions & 21 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ import {
Reflection,
DeclarationReflection,
ProjectReflection,
ParameterType,
ContainerReflection,
} from "typedoc";

declare module "typedoc" {
export interface TypeDocOptionMap {
internalModule: string;
placeInternalsInOwningModule: boolean;
}

export interface Reflection {
Expand Down Expand Up @@ -64,12 +67,6 @@ export function load(app: Application) {
const origCreateSymbolReference = ReferenceType.createSymbolReference;
ReferenceType.createSymbolReference = function (symbol, context, name) {
const owningModule = getOwningModule(context);
console.log(
"Created ref",
symbol.name,
"owner",
owningModule.getFullName(),
);
const set = referencedSymbols.get(context.program);
symbolToOwningModule.set(symbol, owningModule);
if (set) {
Expand All @@ -82,10 +79,28 @@ export function load(app: Application) {

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

app.options.addDeclaration({
name: "placeInternalsInOwningModule",
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.",
defaultValue: false,
type: ParameterType.Boolean,
});

app.converter.on(Converter.EVENT_BEGIN, () => {
if (
app.options.getValue("placeInternalsInOwningModule") &&
app.options.isSet("internalModule")
) {
app.logger.warn(
`[typedoc-plugin-missing-exports] Both placeInternalsInOwningModule and internalModule are set, the internalModule option will be ignored.`,
);
}
});

app.converter.on(
Converter.EVENT_CREATE_DECLARATION,
(context: Context, refl: Reflection) => {
Expand Down Expand Up @@ -115,17 +130,22 @@ export function load(app: Application) {
// Nasty hack here that will almost certainly break in future TypeDoc versions.
context.setActiveProgram(program);

const internalNs = context
.withScope(mod)
.createDeclarationReflection(
ReflectionKind.Module,
void 0,
void 0,
context.converter.application.options.getValue("internalModule"),
);
internalNs[InternalModule] = true;
context.finalizeDeclarationReflection(internalNs);
const internalContext = context.withScope(internalNs);
let internalContext: Context;
if (app.options.getValue("placeInternalsInOwningModule")) {
internalContext = context.withScope(mod);
} else {
const internalNs = context
.withScope(mod)
.createDeclarationReflection(
ReflectionKind.Module,
void 0,
void 0,
app.options.getValue("internalModule"),
);
internalNs[InternalModule] = true;
context.finalizeDeclarationReflection(internalNs);
internalContext = context.withScope(internalNs);
}

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

// All the missing symbols were excluded, so get rid of our namespace.
if (!internalNs.children?.length) {
context.project.removeReflection(internalNs);
// If we added a module and all the missing symbols were excluded, get rid of our namespace.
if (
internalContext.scope[InternalModule] &&
!(internalContext.scope as ContainerReflection).children?.length
) {
context.project.removeReflection(internalContext.scope);
}

context.setActiveProgram(void 0);
Expand Down
49 changes: 47 additions & 2 deletions test/packages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ import {
Application,
ContainerReflection,
LogLevel,
Logger,
Reflection,
ReflectionKind,
TSConfigReader,
} from "typedoc";
import { test, expect, beforeAll } from "vitest";
import { test, expect, beforeAll, afterEach } from "vitest";
import { load } from "../index.js";

let app: Application;
let program: ts.Program;
let logger: TestLogger;

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

class TestLogger extends Logger {
messages: string[] = [];
log(message: string, level: LogLevel): void {
if (level === LogLevel.Verbose) return;
this.messages.push(`${LogLevel[level]}: ${message}`);
}

expectMessage(message: string) {
expect(this.messages).toContain(message);
this.messages.splice(this.messages.indexOf(message), 1);
}
}

beforeAll(async () => {
app = await Application.bootstrap(
{
Expand All @@ -48,6 +63,7 @@ beforeAll(async () => {
},
[new TSConfigReader()],
);
app.logger = logger = new TestLogger();
load(app);

program = ts.createProgram(
Expand All @@ -56,6 +72,14 @@ beforeAll(async () => {
);
});

afterEach(() => {
app.options.reset("internalModule");
app.options.reset("placeInternalsInOwningModule");

expect(logger.messages).toEqual([]);
logger.messages = [];
});

function convert(...paths: string[]) {
const entries = paths.map((path) => {
return {
Expand Down Expand Up @@ -167,7 +191,28 @@ test("Issue #22", () => {
test("Custom module name", () => {
app.options.setValue("internalModule", "internals");
const project = convert("single-missing-export/index.ts");
app.options.reset("internalModule");

expect(project.children?.map((c) => c.name)).toEqual(["internals", "foo"]);
});

test("Disabling <internals> module, #16", () => {
app.options.setValue("placeInternalsInOwningModule", true);
const project = convert("single-missing-export/index.ts");

const hierarchy = outdent`
Type alias FooType
Function foo
`;

expect(toStringHierarchy(project)).toBe(hierarchy);
});

test("Disabling <internals> module but internalModule is set gives warning", () => {
app.options.setValue("placeInternalsInOwningModule", true);
app.options.setValue("internalModule", "internals");
convert("single-missing-export/index.ts");

logger.expectMessage(
"Warn: [typedoc-plugin-missing-exports] Both placeInternalsInOwningModule and internalModule are set, the internalModule option will be ignored.",
);
});

0 comments on commit 498b720

Please sign in to comment.