Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve support for --entryPointStrategy Packages #1977

Merged
merged 4 commits into from Jul 7, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
@@ -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
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
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
27 changes: 20 additions & 7 deletions src/lib/converter/plugins/PackagePlugin.ts
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
Gerrit0 marked this conversation as resolved.
Show resolved Hide resolved
) {
context.logger.warn(
"--includeVersion was specified, but package.json does not specify a version."
);
}
}
}
} else {
Expand All @@ -153,9 +162,13 @@ export class PackagePlugin extends ConverterComponent {
project.name = "Documentation";
}
if (this.includeVersion) {
context.logger.warn(
"--includeVersion was specified, but no package.json was found. Not adding package version to the documentation."
);
// 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 no package.json was found. Not adding package version to the documentation."
);
}
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/lib/models/reflections/declaration.ts
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[];
Gerrit0 marked this conversation as resolved.
Show resolved Hide resolved

override hasGetterOrSetter(): boolean {
return !!this.getSignature || !!this.setSignature;
}
Expand Down
23 changes: 17 additions & 6 deletions src/lib/output/themes/default/partials/index.tsx
@@ -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">
<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>
</>
);
}
25 changes: 24 additions & 1 deletion src/lib/utils/entry-point.ts
@@ -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,23 @@ function getEntryPointsForPackages(
}

results.push({
displayName: packageJson["name"] as string,
displayName:
typedocPackageConfig?.displayName ??
// if displayName is not configured, use the package name (and version, if configured)
`${packageJson["name"]}${
akphi marked this conversation as resolved.
Show resolved Hide resolved
includeVersion && packageJson["version"]
? ` - v${packageJson["version"]}`
: ""
}`,
readmeFile: typedocPackageConfig?.readmeFile
akphi marked this conversation as resolved.
Show resolved Hide resolved
? Path.resolve(
Path.join(
packageJsonPath,
"..",
typedocPackageConfig?.readmeFile
)
)
: undefined,
program,
sourceFile,
});
Expand Down
53 changes: 52 additions & 1 deletion src/lib/utils/package-manifest.ts
Expand Up @@ -7,6 +7,7 @@ import { readFile, glob } from "./fs";
import type { Logger } from "./loggers";
import type { IMinimatch } from "minimatch";
import { matchesAny } from "./paths";
import { additionalProperties, Infer, optional, validate } from "./validation";

/**
* Helper for the TS type system to understand hasOwnProperty
Expand Down Expand Up @@ -36,6 +37,47 @@ export function loadPackageManifest(
return packageJson as Record<string, unknown>;
}

const typedocPackageManifestConfigSchema = {
displayName: optional(String),
entryPoint: optional(String),
readmeFile: optional(String),

[additionalProperties]: false,
};

export type TypedocPackageManifestConfig = Infer<
typeof typedocPackageManifestConfigSchema
>;

/**
* 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
) {
if (
!validate(typedocPackageManifestConfigSchema, packageJson.typedoc)
) {
logger.error(
`Typedoc config extracted from package manifest file ${packageJsonPath} is not valid`
);
return undefined;
}
return packageJson.typedoc;
}
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,10 +247,19 @@ 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") &&
Gerrit0 marked this conversation as resolved.
Show resolved Hide resolved
typeof packageJson.typedocMain == "string"
) {
logger.warn(
`Legacy typedoc entry point config (using "typedocMain" field) found for "${packageJsonPath}". Please update to use "typedoc": { "entryPoint": "..." } instead. In future upgrade, "typedocMain" field will be ignored.`
akphi marked this conversation as resolved.
Show resolved Hide resolved
);
packageMain = packageJson.typedocMain;
} else if (
hasOwnProperty(packageJson, "main") &&
Expand Down