Skip to content

Commit

Permalink
feat: Support for specifying comments on export declarations
Browse files Browse the repository at this point in the history
Also soft-deprecates the use of `@packageDocumentation` to mark a comment as a module comment. Use `@module` instead.

Resolves #1504.
  • Loading branch information
Gerrit0 committed Feb 15, 2021
1 parent 1ff431b commit 7b7bf66
Show file tree
Hide file tree
Showing 14 changed files with 241 additions and 73 deletions.
3 changes: 0 additions & 3 deletions examples/basic/src/single-export.ts
Expand Up @@ -46,7 +46,4 @@ class SingleExportedClass {
}
}

/**
* The export statement.
*/
export = SingleExportedClass;
26 changes: 24 additions & 2 deletions src/lib/converter/context.ts
Expand Up @@ -70,6 +70,19 @@ export class Context {
this.convertingTypeNode = true;
}

/**
* This is a horrible hack to avoid breaking backwards compatibility for plugins
* that use EVENT_CREATE_DECLARATION. The comment plugin needs to be able to check
* this to properly get the comment for module re-exports:
* ```ts
* /** We should use this comment */
* export * as Mod from "./mod"
* ```
* Will be removed in 0.21.
* @internal
*/
exportSymbol?: ts.Symbol;

private convertingTypeNode = false;

/**
Expand Down Expand Up @@ -169,22 +182,31 @@ export class Context {
createDeclarationReflection(
kind: ReflectionKind,
symbol: ts.Symbol | undefined,
name = getHumanName(symbol?.name ?? "unknown")
exportSymbol: ts.Symbol | undefined,
// We need this because modules don't always have symbols.
nameOverride?: string
) {
const name = getHumanName(
nameOverride ?? exportSymbol?.name ?? symbol?.name ?? "unknown"
);
const reflection = new DeclarationReflection(name, kind, this.scope);
this.addChild(reflection);
if (symbol && this.converter.isExternal(symbol)) {
reflection.setFlag(ReflectionFlag.External);
}
if (exportSymbol) {
this.registerReflection(reflection, exportSymbol);
}
this.registerReflection(reflection, symbol);

this.exportSymbol = exportSymbol;
this.converter.trigger(
ConverterEvents.CREATE_DECLARATION,
this,
reflection,
// FIXME this isn't good enough.
symbol && this.converter.getNodesForSymbol(symbol, kind)[0]
);
this.exportSymbol = undefined;

return reflection;
}
Expand Down
1 change: 1 addition & 0 deletions src/lib/converter/converter.ts
Expand Up @@ -320,6 +320,7 @@ export class Converter extends ChildableComponent<
const reflection = context.createDeclarationReflection(
ReflectionKind.Module,
symbol,
void 0,
entryName
);
moduleContext = context.withScope(reflection);
Expand Down
34 changes: 30 additions & 4 deletions src/lib/converter/factories/comment.ts
Expand Up @@ -119,13 +119,26 @@ export function getRawComment(node: ts.Node): string | undefined {
} else {
node = getRootModuleDeclaration(<ts.ModuleDeclaration>node);
}
} else if (node.kind === ts.SyntaxKind.NamespaceExport) {
node = node.parent;
} else if (node.kind === ts.SyntaxKind.ExportSpecifier) {
node = node.parent.parent;
}

const sourceFile = node.getSourceFile();
const comments = getJSDocCommentRanges(node, sourceFile.text);
if (comments.length) {
let comment: ts.CommentRange;
const explicitPackageComment = comments.find((comment) =>
let explicitPackageComment = comments.find((comment) =>
sourceFile.text
.substring(comment.pos, comment.end)
.includes("@module")
);

// TODO: Deprecate and remove. This is an abuse of the @packageDocumentation tag. See:
// https://github.com/TypeStrong/typedoc/issues/1504#issuecomment-775842609
// Deprecate in 0.21, remove in 0.22
explicitPackageComment ??= comments.find((comment) =>
sourceFile.text
.substring(comment.pos, comment.end)
.includes("@packageDocumentation")
Expand All @@ -138,20 +151,33 @@ export function getRawComment(node: ts.Node): string | undefined {
// FUTURE: GH#1083, follow deprecation process to phase this out.
comment = comments[0];
} else {
// Single comment that may be a license comment, bail.
// Single comment that may be a license comment, or no comments, bail.
return;
}
} else {
comment = comments[comments.length - 1];
// If a non-SourceFile node comment has this tag, it should not be attached to the node
// as it documents the whole file by convention.
// TODO: Deprecate and remove. This is an abuse of the @packageDocumentation tag. See:
// https://github.com/TypeStrong/typedoc/issues/1504#issuecomment-775842609
// Deprecate in 0.21, remove in 0.22
if (
sourceFile.text
.substring(comment.pos, comment.end)
.includes("@packageDocumentation")
) {
return;
}

// If a non-SourceFile node comment has this tag, it should not be attached to the node
// as it documents the module.
if (
sourceFile.text
.substring(comment.pos, comment.end)
.includes("@module")
) {
return;
}
}

return sourceFile.text.substring(comment.pos, comment.end);
Expand All @@ -164,8 +190,8 @@ export function getRawComment(node: ts.Node): string | undefined {
* Parse the given doc comment string.
*
* @param text The doc comment string that should be parsed.
* @param comment The [[Models.Comment]] instance the parsed results should be stored into.
* @returns A populated [[Models.Comment]] instance.
* @param comment The {@link Comment} instance the parsed results should be stored into.
* @returns A populated {@link Comment} instance.
*/
export function parseComment(
text: string,
Expand Down
14 changes: 7 additions & 7 deletions src/lib/converter/jsdoc.ts
Expand Up @@ -23,20 +23,20 @@ export function convertJsDocAlias(
context: Context,
symbol: ts.Symbol,
declaration: ts.JSDocTypedefTag | ts.JSDocEnumTag,
nameOverride?: string
exportSymbol?: ts.Symbol
) {
if (
declaration.typeExpression &&
ts.isJSDocTypeLiteral(declaration.typeExpression)
) {
convertJsDocInterface(context, declaration, symbol, nameOverride);
convertJsDocInterface(context, declaration, symbol, exportSymbol);
return;
}

const reflection = context.createDeclarationReflection(
ReflectionKind.TypeAlias,
symbol,
nameOverride
exportSymbol
);

reflection.type = context.converter.convertType(
Expand All @@ -54,12 +54,12 @@ export function convertJsDocCallback(
context: Context,
symbol: ts.Symbol,
declaration: ts.JSDocCallbackTag,
nameOverride?: string
exportSymbol?: ts.Symbol
) {
const alias = context.createDeclarationReflection(
ReflectionKind.TypeAlias,
symbol,
nameOverride
exportSymbol
);
const ac = context.withScope(alias);

Expand All @@ -71,12 +71,12 @@ function convertJsDocInterface(
context: Context,
declaration: ts.JSDocTypedefTag | ts.JSDocEnumTag,
symbol: ts.Symbol,
nameOverride?: string
exportSymbol?: ts.Symbol
) {
const reflection = context.createDeclarationReflection(
ReflectionKind.Interface,
symbol,
nameOverride
exportSymbol
);
const rc = context.withScope(reflection);

Expand Down
30 changes: 19 additions & 11 deletions src/lib/converter/plugins/CommentPlugin.ts
Expand Up @@ -107,6 +107,7 @@ export class CommentPlugin extends ConverterComponent {
) ||
reflection.kind === ReflectionKind.Project
) {
comment.removeTags("module");
comment.removeTags("packagedocumentation");
}
}
Expand Down Expand Up @@ -158,33 +159,40 @@ export class CommentPlugin extends ConverterComponent {
* @param node The node that is currently processed if available.
*/
private onDeclaration(
_context: Context,
context: Context,
reflection: Reflection,
node?: ts.Node
) {
if (!node) {
return;
}
if (reflection.kindOf(ReflectionKind.FunctionOrMethod)) {
return;
}
const rawComment = getRawComment(node);

// Clean this up in 0.21. We should really accept a ts.Symbol so we don't need exportSymbol on Context
const exportNode = context.exportSymbol?.getDeclarations()?.[0];
let rawComment = exportNode && getRawComment(exportNode);
rawComment ??= node && getRawComment(node);
if (!rawComment) {
return;
}

const comment = parseComment(rawComment, reflection.comment);
this.applyModifiers(reflection, comment);
this.removeExcludedTags(comment);
reflection.comment = comment;

if (reflection.kindOf(ReflectionKind.Module)) {
const tag = reflection.comment?.getTag("module");
const tag = comment.getTag("module");
if (tag) {
reflection.name = tag.text.trim();
removeIfPresent(reflection.comment?.tags, tag);
// If no name is specified, this is a flag to mark a comment as a module comment
// and should not result in a reflection rename.
const newName = tag.text.trim();
if (newName.length) {
reflection.name = newName;
}
removeIfPresent(comment.tags, tag);
}
}

this.applyModifiers(reflection, comment);
this.removeExcludedTags(comment);
reflection.comment = comment;
}

/**
Expand Down

0 comments on commit 7b7bf66

Please sign in to comment.