Skip to content

Commit

Permalink
Support ESM config files
Browse files Browse the repository at this point in the history
Closes #2268

Co-Authored-By: Connor Prussin <connor@prussin.net>
  • Loading branch information
Gerrit0 and cprussin committed Aug 25, 2023
1 parent 76c918c commit 5c977ae
Show file tree
Hide file tree
Showing 21 changed files with 367 additions and 246 deletions.
1 change: 0 additions & 1 deletion .config/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
../src/test/renderer/specs
../src/test/renderer/testProject
../src/test/utils/options/readers/data/invalid2.json
../src/test/converter2/behavior/constTypeParam.ts
../static/main.js
../example/docs/
**/tmp
Expand Down
4 changes: 0 additions & 4 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@
"static/main.js",
"src/lib/output/themes/default/assets",
"node_modules",
// Temporarily ignored until eslint supports TS 4.9
"src/test/converter/class/getter-setter.ts",
"src/test/converter/variables/variable.ts",
"src/test/converter2/behavior/hiddenAccessor.ts",
// Would be nice to lint these, but they shouldn't be included in the project,
// so we need a second eslint config file.
"example",
Expand Down
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"githubprivate",
"linkcode",
"linkplain",
"nodoc",
"shiki",
"tsbuildinfo",
"tsdoc",
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
- Removed `legacy-packages` option for `--entryPointStrategy`.
- Changed default value of `--categorizeByGroup` to `false`.
- Specifying a link as the `gitRemote` is no longer supported.
- An `Application` instance must now be retrieved via `Application.bootstrap` or `Application.bootstrapWithPlugins`, #2268.
- Removed `ReflectionKind.ObjectLiteral` that was never used by TypeDoc.
- Removed deprecated members `DefaultThemeRenderContext.comment` and `DefaultThemeRenderContext.attemptExternalResolution`.

### Features

- TypeDoc config files now support options default-exported from an ESM config file, #2268.
- Added a no-results placeholder when no search results are available, #2347.
- Implemented several miscellaneous performance improvements to generate docs faster, this took the time to generate TypeDoc's
site from ~5.6 seconds to ~5.4 seconds.
Expand All @@ -36,6 +38,7 @@
### Thanks!

- @camc314
- @cprussin

## v0.24.8 (2023-06-04)

Expand Down
103 changes: 55 additions & 48 deletions scripts/rebuild_specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,48 +14,52 @@ const { basename } = require("path");

const base = path.join(__dirname, "../src/test/converter");

const app = new td.Application();
app.options.addReader(new td.TSConfigReader());
app.bootstrap({
name: "typedoc",
excludeExternals: true,
disableSources: false,
tsconfig: path.join(base, "tsconfig.json"),
externalPattern: ["**/node_modules/**"],
entryPointStrategy: td.EntryPointStrategy.Expand,
logLevel: td.LogLevel.Warn,
gitRevision: "fake",
readme: "none",
});
app.serializer.addSerializer({
priority: -1,
supports(obj) {
return obj instanceof td.SourceReference;
},
/**
* @param {td.SourceReference} ref
*/
toObject(ref, obj) {
if (obj.url) {
obj.url = `typedoc://${obj.url.substring(
obj.url.indexOf(ref.fileName),
)}`;
}
return obj;
},
});
app.serializer.addSerializer({
priority: -1,
supports(obj) {
return obj instanceof td.ProjectReflection;
},
toObject(_refl, obj) {
delete obj.packageVersion;
return obj;
},
});
async function getApp() {
const app = new td.Application();
app.options.addReader(new td.TSConfigReader());
await app.bootstrap({
name: "typedoc",
excludeExternals: true,
disableSources: false,
tsconfig: path.join(base, "tsconfig.json"),
externalPattern: ["**/node_modules/**"],
entryPointStrategy: td.EntryPointStrategy.Expand,
logLevel: td.LogLevel.Warn,
gitRevision: "fake",
readme: "none",
});
app.serializer.addSerializer({
priority: -1,
supports(obj) {
return obj instanceof td.SourceReference;
},
/**
* @param {td.SourceReference} ref
*/
toObject(ref, obj) {
if (obj.url) {
obj.url = `typedoc://${obj.url.substring(
obj.url.indexOf(ref.fileName),
)}`;
}
return obj;
},
});
app.serializer.addSerializer({
priority: -1,
supports(obj) {
return obj instanceof td.ProjectReflection;
},
toObject(_refl, obj) {
delete obj.packageVersion;
return obj;
},
});

return app;
}

/** @type {[string, () => void, () => void][]} */
/** @type {[string, (app: td.Application) => void, (app: td.Application) => void][]} */
const conversions = [
[
"specs",
Expand All @@ -68,21 +72,22 @@ const conversions = [
],
[
"specs-with-lump-categories",
() => app.options.setValue("categorizeByGroup", false),
() => app.options.setValue("categorizeByGroup", true),
(app) => app.options.setValue("categorizeByGroup", false),
(app) => app.options.setValue("categorizeByGroup", true),
],
[
"specs.nodoc",
() => app.options.setValue("excludeNotDocumented", true),
() => app.options.setValue("excludeNotDocumented", false),
(app) => app.options.setValue("excludeNotDocumented", true),
(app) => app.options.setValue("excludeNotDocumented", false),
],
];

/**
* Rebuilds the converter specs for the provided dirs.
* @param {td.Application} app
* @param {string[]} dirs
*/
function rebuildConverterTests(dirs) {
function rebuildConverterTests(app, dirs) {
const program = ts.createProgram(app.options.getFileNames(), {
...app.options.getCompilerOptions(),
noEmit: true,
Expand All @@ -100,7 +105,7 @@ function rebuildConverterTests(dirs) {
const out = path.join(fullPath, `${file}.json`);
if (fs.existsSync(out)) {
td.resetReflectionID();
before();
before(app);
const entry = getExpandedEntryPointsForPaths(
app.logger,
[fullPath],
Expand All @@ -116,7 +121,7 @@ function rebuildConverterTests(dirs) {
);

const data = JSON.stringify(serialized, null, " ") + "\n";
after();
after(app);
fs.writeFileSync(out, data);
}
}
Expand All @@ -126,8 +131,10 @@ function rebuildConverterTests(dirs) {
async function main(filter = "") {
console.log("Base directory is", base);
const dirs = await fs.promises.readdir(base, { withFileTypes: true });
const app = await getApp();

await rebuildConverterTests(
app,
dirs
.filter((dir) => {
if (!dir.isDirectory()) return false;
Expand Down
89 changes: 64 additions & 25 deletions src/lib/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@ import { Converter } from "./converter/index";
import { Renderer } from "./output/renderer";
import { Deserializer, JSONOutput, Serializer } from "./serialization";
import type { ProjectReflection } from "./models/index";
import { Logger, ConsoleLogger, loadPlugins, writeFile } from "./utils/index";
import {
Logger,
ConsoleLogger,
loadPlugins,
writeFile,
OptionsReader,
TSConfigReader,
TypeDocReader,
PackageJsonReader,
} from "./utils/index";

import {
AbstractComponent,
Expand Down Expand Up @@ -43,6 +52,19 @@ const supportedVersionMajorMinor = packageInfo.peerDependencies.typescript
.split("||")
.map((version) => version.replace(/^\s*|\.x\s*$/g, ""));

const DETECTOR = Symbol();

export function createAppForTesting(): Application {
// @ts-expect-error private constructor
return new Application(DETECTOR);
}

const DEFAULT_READERS = [
new TypeDocReader(),
new PackageJsonReader(),
new TSConfigReader(),
];

/**
* The default TypeDoc main application class.
*
Expand Down Expand Up @@ -84,9 +106,9 @@ export class Application extends ChildableComponent<
/**
* The logger that should be used to output messages.
*/
logger: Logger;
logger: Logger = new ConsoleLogger();

options: Options;
options = new Options();

/** @internal */
@BindOption("skipErrorChecking")
Expand Down Expand Up @@ -126,29 +148,36 @@ export class Application extends ChildableComponent<
/**
* Create a new TypeDoc application instance.
*/
constructor() {
private constructor(detector: typeof DETECTOR) {
if (detector !== DETECTOR) {
throw new Error(
"An application handle must be retrieved with Application.bootstrap or Application.bootstrapWithPlugins",
);
}
super(null!); // We own ourselves

this.logger = new ConsoleLogger();
this.options = new Options(this.logger);
this.converter = this.addComponent<Converter>("converter", Converter);
this.renderer = this.addComponent<Renderer>("renderer", Renderer);
}

/**
* Initialize TypeDoc, loading plugins if applicable.
*/
async bootstrapWithPlugins(
static async bootstrapWithPlugins(
options: Partial<TypeDocOptions> = {},
): Promise<void> {
this.options.reset();
this.setOptions(options, /* reportErrors */ false);
this.options.read(new Logger());
this.logger.level = this.options.getValue("logLevel");

await loadPlugins(this, this.options.getValue("plugin"));

this.bootstrap(options);
readers: readonly OptionsReader[] = DEFAULT_READERS,
): Promise<Application> {
const app = new Application(DETECTOR);
readers.forEach((r) => app.options.addReader(r));
app.options.reset();
app.setOptions(options, /* reportErrors */ false);
await app.options.read(new Logger());
app.logger.level = app.options.getValue("logLevel");

await loadPlugins(app, app.options.getValue("plugin"));

await app._bootstrap(options);
return app;
}

/**
Expand All @@ -157,16 +186,26 @@ export class Application extends ChildableComponent<
* @example
* Initialize the application with pretty-printing output disabled.
* ```ts
* const app = new Application();
* app.bootstrap({ pretty: false });
* const app = Application.bootstrap({ pretty: false });
* ```
*
* @param options - Options to set during initialization
* @param options Options to set during initialization
* @param readers Option readers to use to discover options from config files.
*/
bootstrap(options: Partial<TypeDocOptions> = {}): void {
static async bootstrap(
options: Partial<TypeDocOptions> = {},
readers: readonly OptionsReader[] = DEFAULT_READERS,
): Promise<Application> {
const app = new Application(DETECTOR);
readers.forEach((r) => app.options.addReader(r));
await app._bootstrap(options);
return app;
}

private async _bootstrap(options: Partial<TypeDocOptions>) {
this.options.reset();
this.setOptions(options, /* reportErrors */ false);
this.options.read(this.logger);
await this.options.read(this.logger);
this.setOptions(options);
this.logger.level = this.options.getValue("logLevel");

Expand Down Expand Up @@ -217,7 +256,7 @@ export class Application extends ChildableComponent<
*
* @returns An instance of ProjectReflection on success, undefined otherwise.
*/
public convert(): ProjectReflection | undefined {
public async convert(): Promise<ProjectReflection | undefined> {
const start = Date.now();
// We freeze here rather than in the Converter class since TypeDoc's tests reuse the Application
// with a few different settings.
Expand Down Expand Up @@ -527,7 +566,7 @@ export class Application extends ChildableComponent<
].join("\n");
}

private _convertPackages(): ProjectReflection | undefined {
private async _convertPackages(): Promise<ProjectReflection | undefined> {
if (!this.options.isSet("entryPoints")) {
this.logger.error(
"No entry points provided to packages mode, documentation cannot be generated.",
Expand Down Expand Up @@ -557,7 +596,7 @@ export class Application extends ChildableComponent<
const opts = origOptions.copyForPackage(dir);
// Invalid links should only be reported after everything has been merged.
opts.setValue("validation", { invalidLink: false });
opts.read(this.logger, dir);
await opts.read(this.logger, dir);
if (
opts.getValue("entryPointStrategy") ===
EntryPointStrategy.Packages
Expand All @@ -571,7 +610,7 @@ export class Application extends ChildableComponent<
}

this.options = opts;
const project = this.convert();
const project = await this.convert();
if (project) {
this.validate(project);
projects.push(
Expand Down
Loading

0 comments on commit 5c977ae

Please sign in to comment.