Skip to content

Commit

Permalink
improve --entryPointStrategy Packages support
Browse files Browse the repository at this point in the history
  • Loading branch information
akphi committed Jul 2, 2022
1 parent 9750e2e commit 4fb7b6e
Show file tree
Hide file tree
Showing 10 changed files with 183 additions and 24 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Unreleased

### Features

- Improved support for `--entryPointStrategy Packages`. TypeDoc will now load package-specific configurations from `package.json` `typedoc` field. This configuration allows configuring a custom display name (`typedoc.displayName`) field, entry point (`typedoc.entryPoint` - this is equivalent and will override `typedocMain`), and path to a readme file to be rendered at the top of the package page (`typedoc.readmeFile`), #1658.
- The `--includeVersion` option will now be respected by `--entryPointStrategy Packages`. Also, for this combination, missing `version` field in the root `package.json` will not issue a warning.

## v0.23.5 (2022-07-02)

### Features
Expand Down
40 changes: 33 additions & 7 deletions src/lib/converter/converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ProjectReflection, ReflectionKind, SomeType } from "../models/index";
import { Context } from "./context";
import { ConverterComponent } from "./components";
import { Component, ChildableComponent } from "../utils/component";
import { BindOption } from "../utils";
import { BindOption, MinimalSourceFile, readFile } from "../utils";
import { convertType } from "./types";
import { ConverterEvents } from "./converter-events";
import { convertSymbol } from "./symbols";
Expand All @@ -15,6 +15,8 @@ import { hasAllFlags, hasAnyFlag } from "../utils/enum";
import type { DocumentationEntryPoint } from "../utils/entry-point";
import { CommentParserConfig, getComment } from "./comments";
import type { CommentStyle } from "../utils/options/declaration";
import { parseComment } from "./comments/parser";
import { lexCommentString } from "./comments/rawLexer";

/**
* Compiles source files using TypeScript and converts compiler symbols to reflections.
Expand Down Expand Up @@ -201,9 +203,8 @@ export class Converter extends ChildableComponent<
context.setActiveProgram(e.entryPoint.program);
e.context = this.convertExports(
context,
e.entryPoint.sourceFile,
entries.length === 1,
e.entryPoint.displayName
e.entryPoint,
entries.length === 1
);
});
for (const { entryPoint, context } of entries) {
Expand All @@ -218,10 +219,11 @@ export class Converter extends ChildableComponent<

private convertExports(
context: Context,
node: ts.SourceFile,
singleEntryPoint: boolean,
entryName: string
entryPoint: DocumentationEntryPoint,
singleEntryPoint: boolean
) {
const node = entryPoint.sourceFile;
const entryName = entryPoint.displayName;
const symbol = getSymbolForModuleLike(context, node);
let moduleContext: Context;

Expand Down Expand Up @@ -259,6 +261,30 @@ export class Converter extends ChildableComponent<
void 0,
entryName
);

if (entryPoint.readmeFile) {
const readme = readFile(entryPoint.readmeFile);
const comment = parseComment(
lexCommentString(readme),
context.converter.config,
new MinimalSourceFile(readme, entryPoint.readmeFile),
context.logger
);

if (comment.blockTags.length || comment.modifierTags.size) {
const ignored = [
...comment.blockTags.map((tag) => tag.tag),
...comment.modifierTags,
];
context.logger.warn(
`Block and modifier tags will be ignored within the readme:\n\t${ignored.join(
"\n\t"
)}`
);
}

reflection.readme = comment.summary;
}
context.finalizeDeclarationReflection(reflection);
moduleContext = context.withScope(reflection);
}
Expand Down
9 changes: 9 additions & 0 deletions src/lib/converter/plugins/LinkResolverPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
CommentDisplayPart,
InlineTagDisplayPart,
} from "../../models/comments";
import { DeclarationReflection } from "../../models";

const urlPrefix = /^(http|ftp)s?:\/\//;
const brackets = /\[\[([^\]]+)\]\]/g;
Expand Down Expand Up @@ -136,6 +137,14 @@ export class LinkResolverPlugin extends ConverterComponent {
for (const tag of comment.blockTags) {
tag.content = this.processParts(reflection, tag.content, warn);
}

if (reflection instanceof DeclarationReflection && reflection.readme) {
reflection.readme = this.processParts(
reflection,
reflection.readme,
warn
);
}
}

private processParts(
Expand Down
17 changes: 13 additions & 4 deletions src/lib/converter/plugins/PackagePlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as FS from "fs";
import { Component, ConverterComponent } from "../components";
import { Converter } from "../converter";
import type { Context } from "../context";
import { BindOption, readFile } from "../../utils";
import { BindOption, EntryPointStrategy, readFile } from "../../utils";
import { getCommonDirectory } from "../../utils/fs";
import { nicePath } from "../../utils/paths";
import { lexCommentString } from "../comments/rawLexer";
Expand All @@ -23,6 +23,9 @@ export class PackagePlugin extends ConverterComponent {
@BindOption("includeVersion")
includeVersion!: boolean;

@BindOption("entryPointStrategy")
entryPointStrategy!: EntryPointStrategy;

/**
* The file name of the found readme.md file.
*/
Expand Down Expand Up @@ -140,9 +143,15 @@ export class PackagePlugin extends ConverterComponent {
if (packageInfo.version) {
project.name = `${project.name} - v${packageInfo.version}`;
} else {
context.logger.warn(
"--includeVersion was specified, but package.json does not specify a version."
);
// since not all monorepo specifies a meaningful version to the main package.json
// this warning should be optional
if (
this.entryPointStrategy !== EntryPointStrategy.Packages
) {
context.logger.warn(
"--includeVersion was specified, but package.json does not specify a version."
);
}
}
}
} else {
Expand Down
6 changes: 6 additions & 0 deletions src/lib/models/reflections/declaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ContainerReflection } from "./container";
import type { SignatureReflection } from "./signature";
import type { TypeParameterReflection } from "./type-parameter";
import type { Serializer, JSONOutput } from "../../serialization";
import type { CommentDisplayPart } from "../comments";

/**
* Stores hierarchical type data.
Expand Down Expand Up @@ -129,6 +130,11 @@ export class DeclarationReflection extends ContainerReflection {
*/
typeHierarchy?: DeclarationHierarchy;

/**
* The contents of the readme.md file of the module when found.
*/
readme?: CommentDisplayPart[];

override hasGetterOrSetter(): boolean {
return !!this.getSignature || !!this.setSignature;
}
Expand Down
23 changes: 17 additions & 6 deletions src/lib/output/themes/default/partials/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { classNames, wbr } from "../../lib";
import { classNames, displayPartsToMarkdown, wbr } from "../../lib";
import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext";
import { JSX } from "../../../../utils";
import type { ContainerReflection, ReflectionCategory } from "../../../../models";
import { JSX, Raw } from "../../../../utils";
import { ContainerReflection, DeclarationReflection, ReflectionCategory, ReflectionKind } from "../../../../models";

function renderCategory({ urlTo, icons }: DefaultThemeRenderContext, item: ReflectionCategory, prependName = "") {
return (
Expand Down Expand Up @@ -67,8 +67,19 @@ export function index(context: DefaultThemeRenderContext, props: ContainerReflec
}

return (
<section class="tsd-panel-group tsd-index-group">
<section class="tsd-panel tsd-index-panel">{content}</section>
</section>
<>
{props instanceof DeclarationReflection &&
props.kind === ReflectionKind.Module &&
props.readme?.length !== 0 && (
<section class="tsd-panel-group tsd-index-group">
<section class="tsd-panel tsd-typography">
<Raw html={context.markdown(displayPartsToMarkdown(props.readme || [], context.urlTo))} />
</section>
</section>
)}
<section class="tsd-panel-group tsd-index-group">
<section class="tsd-panel tsd-index-panel">{content}</section>
</section>
</>
);
}
12 changes: 7 additions & 5 deletions src/lib/output/themes/default/templates/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import type { PageEvent } from "../../../events";
import { JSX, Raw } from "../../../../utils";
import { displayPartsToMarkdown } from "../../lib";

export const indexTemplate = ({ markdown, urlTo }: DefaultThemeRenderContext, props: PageEvent<ProjectReflection>) => (
<div class="tsd-panel tsd-typography">
<Raw html={markdown(displayPartsToMarkdown(props.model.readme || [], urlTo))} />
</div>
);
export const indexTemplate = ({ markdown, urlTo }: DefaultThemeRenderContext, props: PageEvent<ProjectReflection>) => {
return (
<div class="tsd-panel tsd-typography">
<Raw html={markdown(displayPartsToMarkdown(props.model.readme || [], urlTo))} />
</div>
);
};
24 changes: 23 additions & 1 deletion src/lib/utils/entry-point.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { join, relative, resolve } from "path";
import * as ts from "typescript";
import * as FS from "fs";
import * as Path from "path";
import {
expandPackages,
extractTypedocConfigFromPackageManifest,
getTsEntryPointForPackage,
ignorePackage,
loadPackageManifest,
Expand Down Expand Up @@ -39,6 +41,7 @@ export type EntryPointStrategy =

export interface DocumentationEntryPoint {
displayName: string;
readmeFile?: string;
program: ts.Program;
sourceFile: ts.SourceFile;
}
Expand Down Expand Up @@ -321,6 +324,10 @@ function getEntryPointsForPackages(
for (const packagePath of expandedPackages) {
const packageJsonPath = resolve(packagePath, "package.json");
const packageJson = loadPackageManifest(logger, packageJsonPath);
const includeVersion = options.getValue("includeVersion");
const typedocPackageConfig = packageJson
? extractTypedocConfigFromPackageManifest(logger, packageJsonPath)
: undefined;
if (packageJson === undefined) {
logger.error(`Could not load package manifest ${packageJsonPath}`);
return;
Expand Down Expand Up @@ -384,7 +391,22 @@ function getEntryPointsForPackages(
}

results.push({
displayName: packageJson["name"] as string,
displayName:
typedocPackageConfig?.displayName ??
`${packageJson["name"]}${
includeVersion && packageJson["version"]
? `@${packageJson["version"]}`
: ""
}`,
readmeFile: typedocPackageConfig?.readmeFile
? Path.resolve(
Path.join(
packageJsonPath,
"..",
typedocPackageConfig?.readmeFile
)
)
: undefined,
program,
sourceFile,
});
Expand Down
56 changes: 55 additions & 1 deletion src/lib/utils/package-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,54 @@ export function loadPackageManifest(
return packageJson as Record<string, unknown>;
}

export interface TypedocPackageManifestConfig {
entryPoint?: string | undefined;
displayName?: string | undefined;
readmeFile?: string | undefined;
}

/**
* Extracts typedoc specific config from a specified package manifest
*/
export function extractTypedocConfigFromPackageManifest(
logger: Logger,
packageJsonPath: string
): TypedocPackageManifestConfig | undefined {
const packageJson = loadPackageManifest(logger, packageJsonPath);
if (!packageJson) {
return undefined;
}
if (
hasOwnProperty(packageJson, "typedoc") &&
typeof packageJson.typedoc == "object" &&
packageJson.typedoc
) {
// TODO: we should consider another less manual approach in the future,
// e.g. uses a library to deserialize config from raw JSON
const config: TypedocPackageManifestConfig = packageJson.typedoc;
if (
hasOwnProperty(config, "entryPoint") &&
typeof config.entryPoint !== "string"
) {
logger.error(
`Typedoc config extracted from package manifest file ${packageJsonPath} is malformed: field 'entryPoint' is not a string`
);
return undefined;
}
if (
hasOwnProperty(config, "displayName") &&
typeof config.displayName !== "string"
) {
logger.error(
`Typedoc config extracted from package manifest file ${packageJsonPath} is malformed: field 'displayName' is not a string`
);
return undefined;
}
return config;
}
return undefined;
}

/**
* Load the paths to packages specified in a Yarn workspace package JSON
* Returns undefined if packageJSON does not define a Yarn workspace
Expand Down Expand Up @@ -205,7 +253,13 @@ export function getTsEntryPointForPackage(
): string | undefined | typeof ignorePackage {
let packageMain = "index.js"; // The default, per the npm docs.
let packageTypes = null;
if (
const typedocPackageConfig = extractTypedocConfigFromPackageManifest(
logger,
packageJsonPath
);
if (typedocPackageConfig?.entryPoint) {
packageMain = typedocPackageConfig.entryPoint;
} else if (
hasOwnProperty(packageJson, "typedocMain") &&
typeof packageJson.typedocMain == "string"
) {
Expand Down
15 changes: 15 additions & 0 deletions src/test/packages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,19 @@ describe("Packages support", () => {
});
project.addJsonFile("packages/baz/tsconfig.json", childTsconfig);

// Bay, entry point with "typedoc.entryPoint"
project.addFile("packages/bay/dist/index.js", "module.exports = 123");
project.addFile("packages/bay/index.ts", "export function foo() {}");
project.addJsonFile("packages/bay/package.json", {
name: "typedoc-multi-package-bay",
version: "1.0.0",
main: "dist/index",
typedoc: {
entryPoint: "index.ts",
},
});
project.addJsonFile("packages/bay/tsconfig.json", childTsconfig);

// Foo, entry point with "typedocMain"
project.addFile("packages/foo/dist/index.js", "module.exports = 123");
project.addFile("packages/foo/index.ts", "export function foo() {}");
Expand Down Expand Up @@ -98,6 +111,7 @@ describe("Packages support", () => {
packages,
[
join(project.cwd, "packages/bar"),
join(project.cwd, "packages/bay"),
join(project.cwd, "packages/baz"),
join(project.cwd, "packages/foo"),
].map(normalizePath)
Expand All @@ -114,6 +128,7 @@ describe("Packages support", () => {

equal(entries, [
join(project.cwd, "packages/bar/index.d.ts"),
join(project.cwd, "packages/bay/index.ts"),
join(project.cwd, "packages/baz/index.ts"),
join(project.cwd, "packages/foo/index.ts"),
]);
Expand Down

0 comments on commit 4fb7b6e

Please sign in to comment.